summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am173
-rw-r--r--lib/Makefile.in1052
-rw-r--r--lib/backend/db3.c617
-rw-r--r--lib/backend/dbconfig.c268
-rw-r--r--lib/backend/dbi.h270
-rw-r--r--lib/cpio.c245
-rw-r--r--lib/cpio.h133
-rw-r--r--lib/depends.c596
-rw-r--r--lib/formats.c705
-rw-r--r--lib/fprint.c365
-rw-r--r--lib/fprint.h170
-rw-r--r--lib/fsm.c2332
-rw-r--r--lib/fsm.h227
-rwxr-xr-xlib/gentagtbl.sh80
-rw-r--r--lib/header.c1761
-rw-r--r--lib/header.h446
-rw-r--r--lib/header_internal.h78
-rw-r--r--lib/headerfmt.c868
-rw-r--r--lib/headerutil.c245
-rw-r--r--lib/legacy.c379
-rw-r--r--lib/manifest.c175
-rw-r--r--lib/manifest.h34
-rw-r--r--lib/merge.c347
-rw-r--r--lib/misc.c24
-rw-r--r--lib/misc.h46
-rw-r--r--lib/order.c655
-rw-r--r--lib/package.c807
-rw-r--r--lib/poptALL.c316
-rw-r--r--lib/poptI.c252
-rw-r--r--lib/poptQV.c246
-rw-r--r--lib/psm.c1095
-rw-r--r--lib/query.c575
-rw-r--r--lib/rpmal.c406
-rw-r--r--lib/rpmal.h75
-rw-r--r--lib/rpmcallback.h47
-rw-r--r--lib/rpmchecksig.c469
-rw-r--r--lib/rpmchroot.c105
-rw-r--r--lib/rpmchroot.h45
-rw-r--r--lib/rpmcli.h401
-rw-r--r--lib/rpmdb.c2956
-rw-r--r--lib/rpmdb.h215
-rw-r--r--lib/rpmdb_internal.h155
-rw-r--r--lib/rpmds.c933
-rw-r--r--lib/rpmds.h333
-rw-r--r--lib/rpmfi.c1349
-rw-r--r--lib/rpmfi.h459
-rw-r--r--lib/rpmfi_internal.h172
-rw-r--r--lib/rpmfs.c120
-rw-r--r--lib/rpmfs.h63
-rw-r--r--lib/rpmgi.c241
-rw-r--r--lib/rpmgi.h65
-rw-r--r--lib/rpmhash.C272
-rw-r--r--lib/rpmhash.H145
-rw-r--r--lib/rpminstall.c735
-rw-r--r--lib/rpmlead.c141
-rw-r--r--lib/rpmlead.h82
-rw-r--r--lib/rpmlegacy.h241
-rw-r--r--lib/rpmlib.h210
-rw-r--r--lib/rpmliblua.c45
-rw-r--r--lib/rpmliblua.h18
-rw-r--r--lib/rpmlock.c125
-rw-r--r--lib/rpmlock.h22
-rw-r--r--lib/rpmplugins.c198
-rw-r--r--lib/rpmplugins.h125
-rw-r--r--lib/rpmpol.h28
-rw-r--r--lib/rpmprob.c213
-rw-r--r--lib/rpmprob.h148
-rw-r--r--lib/rpmps.c172
-rw-r--r--lib/rpmps.h110
-rw-r--r--lib/rpmrc.c1710
-rw-r--r--lib/rpmscript.c415
-rw-r--r--lib/rpmscript.h42
-rw-r--r--lib/rpmtag.h472
-rw-r--r--lib/rpmtd.c443
-rw-r--r--lib/rpmtd.h355
-rw-r--r--lib/rpmte.c927
-rw-r--r--lib/rpmte.h262
-rw-r--r--lib/rpmte_internal.h154
-rw-r--r--lib/rpmts.c1036
-rw-r--r--lib/rpmts.h582
-rw-r--r--lib/rpmts_internal.h108
-rw-r--r--lib/rpmtypes.h112
-rw-r--r--lib/rpmug.c202
-rw-r--r--lib/rpmug.h18
-rw-r--r--lib/rpmvercmp.c131
-rw-r--r--lib/rpmvf.h103
-rw-r--r--lib/signature.c534
-rw-r--r--lib/signature.h79
-rw-r--r--lib/tagexts.c705
-rw-r--r--lib/tagname.c318
-rw-r--r--lib/tagtbl.C186
-rw-r--r--lib/transaction.c1471
-rw-r--r--lib/verify.c516
93 files changed, 38102 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..5ad0d9c
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,173 @@
+# Makefile for rpm library.
+
+include $(top_srcdir)/rpm.am
+
+AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) -I$(top_builddir)/include/
+AM_CPPFLAGS += @WITH_NSS_INCLUDE@
+AM_CPPFLAGS += @WITH_POPT_INCLUDE@
+AM_CPPFLAGS += -I$(top_srcdir)/misc
+AM_CPPFLAGS += -DLOCALEDIR="\"$(localedir)\""
+AM_CPPFLAGS += -DSYSCONFDIR="\"$(sysconfdir)\""
+AM_CPPFLAGS += -DLOCALSTATEDIR="\"$(localstatedir)\""
+AM_CPPFLAGS += -DLIBRPMALIAS_FILENAME="\"rpmpopt-${VERSION}\""
+AM_CPPFLAGS += -DLIBRPMALIAS_EXECPATH="\"$(bindir)\""
+
+usrlibdir = $(libdir)
+
+check_PROGRAMS =
+CLEANFILES =
+EXTRA_DIST = gentagtbl.sh tagtbl.C rpmhash.C rpmhash.H
+EXTRA_PROGRAMS =
+
+usrlib_LTLIBRARIES = librpm.la
+librpm_la_SOURCES = \
+ backend/dbconfig.c backend/db3.c backend/dbi.h \
+ headerutil.c header.c headerfmt.c header_internal.h \
+ rpmdb.c rpmdb_internal.h \
+ fprint.c fprint.h tagname.c rpmtd.c \
+ cpio.c cpio.h depends.c order.c formats.c tagexts.c fsm.c fsm.h \
+ manifest.c manifest.h misc.c package.c \
+ poptALL.c poptI.c poptQV.c psm.c query.c \
+ rpmal.c rpmal.h rpmchecksig.c rpmds.c rpmfi.c rpmfi_internal.h \
+ rpmgi.h rpmgi.c rpminstall.c rpmts_internal.h \
+ rpmlead.c rpmlead.h rpmps.c rpmprob.c rpmrc.c \
+ rpmte.c rpmte_internal.h rpmts.c rpmfs.h rpmfs.c \
+ rpmvercmp.c signature.c signature.h transaction.c \
+ verify.c rpmlock.c rpmlock.h misc.h \
+ rpmscript.h rpmscript.c legacy.c merge.c \
+ rpmchroot.c rpmchroot.h \
+ rpmplugins.c rpmplugins.h rpmug.c rpmug.h
+
+librpm_la_LDFLAGS = -version-info 2:1:0
+
+librpm_la_LIBADD = \
+ $(top_builddir)/rpmio/librpmio.la \
+ @WITH_POPT_LIB@ \
+ @WITH_SELINUX_LIB@ \
+ @WITH_CAP_LIB@ \
+ @WITH_ACL_LIB@ \
+ @LIBINTL@
+
+if WITH_LUA
+AM_CPPFLAGS += @LUA_CFLAGS@
+librpm_la_LIBADD += @LUA_LIBS@
+librpm_la_SOURCES += rpmliblua.c rpmliblua.h
+endif
+
+if WITH_INTERNAL_DB
+librpm_la_LIBADD += $(libdb_la)
+else
+librpm_la_LIBADD += @WITH_DB_LIB@
+endif
+
+tagtbl.C: Makefile.am $(srcdir)/rpmtag.h gentagtbl.sh
+ @AWK=${AWK} ${SHELL} $(srcdir)/gentagtbl.sh \
+ $(srcdir)/rpmtag.h > $@.new && \
+ mv -f $@.new $@
+BUILT_SOURCES = tagtbl.C
+
+if WITH_INTERNAL_DB
+# XXX watchout, $(top_builddir)/db3/libdb.la created by this Makefile may surprise
+libdb_la = $(top_builddir)/db3/libdb.la
+
+# XXX grrr, force noinst libdb.la for db3.
+# there are more reliable ways to get the BDB version info, just a dirty
+# hack for now...
+BDBVER = $(shell grep ^LIBVERSION $(top_builddir)/db3/Makefile|cut -f2)
+$(libdb_la): $(top_builddir)/db3/libdb-$(BDBVER).la
+ sed -e"/^libdir=/s/^.*$$/libdir=''/" \
+ < $(top_builddir)/db3/libdb-$(BDBVER).la > $(libdb_la)
+CLEANFILES += $(libdb_la)
+
+
+rpmlibexec_PROGRAMS =
+
+rpmlibexec_PROGRAMS += rpmdb_archive
+rpmdb_archive_SOURCES =
+rpmdb_archive_LDADD = \
+ $(top_builddir)/db3/db_archive.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_checkpoint
+rpmdb_checkpoint_SOURCES =
+rpmdb_checkpoint_LDADD = \
+ $(top_builddir)/db3/db_checkpoint.o \
+ $(top_builddir)/db3/util_log.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_deadlock
+rpmdb_deadlock_SOURCES =
+rpmdb_deadlock_LDADD = \
+ $(top_builddir)/db3/db_deadlock.o \
+ $(top_builddir)/db3/util_log.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_dump
+rpmdb_dump_SOURCES =
+rpmdb_dump_LDADD = \
+ $(top_builddir)/db3/db_dump.o \
+ $(top_builddir)/db3/util_cache.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_load
+rpmdb_load_SOURCES =
+rpmdb_load_LDADD = \
+ $(top_builddir)/db3/db_load.o \
+ $(top_builddir)/db3/util_cache.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_printlog
+rpmdb_printlog_SOURCES =
+rpmdb_printlog_LDADD = \
+ $(top_builddir)/db3/db_printlog.o \
+ $(top_builddir)/db3/btree_autop.o \
+ $(top_builddir)/db3/crdel_autop.o \
+ $(top_builddir)/db3/db_autop.o \
+ $(top_builddir)/db3/dbreg_autop.o \
+ $(top_builddir)/db3/fileops_autop.o \
+ $(top_builddir)/db3/hash_autop.o \
+ $(top_builddir)/db3/qam_autop.o \
+ $(top_builddir)/db3/rep_autop.o \
+ $(top_builddir)/db3/txn_autop.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_recover
+rpmdb_recover_SOURCES =
+rpmdb_recover_LDADD = \
+ $(top_builddir)/db3/db_recover.o \
+ $(top_builddir)/db3/util_cache.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_stat
+rpmdb_stat_SOURCES =
+rpmdb_stat_LDADD = \
+ $(top_builddir)/db3/db_stat.o \
+ $(top_builddir)/db3/util_cache.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_upgrade
+rpmdb_upgrade_SOURCES =
+rpmdb_upgrade_LDADD = \
+ $(top_builddir)/db3/db_upgrade.o \
+ $(top_builddir)/db3/util_cache.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+
+rpmlibexec_PROGRAMS += rpmdb_verify
+rpmdb_verify_SOURCES =
+rpmdb_verify_LDADD = \
+ $(top_builddir)/db3/db_verify.o \
+ $(top_builddir)/db3/util_cache.o \
+ $(top_builddir)/db3/util_sig.o \
+ librpm.la
+endif
+
+CLEANFILES += $(BUILT_SOURCES)
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..220ff43
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,1052 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile for rpm library.
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \
+ $(top_srcdir)/rpm.am
+check_PROGRAMS =
+EXTRA_PROGRAMS =
+@WITH_LUA_TRUE@am__append_1 = @LUA_CFLAGS@
+@WITH_LUA_TRUE@am__append_2 = @LUA_LIBS@
+@WITH_LUA_TRUE@am__append_3 = rpmliblua.c rpmliblua.h
+@WITH_INTERNAL_DB_TRUE@am__append_4 = $(libdb_la)
+@WITH_INTERNAL_DB_FALSE@am__append_5 = @WITH_DB_LIB@
+@WITH_INTERNAL_DB_TRUE@am__append_6 = $(libdb_la)
+@WITH_INTERNAL_DB_TRUE@rpmlibexec_PROGRAMS = rpmdb_archive$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_checkpoint$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_deadlock$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_dump$(EXEEXT) rpmdb_load$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_printlog$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_recover$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_stat$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_upgrade$(EXEEXT) \
+@WITH_INTERNAL_DB_TRUE@ rpmdb_verify$(EXEEXT)
+subdir = lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/lib-ld.m4 \
+ $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__installdirs = "$(DESTDIR)$(usrlibdir)" \
+ "$(DESTDIR)$(rpmlibexecdir)"
+LTLIBRARIES = $(usrlib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+librpm_la_DEPENDENCIES = $(top_builddir)/rpmio/librpmio.la \
+ $(am__DEPENDENCIES_1) $(am__append_4) $(am__DEPENDENCIES_1)
+am__librpm_la_SOURCES_DIST = backend/dbconfig.c backend/db3.c \
+ backend/dbi.h headerutil.c header.c headerfmt.c \
+ header_internal.h rpmdb.c rpmdb_internal.h fprint.c fprint.h \
+ tagname.c rpmtd.c cpio.c cpio.h depends.c order.c formats.c \
+ tagexts.c fsm.c fsm.h manifest.c manifest.h misc.c package.c \
+ poptALL.c poptI.c poptQV.c psm.c query.c rpmal.c rpmal.h \
+ rpmchecksig.c rpmds.c rpmfi.c rpmfi_internal.h rpmgi.h rpmgi.c \
+ rpminstall.c rpmts_internal.h rpmlead.c rpmlead.h rpmps.c \
+ rpmprob.c rpmrc.c rpmte.c rpmte_internal.h rpmts.c rpmfs.h \
+ rpmfs.c rpmvercmp.c signature.c signature.h transaction.c \
+ verify.c rpmlock.c rpmlock.h misc.h rpmscript.h rpmscript.c \
+ legacy.c merge.c rpmchroot.c rpmchroot.h rpmplugins.c \
+ rpmplugins.h rpmug.c rpmug.h rpmliblua.c rpmliblua.h
+am__dirstamp = $(am__leading_dot)dirstamp
+@WITH_LUA_TRUE@am__objects_1 = rpmliblua.lo
+am_librpm_la_OBJECTS = backend/dbconfig.lo backend/db3.lo \
+ headerutil.lo header.lo headerfmt.lo rpmdb.lo fprint.lo \
+ tagname.lo rpmtd.lo cpio.lo depends.lo order.lo formats.lo \
+ tagexts.lo fsm.lo manifest.lo misc.lo package.lo poptALL.lo \
+ poptI.lo poptQV.lo psm.lo query.lo rpmal.lo rpmchecksig.lo \
+ rpmds.lo rpmfi.lo rpmgi.lo rpminstall.lo rpmlead.lo rpmps.lo \
+ rpmprob.lo rpmrc.lo rpmte.lo rpmts.lo rpmfs.lo rpmvercmp.lo \
+ signature.lo transaction.lo verify.lo rpmlock.lo rpmscript.lo \
+ legacy.lo merge.lo rpmchroot.lo rpmplugins.lo rpmug.lo \
+ $(am__objects_1)
+librpm_la_OBJECTS = $(am_librpm_la_OBJECTS)
+librpm_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(librpm_la_LDFLAGS) $(LDFLAGS) -o $@
+PROGRAMS = $(rpmlibexec_PROGRAMS)
+am_rpmdb_archive_OBJECTS =
+rpmdb_archive_OBJECTS = $(am_rpmdb_archive_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_archive_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_archive.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_checkpoint_OBJECTS =
+rpmdb_checkpoint_OBJECTS = $(am_rpmdb_checkpoint_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_checkpoint_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_checkpoint.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_log.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_deadlock_OBJECTS =
+rpmdb_deadlock_OBJECTS = $(am_rpmdb_deadlock_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_deadlock_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_deadlock.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_log.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_dump_OBJECTS =
+rpmdb_dump_OBJECTS = $(am_rpmdb_dump_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_dump_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_dump.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_load_OBJECTS =
+rpmdb_load_OBJECTS = $(am_rpmdb_load_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_load_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_load.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_printlog_OBJECTS =
+rpmdb_printlog_OBJECTS = $(am_rpmdb_printlog_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_printlog_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_printlog.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/btree_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/crdel_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/dbreg_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/fileops_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/hash_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/qam_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/rep_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/txn_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_recover_OBJECTS =
+rpmdb_recover_OBJECTS = $(am_rpmdb_recover_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_recover_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_recover.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_stat_OBJECTS =
+rpmdb_stat_OBJECTS = $(am_rpmdb_stat_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_stat_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_stat.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_upgrade_OBJECTS =
+rpmdb_upgrade_OBJECTS = $(am_rpmdb_upgrade_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_upgrade_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_upgrade.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+am_rpmdb_verify_OBJECTS =
+rpmdb_verify_OBJECTS = $(am_rpmdb_verify_OBJECTS)
+@WITH_INTERNAL_DB_TRUE@rpmdb_verify_DEPENDENCIES = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_verify.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(librpm_la_SOURCES) $(rpmdb_archive_SOURCES) \
+ $(rpmdb_checkpoint_SOURCES) $(rpmdb_deadlock_SOURCES) \
+ $(rpmdb_dump_SOURCES) $(rpmdb_load_SOURCES) \
+ $(rpmdb_printlog_SOURCES) $(rpmdb_recover_SOURCES) \
+ $(rpmdb_stat_SOURCES) $(rpmdb_upgrade_SOURCES) \
+ $(rpmdb_verify_SOURCES)
+DIST_SOURCES = $(am__librpm_la_SOURCES_DIST) $(rpmdb_archive_SOURCES) \
+ $(rpmdb_checkpoint_SOURCES) $(rpmdb_deadlock_SOURCES) \
+ $(rpmdb_dump_SOURCES) $(rpmdb_load_SOURCES) \
+ $(rpmdb_printlog_SOURCES) $(rpmdb_recover_SOURCES) \
+ $(rpmdb_stat_SOURCES) $(rpmdb_upgrade_SOURCES) \
+ $(rpmdb_verify_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOM4TE = @AUTOM4TE@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FIXPERMS = @FIXPERMS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+RPMCANONARCH = @RPMCANONARCH@
+RPMCANONCOLOR = @RPMCANONCOLOR@
+RPMCANONGNU = @RPMCANONGNU@
+RPMCANONOS = @RPMCANONOS@
+RPMCANONVENDOR = @RPMCANONVENDOR@
+RPMCONFIGDIR = @RPMCONFIGDIR@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WITH_ACL_LIB = @WITH_ACL_LIB@
+WITH_BZ2_LIB = @WITH_BZ2_LIB@
+WITH_CAP_LIB = @WITH_CAP_LIB@
+WITH_DB_LIB = @WITH_DB_LIB@
+WITH_LIBELF_LIB = @WITH_LIBELF_LIB@
+WITH_LZMA_LIB = @WITH_LZMA_LIB@
+WITH_MAGIC_INCLUDE = @WITH_MAGIC_INCLUDE@
+WITH_MAGIC_LIB = @WITH_MAGIC_LIB@
+WITH_NSS_INCLUDE = @WITH_NSS_INCLUDE@
+WITH_NSS_LIB = @WITH_NSS_LIB@
+WITH_POPT_INCLUDE = @WITH_POPT_INCLUDE@
+WITH_POPT_LIB = @WITH_POPT_LIB@
+WITH_PYTHON_INCLUDE = @WITH_PYTHON_INCLUDE@
+WITH_PYTHON_LIB = @WITH_PYTHON_LIB@
+WITH_SELINUX_LIB = @WITH_SELINUX_LIB@
+WITH_SEMANAGE_LIB = @WITH_SEMANAGE_LIB@
+WITH_ZLIB_INCLUDE = @WITH_ZLIB_INCLUDE@
+WITH_ZLIB_LIB = @WITH_ZLIB_LIB@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+__BZIP2 = @__BZIP2@
+__CAT = @__CAT@
+__CC = @__CC@
+__CHGRP = @__CHGRP@
+__CHMOD = @__CHMOD@
+__CHOWN = @__CHOWN@
+__CP = @__CP@
+__CPIO = @__CPIO@
+__CURL = @__CURL@
+__FAKECHROOT = @__FAKECHROOT@
+__FILE = @__FILE@
+__GPG = @__GPG@
+__GREP = @__GREP@
+__GZIP = @__GZIP@
+__ID = @__ID@
+__INSTALL = @__INSTALL@
+__LD = @__LD@
+__LRZIP = @__LRZIP@
+__LZIP = @__LZIP@
+__MAKE = @__MAKE@
+__MKDIR = @__MKDIR@
+__MKDIR_P = @__MKDIR_P@
+__MV = @__MV@
+__NM = @__NM@
+__OBJCOPY = @__OBJCOPY@
+__OBJDUMP = @__OBJDUMP@
+__PATCH = @__PATCH@
+__PERL = @__PERL@
+__PGP = @__PGP@
+__PYTHON = @__PYTHON@
+__RESTORECON = @__RESTORECON@
+__RM = @__RM@
+__RSH = @__RSH@
+__SED = @__SED@
+__SEMODULE = @__SEMODULE@
+__SSH = @__SSH@
+__STRIP = @__STRIP@
+__TAR = @__TAR@
+__UNZIP = @__UNZIP@
+__XZ = @__XZ@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dirstamp = @dirstamp@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+subdirs = @subdirs@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# Internal binaries
+rpmlibexecdir = $(prefix)/lib/rpm
+
+# Host independent config files
+rpmconfigdir = $(prefix)/lib/rpm
+AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) \
+ -I$(top_builddir)/include/ @WITH_NSS_INCLUDE@ \
+ @WITH_POPT_INCLUDE@ -I$(top_srcdir)/misc \
+ -DLOCALEDIR="\"$(localedir)\"" \
+ -DSYSCONFDIR="\"$(sysconfdir)\"" \
+ -DLOCALSTATEDIR="\"$(localstatedir)\"" \
+ -DLIBRPMALIAS_FILENAME="\"rpmpopt-${VERSION}\"" \
+ -DLIBRPMALIAS_EXECPATH="\"$(bindir)\"" $(am__append_1)
+usrlibdir = $(libdir)
+CLEANFILES = $(am__append_6) $(BUILT_SOURCES)
+EXTRA_DIST = gentagtbl.sh tagtbl.C rpmhash.C rpmhash.H
+usrlib_LTLIBRARIES = librpm.la
+librpm_la_SOURCES = backend/dbconfig.c backend/db3.c backend/dbi.h \
+ headerutil.c header.c headerfmt.c header_internal.h rpmdb.c \
+ rpmdb_internal.h fprint.c fprint.h tagname.c rpmtd.c cpio.c \
+ cpio.h depends.c order.c formats.c tagexts.c fsm.c fsm.h \
+ manifest.c manifest.h misc.c package.c poptALL.c poptI.c \
+ poptQV.c psm.c query.c rpmal.c rpmal.h rpmchecksig.c rpmds.c \
+ rpmfi.c rpmfi_internal.h rpmgi.h rpmgi.c rpminstall.c \
+ rpmts_internal.h rpmlead.c rpmlead.h rpmps.c rpmprob.c rpmrc.c \
+ rpmte.c rpmte_internal.h rpmts.c rpmfs.h rpmfs.c rpmvercmp.c \
+ signature.c signature.h transaction.c verify.c rpmlock.c \
+ rpmlock.h misc.h rpmscript.h rpmscript.c legacy.c merge.c \
+ rpmchroot.c rpmchroot.h rpmplugins.c rpmplugins.h rpmug.c \
+ rpmug.h $(am__append_3)
+librpm_la_LDFLAGS = -version-info 2:1:0
+librpm_la_LIBADD = $(top_builddir)/rpmio/librpmio.la @WITH_POPT_LIB@ \
+ @WITH_SELINUX_LIB@ @WITH_CAP_LIB@ @WITH_ACL_LIB@ @LIBINTL@ \
+ $(am__append_2) $(am__append_4) $(am__append_5)
+BUILT_SOURCES = tagtbl.C
+
+# XXX watchout, $(top_builddir)/db3/libdb.la created by this Makefile may surprise
+@WITH_INTERNAL_DB_TRUE@libdb_la = $(top_builddir)/db3/libdb.la
+
+# XXX grrr, force noinst libdb.la for db3.
+# there are more reliable ways to get the BDB version info, just a dirty
+# hack for now...
+@WITH_INTERNAL_DB_TRUE@BDBVER = $(shell grep ^LIBVERSION $(top_builddir)/db3/Makefile|cut -f2)
+@WITH_INTERNAL_DB_TRUE@rpmdb_archive_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_archive_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_archive.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_checkpoint_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_checkpoint_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_checkpoint.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_log.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_deadlock_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_deadlock_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_deadlock.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_log.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_dump_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_dump_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_dump.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_load_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_load_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_load.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_printlog_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_printlog_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_printlog.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/btree_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/crdel_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/dbreg_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/fileops_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/hash_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/qam_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/rep_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/txn_autop.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_recover_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_recover_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_recover.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_stat_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_stat_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_stat.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_upgrade_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_upgrade_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_upgrade.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+@WITH_INTERNAL_DB_TRUE@rpmdb_verify_SOURCES =
+@WITH_INTERNAL_DB_TRUE@rpmdb_verify_LDADD = \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/db_verify.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_cache.o \
+@WITH_INTERNAL_DB_TRUE@ $(top_builddir)/db3/util_sig.o \
+@WITH_INTERNAL_DB_TRUE@ librpm.la
+
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/rpm.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-usrlibLTLIBRARIES: $(usrlib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ test -z "$(usrlibdir)" || $(MKDIR_P) "$(DESTDIR)$(usrlibdir)"
+ @list='$(usrlib_LTLIBRARIES)'; test -n "$(usrlibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(usrlibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(usrlibdir)"; \
+ }
+
+uninstall-usrlibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(usrlib_LTLIBRARIES)'; test -n "$(usrlibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(usrlibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(usrlibdir)/$$f"; \
+ done
+
+clean-usrlibLTLIBRARIES:
+ -test -z "$(usrlib_LTLIBRARIES)" || rm -f $(usrlib_LTLIBRARIES)
+ @list='$(usrlib_LTLIBRARIES)'; for p in $$list; do \
+ dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
+ test "$$dir" != "$$p" || dir=.; \
+ echo "rm -f \"$${dir}/so_locations\""; \
+ rm -f "$${dir}/so_locations"; \
+ done
+backend/$(am__dirstamp):
+ @$(MKDIR_P) backend
+ @: > backend/$(am__dirstamp)
+backend/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) backend/$(DEPDIR)
+ @: > backend/$(DEPDIR)/$(am__dirstamp)
+backend/dbconfig.lo: backend/$(am__dirstamp) \
+ backend/$(DEPDIR)/$(am__dirstamp)
+backend/db3.lo: backend/$(am__dirstamp) \
+ backend/$(DEPDIR)/$(am__dirstamp)
+librpm.la: $(librpm_la_OBJECTS) $(librpm_la_DEPENDENCIES)
+ $(librpm_la_LINK) -rpath $(usrlibdir) $(librpm_la_OBJECTS) $(librpm_la_LIBADD) $(LIBS)
+
+clean-checkPROGRAMS:
+ @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-rpmlibexecPROGRAMS: $(rpmlibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(rpmlibexecdir)" || $(MKDIR_P) "$(DESTDIR)$(rpmlibexecdir)"
+ @list='$(rpmlibexec_PROGRAMS)'; test -n "$(rpmlibexecdir)" || list=; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p || test -f $$p1; \
+ then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(rpmlibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(rpmlibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-rpmlibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(rpmlibexec_PROGRAMS)'; test -n "$(rpmlibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(rpmlibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(rpmlibexecdir)" && rm -f $$files
+
+clean-rpmlibexecPROGRAMS:
+ @list='$(rpmlibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+rpmdb_archive$(EXEEXT): $(rpmdb_archive_OBJECTS) $(rpmdb_archive_DEPENDENCIES)
+ @rm -f rpmdb_archive$(EXEEXT)
+ $(LINK) $(rpmdb_archive_OBJECTS) $(rpmdb_archive_LDADD) $(LIBS)
+rpmdb_checkpoint$(EXEEXT): $(rpmdb_checkpoint_OBJECTS) $(rpmdb_checkpoint_DEPENDENCIES)
+ @rm -f rpmdb_checkpoint$(EXEEXT)
+ $(LINK) $(rpmdb_checkpoint_OBJECTS) $(rpmdb_checkpoint_LDADD) $(LIBS)
+rpmdb_deadlock$(EXEEXT): $(rpmdb_deadlock_OBJECTS) $(rpmdb_deadlock_DEPENDENCIES)
+ @rm -f rpmdb_deadlock$(EXEEXT)
+ $(LINK) $(rpmdb_deadlock_OBJECTS) $(rpmdb_deadlock_LDADD) $(LIBS)
+rpmdb_dump$(EXEEXT): $(rpmdb_dump_OBJECTS) $(rpmdb_dump_DEPENDENCIES)
+ @rm -f rpmdb_dump$(EXEEXT)
+ $(LINK) $(rpmdb_dump_OBJECTS) $(rpmdb_dump_LDADD) $(LIBS)
+rpmdb_load$(EXEEXT): $(rpmdb_load_OBJECTS) $(rpmdb_load_DEPENDENCIES)
+ @rm -f rpmdb_load$(EXEEXT)
+ $(LINK) $(rpmdb_load_OBJECTS) $(rpmdb_load_LDADD) $(LIBS)
+rpmdb_printlog$(EXEEXT): $(rpmdb_printlog_OBJECTS) $(rpmdb_printlog_DEPENDENCIES)
+ @rm -f rpmdb_printlog$(EXEEXT)
+ $(LINK) $(rpmdb_printlog_OBJECTS) $(rpmdb_printlog_LDADD) $(LIBS)
+rpmdb_recover$(EXEEXT): $(rpmdb_recover_OBJECTS) $(rpmdb_recover_DEPENDENCIES)
+ @rm -f rpmdb_recover$(EXEEXT)
+ $(LINK) $(rpmdb_recover_OBJECTS) $(rpmdb_recover_LDADD) $(LIBS)
+rpmdb_stat$(EXEEXT): $(rpmdb_stat_OBJECTS) $(rpmdb_stat_DEPENDENCIES)
+ @rm -f rpmdb_stat$(EXEEXT)
+ $(LINK) $(rpmdb_stat_OBJECTS) $(rpmdb_stat_LDADD) $(LIBS)
+rpmdb_upgrade$(EXEEXT): $(rpmdb_upgrade_OBJECTS) $(rpmdb_upgrade_DEPENDENCIES)
+ @rm -f rpmdb_upgrade$(EXEEXT)
+ $(LINK) $(rpmdb_upgrade_OBJECTS) $(rpmdb_upgrade_LDADD) $(LIBS)
+rpmdb_verify$(EXEEXT): $(rpmdb_verify_OBJECTS) $(rpmdb_verify_DEPENDENCIES)
+ @rm -f rpmdb_verify$(EXEEXT)
+ $(LINK) $(rpmdb_verify_OBJECTS) $(rpmdb_verify_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f backend/db3.$(OBJEXT)
+ -rm -f backend/db3.lo
+ -rm -f backend/dbconfig.$(OBJEXT)
+ -rm -f backend/dbconfig.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/depends.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formats.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fprint.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsm.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/header.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/headerfmt.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/headerutil.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/legacy.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/manifest.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/merge.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/order.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/package.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/poptALL.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/poptI.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/poptQV.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psm.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/query.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmal.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmchecksig.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmchroot.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmdb.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmds.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmfi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmfs.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmgi.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpminstall.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmlead.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmliblua.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmlock.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmplugins.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmprob.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmps.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmrc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmscript.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmtd.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmte.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmts.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmug.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmvercmp.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signature.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tagexts.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tagname.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verify.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@backend/$(DEPDIR)/db3.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@backend/$(DEPDIR)/dbconfig.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf backend/.libs backend/_libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(LTLIBRARIES) $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(usrlibdir)" "$(DESTDIR)$(rpmlibexecdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -rm -f backend/$(DEPDIR)/$(am__dirstamp)
+ -rm -f backend/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-am
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libtool \
+ clean-rpmlibexecPROGRAMS clean-usrlibLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR) backend/$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-usrlibLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-rpmlibexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR) backend/$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-rpmlibexecPROGRAMS uninstall-usrlibLTLIBRARIES
+
+.MAKE: all check check-am install install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean \
+ clean-checkPROGRAMS clean-generic clean-libtool \
+ clean-rpmlibexecPROGRAMS clean-usrlibLTLIBRARIES ctags \
+ distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-rpmlibexecPROGRAMS \
+ install-strip install-usrlibLTLIBRARIES installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-rpmlibexecPROGRAMS \
+ uninstall-usrlibLTLIBRARIES
+
+
+tagtbl.C: Makefile.am $(srcdir)/rpmtag.h gentagtbl.sh
+ @AWK=${AWK} ${SHELL} $(srcdir)/gentagtbl.sh \
+ $(srcdir)/rpmtag.h > $@.new && \
+ mv -f $@.new $@
+@WITH_INTERNAL_DB_TRUE@$(libdb_la): $(top_builddir)/db3/libdb-$(BDBVER).la
+@WITH_INTERNAL_DB_TRUE@ sed -e"/^libdir=/s/^.*$$/libdir=''/" \
+@WITH_INTERNAL_DB_TRUE@ < $(top_builddir)/db3/libdb-$(BDBVER).la > $(libdb_la)
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/backend/db3.c b/lib/backend/db3.c
new file mode 100644
index 0000000..ff55791
--- /dev/null
+++ b/lib/backend/db3.c
@@ -0,0 +1,617 @@
+/** \ingroup rpmdb
+ * \file lib/db3.c
+ */
+
+static int _debug = 1; /* XXX if < 0 debugging, > 0 unusual error returns */
+
+#include "system.h"
+
+#include <errno.h>
+#include <sys/wait.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmlog.h>
+
+#include "lib/rpmdb_internal.h"
+
+#include "debug.h"
+
+static const char * _errpfx = "rpmdb";
+
+static int dbapi_err(rpmdb rdb, const char * msg, int error, int printit)
+{
+ if (printit && error) {
+ int db_api = rdb->db_ver;
+ if (msg)
+ rpmlog(RPMLOG_ERR, _("db%d error(%d) from %s: %s\n"),
+ db_api, error, msg, db_strerror(error));
+ else
+ rpmlog(RPMLOG_ERR, _("db%d error(%d): %s\n"),
+ db_api, error, db_strerror(error));
+ }
+ return error;
+}
+
+static int cvtdberr(dbiIndex dbi, const char * msg, int error, int printit)
+{
+ return dbapi_err(dbi->dbi_rpmdb, msg, error, printit);
+}
+
+static void errlog(const DB_ENV * env, const char *errpfx, const char *msg)
+{
+ rpmlog(RPMLOG_ERR, "%s: %s\n", errpfx, msg);
+}
+
+static uint32_t db_envflags(DB * db)
+{
+ DB_ENV * env = db->get_env(db);
+ uint32_t eflags = 0;
+ (void) env->get_open_flags(env, &eflags);
+ return eflags;
+}
+
+static int db_fini(rpmdb rdb, const char * dbhome)
+{
+ DB_ENV * dbenv = rdb->db_dbenv;
+ int rc;
+ uint32_t eflags = 0;
+
+ if (dbenv == NULL)
+ return 0;
+
+ if (rdb->db_opens > 1) {
+ rdb->db_opens--;
+ return 0;
+ }
+
+ (void) dbenv->get_open_flags(dbenv, &eflags);
+ rc = dbenv->close(dbenv, 0);
+ rc = dbapi_err(rdb, "dbenv->close", rc, _debug);
+
+ rpmlog(RPMLOG_DEBUG, "closed db environment %s\n", dbhome);
+
+ if (!(eflags & DB_PRIVATE) && rdb->db_remove_env) {
+ int xx;
+
+ xx = db_env_create(&dbenv, 0);
+ xx = dbapi_err(rdb, "db_env_create", xx, _debug);
+ xx = dbenv->remove(dbenv, dbhome, 0);
+ /* filter out EBUSY as it just means somebody else gets to clean it */
+ xx = dbapi_err(rdb, "dbenv->remove", xx, (xx == EBUSY ? 0 : _debug));
+
+ rpmlog(RPMLOG_DEBUG, "removed db environment %s\n", dbhome);
+
+ }
+ return rc;
+}
+
+static int fsync_disable(int fd)
+{
+ return 0;
+}
+
+/*
+ * dbenv->failchk() callback method for determining is the given pid/tid
+ * is alive. We only care about pid's though.
+ */
+static int isalive(DB_ENV *dbenv, pid_t pid, db_threadid_t tid, uint32_t flags)
+{
+ int alive = 0;
+
+ if (pid == getpid()) {
+ alive = 1;
+ } else if (kill(pid, 0) == 0) {
+ alive = 1;
+ /* only existing processes can fail with EPERM */
+ } else if (errno == EPERM) {
+ alive = 1;
+ }
+
+ return alive;
+}
+
+static int db_init(rpmdb rdb, const char * dbhome)
+{
+ DB_ENV *dbenv = NULL;
+ int rc, xx;
+ int retry_open = 2;
+ struct _dbConfig * cfg = &rdb->cfg;
+ /* This is our setup, thou shall not have other setups before us */
+ uint32_t eflags = (DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB);
+
+ if (rdb->db_dbenv != NULL) {
+ rdb->db_opens++;
+ return 0;
+ }
+
+ /* By no means necessary but speeds things up a bit */
+ if (rdb->db_flags & RPMDB_FLAG_REBUILD)
+ eflags &= ~DB_INIT_CDB;
+ /* XXX Something bizarre with verify... use private environment, no cdb */
+ if (rdb->db_flags & RPMDB_FLAG_VERIFYONLY) {
+ eflags |= DB_PRIVATE;
+ eflags &= ~DB_INIT_CDB;
+ }
+
+ rc = db_env_create(&dbenv, 0);
+ rc = dbapi_err(rdb, "db_env_create", rc, _debug);
+ if (dbenv == NULL || rc)
+ goto errxit;
+
+ dbenv->set_alloc(dbenv, rmalloc, rrealloc, NULL);
+ dbenv->set_errcall(dbenv, NULL);
+ dbenv->set_errpfx(dbenv, _errpfx);
+
+ /*
+ * These enable automatic stale lock removal.
+ * thread_count 8 is some kind of "magic minimum" value...
+ */
+ dbenv->set_thread_count(dbenv, 8);
+ dbenv->set_isalive(dbenv, isalive);
+
+ dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK,
+ (cfg->db_verbose & DB_VERB_DEADLOCK));
+ dbenv->set_verbose(dbenv, DB_VERB_RECOVERY,
+ (cfg->db_verbose & DB_VERB_RECOVERY));
+ dbenv->set_verbose(dbenv, DB_VERB_WAITSFOR,
+ (cfg->db_verbose & DB_VERB_WAITSFOR));
+
+ if (cfg->db_mmapsize) {
+ xx = dbenv->set_mp_mmapsize(dbenv, cfg->db_mmapsize);
+ xx = dbapi_err(rdb, "dbenv->set_mp_mmapsize", xx, _debug);
+ }
+
+ if (cfg->db_cachesize) {
+ xx = dbenv->set_cachesize(dbenv, 0, cfg->db_cachesize, 0);
+ xx = dbapi_err(rdb, "dbenv->set_cachesize", xx, _debug);
+ }
+
+ /*
+ * Actually open the environment. Fall back to private environment
+ * if we dont have permission to join/create shared environment.
+ */
+ while (retry_open) {
+ char *fstr = prDbiOpenFlags(eflags, 1);
+ rpmlog(RPMLOG_DEBUG, "opening db environment %s %s\n", dbhome, fstr);
+ free(fstr);
+
+ rc = (dbenv->open)(dbenv, dbhome, eflags, rdb->db_perms);
+ if (rc == EACCES || rc == EROFS) {
+ eflags |= DB_PRIVATE;
+ retry_open--;
+ } else {
+ retry_open = 0;
+ }
+ }
+ rc = dbapi_err(rdb, "dbenv->open", rc, _debug);
+ if (rc)
+ goto errxit;
+
+ dbenv->set_errcall(dbenv, errlog);
+
+ /* stale lock removal */
+ rc = dbenv->failchk(dbenv, 0);
+ rc = dbapi_err(rdb, "dbenv->failchk", rc, _debug);
+ if (rc)
+ goto errxit;
+
+ rdb->db_dbenv = dbenv;
+ rdb->db_opens = 1;
+
+ return 0;
+
+errxit:
+ if (dbenv) {
+ int xx;
+ xx = dbenv->close(dbenv, 0);
+ xx = dbapi_err(rdb, "dbenv->close", xx, _debug);
+ }
+ return rc;
+}
+
+void dbSetFSync(void *dbenv, int enable)
+{
+ db_env_set_func_fsync(enable ? fdatasync : fsync_disable);
+}
+
+int dbiSync(dbiIndex dbi, unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc = 0;
+
+ if (db != NULL && !dbi->dbi_no_dbsync) {
+ rc = db->sync(db, flags);
+ rc = cvtdberr(dbi, "db->sync", rc, _debug);
+ }
+ return rc;
+}
+
+int dbiCclose(dbiIndex dbi, DBC * dbcursor,
+ unsigned int flags)
+{
+ int rc = -2;
+
+ /* XXX dbiCopen error pathways come through here. */
+ if (dbcursor != NULL) {
+ rc = dbcursor->c_close(dbcursor);
+ rc = cvtdberr(dbi, "dbcursor->c_close", rc, _debug);
+ }
+ return rc;
+}
+
+int dbiCopen(dbiIndex dbi, DBC ** dbcp, unsigned int dbiflags)
+{
+ DB * db = dbi->dbi_db;
+ DBC * dbcursor = NULL;
+ int flags;
+ int rc;
+ uint32_t eflags = db_envflags(db);
+
+ /* XXX DB_WRITECURSOR cannot be used with sunrpc dbenv. */
+ assert(db != NULL);
+ if ((dbiflags & DB_WRITECURSOR) &&
+ (eflags & DB_INIT_CDB) && !(dbi->dbi_oflags & DB_RDONLY))
+ {
+ flags = DB_WRITECURSOR;
+ } else
+ flags = 0;
+
+ rc = db->cursor(db, NULL, &dbcursor, flags);
+ rc = cvtdberr(dbi, "db->cursor", rc, _debug);
+
+ if (dbcp)
+ *dbcp = dbcursor;
+ else
+ (void) dbiCclose(dbi, dbcursor, 0);
+
+ return rc;
+}
+
+
+/* Store (key,data) pair in index database. */
+int dbiPut(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc;
+
+ assert(key->data != NULL && key->size > 0 && data->data != NULL && data->size > 0);
+ assert(db != NULL);
+
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_putops, (ssize_t) 0);
+ if (dbcursor == NULL) {
+ rc = db->put(db, NULL, key, data, 0);
+ rc = cvtdberr(dbi, "db->put", rc, _debug);
+ } else {
+ rc = dbcursor->c_put(dbcursor, key, data, DB_KEYLAST);
+ rc = cvtdberr(dbi, "dbcursor->c_put", rc, _debug);
+ }
+
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_putops, (ssize_t) data->size);
+ return rc;
+}
+
+int dbiDel(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc;
+
+ assert(db != NULL);
+ assert(key->data != NULL && key->size > 0);
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_delops, 0);
+
+ if (dbcursor == NULL) {
+ rc = db->del(db, NULL, key, flags);
+ rc = cvtdberr(dbi, "db->del", rc, _debug);
+ } else {
+ int _printit;
+
+ /* XXX TODO: insure that cursor is positioned with duplicates */
+ rc = dbcursor->c_get(dbcursor, key, data, DB_SET);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "dbcursor->c_get", rc, _printit);
+
+ if (rc == 0) {
+ rc = dbcursor->c_del(dbcursor, flags);
+ rc = cvtdberr(dbi, "dbcursor->c_del", rc, _debug);
+ }
+ }
+
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_delops, data->size);
+ return rc;
+}
+
+/* Retrieve (key,data) pair from index database. */
+int dbiGet(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+
+ DB * db = dbi->dbi_db;
+ int _printit;
+ int rc;
+
+ assert((flags == DB_NEXT) || (key->data != NULL && key->size > 0));
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_getops, 0);
+
+ assert(db != NULL);
+ if (dbcursor == NULL) {
+ /* XXX duplicates require cursors. */
+ rc = db->get(db, NULL, key, data, 0);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "db->get", rc, _printit);
+ } else {
+ /* XXX db4 does DB_FIRST on uninitialized cursor */
+ rc = dbcursor->c_get(dbcursor, key, data, flags);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "dbcursor->c_get", rc, _printit);
+ }
+
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_getops, data->size);
+ return rc;
+}
+
+int dbiCount(dbiIndex dbi, DBC * dbcursor,
+ unsigned int * countp,
+ unsigned int flags)
+{
+ db_recno_t count = 0;
+ int rc = 0;
+
+ flags = 0;
+ rc = dbcursor->c_count(dbcursor, &count, flags);
+ rc = cvtdberr(dbi, "dbcursor->c_count", rc, _debug);
+ if (rc) return rc;
+ if (countp) *countp = count;
+
+ return rc;
+}
+
+int dbiByteSwapped(dbiIndex dbi)
+{
+ DB * db = dbi->dbi_db;
+ int rc = 0;
+
+ if (dbi->dbi_byteswapped != -1)
+ return dbi->dbi_byteswapped;
+
+ if (db != NULL) {
+ int isswapped = 0;
+ rc = db->get_byteswapped(db, &isswapped);
+ if (rc == 0)
+ dbi->dbi_byteswapped = rc = isswapped;
+ }
+ return rc;
+}
+
+dbiIndexType dbiType(dbiIndex dbi)
+{
+ return dbi->dbi_type;
+}
+
+int dbiFlags(dbiIndex dbi)
+{
+ DB *db = dbi->dbi_db;
+ int flags = DBI_NONE;
+ uint32_t oflags = 0;
+
+ if (db && db->get_open_flags(db, &oflags) == 0) {
+ if (oflags & DB_CREATE)
+ flags |= DBI_CREATED;
+ if (oflags & DB_RDONLY)
+ flags |= DBI_RDONLY;
+ }
+ return flags;
+}
+
+const char * dbiName(dbiIndex dbi)
+{
+ return dbi->dbi_file;
+}
+
+int dbiVerify(dbiIndex dbi, unsigned int flags)
+{
+ int rc = 0;
+
+ if (dbi && dbi->dbi_db) {
+ DB * db = dbi->dbi_db;
+
+ rc = db->verify(db, dbi->dbi_file, NULL, NULL, flags);
+ rc = cvtdberr(dbi, "db->verify", rc, _debug);
+
+ rpmlog(RPMLOG_DEBUG, "verified db index %s\n", dbi->dbi_file);
+
+ /* db->verify() destroys the handle, make sure nobody accesses it */
+ dbi->dbi_db = NULL;
+ }
+ return rc;
+}
+
+int dbiClose(dbiIndex dbi, unsigned int flags)
+{
+ rpmdb rdb = dbi->dbi_rpmdb;
+ const char * dbhome = rpmdbHome(rdb);
+ DB * db = dbi->dbi_db;
+ int _printit;
+ int rc = 0, xx;
+
+ flags = 0; /* XXX unused */
+
+ if (db) {
+ rc = db->close(db, 0);
+ /* XXX ignore not found error messages. */
+ _printit = (rc == ENOENT ? 0 : _debug);
+ rc = cvtdberr(dbi, "db->close", rc, _printit);
+ db = dbi->dbi_db = NULL;
+
+ rpmlog(RPMLOG_DEBUG, "closed db index %s/%s\n",
+ dbhome, dbi->dbi_file);
+ }
+
+ xx = db_fini(rdb, dbhome ? dbhome : "");
+
+ dbi->dbi_db = NULL;
+
+ dbi = dbiFree(dbi);
+
+ return rc;
+}
+
+/*
+ * Lock a file using fcntl(2). Traditionally this is Packages,
+ * the file used to store metadata of installed header(s),
+ * as Packages is always opened, and should be opened first,
+ * for any rpmdb access.
+ *
+ * If no DBENV is used, then access is protected with a
+ * shared/exclusive locking scheme, as always.
+ *
+ * With a DBENV, the fcntl(2) lock is necessary only to keep
+ * the riff-raff from playing where they don't belong, as
+ * the DBENV should provide it's own locking scheme. So try to
+ * acquire a lock, but permit failures, as some other
+ * DBENV player may already have acquired the lock.
+ *
+ * With NPTL posix mutexes, revert to fcntl lock on non-functioning
+ * glibc/kernel combinations.
+ */
+static int dbiFlock(dbiIndex dbi, int mode)
+{
+ int fdno = -1;
+ int rc = 0;
+ DB * db = dbi->dbi_db;
+
+ if (!(db->fd(db, &fdno) == 0 && fdno >= 0)) {
+ rc = 1;
+ } else {
+ const char *dbhome = rpmdbHome(dbi->dbi_rpmdb);
+ struct flock l;
+ memset(&l, 0, sizeof(l));
+ l.l_whence = 0;
+ l.l_start = 0;
+ l.l_len = 0;
+ l.l_type = (mode & O_ACCMODE) == O_RDONLY
+ ? F_RDLCK : F_WRLCK;
+ l.l_pid = 0;
+
+ rc = fcntl(fdno, F_SETLK, (void *) &l);
+ if (rc) {
+ uint32_t eflags = db_envflags(db);
+ /* Warning iff using non-private CDB locking. */
+ rc = (((eflags & DB_INIT_CDB) && !(eflags & DB_PRIVATE)) ? 0 : 1);
+ rpmlog( (rc ? RPMLOG_ERR : RPMLOG_WARNING),
+ _("cannot get %s lock on %s/%s\n"),
+ ((mode & O_ACCMODE) == O_RDONLY)
+ ? _("shared") : _("exclusive"),
+ dbhome, dbi->dbi_file);
+ } else {
+ rpmlog(RPMLOG_DEBUG,
+ "locked db index %s/%s\n",
+ dbhome, dbi->dbi_file);
+ }
+ }
+ return rc;
+}
+
+int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
+{
+ const char *dbhome = rpmdbHome(rdb);
+ dbiIndex dbi = NULL;
+ int rc = 0;
+ int retry_open;
+ int verifyonly = (flags & RPMDB_FLAG_VERIFYONLY);
+
+ DB * db = NULL;
+ DBTYPE dbtype = DB_UNKNOWN;
+ uint32_t oflags;
+ static int _lockdbfd = 0;
+
+ if (dbip)
+ *dbip = NULL;
+
+ /*
+ * Parse db configuration parameters.
+ */
+ if ((dbi = dbiNew(rdb, rpmtag)) == NULL)
+ return 1;
+
+ oflags = dbi->dbi_oflags;
+
+ /*
+ * Map open mode flags onto configured database/environment flags.
+ */
+ if ((rdb->db_mode & O_ACCMODE) == O_RDONLY) oflags |= DB_RDONLY;
+
+ rc = db_init(rdb, dbhome);
+
+ retry_open = (rc == 0) ? 2 : 0;
+
+ while (retry_open) {
+ rc = db_create(&db, rdb->db_dbenv, 0);
+ rc = cvtdberr(dbi, "db_create", rc, _debug);
+
+ /* For verify we only want the handle, not an open db */
+ if (verifyonly)
+ break;
+
+ if (rc == 0 && db != NULL) {
+ int _printit, xx;
+ char *dbfs = prDbiOpenFlags(oflags, 0);
+ rpmlog(RPMLOG_DEBUG, "opening db index %s/%s %s mode=0x%x\n",
+ dbhome, dbi->dbi_file, dbfs, rdb->db_mode);
+ free(dbfs);
+
+ rc = (db->open)(db, NULL, dbi->dbi_file, NULL,
+ dbtype, oflags, rdb->db_perms);
+
+ /* Attempt to create if missing, discarding DB_RDONLY (!) */
+ if (rc == ENOENT) {
+ oflags |= DB_CREATE;
+ oflags &= ~DB_RDONLY;
+ dbtype = (dbiType(dbi) == DBI_PRIMARY) ? DB_HASH : DB_BTREE;
+ retry_open--;
+ } else {
+ retry_open = 0;
+ }
+
+ /* XXX return rc == errno without printing */
+ _printit = (rc > 0 ? 0 : _debug);
+ xx = cvtdberr(dbi, "db->open", rc, _printit);
+
+ /* Validate the index type is something we can support */
+ if ((rc == 0) && (dbtype == DB_UNKNOWN)) {
+ db->get_type(db, &dbtype);
+ if (dbtype != DB_HASH && dbtype != DB_BTREE) {
+ rpmlog(RPMLOG_ERR, _("invalid index type %x on %s/%s\n"),
+ dbtype, dbhome, dbi->dbi_file);
+ rc = 1;
+ }
+ }
+
+ if (rc != 0) {
+ db->close(db, 0);
+ db = NULL;
+ }
+ }
+ }
+
+ dbi->dbi_db = db;
+ dbi->dbi_oflags = oflags;
+
+ if (!verifyonly && rc == 0 && dbi->dbi_lockdbfd && _lockdbfd++ == 0) {
+ rc = dbiFlock(dbi, rdb->db_mode);
+ }
+
+ if (rc == 0 && dbi->dbi_db != NULL && dbip != NULL) {
+ *dbip = dbi;
+ } else {
+ (void) dbiClose(dbi, 0);
+ }
+
+ return rc;
+}
diff --git a/lib/backend/dbconfig.c b/lib/backend/dbconfig.c
new file mode 100644
index 0000000..9e81476
--- /dev/null
+++ b/lib/backend/dbconfig.c
@@ -0,0 +1,268 @@
+/** \ingroup rpmdb
+ * \file lib/dbconfig.c
+ */
+
+#include "system.h"
+
+#include <popt.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+#include <rpm/argv.h>
+#include "lib/rpmdb_internal.h"
+#include "debug.h"
+
+static struct _dbiIndex staticdbi;
+static struct _dbConfig staticcfg;
+static int db_eflags;
+
+/** \ingroup dbi
+ */
+static const struct poptOption rdbOptions[] = {
+ /* Environment options */
+
+ { "cdb", 0,POPT_BIT_SET, &db_eflags, DB_INIT_CDB,
+ NULL, NULL },
+ { "lock", 0,POPT_BIT_SET, &db_eflags, DB_INIT_LOCK,
+ NULL, NULL },
+ { "log", 0,POPT_BIT_SET, &db_eflags, DB_INIT_LOG,
+ NULL, NULL },
+ { "txn", 0,POPT_BIT_SET, &db_eflags, DB_INIT_TXN,
+ NULL, NULL },
+ { "recover", 0,POPT_BIT_SET, &db_eflags, DB_RECOVER,
+ NULL, NULL },
+ { "recover_fatal", 0,POPT_BIT_SET, &db_eflags, DB_RECOVER_FATAL,
+ NULL, NULL },
+ { "lockdown", 0,POPT_BIT_SET, &db_eflags, DB_LOCKDOWN,
+ NULL, NULL },
+ { "private", 0,POPT_BIT_SET, &db_eflags, DB_PRIVATE,
+ NULL, NULL },
+
+ { "deadlock", 0,POPT_BIT_SET, &staticcfg.db_verbose, DB_VERB_DEADLOCK,
+ NULL, NULL },
+ { "recovery", 0,POPT_BIT_SET, &staticcfg.db_verbose, DB_VERB_RECOVERY,
+ NULL, NULL },
+ { "waitsfor", 0,POPT_BIT_SET, &staticcfg.db_verbose, DB_VERB_WAITSFOR,
+ NULL, NULL },
+ { "verbose", 0,POPT_ARG_VAL, &staticcfg.db_verbose, -1,
+ NULL, NULL },
+
+ { "cachesize", 0,POPT_ARG_INT, &staticcfg.db_cachesize, 0,
+ NULL, NULL },
+ { "mmapsize", 0,POPT_ARG_INT, &staticcfg.db_mmapsize, 0,
+ NULL, NULL },
+ { "mp_mmapsize", 0,POPT_ARG_INT, &staticcfg.db_mmapsize, 0,
+ NULL, NULL },
+ { "mp_size", 0,POPT_ARG_INT, &staticcfg.db_cachesize, 0,
+ NULL, NULL },
+
+ { "nofsync", 0,POPT_ARG_NONE, &staticcfg.db_no_fsync, 0,
+ NULL, NULL },
+
+ /* Per-dbi options */
+ { "nommap", 0,POPT_BIT_SET, &staticdbi.dbi_oflags, DB_NOMMAP,
+ NULL, NULL },
+
+ { "nodbsync", 0,POPT_ARG_NONE, &staticdbi.dbi_no_dbsync, 0,
+ NULL, NULL },
+ { "lockdbfd", 0,POPT_ARG_NONE, &staticdbi.dbi_lockdbfd, 0,
+ NULL, NULL },
+
+ POPT_TABLEEND
+};
+
+dbiIndex dbiFree(dbiIndex dbi)
+{
+ if (dbi) {
+ dbi = _free(dbi);
+ }
+ return dbi;
+}
+
+dbiIndex dbiNew(rpmdb rdb, rpmDbiTagVal rpmtag)
+{
+ dbiIndex dbi = xcalloc(1, sizeof(*dbi));
+ char *dbOpts;
+
+ dbOpts = rpmExpand("%{_dbi_config_", rpmTagGetName(rpmtag), "}", NULL);
+
+ if (!(dbOpts && *dbOpts && *dbOpts != '%')) {
+ dbOpts = _free(dbOpts);
+ dbOpts = rpmExpand("%{_dbi_config}", NULL);
+ if (!(dbOpts && *dbOpts && *dbOpts != '%')) {
+ dbOpts = _free(dbOpts);
+ }
+ }
+
+ /* Parse the options for the database element(s). */
+ if (dbOpts && *dbOpts && *dbOpts != '%') {
+ char *o, *oe;
+ char *p, *pe;
+
+ memset(&staticdbi, 0, sizeof(staticdbi));
+/*=========*/
+ for (o = dbOpts; o && *o; o = oe) {
+ const struct poptOption *opt;
+ const char * tok;
+ unsigned int argInfo;
+
+ /* Skip leading white space. */
+ while (*o && risspace(*o))
+ o++;
+
+ /* Find and terminate next key=value pair. Save next start point. */
+ for (oe = o; oe && *oe; oe++) {
+ if (risspace(*oe))
+ break;
+ if (oe[0] == ':' && !(oe[1] == '/' && oe[2] == '/'))
+ break;
+ }
+ if (oe && *oe)
+ *oe++ = '\0';
+ if (*o == '\0')
+ continue;
+
+ /* Separate key from value, save value start (if any). */
+ for (pe = o; pe && *pe && *pe != '='; pe++)
+ {};
+ p = (pe ? *pe++ = '\0', pe : NULL);
+
+ /* Skip over negation at start of token. */
+ for (tok = o; *tok == '!'; tok++)
+ {};
+
+ /* Find key in option table. */
+ for (opt = rdbOptions; opt->longName != NULL; opt++) {
+ if (!rstreq(tok, opt->longName))
+ continue;
+ break;
+ }
+ if (opt->longName == NULL) {
+ rpmlog(RPMLOG_ERR,
+ _("unrecognized db option: \"%s\" ignored.\n"), o);
+ continue;
+ }
+
+ /* Toggle the flags for negated tokens, if necessary. */
+ argInfo = opt->argInfo;
+ if (argInfo == POPT_BIT_SET && *o == '!' && ((tok - o) % 2))
+ argInfo = POPT_BIT_CLR;
+
+ /* Save value in template as appropriate. */
+ switch (argInfo & POPT_ARG_MASK) {
+
+ case POPT_ARG_NONE:
+ (void) poptSaveInt((int *)opt->arg, argInfo, 1L);
+ break;
+ case POPT_ARG_VAL:
+ (void) poptSaveInt((int *)opt->arg, argInfo, (long)opt->val);
+ break;
+ case POPT_ARG_STRING:
+ { char ** t = opt->arg;
+ if (t) {
+/* FIX: opt->arg annotation in popt.h */
+ *t = _free(*t);
+ *t = xstrdup( (p ? p : "") );
+ }
+ } break;
+
+ case POPT_ARG_INT:
+ case POPT_ARG_LONG:
+ { long aLong = strtol(p, &pe, 0);
+ if (pe) {
+ if (!rstrncasecmp(pe, "Mb", 2))
+ aLong *= 1024 * 1024;
+ else if (!rstrncasecmp(pe, "Kb", 2))
+ aLong *= 1024;
+ else if (*pe != '\0') {
+ rpmlog(RPMLOG_ERR,
+ _("%s has invalid numeric value, skipped\n"),
+ opt->longName);
+ continue;
+ }
+ }
+
+ if ((argInfo & POPT_ARG_MASK) == POPT_ARG_LONG) {
+ if (aLong == LONG_MIN || aLong == LONG_MAX) {
+ rpmlog(RPMLOG_ERR,
+ _("%s has too large or too small long value, skipped\n"),
+ opt->longName);
+ continue;
+ }
+ (void) poptSaveLong((long *)opt->arg, argInfo, aLong);
+ break;
+ } else {
+ if (aLong > INT_MAX || aLong < INT_MIN) {
+ rpmlog(RPMLOG_ERR,
+ _("%s has too large or too small integer value, skipped\n"),
+ opt->longName);
+ continue;
+ }
+ (void) poptSaveInt((int *)opt->arg, argInfo, aLong);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+/*=========*/
+ }
+
+ dbOpts = _free(dbOpts);
+
+ *dbi = staticdbi; /* structure assignment */
+ memset(&staticdbi, 0, sizeof(staticdbi));
+
+ /* FIX: figger lib/dbi refcounts */
+ dbi->dbi_rpmdb = rdb;
+ dbi->dbi_file = rpmTagGetName(rpmtag);
+ dbi->dbi_type = (rpmtag == RPMDBI_PACKAGES) ? DBI_PRIMARY : DBI_SECONDARY;
+ dbi->dbi_byteswapped = -1; /* -1 unknown, 0 native order, 1 alien order */
+
+ /* XXX FIXME: Get environment configuration out of here! */
+ if (rdb->db_dbenv == NULL) {
+ struct _dbConfig * cfg = &rdb->cfg;
+ *cfg = staticcfg; /* structure assignment */
+ /* Throw in some defaults if configuration didn't set any */
+ if (!cfg->db_mmapsize) cfg->db_mmapsize = 16 * 1024 * 1024;
+ if (!cfg->db_cachesize) cfg->db_cachesize = 8 * 1024 * 1024;
+ }
+
+ /* FIX: *(rdbOptions->arg) reachable */
+ return dbi;
+}
+
+char * prDbiOpenFlags(int dbflags, int print_dbenv_flags)
+{
+ ARGV_t flags = NULL;
+ const struct poptOption *opt;
+ char *buf;
+
+ for (opt = rdbOptions; opt->longName != NULL; opt++) {
+ if (opt->argInfo != POPT_BIT_SET)
+ continue;
+ if (print_dbenv_flags) {
+ if (!(opt->arg == &db_eflags))
+ continue;
+ } else {
+ if (!(opt->arg == &staticdbi.dbi_oflags))
+ continue;
+ }
+ if ((dbflags & opt->val) != opt->val)
+ continue;
+ argvAdd(&flags, opt->longName);
+ dbflags &= ~opt->val;
+ }
+ if (dbflags) {
+ char *df = NULL;
+ rasprintf(&df, "0x%x", (unsigned)dbflags);
+ argvAdd(&flags, df);
+ free(df);
+ }
+ buf = argvJoin(flags, ":");
+ argvFree(flags);
+
+ return buf ? buf : xstrdup("(none)");
+}
diff --git a/lib/backend/dbi.h b/lib/backend/dbi.h
new file mode 100644
index 0000000..f37a160
--- /dev/null
+++ b/lib/backend/dbi.h
@@ -0,0 +1,270 @@
+#ifndef _DBI_H
+#define _DBI_H
+
+enum rpmdbFlags {
+ RPMDB_FLAG_JUSTCHECK = (1 << 0),
+ RPMDB_FLAG_REBUILD = (1 << 1),
+ RPMDB_FLAG_VERIFYONLY = (1 << 2),
+};
+
+typedef struct _dbiIndex * dbiIndex;
+
+struct _dbConfig {
+ int db_mmapsize; /*!< (10Mb) */
+ int db_cachesize; /*!< (128Kb) */
+ int db_verbose;
+ int db_no_fsync; /*!< no-op fsync for db */
+};
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+#define HASHTYPE intHash
+#define HTKEYTYPE unsigned int
+#include "lib/rpmhash.H"
+#undef HASHTYPE
+#undef HTKEYTYPE
+
+/** \ingroup rpmdb
+ * Describes the collection of index databases used by rpm.
+ */
+struct rpmdb_s {
+ char * db_root;/*!< path prefix */
+ char * db_home;/*!< directory path */
+ char * db_fullpath; /*!< full db path including prefix */
+ int db_flags;
+ int db_mode; /*!< open mode */
+ int db_perms; /*!< open permissions */
+ int db_ver; /*!< Berkeley DB version */
+ intHash db_checked; /*!< headerCheck()'ed package instances */
+ rpmdb db_next;
+ int db_opens;
+ int db_ndbi; /*!< No. of tag indices. */
+ dbiIndex * _dbi; /*!< Tag indices. */
+ int db_buildindex; /*!< Index rebuild indicator */
+
+ /* dbenv and related parameters */
+ void * db_dbenv; /*!< Berkeley DB_ENV handle. */
+ struct _dbConfig cfg;
+ int db_remove_env;
+
+ struct rpmop_s db_getops;
+ struct rpmop_s db_putops;
+ struct rpmop_s db_delops;
+
+ int nrefs; /*!< Reference count. */
+};
+
+/* Type of the dbi, also serves as the join key size */
+typedef enum dbiIndexType_e {
+ DBI_PRIMARY = (1 * sizeof(int32_t)),
+ DBI_SECONDARY = (2 * sizeof(int32_t)),
+} dbiIndexType;
+
+enum dbiFlags_e {
+ DBI_NONE = 0,
+ DBI_CREATED = (1 << 0),
+ DBI_RDONLY = (1 << 1),
+};
+
+/** \ingroup dbi
+ * Describes an index database (implemented on Berkeley db functionality).
+ */
+struct _dbiIndex {
+ const char * dbi_file; /*!< file component of path */
+
+ int dbi_oflags; /*!< db->open flags */
+ int dbi_permit_dups; /*!< permit duplicate entries? */
+ int dbi_no_dbsync; /*!< don't call dbiSync */
+ int dbi_lockdbfd; /*!< do fcntl lock on db fd */
+ int dbi_byteswapped;
+
+ rpmdb dbi_rpmdb; /*!< the parent rpm database */
+ dbiIndexType dbi_type; /*! Type of dbi (primary / index) */
+
+ DB * dbi_db; /*!< Berkeley DB * handle */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+RPM_GNUC_INTERNAL
+/* Globally enable/disable fsync in the backend */
+void dbSetFSync(void *dbenv, int enable);
+
+/** \ingroup dbi
+ * Return new configured index database handle instance.
+ * @param rdb rpm database
+ * @param rpmtag database index tag
+ * @return index database handle
+ */
+RPM_GNUC_INTERNAL
+dbiIndex dbiNew(rpmdb rdb, rpmDbiTagVal rpmtag);
+
+/** \ingroup dbi
+ * Destroy index database handle instance.
+ * @param dbi index database handle
+ * @return NULL always
+ */
+RPM_GNUC_INTERNAL
+dbiIndex dbiFree( dbiIndex dbi);
+
+/** \ingroup dbi
+ * Format dbi open flags for debugging print.
+ * @param dbflags db open flags
+ * @param print_dbenv_flags format db env flags instead?
+ * @return formatted flags (malloced)
+ */
+RPM_GNUC_INTERNAL
+char * prDbiOpenFlags(int dbflags, int print_dbenv_flags);
+
+/** \ingroup dbi
+ * Actually open the database of the index.
+ * @param db rpm database
+ * @param rpmtag database index tag
+ * @param dbiIndex address of index database handle
+ * @param flags
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags);
+
+
+/* FIX: vector annotations */
+/** \ingroup dbi
+ * Open a database cursor.
+ * @param dbi index database handle
+ * @retval dbcp returned database cursor
+ * @param flags DB_WRITECURSOR if writing, or 0
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiCopen(dbiIndex dbi, DBC ** dbcp, unsigned int flags);
+
+/** \ingroup dbi
+ * Close a database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiCclose(dbiIndex dbi, DBC * dbcursor, unsigned int flags);
+
+/** \ingroup dbi
+ * Delete (key,data) pair(s) from index database.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->del)
+ * @param key delete key value/length/flags
+ * @param data delete data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiDel(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Retrieve (key,data) pair from index database.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->get)
+ * @param key retrieve key value/length/flags
+ * @param data retrieve data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiGet(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Store (key,data) pair in index database.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->put)
+ * @param key store key value/length/flags
+ * @param data store data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiPut(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Retrieve count of (possible) duplicate items.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param countp address of count
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiCount(dbiIndex dbi, DBC * dbcursor, unsigned int * countp,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Close index database.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiClose(dbiIndex dbi, unsigned int flags);
+
+/** \ingroup dbi
+ * Flush pending operations to disk.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiSync (dbiIndex dbi, unsigned int flags);
+
+/** \ingroup dbi
+ * Verify (and close) index database.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int dbiVerify(dbiIndex dbi, unsigned int flags);
+
+/** \ingroup dbi
+ * Is database byte swapped?
+ * @param dbi index database handle
+ * @return 0 same order, 1 swapped order
+ */
+RPM_GNUC_INTERNAL
+int dbiByteSwapped(dbiIndex dbi);
+
+/** \ingroup dbi
+ * Type of dbi (primary data / index)
+ * @param dbi index database handle
+ * @return type of dbi
+ */
+RPM_GNUC_INTERNAL
+dbiIndexType dbiType(dbiIndex dbi);
+
+/** \ingroup dbi
+ * Retrieve index control flags (new/existing, read-only etc)
+ * @param dbi index database handle
+ * @return dbi control flags
+ */
+RPM_GNUC_INTERNAL
+int dbiFlags(dbiIndex dbi);
+
+/** \ingroup dbi
+ * Retrieve index name (same as the backing file name)
+ * @param dbi index database handle
+ * @return dbi name
+ */
+RPM_GNUC_INTERNAL
+const char * dbiName(dbiIndex dbi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DBI_H */
diff --git a/lib/cpio.c b/lib/cpio.c
new file mode 100644
index 0000000..b5c39c0
--- /dev/null
+++ b/lib/cpio.c
@@ -0,0 +1,245 @@
+/** \ingroup payload
+ * \file lib/cpio.c
+ * Handle cpio payloads within rpm packages.
+ *
+ * \warning FIXME: We don't translate between cpio and system mode bits! These
+ * should both be the same, but really odd things are going to happen if
+ * that's not true!
+ */
+
+#include "system.h"
+
+#if MAJOR_IN_MKDEV
+#include <sys/mkdev.h>
+#elif MAJOR_IN_SYSMACROS
+#include <sys/sysmacros.h>
+#else
+#include <sys/types.h> /* already included from system.h */
+#endif
+#include <errno.h>
+
+#include <rpm/rpmio.h>
+#include <rpm/rpmlog.h>
+
+#include "lib/cpio.h"
+#include "lib/fsm.h"
+
+#include "debug.h"
+
+
+/**
+ * Convert string to unsigned integer (with buffer size check).
+ * @param str input string
+ * @retval endptr address of 1st character not processed
+ * @param base numerical conversion base
+ * @param num max no. of bytes to read
+ * @return converted integer
+ */
+static unsigned long strntoul(const char *str,char **endptr, int base, size_t num)
+{
+ char buf[num+1], * end;
+ unsigned long ret;
+
+ strncpy(buf, str, num);
+ buf[num] = '\0';
+
+ ret = strtoul(buf, &end, base);
+ if (*end != '\0')
+ *endptr = ((char *)str) + (end - buf); /* XXX discards const */
+ else
+ *endptr = ((char *)str) + strlen(buf);
+
+ return ret;
+}
+
+#define GET_NUM_FIELD(phys, log) \
+ \
+ log = strntoul(phys, &end, 16, sizeof(phys)); \
+ \
+ if ( (end - phys) != sizeof(phys) ) return CPIOERR_BAD_HEADER;
+#define SET_NUM_FIELD(phys, val, space) \
+ sprintf(space, "%8.8lx", (unsigned long) (val)); \
+ \
+ memcpy(phys, space, 8) \
+
+int cpioTrailerWrite(FSM_t fsm)
+{
+ struct cpioCrcPhysicalHeader * hdr =
+ (struct cpioCrcPhysicalHeader *)fsm->rdbuf;
+ int rc;
+
+ memset(hdr, '0', PHYS_HDR_SIZE);
+ memcpy(hdr->magic, CPIO_NEWC_MAGIC, sizeof(hdr->magic));
+ memcpy(hdr->nlink, "00000001", 8);
+ memcpy(hdr->namesize, "0000000b", 8);
+ memcpy(fsm->rdbuf + PHYS_HDR_SIZE, CPIO_TRAILER, sizeof(CPIO_TRAILER));
+
+ /* XXX DWRITE uses rdnb for I/O length. */
+ fsm->rdnb = PHYS_HDR_SIZE + sizeof(CPIO_TRAILER);
+ rc = fsmNext(fsm, FSM_DWRITE);
+
+ /*
+ * GNU cpio pads to 512 bytes here, but we don't. This may matter for
+ * tape device(s) and/or concatenated cpio archives. <shrug>
+ */
+ if (!rc)
+ rc = fsmNext(fsm, FSM_PAD);
+
+ return rc;
+}
+
+int cpioHeaderWrite(FSM_t fsm, struct stat * st)
+{
+ struct cpioCrcPhysicalHeader * hdr = (struct cpioCrcPhysicalHeader *)fsm->rdbuf;
+ char field[64];
+ size_t len;
+ dev_t dev;
+ int rc = 0;
+
+ memcpy(hdr->magic, CPIO_NEWC_MAGIC, sizeof(hdr->magic));
+ SET_NUM_FIELD(hdr->inode, st->st_ino, field);
+ SET_NUM_FIELD(hdr->mode, st->st_mode, field);
+ SET_NUM_FIELD(hdr->uid, st->st_uid, field);
+ SET_NUM_FIELD(hdr->gid, st->st_gid, field);
+ SET_NUM_FIELD(hdr->nlink, st->st_nlink, field);
+ SET_NUM_FIELD(hdr->mtime, st->st_mtime, field);
+ SET_NUM_FIELD(hdr->filesize, st->st_size, field);
+
+ dev = major(st->st_dev); SET_NUM_FIELD(hdr->devMajor, dev, field);
+ dev = minor(st->st_dev); SET_NUM_FIELD(hdr->devMinor, dev, field);
+ dev = major(st->st_rdev); SET_NUM_FIELD(hdr->rdevMajor, dev, field);
+ dev = minor(st->st_rdev); SET_NUM_FIELD(hdr->rdevMinor, dev, field);
+
+ len = strlen(fsm->path) + 1; SET_NUM_FIELD(hdr->namesize, len, field);
+ memcpy(hdr->checksum, "00000000", 8);
+ memcpy(fsm->rdbuf + PHYS_HDR_SIZE, fsm->path, len);
+
+ /* XXX DWRITE uses rdnb for I/O length. */
+ fsm->rdnb = PHYS_HDR_SIZE + len;
+ rc = fsmNext(fsm, FSM_DWRITE);
+ if (!rc && fsm->rdnb != fsm->wrnb)
+ rc = CPIOERR_WRITE_FAILED;
+ if (!rc)
+ rc = fsmNext(fsm, FSM_PAD);
+ return rc;
+}
+
+int cpioHeaderRead(FSM_t fsm, struct stat * st)
+{
+ struct cpioCrcPhysicalHeader hdr;
+ int nameSize;
+ char * end;
+ unsigned int major, minor;
+ int rc = 0;
+
+ fsm->wrlen = PHYS_HDR_SIZE;
+ rc = fsmNext(fsm, FSM_DREAD);
+ if (!rc && fsm->rdnb != fsm->wrlen)
+ rc = CPIOERR_READ_FAILED;
+ if (rc) return rc;
+ memcpy(&hdr, fsm->wrbuf, fsm->rdnb);
+
+ if (strncmp(CPIO_CRC_MAGIC, hdr.magic, sizeof(CPIO_CRC_MAGIC)-1) &&
+ strncmp(CPIO_NEWC_MAGIC, hdr.magic, sizeof(CPIO_NEWC_MAGIC)-1))
+ return CPIOERR_BAD_MAGIC;
+
+ GET_NUM_FIELD(hdr.inode, st->st_ino);
+ GET_NUM_FIELD(hdr.mode, st->st_mode);
+ GET_NUM_FIELD(hdr.uid, st->st_uid);
+ GET_NUM_FIELD(hdr.gid, st->st_gid);
+ GET_NUM_FIELD(hdr.nlink, st->st_nlink);
+ GET_NUM_FIELD(hdr.mtime, st->st_mtime);
+ GET_NUM_FIELD(hdr.filesize, st->st_size);
+
+ GET_NUM_FIELD(hdr.devMajor, major);
+ GET_NUM_FIELD(hdr.devMinor, minor);
+ st->st_dev = makedev(major, minor);
+
+ GET_NUM_FIELD(hdr.rdevMajor, major);
+ GET_NUM_FIELD(hdr.rdevMinor, minor);
+ st->st_rdev = makedev(major, minor);
+
+ GET_NUM_FIELD(hdr.namesize, nameSize);
+ if (nameSize >= fsm->wrsize)
+ return CPIOERR_BAD_HEADER;
+
+ { char * t = xmalloc(nameSize + 1);
+ fsm->wrlen = nameSize;
+ rc = fsmNext(fsm, FSM_DREAD);
+ if (!rc && fsm->rdnb != fsm->wrlen)
+ rc = CPIOERR_BAD_HEADER;
+ if (rc) {
+ t = _free(t);
+ fsm->path = NULL;
+ return rc;
+ }
+ memcpy(t, fsm->wrbuf, fsm->rdnb);
+ t[nameSize] = '\0';
+ fsm->path = t;
+ }
+
+ return 0;
+}
+
+const char * cpioStrerror(int rc)
+{
+ static char msg[256];
+ const char *s;
+ int myerrno = errno;
+ size_t l;
+
+ strcpy(msg, "cpio: ");
+ switch (rc) {
+ default: {
+ char *t = msg + strlen(msg);
+ sprintf(t, _("(error 0x%x)"), (unsigned)rc);
+ s = NULL;
+ break;
+ }
+ case CPIOERR_BAD_MAGIC: s = _("Bad magic"); break;
+ case CPIOERR_BAD_HEADER: s = _("Bad/unreadable header");break;
+
+ case CPIOERR_OPEN_FAILED: s = "open"; break;
+ case CPIOERR_CHMOD_FAILED: s = "chmod"; break;
+ case CPIOERR_CHOWN_FAILED: s = "chown"; break;
+ case CPIOERR_WRITE_FAILED: s = "write"; break;
+ case CPIOERR_UTIME_FAILED: s = "utime"; break;
+ case CPIOERR_UNLINK_FAILED: s = "unlink"; break;
+ case CPIOERR_RENAME_FAILED: s = "rename"; break;
+ case CPIOERR_SYMLINK_FAILED: s = "symlink"; break;
+ case CPIOERR_STAT_FAILED: s = "stat"; break;
+ case CPIOERR_LSTAT_FAILED: s = "lstat"; break;
+ case CPIOERR_MKDIR_FAILED: s = "mkdir"; break;
+ case CPIOERR_RMDIR_FAILED: s = "rmdir"; break;
+ case CPIOERR_MKNOD_FAILED: s = "mknod"; break;
+ case CPIOERR_MKFIFO_FAILED: s = "mkfifo"; break;
+ case CPIOERR_LINK_FAILED: s = "link"; break;
+ case CPIOERR_READLINK_FAILED: s = "readlink"; break;
+ case CPIOERR_READ_FAILED: s = "read"; break;
+ case CPIOERR_COPY_FAILED: s = "copy"; break;
+ case CPIOERR_LSETFCON_FAILED: s = "lsetfilecon"; break;
+ case CPIOERR_SETCAP_FAILED: s = "cap_set_file"; break;
+
+ case CPIOERR_HDR_SIZE: s = _("Header size too big"); break;
+ case CPIOERR_UNKNOWN_FILETYPE: s = _("Unknown file type"); break;
+ case CPIOERR_MISSING_HARDLINK: s = _("Missing hard link(s)"); break;
+ case CPIOERR_DIGEST_MISMATCH: s = _("Digest mismatch"); break;
+ case CPIOERR_INTERNAL: s = _("Internal error"); break;
+ case CPIOERR_UNMAPPED_FILE: s = _("Archive file not in header"); break;
+ case CPIOERR_ENOENT: s = strerror(ENOENT); break;
+ case CPIOERR_ENOTEMPTY: s = strerror(ENOTEMPTY); break;
+ }
+
+ l = sizeof(msg) - strlen(msg) - 1;
+ if (s != NULL) {
+ if (l > 0) strncat(msg, s, l);
+ l -= strlen(s);
+ }
+ if ((rc & CPIOERR_CHECK_ERRNO) && myerrno) {
+ s = _(" failed - ");
+ if (l > 0) strncat(msg, s, l);
+ l -= strlen(s);
+ if (l > 0) strncat(msg, strerror(myerrno), l);
+ }
+ return msg;
+}
diff --git a/lib/cpio.h b/lib/cpio.h
new file mode 100644
index 0000000..9453184
--- /dev/null
+++ b/lib/cpio.h
@@ -0,0 +1,133 @@
+#ifndef H_CPIO
+#define H_CPIO
+
+/** \ingroup payload
+ * \file lib/cpio.h
+ * Structures used to handle cpio payloads within rpm packages.
+ *
+ * @warning Rpm's cpio implementation may be different than standard cpio.
+ * The implementation is pretty close, but it has some behaviors which are
+ * more to RPM's liking. I tried to document the differing behavior in cpio.c,
+ * but I may have missed some (ewt).
+ *
+ */
+
+#include "lib/fsm.h"
+
+/** \ingroup payload
+ * @note CPIO_CHECK_ERRNO bit is set only if errno is valid.
+ */
+#define CPIOERR_CHECK_ERRNO 0x00008000
+
+/** \ingroup payload
+ */
+enum cpioErrorReturns {
+ CPIOERR_BAD_MAGIC = 2,
+ CPIOERR_BAD_HEADER = 3,
+ CPIOERR_OPEN_FAILED = 4 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_CHMOD_FAILED = 5 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_CHOWN_FAILED = 6 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_WRITE_FAILED = 7 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_UTIME_FAILED = 8 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_UNLINK_FAILED = 9 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_RENAME_FAILED = 10 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_SYMLINK_FAILED = 11 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_STAT_FAILED = 12 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_LSTAT_FAILED = 13 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_MKDIR_FAILED = 14 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_RMDIR_FAILED = 15 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_MKNOD_FAILED = 16 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_MKFIFO_FAILED = 17 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_LINK_FAILED = 18 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_READLINK_FAILED = 19 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_READ_FAILED = 20 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_COPY_FAILED = 21 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_LSETFCON_FAILED = 22 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_HDR_SIZE = 23,
+ CPIOERR_HDR_TRAILER = 24,
+ CPIOERR_UNKNOWN_FILETYPE= 25,
+ CPIOERR_MISSING_HARDLINK= 26,
+ CPIOERR_DIGEST_MISMATCH = 27,
+ CPIOERR_INTERNAL = 28,
+ CPIOERR_UNMAPPED_FILE = 29,
+ CPIOERR_ENOENT = 30,
+ CPIOERR_ENOTEMPTY = 31,
+ CPIOERR_SETCAP_FAILED = 32 | CPIOERR_CHECK_ERRNO,
+};
+
+/*
+ * Size limit for individual files in "new ascii format" cpio archives.
+ * The max size of the entire archive is unlimited from cpio POV,
+ * but subject to filesystem limitations.
+ */
+#define CPIO_FILESIZE_MAX UINT32_MAX
+
+#define CPIO_NEWC_MAGIC "070701"
+#define CPIO_CRC_MAGIC "070702"
+#define CPIO_TRAILER "TRAILER!!!"
+
+/** \ingroup payload
+ * Cpio archive header information.
+ */
+struct cpioCrcPhysicalHeader {
+ char magic[6];
+ char inode[8];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char nlink[8];
+ char mtime[8];
+ char filesize[8];
+ char devMajor[8];
+ char devMinor[8];
+ char rdevMajor[8];
+ char rdevMinor[8];
+ char namesize[8];
+ char checksum[8]; /* ignored !! */
+};
+
+#define PHYS_HDR_SIZE 110 /* Don't depend on sizeof(struct) */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Write cpio trailer.
+ * @retval fsm file path and stat info
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int cpioTrailerWrite(FSM_t fsm);
+
+/**
+ * Write cpio header.
+ * @retval fsm file path and stat info
+ * @param st
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int cpioHeaderWrite(FSM_t fsm, struct stat * st);
+
+/**
+ * Read cpio header.
+ * @retval fsm file path and stat info
+ * @retval st
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int cpioHeaderRead(FSM_t fsm, struct stat * st);
+
+/** \ingroup payload
+ * Return formatted error message on payload handling failure.
+ * @param rc error code
+ * @return formatted error string
+ */
+/* XXX should be RPM_GNUC_INTERNAL too but build/pack.c uses */
+const char * cpioStrerror(int rc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_CPIO */
diff --git a/lib/depends.c b/lib/depends.c
new file mode 100644
index 0000000..69aecbb
--- /dev/null
+++ b/lib/depends.c
@@ -0,0 +1,596 @@
+/** \ingroup rpmts
+ * \file lib/depends.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmcli.h> /* XXX rpmcliPackagesTotal */
+
+#include <rpm/rpmlib.h> /* rpmVersionCompare, rpmlib provides */
+#include <rpm/rpmtag.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmfi.h>
+
+#include "lib/rpmts_internal.h"
+#include "lib/rpmte_internal.h"
+#include "lib/misc.h"
+
+#include "debug.h"
+
+const char * const RPMVERSION = VERSION;
+
+const char * const rpmNAME = PACKAGE;
+
+const char * const rpmEVR = VERSION;
+
+const int rpmFLAGS = RPMSENSE_EQUAL;
+
+/* rpmlib provides */
+static rpmds rpmlibP = NULL;
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+
+#define HASHTYPE depCache
+#define HTKEYTYPE const char *
+#define HTDATATYPE int
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+
+#define HASHTYPE intHash
+#define HTKEYTYPE unsigned int
+#include "rpmhash.C"
+#undef HASHTYPE
+#undef HASHKEYTYPE
+
+/**
+ * Add removed package instance to ordered transaction set.
+ * @param ts transaction set
+ * @param h header
+ * @param depends installed package of pair (or RPMAL_NOMATCH on erase)
+ * @return 0 on success
+ */
+static int removePackage(rpmts ts, Header h, rpmte depends)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpmte p;
+ unsigned int dboffset = headerGetInstance(h);
+
+ /* Can't remove what's not installed */
+ if (dboffset == 0) return 1;
+
+ /* Filter out duplicate erasures. */
+ if (intHashHasEntry(tsmem->removedPackages, dboffset)) {
+ return 0;
+ }
+
+ intHashAddEntry(tsmem->removedPackages, dboffset);
+
+ if (tsmem->orderCount >= tsmem->orderAlloced) {
+ tsmem->orderAlloced += (tsmem->orderCount - tsmem->orderAlloced) + tsmem->delta;
+ tsmem->order = xrealloc(tsmem->order, sizeof(*tsmem->order) * tsmem->orderAlloced);
+ }
+
+ p = rpmteNew(ts, h, TR_REMOVED, NULL, NULL);
+ rpmteSetDependsOn(p, depends);
+
+ tsmem->order[tsmem->orderCount] = p;
+ tsmem->orderCount++;
+
+ return 0;
+}
+
+/* Return rpmdb iterator with removals pruned out */
+static rpmdbMatchIterator rpmtsPrunedIterator(rpmts ts, rpmDbiTagVal tag, const char * key)
+{
+ rpmdbMatchIterator mi = rpmtsInitIterator(ts, tag, key, 0);
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpmdbPruneIterator(mi, tsmem->removedPackages);
+ return mi;
+}
+
+#define skipColor(_tscolor, _color, _ocolor) \
+ ((_tscolor) && (_color) && (_ocolor) && !((_color) & (_ocolor)))
+
+/* Add erase elements for older packages of same color (if any). */
+static void addUpgradeErasures(rpmts ts, tsMembers tsmem, rpm_color_t tscolor,
+ rpmte p, rpm_color_t hcolor, Header h)
+{
+ Header oh;
+ rpmdbMatchIterator mi = rpmtsInitIterator(ts, RPMDBI_NAME, rpmteN(p), 0);
+
+ while((oh = rpmdbNextIterator(mi)) != NULL) {
+ /* Ignore colored packages not in our rainbow. */
+ if (skipColor(tscolor, hcolor, headerGetNumber(oh, RPMTAG_HEADERCOLOR)))
+ continue;
+
+ /* Skip packages that contain identical NEVR. */
+ if (rpmVersionCompare(h, oh) == 0)
+ continue;
+
+ removePackage(ts, oh, p);
+ }
+ mi = rpmdbFreeIterator(mi);
+}
+
+/* Add erase elements for obsoleted packages of same color (if any). */
+static void addObsoleteErasures(rpmts ts, tsMembers tsmem, rpm_color_t tscolor,
+ rpmte p, rpm_color_t hcolor)
+{
+ rpmds obsoletes = rpmdsInit(rpmteDS(p, RPMTAG_OBSOLETENAME));
+ Header oh;
+
+ while (rpmdsNext(obsoletes) >= 0) {
+ const char * Name;
+ rpmdbMatchIterator mi = NULL;
+
+ if ((Name = rpmdsN(obsoletes)) == NULL)
+ continue; /* XXX can't happen */
+
+ /* XXX avoid self-obsoleting packages. */
+ if (rstreq(rpmteN(p), Name))
+ continue;
+
+ mi = rpmtsPrunedIterator(ts, RPMDBI_NAME, Name);
+
+ while((oh = rpmdbNextIterator(mi)) != NULL) {
+ /* Ignore colored packages not in our rainbow. */
+ if (skipColor(tscolor, hcolor,
+ headerGetNumber(oh, RPMTAG_HEADERCOLOR)))
+ continue;
+
+ /*
+ * Rpm prior to 3.0.3 does not have versioned obsoletes.
+ * If no obsoletes version info is available, match all names.
+ */
+ if (rpmdsEVR(obsoletes) == NULL
+ || rpmdsAnyMatchesDep(oh, obsoletes, _rpmds_nopromote)) {
+ char * ohNEVRA = headerGetAsString(oh, RPMTAG_NEVRA);
+ rpmlog(RPMLOG_DEBUG, " Obsoletes: %s\t\terases %s\n",
+ rpmdsDNEVR(obsoletes)+2, ohNEVRA);
+ ohNEVRA = _free(ohNEVRA);
+
+ removePackage(ts, oh, p);
+ }
+ }
+ mi = rpmdbFreeIterator(mi);
+ }
+}
+
+/*
+ * Check for previously added versions and obsoletions.
+ * Return index where to place this element, or -1 to skip.
+ */
+static int findPos(rpmts ts, rpm_color_t tscolor, Header h, int upgrade)
+{
+ int oc;
+ int obsolete = 0;
+ const char * arch = headerGetString(h, RPMTAG_ARCH);
+ const char * os = headerGetString(h, RPMTAG_OS);
+ rpmte p;
+ rpmds oldChk = rpmdsThis(h, RPMTAG_REQUIRENAME, (RPMSENSE_LESS));
+ rpmds newChk = rpmdsThis(h, RPMTAG_REQUIRENAME, (RPMSENSE_GREATER));
+ rpmds sameChk = rpmdsThis(h, RPMTAG_REQUIRENAME, (RPMSENSE_EQUAL));
+ rpmds obsChk = rpmdsNew(h, RPMTAG_OBSOLETENAME, 0);
+ rpmtsi pi = rpmtsiInit(ts);
+
+ /* XXX can't use rpmtsiNext() filter or oc will have wrong value. */
+ for (oc = 0; (p = rpmtsiNext(pi, 0)) != NULL; oc++) {
+ rpmds thisds, obsoletes;
+
+ /* Only added binary packages need checking */
+ if (rpmteType(p) == TR_REMOVED || rpmteIsSource(p))
+ continue;
+
+ /* Skip packages obsoleted by already added packages */
+ obsoletes = rpmdsInit(rpmteDS(p, RPMTAG_OBSOLETENAME));
+ while (rpmdsNext(obsoletes) >= 0) {
+ if (rpmdsCompare(obsoletes, sameChk)) {
+ obsolete = 1;
+ oc = -1;
+ break;
+ }
+ }
+
+ /* Replace already added obsoleted packages by obsoleting package */
+ thisds = rpmteDS(p, RPMTAG_NAME);
+ rpmdsInit(obsChk);
+ while (rpmdsNext(obsChk) >= 0) {
+ if (rpmdsCompare(obsChk, thisds)) {
+ obsolete = 1;
+ break;
+ }
+ }
+
+ if (obsolete)
+ break;
+
+ if (tscolor) {
+ const char * parch = rpmteA(p);
+ const char * pos = rpmteO(p);
+
+ if (arch == NULL || parch == NULL || os == NULL || pos == NULL)
+ continue;
+ if (!rstreq(arch, parch) || !rstreq(os, pos))
+ continue;
+ }
+
+ /*
+ * Always skip identical NEVR.
+ * On upgrade, if newer NEVR was previously added, skip adding older.
+ */
+ if (rpmdsCompare(sameChk, thisds) ||
+ (upgrade && rpmdsCompare(newChk, thisds))) {
+ oc = -1;
+ break;;
+ }
+
+ /* On upgrade, if older NEVR was previously added, replace with new */
+ if (upgrade && rpmdsCompare(oldChk, thisds) != 0) {
+ break;
+ }
+ }
+
+ /* If we broke out of the loop early we've something to say */
+ if (p != NULL && rpmIsVerbose()) {
+ char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
+ const char *msg = (oc < 0) ?
+ _("package %s was already added, skipping %s\n") :
+ _("package %s was already added, replacing with %s\n");
+ rpmlog(RPMLOG_WARNING, msg, rpmteNEVRA(p), nevra);
+ free(nevra);
+ }
+
+ rpmtsiFree(pi);
+ rpmdsFree(oldChk);
+ rpmdsFree(newChk);
+ rpmdsFree(sameChk);
+ rpmdsFree(obsChk);
+ return oc;
+}
+
+
+int rpmtsAddInstallElement(rpmts ts, Header h,
+ fnpyKey key, int upgrade, rpmRelocation * relocs)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpmte p = NULL;
+ int isSource = headerIsSource(h);
+ int ec = 0;
+ int oc = tsmem->orderCount;
+
+ /* Check for supported payload format if it's a package */
+ if (key && headerCheckPayloadFormat(h) != RPMRC_OK) {
+ ec = 1;
+ goto exit;
+ }
+
+ /* Check binary packages for redundancies in the set */
+ if (!isSource) {
+ oc = findPos(ts, tscolor, h, upgrade);
+ /* If we're replacing a previously added element, free the old one */
+ if (oc >= 0 && oc < tsmem->orderCount) {
+ rpmalDel(tsmem->addedPackages, tsmem->order[oc]);
+ tsmem->order[oc] = rpmteFree(tsmem->order[oc]);
+ /* If newer NEVR was already added, we're done */
+ } else if (oc < 0) {
+ goto exit;
+ }
+ }
+
+ if (tsmem->addedPackages == NULL) {
+ tsmem->addedPackages = rpmalCreate(5, tscolor, rpmtsPrefColor(ts));
+ }
+
+ if (oc >= tsmem->orderAlloced) {
+ tsmem->orderAlloced += (oc - tsmem->orderAlloced) + tsmem->delta;
+ tsmem->order = xrealloc(tsmem->order,
+ tsmem->orderAlloced * sizeof(*tsmem->order));
+ }
+
+ p = rpmteNew(ts, h, TR_ADDED, key, relocs);
+
+ tsmem->order[oc] = p;
+ if (oc == tsmem->orderCount) {
+ tsmem->orderCount++;
+ rpmcliPackagesTotal++;
+ }
+
+ rpmalAdd(tsmem->addedPackages, p);
+
+ /* If not upgrading or a source package, then we're done. */
+ if (!(upgrade & 0x1) || isSource)
+ goto exit;
+
+ /* Do lazy (readonly?) open of rpm database. */
+ if (rpmtsGetRdb(ts) == NULL && rpmtsGetDBMode(ts) != -1) {
+ if ((ec = rpmtsOpenDB(ts, rpmtsGetDBMode(ts))) != 0)
+ goto exit;
+ }
+
+ /* Add erasure elements for old versions and obsoletions */
+ addUpgradeErasures(ts, tsmem, tscolor, p, rpmteColor(p), h);
+ addObsoleteErasures(ts, tsmem, tscolor, p, rpmteColor(p));
+
+exit:
+ return ec;
+}
+
+int rpmtsAddEraseElement(rpmts ts, Header h, int dboffset)
+{
+ return removePackage(ts, h, NULL);
+}
+
+/* Cached rpmdb provide lookup, returns 0 if satisfied, 1 otherwise */
+static int rpmdbProvides(rpmts ts, depCache dcache, rpmds dep)
+{
+ const char * Name = rpmdsN(dep);
+ const char * DNEVR = rpmdsDNEVR(dep);
+ int *cachedrc = NULL;
+ rpmdbMatchIterator mi = NULL;
+ Header h = NULL;
+ int rc = 0;
+
+ /* See if we already looked this up */
+ if (depCacheGetEntry(dcache, DNEVR, &cachedrc, NULL, NULL)) {
+ rc = *cachedrc;
+ rpmdsNotify(dep, "(cached)", rc);
+ return rc;
+ }
+
+ /*
+ * See if a filename dependency is a real file in some package,
+ * taking file state into account: replaced, wrong colored and
+ * not installed files can not satisfy a dependency.
+ */
+ if (Name[0] == '/') {
+ mi = rpmtsPrunedIterator(ts, RPMDBI_BASENAMES, Name);
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ int fs = RPMFILE_STATE_MISSING;
+ struct rpmtd_s states;
+ if (headerGet(h, RPMTAG_FILESTATES, &states, HEADERGET_MINMEM)) {
+ rpmtdSetIndex(&states, rpmdbGetIteratorFileNum(mi));
+ fs = rpmtdGetNumber(&states);
+ rpmtdFreeData(&states);
+ }
+ if (fs == RPMFILE_STATE_NORMAL || fs == RPMFILE_STATE_NETSHARED) {
+ rpmdsNotify(dep, "(db files)", rc);
+ break;
+ }
+ }
+ rpmdbFreeIterator(mi);
+ }
+
+ /* Otherwise look in provides no matter what the dependency looks like */
+ if (h == NULL) {
+ mi = rpmtsPrunedIterator(ts, RPMDBI_PROVIDENAME, Name);
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ if (rpmdsAnyMatchesDep(h, dep, _rpmds_nopromote)) {
+ rpmdsNotify(dep, "(db provides)", rc);
+ break;
+ }
+ }
+ rpmdbFreeIterator(mi);
+ }
+ rc = (h != NULL) ? 0 : 1;
+
+ /* Cache the relatively expensive rpmdb lookup results */
+ depCacheAddEntry(dcache, xstrdup(DNEVR), rc);
+ return rc;
+}
+
+/**
+ * Check dep for an unsatisfied dependency.
+ * @param ts transaction set
+ * @param dep dependency
+ * @return 0 if satisfied, 1 if not satisfied
+ */
+static int unsatisfiedDepend(rpmts ts, depCache dcache, rpmds dep)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ int rc;
+ int retrying = 0;
+ int adding = (rpmdsInstance(dep) == 0);
+ rpmsenseFlags dsflags = rpmdsFlags(dep);
+
+retry:
+ rc = 0; /* assume dependency is satisfied */
+
+ /*
+ * New features in rpm packaging implicitly add versioned dependencies
+ * on rpmlib provides. The dependencies look like "rpmlib(YaddaYadda)".
+ * Check those dependencies now.
+ */
+ if (dsflags & RPMSENSE_RPMLIB) {
+ static int oneshot = -1;
+ if (oneshot)
+ oneshot = rpmdsRpmlib(&rpmlibP, NULL);
+
+ if (rpmlibP != NULL && rpmdsSearch(rpmlibP, dep) >= 0) {
+ rpmdsNotify(dep, "(rpmlib provides)", rc);
+ goto exit;
+ }
+ goto unsatisfied;
+ }
+
+ /* Dont look at pre-requisites of already installed packages */
+ if (!adding && isInstallPreReq(dsflags) && !isErasePreReq(dsflags))
+ goto exit;
+
+ /* Pretrans dependencies can't be satisfied by added packages. */
+ if (!(dsflags & RPMSENSE_PRETRANS)) {
+ rpmte match = rpmalSatisfiesDepend(tsmem->addedPackages, dep);
+
+ /*
+ * Handle definitive matches within the added package set.
+ * Self-obsoletes and -conflicts fall through here as we need to
+ * check for possible other matches in the rpmdb.
+ */
+ if (match) {
+ rpmTagVal dtag = rpmdsTagN(dep);
+ /* Requires match, look no further */
+ if (dtag == RPMTAG_REQUIRENAME)
+ goto exit;
+
+ /* Conflicts/obsoletes match on another package, look no further */
+ if (rpmteDS(match, dtag) != dep)
+ goto exit;
+ }
+ }
+
+ /* See if the rpmdb provides it */
+ if (rpmdbProvides(ts, dcache, dep) == 0)
+ goto exit;
+
+ /* Search for an unsatisfied dependency. */
+ if (adding && !retrying && !(dsflags & RPMSENSE_PRETRANS)) {
+ int xx = rpmtsSolve(ts, dep);
+ if (xx == 0)
+ goto exit;
+ if (xx == -1) {
+ retrying = 1;
+ goto retry;
+ }
+ }
+
+unsatisfied:
+ rc = 1; /* dependency is unsatisfied */
+ rpmdsNotify(dep, NULL, rc);
+
+exit:
+ return rc;
+}
+
+/* Check a dependency set for problems */
+static void checkDS(rpmts ts, depCache dcache, rpmte te,
+ const char * pkgNEVRA, rpmds ds,
+ const char * depName, rpm_color_t tscolor)
+{
+ rpm_color_t dscolor;
+ /* require-problems are unsatisfied, others appear "satisfied" */
+ int is_problem = (rpmdsTagN(ds) == RPMTAG_REQUIRENAME);
+
+ ds = rpmdsInit(ds);
+ while (rpmdsNext(ds) >= 0) {
+ /* Filter out dependencies that came along for the ride. */
+ if (depName != NULL && !rstreq(depName, rpmdsN(ds)))
+ continue;
+
+ /* Ignore colored dependencies not in our rainbow. */
+ dscolor = rpmdsColor(ds);
+ if (tscolor && dscolor && !(tscolor & dscolor))
+ continue;
+
+ if (unsatisfiedDepend(ts, dcache, ds) == is_problem)
+ rpmteAddDepProblem(te, pkgNEVRA, ds, NULL);
+ }
+}
+
+/* Check a given dependency type against installed packages */
+static void checkInstDeps(rpmts ts, depCache dcache, rpmte te,
+ rpmTag depTag, const char *dep)
+{
+ Header h;
+ rpmdbMatchIterator mi = rpmtsPrunedIterator(ts, depTag, dep);
+
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ char * pkgNEVRA = headerGetAsString(h, RPMTAG_NEVRA);
+ rpmds ds = rpmdsNew(h, depTag, 0);
+
+ checkDS(ts, dcache, te, pkgNEVRA, ds, dep, 0);
+
+ ds = rpmdsFree(ds);
+ pkgNEVRA = _free(pkgNEVRA);
+ }
+ rpmdbFreeIterator(mi);
+}
+
+int rpmtsCheck(rpmts ts)
+{
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpmtsi pi = NULL; rpmte p;
+ int closeatexit = 0;
+ int rc = 0;
+ depCache dcache = NULL;
+
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_CHECK), 0);
+
+ /* Do lazy, readonly, open of rpm database. */
+ if (rpmtsGetRdb(ts) == NULL && rpmtsGetDBMode(ts) != -1) {
+ if ((rc = rpmtsOpenDB(ts, rpmtsGetDBMode(ts))) != 0)
+ goto exit;
+ closeatexit = 1;
+ }
+
+ /* XXX FIXME: figure some kind of heuristic for the cache size */
+ dcache = depCacheCreate(5001, hashFunctionString, strcmp,
+ (depCacheFreeKey)rfree, NULL);
+
+ /*
+ * Look at all of the added packages and make sure their dependencies
+ * are satisfied.
+ */
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, TR_ADDED)) != NULL) {
+ rpmds provides = rpmdsInit(rpmteDS(p, RPMTAG_PROVIDENAME));
+
+ rpmlog(RPMLOG_DEBUG, "========== +++ %s %s/%s 0x%x\n",
+ rpmteNEVR(p), rpmteA(p), rpmteO(p), rpmteColor(p));
+
+ checkDS(ts, dcache, p, rpmteNEVRA(p), rpmteDS(p, RPMTAG_REQUIRENAME),
+ NULL, tscolor);
+ checkDS(ts, dcache, p, rpmteNEVRA(p), rpmteDS(p, RPMTAG_CONFLICTNAME),
+ NULL, tscolor);
+
+ /* Check provides against conflicts in installed packages. */
+ while (rpmdsNext(provides) >= 0) {
+ checkInstDeps(ts, dcache, p, RPMTAG_CONFLICTNAME, rpmdsN(provides));
+ }
+
+ /* Skip obsoletion checks for source packages (ie build) */
+ if (rpmteIsSource(p))
+ continue;
+
+ /* Check package name (not provides!) against installed obsoletes */
+ checkInstDeps(ts, dcache, p, RPMTAG_OBSOLETENAME, rpmteN(p));
+ }
+ pi = rpmtsiFree(pi);
+
+ /*
+ * Look at the removed packages and make sure they aren't critical.
+ */
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, TR_REMOVED)) != NULL) {
+ rpmds provides = rpmdsInit(rpmteDS(p, RPMTAG_PROVIDENAME));
+ rpmfi fi = rpmfiInit(rpmteFI(p), 0);
+
+ rpmlog(RPMLOG_DEBUG, "========== --- %s %s/%s 0x%x\n",
+ rpmteNEVR(p), rpmteA(p), rpmteO(p), rpmteColor(p));
+
+ /* Check provides and filenames against installed dependencies. */
+ while (rpmdsNext(provides) >= 0) {
+ checkInstDeps(ts, dcache, p, RPMTAG_REQUIRENAME, rpmdsN(provides));
+ }
+
+ while (rpmfiNext(fi) >= 0) {
+ checkInstDeps(ts, dcache, p, RPMTAG_REQUIRENAME, rpmfiFN(fi));
+ }
+ }
+ pi = rpmtsiFree(pi);
+
+exit:
+ depCacheFree(dcache);
+
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_CHECK), 0);
+
+ if (closeatexit)
+ (void) rpmtsCloseDB(ts);
+ return rc;
+}
diff --git a/lib/formats.c b/lib/formats.c
new file mode 100644
index 0000000..7ce4608
--- /dev/null
+++ b/lib/formats.c
@@ -0,0 +1,705 @@
+/** \ingroup header
+ * \file lib/formats.c
+ */
+
+#include "system.h"
+
+#include <inttypes.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmtd.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmmacro.h>
+
+#include "rpmio/digest.h"
+#include "lib/manifest.h"
+#include "lib/misc.h"
+
+#include "debug.h"
+
+/** \ingroup header
+ * Define header tag output formats.
+ */
+
+struct headerFormatFunc_s {
+ rpmtdFormats fmt; /*!< Value of extension */
+ const char *name; /*!< Name of extension. */
+ headerTagFormatFunction func; /*!< Pointer to formatter function. */
+};
+
+/**
+ * barebones string representation with no extra formatting
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * stringFormat(rpmtd td, char *formatPrefix)
+{
+ const char *str = NULL;
+ char *val = NULL, *buf = NULL;
+
+ switch (rpmtdClass(td)) {
+ case RPM_NUMERIC_CLASS:
+ strcat(formatPrefix, PRIu64);
+ rasprintf(&val, formatPrefix, rpmtdGetNumber(td));
+ break;
+ case RPM_STRING_CLASS:
+ str = rpmtdGetString(td);
+ strcat(formatPrefix, "s");
+ rasprintf(&val, formatPrefix, str);
+ break;
+ case RPM_BINARY_CLASS:
+ buf = pgpHexStr(td->data, td->count);
+ strcat(formatPrefix, "s");
+ rasprintf(&val, formatPrefix, buf);
+ free(buf);
+ break;
+ default:
+ val = xstrdup("(unknown type)");
+ break;
+ }
+ return val;
+}
+
+static char * numFormat(rpmtd td, char * formatPrefix, char *format)
+{
+ char * val = NULL;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ strcat(formatPrefix, format);
+ rasprintf(&val, formatPrefix, rpmtdGetNumber(td));
+ }
+
+ return val;
+}
+/**
+ * octalFormat.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * octalFormat(rpmtd td, char * formatPrefix)
+{
+ return numFormat(td, formatPrefix, "o");
+}
+
+/**
+ * hexFormat.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * hexFormat(rpmtd td, char * formatPrefix)
+{
+ return numFormat(td, formatPrefix, "x");
+}
+
+/**
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * realDateFormat(rpmtd td, char * formatPrefix,
+ const char * strftimeFormat)
+{
+ char * val = NULL;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ struct tm * tstruct;
+ char buf[50];
+ time_t dateint = rpmtdGetNumber(td);
+ tstruct = localtime(&dateint);
+
+ strcat(formatPrefix, "s");
+
+ buf[0] = '\0';
+ if (tstruct)
+ (void) strftime(buf, sizeof(buf) - 1, strftimeFormat, tstruct);
+ rasprintf(&val, formatPrefix, buf);
+ }
+
+ return val;
+}
+
+/**
+ * Format a date.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * dateFormat(rpmtd td, char * formatPrefix)
+{
+ return realDateFormat(td, formatPrefix, _("%c"));
+}
+
+/**
+ * Format a day.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * dayFormat(rpmtd td, char * formatPrefix)
+{
+ return realDateFormat(td, formatPrefix, _("%a %b %d %Y"));
+}
+
+/**
+ * Return shell escape formatted data.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * shescapeFormat(rpmtd td, char * formatPrefix)
+{
+ char * result = NULL, * dst, * src;
+
+ if (rpmtdClass(td) == RPM_NUMERIC_CLASS) {
+ strcat(formatPrefix, PRIu64);
+ rasprintf(&result, formatPrefix, rpmtdGetNumber(td));
+ } else {
+ char *buf = NULL;
+ strcat(formatPrefix, "s");
+ rasprintf(&buf, formatPrefix, rpmtdGetString(td));
+
+ result = dst = xmalloc(strlen(buf) * 4 + 3);
+ *dst++ = '\'';
+ for (src = buf; *src != '\0'; src++) {
+ if (*src == '\'') {
+ *dst++ = '\'';
+ *dst++ = '\\';
+ *dst++ = '\'';
+ *dst++ = '\'';
+ } else {
+ *dst++ = *src;
+ }
+ }
+ *dst++ = '\'';
+ *dst = '\0';
+ free(buf);
+ }
+
+ return result;
+}
+
+
+/**
+ * Identify type of trigger.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * triggertypeFormat(rpmtd td, char * formatPrefix)
+{
+ char * val;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ uint64_t item = rpmtdGetNumber(td);
+ if (item & RPMSENSE_TRIGGERPREIN)
+ val = xstrdup("prein");
+ else if (item & RPMSENSE_TRIGGERIN)
+ val = xstrdup("in");
+ else if (item & RPMSENSE_TRIGGERUN)
+ val = xstrdup("un");
+ else if (item & RPMSENSE_TRIGGERPOSTUN)
+ val = xstrdup("postun");
+ else
+ val = xstrdup("");
+ }
+ return val;
+}
+
+/**
+ * Identify type of dependency.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * deptypeFormat(rpmtd td, char * formatPrefix)
+{
+ char *val = NULL;
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ ARGV_t sdeps = NULL;
+ uint64_t item = rpmtdGetNumber(td);
+
+ if (item & RPMSENSE_SCRIPT_PRE)
+ argvAdd(&sdeps, "pre");
+ if (item & RPMSENSE_SCRIPT_POST)
+ argvAdd(&sdeps, "post");
+ if (item & RPMSENSE_SCRIPT_PREUN)
+ argvAdd(&sdeps, "preun");
+ if (item & RPMSENSE_SCRIPT_POSTUN)
+ argvAdd(&sdeps, "postun");
+ if (item & RPMSENSE_SCRIPT_VERIFY)
+ argvAdd(&sdeps, "verify");
+ if (item & RPMSENSE_INTERP)
+ argvAdd(&sdeps, "interp");
+ if (item & RPMSENSE_RPMLIB)
+ argvAdd(&sdeps, "rpmlib");
+ if ((item & RPMSENSE_FIND_REQUIRES) || (item & RPMSENSE_FIND_PROVIDES))
+ argvAdd(&sdeps, "auto");
+ if (item & RPMSENSE_PREREQ)
+ argvAdd(&sdeps, "prereq");
+ if (item & RPMSENSE_PRETRANS)
+ argvAdd(&sdeps, "pretrans");
+ if (item & RPMSENSE_POSTTRANS)
+ argvAdd(&sdeps, "posttrans");
+
+ if (sdeps) {
+ val = argvJoin(sdeps, ",");
+ } else {
+ val = xstrdup("manual");
+ }
+
+ argvFree(sdeps);
+ }
+ return val;
+}
+
+/**
+ * Format file permissions for display.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * permsFormat(rpmtd td, char * formatPrefix)
+{
+ char * val = NULL;
+ char * buf;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ strcat(formatPrefix, "s");
+ buf = rpmPermsString(rpmtdGetNumber(td));
+ rasprintf(&val, formatPrefix, buf);
+ buf = _free(buf);
+ }
+
+ return val;
+}
+
+/**
+ * Format file flags for display.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * fflagsFormat(rpmtd td, char * formatPrefix)
+{
+ char * val = NULL;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ char *buf = rpmFFlagsString(rpmtdGetNumber(td), "");
+ strcat(formatPrefix, "s");
+ rasprintf(&val, formatPrefix, buf);
+ free(buf);
+ }
+
+ return val;
+}
+
+/**
+ * Wrap a pubkey in ascii armor for display.
+ * @todo Permit selectable display formats (i.e. binary).
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * armorFormat(rpmtd td, char * formatPrefix)
+{
+ const char * enc;
+ const unsigned char * s;
+ unsigned char * bs = NULL;
+ char *val;
+ size_t ns;
+ int atype;
+
+ switch (rpmtdType(td)) {
+ case RPM_BIN_TYPE:
+ s = td->data;
+ /* XXX HACK ALERT: element field abused as no. bytes of binary data. */
+ ns = td->count;
+ atype = PGPARMOR_SIGNATURE; /* XXX check pkt for signature */
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ enc = rpmtdGetString(td);
+ if (b64decode(enc, (void **)&bs, &ns))
+ return xstrdup(_("(not base64)"));
+ s = bs;
+ atype = PGPARMOR_PUBKEY; /* XXX check pkt for pubkey */
+ break;
+ case RPM_NULL_TYPE:
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ case RPM_INT16_TYPE:
+ case RPM_INT32_TYPE:
+ case RPM_INT64_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ default:
+ return xstrdup(_("(invalid type)"));
+ break;
+ }
+
+ /* XXX this doesn't use padding directly, assumes enough slop in retval. */
+ val = pgpArmorWrap(atype, s, ns);
+ if (atype == PGPARMOR_PUBKEY) {
+ free(bs);
+ }
+ return val;
+}
+
+/**
+ * Encode binary data in base64 for display.
+ * @todo Permit selectable display formats (i.e. binary).
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * base64Format(rpmtd td, char * formatPrefix)
+{
+ char * val = NULL;
+
+ if (rpmtdType(td) != RPM_BIN_TYPE) {
+ val = xstrdup(_("(not a blob)"));
+ } else {
+ char * enc;
+ if ((enc = b64encode(td->data, td->count, -1)) != NULL) {
+ strcat(formatPrefix, "s");
+ rasprintf(&val, formatPrefix, enc ? enc : "");
+ free(enc);
+ }
+ }
+
+ return val;
+}
+
+/**
+ * Wrap tag data in simple header xml markup.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * xmlFormat(rpmtd td, char * formatPrefix)
+{
+ const char *xtag = NULL;
+ char *val = NULL;
+ char *s = NULL;
+ rpmtdFormats fmt = RPMTD_FORMAT_STRING;
+
+ switch (rpmtdClass(td)) {
+ case RPM_STRING_CLASS:
+ xtag = "string";
+ break;
+ case RPM_BINARY_CLASS:
+ fmt = RPMTD_FORMAT_BASE64;
+ xtag = "base64";
+ break;
+ case RPM_NUMERIC_CLASS:
+ xtag = "integer";
+ break;
+ case RPM_NULL_TYPE:
+ default:
+ return xstrdup(_("(invalid xml type)"));
+ break;
+ }
+
+ /* XXX TODO: handle errors */
+ s = rpmtdFormat(td, fmt, NULL);
+
+ if (s[0] == '\0') {
+ val = rstrscat(NULL, "\t<", xtag, "/>", NULL);
+ } else {
+ char *new_s = NULL;
+ size_t i, s_size = strlen(s);
+
+ for (i=0; i<s_size; i++) {
+ switch (s[i]) {
+ case '<': rstrcat(&new_s, "&lt;"); break;
+ case '>': rstrcat(&new_s, "&gt;"); break;
+ case '&': rstrcat(&new_s, "&amp;"); break;
+ default: {
+ char c[2] = " ";
+ *c = s[i];
+ rstrcat(&new_s, c);
+ break;
+ }
+ }
+ }
+
+ val = rstrscat(NULL, "\t<", xtag, ">", new_s, "</", xtag, ">", NULL);
+ free(new_s);
+ }
+ free(s);
+
+ strcat(formatPrefix, "s");
+ return val;
+}
+
+/**
+ * Display signature fingerprint and time.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * pgpsigFormat(rpmtd td, char * formatPrefix)
+{
+ char * val, * t;
+
+ if (rpmtdType(td) != RPM_BIN_TYPE) {
+ val = xstrdup(_("(not a blob)"));
+ } else {
+ const uint8_t * pkt = td->data;
+ size_t pktlen = 0;
+ unsigned int v = *pkt;
+ pgpTag tag = 0;
+ size_t plen;
+ size_t hlen = 0;
+
+ if (v & 0x80) {
+ if (v & 0x40) {
+ tag = (v & 0x3f);
+ plen = pgpLen(pkt+1, &hlen);
+ } else {
+ tag = (v >> 2) & 0xf;
+ plen = (1 << (v & 0x3));
+ hlen = pgpGrab(pkt+1, plen);
+ }
+
+ pktlen = 1 + plen + hlen;
+ }
+
+ if (pktlen == 0 || tag != PGPTAG_SIGNATURE) {
+ val = xstrdup(_("(not an OpenPGP signature)"));
+ } else {
+ pgpDig dig = pgpNewDig();
+ pgpDigParams sigp = &dig->signature;
+ size_t nb = 0;
+ char *tempstr = NULL;
+
+ (void) pgpPrtPkts(pkt, pktlen, dig, 0);
+
+ val = NULL;
+ again:
+ nb += 100;
+ val = t = xrealloc(val, nb + 1);
+
+ t = stpcpy(t, pgpValString(PGPVAL_PUBKEYALGO, sigp->pubkey_algo));
+ if (t + 5 >= val + nb)
+ goto again;
+ *t++ = '/';
+ t = stpcpy(t, pgpValString(PGPVAL_HASHALGO, sigp->hash_algo));
+ if (t + strlen (", ") + 1 >= val + nb)
+ goto again;
+
+ t = stpcpy(t, ", ");
+
+ /* this is important if sizeof(int32_t) ! sizeof(time_t) */
+ { time_t dateint = pgpGrab(sigp->time, sizeof(sigp->time));
+ struct tm * tstruct = localtime(&dateint);
+ if (tstruct)
+ (void) strftime(t, (nb - (t - val)), "%c", tstruct);
+ }
+ t += strlen(t);
+ if (t + strlen (", Key ID ") + 1 >= val + nb)
+ goto again;
+ t = stpcpy(t, ", Key ID ");
+ tempstr = pgpHexStr(sigp->signid, sizeof(sigp->signid));
+ if (t + strlen (tempstr) > val + nb)
+ goto again;
+ t = stpcpy(t, tempstr);
+ free(tempstr);
+
+ dig = pgpFreeDig(dig);
+ }
+ }
+
+ return val;
+}
+
+/**
+ * Format dependency flags for display.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * depflagsFormat(rpmtd td, char * formatPrefix)
+{
+ char * val = NULL;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ uint64_t anint = rpmtdGetNumber(td);
+ char buf[10];
+ buf[0] = '\0';
+
+ if (anint & RPMSENSE_LESS)
+ strcat(buf, "<");
+ if (anint & RPMSENSE_GREATER)
+ strcat(buf, ">");
+ if (anint & RPMSENSE_EQUAL)
+ strcat(buf, "=");
+
+ strcat(formatPrefix, "s");
+ rasprintf(&val, formatPrefix, buf);
+ }
+
+ return val;
+}
+
+/**
+ * Return tag container array size.
+ * @param td tag data container
+ * @param formatPrefix sprintf format string
+ * @return formatted string
+ */
+static char * arraysizeFormat(rpmtd td, char * formatPrefix)
+{
+ char *val = NULL;
+ strcat(formatPrefix, "u");
+ rasprintf(&val, formatPrefix, rpmtdCount(td));
+ return val;
+}
+
+static char * fstateFormat(rpmtd td, char * formatPrefix)
+{
+ char * val = NULL;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ const char * str;
+ rpmfileState fstate = rpmtdGetNumber(td);
+ switch (fstate) {
+ case RPMFILE_STATE_NORMAL:
+ str = _("normal");
+ break;
+ case RPMFILE_STATE_REPLACED:
+ str = _("replaced");
+ break;
+ case RPMFILE_STATE_NOTINSTALLED:
+ str = _("not installed");
+ break;
+ case RPMFILE_STATE_NETSHARED:
+ str = _("net shared");
+ break;
+ case RPMFILE_STATE_WRONGCOLOR:
+ str = _("wrong color");
+ break;
+ case RPMFILE_STATE_MISSING:
+ str = _("missing");
+ break;
+ default:
+ str = _("(unknown)");
+ break;
+ }
+
+ strcat(formatPrefix, "s");
+ rasprintf(&val, formatPrefix, str);
+ }
+ return val;
+}
+
+static char * verifyFlags(rpmtd td, char * formatPrefix, const char *pad)
+{
+ char * val = NULL;
+
+ if (rpmtdClass(td) != RPM_NUMERIC_CLASS) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ strcat(formatPrefix, "s");
+ char *buf = rpmVerifyString(rpmtdGetNumber(td), pad);
+ rasprintf(&val, formatPrefix, buf);
+ buf = _free(buf);
+ }
+ return val;
+}
+
+static char * vflagsFormat(rpmtd td, char * formatPrefix)
+{
+ return verifyFlags(td, formatPrefix, "");
+}
+
+static char * fstatusFormat(rpmtd td, char * formatPrefix)
+{
+ return verifyFlags(td, formatPrefix, ".");
+}
+
+static char * expandFormat(rpmtd td, char * formatPrefix)
+{
+ char *val = NULL;
+ if (rpmtdClass(td) != RPM_STRING_CLASS) {
+ val = xstrdup(_("(not a string)"));
+ } else {
+ val = rpmExpand(td->data, NULL);
+ }
+ strcat(formatPrefix, "s");
+ return val;
+}
+
+static const struct headerFormatFunc_s rpmHeaderFormats[] = {
+ { RPMTD_FORMAT_STRING, "string", stringFormat },
+ { RPMTD_FORMAT_ARMOR, "armor", armorFormat },
+ { RPMTD_FORMAT_BASE64, "base64", base64Format },
+ { RPMTD_FORMAT_PGPSIG, "pgpsig", pgpsigFormat },
+ { RPMTD_FORMAT_DEPFLAGS, "depflags", depflagsFormat },
+ { RPMTD_FORMAT_DEPTYPE, "deptype", deptypeFormat },
+ { RPMTD_FORMAT_FFLAGS, "fflags", fflagsFormat },
+ { RPMTD_FORMAT_PERMS, "perms", permsFormat },
+ { RPMTD_FORMAT_PERMS, "permissions", permsFormat },
+ { RPMTD_FORMAT_TRIGGERTYPE, "triggertype", triggertypeFormat },
+ { RPMTD_FORMAT_XML, "xml", xmlFormat },
+ { RPMTD_FORMAT_OCTAL, "octal", octalFormat },
+ { RPMTD_FORMAT_HEX, "hex", hexFormat },
+ { RPMTD_FORMAT_DATE, "date", dateFormat },
+ { RPMTD_FORMAT_DAY, "day", dayFormat },
+ { RPMTD_FORMAT_SHESCAPE, "shescape", shescapeFormat },
+ { RPMTD_FORMAT_ARRAYSIZE, "arraysize", arraysizeFormat },
+ { RPMTD_FORMAT_FSTATE, "fstate", fstateFormat },
+ { RPMTD_FORMAT_VFLAGS, "vflags", vflagsFormat },
+ { RPMTD_FORMAT_EXPAND, "expand", expandFormat },
+ { RPMTD_FORMAT_FSTATUS, "fstatus", fstatusFormat },
+ { -1, NULL, NULL }
+};
+
+headerTagFormatFunction rpmHeaderFormatFuncByName(const char *fmt)
+{
+ const struct headerFormatFunc_s * ext;
+ headerTagFormatFunction func = NULL;
+
+ for (ext = rpmHeaderFormats; ext->name != NULL; ext++) {
+ if (rstreq(ext->name, fmt)) {
+ func = ext->func;
+ break;
+ }
+ }
+ return func;
+}
+
+headerTagFormatFunction rpmHeaderFormatFuncByValue(rpmtdFormats fmt)
+{
+ const struct headerFormatFunc_s * ext;
+ headerTagFormatFunction func = NULL;
+
+ for (ext = rpmHeaderFormats; ext->name != NULL; ext++) {
+ if (fmt == ext->fmt) {
+ func = ext->func;
+ break;
+ }
+ }
+ return func;
+}
+
diff --git a/lib/fprint.c b/lib/fprint.c
new file mode 100644
index 0000000..2289224
--- /dev/null
+++ b/lib/fprint.c
@@ -0,0 +1,365 @@
+/**
+ * \file lib/fprint.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmfileutil.h> /* for rpmCleanPath */
+
+#include "lib/rpmdb_internal.h"
+#include "lib/rpmfi_internal.h"
+#include "lib/fprint.h"
+#include "lib/misc.h"
+#include "debug.h"
+#include <libgen.h>
+
+/* Create new hash table type rpmFpEntryHash */
+#include "lib/rpmhash.C"
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+#define HASHTYPE rpmFpEntryHash
+#define HTKEYTYPE const char *
+#define HTDATATYPE const struct fprintCacheEntry_s *
+#include "lib/rpmhash.C"
+
+fingerPrintCache fpCacheCreate(int sizeHint)
+{
+ fingerPrintCache fpc;
+
+ fpc = xmalloc(sizeof(*fpc));
+ fpc->ht = rpmFpEntryHashCreate(sizeHint, hashFunctionString, strcmp,
+ (rpmFpEntryHashFreeKey)free,
+ (rpmFpEntryHashFreeData)free);
+ return fpc;
+}
+
+fingerPrintCache fpCacheFree(fingerPrintCache cache)
+{
+ if (cache) {
+ cache->ht = rpmFpEntryHashFree(cache->ht);
+ free(cache);
+ }
+ return NULL;
+}
+
+/**
+ * Find directory name entry in cache.
+ * @param cache pointer to fingerprint cache
+ * @param dirName string to locate in cache
+ * @return pointer to directory name entry (or NULL if not found).
+ */
+static const struct fprintCacheEntry_s * cacheContainsDirectory(
+ fingerPrintCache cache,
+ const char * dirName)
+{
+ const struct fprintCacheEntry_s ** data;
+
+ if (rpmFpEntryHashGetEntry(cache->ht, dirName, &data, NULL, NULL))
+ return data[0];
+ return NULL;
+}
+
+/**
+ * Return finger print of a file path.
+ * @param cache pointer to fingerprint cache
+ * @param dirName leading directory name of path
+ * @param baseName file name of path
+ * @param scareMemory
+ * @return pointer to the finger print associated with a file path.
+ */
+fingerPrint fpLookup(fingerPrintCache cache,
+ const char * dirName, const char * baseName, int scareMemory)
+{
+ char dir[PATH_MAX];
+ const char * cleanDirName;
+ size_t cdnl;
+ char * end; /* points to the '\0' at the end of "buf" */
+ fingerPrint fp;
+ struct stat sb;
+ char *buf = NULL;
+ char *cdnbuf = NULL;
+ const struct fprintCacheEntry_s * cacheHit;
+
+ /* assert(*dirName == '/' || !scareMemory); */
+
+ /* XXX WATCHOUT: fp.subDir is set below from relocated dirName arg */
+ cleanDirName = dirName;
+ cdnl = strlen(cleanDirName);
+
+ if (*cleanDirName == '/') {
+ if (!scareMemory) {
+ cdnbuf = xstrdup(dirName);
+ char trailingslash = (cdnbuf[strlen(cdnbuf)-1] == '/');
+ cdnbuf = rpmCleanPath(cdnbuf);
+ if (trailingslash) {
+ cdnbuf = rstrcat(&cdnbuf, "/");
+ }
+ cleanDirName = cdnbuf;
+ cdnl = strlen(cleanDirName);
+ }
+ } else {
+ scareMemory = 0; /* XXX causes memory leak */
+
+ /* Using realpath on the arg isn't correct if the arg is a symlink,
+ * especially if the symlink is a dangling link. What we
+ * do instead is use realpath() on `.' and then append arg to
+ * the result.
+ */
+
+ /* if the current directory doesn't exist, we might fail.
+ oh well. likewise if it's too long. */
+ dir[0] = '\0';
+ if (realpath(".", dir) != NULL) {
+ end = dir + strlen(dir);
+ if (end[-1] != '/') *end++ = '/';
+ end = stpncpy(end, cleanDirName, sizeof(dir) - (end - dir));
+ *end = '\0';
+ (void)rpmCleanPath(dir); /* XXX possible /../ from concatenation */
+ end = dir + strlen(dir);
+ if (end[-1] != '/') *end++ = '/';
+ *end = '\0';
+ cleanDirName = dir;
+ cdnl = end - dir;
+ }
+ }
+ fp.entry = NULL;
+ fp.subDir = NULL;
+ fp.baseName = NULL;
+ if (cleanDirName == NULL) goto exit; /* XXX can't happen */
+
+ buf = xstrdup(cleanDirName);
+ end = buf + cdnl;
+
+ /* no need to pay attention to that extra little / at the end of dirName */
+ if (buf[1] && end[-1] == '/') {
+ end--;
+ *end = '\0';
+ }
+
+ while (1) {
+
+ /* as we're stating paths here, we want to follow symlinks */
+
+ cacheHit = cacheContainsDirectory(cache, (*buf != '\0' ? buf : "/"));
+ if (cacheHit != NULL) {
+ fp.entry = cacheHit;
+ } else if (!stat((*buf != '\0' ? buf : "/"), &sb)) {
+ struct fprintCacheEntry_s * newEntry = xmalloc(sizeof(* newEntry));
+
+ newEntry->ino = sb.st_ino;
+ newEntry->dev = sb.st_dev;
+ newEntry->dirName = xstrdup((*buf != '\0' ? buf : "/"));
+ fp.entry = newEntry;
+
+ rpmFpEntryHashAddEntry(cache->ht, newEntry->dirName, fp.entry);
+ }
+
+ if (fp.entry) {
+ fp.subDir = cleanDirName + (end - buf);
+ if (fp.subDir[0] == '/' && fp.subDir[1] != '\0')
+ fp.subDir++;
+ if (fp.subDir[0] == '\0' ||
+ /* XXX don't bother saving '/' as subdir */
+ (fp.subDir[0] == '/' && fp.subDir[1] == '\0'))
+ fp.subDir = NULL;
+ fp.baseName = baseName;
+ if (!scareMemory && fp.subDir != NULL)
+ fp.subDir = xstrdup(fp.subDir);
+ /* FIX: fp.entry.{dirName,dev,ino} undef @*/
+ goto exit;
+ }
+
+ /* stat of '/' just failed! */
+ if (end == buf + 1)
+ abort();
+
+ end--;
+ while ((end > buf) && *end != '/') end--;
+ if (end == buf) /* back to stat'ing just '/' */
+ end++;
+
+ *end = '\0';
+ }
+
+exit:
+ free(buf);
+ free(cdnbuf);
+ /* FIX: fp.entry.{dirName,dev,ino} undef @*/
+ return fp;
+}
+
+unsigned int fpHashFunction(const fingerPrint * fp)
+{
+ unsigned int hash = 0;
+ int j;
+
+ hash = hashFunctionString(fp->baseName);
+ if (fp->subDir) hash ^= hashFunctionString(fp->subDir);
+
+ hash ^= ((unsigned)fp->entry->dev);
+ for (j=0; j<4; j++) hash ^= ((fp->entry->ino >> (8*j)) & 0xFF) << ((3-j)*8);
+
+ return hash;
+}
+
+int fpEqual(const fingerPrint * k1, const fingerPrint * k2)
+{
+ /* If the addresses are the same, so are the values. */
+ if (k1 == k2)
+ return 0;
+
+ /* Otherwise, compare fingerprints by value. */
+ if (FP_EQUAL(*k1, *k2))
+ return 0;
+ return 1;
+
+}
+
+void fpLookupList(fingerPrintCache cache, const char ** dirNames,
+ const char ** baseNames, const uint32_t * dirIndexes,
+ int fileCount, fingerPrint * fpList)
+{
+ int i;
+
+ for (i = 0; i < fileCount; i++) {
+ /* If this is in the same directory as the last file, don't bother
+ redoing all of this work */
+ if (i > 0 && dirIndexes[i - 1] == dirIndexes[i]) {
+ fpList[i].entry = fpList[i - 1].entry;
+ fpList[i].subDir = fpList[i - 1].subDir;
+ fpList[i].baseName = baseNames[i];
+ } else {
+ fpList[i] = fpLookup(cache, dirNames[dirIndexes[i]], baseNames[i],
+ 1);
+ }
+ }
+}
+
+void fpLookupSubdir(rpmFpHash symlinks, rpmFpHash fphash, fingerPrintCache fpc, rpmte p, int filenr)
+{
+ rpmfi fi = rpmteFI(p);
+ struct fingerPrint_s current_fp;
+ char *endsubdir, *endbasename, *currentsubdir;
+ size_t lensubDir;
+
+ struct rpmffi_s * recs;
+ int numRecs;
+ int i, fiFX;
+ fingerPrint *fp = rpmfiFpsIndex(fi, filenr);
+ int symlinkcount = 0;
+ struct rpmffi_s ffi = { p, filenr};
+
+ if (fp->subDir == NULL) {
+ rpmFpHashAddEntry(fphash, fp, ffi);
+ return;
+ }
+
+ lensubDir = strlen(fp->subDir);
+ current_fp = *fp;
+ currentsubdir = xstrdup(fp->subDir);
+
+ /* Set baseName to the upper most dir */
+ current_fp.baseName = endbasename = currentsubdir;
+ while (*endbasename != '/' && endbasename < currentsubdir + lensubDir - 1)
+ endbasename++;
+ *endbasename = '\0';
+
+ current_fp.subDir = endsubdir = NULL; // no subDir for now
+
+ while (endbasename < currentsubdir + lensubDir - 1) {
+ char found;
+ found = 0;
+
+ rpmFpHashGetEntry(symlinks, &current_fp,
+ &recs, &numRecs, NULL);
+
+ for (i=0; i<numRecs; i++) {
+ rpmfi foundfi;
+ int filenr;
+ char const *linktarget;
+ char *link;
+
+ foundfi = rpmteFI(recs[i].p);
+ fiFX = rpmfiFX(foundfi);
+
+ filenr = recs[i].fileno;
+ rpmfiSetFX(foundfi, filenr);
+ linktarget = rpmfiFLink(foundfi);
+
+ if (linktarget && *linktarget != '\0') {
+ /* this "directory" is a symlink */
+ link = NULL;
+ if (*linktarget != '/') {
+ rstrscat(&link, current_fp.entry->dirName,
+ current_fp.subDir ? "/" : "",
+ current_fp.subDir ? current_fp.subDir : "",
+ "/", NULL);
+ }
+ rstrscat(&link, linktarget, "/", NULL);
+ if (strlen(endbasename+1)) {
+ rstrscat(&link, endbasename+1, "/", NULL);
+ }
+
+ *fp = fpLookup(fpc, link, fp->baseName, 0);
+
+ free(link);
+ free(currentsubdir);
+ symlinkcount++;
+
+ /* setup current_fp for the new path */
+ found = 1;
+ current_fp = *fp;
+ if (fp->subDir == NULL) {
+ /* directory exists - no need to look for symlinks */
+ rpmFpHashAddEntry(fphash, fp, ffi);
+ return;
+ }
+ lensubDir = strlen(fp->subDir);
+ currentsubdir = xstrdup(fp->subDir);
+ current_fp.subDir = endsubdir = NULL; // no subDir for now
+
+ /* Set baseName to the upper most dir */
+ current_fp.baseName = currentsubdir;
+ endbasename = currentsubdir;
+ while (*endbasename != '/' &&
+ endbasename < currentsubdir + lensubDir - 1)
+ endbasename++;
+ *endbasename = '\0';
+ break;
+
+ }
+ rpmfiSetFX(foundfi, fiFX);
+ }
+ if (symlinkcount>50) {
+ // found too many symlinks in the path
+ // most likley a symlink cicle
+ // giving up
+ // TODO warning/error
+ break;
+ }
+ if (found) {
+ continue; // restart loop after symlink
+ }
+
+ if (current_fp.subDir == NULL) {
+ /* after first round set former baseName as subDir */
+ current_fp.subDir = currentsubdir;
+ } else {
+ *endsubdir = '/'; // rejoin the former baseName with subDir
+ }
+ endsubdir = endbasename;
+
+ /* set baseName to the next lower dir */
+ endbasename++;
+ while (*endbasename != '\0' && *endbasename != '/')
+ endbasename++;
+ *endbasename = '\0';
+ current_fp.baseName = endsubdir+1;
+
+ }
+ free(currentsubdir);
+ rpmFpHashAddEntry(fphash, fp, ffi);
+
+}
diff --git a/lib/fprint.h b/lib/fprint.h
new file mode 100644
index 0000000..f2ea6f5
--- /dev/null
+++ b/lib/fprint.h
@@ -0,0 +1,170 @@
+#ifndef H_FINGERPRINT
+#define H_FINGERPRINT
+
+/** \ingroup rpmtrans
+ * \file lib/fprint.h
+ * Identify a file name path by a unique "finger print".
+ */
+
+#include <rpm/header.h>
+#include <rpm/rpmte.h>
+#include "lib/rpmdb_internal.h"
+
+/**
+ */
+typedef struct fprintCache_s * fingerPrintCache;
+
+/**
+ * @todo Convert to pointer and make abstract.
+ */
+typedef struct fingerPrint_s fingerPrint;
+
+/**
+ * Associates a trailing sub-directory and final base name with an existing
+ * directory finger print.
+ */
+struct fingerPrint_s {
+/*! directory finger print entry (the directory path is stat(2)-able */
+ const struct fprintCacheEntry_s * entry;
+/*! trailing sub-directory path (directories that are not stat(2)-able */
+const char * subDir;
+const char * baseName; /*!< file base name */
+};
+
+/* Create new hash table data type */
+#define HASHTYPE rpmFpEntryHash
+#define HTKEYTYPE const char *
+#define HTDATATYPE const struct fprintCacheEntry_s *
+#include "lib/rpmhash.H"
+
+/**
+ * Finger print cache entry.
+ * This is really a directory and symlink cache. We don't differentiate between
+ * the two. We can prepopulate it, which allows us to easily conduct "fake"
+ * installs of a system w/o actually mounting filesystems.
+ */
+struct fprintCacheEntry_s {
+ const char * dirName; /*!< path to existing directory */
+ dev_t dev; /*!< stat(2) device number */
+ ino_t ino; /*!< stat(2) inode number */
+};
+
+/**
+ * Finger print cache.
+ */
+struct fprintCache_s {
+ rpmFpEntryHash ht; /*!< hashed by dirName */
+};
+
+/* Create new hash table data type */
+
+struct rpmffi_s {
+ rpmte p;
+ int fileno;
+};
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+
+#define HASHTYPE rpmFpHash
+#define HTKEYTYPE const fingerPrint *
+#define HTDATATYPE struct rpmffi_s
+#include "lib/rpmhash.H"
+
+/** */
+#define FP_ENTRY_EQUAL(a, b) (((a)->dev == (b)->dev) && ((a)->ino == (b)->ino))
+
+/** */
+#define FP_EQUAL(a, b) ( \
+ FP_ENTRY_EQUAL((a).entry, (b).entry) && \
+ !strcmp((a).baseName, (b).baseName) && ( \
+ ((a).subDir == (b).subDir) || \
+ ((a).subDir && (b).subDir && !strcmp((a).subDir, (b).subDir)) \
+ ) \
+ )
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Create finger print cache.
+ * @param sizeHint number of elements expected
+ * @return pointer to initialized fingerprint cache
+ */
+RPM_GNUC_INTERNAL
+fingerPrintCache fpCacheCreate(int sizeHint);
+
+/**
+ * Destroy finger print cache.
+ * @param cache pointer to fingerprint cache
+ * @return NULL always
+ */
+RPM_GNUC_INTERNAL
+fingerPrintCache fpCacheFree(fingerPrintCache cache);
+
+/**
+ * Return finger print of a file path.
+ * @param cache pointer to fingerprint cache
+ * @param dirName leading directory name of file path
+ * @param baseName base name of file path
+ * @param scareMemory
+ * @return pointer to the finger print associated with a file path.
+ */
+RPM_GNUC_INTERNAL
+fingerPrint fpLookup(fingerPrintCache cache, const char * dirName,
+ const char * baseName, int scareMemory);
+
+/**
+ * Return hash value for a finger print.
+ * Hash based on dev and inode only!
+ * @param key pointer to finger print entry
+ * @return hash value
+ */
+RPM_GNUC_INTERNAL
+unsigned int fpHashFunction(const fingerPrint * key);
+
+/**
+ * Compare two finger print entries.
+ * This routine is exactly equivalent to the FP_EQUAL macro.
+ * @param key1 finger print 1
+ * @param key2 finger print 2
+ * @return result of comparing key1 and key2
+ */
+RPM_GNUC_INTERNAL
+int fpEqual(const fingerPrint * key1, const fingerPrint * key2);
+
+/**
+ * Return finger prints of an array of file paths.
+ * @warning: scareMemory is assumed!
+ * @param cache pointer to fingerprint cache
+ * @param dirNames directory names
+ * @param baseNames file base names
+ * @param dirIndexes index into dirNames for each baseNames
+ * @param fileCount number of file entries
+ * @retval fpList pointer to array of finger prints
+ */
+RPM_GNUC_INTERNAL
+void fpLookupList(fingerPrintCache cache, const char ** dirNames,
+ const char ** baseNames, const uint32_t * dirIndexes,
+ int fileCount, fingerPrint * fpList);
+
+/**
+ * Check file for to be installed symlinks in their path,
+ * correct their fingerprint and add it to newht.
+ * @param ht hash table containing all files fingerprints
+ * @param newht hash table to add the corrected fingerprints
+ * @param fpc fingerprint cache
+ * @param fi file iterator of the package
+ * @param filenr the number of the file we are dealing with
+ */
+RPM_GNUC_INTERNAL
+void fpLookupSubdir(rpmFpHash symlinks, rpmFpHash fphash, fingerPrintCache fpc, rpmte p, int filenr);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/fsm.c b/lib/fsm.c
new file mode 100644
index 0000000..9a475a2
--- /dev/null
+++ b/lib/fsm.c
@@ -0,0 +1,2332 @@
+/** \ingroup payload
+ * \file lib/fsm.c
+ * File state machine to handle a payload from a package.
+ */
+
+#include "system.h"
+
+#include <utime.h>
+#include <errno.h>
+#if defined(HAVE_MMAP)
+#include <sys/mman.h>
+#endif
+#if WITH_CAP
+#include <sys/capability.h>
+#endif
+
+#include <rpm/rpmte.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmsq.h>
+#include <rpm/rpmlog.h>
+
+#include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */
+#include "lib/cpio.h"
+#include "lib/fsm.h"
+#define fsmUNSAFE fsmStage
+#include "lib/rpmfi_internal.h" /* XXX fi->apath, ... */
+#include "lib/rpmte_internal.h" /* XXX rpmfs */
+#include "lib/rpmts_internal.h" /* rpmtsSELabelFoo() only */
+#include "lib/rpmug.h"
+
+#include "debug.h"
+
+#define _FSM_DEBUG 0
+int _fsm_debug = _FSM_DEBUG;
+
+/* XXX Failure to remove is not (yet) cause for failure. */
+static int strict_erasures = 0;
+
+/** \ingroup payload
+ * Keeps track of the set of all hard links to a file in an archive.
+ */
+struct hardLink_s {
+ hardLink_t next;
+ const char ** nsuffix;
+ int * filex;
+ struct stat sb;
+ nlink_t nlink;
+ nlink_t linksLeft;
+ int linkIndex;
+ int createdPath;
+};
+
+/** \ingroup payload
+ * Iterator across package file info, forward on install, backward on erase.
+ */
+struct fsmIterator_s {
+ rpmts ts; /*!< transaction set. */
+ rpmte te; /*!< transaction element. */
+ rpmfi fi; /*!< transaction element file info. */
+ int reverse; /*!< reversed traversal? */
+ int isave; /*!< last returned iterator index. */
+ int i; /*!< iterator index. */
+};
+
+/**
+ * Retrieve transaction set from file state machine iterator.
+ * @param fsm file state machine
+ * @return transaction set
+ */
+static rpmts fsmGetTs(const FSM_t fsm) {
+ const FSMI_t iter = fsm->iter;
+ return (iter ? iter->ts : NULL);
+}
+
+/**
+ * Retrieve transaction element file info from file state machine iterator.
+ * @param fsm file state machine
+ * @return transaction element file info
+ */
+static rpmfi fsmGetFi(const FSM_t fsm)
+{
+ const FSMI_t iter = fsm->iter;
+ return (iter ? iter->fi : NULL);
+}
+
+static rpmte fsmGetTe(const FSM_t fsm)
+{
+ const FSMI_t iter = fsm->iter;
+ return (iter ? iter->te : NULL);
+}
+
+#define SUFFIX_RPMORIG ".rpmorig"
+#define SUFFIX_RPMSAVE ".rpmsave"
+#define SUFFIX_RPMNEW ".rpmnew"
+
+/* Default directory and file permissions if not mapped */
+#define _dirPerms 0755
+#define _filePerms 0644
+
+/*
+ * XXX Forward declarations for previously exported functions to avoid moving
+ * things around needlessly
+ */
+static const char * fileStageString(fileStage a);
+static const char * fileActionString(rpmFileAction a);
+static int fsmStage(FSM_t fsm, fileStage stage);
+
+/** \ingroup payload
+ * Build path to file from file info, ornamented with subdir and suffix.
+ * @param fsm file state machine data
+ * @param st file stat info
+ * @param subdir subdir to use (NULL disables)
+ * @param suffix suffix to use (NULL disables)
+ * @retval path to file (malloced)
+ */
+static char * fsmFsPath(const FSM_t fsm,
+ const struct stat * st,
+ const char * subdir,
+ const char * suffix)
+{
+ char * s = NULL;
+
+ if (fsm) {
+ int isDir = (st && S_ISDIR(st->st_mode));
+ s = rstrscat(NULL, fsm->dirName,
+ (!isDir && subdir) ? subdir : "",
+ fsm->baseName,
+ (!isDir && suffix) ? suffix : "",
+ NULL);
+ }
+ return s;
+}
+
+/** \ingroup payload
+ * Destroy file info iterator.
+ * @param p file info iterator
+ * @retval NULL always
+ */
+static FSMI_t mapFreeIterator(FSMI_t iter)
+{
+ if (iter) {
+ iter->ts = rpmtsFree(iter->ts);
+ iter->te = NULL; /* XXX rpmte is not refcounted yet */
+ iter->fi = rpmfiFree(iter->fi);
+ free(iter);
+ }
+ return NULL;
+}
+
+/** \ingroup payload
+ * Create file info iterator.
+ * @param ts transaction set
+ * @param fi transaction element file info
+ * @return file info iterator
+ */
+static FSMI_t
+mapInitIterator(rpmts ts, rpmte te, rpmfi fi)
+{
+ FSMI_t iter = NULL;
+
+ iter = xcalloc(1, sizeof(*iter));
+ iter->ts = rpmtsLink(ts);
+ iter->te = te; /* XXX rpmte is not refcounted yet */
+ iter->fi = rpmfiLink(fi);
+ iter->reverse = (rpmteType(te) == TR_REMOVED);
+ iter->i = (iter->reverse ? (rpmfiFC(fi) - 1) : 0);
+ iter->isave = iter->i;
+ return iter;
+}
+
+/** \ingroup payload
+ * Return next index into file info.
+ * @param a file info iterator
+ * @return next index, -1 on termination
+ */
+static int mapNextIterator(FSMI_t iter)
+{
+ int i = -1;
+
+ if (iter) {
+ const rpmfi fi = iter->fi;
+ if (iter->reverse) {
+ if (iter->i >= 0) i = iter->i--;
+ } else {
+ if (iter->i < rpmfiFC(fi)) i = iter->i++;
+ }
+ iter->isave = i;
+ }
+ return i;
+}
+
+/** \ingroup payload
+ */
+static int cpioStrCmp(const void * a, const void * b)
+{
+ const char * afn = *(const char **)a;
+ const char * bfn = *(const char **)b;
+
+ /* Match rpm-4.0 payloads with ./ prefixes. */
+ if (afn[0] == '.' && afn[1] == '/') afn += 2;
+ if (bfn[0] == '.' && bfn[1] == '/') bfn += 2;
+
+ /* If either path is absolute, make it relative. */
+ if (afn[0] == '/') afn += 1;
+ if (bfn[0] == '/') bfn += 1;
+
+ return strcmp(afn, bfn);
+}
+
+/** \ingroup payload
+ * Locate archive path in file info.
+ * @param iter file info iterator
+ * @param fsmPath archive path
+ * @return index into file info, -1 if archive path was not found
+ */
+static int mapFind(FSMI_t iter, const char * fsmPath)
+{
+ int ix = -1;
+
+ if (iter) {
+ const rpmfi fi = iter->fi;
+ int fc = rpmfiFC(fi);
+ if (fi && fc > 0 && fi->apath && fsmPath && *fsmPath) {
+ char ** p = NULL;
+
+ if (fi->apath != NULL)
+ p = bsearch(&fsmPath, fi->apath, fc, sizeof(fsmPath),
+ cpioStrCmp);
+ if (p) {
+ iter->i = p - fi->apath;
+ ix = mapNextIterator(iter);
+ }
+ }
+ }
+ return ix;
+}
+
+/** \ingroup payload
+ * Directory name iterator.
+ */
+typedef struct dnli_s {
+ rpmfi fi;
+ char * active;
+ int reverse;
+ int isave;
+ int i;
+} * DNLI_t;
+
+/** \ingroup payload
+ * Destroy directory name iterator.
+ * @param a directory name iterator
+ * @retval NULL always
+ */
+static DNLI_t dnlFreeIterator(DNLI_t dnli)
+{
+ if (dnli) {
+ if (dnli->active) free(dnli->active);
+ free(dnli);
+ }
+ return NULL;
+}
+
+/** \ingroup payload
+ */
+static inline int dnlCount(const DNLI_t dnli)
+{
+ return (dnli ? rpmfiDC(dnli->fi) : 0);
+}
+
+/** \ingroup payload
+ */
+static inline int dnlIndex(const DNLI_t dnli)
+{
+ return (dnli ? dnli->isave : -1);
+}
+
+/** \ingroup payload
+ * Create directory name iterator.
+ * @param fsm file state machine data
+ * @param reverse traverse directory names in reverse order?
+ * @return directory name iterator
+ */
+static DNLI_t dnlInitIterator(const FSM_t fsm, int reverse)
+{
+ rpmfi fi = fsmGetFi(fsm);
+ rpmfs fs = rpmteGetFileStates(fsmGetTe(fsm));
+ DNLI_t dnli;
+ int i, j;
+ int dc;
+
+ if (fi == NULL)
+ return NULL;
+ dc = rpmfiDC(fi);
+ dnli = xcalloc(1, sizeof(*dnli));
+ dnli->fi = fi;
+ dnli->reverse = reverse;
+ dnli->i = (reverse ? dc : 0);
+
+ if (dc) {
+ dnli->active = xcalloc(dc, sizeof(*dnli->active));
+ int fc = rpmfiFC(fi);
+
+ /* Identify parent directories not skipped. */
+ for (i = 0; i < fc; i++)
+ if (!XFA_SKIPPING(rpmfsGetAction(fs, i)))
+ dnli->active[rpmfiDIIndex(fi, i)] = 1;
+
+ /* Exclude parent directories that are explicitly included. */
+ for (i = 0; i < fc; i++) {
+ int dil;
+ size_t dnlen, bnlen;
+
+ if (!S_ISDIR(rpmfiFModeIndex(fi, i)))
+ continue;
+
+ dil = rpmfiDIIndex(fi, i);
+ dnlen = strlen(rpmfiDNIndex(fi, dil));
+ bnlen = strlen(rpmfiBNIndex(fi, i));
+
+ for (j = 0; j < dc; j++) {
+ const char * dnl;
+ size_t jlen;
+
+ if (!dnli->active[j] || j == dil)
+ continue;
+ dnl = rpmfiDNIndex(fi, j);
+ jlen = strlen(dnl);
+ if (jlen != (dnlen+bnlen+1))
+ continue;
+ if (!rstreqn(dnl, rpmfiDNIndex(fi, dil), dnlen))
+ continue;
+ if (!rstreqn(dnl+dnlen, rpmfiBNIndex(fi, i), bnlen))
+ continue;
+ if (dnl[dnlen+bnlen] != '/' || dnl[dnlen+bnlen+1] != '\0')
+ continue;
+ /* This directory is included in the package. */
+ dnli->active[j] = 0;
+ break;
+ }
+ }
+
+ /* Print only once per package. */
+ if (!reverse) {
+ j = 0;
+ for (i = 0; i < dc; i++) {
+ if (!dnli->active[i]) continue;
+ if (j == 0) {
+ j = 1;
+ rpmlog(RPMLOG_DEBUG,
+ "========== Directories not explicitly included in package:\n");
+ }
+ rpmlog(RPMLOG_DEBUG, "%10d %s\n", i, rpmfiDNIndex(fi, i));
+ }
+ if (j)
+ rpmlog(RPMLOG_DEBUG, "==========\n");
+ }
+ }
+ return dnli;
+}
+
+/** \ingroup payload
+ * Return next directory name (from file info).
+ * @param dnli directory name iterator
+ * @return next directory name
+ */
+static
+const char * dnlNextIterator(DNLI_t dnli)
+{
+ const char * dn = NULL;
+
+ if (dnli) {
+ rpmfi fi = dnli->fi;
+ int dc = rpmfiDC(fi);
+ int i = -1;
+
+ if (dnli->active)
+ do {
+ i = (!dnli->reverse ? dnli->i++ : --dnli->i);
+ } while (i >= 0 && i < dc && !dnli->active[i]);
+
+ if (i >= 0 && i < dc)
+ dn = rpmfiDNIndex(fi, i);
+ else
+ i = -1;
+ dnli->isave = i;
+ }
+ return dn;
+}
+
+int fsmNext(FSM_t fsm, fileStage nstage)
+{
+ fsm->nstage = nstage;
+ return fsmStage(fsm, fsm->nstage);
+}
+
+/**
+ * Map next file path and action.
+ * @param fsm file state machine
+ */
+static int fsmMapPath(FSM_t fsm)
+{
+ rpmfi fi = fsmGetFi(fsm); /* XXX const except for fstates */
+ int rc = 0;
+ int i;
+
+ fsm->osuffix = NULL;
+ fsm->nsuffix = NULL;
+ fsm->action = FA_UNKNOWN;
+
+ i = fsm->ix;
+ if (fi && i >= 0 && i < rpmfiFC(fi)) {
+ rpmte te = fsmGetTe(fsm);
+ rpmfs fs = rpmteGetFileStates(te);
+ /* XXX these should use rpmfiFFlags() etc */
+ fsm->action = rpmfsGetAction(fs, i);
+ fsm->fflags = rpmfiFFlagsIndex(fi, i);
+
+ /* src rpms have simple base name in payload. */
+ fsm->dirName = rpmfiDNIndex(fi, rpmfiDIIndex(fi, i));
+ fsm->baseName = rpmfiBNIndex(fi, i);
+
+ if (rpmteType(te) == TR_ADDED) {
+ switch (fsm->action) {
+ case FA_SKIPNSTATE:
+ rpmfsSetState(fs, i, RPMFILE_STATE_NOTINSTALLED);
+ break;
+ case FA_SKIPNETSHARED:
+ rpmfsSetState(fs, i, RPMFILE_STATE_NETSHARED);
+ break;
+ case FA_SKIPCOLOR:
+ rpmfsSetState(fs, i, RPMFILE_STATE_WRONGCOLOR);
+ break;
+ case FA_ALTNAME:
+ if (!(fsm->fflags & RPMFILE_GHOST)) /* XXX Don't if %ghost file. */
+ fsm->nsuffix = SUFFIX_RPMNEW;
+ break;
+ case FA_SAVE:
+ if (!(fsm->fflags & RPMFILE_GHOST)) /* XXX Don't if %ghost file. */
+ fsm->osuffix = SUFFIX_RPMSAVE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (fsm->action == FA_BACKUP && !(fsm->fflags & RPMFILE_GHOST)) {
+ /* XXX Don't if %ghost file. */
+ fsm->osuffix = (rpmteType(te) == TR_ADDED) ? SUFFIX_RPMORIG : SUFFIX_RPMSAVE;
+ }
+
+ if ((fsm->mapFlags & CPIO_MAP_PATH) || fsm->nsuffix) {
+ const struct stat * st = &fsm->sb;
+ fsm->path = _free(fsm->path);
+ fsm->path = fsmFsPath(fsm, st, fsm->subdir,
+ (fsm->suffix ? fsm->suffix : fsm->nsuffix));
+ }
+ }
+ return rc;
+}
+
+/** \ingroup payload
+ * Save hard link in chain.
+ * @param fsm file state machine data
+ * @return Is chain only partially filled?
+ */
+static int saveHardLink(FSM_t fsm)
+{
+ struct stat * st = &fsm->sb;
+ int rc = 0;
+ int ix = -1;
+ int j;
+
+ /* Find hard link set. */
+ for (fsm->li = fsm->links; fsm->li; fsm->li = fsm->li->next) {
+ if (fsm->li->sb.st_ino == st->st_ino && fsm->li->sb.st_dev == st->st_dev)
+ break;
+ }
+
+ /* New hard link encountered, add new link to set. */
+ if (fsm->li == NULL) {
+ fsm->li = xcalloc(1, sizeof(*fsm->li));
+ fsm->li->next = NULL;
+ fsm->li->sb = *st; /* structure assignment */
+ fsm->li->nlink = st->st_nlink;
+ fsm->li->linkIndex = fsm->ix;
+ fsm->li->createdPath = -1;
+
+ fsm->li->filex = xcalloc(st->st_nlink, sizeof(fsm->li->filex[0]));
+ memset(fsm->li->filex, -1, (st->st_nlink * sizeof(fsm->li->filex[0])));
+ fsm->li->nsuffix = xcalloc(st->st_nlink, sizeof(*fsm->li->nsuffix));
+
+ if (fsm->goal == FSM_PKGBUILD)
+ fsm->li->linksLeft = st->st_nlink;
+ if (fsm->goal == FSM_PKGINSTALL)
+ fsm->li->linksLeft = 0;
+
+ fsm->li->next = fsm->links;
+ fsm->links = fsm->li;
+ }
+
+ if (fsm->goal == FSM_PKGBUILD) --fsm->li->linksLeft;
+ fsm->li->filex[fsm->li->linksLeft] = fsm->ix;
+ fsm->li->nsuffix[fsm->li->linksLeft] = fsm->nsuffix;
+ if (fsm->goal == FSM_PKGINSTALL) fsm->li->linksLeft++;
+
+ if (fsm->goal == FSM_PKGBUILD)
+ return (fsm->li->linksLeft > 0);
+
+ if (fsm->goal != FSM_PKGINSTALL)
+ return 0;
+
+ if (!(st->st_size || fsm->li->linksLeft == st->st_nlink))
+ return 1;
+
+ /* Here come the bits, time to choose a non-skipped file name. */
+ { rpmfs fs = rpmteGetFileStates(fsmGetTe(fsm));
+
+ for (j = fsm->li->linksLeft - 1; j >= 0; j--) {
+ ix = fsm->li->filex[j];
+ if (ix < 0 || XFA_SKIPPING(rpmfsGetAction(fs, ix)))
+ continue;
+ break;
+ }
+ }
+
+ /* Are all links skipped or not encountered yet? */
+ if (ix < 0 || j < 0)
+ return 1; /* XXX W2DO? */
+
+ /* Save the non-skipped file name and map index. */
+ fsm->li->linkIndex = j;
+ fsm->path = _free(fsm->path);
+ fsm->ix = ix;
+ rc = fsmMapPath(fsm);
+ return rc;
+}
+
+/** \ingroup payload
+ * Destroy set of hard links.
+ * @param li set of hard links
+ * @return NULL always
+ */
+static hardLink_t freeHardLink(hardLink_t li)
+{
+ if (li) {
+ li->nsuffix = _free(li->nsuffix); /* XXX elements are shared */
+ li->filex = _free(li->filex);
+ _free(li);
+ }
+ return NULL;
+}
+
+FSM_t newFSM(cpioMapFlags mapflags)
+{
+ FSM_t fsm = xcalloc(1, sizeof(*fsm));
+ fsm->mapFlags = mapflags;
+ return fsm;
+}
+
+FSM_t freeFSM(FSM_t fsm)
+{
+ if (fsm) {
+ fsm->path = _free(fsm->path);
+ while ((fsm->li = fsm->links) != NULL) {
+ fsm->links = fsm->li->next;
+ fsm->li->next = NULL;
+ fsm->li = freeHardLink(fsm->li);
+ }
+ fsm->dnlx = _free(fsm->dnlx);
+ fsm->ldn = _free(fsm->ldn);
+ fsm->iter = mapFreeIterator(fsm->iter);
+ _free(fsm);
+ }
+ return NULL;
+}
+
+/* forward declaration*/
+static int fsmMkdirs(FSM_t fsm);
+
+static int fsmCreate(FSM_t fsm)
+{
+ int rc = 0;
+ fsm->path = _free(fsm->path);
+ fsm->opath = _free(fsm->opath);
+ fsm->dnlx = _free(fsm->dnlx);
+
+ fsm->ldn = _free(fsm->ldn);
+ fsm->ldnalloc = fsm->ldnlen = 0;
+
+ fsm->rdsize = fsm->wrsize = 0;
+ fsm->rdbuf = fsm->rdb = _free(fsm->rdb);
+ fsm->wrbuf = fsm->wrb = _free(fsm->wrb);
+ if (fsm->goal == FSM_PKGINSTALL || fsm->goal == FSM_PKGBUILD) {
+ fsm->rdsize = 8 * BUFSIZ;
+ fsm->rdbuf = fsm->rdb = xmalloc(fsm->rdsize);
+ fsm->wrsize = 8 * BUFSIZ;
+ fsm->wrbuf = fsm->wrb = xmalloc(fsm->wrsize);
+ }
+
+ fsm->mkdirsdone = 0;
+ fsm->ix = -1;
+ fsm->links = NULL;
+ fsm->li = NULL;
+ errno = 0; /* XXX get rid of EBADF */
+
+ /* Detect and create directories not explicitly in package. */
+ if (fsm->goal == FSM_PKGINSTALL) {
+ rc = fsmMkdirs(fsm);
+ if (!rc) fsm->mkdirsdone = 1;
+ }
+ return rc;
+}
+
+int fsmSetup(FSM_t fsm, fileStage goal,
+ rpmts ts, rpmte te, rpmfi fi, FD_t cfd,
+ rpm_loff_t * archiveSize, char ** failedFile)
+{
+ int rc, ec = 0;
+
+ fsm->goal = goal;
+ if (cfd != NULL) {
+ fsm->cfd = fdLink(cfd);
+ }
+ fsm->cpioPos = 0;
+ fsm->iter = mapInitIterator(ts, te, fi);
+ fsm->digestalgo = rpmfiDigestAlgo(fi);
+
+ if (fsm->goal == FSM_PKGINSTALL || fsm->goal == FSM_PKGBUILD) {
+ void * ptr;
+ fsm->archivePos = 0;
+ ptr = rpmtsNotify(ts, te,
+ RPMCALLBACK_INST_START, fsm->archivePos, fi->archiveSize);
+ }
+
+ fsm->archiveSize = archiveSize;
+ if (fsm->archiveSize)
+ *fsm->archiveSize = 0;
+ fsm->failedFile = failedFile;
+ if (fsm->failedFile)
+ *fsm->failedFile = NULL;
+
+ memset(fsm->sufbuf, 0, sizeof(fsm->sufbuf));
+ if (fsm->goal == FSM_PKGINSTALL) {
+ if (ts && rpmtsGetTid(ts) != (rpm_tid_t)-1)
+ sprintf(fsm->sufbuf, ";%08x", (unsigned)rpmtsGetTid(ts));
+ }
+
+ ec = fsm->rc = 0;
+ rc = fsmCreate(fsm);
+ if (rc && !ec) ec = rc;
+
+ rc = fsmUNSAFE(fsm, fsm->goal);
+ if (rc && !ec) ec = rc;
+
+ if (fsm->archiveSize && ec == 0)
+ *fsm->archiveSize = fsm->cpioPos;
+
+/* FIX: *fsm->failedFile may be NULL */
+ return ec;
+}
+
+int fsmTeardown(FSM_t fsm)
+{
+ int rc = fsm->rc;
+
+ if (!rc)
+ rc = fsmUNSAFE(fsm, FSM_DESTROY);
+
+ fsm->iter = mapFreeIterator(fsm->iter);
+ if (fsm->cfd != NULL) {
+ fsm->cfd = fdFree(fsm->cfd);
+ fsm->cfd = NULL;
+ }
+ fsm->failedFile = NULL;
+ return rc;
+}
+
+static int fsmMapFContext(FSM_t fsm)
+{
+ rpmts ts = fsmGetTs(fsm);
+ struct stat * st;
+ st = &fsm->sb;
+
+ /*
+ * Find file security context (if not disabled).
+ */
+ fsm->fcontext = NULL;
+ if (ts != NULL && !(rpmtsFlags(ts) & RPMTRANS_FLAG_NOCONTEXTS) && rpmtsSELabelHandle(ts)) {
+ security_context_t scon = NULL;
+
+ if (selabel_lookup_raw(rpmtsSELabelHandle(ts), &scon, fsm->path, st->st_mode) == 0 && scon != NULL) {
+ fsm->fcontext = scon;
+ }
+ }
+ return 0;
+}
+
+#if WITH_CAP
+static int fsmMapFCaps(FSM_t fsm)
+{
+ rpmfi fi = fsmGetFi(fsm);
+ const char *captxt = rpmfiFCapsIndex(fi, fsm->ix);
+ fsm->fcaps = NULL;
+ if (captxt && *captxt != '\0') {
+ cap_t fcaps = cap_from_text(captxt);
+ if (fcaps) {
+ fsm->fcaps = fcaps;
+ }
+ }
+ return 0;
+}
+#endif
+
+/**
+ * Map file stat(2) info.
+ * @param fsm file state machine
+ */
+static int fsmMapAttrs(FSM_t fsm)
+{
+ struct stat * st = &fsm->sb;
+ rpmfi fi = fsmGetFi(fsm);
+ int i = fsm->ix;
+
+ /* this check is pretty moot, rpmfi accessors check array bounds etc */
+ if (fi && i >= 0 && i < rpmfiFC(fi)) {
+ mode_t finalMode = rpmfiFModeIndex(fi, i);
+ dev_t finalRdev = rpmfiFRdevIndex(fi, i);
+ time_t finalMtime = rpmfiFMtimeIndex(fi, i);
+ const char *user = rpmfiFUserIndex(fi, i);
+ const char *group = rpmfiFGroupIndex(fi, i);
+ uid_t uid = 0;
+ gid_t gid = 0;
+
+ if (user && rpmugUid(user, &uid)) {
+ if (fsm->goal == FSM_PKGINSTALL)
+ rpmlog(RPMLOG_WARNING,
+ _("user %s does not exist - using root\n"), user);
+ finalMode &= ~S_ISUID; /* turn off suid bit */
+ }
+
+ if (group && rpmugGid(group, &gid)) {
+ if (fsm->goal == FSM_PKGINSTALL)
+ rpmlog(RPMLOG_WARNING,
+ _("group %s does not exist - using root\n"), group);
+ finalMode &= ~S_ISGID; /* turn off sgid bit */
+ }
+
+ if (fsm->mapFlags & CPIO_MAP_MODE)
+ st->st_mode = (st->st_mode & S_IFMT) | (finalMode & ~S_IFMT);
+ if (fsm->mapFlags & CPIO_MAP_TYPE) {
+ st->st_mode = (st->st_mode & ~S_IFMT) | (finalMode & S_IFMT);
+ if ((S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+ && st->st_nlink == 0)
+ st->st_nlink = 1;
+ st->st_rdev = finalRdev;
+ st->st_mtime = finalMtime;
+ }
+ if (fsm->mapFlags & CPIO_MAP_UID)
+ st->st_uid = uid;
+ if (fsm->mapFlags & CPIO_MAP_GID)
+ st->st_gid = gid;
+
+ { rpmts ts = fsmGetTs(fsm);
+
+ /*
+ * Set file digest (if not disabled).
+ */
+ if (ts != NULL && !(rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST)) {
+ fsm->digest = rpmfiFDigestIndex(fi, i, NULL, NULL);
+ } else {
+ fsm->digest = NULL;
+ }
+ }
+ }
+ return 0;
+}
+
+/** \ingroup payload
+ * Create file from payload stream.
+ * @param fsm file state machine data
+ * @return 0 on success
+ */
+static int expandRegular(FSM_t fsm)
+{
+ const struct stat * st = &fsm->sb;
+ rpm_loff_t left = st->st_size;
+ int rc = 0;
+
+ rc = fsmNext(fsm, FSM_WOPEN);
+ if (rc)
+ goto exit;
+
+ if (st->st_size > 0 && fsm->digest != NULL)
+ fdInitDigest(fsm->wfd, fsm->digestalgo, 0);
+
+ while (left) {
+
+ fsm->wrlen = (left > fsm->wrsize ? fsm->wrsize : left);
+ rc = fsmNext(fsm, FSM_DREAD);
+ if (rc)
+ goto exit;
+
+ rc = fsmNext(fsm, FSM_WRITE);
+ if (rc)
+ goto exit;
+
+ left -= fsm->wrnb;
+
+ /* don't call this with fileSize == fileComplete */
+ if (!rc && left)
+ (void) fsmNext(fsm, FSM_NOTIFY);
+ }
+
+ if (st->st_size > 0 && fsm->digest) {
+ void * digest = NULL;
+ int asAscii = (fsm->digest == NULL ? 1 : 0);
+
+ (void) Fflush(fsm->wfd);
+ fdFiniDigest(fsm->wfd, fsm->digestalgo, &digest, NULL, asAscii);
+
+ if (digest == NULL) {
+ rc = CPIOERR_DIGEST_MISMATCH;
+ goto exit;
+ }
+
+ if (fsm->digest != NULL) {
+ size_t diglen = rpmDigestLength(fsm->digestalgo);
+ if (memcmp(digest, fsm->digest, diglen))
+ rc = CPIOERR_DIGEST_MISMATCH;
+ } else {
+ rc = CPIOERR_DIGEST_MISMATCH;
+ }
+ digest = _free(digest);
+ }
+
+exit:
+ (void) fsmNext(fsm, FSM_WCLOSE);
+ return rc;
+}
+
+static int fsmReadLink(FSM_t fsm)
+{
+ int rc;
+ /* XXX NUL terminated result in fsm->rdbuf, len in fsm->rdnb. */
+ rc = readlink(fsm->path, fsm->rdbuf, fsm->rdsize - 1);
+ if (_fsm_debug && (FSM_READLINK & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, rdbuf, %d) %s\n", fileStageString(FSM_READLINK),
+ fsm->path, (int)(fsm->rdsize -1), (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_READLINK_FAILED;
+ else {
+ fsm->rdnb = rc;
+ fsm->rdbuf[fsm->rdnb] = '\0';
+ rc = 0;
+ }
+ return rc;
+}
+
+/** \ingroup payload
+ * Write next item to payload stream.
+ * @param fsm file state machine data
+ * @param writeData should data be written?
+ * @return 0 on success
+ */
+static int writeFile(FSM_t fsm, int writeData)
+{
+ char * path = fsm->path;
+ char * opath = fsm->opath;
+ struct stat * st = &fsm->sb;
+ struct stat * ost = &fsm->osb;
+ char * symbuf = NULL;
+ rpm_loff_t left;
+ int rc;
+
+ st->st_size = (writeData ? ost->st_size : 0);
+
+ if (S_ISDIR(st->st_mode)) {
+ st->st_size = 0;
+ } else if (S_ISLNK(st->st_mode)) {
+ /*
+ * While linux puts the size of a symlink in the st_size field,
+ * I don't think that's a specified standard.
+ */
+ /* XXX NUL terminated result in fsm->rdbuf, len in fsm->rdnb. */
+ rc = fsmReadLink(fsm);
+ if (rc) goto exit;
+ st->st_size = fsm->rdnb;
+ rstrcat(&symbuf, fsm->rdbuf); /* XXX save readlink return. */
+ }
+
+ if (fsm->mapFlags & CPIO_MAP_ABSOLUTE) {
+ fsm->path = rstrscat(NULL, (fsm->mapFlags & CPIO_MAP_ADDDOT) ? "." : "",
+ fsm->dirName, fsm->baseName, NULL);
+ } else if (fsm->mapFlags & CPIO_MAP_PATH) {
+ rpmfi fi = fsmGetFi(fsm);
+ fsm->path = xstrdup((fi->apath ? fi->apath[fsm->ix] :
+ rpmfiBNIndex(fi, fsm->ix)));
+ }
+
+ rc = cpioHeaderWrite(fsm, st);
+ _free(fsm->path);
+ fsm->path = path;
+ if (rc) goto exit;
+
+ if (writeData && S_ISREG(st->st_mode)) {
+#ifdef HAVE_MMAP
+ char * rdbuf = NULL;
+ void * mapped = MAP_FAILED;
+ size_t nmapped;
+ int xx;
+#endif
+
+ rc = fsmNext(fsm, FSM_ROPEN);
+ if (rc) goto exit;
+
+ /* XXX unbuffered mmap generates *lots* of fdio debugging */
+#ifdef HAVE_MMAP
+ nmapped = 0;
+ mapped = mmap(NULL, st->st_size, PROT_READ, MAP_SHARED, Fileno(fsm->rfd), 0);
+ if (mapped != MAP_FAILED) {
+ rdbuf = fsm->rdbuf;
+ fsm->rdbuf = (char *) mapped;
+ fsm->rdlen = nmapped = st->st_size;
+#if defined(MADV_DONTNEED)
+ xx = madvise(mapped, nmapped, MADV_DONTNEED);
+#endif
+ }
+#endif
+
+ left = st->st_size;
+
+ while (left) {
+#ifdef HAVE_MMAP
+ if (mapped != MAP_FAILED) {
+ fsm->rdnb = nmapped;
+ } else
+#endif
+ {
+ fsm->rdlen = (left > fsm->rdsize ? fsm->rdsize : left),
+ rc = fsmNext(fsm, FSM_READ);
+ if (rc) goto exit;
+ }
+
+ /* XXX DWRITE uses rdnb for I/O length. */
+ rc = fsmNext(fsm, FSM_DWRITE);
+ if (rc) goto exit;
+
+ left -= fsm->wrnb;
+ }
+
+#ifdef HAVE_MMAP
+ if (mapped != MAP_FAILED) {
+ xx = msync(mapped, nmapped, MS_ASYNC);
+#if defined(MADV_DONTNEED)
+ xx = madvise(mapped, nmapped, MADV_DONTNEED);
+#endif
+ xx = munmap(mapped, nmapped);
+ fsm->rdbuf = rdbuf;
+ }
+#endif
+
+ } else if (writeData && S_ISLNK(st->st_mode)) {
+ /* XXX DWRITE uses rdnb for I/O length. */
+ strcpy(fsm->rdbuf, symbuf); /* XXX restore readlink buffer. */
+ fsm->rdnb = strlen(symbuf);
+ rc = fsmNext(fsm, FSM_DWRITE);
+ if (rc) goto exit;
+ }
+
+ rc = fsmNext(fsm, FSM_PAD);
+ if (rc) goto exit;
+
+ rc = 0;
+
+exit:
+ if (fsm->rfd != NULL)
+ (void) fsmNext(fsm, FSM_RCLOSE);
+ fsm->opath = opath;
+ fsm->path = path;
+ free(symbuf);
+ return rc;
+}
+
+/** \ingroup payload
+ * Write set of linked files to payload stream.
+ * @param fsm file state machine data
+ * @return 0 on success
+ */
+static int writeLinkedFile(FSM_t fsm)
+{
+ char * path = fsm->path;
+ const char * nsuffix = fsm->nsuffix;
+ int iterIndex = fsm->ix;
+ int ec = 0;
+ int rc;
+ int i;
+
+ fsm->path = NULL;
+ fsm->nsuffix = NULL;
+ fsm->ix = -1;
+
+ for (i = fsm->li->nlink - 1; i >= 0; i--) {
+
+ if (fsm->li->filex[i] < 0) continue;
+
+ fsm->ix = fsm->li->filex[i];
+ rc = fsmMapPath(fsm);
+
+ /* Write data after last link. */
+ rc = writeFile(fsm, (i == 0));
+ if (fsm->failedFile && rc != 0 && *fsm->failedFile == NULL) {
+ ec = rc;
+ *fsm->failedFile = xstrdup(fsm->path);
+ }
+
+ fsm->path = _free(fsm->path);
+ fsm->li->filex[i] = -1;
+ }
+
+ fsm->ix = iterIndex;
+ fsm->nsuffix = nsuffix;
+ fsm->path = path;
+ return ec;
+}
+
+static int fsmStat(FSM_t fsm, int dolstat)
+{
+ int rc;
+ if (dolstat){
+ rc = lstat(fsm->path, &fsm->osb);
+ } else {
+ rc = stat(fsm->path, &fsm->osb);
+ }
+ if (_fsm_debug && (FSM_STAT & FSM_SYSCALL) && rc && errno != ENOENT)
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n",
+ fileStageString(dolstat ? FSM_LSTAT : FSM_STAT),
+ fsm->path, (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) {
+ rc = (errno == ENOENT ? CPIOERR_ENOENT : CPIOERR_LSTAT_FAILED);
+ memset(&fsm->osb, 0, sizeof(fsm->osb)); /* XXX s390x hackery */
+ }
+ return rc;
+}
+
+static int fsmVerify(FSM_t fsm);
+
+/** \ingroup payload
+ * Create pending hard links to existing file.
+ * @param fsm file state machine data
+ * @return 0 on success
+ */
+static int fsmMakeLinks(FSM_t fsm)
+{
+ char * path = fsm->path;
+ char * opath = fsm->opath;
+ const char * nsuffix = fsm->nsuffix;
+ int iterIndex = fsm->ix;
+ int ec = 0;
+ int rc;
+ int i;
+
+ fsm->path = NULL;
+ fsm->opath = NULL;
+ fsm->nsuffix = NULL;
+ fsm->ix = -1;
+
+ fsm->ix = fsm->li->filex[fsm->li->createdPath];
+ rc = fsmMapPath(fsm);
+ fsm->opath = fsm->path;
+ fsm->path = NULL;
+ for (i = 0; i < fsm->li->nlink; i++) {
+ if (fsm->li->filex[i] < 0) continue;
+ if (fsm->li->createdPath == i) continue;
+
+ fsm->ix = fsm->li->filex[i];
+ fsm->path = _free(fsm->path);
+ rc = fsmMapPath(fsm);
+ if (XFA_SKIPPING(fsm->action)) continue;
+
+ rc = fsmVerify(fsm);
+ if (!rc) continue;
+ if (!(rc == CPIOERR_ENOENT)) break;
+
+ /* XXX link(fsm->opath, fsm->path) */
+ rc = link(fsm->opath, fsm->path);
+ if (_fsm_debug && (FSM_LINK & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", fileStageString(FSM_LINK),
+ fsm->opath, fsm->path, (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_LINK_FAILED;
+
+ if (fsm->failedFile && rc != 0 && *fsm->failedFile == NULL) {
+ ec = rc;
+ *fsm->failedFile = xstrdup(fsm->path);
+ }
+
+ fsm->li->linksLeft--;
+ }
+ fsm->path = _free(fsm->path);
+ fsm->opath = _free(fsm->opath);
+
+ fsm->ix = iterIndex;
+ fsm->nsuffix = nsuffix;
+ fsm->path = path;
+ fsm->opath = opath;
+ return ec;
+}
+
+/** \ingroup payload
+ * Commit hard linked file set atomically.
+ * @param fsm file state machine data
+ * @return 0 on success
+ */
+static int fsmCommitLinks(FSM_t fsm)
+{
+ char * path = fsm->path;
+ const char * nsuffix = fsm->nsuffix;
+ int iterIndex = fsm->ix;
+ struct stat * st = &fsm->sb;
+ int rc = 0;
+ nlink_t i;
+
+ fsm->path = NULL;
+ fsm->nsuffix = NULL;
+ fsm->ix = -1;
+
+ for (fsm->li = fsm->links; fsm->li; fsm->li = fsm->li->next) {
+ if (fsm->li->sb.st_ino == st->st_ino && fsm->li->sb.st_dev == st->st_dev)
+ break;
+ }
+
+ for (i = 0; i < fsm->li->nlink; i++) {
+ if (fsm->li->filex[i] < 0) continue;
+ fsm->ix = fsm->li->filex[i];
+ rc = fsmMapPath(fsm);
+ if (!XFA_SKIPPING(fsm->action))
+ rc = fsmNext(fsm, FSM_COMMIT);
+ fsm->path = _free(fsm->path);
+ fsm->li->filex[i] = -1;
+ }
+
+ fsm->ix = iterIndex;
+ fsm->nsuffix = nsuffix;
+ fsm->path = path;
+ return rc;
+}
+
+static int fsmRmdir(FSM_t fsm)
+{
+ int rc = rmdir(fsm->path);
+ if (_fsm_debug && (FSM_RMDIR & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", fileStageString(FSM_RMDIR),
+ fsm->path, (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0)
+ switch (errno) {
+ case ENOENT: rc = CPIOERR_ENOENT; break;
+ case ENOTEMPTY: rc = CPIOERR_ENOTEMPTY; break;
+ default: rc = CPIOERR_RMDIR_FAILED; break;
+ }
+ return rc;
+}
+
+static int fsmLsetfcon(FSM_t fsm)
+{
+ int rc = 0;
+ if (fsm->fcontext == NULL || *fsm->fcontext == '\0'
+ || rstreq(fsm->fcontext, "<<none>>"))
+ return rc;
+ rc = lsetfilecon(fsm->path, (security_context_t)fsm->fcontext);
+ if (_fsm_debug && (FSM_LSETFCON & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", fileStageString(FSM_LSETFCON),
+ fsm->path, fsm->fcontext,
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = (errno == EOPNOTSUPP ? 0 : CPIOERR_LSETFCON_FAILED);
+ return rc;
+}
+
+static int fsmMkdir(FSM_t fsm)
+{
+ int rc = mkdir(fsm->path, (fsm->sb.st_mode & 07777));
+ if (_fsm_debug && (FSM_MKDIR & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", fileStageString(FSM_MKDIR),
+ fsm->path, (unsigned)(fsm->sb.st_mode & 07777),
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_MKDIR_FAILED;
+ return rc;
+}
+
+/**
+ * Create (if necessary) directories not explicitly included in package.
+ * @param fsm file state machine data
+ * @return 0 on success
+ */
+static int fsmMkdirs(FSM_t fsm)
+{
+ struct stat * st = &fsm->sb;
+ struct stat * ost = &fsm->osb;
+ char * path = fsm->path;
+ const char *dpath;
+ mode_t st_mode = st->st_mode;
+ DNLI_t dnli = dnlInitIterator(fsm, 0);
+ int dc = dnlCount(dnli);
+ int rc = 0;
+ int i;
+ rpmts ts = fsmGetTs(fsm);
+ security_context_t scon = NULL;
+
+ fsm->dnlx = (dc ? xcalloc(dc, sizeof(*fsm->dnlx)) : NULL);
+ if (fsm->dnlx != NULL)
+ while ((dpath = dnlNextIterator(dnli)) != NULL) {
+ size_t dnlen = strlen(dpath);
+ char * te, dn[dnlen+1];
+
+ dc = dnlIndex(dnli);
+ if (dc < 0) continue;
+ fsm->dnlx[dc] = dnlen;
+ if (dnlen <= 1)
+ continue;
+
+ if (dnlen <= fsm->ldnlen && rstreq(dpath, fsm->ldn))
+ continue;
+
+ /* Copy as we need to modify the string */
+ (void) stpcpy(dn, dpath);
+ fsm->path = dn;
+
+ /* Assume '/' directory exists, "mkdir -p" for others if non-existent */
+ for (i = 1, te = dn + 1; *te != '\0'; te++, i++) {
+ if (*te != '/')
+ continue;
+
+ *te = '\0';
+
+ /* Already validated? */
+ if (i < fsm->ldnlen &&
+ (fsm->ldn[i] == '/' || fsm->ldn[i] == '\0') &&
+ rstreqn(fsm->path, fsm->ldn, i))
+ {
+ *te = '/';
+ /* Move pre-existing path marker forward. */
+ fsm->dnlx[dc] = (te - dn);
+ continue;
+ }
+
+ /* Validate next component of path. */
+ rc = fsmStat(fsm, 1); /* lstat */
+ *te = '/';
+
+ /* Directory already exists? */
+ if (rc == 0 && S_ISDIR(ost->st_mode)) {
+ /* Move pre-existing path marker forward. */
+ fsm->dnlx[dc] = (te - dn);
+ } else if (rc == CPIOERR_ENOENT) {
+ *te = '\0';
+ st->st_mode = S_IFDIR | (_dirPerms & 07777);
+ rc = fsmMkdir(fsm);
+ if (!rc) {
+ /* XXX FIXME? only new dir will have context set. */
+ /* Get file security context from patterns. */
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOCONTEXTS) && rpmtsSELabelHandle(ts)) {
+ if (selabel_lookup_raw(rpmtsSELabelHandle(ts), &scon, fsm->path, st->st_mode) == 0 &&
+ scon != NULL) {
+ fsm->fcontext = scon;
+ rc = fsmLsetfcon(fsm);
+ }
+ }
+
+ if (fsm->fcontext == NULL)
+ rpmlog(RPMLOG_DEBUG,
+ "%s directory created with perms %04o, no context.\n",
+ fsm->path, (unsigned)(st->st_mode & 07777));
+ else {
+ rpmlog(RPMLOG_DEBUG,
+ "%s directory created with perms %04o, context %s.\n",
+ fsm->path, (unsigned)(st->st_mode & 07777),
+ fsm->fcontext);
+ freecon(fsm->fcontext);
+ }
+ fsm->fcontext = NULL;
+ }
+ *te = '/';
+ }
+ if (rc)
+ break;
+ }
+ if (rc) break;
+
+ /* Save last validated path. */
+/* FIX: ldn/path annotations ? */
+ if (fsm->ldnalloc < (dnlen + 1)) {
+ fsm->ldnalloc = dnlen + 100;
+ fsm->ldn = xrealloc(fsm->ldn, fsm->ldnalloc);
+ }
+ if (fsm->ldn != NULL) { /* XXX can't happen */
+ strcpy(fsm->ldn, fsm->path);
+ fsm->ldnlen = dnlen;
+ }
+ }
+ dnli = dnlFreeIterator(dnli);
+
+ fsm->path = path;
+ st->st_mode = st_mode; /* XXX restore st->st_mode */
+/* FIX: ldn/path annotations ? */
+ return rc;
+}
+
+static void removeSBITS(const char *path)
+{
+ struct stat stb;
+ if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) {
+ if ((stb.st_mode & 06000) != 0) {
+ (void) chmod(path, stb.st_mode & 0777);
+ }
+#if WITH_CAP
+ if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
+ (void) cap_set_file(path, NULL);
+ }
+#endif
+ }
+}
+
+/********************************************************************/
+
+static int fsmInit(FSM_t fsm)
+{
+ int rc = 0;
+
+ fsm->path = _free(fsm->path);
+ fsm->postpone = 0;
+ fsm->diskchecked = fsm->exists = 0;
+ fsm->subdir = NULL;
+ fsm->suffix = (fsm->sufbuf[0] != '\0' ? fsm->sufbuf : NULL);
+ fsm->action = FA_UNKNOWN;
+ fsm->osuffix = NULL;
+ fsm->nsuffix = NULL;
+
+ if (fsm->goal == FSM_PKGINSTALL) {
+ /* Read next header from payload, checking for end-of-payload. */
+ rc = fsmUNSAFE(fsm, FSM_NEXT);
+ }
+ if (rc) return rc;
+
+ /* Identify mapping index. */
+ fsm->ix = ((fsm->goal == FSM_PKGINSTALL)
+ ? mapFind(fsm->iter, fsm->path) : mapNextIterator(fsm->iter));
+
+ /* Detect end-of-loop and/or mapping error. */
+ if (fsm->ix < 0) {
+ if (fsm->goal == FSM_PKGINSTALL) {
+#if 0
+ rpmlog(RPMLOG_WARNING,
+ _("archive file %s was not found in header file list\n"),
+ fsm->path);
+#endif
+ if (fsm->failedFile && *fsm->failedFile == NULL)
+ *fsm->failedFile = xstrdup(fsm->path);
+ rc = CPIOERR_UNMAPPED_FILE;
+ } else {
+ rc = CPIOERR_HDR_TRAILER;
+ }
+ return rc;
+ }
+
+ /* On non-install, mode must be known so that dirs don't get suffix. */
+ if (fsm->goal != FSM_PKGINSTALL) {
+ rpmfi fi = fsmGetFi(fsm);
+ fsm->sb.st_mode = rpmfiFModeIndex(fi, fsm->ix);
+ }
+
+ /* Generate file path. */
+ rc = fsmMapPath(fsm);
+ if (rc) return rc;
+
+ /* Perform lstat/stat for disk file. */
+ if (fsm->path != NULL &&
+ !(fsm->goal == FSM_PKGINSTALL && S_ISREG(fsm->sb.st_mode)))
+ {
+ rc = fsmStat(fsm, (!(fsm->mapFlags & CPIO_FOLLOW_SYMLINKS)));
+ if (rc == CPIOERR_ENOENT) {
+ // errno = saveerrno; XXX temporary commented out
+ rc = 0;
+ fsm->exists = 0;
+ } else if (rc == 0) {
+ fsm->exists = 1;
+ }
+ } else {
+ /* Skip %ghost files on build. */
+ fsm->exists = 0;
+ }
+ fsm->diskchecked = 1;
+ if (rc) return rc;
+
+ /* On non-install, the disk file stat is what's remapped. */
+ if (fsm->goal != FSM_PKGINSTALL)
+ fsm->sb = fsm->osb; /* structure assignment */
+
+ /* Remap file perms, owner, and group. */
+ rc = fsmMapAttrs(fsm);
+ if (rc) return rc;
+
+ fsm->postpone = XFA_SKIPPING(fsm->action);
+ if (fsm->goal == FSM_PKGINSTALL || fsm->goal == FSM_PKGBUILD) {
+ /* FIX: saveHardLink can modify fsm */
+ if (S_ISREG(fsm->sb.st_mode) && fsm->sb.st_nlink > 1)
+ fsm->postpone = saveHardLink(fsm);
+ }
+ return rc;
+
+}
+
+static int fsmUnlink(FSM_t fsm)
+{
+ int rc = 0;
+ if (fsm->mapFlags & CPIO_SBIT_CHECK)
+ removeSBITS(fsm->path);
+ rc = unlink(fsm->path);
+ if (_fsm_debug && (FSM_UNLINK & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", fileStageString(FSM_UNLINK),
+ fsm->path, (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0)
+ rc = (errno == ENOENT ? CPIOERR_ENOENT : CPIOERR_UNLINK_FAILED);
+ return rc;
+}
+
+static int fsmRename(FSM_t fsm)
+{
+ if (fsm->mapFlags & CPIO_SBIT_CHECK)
+ removeSBITS(fsm->path);
+ int rc = rename(fsm->opath, fsm->path);
+#if defined(ETXTBSY) && defined(__HPUX__)
+ if (rc && errno == ETXTBSY) {
+ char *path = NULL;
+ rstrscat(&path, fsm->path, "-RPMDELETE", NULL);
+ /*
+ * XXX HP-UX (and other os'es) don't permit rename to busy
+ * XXX files.
+ */
+ rc = rename(fsm->path, path);
+ if (!rc) rc = rename(fsm->opath, fsm->path);
+ free(path);
+ }
+#endif
+ if (_fsm_debug && (FSM_RENAME & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", fileStageString(FSM_RENAME),
+ fsm->opath, fsm->path, (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_RENAME_FAILED;
+ return rc;
+}
+
+
+static int fsmChown(FSM_t fsm)
+{
+ int rc = chown(fsm->path, fsm->sb.st_uid, fsm->sb.st_gid);
+ if (rc < 0) {
+ struct stat st;
+ if (lstat(fsm->path, &st) == 0 && st.st_uid == fsm->sb.st_uid && st.st_gid == fsm->sb.st_gid)
+ rc = 0;
+ }
+ if (_fsm_debug && (FSM_CHOWN & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", fileStageString(FSM_CHOWN),
+ fsm->path, (int)fsm->sb.st_uid, (int)fsm->sb.st_gid,
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_CHOWN_FAILED;
+ return rc;
+}
+
+static int fsmLChown(FSM_t fsm)
+{
+ int rc = 0;
+ rc = lchown(fsm->path, fsm->sb.st_uid, fsm->sb.st_gid);
+ if (rc < 0) {
+ struct stat st;
+ if (lstat(fsm->path, &st) == 0 && st.st_uid == fsm->sb.st_uid && st.st_gid == fsm->sb.st_gid)
+ rc = 0;
+ }
+ if (_fsm_debug && (FSM_LCHOWN & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", fileStageString(FSM_LCHOWN),
+ fsm->path, (int)fsm->sb.st_uid, (int)fsm->sb.st_gid,
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_CHOWN_FAILED;
+ return rc;
+}
+
+static int fsmChmod(FSM_t fsm)
+{
+ int rc = chmod(fsm->path, (fsm->sb.st_mode & 07777));
+ if (rc < 0) {
+ struct stat st;
+ if (lstat(fsm->path, &st) == 0 && (st.st_mode & 07777) == (fsm->sb.st_mode & 07777))
+ rc = 0;
+ }
+ if (_fsm_debug && (FSM_CHMOD & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", fileStageString(FSM_CHMOD),
+ fsm->path, (unsigned)(fsm->sb.st_mode & 07777),
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_CHMOD_FAILED;
+ return rc;
+}
+
+static int fsmUtime(FSM_t fsm)
+{
+ int rc = 0;
+ struct utimbuf stamp;
+ stamp.actime = fsm->sb.st_mtime;
+ stamp.modtime = fsm->sb.st_mtime;
+ rc = utime(fsm->path, &stamp);
+ if (_fsm_debug && (FSM_UTIME & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", fileStageString(FSM_UTIME),
+ fsm->path, (unsigned)fsm->sb.st_mtime,
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_UTIME_FAILED;
+ return rc;
+}
+
+#if WITH_CAP
+static int fsmSetcap(FSM_t fsm)
+{
+ int rc = cap_set_file(fsm->path, fsm->fcaps);
+ if (rc < 0) {
+ rc = CPIOERR_SETCAP_FAILED;
+ }
+ return rc;
+}
+#endif /* WITH_CAP */
+
+static int fsmVerify(FSM_t fsm)
+{
+ int rc;
+ struct stat * st = &fsm->sb;
+ struct stat * ost = &fsm->osb;
+ int saveerrno = errno;
+
+ if (fsm->diskchecked && !fsm->exists) {
+ return CPIOERR_ENOENT;
+ }
+ if (S_ISREG(st->st_mode)) {
+ /*
+ * XXX HP-UX (and other os'es) don't permit unlink on busy
+ * XXX files.
+ */
+ fsm->opath = fsm->path;
+ fsm->path = rstrscat(NULL, fsm->path, "-RPMDELETE", NULL);
+ rc = fsmRename(fsm);
+ if (!rc)
+ (void) fsmUnlink(fsm);
+ else
+ rc = CPIOERR_UNLINK_FAILED;
+ _free(fsm->path);
+ fsm->path = fsm->opath;
+ fsm->opath = NULL;
+ return (rc ? rc : CPIOERR_ENOENT); /* XXX HACK */
+ } else if (S_ISDIR(st->st_mode)) {
+ if (S_ISDIR(ost->st_mode)) return 0;
+ if (S_ISLNK(ost->st_mode)) {
+ rc = fsmStat(fsm, 0);
+ if (rc == CPIOERR_ENOENT) rc = 0;
+ if (rc) return rc;
+ errno = saveerrno;
+ if (S_ISDIR(ost->st_mode)) return 0;
+ }
+ } else if (S_ISLNK(st->st_mode)) {
+ if (S_ISLNK(ost->st_mode)) {
+ /* XXX NUL terminated result in fsm->rdbuf, len in fsm->rdnb. */
+ rc = fsmReadLink(fsm);
+ errno = saveerrno;
+ if (rc) return rc;
+ if (rstreq(fsm->opath, fsm->rdbuf)) return 0;
+ }
+ } else if (S_ISFIFO(st->st_mode)) {
+ if (S_ISFIFO(ost->st_mode)) return 0;
+ } else if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
+ if ((S_ISCHR(ost->st_mode) || S_ISBLK(ost->st_mode)) &&
+ (ost->st_rdev == st->st_rdev)) return 0;
+ } else if (S_ISSOCK(st->st_mode)) {
+ if (S_ISSOCK(ost->st_mode)) return 0;
+ }
+ /* XXX shouldn't do this with commit/undo. */
+ rc = 0;
+ if (fsm->stage == FSM_PROCESS) rc = fsmUnlink(fsm);
+ if (rc == 0) rc = CPIOERR_ENOENT;
+ return (rc ? rc : CPIOERR_ENOENT); /* XXX HACK */
+}
+
+/********************************************************************/
+
+#define IS_DEV_LOG(_x) \
+ ((_x) != NULL && strlen(_x) >= (sizeof("/dev/log")-1) && \
+ rstreqn((_x), "/dev/log", sizeof("/dev/log")-1) && \
+ ((_x)[sizeof("/dev/log")-1] == '\0' || \
+ (_x)[sizeof("/dev/log")-1] == ';'))
+
+/**
+ * File state machine driver.
+ * @param fsm file state machine
+ * @param stage next stage
+ * @return 0 on success
+ */
+static int fsmStage(FSM_t fsm, fileStage stage)
+{
+#ifdef UNUSED
+ fileStage prevStage = fsm->stage;
+ const char * const prev = fileStageString(prevStage);
+#endif
+ static int modulo = 4;
+ const char * const cur = fileStageString(stage);
+ struct stat * st = &fsm->sb;
+ struct stat * ost = &fsm->osb;
+ int saveerrno = errno;
+ int rc = fsm->rc;
+ rpm_loff_t left;
+
+#define _fafilter(_a) \
+ (!((_a) == FA_CREATE || (_a) == FA_ERASE || (_a) == FA_COPYIN || (_a) == FA_COPYOUT) \
+ ? fileActionString(_a) : "")
+
+ if (stage & FSM_DEAD) {
+ /* do nothing */
+ } else if (stage & FSM_INTERNAL) {
+ if (_fsm_debug && !(stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s %06o%3d (%4d,%4d)%10d %s %s\n",
+ cur,
+ (unsigned)st->st_mode, (int)st->st_nlink,
+ (int)st->st_uid, (int)st->st_gid, (int)st->st_size,
+ (fsm->path ? fsm->path : ""),
+ _fafilter(fsm->action));
+ } else {
+ fsm->stage = stage;
+ if (_fsm_debug || !(stage & FSM_VERBOSE))
+ rpmlog(RPMLOG_DEBUG, "%-8s %06o%3d (%4d,%4d)%10d %s %s\n",
+ cur,
+ (unsigned)st->st_mode, (int)st->st_nlink,
+ (int)st->st_uid, (int)st->st_gid, (int)st->st_size,
+ (fsm->path ? fsm->path : ""),
+ _fafilter(fsm->action));
+ }
+#undef _fafilter
+
+ switch (stage) {
+ case FSM_UNKNOWN:
+ break;
+ case FSM_PKGINSTALL:
+ while (1) {
+ /* Clean fsm, free'ing memory. Read next archive header. */
+ rc = fsmInit(fsm);
+
+ /* Exit on end-of-payload. */
+ if (rc == CPIOERR_HDR_TRAILER) {
+ rc = 0;
+ break;
+ }
+
+ /* Exit on error. */
+ if (rc) {
+ fsm->postpone = 1;
+ (void) fsmNext(fsm, FSM_UNDO);
+ break;
+ }
+
+ /* Extract file from archive. */
+ rc = fsmNext(fsm, FSM_PROCESS);
+ if (rc) {
+ (void) fsmNext(fsm, FSM_UNDO);
+ break;
+ }
+
+ /* Notify on success. */
+ (void) fsmNext(fsm, FSM_NOTIFY);
+
+ rc = fsmNext(fsm, FSM_FINI);
+ if (rc) {
+ break;
+ }
+ }
+ break;
+ case FSM_PKGERASE:
+ while (1) {
+ /* Clean fsm, free'ing memory. */
+ rc = fsmInit(fsm);
+
+ /* Exit on end-of-payload. */
+ if (rc == CPIOERR_HDR_TRAILER) {
+ rc = 0;
+ break;
+ }
+
+ /* Rename/erase next item. */
+ if (fsmNext(fsm, FSM_FINI))
+ break;
+ }
+ break;
+ case FSM_PKGBUILD:
+ while (1) {
+
+ rc = fsmInit(fsm);
+
+ /* Exit on end-of-payload. */
+ if (rc == CPIOERR_HDR_TRAILER) {
+ rc = 0;
+ break;
+ }
+
+ /* Exit on error. */
+ if (rc) {
+ fsm->postpone = 1;
+ (void) fsmNext(fsm, FSM_UNDO);
+ break;
+ }
+
+ /* Copy file into archive. */
+ rc = fsmNext(fsm, FSM_PROCESS);
+ if (rc) {
+ (void) fsmNext(fsm, FSM_UNDO);
+ break;
+ }
+
+ /* Notify on success. */
+ (void) fsmNext(fsm, FSM_NOTIFY);
+
+ if (fsmNext(fsm, FSM_FINI))
+ break;
+ }
+
+ /* Flush partial sets of hard linked files. */
+ if (!(fsm->mapFlags & CPIO_ALL_HARDLINKS)) {
+ nlink_t i, nlink;
+ int j;
+ while ((fsm->li = fsm->links) != NULL) {
+ fsm->links = fsm->li->next;
+ fsm->li->next = NULL;
+
+ /* Re-calculate link count for archive header. */
+ for (j = -1, nlink = 0, i = 0; i < fsm->li->nlink; i++) {
+ if (fsm->li->filex[i] < 0)
+ continue;
+ nlink++;
+ if (j == -1) j = i;
+ }
+ /* XXX force the contents out as well. */
+ if (j != 0) {
+ fsm->li->filex[0] = fsm->li->filex[j];
+ fsm->li->filex[j] = -1;
+ }
+ fsm->li->sb.st_nlink = nlink;
+
+ fsm->sb = fsm->li->sb; /* structure assignment */
+ fsm->osb = fsm->sb; /* structure assignment */
+
+ if (!rc) rc = writeLinkedFile(fsm);
+
+ fsm->li = freeHardLink(fsm->li);
+ }
+ }
+
+ if (!rc)
+ rc = cpioTrailerWrite(fsm);
+
+ break;
+ case FSM_PROCESS:
+ if (fsm->postpone) {
+ if (fsm->goal == FSM_PKGINSTALL)
+ rc = fsmNext(fsm, FSM_EAT);
+ break;
+ }
+
+ if (fsm->goal == FSM_PKGBUILD) {
+ if (fsm->fflags & RPMFILE_GHOST) /* XXX Don't if %ghost file. */
+ break;
+ if (S_ISREG(st->st_mode) && st->st_nlink > 1) {
+ hardLink_t li, prev;
+
+if (!(fsm->mapFlags & CPIO_ALL_HARDLINKS)) break;
+ rc = writeLinkedFile(fsm);
+ if (rc) break; /* W2DO? */
+
+ for (li = fsm->links, prev = NULL; li; prev = li, li = li->next)
+ if (li == fsm->li)
+ break;
+
+ if (prev == NULL)
+ fsm->links = fsm->li->next;
+ else
+ prev->next = fsm->li->next;
+ fsm->li->next = NULL;
+ fsm->li = freeHardLink(fsm->li);
+ } else {
+ rc = writeFile(fsm, 1);
+ }
+ break;
+ }
+
+ if (fsm->goal != FSM_PKGINSTALL)
+ break;
+
+ if (S_ISREG(st->st_mode)) {
+ char * path = fsm->path;
+ if (fsm->osuffix)
+ fsm->path = fsmFsPath(fsm, st, NULL, NULL);
+ rc = fsmVerify(fsm);
+
+ if (rc == 0 && fsm->osuffix) {
+ char * opath = fsm->opath;
+ fsm->opath = fsm->path;
+ fsm->path = fsmFsPath(fsm, st, NULL, fsm->osuffix);
+ rc = fsmRename(fsm);
+ if (!rc)
+ rpmlog(RPMLOG_WARNING,
+ _("%s saved as %s\n"),
+ (fsm->opath ? fsm->opath : ""),
+ (fsm->path ? fsm->path : ""));
+ fsm->path = _free(fsm->path);
+ fsm->opath = opath;
+ }
+
+ fsm->path = path;
+ if (!(rc == CPIOERR_ENOENT)) return rc;
+ rc = expandRegular(fsm);
+ } else if (S_ISDIR(st->st_mode)) {
+ mode_t st_mode = st->st_mode;
+ rc = fsmVerify(fsm);
+ if (rc == CPIOERR_ENOENT) {
+ st->st_mode &= ~07777; /* XXX abuse st->st_mode */
+ st->st_mode |= 00700;
+ rc = fsmMkdir(fsm);
+ st->st_mode = st_mode; /* XXX restore st->st_mode */
+ }
+ } else if (S_ISLNK(st->st_mode)) {
+ char * opath = fsm->opath;
+
+ if ((st->st_size + 1) > fsm->rdsize) {
+ rc = CPIOERR_HDR_SIZE;
+ break;
+ }
+
+ fsm->wrlen = st->st_size;
+ rc = fsmNext(fsm, FSM_DREAD);
+ if (!rc && fsm->rdnb != fsm->wrlen)
+ rc = CPIOERR_READ_FAILED;
+ if (rc) break;
+
+ fsm->wrbuf[st->st_size] = '\0';
+ /* XXX symlink(fsm->opath, fsm->path) */
+ fsm->opath = fsm->wrbuf;
+ rc = fsmVerify(fsm);
+ if (rc == CPIOERR_ENOENT) {
+ rc = symlink(fsm->opath, fsm->path);
+ if (_fsm_debug && (FSM_SYMLINK & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", fileStageString(FSM_SYMLINK),
+ fsm->opath, fsm->path, (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_SYMLINK_FAILED;
+ }
+ fsm->opath = opath; /* XXX restore fsm->path */
+ } else if (S_ISFIFO(st->st_mode)) {
+ mode_t st_mode = st->st_mode;
+ /* This mimics cpio S_ISSOCK() behavior but probably isnt' right */
+ rc = fsmVerify(fsm);
+ if (rc == CPIOERR_ENOENT) {
+ st->st_mode = 0000; /* XXX abuse st->st_mode */
+
+ rc = mkfifo(fsm->path, (st->st_mode & 07777));
+ if (_fsm_debug && (FSM_MKFIFO & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", fileStageString(FSM_MKFIFO),
+ fsm->path, (unsigned)(st->st_mode & 07777),
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_MKFIFO_FAILED;
+
+ st->st_mode = st_mode; /* XXX restore st->st_mode */
+ }
+ } else if (S_ISCHR(st->st_mode) ||
+ S_ISBLK(st->st_mode) ||
+ S_ISSOCK(st->st_mode))
+ {
+ rc = fsmVerify(fsm);
+ if (rc == CPIOERR_ENOENT) {
+ /* FIX: check S_IFIFO or dev != 0 */
+ rc = mknod(fsm->path, (fsm->sb.st_mode & ~07777), fsm->sb.st_rdev);
+ if (_fsm_debug && (FSM_MKNOD & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%o, 0x%x) %s\n", fileStageString(FSM_MKNOD),
+ fsm->path, (unsigned)(fsm->sb.st_mode & ~07777),
+ (unsigned)fsm->sb.st_rdev,
+ (rc < 0 ? strerror(errno) : ""));
+ if (rc < 0) rc = CPIOERR_MKNOD_FAILED;
+ }
+ } else {
+ /* XXX Special case /dev/log, which shouldn't be packaged anyways */
+ if (!IS_DEV_LOG(fsm->path))
+ rc = CPIOERR_UNKNOWN_FILETYPE;
+ }
+ if (S_ISREG(st->st_mode) && st->st_nlink > 1) {
+ fsm->li->createdPath = fsm->li->linkIndex;
+ rc = fsmMakeLinks(fsm);
+ }
+ break;
+ case FSM_POST:
+ break;
+ case FSM_MKLINKS:
+ rc = fsmMakeLinks(fsm);
+ break;
+ case FSM_NOTIFY: /* XXX move from fsm to psm -> tsm */
+ if (fsm->goal == FSM_PKGINSTALL || fsm->goal == FSM_PKGBUILD) {
+ rpmts ts = fsmGetTs(fsm);
+ rpmte te = fsmGetTe(fsm);
+ rpmfi fi = fsmGetFi(fsm);
+ void * ptr;
+ if (fsm->cpioPos > fsm->archivePos) {
+ fsm->archivePos = fsm->cpioPos;
+ ptr = rpmtsNotify(ts, te, RPMCALLBACK_INST_PROGRESS,
+ fsm->archivePos, fi->archiveSize);
+ }
+ }
+ break;
+ case FSM_UNDO:
+ if (fsm->postpone)
+ break;
+ if (fsm->goal == FSM_PKGINSTALL) {
+ /* XXX only erase if temp fn w suffix is in use */
+ if (fsm->sufbuf[0] != '\0') {
+ if (S_ISDIR(st->st_mode)) {
+ (void) fsmRmdir(fsm);
+ } else {
+ (void) fsmUnlink(fsm);
+ }
+ }
+ errno = saveerrno;
+ }
+ if (fsm->failedFile && *fsm->failedFile == NULL)
+ *fsm->failedFile = xstrdup(fsm->path);
+ break;
+ case FSM_FINI:
+ if (!fsm->postpone) {
+ if (fsm->goal == FSM_PKGINSTALL)
+ rc = ((S_ISREG(st->st_mode) && st->st_nlink > 1)
+ ? fsmCommitLinks(fsm) : fsmNext(fsm, FSM_COMMIT));
+ if (fsm->goal == FSM_PKGERASE)
+ rc = fsmNext(fsm, FSM_COMMIT);
+ }
+ fsm->path = _free(fsm->path);
+ fsm->opath = _free(fsm->opath);
+ memset(st, 0, sizeof(*st));
+ memset(ost, 0, sizeof(*ost));
+ break;
+ case FSM_COMMIT:
+ /* Rename pre-existing modified or unmanaged file. */
+ if (fsm->osuffix && fsm->diskchecked &&
+ (fsm->exists || (fsm->goal == FSM_PKGINSTALL && S_ISREG(st->st_mode))))
+ {
+ char * opath = fsm->opath;
+ char * path = fsm->path;
+ fsm->opath = fsmFsPath(fsm, st, NULL, NULL);
+ fsm->path = fsmFsPath(fsm, st, NULL, fsm->osuffix);
+ rc = fsmRename(fsm);
+ if (!rc) {
+ rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"),
+ (fsm->opath ? fsm->opath : ""),
+ (fsm->path ? fsm->path : ""));
+ }
+ fsm->path = _free(fsm->path);
+ fsm->path = path;
+ fsm->opath = _free(fsm->opath);
+ fsm->opath = opath;
+ }
+
+ /* Remove erased files. */
+ if (fsm->goal == FSM_PKGERASE) {
+ if (fsm->action == FA_ERASE) {
+ rpmte te = fsmGetTe(fsm);
+ if (S_ISDIR(st->st_mode)) {
+ rc = fsmRmdir(fsm);
+ if (!rc) break;
+ switch (rc) {
+ case CPIOERR_ENOENT: /* XXX rmdir("/") linux 2.2.x kernel hack */
+ case CPIOERR_ENOTEMPTY:
+ /* XXX make sure that build side permits %missingok on directories. */
+ if (fsm->fflags & RPMFILE_MISSINGOK)
+ break;
+
+ /* XXX common error message. */
+ rpmlog(
+ (strict_erasures ? RPMLOG_ERR : RPMLOG_DEBUG),
+ _("%s rmdir of %s failed: Directory not empty\n"),
+ rpmteTypeString(te), fsm->path);
+ break;
+ default:
+ rpmlog(
+ (strict_erasures ? RPMLOG_ERR : RPMLOG_DEBUG),
+ _("%s rmdir of %s failed: %s\n"),
+ rpmteTypeString(te), fsm->path, strerror(errno));
+ break;
+ }
+ } else {
+ rc = fsmUnlink(fsm);
+ if (!rc) break;
+ switch (rc) {
+ case CPIOERR_ENOENT:
+ if (fsm->fflags & RPMFILE_MISSINGOK)
+ break;
+ default:
+ rpmlog(
+ (strict_erasures ? RPMLOG_ERR : RPMLOG_DEBUG),
+ _("%s unlink of %s failed: %s\n"),
+ rpmteTypeString(te), fsm->path, strerror(errno));
+ break;
+ }
+ }
+ }
+ /* XXX Failure to remove is not (yet) cause for failure. */
+ if (!strict_erasures) rc = 0;
+ break;
+ }
+
+ /* XXX Special case /dev/log, which shouldn't be packaged anyways */
+ if (!S_ISSOCK(st->st_mode) && !IS_DEV_LOG(fsm->path)) {
+ /* Rename temporary to final file name. */
+ if (!S_ISDIR(st->st_mode) &&
+ (fsm->subdir || fsm->suffix || fsm->nsuffix))
+ {
+ fsm->opath = fsm->path;
+ fsm->path = fsmFsPath(fsm, st, NULL, fsm->nsuffix);
+ rc = fsmRename(fsm);
+ if (!rc && fsm->nsuffix) {
+ char * opath = fsmFsPath(fsm, st, NULL, NULL);
+ rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
+ (opath ? opath : ""),
+ (fsm->path ? fsm->path : ""));
+ opath = _free(opath);
+ }
+ fsm->opath = _free(fsm->opath);
+ }
+ /*
+ * Set file security context (if not disabled).
+ */
+ if (!rc && !getuid()) {
+ rc = fsmMapFContext(fsm);
+ if (!rc) {
+ rc = fsmLsetfcon(fsm);
+ freecon(fsm->fcontext);
+ }
+ fsm->fcontext = NULL;
+ }
+ if (S_ISLNK(st->st_mode)) {
+ if (!rc && !getuid())
+ rc = fsmLChown(fsm);
+ } else {
+ if (!rc && !getuid())
+ rc = fsmChown(fsm);
+ if (!rc)
+ rc = fsmChmod(fsm);
+ if (!rc) {
+ time_t mtime = st->st_mtime;
+ rpmfi fi = fsmGetFi(fsm);
+ st->st_mtime = rpmfiFMtimeIndex(fi, fsm->ix);
+ rc = fsmUtime(fsm);
+ st->st_mtime = mtime;
+ /* utime error is not critical for directories */
+ if (rc && S_ISDIR(st->st_mode))
+ rc = 0;
+ }
+#if WITH_CAP
+ if (!rc && !S_ISDIR(st->st_mode) && !getuid()) {
+ rc = fsmMapFCaps(fsm);
+ if (!rc && fsm->fcaps) {
+ rc = fsmSetcap(fsm);
+ cap_free(fsm->fcaps);
+ }
+ fsm->fcaps = NULL;
+ }
+#endif /* WITH_CAP */
+ }
+ }
+
+ /* Notify on success. */
+ if (!rc) rc = fsmNext(fsm, FSM_NOTIFY);
+ else if (fsm->failedFile && *fsm->failedFile == NULL) {
+ *fsm->failedFile = fsm->path;
+ fsm->path = NULL;
+ }
+ break;
+ case FSM_DESTROY:
+ fsm->path = _free(fsm->path);
+
+ /* Check for hard links missing from payload. */
+ while ((fsm->li = fsm->links) != NULL) {
+ fsm->links = fsm->li->next;
+ fsm->li->next = NULL;
+ if (fsm->goal == FSM_PKGINSTALL && fsm->li->linksLeft) {
+ for (nlink_t i = 0 ; i < fsm->li->linksLeft; i++) {
+ if (fsm->li->filex[i] < 0)
+ continue;
+ rc = CPIOERR_MISSING_HARDLINK;
+ if (fsm->failedFile && *fsm->failedFile == NULL) {
+ fsm->ix = fsm->li->filex[i];
+ if (!fsmMapPath(fsm)) {
+ /* Out-of-sync hardlinks handled as sub-state */
+ *fsm->failedFile = fsm->path;
+ fsm->path = NULL;
+ }
+ }
+ break;
+ }
+ }
+ if (fsm->goal == FSM_PKGBUILD &&
+ (fsm->mapFlags & CPIO_ALL_HARDLINKS))
+ {
+ rc = CPIOERR_MISSING_HARDLINK;
+ }
+ fsm->li = freeHardLink(fsm->li);
+ }
+ fsm->ldn = _free(fsm->ldn);
+ fsm->ldnalloc = fsm->ldnlen = 0;
+ fsm->rdbuf = fsm->rdb = _free(fsm->rdb);
+ fsm->wrbuf = fsm->wrb = _free(fsm->wrb);
+ break;
+ case FSM_NEXT:
+ rc = fsmNext(fsm, FSM_POS);
+ if (!rc)
+ rc = cpioHeaderRead(fsm, st); /* Read next payload header. */
+ if (rc) break;
+ if (rstreq(fsm->path, CPIO_TRAILER)) { /* Detect end-of-payload. */
+ fsm->path = _free(fsm->path);
+ rc = CPIOERR_HDR_TRAILER;
+ }
+ if (!rc)
+ rc = fsmNext(fsm, FSM_POS);
+ break;
+ case FSM_EAT:
+ for (left = st->st_size; left > 0; left -= fsm->rdnb) {
+ fsm->wrlen = (left > fsm->wrsize ? fsm->wrsize : left);
+ rc = fsmNext(fsm, FSM_DREAD);
+ if (rc)
+ break;
+ }
+ break;
+ case FSM_POS:
+ left = (modulo - (fsm->cpioPos % modulo)) % modulo;
+ if (left) {
+ fsm->wrlen = left;
+ (void) fsmNext(fsm, FSM_DREAD);
+ }
+ break;
+ case FSM_PAD:
+ left = (modulo - (fsm->cpioPos % modulo)) % modulo;
+ if (left) {
+ memset(fsm->rdbuf, 0, left);
+ /* XXX DWRITE uses rdnb for I/O length. */
+ fsm->rdnb = left;
+ (void) fsmNext(fsm, FSM_DWRITE);
+ }
+ break;
+ case FSM_DREAD:
+ fsm->rdnb = Fread(fsm->wrbuf, sizeof(*fsm->wrbuf), fsm->wrlen, fsm->cfd);
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, cfd)\trdnb %d\n",
+ cur, (fsm->wrbuf == fsm->wrb ? "wrbuf" : "mmap"),
+ (int)fsm->wrlen, (int)fsm->rdnb);
+ if (fsm->rdnb != fsm->wrlen || Ferror(fsm->cfd))
+ rc = CPIOERR_READ_FAILED;
+ if (fsm->rdnb > 0)
+ fsm->cpioPos += fsm->rdnb;
+ break;
+ case FSM_DWRITE:
+ fsm->wrnb = Fwrite(fsm->rdbuf, sizeof(*fsm->rdbuf), fsm->rdnb, fsm->cfd);
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, cfd)\twrnb %d\n",
+ cur, (fsm->rdbuf == fsm->rdb ? "rdbuf" : "mmap"),
+ (int)fsm->rdnb, (int)fsm->wrnb);
+ if (fsm->rdnb != fsm->wrnb || Ferror(fsm->cfd))
+ rc = CPIOERR_WRITE_FAILED;
+ if (fsm->wrnb > 0)
+ fsm->cpioPos += fsm->wrnb;
+ break;
+
+ case FSM_ROPEN:
+ fsm->rfd = Fopen(fsm->path, "r.ufdio");
+ if (fsm->rfd == NULL || Ferror(fsm->rfd)) {
+ if (fsm->rfd != NULL) (void) fsmNext(fsm, FSM_RCLOSE);
+ fsm->rfd = NULL;
+ rc = CPIOERR_OPEN_FAILED;
+ break;
+ }
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, \"r\") rfd %p rdbuf %p\n", cur,
+ fsm->path, fsm->rfd, fsm->rdbuf);
+ break;
+ case FSM_READ:
+ fsm->rdnb = Fread(fsm->rdbuf, sizeof(*fsm->rdbuf), fsm->rdlen, fsm->rfd);
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (rdbuf, %d, rfd)\trdnb %d\n",
+ cur, (int)fsm->rdlen, (int)fsm->rdnb);
+ if (fsm->rdnb != fsm->rdlen || Ferror(fsm->rfd))
+ rc = CPIOERR_READ_FAILED;
+ break;
+ case FSM_RCLOSE:
+ if (fsm->rfd != NULL) {
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%p)\n", cur, fsm->rfd);
+ (void) rpmswAdd(rpmtsOp(fsmGetTs(fsm), RPMTS_OP_DIGEST),
+ fdOp(fsm->rfd, FDSTAT_DIGEST));
+ (void) Fclose(fsm->rfd);
+ errno = saveerrno;
+ }
+ fsm->rfd = NULL;
+ break;
+ case FSM_WOPEN:
+ fsm->wfd = Fopen(fsm->path, "w.ufdio");
+ if (fsm->wfd == NULL || Ferror(fsm->wfd)) {
+ if (fsm->wfd != NULL) (void) fsmNext(fsm, FSM_WCLOSE);
+ fsm->wfd = NULL;
+ rc = CPIOERR_OPEN_FAILED;
+ }
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, \"w\") wfd %p wrbuf %p\n", cur,
+ fsm->path, fsm->wfd, fsm->wrbuf);
+ break;
+ case FSM_WRITE:
+ fsm->wrnb = Fwrite(fsm->wrbuf, sizeof(*fsm->wrbuf), fsm->rdnb, fsm->wfd);
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (wrbuf, %d, wfd)\twrnb %d\n",
+ cur, (int)fsm->rdnb, (int)fsm->wrnb);
+ if (fsm->rdnb != fsm->wrnb || Ferror(fsm->wfd))
+ rc = CPIOERR_WRITE_FAILED;
+ break;
+ case FSM_WCLOSE:
+ if (fsm->wfd != NULL) {
+ if (_fsm_debug && (stage & FSM_SYSCALL))
+ rpmlog(RPMLOG_DEBUG, " %8s (%p)\n", cur, fsm->wfd);
+ (void) rpmswAdd(rpmtsOp(fsmGetTs(fsm), RPMTS_OP_DIGEST),
+ fdOp(fsm->wfd, FDSTAT_DIGEST));
+ (void) Fclose(fsm->wfd);
+ errno = saveerrno;
+ }
+ fsm->wfd = NULL;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!(stage & FSM_INTERNAL)) {
+ fsm->rc = (rc == CPIOERR_HDR_TRAILER ? 0 : rc);
+ }
+ return rc;
+}
+
+/**
+ * Return formatted string representation of file disposition.
+ * @param a file dispostion
+ * @return formatted string
+ */
+static const char * fileActionString(rpmFileAction a)
+{
+ switch (a) {
+ case FA_UNKNOWN: return "unknown";
+ case FA_CREATE: return "create";
+ case FA_COPYOUT: return "copyout";
+ case FA_COPYIN: return "copyin";
+ case FA_BACKUP: return "backup";
+ case FA_SAVE: return "save";
+ case FA_SKIP: return "skip";
+ case FA_ALTNAME: return "altname";
+ case FA_ERASE: return "erase";
+ case FA_SKIPNSTATE: return "skipnstate";
+ case FA_SKIPNETSHARED: return "skipnetshared";
+ case FA_SKIPCOLOR: return "skipcolor";
+ default: return "???";
+ }
+}
+
+/**
+ * Return formatted string representation of file stages.
+ * @param a file stage
+ * @return formatted string
+ */
+static const char * fileStageString(fileStage a) {
+ switch(a) {
+ case FSM_UNKNOWN: return "unknown";
+
+ case FSM_PKGINSTALL:return "INSTALL";
+ case FSM_PKGERASE: return "ERASE";
+ case FSM_PKGBUILD: return "BUILD";
+ case FSM_PKGUNDO: return "UNDO";
+
+ case FSM_CREATE: return "create";
+ case FSM_INIT: return "init";
+ case FSM_MAP: return "map";
+ case FSM_MKDIRS: return "mkdirs";
+ case FSM_RMDIRS: return "rmdirs";
+ case FSM_PRE: return "pre";
+ case FSM_PROCESS: return "process";
+ case FSM_POST: return "post";
+ case FSM_MKLINKS: return "mklinks";
+ case FSM_NOTIFY: return "notify";
+ case FSM_UNDO: return "undo";
+ case FSM_FINI: return "fini";
+ case FSM_COMMIT: return "commit";
+ case FSM_DESTROY: return "destroy";
+ case FSM_VERIFY: return "verify";
+
+ case FSM_UNLINK: return "unlink";
+ case FSM_RENAME: return "rename";
+ case FSM_MKDIR: return "mkdir";
+ case FSM_RMDIR: return "rmdir";
+ case FSM_LSETFCON: return "lsetfcon";
+ case FSM_CHOWN: return "chown";
+ case FSM_LCHOWN: return "lchown";
+ case FSM_CHMOD: return "chmod";
+ case FSM_UTIME: return "utime";
+ case FSM_SYMLINK: return "symlink";
+ case FSM_LINK: return "link";
+ case FSM_MKFIFO: return "mkfifo";
+ case FSM_MKNOD: return "mknod";
+ case FSM_LSTAT: return "lstat";
+ case FSM_STAT: return "stat";
+ case FSM_READLINK: return "readlink";
+ case FSM_SETCAP: return "setcap";
+
+ case FSM_NEXT: return "next";
+ case FSM_EAT: return "eat";
+ case FSM_POS: return "pos";
+ case FSM_PAD: return "pad";
+ case FSM_TRAILER: return "trailer";
+ case FSM_HREAD: return "hread";
+ case FSM_HWRITE: return "hwrite";
+ case FSM_DREAD: return "Fread";
+ case FSM_DWRITE: return "Fwrite";
+
+ case FSM_ROPEN: return "Fopen";
+ case FSM_READ: return "Fread";
+ case FSM_RCLOSE: return "Fclose";
+ case FSM_WOPEN: return "Fopen";
+ case FSM_WRITE: return "Fwrite";
+ case FSM_WCLOSE: return "Fclose";
+
+ default: return "???";
+ }
+}
diff --git a/lib/fsm.h b/lib/fsm.h
new file mode 100644
index 0000000..22aa935
--- /dev/null
+++ b/lib/fsm.h
@@ -0,0 +1,227 @@
+#ifndef H_FSM
+#define H_FSM
+
+/** \ingroup payload
+ * \file lib/fsm.h
+ * File state machine to handle a payload within an rpm package.
+ */
+
+#include <rpm/rpmfi.h>
+
+extern int _fsm_debug;
+
+/**
+ */
+#define FSM_VERBOSE 0x8000
+#define FSM_INTERNAL 0x4000
+#define FSM_SYSCALL 0x2000
+#define FSM_DEAD 0x1000
+
+#define _fv(_a) ((_a) | FSM_VERBOSE)
+#define _fi(_a) ((_a) | FSM_INTERNAL)
+#define _fs(_a) ((_a) | (FSM_INTERNAL | FSM_SYSCALL))
+#define _fd(_a) ((_a) | (FSM_INTERNAL | FSM_DEAD))
+
+typedef enum fileStage_e {
+ FSM_UNKNOWN = 0,
+ FSM_INIT = _fd(1),
+ FSM_PRE = _fd(2),
+ FSM_PROCESS = _fv(3),
+ FSM_POST = _fd(4),
+ FSM_UNDO = 5,
+ FSM_FINI = 6,
+
+ FSM_PKGINSTALL = _fd(7),
+ FSM_PKGERASE = _fd(8),
+ FSM_PKGBUILD = _fd(9),
+ FSM_PKGUNDO = _fd(11),
+
+ FSM_CREATE = _fd(17),
+ FSM_MAP = _fd(18),
+ FSM_MKDIRS = _fi(19),
+ FSM_RMDIRS = _fi(20),
+ FSM_MKLINKS = _fi(21),
+ FSM_NOTIFY = _fd(22),
+ FSM_DESTROY = _fd(23),
+ FSM_VERIFY = _fd(24),
+ FSM_COMMIT = _fd(25),
+
+ FSM_UNLINK = _fs(33),
+ FSM_RENAME = _fs(34),
+ FSM_MKDIR = _fs(35),
+ FSM_RMDIR = _fs(36),
+ FSM_LSETFCON= _fs(39),
+ FSM_CHOWN = _fs(40),
+ FSM_LCHOWN = _fs(41),
+ FSM_CHMOD = _fs(42),
+ FSM_UTIME = _fs(43),
+ FSM_SYMLINK = _fs(44),
+ FSM_LINK = _fs(45),
+ FSM_MKFIFO = _fs(46),
+ FSM_MKNOD = _fs(47),
+ FSM_LSTAT = _fs(48),
+ FSM_STAT = _fs(49),
+ FSM_READLINK= _fs(50),
+ FSM_SETCAP = _fs(52),
+
+ FSM_NEXT = _fd(65),
+ FSM_EAT = _fd(66),
+ FSM_POS = _fd(67),
+ FSM_PAD = _fd(68),
+ FSM_TRAILER = _fd(69),
+ FSM_HREAD = _fd(70),
+ FSM_HWRITE = _fd(71),
+ FSM_DREAD = _fs(72),
+ FSM_DWRITE = _fs(73),
+
+ FSM_ROPEN = _fs(129),
+ FSM_READ = _fs(130),
+ FSM_RCLOSE = _fs(131),
+ FSM_WOPEN = _fs(132),
+ FSM_WRITE = _fs(133),
+ FSM_WCLOSE = _fs(134)
+} fileStage;
+#undef _fv
+#undef _fi
+#undef _fs
+#undef _fd
+
+/** \ingroup payload
+ */
+enum cpioMapFlags_e {
+ CPIO_MAP_PATH = (1 << 0),
+ CPIO_MAP_MODE = (1 << 1),
+ CPIO_MAP_UID = (1 << 2),
+ CPIO_MAP_GID = (1 << 3),
+ CPIO_FOLLOW_SYMLINKS= (1 << 4), /*!< only for building. */
+ CPIO_MAP_ABSOLUTE = (1 << 5),
+ CPIO_MAP_ADDDOT = (1 << 6),
+ CPIO_ALL_HARDLINKS = (1 << 7), /*!< fail if hardlinks are missing. */
+ CPIO_MAP_TYPE = (1 << 8), /*!< only for building. */
+ CPIO_SBIT_CHECK = (1 << 9)
+};
+typedef rpmFlags cpioMapFlags;
+
+typedef struct fsmIterator_s * FSMI_t;
+typedef struct fsm_s * FSM_t;
+
+typedef struct hardLink_s * hardLink_t;
+
+/** \ingroup payload
+ * File name and stat information.
+ */
+struct fsm_s {
+ char * path; /*!< Current file name. */
+ char * opath; /*!< Original file name. */
+ FD_t cfd; /*!< Payload file handle. */
+ FD_t rfd; /*!< read: File handle. */
+ char * rdbuf; /*!< read: Buffer. */
+ char * rdb; /*!< read: Buffer allocated. */
+ size_t rdsize; /*!< read: Buffer allocated size. */
+ size_t rdlen; /*!< read: Number of bytes requested.*/
+ size_t rdnb; /*!< read: Number of bytes returned. */
+ FD_t wfd; /*!< write: File handle. */
+ char * wrbuf; /*!< write: Buffer. */
+ char * wrb; /*!< write: Buffer allocated. */
+ size_t wrsize; /*!< write: Buffer allocated size. */
+ size_t wrlen; /*!< write: Number of bytes requested.*/
+ size_t wrnb; /*!< write: Number of bytes returned. */
+ FSMI_t iter; /*!< File iterator. */
+ int ix; /*!< Current file iterator index. */
+ hardLink_t links; /*!< Pending hard linked file(s). */
+ hardLink_t li; /*!< Current hard linked file(s). */
+ rpm_loff_t * archiveSize; /*!< Pointer to archive size. */
+ rpm_loff_t archivePos; /*!< Current archive position. */
+ rpm_loff_t cpioPos;
+ char ** failedFile; /*!< First file name that failed. */
+ const char * subdir; /*!< Current file sub-directory. */
+ const char * osuffix; /*!< Old, preserved, file suffix. */
+ const char * nsuffix; /*!< New, created, file suffix. */
+ const char * suffix; /*!< Current file suffix. */
+ char sufbuf[64]; /* XXX eliminate */
+ short * dnlx; /*!< Last dirpath verified indexes. */
+ char * ldn; /*!< Last dirpath verified. */
+ int ldnlen; /*!< Last dirpath current length. */
+ int ldnalloc; /*!< Last dirpath allocated length. */
+ int postpone; /*!< Skip remaining stages? */
+ int diskchecked; /*!< Has stat(2) been performed? */
+ int exists; /*!< Does current file exist on disk? */
+ int mkdirsdone; /*!< Have "orphan" dirs been created? */
+ int rc; /*!< External file stage return code. */
+ cpioMapFlags mapFlags; /*!< Bit(s) to control mapping. */
+ const char * dirName; /*!< File directory name. */
+ const char * baseName; /*!< File base name. */
+ const unsigned char * digest; /*!< Binary digest (NULL disables). */
+ security_context_t fcontext;/*!< File security context (NULL disables). */
+ void *fcaps; /*!< File capabilities */
+ pgpHashAlgo digestalgo; /*!< File digest algorithm */
+
+ unsigned fflags; /*!< File flags. */
+ rpmFileAction action; /*!< File disposition. */
+ fileStage goal; /*!< Package state machine goal. */
+ fileStage stage; /*!< External file stage. */
+ fileStage nstage; /*!< Next file stage. */
+ struct stat sb; /*!< Current file stat(2) info. */
+ struct stat osb; /*!< Original file stat(2) info. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Create file state machine instance.
+ * @param mapflags CPIO map flags to use
+ * @return file state machine
+ */
+RPM_GNUC_INTERNAL
+FSM_t newFSM(cpioMapFlags mapflags);
+
+/**
+ * Destroy file state machine instance.
+ * @param fsm file state machine
+ * @return always NULL
+ */
+RPM_GNUC_INTERNAL
+FSM_t freeFSM(FSM_t fsm);
+
+/**
+ * Load external data into file state machine.
+ * @param fsm file state machine
+ * @param goal
+ * @param ts transaction set
+ * @param fi transaction element file info
+ * @param cfd
+ * @retval archiveSize pointer to archive size
+ * @retval failedFile pointer to first file name that failed (malloced)
+ * @return 0 on success
+ */
+int fsmSetup(FSM_t fsm, fileStage goal,
+ rpmts ts,
+ rpmte te,
+ rpmfi fi,
+ FD_t cfd,
+ rpm_loff_t * archiveSize,
+ char ** failedFile);
+
+/**
+ * Clean file state machine.
+ * @param fsm file state machine
+ * @return 0 on success
+ */
+int fsmTeardown(FSM_t fsm);
+
+/**
+ * File state machine driver.
+ * @param fsm file state machine
+ * @param nstage next stage
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int fsmNext(FSM_t fsm, fileStage nstage);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_FSM */
diff --git a/lib/gentagtbl.sh b/lib/gentagtbl.sh
new file mode 100755
index 0000000..0558d09
--- /dev/null
+++ b/lib/gentagtbl.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+cat << EOF
+static const struct headerTagTableEntry_s rpmTagTable[] = {
+EOF
+
+${AWK} '/[\t ](RPMTAG_[A-Z0-9]*)[ \t]+([0-9]*)/ && !/internal/ && !/unimplemented/ {
+ tt = "NULL"
+ ta = "ANY"
+ ext = "0"
+ if ($5 == "c") {
+ tt = "CHAR"
+ ta = "SCALAR"
+ }
+ if ($5 == "c[]") {
+ tt = "CHAR"
+ ta = "ARRAY"
+ }
+ if ($5 == "h") {
+ tt = "INT16"
+ ta = "SCALAR"
+ }
+ if ($5 == "h[]") {
+ tt = "INT16"
+ ta = "ARRAY"
+ }
+ if ($5 == "i") {
+ tt = "INT32"
+ ta = "SCALAR"
+ }
+ if ($5 == "i[]") {
+ tt = "INT32"
+ ta = "ARRAY"
+ }
+ if ($5 == "l") {
+ tt = "INT64"
+ ta = "SCALAR"
+ }
+ if ($5 == "l[]") {
+ tt = "INT64"
+ ta = "ARRAY"
+ }
+ if ($5 == "s") {
+ tt = "STRING"
+ ta = "SCALAR"
+ }
+ if ($5 == "s[]") {
+ tt = "STRING_ARRAY"
+ ta = "ARRAY"
+ }
+ if ($5 == "s{}") {
+ tt = "I18NSTRING"
+ ta = "SCALAR"
+ }
+ if ($5 == "x") {
+ tt = "BIN"
+ ta = "SCALAR"
+ }
+ if ($6 == "extension") {
+ ext = "1"
+ }
+ if ($2 == "=") {
+ tnarg = $1
+ } else {
+ tnarg = $2
+ }
+ tn = substr(tnarg, index(tnarg, "_") + 1)
+ sn = (substr(tn, 1, 1) tolower(substr(tn, 2)))
+ if ($2 == "=") {
+ printf(" { \"%s\", \"%s\", %s, RPM_%s_TYPE, RPM_%s_RETURN_TYPE, %d },\n", tnarg, sn, tnarg, tt, ta, ext)
+ } else {
+ printf(" { \"%s\", \"%s\", %s, RPM_%s_TYPE, RPM_%s_RETURN_TYPE, %d },\n", tnarg, sn, $3, tt, ta, ext)
+ }
+}' < $1 | sort
+
+cat << EOF
+ { NULL, NULL, RPMTAG_NOT_FOUND, RPM_NULL_TYPE, 0 }
+};
+EOF
+
diff --git a/lib/header.c b/lib/header.c
new file mode 100644
index 0000000..a1bfe38
--- /dev/null
+++ b/lib/header.c
@@ -0,0 +1,1761 @@
+/** \ingroup header
+ * \file lib/header.c
+ */
+
+/* RPM - Copyright (C) 1995-2002 Red Hat Software */
+
+/* Data written to file descriptors is in network byte order. */
+/* Data read from file descriptors is expected to be in */
+/* network byte order and is converted on the fly to host order. */
+
+#include "system.h"
+#include <netdb.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmstring.h>
+#include "lib/header_internal.h"
+#include "lib/misc.h" /* tag function proto */
+
+#include "debug.h"
+
+/** \ingroup header
+ */
+const unsigned char rpm_header_magic[8] = {
+ 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+};
+
+/** \ingroup header
+ * Alignment needed for header data types.
+ */
+static const int typeAlign[16] = {
+ 1, /*!< RPM_NULL_TYPE */
+ 1, /*!< RPM_CHAR_TYPE */
+ 1, /*!< RPM_INT8_TYPE */
+ 2, /*!< RPM_INT16_TYPE */
+ 4, /*!< RPM_INT32_TYPE */
+ 8, /*!< RPM_INT64_TYPE */
+ 1, /*!< RPM_STRING_TYPE */
+ 1, /*!< RPM_BIN_TYPE */
+ 1, /*!< RPM_STRING_ARRAY_TYPE */
+ 1, /*!< RPM_I18NSTRING_TYPE */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+/** \ingroup header
+ * Size of header data types.
+ */
+static const int typeSizes[16] = {
+ 0, /*!< RPM_NULL_TYPE */
+ 1, /*!< RPM_CHAR_TYPE */
+ 1, /*!< RPM_INT8_TYPE */
+ 2, /*!< RPM_INT16_TYPE */
+ 4, /*!< RPM_INT32_TYPE */
+ 8, /*!< RPM_INT64_TYPE */
+ -1, /*!< RPM_STRING_TYPE */
+ 1, /*!< RPM_BIN_TYPE */
+ -1, /*!< RPM_STRING_ARRAY_TYPE */
+ -1, /*!< RPM_I18NSTRING_TYPE */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+enum headerFlags_e {
+ HEADERFLAG_SORTED = (1 << 0), /*!< Are header entries sorted? */
+ HEADERFLAG_ALLOCATED = (1 << 1), /*!< Is 1st header region allocated? */
+ HEADERFLAG_LEGACY = (1 << 2), /*!< Header came from legacy source? */
+ HEADERFLAG_DEBUG = (1 << 3), /*!< Debug this header? */
+};
+
+typedef rpmFlags headerFlags;
+
+/** \ingroup header
+ * The Header data structure.
+ */
+struct headerToken_s {
+ void * blob; /*!< Header region blob. */
+ indexEntry index; /*!< Array of tags. */
+ int indexUsed; /*!< Current size of tag array. */
+ int indexAlloced; /*!< Allocated size of tag array. */
+ unsigned int instance; /*!< Rpmdb instance (offset) */
+ headerFlags flags;
+ int nrefs; /*!< Reference count. */
+};
+
+/** \ingroup header
+ * Maximum no. of bytes permitted in a header.
+ */
+static const size_t headerMaxbytes = (32*1024*1024);
+
+#define INDEX_MALLOC_SIZE 8
+
+#define ENTRY_IS_REGION(_e) \
+ (((_e)->info.tag >= RPMTAG_HEADERIMAGE) && ((_e)->info.tag < RPMTAG_HEADERREGIONS))
+#define ENTRY_IN_REGION(_e) ((_e)->info.offset < 0)
+
+/* Convert a 64bit value to network byte order. */
+static uint64_t htonll( uint64_t n ) {
+ uint32_t *i = (uint32_t*)&n;
+ uint32_t b = i[0];
+ i[0] = htonl(i[1]);
+ i[1] = htonl(b);
+ return n;
+}
+
+Header headerLink(Header h)
+{
+ if (h != NULL)
+ h->nrefs++;
+ return h;
+}
+
+static Header headerUnlink(Header h)
+{
+ if (h != NULL)
+ h->nrefs--;
+ return NULL;
+}
+
+Header headerFree(Header h)
+{
+ (void) headerUnlink(h);
+
+ if (h == NULL || h->nrefs > 0)
+ return NULL; /* XXX return previous header? */
+
+ if (h->index) {
+ indexEntry entry = h->index;
+ int i;
+ for (i = 0; i < h->indexUsed; i++, entry++) {
+ if ((h->flags & HEADERFLAG_ALLOCATED) && ENTRY_IS_REGION(entry)) {
+ if (entry->length > 0) {
+ int32_t * ei = entry->data;
+ if ((ei - 2) == h->blob) h->blob = _free(h->blob);
+ entry->data = NULL;
+ }
+ } else if (!ENTRY_IN_REGION(entry)) {
+ entry->data = _free(entry->data);
+ }
+ entry->data = NULL;
+ }
+ h->index = _free(h->index);
+ }
+
+ h = _free(h);
+ return h;
+}
+
+static Header headerCreate(void *blob, int32_t indexLen)
+{
+ Header h = xcalloc(1, sizeof(*h));
+ h->blob = blob;
+ if (blob) {
+ h->indexAlloced = indexLen + 1;
+ h->indexUsed = indexLen;
+ } else {
+ h->indexAlloced = INDEX_MALLOC_SIZE;
+ h->indexUsed = 0;
+ }
+ h->instance = 0;
+ h->flags |= HEADERFLAG_SORTED;
+
+ h->index = (h->indexAlloced
+ ? xcalloc(h->indexAlloced, sizeof(*h->index))
+ : NULL);
+
+ h->nrefs = 0;
+ return headerLink(h);
+}
+
+Header headerNew(void)
+{
+ return headerCreate(NULL, 0);
+}
+
+int headerVerifyInfo(int il, int dl, const void * pev, void * iv, int negate)
+{
+ entryInfo pe = (entryInfo) pev;
+ entryInfo info = iv;
+ int i;
+
+ for (i = 0; i < il; i++) {
+ info->tag = ntohl(pe[i].tag);
+ info->type = ntohl(pe[i].type);
+ info->offset = ntohl(pe[i].offset);
+ if (negate)
+ info->offset = -info->offset;
+ info->count = ntohl(pe[i].count);
+
+ if (hdrchkType(info->type))
+ return i;
+ if (hdrchkAlign(info->type, info->offset))
+ return i;
+ if (!negate && hdrchkRange(dl, info->offset))
+ return i;
+ if (hdrchkData(info->count))
+ return i;
+
+ }
+ return -1;
+}
+
+/**
+ */
+static int indexCmp(const void * avp, const void * bvp)
+{
+ indexEntry ap = (indexEntry) avp, bp = (indexEntry) bvp;
+ return (ap->info.tag - bp->info.tag);
+}
+
+void headerSort(Header h)
+{
+ if (!(h->flags & HEADERFLAG_SORTED)) {
+ qsort(h->index, h->indexUsed, sizeof(*h->index), indexCmp);
+ h->flags |= HEADERFLAG_SORTED;
+ }
+}
+
+/**
+ */
+static int offsetCmp(const void * avp, const void * bvp)
+{
+ indexEntry ap = (indexEntry) avp, bp = (indexEntry) bvp;
+ int rc = (ap->info.offset - bp->info.offset);
+
+ if (rc == 0) {
+ /* Within a region, entries sort by address. Added drips sort by tag. */
+ if (ap->info.offset < 0)
+ rc = (((char *)ap->data) - ((char *)bp->data));
+ else
+ rc = (ap->info.tag - bp->info.tag);
+ }
+ return rc;
+}
+
+/** \ingroup header
+ * Restore tags in header to original ordering.
+ * @param h header
+ */
+void headerUnsort(Header h)
+{
+ if (h->flags & HEADERFLAG_SORTED) {
+ qsort(h->index, h->indexUsed, sizeof(*h->index), offsetCmp);
+ h->flags &= ~HEADERFLAG_SORTED;
+ }
+}
+
+unsigned headerSizeof(Header h, int magicp)
+{
+ indexEntry entry;
+ unsigned int size = 0;
+ int i;
+
+ if (h == NULL)
+ return size;
+
+ headerSort(h);
+
+ switch (magicp) {
+ case HEADER_MAGIC_YES:
+ size += sizeof(rpm_header_magic);
+ break;
+ case HEADER_MAGIC_NO:
+ break;
+ }
+
+ size += 2 * sizeof(int32_t); /* count of index entries */
+
+ for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
+ rpm_tagtype_t type;
+
+ /* Regions go in as is ... */
+ if (ENTRY_IS_REGION(entry)) {
+ size += entry->length;
+ /* XXX Legacy regions do not include the region tag and data. */
+ if (i == 0 && (h->flags & HEADERFLAG_LEGACY))
+ size += sizeof(struct entryInfo_s) + entry->info.count;
+ continue;
+ }
+
+ /* ... and region elements are skipped. */
+ if (entry->info.offset < 0)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff = typeSizes[type] - (size % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ size += diff;
+ }
+ }
+
+ size += sizeof(struct entryInfo_s) + entry->length;
+ }
+
+ return size;
+}
+
+/**
+ * Return length of entry data.
+ * @param type entry data type
+ * @param p entry data
+ * @param count entry item count
+ * @param onDisk data is concatenated strings (with NUL's))?
+ * @param pend pointer to end of data (or NULL)
+ * @return no. bytes in data, -1 on failure
+ */
+static int dataLength(rpm_tagtype_t type, rpm_constdata_t p, rpm_count_t count,
+ int onDisk, rpm_constdata_t pend)
+{
+ const unsigned char * s = p;
+ const unsigned char * se = pend;
+ int length = 0;
+
+ switch (type) {
+ case RPM_STRING_TYPE:
+ if (count != 1)
+ return -1;
+ while (*s++) {
+ if (se && s > se)
+ return -1;
+ length++;
+ }
+ length++; /* count nul terminator too. */
+ break;
+
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ /* These are like RPM_STRING_TYPE, except they're *always* an array */
+ /* Compute sum of length of all strings, including nul terminators */
+
+ if (onDisk) {
+ while (count--) {
+ length++; /* count nul terminator too */
+ while (*s++) {
+ if (se && s > se)
+ return -1;
+ length++;
+ }
+ }
+ } else {
+ const char ** av = (const char **)p;
+ while (count--) {
+ /* add one for null termination */
+ length += strlen(*av++) + 1;
+ }
+ }
+ break;
+
+ default:
+ if (typeSizes[type] == -1)
+ return -1;
+ length = typeSizes[(type & 0xf)] * count;
+ if (length < 0 || (se && (s + length) > se))
+ return -1;
+ break;
+ }
+
+ return length;
+}
+
+/** \ingroup header
+ * Swap int32_t and int16_t arrays within header region.
+ *
+ * If a header region tag is in the set to be swabbed, as the data for a
+ * a header region is located after all other tag data.
+ *
+ * @param entry header entry
+ * @param il no. of entries
+ * @param dl start no. bytes of data
+ * @param pe header physical entry pointer (swapped)
+ * @param dataStart header data start
+ * @param dataEnd header data end
+ * @param regionid region offset
+ * @return no. bytes of data in region, -1 on error
+ */
+static int regionSwab(indexEntry entry, int il, int dl,
+ entryInfo pe,
+ unsigned char * dataStart,
+ const unsigned char * dataEnd,
+ int regionid)
+{
+ for (; il > 0; il--, pe++) {
+ struct indexEntry_s ie;
+ rpm_tagtype_t type;
+
+ ie.info.tag = ntohl(pe->tag);
+ ie.info.type = ntohl(pe->type);
+ ie.info.count = ntohl(pe->count);
+ ie.info.offset = ntohl(pe->offset);
+
+ if (hdrchkType(ie.info.type))
+ return -1;
+ if (hdrchkData(ie.info.count))
+ return -1;
+ if (hdrchkData(ie.info.offset))
+ return -1;
+ if (hdrchkAlign(ie.info.type, ie.info.offset))
+ return -1;
+
+ ie.data = dataStart + ie.info.offset;
+ if (dataEnd && (unsigned char *)ie.data >= dataEnd)
+ return -1;
+
+ ie.length = dataLength(ie.info.type, ie.data, ie.info.count, 1, dataEnd);
+ if (ie.length < 0 || hdrchkData(ie.length))
+ return -1;
+
+ ie.rdlen = 0;
+
+ if (entry) {
+ ie.info.offset = regionid;
+ *entry = ie; /* structure assignment */
+ entry++;
+ }
+
+ /* Alignment */
+ type = ie.info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff = typeSizes[type] - (dl % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ dl += diff;
+ }
+ }
+
+ /* Perform endian conversions */
+ switch (ntohl(pe->type)) {
+ case RPM_INT64_TYPE:
+ { uint64_t * it = ie.data;
+ for (; ie.info.count > 0; ie.info.count--, it += 1) {
+ if (dataEnd && ((unsigned char *)it) >= dataEnd)
+ return -1;
+ *it = htonll(*it);
+ }
+ } break;
+ case RPM_INT32_TYPE:
+ { int32_t * it = ie.data;
+ for (; ie.info.count > 0; ie.info.count--, it += 1) {
+ if (dataEnd && ((unsigned char *)it) >= dataEnd)
+ return -1;
+ *it = htonl(*it);
+ }
+ } break;
+ case RPM_INT16_TYPE:
+ { int16_t * it = ie.data;
+ for (; ie.info.count > 0; ie.info.count--, it += 1) {
+ if (dataEnd && ((unsigned char *)it) >= dataEnd)
+ return -1;
+ *it = htons(*it);
+ }
+ } break;
+ }
+
+ dl += ie.length;
+ }
+
+ return dl;
+}
+
+/** \ingroup header
+ * doHeaderUnload.
+ * @param h header
+ * @retval *lengthPtr no. bytes in unloaded header blob
+ * @return unloaded header blob (NULL on error)
+ */
+static void * doHeaderUnload(Header h,
+ size_t * lengthPtr)
+{
+ int32_t * ei = NULL;
+ entryInfo pe;
+ char * dataStart;
+ char * te;
+ unsigned len;
+ int32_t il = 0;
+ int32_t dl = 0;
+ indexEntry entry;
+ rpm_tagtype_t type;
+ int i;
+ int drlen, ndribbles;
+
+ if (h == NULL) return NULL;
+
+ /* Sort entries by (offset,tag). */
+ headerUnsort(h);
+
+ /* Compute (il,dl) for all tags, including those deleted in region. */
+ drlen = ndribbles = 0;
+ for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
+ if (ENTRY_IS_REGION(entry)) {
+ int32_t rdl = -entry->info.offset; /* negative offset */
+ int32_t ril = rdl/sizeof(*pe);
+ int rid = entry->info.offset;
+
+ il += ril;
+ dl += entry->rdlen + entry->info.count;
+ /* XXX Legacy regions do not include the region tag and data. */
+ if (i == 0 && (h->flags & HEADERFLAG_LEGACY))
+ il += 1;
+
+ /* Skip rest of entries in region, but account for dribbles. */
+ for (; i < h->indexUsed && entry->info.offset <= rid+1; i++, entry++) {
+ if (entry->info.offset <= rid)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff = typeSizes[type] - (dl % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ drlen += diff;
+ dl += diff;
+ }
+ }
+
+ ndribbles++;
+ il++;
+ drlen += entry->length;
+ dl += entry->length;
+ }
+ i--;
+ entry--;
+ continue;
+ }
+
+ /* Ignore deleted drips. */
+ if (entry->data == NULL || entry->length <= 0)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff = typeSizes[type] - (dl % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ dl += diff;
+ }
+ }
+
+ il++;
+ dl += entry->length;
+ }
+
+ /* Sanity checks on header intro. */
+ if (hdrchkTags(il) || hdrchkData(dl))
+ goto errxit;
+
+ len = sizeof(il) + sizeof(dl) + (il * sizeof(*pe)) + dl;
+
+ ei = xmalloc(len);
+ ei[0] = htonl(il);
+ ei[1] = htonl(dl);
+
+ pe = (entryInfo) &ei[2];
+ dataStart = te = (char *) (pe + il);
+
+ for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
+ const char * src;
+ unsigned char *t;
+ int count;
+ int rdlen;
+
+ if (entry->data == NULL || entry->length <= 0)
+ continue;
+
+ t = (unsigned char*)te;
+ pe->tag = htonl(entry->info.tag);
+ pe->type = htonl(entry->info.type);
+ pe->count = htonl(entry->info.count);
+
+ if (ENTRY_IS_REGION(entry)) {
+ int32_t rdl = -entry->info.offset; /* negative offset */
+ int32_t ril = rdl/sizeof(*pe) + ndribbles;
+ int rid = entry->info.offset;
+
+ src = (char *)entry->data;
+ rdlen = entry->rdlen;
+
+ /* XXX Legacy regions do not include the region tag and data. */
+ if (i == 0 && (h->flags & HEADERFLAG_LEGACY)) {
+ int32_t stei[4];
+
+ memcpy(pe+1, src, rdl);
+ memcpy(te, src + rdl, rdlen);
+ te += rdlen;
+
+ pe->offset = htonl(te - dataStart);
+ stei[0] = pe->tag;
+ stei[1] = pe->type;
+ stei[2] = htonl(-rdl-entry->info.count);
+ stei[3] = pe->count;
+ memcpy(te, stei, entry->info.count);
+ te += entry->info.count;
+ ril++;
+ rdlen += entry->info.count;
+
+ count = regionSwab(NULL, ril, 0, pe, t, NULL, 0);
+ if (count != rdlen)
+ goto errxit;
+
+ } else {
+
+ memcpy(pe+1, src + sizeof(*pe), ((ril-1) * sizeof(*pe)));
+ memcpy(te, src + (ril * sizeof(*pe)), rdlen+entry->info.count+drlen);
+ te += rdlen;
+ {
+ entryInfo se = (entryInfo)src;
+ int off = ntohl(se->offset);
+ pe->offset = (off) ? htonl(te - dataStart) : htonl(off);
+ }
+ te += entry->info.count + drlen;
+
+ count = regionSwab(NULL, ril, 0, pe, t, NULL, 0);
+ if (count != (rdlen + entry->info.count + drlen))
+ goto errxit;
+ }
+
+ /* Skip rest of entries in region. */
+ while (i < h->indexUsed && entry->info.offset <= rid+1) {
+ i++;
+ entry++;
+ }
+ i--;
+ entry--;
+ pe += ril;
+ continue;
+ }
+
+ /* Ignore deleted drips. */
+ if (entry->data == NULL || entry->length <= 0)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff;
+ diff = typeSizes[type] - ((te - dataStart) % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ memset(te, 0, diff);
+ te += diff;
+ }
+ }
+
+ pe->offset = htonl(te - dataStart);
+
+ /* copy data w/ endian conversions */
+ switch (entry->info.type) {
+ case RPM_INT64_TYPE:
+ count = entry->info.count;
+ src = entry->data;
+ while (count--) {
+ *((uint64_t *)te) = htonll(*((uint64_t *)src));
+ te += sizeof(uint64_t);
+ src += sizeof(uint64_t);
+ }
+ break;
+
+ case RPM_INT32_TYPE:
+ count = entry->info.count;
+ src = entry->data;
+ while (count--) {
+ *((int32_t *)te) = htonl(*((int32_t *)src));
+ te += sizeof(int32_t);
+ src += sizeof(int32_t);
+ }
+ break;
+
+ case RPM_INT16_TYPE:
+ count = entry->info.count;
+ src = entry->data;
+ while (count--) {
+ *((int16_t *)te) = htons(*((int16_t *)src));
+ te += sizeof(int16_t);
+ src += sizeof(int16_t);
+ }
+ break;
+
+ default:
+ memcpy(te, entry->data, entry->length);
+ te += entry->length;
+ break;
+ }
+ pe++;
+ }
+
+ /* Insure that there are no memcpy underruns/overruns. */
+ if (((char *)pe) != dataStart)
+ goto errxit;
+ if ((((char *)ei)+len) != te)
+ goto errxit;
+
+ if (lengthPtr)
+ *lengthPtr = len;
+
+ headerSort(h);
+
+ return (void *) ei;
+
+errxit:
+ ei = _free(ei);
+ return (void *) ei;
+}
+
+void * headerUnload(Header h)
+{
+ size_t length;
+ void * uh = doHeaderUnload(h, &length);
+ return uh;
+}
+
+/**
+ * Find matching (tag,type) entry in header.
+ * @param h header
+ * @param tag entry tag
+ * @param type entry type
+ * @return header entry
+ */
+static
+indexEntry findEntry(Header h, rpmTagVal tag, rpm_tagtype_t type)
+{
+ indexEntry entry;
+ struct indexEntry_s key;
+
+ if (h == NULL) return NULL;
+ if (!(h->flags & HEADERFLAG_SORTED)) headerSort(h);
+
+ key.info.tag = tag;
+
+ entry = bsearch(&key, h->index, h->indexUsed, sizeof(*h->index), indexCmp);
+ if (entry == NULL)
+ return NULL;
+
+ if (type == RPM_NULL_TYPE)
+ return entry;
+
+ /* look backwards */
+ while (entry->info.tag == tag && entry->info.type != type &&
+ entry > h->index) entry--;
+
+ if (entry->info.tag == tag && entry->info.type == type)
+ return entry;
+
+ return NULL;
+}
+
+int headerDel(Header h, rpmTagVal tag)
+{
+ indexEntry last = h->index + h->indexUsed;
+ indexEntry entry, first;
+ int ne;
+
+ entry = findEntry(h, tag, RPM_NULL_TYPE);
+ if (!entry) return 1;
+
+ /* Make sure entry points to the first occurence of this tag. */
+ while (entry > h->index && (entry - 1)->info.tag == tag)
+ entry--;
+
+ /* Free data for tags being removed. */
+ for (first = entry; first < last; first++) {
+ rpm_data_t data;
+ if (first->info.tag != tag)
+ break;
+ data = first->data;
+ first->data = NULL;
+ first->length = 0;
+ if (ENTRY_IN_REGION(first))
+ continue;
+ data = _free(data);
+ }
+
+ ne = (first - entry);
+ if (ne > 0) {
+ h->indexUsed -= ne;
+ ne = last - first;
+ if (ne > 0)
+ memmove(entry, first, (ne * sizeof(*entry)));
+ }
+
+ return 0;
+}
+
+Header headerLoad(void * uh)
+{
+ int32_t * ei = (int32_t *) uh;
+ int32_t il = ntohl(ei[0]); /* index length */
+ int32_t dl = ntohl(ei[1]); /* data length */
+ size_t pvlen = sizeof(il) + sizeof(dl) +
+ (il * sizeof(struct entryInfo_s)) + dl;
+ void * pv = uh;
+ Header h = NULL;
+ entryInfo pe;
+ unsigned char * dataStart;
+ unsigned char * dataEnd;
+ indexEntry entry;
+ int rdlen;
+
+ /* Sanity checks on header intro. */
+ if (hdrchkTags(il) || hdrchkData(dl))
+ goto errxit;
+
+ ei = (int32_t *) pv;
+ pe = (entryInfo) &ei[2];
+ dataStart = (unsigned char *) (pe + il);
+ dataEnd = dataStart + dl;
+
+ h = headerCreate(uh, il);
+
+ entry = h->index;
+ if (!(htonl(pe->tag) < RPMTAG_HEADERI18NTABLE)) {
+ h->flags |= HEADERFLAG_LEGACY;
+ entry->info.type = REGION_TAG_TYPE;
+ entry->info.tag = RPMTAG_HEADERIMAGE;
+ entry->info.count = REGION_TAG_COUNT;
+ entry->info.offset = ((unsigned char *)pe - dataStart); /* negative offset */
+
+ entry->data = pe;
+ entry->length = pvlen - sizeof(il) - sizeof(dl);
+ rdlen = regionSwab(entry+1, il, 0, pe, dataStart, dataEnd, entry->info.offset);
+ if (rdlen != dl)
+ goto errxit;
+ entry->rdlen = rdlen;
+ h->indexUsed++;
+ } else {
+ int32_t rdl;
+ int32_t ril;
+
+ h->flags &= ~HEADERFLAG_LEGACY;
+
+ entry->info.type = htonl(pe->type);
+ entry->info.count = htonl(pe->count);
+
+ if (hdrchkType(entry->info.type))
+ goto errxit;
+ if (hdrchkTags(entry->info.count))
+ goto errxit;
+
+ { int off = ntohl(pe->offset);
+
+ if (hdrchkData(off))
+ goto errxit;
+ if (off) {
+ size_t nb = REGION_TAG_COUNT;
+ int32_t stei[nb];
+ /* XXX Hmm, why the copy? */
+ memcpy(&stei, dataStart + off, nb);
+ rdl = -ntohl(stei[2]); /* negative offset */
+ ril = rdl/sizeof(*pe);
+ if (hdrchkTags(ril) || hdrchkData(rdl))
+ goto errxit;
+ entry->info.tag = htonl(pe->tag);
+ } else {
+ ril = il;
+ rdl = (ril * sizeof(struct entryInfo_s));
+ entry->info.tag = RPMTAG_HEADERIMAGE;
+ }
+ }
+ entry->info.offset = -rdl; /* negative offset */
+
+ entry->data = pe;
+ entry->length = pvlen - sizeof(il) - sizeof(dl);
+ rdlen = regionSwab(entry+1, ril-1, 0, pe+1, dataStart, dataEnd, entry->info.offset);
+ if (rdlen < 0)
+ goto errxit;
+ entry->rdlen = rdlen;
+
+ if (ril < h->indexUsed) {
+ indexEntry newEntry = entry + ril;
+ int ne = (h->indexUsed - ril);
+ int rid = entry->info.offset+1;
+ int rc;
+
+ /* Load dribble entries from region. */
+ rc = regionSwab(newEntry, ne, 0, pe+ril, dataStart, dataEnd, rid);
+ if (rc < 0)
+ goto errxit;
+ rdlen += rc;
+
+ { indexEntry firstEntry = newEntry;
+ int save = h->indexUsed;
+ int j;
+
+ /* Dribble entries replace duplicate region entries. */
+ h->indexUsed -= ne;
+ for (j = 0; j < ne; j++, newEntry++) {
+ (void) headerDel(h, newEntry->info.tag);
+ if (newEntry->info.tag == RPMTAG_BASENAMES)
+ (void) headerDel(h, RPMTAG_OLDFILENAMES);
+ }
+
+ /* If any duplicate entries were replaced, move new entries down. */
+ if (h->indexUsed < (save - ne)) {
+ memmove(h->index + h->indexUsed, firstEntry,
+ (ne * sizeof(*entry)));
+ }
+ h->indexUsed += ne;
+ }
+ }
+ }
+
+ h->flags &= ~HEADERFLAG_SORTED;
+ headerSort(h);
+ h->flags |= HEADERFLAG_ALLOCATED;
+
+ return h;
+
+errxit:
+ if (h) {
+ h->index = _free(h->index);
+ h = _free(h);
+ }
+ return h;
+}
+
+Header headerReload(Header h, rpmTagVal tag)
+{
+ Header nh;
+ size_t length;
+ void * uh = doHeaderUnload(h, &length);
+
+ h = headerFree(h);
+ if (uh == NULL)
+ return NULL;
+ nh = headerLoad(uh);
+ if (nh == NULL) {
+ uh = _free(uh);
+ return NULL;
+ }
+ if (ENTRY_IS_REGION(nh->index)) {
+ if (tag == RPMTAG_HEADERSIGNATURES || tag == RPMTAG_HEADERIMMUTABLE)
+ nh->index[0].info.tag = tag;
+ }
+ return nh;
+}
+
+Header headerCopyLoad(const void * uh)
+{
+ int32_t * ei = (int32_t *) uh;
+ int32_t il = ntohl(ei[0]); /* index length */
+ int32_t dl = ntohl(ei[1]); /* data length */
+ size_t pvlen = sizeof(il) + sizeof(dl) +
+ (il * sizeof(struct entryInfo_s)) + dl;
+ void * nuh = NULL;
+ Header h = NULL;
+
+ /* Sanity checks on header intro. */
+ if (!(hdrchkTags(il) || hdrchkData(dl)) && pvlen < headerMaxbytes) {
+ nuh = memcpy(xmalloc(pvlen), uh, pvlen);
+ if ((h = headerLoad(nuh)) == NULL)
+ nuh = _free(nuh);
+ }
+ return h;
+}
+
+/** \ingroup header
+ * Read (and load) header from file handle.
+ * @param fd file handle
+ * @param magicp read (and verify) 8 bytes of (magic, 0)?
+ * @return header (or NULL on error)
+ */
+Header headerRead(FD_t fd, int magicp)
+{
+ int32_t block[4];
+ int32_t reserved;
+ int32_t * ei = NULL;
+ int32_t il;
+ int32_t dl;
+ int32_t magic;
+ Header h = NULL;
+ size_t len;
+ int i;
+
+ memset(block, 0, sizeof(block));
+ i = 2;
+ if (magicp == HEADER_MAGIC_YES)
+ i += 2;
+
+ /* FIX: cast? */
+ if (timedRead(fd, (char *)block, i*sizeof(*block)) != (i * sizeof(*block)))
+ goto exit;
+
+ i = 0;
+
+ if (magicp == HEADER_MAGIC_YES) {
+ magic = block[i++];
+ if (memcmp(&magic, rpm_header_magic, sizeof(magic)))
+ goto exit;
+ reserved = block[i++];
+ }
+
+ il = ntohl(block[i]); i++;
+ dl = ntohl(block[i]); i++;
+
+ len = sizeof(il) + sizeof(dl) + (il * sizeof(struct entryInfo_s)) + dl;
+
+ /* Sanity checks on header intro. */
+ if (hdrchkTags(il) || hdrchkData(dl) || len > headerMaxbytes)
+ goto exit;
+
+ ei = xmalloc(len);
+ ei[0] = htonl(il);
+ ei[1] = htonl(dl);
+ len -= sizeof(il) + sizeof(dl);
+
+ /* FIX: cast? */
+ if (timedRead(fd, (char *)&ei[2], len) != len)
+ goto exit;
+
+ h = headerLoad(ei);
+
+exit:
+ if (h == NULL && ei != NULL) {
+ free(ei);
+ }
+ return h;
+}
+
+int headerWrite(FD_t fd, Header h, int magicp)
+{
+ ssize_t nb;
+ size_t length;
+ void * uh;
+
+ uh = doHeaderUnload(h, &length);
+ if (uh == NULL)
+ return 1;
+ switch (magicp) {
+ case HEADER_MAGIC_YES:
+ nb = Fwrite(rpm_header_magic, sizeof(uint8_t), sizeof(rpm_header_magic), fd);
+ if (nb != sizeof(rpm_header_magic))
+ goto exit;
+ break;
+ case HEADER_MAGIC_NO:
+ break;
+ }
+
+ nb = Fwrite(uh, sizeof(char), length, fd);
+
+exit:
+ uh = _free(uh);
+ return (nb == length ? 0 : 1);
+}
+
+int headerIsEntry(Header h, rpmTagVal tag)
+{
+ /* FIX: h modified by sort. */
+ return (findEntry(h, tag, RPM_NULL_TYPE) ? 1 : 0);
+
+}
+
+/** \ingroup header
+ * Retrieve data from header entry.
+ * Relevant flags (others are ignored), if neither is set allocation
+ * behavior depends on data type(!)
+ * HEADERGET_MINMEM: return pointers to header memory
+ * HEADERGET_ALLOC: always return malloced memory, overrides MINMEM
+ *
+ * @todo Permit retrieval of regions other than HEADER_IMUTABLE.
+ * @param entry header entry
+ * @param td tag data container
+ * @param minMem string pointers refer to header memory?
+ * @param flags flags to control memory allocation
+ * @return 1 on success, otherwise error.
+ */
+static int copyTdEntry(const indexEntry entry, rpmtd td, headerGetFlags flags)
+{
+ rpm_count_t count = entry->info.count;
+ int rc = 1; /* XXX 1 on success. */
+ /* ALLOC overrides MINMEM */
+ int allocMem = flags & HEADERGET_ALLOC;
+ int minMem = allocMem ? 0 : flags & HEADERGET_MINMEM;
+ int argvArray = (flags & HEADERGET_ARGV) ? 1 : 0;
+
+ assert(td != NULL);
+ td->flags = RPMTD_IMMUTABLE;
+ switch (entry->info.type) {
+ case RPM_BIN_TYPE:
+ /*
+ * XXX This only works for
+ * XXX "sealed" HEADER_IMMUTABLE/HEADER_SIGNATURES/HEADER_IMAGE.
+ * XXX This will *not* work for unsealed legacy HEADER_IMAGE (i.e.
+ * XXX a legacy header freshly read, but not yet unloaded to the rpmdb).
+ */
+ if (ENTRY_IS_REGION(entry)) {
+ int32_t * ei = ((int32_t *)entry->data) - 2;
+ entryInfo pe = (entryInfo) (ei + 2);
+ unsigned char * dataStart = (unsigned char *) (pe + ntohl(ei[0]));
+ int32_t rdl = -entry->info.offset; /* negative offset */
+ int32_t ril = rdl/sizeof(*pe);
+
+ rdl = entry->rdlen;
+ count = 2 * sizeof(*ei) + (ril * sizeof(*pe)) + rdl;
+ if (entry->info.tag == RPMTAG_HEADERIMAGE) {
+ ril -= 1;
+ pe += 1;
+ } else {
+ count += REGION_TAG_COUNT;
+ rdl += REGION_TAG_COUNT;
+ }
+
+ td->data = xmalloc(count);
+ ei = (int32_t *) td->data;
+ ei[0] = htonl(ril);
+ ei[1] = htonl(rdl);
+
+ pe = (entryInfo) memcpy(ei + 2, pe, (ril * sizeof(*pe)));
+
+ dataStart = (unsigned char *) memcpy(pe + ril, dataStart, rdl);
+
+ rc = regionSwab(NULL, ril, 0, pe, dataStart, dataStart + rdl, 0);
+ /* don't return data on failure */
+ if (rc < 0) {
+ td->data = _free(td->data);
+ }
+ /* XXX 1 on success. */
+ rc = (rc < 0) ? 0 : 1;
+ } else {
+ count = entry->length;
+ td->data = (!minMem
+ ? memcpy(xmalloc(count), entry->data, count)
+ : entry->data);
+ }
+ break;
+ case RPM_STRING_TYPE:
+ /* simple string, but fallthrough if its actually an array */
+ if (count == 1 && !argvArray) {
+ td->data = allocMem ? xstrdup(entry->data) : entry->data;
+ break;
+ }
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ { const char ** ptrEntry;
+ int tableSize = (count + argvArray) * sizeof(char *);
+ char * t;
+ int i;
+
+ if (minMem) {
+ td->data = xmalloc(tableSize);
+ ptrEntry = (const char **) td->data;
+ t = entry->data;
+ } else {
+ t = xmalloc(tableSize + entry->length);
+ td->data = (void *)t;
+ ptrEntry = (const char **) td->data;
+ t += tableSize;
+ memcpy(t, entry->data, entry->length);
+ }
+ for (i = 0; i < count; i++) {
+ *ptrEntry++ = t;
+ t = strchr(t, 0);
+ t++;
+ }
+ if (argvArray) {
+ *ptrEntry = NULL;
+ td->flags |= RPMTD_ARGV;
+ }
+ } break;
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ case RPM_INT16_TYPE:
+ case RPM_INT32_TYPE:
+ case RPM_INT64_TYPE:
+ if (allocMem) {
+ td->data = xmalloc(entry->length);
+ memcpy(td->data, entry->data, entry->length);
+ } else {
+ td->data = entry->data;
+ }
+ break;
+ default:
+ /* WTH? Don't mess with unknown data types... */
+ rc = 0;
+ td->data = NULL;
+ break;
+ }
+ td->type = entry->info.type;
+ td->count = count;
+
+ if (td->data && entry->data != td->data) {
+ td->flags |= RPMTD_ALLOCED;
+ }
+
+ return rc;
+}
+
+/**
+ * Does locale match entry in header i18n table?
+ *
+ * \verbatim
+ * The range [l,le) contains the next locale to match:
+ * ll[_CC][.EEEEE][@dddd]
+ * where
+ * ll ISO language code (in lowercase).
+ * CC (optional) ISO coutnry code (in uppercase).
+ * EEEEE (optional) encoding (not really standardized).
+ * dddd (optional) dialect.
+ * \endverbatim
+ *
+ * @param td header i18n table data, NUL terminated
+ * @param l start of locale to match
+ * @param le end of locale to match
+ * @return 1 on good match, 2 on weak match, 0 on no match
+ */
+static int headerMatchLocale(const char *td, const char *l, const char *le)
+{
+ const char *fe;
+
+ /* First try a complete match. */
+ if (strlen(td) == (le-l) && rstreqn(td, l, (le - l)))
+ return 1;
+
+ /* Next, try stripping optional dialect and matching. */
+ for (fe = l; fe < le && *fe != '@'; fe++)
+ {};
+ if (fe < le && rstreqn(td, l, (fe - l)))
+ return 1;
+
+ /* Next, try stripping optional codeset and matching. */
+ for (fe = l; fe < le && *fe != '.'; fe++)
+ {};
+ if (fe < le && rstreqn(td, l, (fe - l)))
+ return 1;
+
+ /* Finally, try stripping optional country code and matching. */
+ for (fe = l; fe < le && *fe != '_'; fe++)
+ {};
+ if (fe < le && rstreqn(td, l, (fe - l)))
+ return 2;
+
+ return 0;
+}
+
+/**
+ * Return i18n string from header that matches locale.
+ * @param h header
+ * @param entry i18n string data
+ * @retval td tag data container
+ * @param flags flags to control allocation
+ * @return 1 always
+ */
+static int copyI18NEntry(Header h, indexEntry entry, rpmtd td,
+ headerGetFlags flags)
+{
+ const char *lang, *l, *le;
+ indexEntry table;
+
+ td->type = RPM_STRING_TYPE;
+ td->count = 1;
+ /* if no match, just return the first string */
+ td->data = entry->data;
+
+ /* XXX Drepper sez' this is the order. */
+ if ((lang = getenv("LANGUAGE")) == NULL &&
+ (lang = getenv("LC_ALL")) == NULL &&
+ (lang = getenv("LC_MESSAGES")) == NULL &&
+ (lang = getenv("LANG")) == NULL)
+ goto exit;
+
+ if ((table = findEntry(h, RPMTAG_HEADERI18NTABLE, RPM_STRING_ARRAY_TYPE)) == NULL)
+ goto exit;
+
+ for (l = lang; *l != '\0'; l = le) {
+ const char *t;
+ char *ed, *ed_weak = NULL;
+ int langNum;
+
+ while (*l && *l == ':') /* skip leading colons */
+ l++;
+ if (*l == '\0')
+ break;
+ for (le = l; *le && *le != ':'; le++) /* find end of this locale */
+ {};
+
+ /* For each entry in the header ... */
+ for (langNum = 0, t = table->data, ed = entry->data;
+ langNum < entry->info.count;
+ langNum++, t += strlen(t) + 1, ed += strlen(ed) + 1) {
+
+ int match = headerMatchLocale(t, l, le);
+ if (match == 1) {
+ td->data = ed;
+ goto exit;
+ } else if (match == 2) {
+ ed_weak = ed;
+ }
+ }
+ if (ed_weak) {
+ td->data = ed_weak;
+ goto exit;
+ }
+ }
+
+exit:
+ if (flags & HEADERGET_ALLOC) {
+ td->data = xstrdup(td->data);
+ td->flags |= RPMTD_ALLOCED;
+ }
+
+ return 1;
+}
+
+/**
+ * Retrieve tag data from header.
+ * @param h header
+ * @retval td tag data container
+ * @param flags flags to control retrieval
+ * @return 1 on success, 0 on not found
+ */
+static int intGetTdEntry(Header h, rpmtd td, headerGetFlags flags)
+{
+ indexEntry entry;
+ int rc;
+
+ /* First find the tag */
+ /* FIX: h modified by sort. */
+ entry = findEntry(h, td->tag, RPM_NULL_TYPE);
+ if (entry == NULL) {
+ /* Td is zeroed above, just return... */
+ return 0;
+ }
+
+ if (flags & HEADERGET_RAW) {
+ rc = copyTdEntry(entry, td, flags);
+ } else {
+ switch (entry->info.type) {
+ case RPM_I18NSTRING_TYPE:
+ rc = copyI18NEntry(h, entry, td, flags);
+ break;
+ default:
+ rc = copyTdEntry(entry, td, flags);
+ break;
+ }
+ }
+
+ /* XXX 1 on success */
+ return ((rc == 1) ? 1 : 0);
+}
+
+int headerGet(Header h, rpmTagVal tag, rpmtd td, headerGetFlags flags)
+{
+ int rc;
+ headerTagTagFunction tagfunc = intGetTdEntry;
+
+ if (td == NULL) return 0;
+
+ rpmtdReset(td);
+ td->tag = tag;
+
+ if (flags & HEADERGET_EXT) {
+ headerTagTagFunction extfunc = rpmHeaderTagFunc(tag);
+ if (extfunc) tagfunc = extfunc;
+ }
+ rc = tagfunc(h, td, flags);
+
+ assert(tag == td->tag);
+ return rc;
+}
+
+/**
+ */
+static void copyData(rpm_tagtype_t type, rpm_data_t dstPtr,
+ rpm_constdata_t srcPtr, rpm_count_t cnt, int dataLength)
+{
+ switch (type) {
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ { const char ** av = (const char **) srcPtr;
+ char * t = dstPtr;
+
+ while (cnt-- > 0 && dataLength > 0) {
+ const char * s;
+ if ((s = *av++) == NULL)
+ continue;
+ do {
+ *t++ = *s++;
+ } while (s[-1] && --dataLength > 0);
+ }
+ } break;
+
+ default:
+ memmove(dstPtr, srcPtr, dataLength);
+ break;
+ }
+}
+
+/**
+ * Return (malloc'ed) copy of entry data.
+ * @param type entry data type
+ * @param p entry data
+ * @param c entry item count
+ * @retval lengthPtr no. bytes in returned data
+ * @return (malloc'ed) copy of entry data, NULL on error
+ */
+static void *
+grabData(rpm_tagtype_t type, rpm_constdata_t p, rpm_count_t c, int * lengthPtr)
+{
+ rpm_data_t data = NULL;
+ int length;
+
+ length = dataLength(type, p, c, 0, NULL);
+ if (length > 0) {
+ data = xmalloc(length);
+ copyData(type, data, p, c, length);
+ }
+
+ if (lengthPtr)
+ *lengthPtr = length;
+ return data;
+}
+
+static int intAddEntry(Header h, rpmtd td)
+{
+ indexEntry entry;
+ rpm_data_t data;
+ int length;
+
+ /* Count must always be >= 1 for headerAddEntry. */
+ if (td->count <= 0)
+ return 0;
+
+ if (hdrchkType(td->type))
+ return 0;
+ if (hdrchkData(td->count))
+ return 0;
+
+ length = 0;
+ data = grabData(td->type, td->data, td->count, &length);
+ if (data == NULL || length <= 0)
+ return 0;
+
+ /* Allocate more index space if necessary */
+ if (h->indexUsed == h->indexAlloced) {
+ h->indexAlloced += INDEX_MALLOC_SIZE;
+ h->index = xrealloc(h->index, h->indexAlloced * sizeof(*h->index));
+ }
+
+ /* Fill in the index */
+ entry = h->index + h->indexUsed;
+ entry->info.tag = td->tag;
+ entry->info.type = td->type;
+ entry->info.count = td->count;
+ entry->info.offset = 0;
+ entry->data = data;
+ entry->length = length;
+
+ if (h->indexUsed > 0 && td->tag < h->index[h->indexUsed-1].info.tag)
+ h->flags &= ~HEADERFLAG_SORTED;
+ h->indexUsed++;
+
+ return 1;
+}
+
+static int intAppendEntry(Header h, rpmtd td)
+{
+ indexEntry entry;
+ int length;
+
+ if (td->type == RPM_STRING_TYPE || td->type == RPM_I18NSTRING_TYPE) {
+ /* we can't do this */
+ return 0;
+ }
+
+ /* Find the tag entry in the header. */
+ entry = findEntry(h, td->tag, td->type);
+ if (!entry)
+ return 0;
+
+ length = dataLength(td->type, td->data, td->count, 0, NULL);
+ if (length < 0)
+ return 0;
+
+ if (ENTRY_IN_REGION(entry)) {
+ char * t = xmalloc(entry->length + length);
+ memcpy(t, entry->data, entry->length);
+ entry->data = t;
+ entry->info.offset = 0;
+ } else
+ entry->data = xrealloc(entry->data, entry->length + length);
+
+ copyData(td->type, ((char *) entry->data) + entry->length,
+ td->data, td->count, length);
+
+ entry->length += length;
+
+ entry->info.count += td->count;
+
+ return 1;
+}
+
+int headerPut(Header h, rpmtd td, headerPutFlags flags)
+{
+ int rc;
+
+ assert(td != NULL);
+ if (flags & HEADERPUT_APPEND) {
+ rc = findEntry(h, td->tag, td->type) ?
+ intAppendEntry(h, td) :
+ intAddEntry(h, td);
+ } else {
+ rc = intAddEntry(h, td);
+ }
+ return rc;
+}
+
+int headerAddI18NString(Header h, rpmTagVal tag, const char * string,
+ const char * lang)
+{
+ indexEntry table, entry;
+ const char ** strArray;
+ int length;
+ int ghosts;
+ rpm_count_t i, langNum;
+ char * buf;
+
+ table = findEntry(h, RPMTAG_HEADERI18NTABLE, RPM_STRING_ARRAY_TYPE);
+ entry = findEntry(h, tag, RPM_I18NSTRING_TYPE);
+
+ if (!table && entry)
+ return 0; /* this shouldn't ever happen!! */
+
+ if (!table && !entry) {
+ const char * charArray[2];
+ rpm_count_t count = 0;
+ struct rpmtd_s td;
+ if (!lang || (lang[0] == 'C' && lang[1] == '\0')) {
+ charArray[count++] = "C";
+ } else {
+ charArray[count++] = "C";
+ charArray[count++] = lang;
+ }
+
+ rpmtdReset(&td);
+ td.tag = RPMTAG_HEADERI18NTABLE;
+ td.type = RPM_STRING_ARRAY_TYPE;
+ td.data = (void *) charArray;
+ td.count = count;
+ if (!headerPut(h, &td, HEADERPUT_DEFAULT))
+ return 0;
+ table = findEntry(h, RPMTAG_HEADERI18NTABLE, RPM_STRING_ARRAY_TYPE);
+ }
+
+ if (!table)
+ return 0;
+ if (!lang) lang = "C";
+
+ { const char * l = table->data;
+ for (langNum = 0; langNum < table->info.count; langNum++) {
+ if (rstreq(l, lang)) break;
+ l += strlen(l) + 1;
+ }
+ }
+
+ if (langNum >= table->info.count) {
+ length = strlen(lang) + 1;
+ if (ENTRY_IN_REGION(table)) {
+ char * t = xmalloc(table->length + length);
+ memcpy(t, table->data, table->length);
+ table->data = t;
+ table->info.offset = 0;
+ } else
+ table->data = xrealloc(table->data, table->length + length);
+ memmove(((char *)table->data) + table->length, lang, length);
+ table->length += length;
+ table->info.count++;
+ }
+
+ if (!entry) {
+ int rc;
+ struct rpmtd_s td;
+ strArray = xmalloc(sizeof(*strArray) * (langNum + 1));
+ for (i = 0; i < langNum; i++)
+ strArray[i] = "";
+ strArray[langNum] = string;
+
+ rpmtdReset(&td);
+ td.tag = tag;
+ td.type = RPM_I18NSTRING_TYPE;
+ td.data = strArray;
+ td.count = langNum + 1;
+ rc = headerPut(h, &td, HEADERPUT_DEFAULT);
+ free(strArray);
+ return rc;
+ } else if (langNum >= entry->info.count) {
+ ghosts = langNum - entry->info.count;
+
+ length = strlen(string) + 1 + ghosts;
+ if (ENTRY_IN_REGION(entry)) {
+ char * t = xmalloc(entry->length + length);
+ memcpy(t, entry->data, entry->length);
+ entry->data = t;
+ entry->info.offset = 0;
+ } else
+ entry->data = xrealloc(entry->data, entry->length + length);
+
+ memset(((char *)entry->data) + entry->length, '\0', ghosts);
+ memmove(((char *)entry->data) + entry->length + ghosts, string, strlen(string)+1);
+
+ entry->length += length;
+ entry->info.count = langNum + 1;
+ } else {
+ char *b, *be, *e, *ee, *t;
+ size_t bn, sn, en;
+
+ /* Set beginning/end pointers to previous data */
+ b = be = e = ee = entry->data;
+ for (i = 0; i < table->info.count; i++) {
+ if (i == langNum)
+ be = ee;
+ ee += strlen(ee) + 1;
+ if (i == langNum)
+ e = ee;
+ }
+
+ /* Get storage for new buffer */
+ bn = (be-b);
+ sn = strlen(string) + 1;
+ en = (ee-e);
+ length = bn + sn + en;
+ t = buf = xmalloc(length);
+
+ /* Copy values into new storage */
+ memcpy(t, b, bn);
+ t += bn;
+ memcpy(t, string, sn);
+ t += sn;
+ memcpy(t, e, en);
+ t += en;
+
+ /* Replace i18N string array */
+ entry->length -= strlen(be) + 1;
+ entry->length += sn;
+
+ if (ENTRY_IN_REGION(entry)) {
+ entry->info.offset = 0;
+ } else
+ entry->data = _free(entry->data);
+ entry->data = buf;
+ }
+
+ return 0;
+}
+
+int headerMod(Header h, rpmtd td)
+{
+ indexEntry entry;
+ rpm_data_t oldData;
+ rpm_data_t data;
+ int length;
+
+ /* First find the tag */
+ entry = findEntry(h, td->tag, td->type);
+ if (!entry)
+ return 0;
+
+ length = 0;
+ data = grabData(td->type, td->data, td->count, &length);
+ if (data == NULL || length <= 0)
+ return 0;
+
+ /* make sure entry points to the first occurence of this tag */
+ while (entry > h->index && (entry - 1)->info.tag == td->tag)
+ entry--;
+
+ /* free after we've grabbed the new data in case the two are intertwined;
+ that's a bad idea but at least we won't break */
+ oldData = entry->data;
+
+ entry->info.count = td->count;
+ entry->info.type = td->type;
+ entry->data = data;
+ entry->length = length;
+
+ if (ENTRY_IN_REGION(entry)) {
+ entry->info.offset = 0;
+ } else
+ oldData = _free(oldData);
+
+ return 1;
+}
+
+/**
+ * Header tag iterator data structure.
+ */
+struct headerIterator_s {
+ Header h; /*!< Header being iterated. */
+ int next_index; /*!< Next tag index. */
+};
+
+HeaderIterator headerFreeIterator(HeaderIterator hi)
+{
+ if (hi != NULL) {
+ hi->h = headerFree(hi->h);
+ hi = _free(hi);
+ }
+ return hi;
+}
+
+HeaderIterator headerInitIterator(Header h)
+{
+ HeaderIterator hi = xmalloc(sizeof(*hi));
+
+ headerSort(h);
+
+ hi->h = headerLink(h);
+ hi->next_index = 0;
+ return hi;
+}
+
+static indexEntry nextIndex(HeaderIterator hi)
+{
+ Header h = hi->h;
+ int slot;
+ indexEntry entry = NULL;
+
+ for (slot = hi->next_index; slot < h->indexUsed; slot++) {
+ entry = h->index + slot;
+ if (!ENTRY_IS_REGION(entry))
+ break;
+ }
+ hi->next_index = slot;
+ if (entry == NULL || slot >= h->indexUsed)
+ return NULL;
+
+ hi->next_index++;
+ return entry;
+}
+
+rpmTagVal headerNextTag(HeaderIterator hi)
+{
+ indexEntry entry = nextIndex(hi);
+ return entry ? entry->info.tag : RPMTAG_NOT_FOUND;
+}
+
+int headerNext(HeaderIterator hi, rpmtd td)
+{
+ indexEntry entry = nextIndex(hi);
+ int rc = 0;
+
+ rpmtdReset(td);
+ if (entry) {
+ td->tag = entry->info.tag;
+ rc = copyTdEntry(entry, td, HEADERGET_DEFAULT);
+ }
+ return ((rc == 1) ? 1 : 0);
+}
+
+unsigned int headerGetInstance(Header h)
+{
+ return h ? h->instance : 0;
+}
+
+void headerSetInstance(Header h, unsigned int instance)
+{
+ h->instance = instance;
+}
+
diff --git a/lib/header.h b/lib/header.h
new file mode 100644
index 0000000..a9160fc
--- /dev/null
+++ b/lib/header.h
@@ -0,0 +1,446 @@
+#ifndef H_HEADER
+#define H_HEADER
+
+/** \ingroup header
+ * \file lib/header.h
+ *
+ * An rpm header carries all information about a package. A header is
+ * a collection of data elements called tags. Each tag has a data type,
+ * and includes 1 or more values.
+ *
+ */
+
+/* RPM - Copyright (C) 1995-2001 Red Hat Software */
+
+#include <rpm/rpmio.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmtd.h>
+#include <rpm/rpmutil.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup header
+ * Header magic value
+ */
+extern const unsigned char rpm_header_magic[8];
+
+/** \ingroup header
+ * Include calculation for 8 bytes of (magic, 0)?
+ */
+enum hMagic {
+ HEADER_MAGIC_NO = 0,
+ HEADER_MAGIC_YES = 1
+};
+
+/** \ingroup header
+ * Create new (empty) header instance.
+ * @return header
+ */
+Header headerNew(void);
+
+/** \ingroup header
+ * Dereference a header instance.
+ * @param h header
+ * @return NULL always
+ */
+Header headerFree( Header h);
+
+/** \ingroup header
+ * Reference a header instance.
+ * @param h header
+ * @return new header reference
+ */
+Header headerLink(Header h);
+
+/** \ingroup header
+ * Sort tags in header.
+ * @param h header
+ */
+void headerSort(Header h);
+
+/** \ingroup header
+ * Restore tags in header to original ordering.
+ * @param h header
+ */
+void headerUnsort(Header h);
+
+/** \ingroup header
+ * Return size of on-disk header representation in bytes.
+ * @param h header
+ * @param magicp include size of 8 bytes for (magic, 0)?
+ * @return size of on-disk header
+ */
+unsigned int headerSizeof(Header h, int magicp);
+
+/** \ingroup header
+ * Perform simple sanity and range checks on header tag(s).
+ * @param il no. of tags in header
+ * @param dl no. of bytes in header data.
+ * @param pev 1st element in tag array, big-endian
+ * @param iv failing (or last) tag element, host-endian
+ * @param negate negative offset expected?
+ * @return -1 on success, otherwise failing tag element index
+ */
+int headerVerifyInfo(int il, int dl, const void * pev, void * iv, int negate);
+
+/** \ingroup header
+ * Convert header to on-disk representation.
+ * @param h header (with pointers)
+ * @return on-disk header blob (i.e. with offsets)
+ */
+void * headerUnload(Header h);
+
+/** \ingroup header
+ * Convert header to on-disk representation, and then reload.
+ * This is used to insure that all header data is in one chunk.
+ * @param h header (with pointers)
+ * @param tag region tag
+ * @return on-disk header (with offsets)
+ */
+Header headerReload(Header h, rpmTagVal tag);
+
+/** \ingroup header
+ * Duplicate a header.
+ * @param h header
+ * @return new header instance
+ */
+Header headerCopy(Header h);
+
+/** \ingroup header
+ * Convert header to in-memory representation.
+ * @param uh on-disk header blob (i.e. with offsets)
+ * @return header
+ */
+Header headerLoad(void * uh);
+
+/** \ingroup header
+ * Make a copy and convert header to in-memory representation.
+ * @param uh on-disk header blob (i.e. with offsets)
+ * @return header
+ */
+Header headerCopyLoad(const void * uh);
+
+/** \ingroup header
+ * Read (and load) header from file handle.
+ * @param fd file handle
+ * @param magicp read (and verify) 8 bytes of (magic, 0)?
+ * @return header (or NULL on error)
+ */
+Header headerRead(FD_t fd, int magicp);
+
+/** \ingroup header
+ * Write (with unload) header to file handle.
+ * @param fd file handle
+ * @param h header
+ * @param magicp prefix write with 8 bytes of (magic, 0)?
+ * @return 0 on success, 1 on error
+ */
+int headerWrite(FD_t fd, Header h, int magicp);
+
+/** \ingroup header
+ * Check if tag is in header.
+ * @param h header
+ * @param tag tag
+ * @return 1 on success, 0 on failure
+ */
+int headerIsEntry(Header h, rpmTagVal tag);
+
+/** \ingroup header
+ * Modifier flags for headerGet() operation.
+ * For consistent behavior you'll probably want to use ALLOC to ensure
+ * the caller owns the data, but MINMEM is useful for avoiding extra
+ * copy of data when you are sure the header wont go away.
+ * Most of the time you'll probably want EXT too, but note that extensions
+ * tags don't generally honor the other flags, MINMEM, RAW, ALLOC and ARGV
+ * are only relevant for non-extension data.
+ */
+enum headerGetFlags_e {
+ HEADERGET_DEFAULT = 0, /* legacy headerGetEntry() behavior */
+ HEADERGET_MINMEM = (1 << 0), /* pointers can refer to header memory */
+ HEADERGET_EXT = (1 << 1), /* lookup extension types too */
+ HEADERGET_RAW = (1 << 2), /* return raw contents (no i18n lookups) */
+ HEADERGET_ALLOC = (1 << 3), /* always allocate memory for all data */
+ HEADERGET_ARGV = (1 << 4), /* return string arrays NULL-terminated */
+};
+
+typedef rpmFlags headerGetFlags;
+
+/** \ingroup header
+ * Retrieve tag value.
+ * @param h header
+ * @param tag tag
+ * @retval td tag data container
+ * @param flags retrieval modifier flags
+ * @return 1 on success, 0 on failure
+ */
+int headerGet(Header h, rpmTagVal tag, rpmtd td, headerGetFlags flags);
+
+
+enum headerPutFlags_e {
+ HEADERPUT_DEFAULT = 0,
+ HEADERPUT_APPEND = (1 << 0),
+};
+
+typedef rpmFlags headerPutFlags;
+
+/** \ingroup header
+ * Add or append tag to header.
+ *
+ * @param h header
+ * @param td tag data container
+ * @param flags flags to control operation
+ * @return 1 on success, 0 on failure
+ */
+int headerPut(Header h, rpmtd td, headerPutFlags flags);
+
+/** \ingroup header
+ * @{
+ * Type-safe methods for inserting tag data to header.
+ * Tag data type is validated to match the function type, ie things like
+ * headerPutUint32(h, RPMTAG_NAME, ...) will return failure. For non-array
+ * types size must equal 1, and data is checked to be non-NULL. For array
+ * types, add-or-append mode is always used.
+ *
+ * headerPutString() can be used on both RPM_STRING_TYPE and
+ * RPM_STRING_ARRAY_TYPE (to add a single string into the array) tags,
+ * for others the type must match exactly.
+ *
+ * These are intended to "do the right thing" in the common case, if you
+ * need more fine grained control use headerPut() & friends instead.
+ * @todo Make doxygen group these meaningfully.
+ *
+ * @param h header
+ * @param tag tag to insert
+ * @param val pointer to value(s)
+ * @param size number of items in array (1 or larger)
+ * @return 1 on success, 0 on failure
+ *
+ */
+int headerPutString(Header h, rpmTagVal tag, const char *val);
+int headerPutStringArray(Header h, rpmTagVal tag, const char **val, rpm_count_t size);
+int headerPutBin(Header h, rpmTagVal tag, const uint8_t *val, rpm_count_t size);
+int headerPutChar(Header h, rpmTagVal tag, const char *val, rpm_count_t size);
+int headerPutUint8(Header h, rpmTagVal tag, const uint8_t *val, rpm_count_t size);
+int headerPutUint16(Header h, rpmTagVal tag, const uint16_t *val, rpm_count_t size);
+int headerPutUint32(Header h, rpmTagVal tag, const uint32_t *val, rpm_count_t size);
+int headerPutUint64(Header h, rpmTagVal tag, const uint64_t *val, rpm_count_t size);
+/** @} */
+
+/** \ingroup header
+ * Add locale specific tag to header.
+ * A NULL lang is interpreted as the C locale. Here are the rules:
+ * \verbatim
+ * - If the tag isn't in the header, it's added with the passed string
+ * as new value.
+ * - If the tag occurs multiple times in entry, which tag is affected
+ * by the operation is undefined.
+ * - If the tag is in the header w/ this language, the entry is
+ * *replaced* (like headerMod()).
+ * \endverbatim
+ * This function is intended to just "do the right thing". If you need
+ * more fine grained control use headerPut() and headerMod().
+ *
+ * @param h header
+ * @param tag tag
+ * @param string tag value
+ * @param lang locale
+ * @return 1 on success, 0 on failure
+ */
+int headerAddI18NString(Header h, rpmTagVal tag, const char * string,
+ const char * lang);
+
+/** \ingroup header
+ * Modify tag in header.
+ * If there are multiple entries with this tag, the first one gets replaced.
+ * @param h header
+ * @param td tag data container
+ * @return 1 on success, 0 on failure
+ */
+int headerMod(Header h, rpmtd td);
+
+/** \ingroup header
+ * Delete tag in header.
+ * Removes all entries of type tag from the header, returns 1 if none were
+ * found.
+ *
+ * @param h header
+ * @param tag tag
+ * @return 0 on success, 1 on failure (INCONSISTENT)
+ */
+int headerDel(Header h, rpmTagVal tag);
+
+/** \ingroup header
+ * Return formatted output string from header tags.
+ * The returned string must be free()d.
+ *
+ * @param h header
+ * @param fmt format to use
+ * @retval errmsg error message (if any)
+ * @return formatted output string (malloc'ed)
+ */
+char * headerFormat(Header h, const char * fmt, errmsg_t * errmsg);
+
+/** \ingroup header
+ * Duplicate tag values from one header into another.
+ * @param headerFrom source header
+ * @param headerTo destination header
+ * @param tagstocopy array of tags that are copied
+ */
+void headerCopyTags(Header headerFrom, Header headerTo,
+ const rpmTagVal * tagstocopy);
+
+/** \ingroup header
+ * Destroy header tag iterator.
+ * @param hi header tag iterator
+ * @return NULL always
+ */
+HeaderIterator headerFreeIterator(HeaderIterator hi);
+
+/** \ingroup header
+ * Create header tag iterator.
+ * @param h header
+ * @return header tag iterator
+ */
+HeaderIterator headerInitIterator(Header h);
+
+/** \ingroup header
+ * Return next tag contents from header.
+ * @param hi header tag iterator
+ * @retval td tag data container
+ * @return 1 on success, 0 on failure
+ */
+int headerNext(HeaderIterator hi, rpmtd td);
+
+/** \ingroup header
+ * Return next tag number from header.
+ * @param hi header tag iterator
+ * @return next tag, RPMTAG_NOT_FOUND to stop iteration
+ */
+rpmTagVal headerNextTag(HeaderIterator hi);
+
+/** \ingroup header
+ * Return name, version, release strings from header.
+ * @param h header
+ * @retval *np name pointer (or NULL)
+ * @retval *vp version pointer (or NULL)
+ * @retval *rp release pointer (or NULL)
+ * @return 0 always
+ */
+RPM_GNUC_DEPRECATED
+int headerNVR(Header h,
+ const char ** np,
+ const char ** vp,
+ const char ** rp);
+
+/** \ingroup header
+ * Return name, epoch, version, release, arch strings from header.
+ * @param h header
+ * @retval *np name pointer (or NULL)
+ * @retval *ep epoch pointer (or NULL)
+ * @retval *vp version pointer (or NULL)
+ * @retval *rp release pointer (or NULL)
+ * @retval *ap arch pointer (or NULL)
+ * @return 0 always
+ */
+RPM_GNUC_DEPRECATED
+int headerNEVRA(Header h,
+ const char ** np,
+ uint32_t ** ep,
+ const char ** vp,
+ const char ** rp,
+ const char ** ap);
+
+/** \ingroup header
+ * Return (malloc'd) header name-version-release string.
+ * @param h header
+ * @retval np name tag value
+ * @return name-version-release string
+ */
+RPM_GNUC_DEPRECATED
+char * headerGetNEVR(Header h, const char ** np );
+
+/** \ingroup header
+ * Return (malloc'd) header name-version-release.arch string.
+ * @param h header
+ * @retval np name tag value
+ * @return name-version-release string
+ */
+RPM_GNUC_DEPRECATED
+char * headerGetNEVRA(Header h, const char ** np );
+
+/* \ingroup header
+ * Return (malloc'd) header (epoch:)version-release string.
+ * @param h header
+ * @retval np name tag value (or NULL)
+ * @return (epoch:)version-release string
+ */
+RPM_GNUC_DEPRECATED
+char * headerGetEVR(Header h, const char **np);
+
+/** \ingroup header
+ * Return any non-array tag from header, converted to string
+ * @param h header
+ * @param tag tag to retrieve
+ * @return string pointer (malloced) or NULL on failure
+ */
+char * headerGetAsString(Header h, rpmTagVal tag);
+
+/** \ingroup header
+ * Return a simple string tag from header
+ * @param h header
+ * @param tag tag to retrieve
+ * @return string pointer (to header memory) or NULL on failure
+ */
+const char * headerGetString(Header h, rpmTagVal tag);
+
+/* \ingroup header
+ * Return a simple number tag (or extension) from header
+ * @param h header
+ * @param tag tag to retrieve
+ * @return numeric tag value or 0 on failure
+ */
+uint64_t headerGetNumber(Header h, rpmTagVal tag);
+
+/** \ingroup header
+ * Return header color.
+ * @param h header
+ * @return header color
+ */
+RPM_GNUC_DEPRECATED
+rpm_color_t headerGetColor(Header h);
+
+/** \ingroup header
+ * Check if header is a source or binary package header
+ * @param h header
+ * @return 0 == binary, 1 == source
+ */
+int headerIsSource(Header h);
+
+/** \ingroup header
+ * Return header instance, ie is the header from rpmdb.
+ * @param h header
+ * @return rpmdb record number or 0
+ */
+unsigned int headerGetInstance(Header h);
+
+typedef enum headerConvOps_e {
+ HEADERCONV_EXPANDFILELIST = 0,
+ HEADERCONV_COMPRESSFILELIST = 1,
+ HEADERCONV_RETROFIT_V3 = 2,
+} headerConvOps;
+
+/** \ingroup header
+ * Convert header to/from (legacy) data presentation
+ * @param h header
+ * @param op one of headerConvOps operations
+ * @return 1 on success, 0 on failure
+ */
+int headerConvert(Header h, int op);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_HEADER */
diff --git a/lib/header_internal.h b/lib/header_internal.h
new file mode 100644
index 0000000..82b96e3
--- /dev/null
+++ b/lib/header_internal.h
@@ -0,0 +1,78 @@
+#ifndef H_HEADER_INTERNAL
+#define H_HEADER_INTERNAL
+
+/** \ingroup header
+ * \file lib/header_internal.h
+ */
+
+#include <rpm/header.h>
+
+/** \ingroup header
+ * Description of tag data.
+ */
+typedef struct entryInfo_s * entryInfo;
+struct entryInfo_s {
+ rpm_tag_t tag; /*!< Tag identifier. */
+ rpm_tagtype_t type; /*!< Tag data type. */
+ int32_t offset; /*!< Offset into data segment (ondisk only). */
+ rpm_count_t count; /*!< Number of tag elements. */
+};
+
+#define REGION_TAG_TYPE RPM_BIN_TYPE
+#define REGION_TAG_COUNT sizeof(struct entryInfo_s)
+
+/** \ingroup header
+ * A single tag from a Header.
+ */
+typedef struct indexEntry_s * indexEntry;
+struct indexEntry_s {
+ struct entryInfo_s info; /*!< Description of tag data. */
+ rpm_data_t data; /*!< Location of tag data. */
+ int length; /*!< No. bytes of data. */
+ int rdlen; /*!< No. bytes of data in region. */
+};
+
+/**
+ * Sanity check on no. of tags.
+ * This check imposes a limit of 65K tags, more than enough.
+ */
+#define hdrchkTags(_ntags) ((_ntags) & 0xffff0000)
+
+/**
+ * Sanity check on type values.
+ */
+#define hdrchkType(_type) ((_type) < RPM_MIN_TYPE || (_type) > RPM_MAX_TYPE)
+
+/**
+ * Sanity check on data size and/or offset and/or count.
+ * This check imposes a limit of 16 MB, more than enough.
+ */
+#define hdrchkData(_nbytes) ((_nbytes) & 0xff000000)
+
+/**
+ * Sanity check on data alignment for data type.
+ */
+#define hdrchkAlign(_type, _off) ((_off) & (typeAlign[_type]-1))
+
+/**
+ * Sanity check on range of data offset.
+ */
+#define hdrchkRange(_dl, _off) ((_off) < 0 || (_off) > (_dl))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup header
+ * Set header instance (rpmdb record number)
+ * @param h header
+ * @param instance record number
+ */
+RPM_GNUC_INTERNAL
+void headerSetInstance(Header h, unsigned int instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_HEADER_INTERNAL */
diff --git a/lib/headerfmt.c b/lib/headerfmt.c
new file mode 100644
index 0000000..49c7047
--- /dev/null
+++ b/lib/headerfmt.c
@@ -0,0 +1,868 @@
+/** \ingroup header
+ * \file lib/headerfmt.c
+ */
+
+#include "system.h"
+
+#include <rpm/header.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmpgp.h>
+#include "lib/misc.h" /* format function protos */
+
+#include "debug.h"
+
+#define PARSER_BEGIN 0
+#define PARSER_IN_ARRAY 1
+#define PARSER_IN_EXPR 2
+
+/** \ingroup header
+ */
+typedef struct sprintfTag_s * sprintfTag;
+struct sprintfTag_s {
+ headerTagFormatFunction fmt;
+ rpmTagVal tag;
+ int justOne;
+ char * format;
+ char * type;
+};
+
+typedef enum {
+ PTOK_NONE = 0,
+ PTOK_TAG,
+ PTOK_ARRAY,
+ PTOK_STRING,
+ PTOK_COND
+} ptokType;
+
+/** \ingroup header
+ */
+typedef struct sprintfToken_s * sprintfToken;
+struct sprintfToken_s {
+ ptokType type;
+ union {
+ struct sprintfTag_s tag; /*!< PTOK_TAG */
+ struct {
+ sprintfToken format;
+ int i;
+ int numTokens;
+ } array; /*!< PTOK_ARRAY */
+ struct {
+ char * string;
+ int len;
+ } string; /*!< PTOK_STRING */
+ struct {
+ sprintfToken ifFormat;
+ int numIfTokens;
+ sprintfToken elseFormat;
+ int numElseTokens;
+ struct sprintfTag_s tag;
+ } cond; /*!< PTOK_COND */
+ } u;
+};
+
+#define HASHTYPE tagCache
+#define HTKEYTYPE rpmTagVal
+#define HTDATATYPE rpmtd
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+
+/**
+ */
+typedef struct headerSprintfArgs_s {
+ Header h;
+ char * fmt;
+ const char * errmsg;
+ tagCache cache;
+ sprintfToken format;
+ HeaderIterator hi;
+ char * val;
+ size_t vallen;
+ size_t alloced;
+ int numTokens;
+ int i;
+ headerGetFlags hgflags;
+} * headerSprintfArgs;
+
+
+static char escapedChar(const char ch)
+{
+ switch (ch) {
+ case 'a': return '\a';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ default: return ch;
+ }
+}
+
+/**
+ * Destroy headerSprintf format array.
+ * @param format sprintf format array
+ * @param num number of elements
+ * @return NULL always
+ */
+static sprintfToken
+freeFormat( sprintfToken format, int num)
+{
+ int i;
+
+ if (format == NULL) return NULL;
+
+ for (i = 0; i < num; i++) {
+ switch (format[i].type) {
+ case PTOK_ARRAY:
+ format[i].u.array.format =
+ freeFormat(format[i].u.array.format,
+ format[i].u.array.numTokens);
+ break;
+ case PTOK_COND:
+ format[i].u.cond.ifFormat =
+ freeFormat(format[i].u.cond.ifFormat,
+ format[i].u.cond.numIfTokens);
+ format[i].u.cond.elseFormat =
+ freeFormat(format[i].u.cond.elseFormat,
+ format[i].u.cond.numElseTokens);
+ break;
+ case PTOK_NONE:
+ case PTOK_TAG:
+ case PTOK_STRING:
+ default:
+ break;
+ }
+ }
+ format = _free(format);
+ return NULL;
+}
+
+/**
+ * Initialize an hsa iteration.
+ * @param hsa headerSprintf args
+ */
+static void hsaInit(headerSprintfArgs hsa)
+{
+ sprintfTag tag =
+ (hsa->format->type == PTOK_TAG
+ ? &hsa->format->u.tag :
+ (hsa->format->type == PTOK_ARRAY
+ ? &hsa->format->u.array.format->u.tag :
+ NULL));
+
+ hsa->i = 0;
+ if (tag != NULL && tag->tag == -2)
+ hsa->hi = headerInitIterator(hsa->h);
+ /* Normally with bells and whistles enabled, but raw dump on iteration. */
+ hsa->hgflags = (hsa->hi == NULL) ? HEADERGET_EXT : HEADERGET_RAW;
+}
+
+/**
+ * Return next hsa iteration item.
+ * @param hsa headerSprintf args
+ * @return next sprintfToken (or NULL)
+ */
+static sprintfToken hsaNext(headerSprintfArgs hsa)
+{
+ sprintfToken fmt = NULL;
+ sprintfTag tag =
+ (hsa->format->type == PTOK_TAG
+ ? &hsa->format->u.tag :
+ (hsa->format->type == PTOK_ARRAY
+ ? &hsa->format->u.array.format->u.tag :
+ NULL));
+
+ if (hsa->i >= 0 && hsa->i < hsa->numTokens) {
+ fmt = hsa->format + hsa->i;
+ if (hsa->hi == NULL) {
+ hsa->i++;
+ } else {
+ tag->tag = headerNextTag(hsa->hi);
+ if (tag->tag == RPMTAG_NOT_FOUND)
+ fmt = NULL;
+ }
+ }
+
+ return fmt;
+}
+
+/**
+ * Finish an hsa iteration.
+ * @param hsa headerSprintf args
+ */
+static void hsaFini(headerSprintfArgs hsa)
+{
+ hsa->hi = headerFreeIterator(hsa->hi);
+ hsa->i = 0;
+}
+
+/**
+ * Reserve sufficient buffer space for next output value.
+ * @param hsa headerSprintf args
+ * @param need no. of bytes to reserve
+ * @return pointer to reserved space
+ */
+static char * hsaReserve(headerSprintfArgs hsa, size_t need)
+{
+ if ((hsa->vallen + need) >= hsa->alloced) {
+ if (hsa->alloced <= need)
+ hsa->alloced += need;
+ hsa->alloced <<= 1;
+ hsa->val = xrealloc(hsa->val, hsa->alloced+1);
+ }
+ return hsa->val + hsa->vallen;
+}
+
+/**
+ * Search tags for a name.
+ * @param hsa headerSprintf args
+ * @param token parsed fields
+ * @param name name to find
+ * @return 0 on success, 1 on not found
+ */
+static int findTag(headerSprintfArgs hsa, sprintfToken token, const char * name)
+{
+ const char *tagname = name;
+ sprintfTag stag = (token->type == PTOK_COND
+ ? &token->u.cond.tag : &token->u.tag);
+
+ stag->fmt = NULL;
+ stag->tag = RPMTAG_NOT_FOUND;
+
+ if (rstreq(tagname, "*")) {
+ stag->tag = -2;
+ goto bingo;
+ }
+
+ if (rstreqn("RPMTAG_", tagname, sizeof("RPMTAG_")-1)) {
+ tagname += sizeof("RPMTAG");
+ }
+
+ /* Search tag names. */
+ stag->tag = rpmTagGetValue(tagname);
+ if (stag->tag != RPMTAG_NOT_FOUND)
+ goto bingo;
+
+ return 1;
+
+bingo:
+ /* Search extensions for specific format. */
+ if (stag->type != NULL)
+ stag->fmt = rpmHeaderFormatFuncByName(stag->type);
+
+ return stag->fmt ? 0 : 1;
+}
+
+/* forward ref */
+/**
+ * Parse an expression.
+ * @param hsa headerSprintf args
+ * @param token token
+ * @param str string
+ * @param[out] *endPtr
+ * @return 0 on success
+ */
+static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
+ char * str,char ** endPtr);
+
+/**
+ * Parse a headerSprintf term.
+ * @param hsa headerSprintf args
+ * @param str
+ * @retval *formatPtr
+ * @retval *numTokensPtr
+ * @retval *endPtr
+ * @param state
+ * @return 0 on success
+ */
+static int parseFormat(headerSprintfArgs hsa, char * str,
+ sprintfToken * formatPtr,int * numTokensPtr,
+ char ** endPtr, int state)
+{
+ char * chptr, * start, * next, * dst;
+ sprintfToken format;
+ sprintfToken token;
+ int numTokens;
+ int done = 0;
+
+ /* upper limit on number of individual formats */
+ numTokens = 0;
+ if (str != NULL)
+ for (chptr = str; *chptr != '\0'; chptr++)
+ if (*chptr == '%' || *chptr == '[') numTokens++;
+ numTokens = numTokens * 2 + 1;
+
+ format = xcalloc(numTokens, sizeof(*format));
+ if (endPtr) *endPtr = NULL;
+
+ dst = start = str;
+ numTokens = 0;
+ token = NULL;
+ if (start != NULL)
+ while (*start != '\0') {
+ switch (*start) {
+ case '%':
+ /* handle %% */
+ if (*(start + 1) == '%') {
+ if (token == NULL || token->type != PTOK_STRING) {
+ token = format + numTokens++;
+ token->type = PTOK_STRING;
+ dst = token->u.string.string = start;
+ }
+ start++;
+ *dst++ = *start++;
+ break;
+ }
+
+ token = format + numTokens++;
+ *dst++ = '\0';
+ start++;
+
+ if (*start == '|') {
+ char * newEnd;
+
+ start++;
+ if (parseExpression(hsa, token, start, &newEnd)) {
+ goto errxit;
+ }
+ start = newEnd;
+ break;
+ }
+
+ token->u.tag.format = start;
+ token->u.tag.justOne = 0;
+
+ chptr = start;
+ while (*chptr && *chptr != '{' && *chptr != '%') chptr++;
+ if (!*chptr || *chptr == '%') {
+ hsa->errmsg = _("missing { after %");
+ goto errxit;
+ }
+
+ *chptr++ = '\0';
+
+ while (start < chptr) {
+ start++;
+ }
+
+ if (*start == '=') {
+ token->u.tag.justOne = 1;
+ start++;
+ } else if (*start == '#') {
+ token->u.tag.justOne = 1;
+ token->u.tag.type = "arraysize";
+ start++;
+ }
+
+ dst = next = start;
+ while (*next && *next != '}') next++;
+ if (!*next) {
+ hsa->errmsg = _("missing } after %{");
+ goto errxit;
+ }
+ *next++ = '\0';
+
+ chptr = start;
+ while (*chptr && *chptr != ':') chptr++;
+
+ if (*chptr != '\0') {
+ *chptr++ = '\0';
+ if (!*chptr) {
+ hsa->errmsg = _("empty tag format");
+ goto errxit;
+ }
+ token->u.tag.type = chptr;
+ }
+ /* default to string conversion if no formats found by now */
+ if (!token->u.tag.type) {
+ token->u.tag.type = "string";
+ }
+
+ if (!*start) {
+ hsa->errmsg = _("empty tag name");
+ goto errxit;
+ }
+
+ token->type = PTOK_TAG;
+
+ if (findTag(hsa, token, start)) {
+ hsa->errmsg = _("unknown tag");
+ goto errxit;
+ }
+
+ start = next;
+ break;
+
+ case '[':
+ *dst++ = '\0';
+ *start++ = '\0';
+ token = format + numTokens++;
+
+ if (parseFormat(hsa, start,
+ &token->u.array.format,
+ &token->u.array.numTokens,
+ &start, PARSER_IN_ARRAY)) {
+ goto errxit;
+ }
+
+ if (!start) {
+ hsa->errmsg = _("] expected at end of array");
+ goto errxit;
+ }
+
+ dst = start;
+
+ token->type = PTOK_ARRAY;
+
+ break;
+
+ case ']':
+ if (state != PARSER_IN_ARRAY) {
+ hsa->errmsg = _("unexpected ]");
+ goto errxit;
+ }
+ *start++ = '\0';
+ if (endPtr) *endPtr = start;
+ done = 1;
+ break;
+
+ case '}':
+ if (state != PARSER_IN_EXPR) {
+ hsa->errmsg = _("unexpected }");
+ goto errxit;
+ }
+ *start++ = '\0';
+ if (endPtr) *endPtr = start;
+ done = 1;
+ break;
+
+ default:
+ if (token == NULL || token->type != PTOK_STRING) {
+ token = format + numTokens++;
+ token->type = PTOK_STRING;
+ dst = token->u.string.string = start;
+ }
+
+ if (*start == '\\') {
+ start++;
+ *dst++ = escapedChar(*start++);
+ } else {
+ *dst++ = *start++;
+ }
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ if (dst != NULL)
+ *dst = '\0';
+
+ for (int i = 0; i < numTokens; i++) {
+ token = format + i;
+ if (token->type == PTOK_STRING)
+ token->u.string.len = strlen(token->u.string.string);
+ }
+
+ *numTokensPtr = numTokens;
+ *formatPtr = format;
+ return 0;
+
+errxit:
+ freeFormat(format, numTokens);
+ return 1;
+}
+
+static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
+ char * str, char ** endPtr)
+{
+ char * chptr;
+ char * end;
+
+ hsa->errmsg = NULL;
+ chptr = str;
+ while (*chptr && *chptr != '?') chptr++;
+
+ if (*chptr != '?') {
+ hsa->errmsg = _("? expected in expression");
+ return 1;
+ }
+
+ *chptr++ = '\0';;
+
+ if (*chptr != '{') {
+ hsa->errmsg = _("{ expected after ? in expression");
+ return 1;
+ }
+
+ chptr++;
+
+ if (parseFormat(hsa, chptr, &token->u.cond.ifFormat,
+ &token->u.cond.numIfTokens, &end, PARSER_IN_EXPR))
+ return 1;
+
+ /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{%}:{NAME}|\n'"*/
+ if (!(end && *end)) {
+ hsa->errmsg = _("} expected in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr = end;
+ if (*chptr != ':' && *chptr != '|') {
+ hsa->errmsg = _(": expected following ? subexpression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ if (*chptr == '|') {
+ if (parseFormat(hsa, NULL, &token->u.cond.elseFormat,
+ &token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
+ {
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+ } else {
+ chptr++;
+
+ if (*chptr != '{') {
+ hsa->errmsg = _("{ expected after : in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr++;
+
+ if (parseFormat(hsa, chptr, &token->u.cond.elseFormat,
+ &token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
+ return 1;
+
+ /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{a}:{%}|{NAME}\n'" */
+ if (!(end && *end)) {
+ hsa->errmsg = _("} expected in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr = end;
+ if (*chptr != '|') {
+ hsa->errmsg = _("| expected at end of expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ token->u.cond.elseFormat =
+ freeFormat(token->u.cond.elseFormat, token->u.cond.numElseTokens);
+ return 1;
+ }
+ }
+
+ chptr++;
+
+ *endPtr = chptr;
+
+ token->type = PTOK_COND;
+
+ (void) findTag(hsa, token, str);
+
+ return 0;
+}
+
+static rpmtd getCached(tagCache cache, rpmTagVal tag)
+{
+ rpmtd *res = NULL;
+ return tagCacheGetEntry(cache, tag, &res, NULL, NULL) ? res[0] : NULL;
+}
+
+/**
+ * Do headerGet() just once for given tag, cache results.
+ * @param hsa headerSprintf args
+ * @param tag
+ * @retval *typeptr
+ * @retval *data
+ * @retval *countptr
+ * @return 1 on success, 0 on failure
+ */
+static rpmtd getData(headerSprintfArgs hsa, rpmTagVal tag)
+{
+ rpmtd td = NULL;
+
+ if (!(td = getCached(hsa->cache, tag))) {
+ td = rpmtdNew();
+ if (!headerGet(hsa->h, tag, td, hsa->hgflags)) {
+ rpmtdFree(td);
+ return NULL;
+ }
+ tagCacheAddEntry(hsa->cache, tag, td);
+ }
+
+ return td;
+}
+
+/**
+ * formatValue
+ * @param hsa headerSprintf args
+ * @param tag
+ * @param element
+ * @return end of formatted string (NULL on error)
+ */
+static char * formatValue(headerSprintfArgs hsa, sprintfTag tag, int element)
+{
+ char * val = NULL;
+ size_t need = 0;
+ char * t, * te;
+ char buf[20];
+ rpmtd td;
+
+ memset(buf, 0, sizeof(buf));
+ if ((td = getData(hsa, tag->tag))) {
+ td->ix = element; /* Ick, use iterators instead */
+ stpcpy(stpcpy(buf, "%"), tag->format);
+ val = tag->fmt(td, buf);
+ } else {
+ stpcpy(buf, "%s");
+ val = xstrdup("(none)");
+ }
+
+ need = strlen(val);
+
+ if (val && need > 0) {
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, val);
+ hsa->vallen += (te - t);
+ }
+ free(val);
+
+ return (hsa->val + hsa->vallen);
+}
+
+/**
+ * Format a single headerSprintf item.
+ * @param hsa headerSprintf args
+ * @param token
+ * @param element
+ * @return end of formatted string (NULL on error)
+ */
+static char * singleSprintf(headerSprintfArgs hsa, sprintfToken token,
+ int element)
+{
+ char * t, * te;
+ int i, j, found;
+ rpm_count_t count, numElements;
+ sprintfToken spft;
+ int condNumFormats;
+ size_t need;
+
+ /* we assume the token and header have been validated already! */
+
+ switch (token->type) {
+ case PTOK_NONE:
+ break;
+
+ case PTOK_STRING:
+ need = token->u.string.len;
+ if (need == 0) break;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, token->u.string.string);
+ hsa->vallen += (te - t);
+ break;
+
+ case PTOK_TAG:
+ t = hsa->val + hsa->vallen;
+ te = formatValue(hsa, &token->u.tag,
+ (token->u.tag.justOne ? 0 : element));
+ if (te == NULL)
+ return NULL;
+ break;
+
+ case PTOK_COND:
+ if (getData(hsa, token->u.cond.tag.tag) ||
+ headerIsEntry(hsa->h, token->u.cond.tag.tag)) {
+ spft = token->u.cond.ifFormat;
+ condNumFormats = token->u.cond.numIfTokens;
+ } else {
+ spft = token->u.cond.elseFormat;
+ condNumFormats = token->u.cond.numElseTokens;
+ }
+
+ need = condNumFormats * 20;
+ if (spft == NULL || need == 0) break;
+
+ t = hsaReserve(hsa, need);
+ for (i = 0; i < condNumFormats; i++, spft++) {
+ te = singleSprintf(hsa, spft, element);
+ if (te == NULL)
+ return NULL;
+ }
+ break;
+
+ case PTOK_ARRAY:
+ numElements = 0;
+ found = 0;
+ spft = token->u.array.format;
+ for (i = 0; i < token->u.array.numTokens; i++, spft++)
+ {
+ rpmtd td = NULL;
+ if (spft->type != PTOK_TAG ||
+ spft->u.tag.justOne) continue;
+
+ if (!(td = getData(hsa, spft->u.tag.tag))) {
+ continue;
+ }
+
+ found = 1;
+ count = rpmtdCount(td);
+
+ if (numElements > 1 && count != numElements)
+ switch (td->type) {
+ default:
+ hsa->errmsg =
+ _("array iterator used with different sized arrays");
+ return NULL;
+ break;
+ case RPM_BIN_TYPE:
+ case RPM_STRING_TYPE:
+ break;
+ }
+ if (count > numElements)
+ numElements = count;
+ }
+
+ if (found) {
+ int isxml;
+
+ need = numElements * token->u.array.numTokens * 10;
+ if (need == 0) break;
+
+ spft = token->u.array.format;
+ isxml = (spft->type == PTOK_TAG && spft->u.tag.type != NULL &&
+ rstreq(spft->u.tag.type, "xml"));
+
+ if (isxml) {
+ const char * tagN = rpmTagGetName(spft->u.tag.tag);
+
+ need = sizeof(" <rpmTag name=\"\">\n") - 1;
+ if (tagN != NULL)
+ need += strlen(tagN);
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, " <rpmTag name=\"");
+ if (tagN != NULL)
+ te = stpcpy(te, tagN);
+ te = stpcpy(te, "\">\n");
+ hsa->vallen += (te - t);
+ }
+
+ t = hsaReserve(hsa, need);
+ for (j = 0; j < numElements; j++) {
+ spft = token->u.array.format;
+ for (i = 0; i < token->u.array.numTokens; i++, spft++) {
+ te = singleSprintf(hsa, spft, j);
+ if (te == NULL)
+ return NULL;
+ }
+ }
+
+ if (isxml) {
+ need = sizeof(" </rpmTag>\n") - 1;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, " </rpmTag>\n");
+ hsa->vallen += (te - t);
+ }
+
+ }
+ break;
+ }
+
+ return (hsa->val + hsa->vallen);
+}
+
+static int tagCmp(rpmTagVal a, rpmTagVal b)
+{
+ return (a != b);
+}
+
+static unsigned int tagId(rpmTagVal tag)
+{
+ return tag;
+}
+
+static rpmtd tagFree(rpmtd td)
+{
+ rpmtdFreeData(td);
+ rpmtdFree(td);
+ return NULL;
+}
+
+char * headerFormat(Header h, const char * fmt, errmsg_t * errmsg)
+{
+ struct headerSprintfArgs_s hsa;
+ sprintfToken nextfmt;
+ sprintfTag tag;
+ char * t, * te;
+ int isxml;
+ size_t need;
+
+ memset(&hsa, 0, sizeof(hsa));
+ hsa.h = headerLink(h);
+ hsa.fmt = xstrdup(fmt);
+ hsa.errmsg = NULL;
+
+ if (parseFormat(&hsa, hsa.fmt, &hsa.format, &hsa.numTokens, NULL, PARSER_BEGIN))
+ goto exit;
+
+ hsa.cache = tagCacheCreate(128, tagId, tagCmp, NULL, tagFree);
+ hsa.val = xstrdup("");
+
+ tag =
+ (hsa.format->type == PTOK_TAG
+ ? &hsa.format->u.tag :
+ (hsa.format->type == PTOK_ARRAY
+ ? &hsa.format->u.array.format->u.tag :
+ NULL));
+ isxml = (tag != NULL && tag->tag == -2 && tag->type != NULL && rstreq(tag->type, "xml"));
+
+ if (isxml) {
+ need = sizeof("<rpmHeader>\n") - 1;
+ t = hsaReserve(&hsa, need);
+ te = stpcpy(t, "<rpmHeader>\n");
+ hsa.vallen += (te - t);
+ }
+
+ hsaInit(&hsa);
+ while ((nextfmt = hsaNext(&hsa)) != NULL) {
+ te = singleSprintf(&hsa, nextfmt, 0);
+ if (te == NULL) {
+ hsa.val = _free(hsa.val);
+ break;
+ }
+ }
+ hsaFini(&hsa);
+
+ if (isxml) {
+ need = sizeof("</rpmHeader>\n") - 1;
+ t = hsaReserve(&hsa, need);
+ te = stpcpy(t, "</rpmHeader>\n");
+ hsa.vallen += (te - t);
+ }
+
+ if (hsa.val != NULL && hsa.vallen < hsa.alloced)
+ hsa.val = xrealloc(hsa.val, hsa.vallen+1);
+
+ hsa.cache = tagCacheFree(hsa.cache);
+ hsa.format = freeFormat(hsa.format, hsa.numTokens);
+
+exit:
+ if (errmsg)
+ *errmsg = hsa.errmsg;
+ hsa.h = headerFree(hsa.h);
+ hsa.fmt = _free(hsa.fmt);
+ return hsa.val;
+}
+
diff --git a/lib/headerutil.c b/lib/headerutil.c
new file mode 100644
index 0000000..d207fb8
--- /dev/null
+++ b/lib/headerutil.c
@@ -0,0 +1,245 @@
+/** \ingroup rpmdb
+ * \file lib/hdrNVR.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmtypes.h>
+#include <rpm/header.h>
+#include <rpm/rpmstring.h>
+
+#include "debug.h"
+
+static int NEVRA(Header h, const char **np,
+ uint32_t **ep, const char **vp, const char **rp,
+ const char **ap)
+{
+ if (np) *np = headerGetString(h, RPMTAG_NAME);
+ if (vp) *vp = headerGetString(h, RPMTAG_VERSION);
+ if (rp) *rp = headerGetString(h, RPMTAG_RELEASE);
+ if (ap) *ap = headerGetString(h, RPMTAG_ARCH);
+ if (ep) {
+ struct rpmtd_s td;
+ headerGet(h, RPMTAG_EPOCH, &td, HEADERGET_DEFAULT);
+ *ep = rpmtdGetUint32(&td);
+ }
+ return 0;
+}
+
+int headerNVR(Header h, const char **np, const char **vp, const char **rp)
+{
+ return NEVRA(h, np, NULL, vp, rp, NULL);
+}
+
+int headerNEVRA(Header h, const char **np,
+ uint32_t **ep, const char **vp, const char **rp,
+ const char **ap)
+{
+ return NEVRA(h, np, ep, vp, rp, ap);
+}
+
+static char *getNEVRA(Header h, rpmTag tag, const char **np)
+{
+ if (np) *np = headerGetString(h, RPMTAG_NAME);
+ return headerGetAsString(h, tag);
+}
+
+char * headerGetNEVR(Header h, const char ** np)
+{
+ return getNEVRA(h, RPMTAG_NEVR, np);
+}
+
+char * headerGetNEVRA(Header h, const char ** np)
+{
+ return getNEVRA(h, RPMTAG_NEVRA, np);
+}
+
+char * headerGetEVR(Header h, const char ** np)
+{
+ return getNEVRA(h, RPMTAG_EVR, np);
+}
+
+rpm_color_t headerGetColor(Header h)
+{
+ return headerGetNumber(h, RPMTAG_HEADERCOLOR);
+}
+
+int headerIsSource(Header h)
+{
+ return (!headerIsEntry(h, RPMTAG_SOURCERPM));
+}
+
+Header headerCopy(Header h)
+{
+ Header nh = headerNew();
+ HeaderIterator hi;
+ struct rpmtd_s td;
+
+ hi = headerInitIterator(h);
+ while (headerNext(hi, &td)) {
+ if (rpmtdCount(&td) > 0) {
+ (void) headerPut(nh, &td, HEADERPUT_DEFAULT);
+ }
+ rpmtdFreeData(&td);
+ }
+ hi = headerFreeIterator(hi);
+
+ return headerReload(nh, RPMTAG_HEADERIMAGE);
+}
+
+void headerCopyTags(Header headerFrom, Header headerTo,
+ const rpmTagVal * tagstocopy)
+{
+ const rpmTagVal * p;
+ struct rpmtd_s td;
+
+ if (headerFrom == headerTo)
+ return;
+
+ for (p = tagstocopy; *p != 0; p++) {
+ if (headerIsEntry(headerTo, *p))
+ continue;
+ if (!headerGet(headerFrom, *p, &td, (HEADERGET_MINMEM|HEADERGET_RAW)))
+ continue;
+ (void) headerPut(headerTo, &td, HEADERPUT_DEFAULT);
+ rpmtdFreeData(&td);
+ }
+}
+
+char * headerGetAsString(Header h, rpmTagVal tag)
+{
+ char *res = NULL;
+ struct rpmtd_s td;
+
+ if (headerGet(h, tag, &td, HEADERGET_EXT)) {
+ if (rpmtdCount(&td) == 1) {
+ res = rpmtdFormat(&td, RPMTD_FORMAT_STRING, NULL);
+ }
+ rpmtdFreeData(&td);
+ }
+ return res;
+}
+
+const char * headerGetString(Header h, rpmTagVal tag)
+{
+ const char *res = NULL;
+ struct rpmtd_s td;
+
+ if (headerGet(h, tag, &td, HEADERGET_MINMEM)) {
+ if (rpmtdCount(&td) == 1) {
+ res = rpmtdGetString(&td);
+ }
+ rpmtdFreeData(&td);
+ }
+ return res;
+}
+
+uint64_t headerGetNumber(Header h, rpmTagVal tag)
+{
+ uint64_t res = 0;
+ struct rpmtd_s td;
+
+ if (headerGet(h, tag, &td, HEADERGET_EXT)) {
+ if (rpmtdCount(&td) == 1) {
+ res = rpmtdGetNumber(&td);
+ }
+ rpmtdFreeData(&td);
+ }
+ return res;
+}
+
+/*
+ * Sanity check data types against tag table before putting. Assume
+ * append on all array-types.
+ */
+static int headerPutType(Header h, rpmTagVal tag, rpmTagType reqtype,
+ rpm_constdata_t data, rpm_count_t size)
+{
+ struct rpmtd_s td;
+ rpmTagType type = rpmTagGetTagType(tag);
+ rpmTagReturnType retype = rpmTagGetReturnType(tag);
+ headerPutFlags flags = HEADERPUT_APPEND;
+ int valid = 1;
+
+ /* Basic sanity checks: type must match and there must be data to put */
+ if (type != reqtype
+ || size < 1 || data == NULL || h == NULL) {
+ valid = 0;
+ }
+
+ /*
+ * Non-array types can't be appended to. Binary types use size
+ * for data length, for other non-array types size must be 1.
+ */
+ if (retype != RPM_ARRAY_RETURN_TYPE) {
+ flags = HEADERPUT_DEFAULT;
+ if (type != RPM_BIN_TYPE && size != 1) {
+ valid = 0;
+ }
+ }
+
+ if (valid) {
+ rpmtdReset(&td);
+ td.tag = tag;
+ td.type = type;
+ td.data = (void *) data;
+ td.count = size;
+
+ valid = headerPut(h, &td, flags);
+ }
+
+ return valid;
+}
+
+int headerPutString(Header h, rpmTagVal tag, const char *val)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ const void *sptr = NULL;
+
+ /* string arrays expect char **, arrange that */
+ if (type == RPM_STRING_ARRAY_TYPE || type == RPM_I18NSTRING_TYPE) {
+ sptr = &val;
+ } else if (type == RPM_STRING_TYPE) {
+ sptr = val;
+ } else {
+ return 0;
+ }
+
+ return headerPutType(h, tag, type, sptr, 1);
+}
+
+int headerPutStringArray(Header h, rpmTagVal tag, const char **array, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_STRING_ARRAY_TYPE, array, size);
+}
+
+int headerPutChar(Header h, rpmTagVal tag, const char *val, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_CHAR_TYPE, val, size);
+}
+
+int headerPutUint8(Header h, rpmTagVal tag, const uint8_t *val, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_INT8_TYPE, val, size);
+}
+
+int headerPutUint16(Header h, rpmTagVal tag, const uint16_t *val, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_INT16_TYPE, val, size);
+}
+
+int headerPutUint32(Header h, rpmTagVal tag, const uint32_t *val, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_INT32_TYPE, val, size);
+}
+
+int headerPutUint64(Header h, rpmTagVal tag, const uint64_t *val, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_INT64_TYPE, val, size);
+}
+
+int headerPutBin(Header h, rpmTagVal tag, const uint8_t *val, rpm_count_t size)
+{
+ return headerPutType(h, tag, RPM_BIN_TYPE, val, size);
+}
+
diff --git a/lib/legacy.c b/lib/legacy.c
new file mode 100644
index 0000000..df7911e
--- /dev/null
+++ b/lib/legacy.c
@@ -0,0 +1,379 @@
+/**
+ * \file lib/legacy.c
+ */
+
+#include "system.h"
+
+#include <rpm/header.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmds.h>
+
+#include "debug.h"
+
+static int dncmp(const void * a, const void * b)
+{
+ const char *const * first = a;
+ const char *const * second = b;
+ return strcmp(*first, *second);
+}
+
+static void compressFilelist(Header h)
+{
+ struct rpmtd_s fileNames;
+ char ** dirNames;
+ const char ** baseNames;
+ uint32_t * dirIndexes;
+ rpm_count_t count;
+ int xx, i;
+ int dirIndex = -1;
+
+ /*
+ * This assumes the file list is already sorted, and begins with a
+ * single '/'. That assumption isn't critical, but it makes things go
+ * a bit faster.
+ */
+
+ if (headerIsEntry(h, RPMTAG_DIRNAMES)) {
+ xx = headerDel(h, RPMTAG_OLDFILENAMES);
+ return; /* Already converted. */
+ }
+
+ if (!headerGet(h, RPMTAG_OLDFILENAMES, &fileNames, HEADERGET_MINMEM))
+ return;
+ count = rpmtdCount(&fileNames);
+ if (count < 1)
+ return;
+
+ dirNames = xmalloc(sizeof(*dirNames) * count); /* worst case */
+ baseNames = xmalloc(sizeof(*dirNames) * count);
+ dirIndexes = xmalloc(sizeof(*dirIndexes) * count);
+
+ /* HACK. Source RPM, so just do things differently */
+ { const char *fn = rpmtdGetString(&fileNames);
+ if (fn && *fn != '/') {
+ dirIndex = 0;
+ dirNames[dirIndex] = xstrdup("");
+ while ((i = rpmtdNext(&fileNames)) >= 0) {
+ dirIndexes[i] = dirIndex;
+ baseNames[i] = rpmtdGetString(&fileNames);
+ }
+ goto exit;
+ }
+ }
+
+ /*
+ * XXX EVIL HACK, FIXME:
+ * This modifies (and then restores) a const string from rpmtd
+ * through basename retrieved from strrchr() which silently
+ * casts away const on return.
+ */
+ while ((i = rpmtdNext(&fileNames)) >= 0) {
+ char ** needle;
+ char savechar;
+ char * baseName;
+ size_t len;
+ char *filename = (char *) rpmtdGetString(&fileNames); /* HACK HACK */
+
+ if (filename == NULL) /* XXX can't happen */
+ continue;
+ baseName = strrchr(filename, '/') + 1;
+ len = baseName - filename;
+ needle = dirNames;
+ savechar = *baseName;
+ *baseName = '\0';
+ if (dirIndex < 0 ||
+ (needle = bsearch(&filename, dirNames, dirIndex + 1, sizeof(dirNames[0]), dncmp)) == NULL) {
+ char *s = xmalloc(len + 1);
+ rstrlcpy(s, filename, len + 1);
+ dirIndexes[i] = ++dirIndex;
+ dirNames[dirIndex] = s;
+ } else
+ dirIndexes[i] = needle - dirNames;
+
+ *baseName = savechar;
+ baseNames[i] = baseName;
+ }
+
+exit:
+ if (count > 0) {
+ headerPutUint32(h, RPMTAG_DIRINDEXES, dirIndexes, count);
+ headerPutStringArray(h, RPMTAG_BASENAMES, baseNames, count);
+ headerPutStringArray(h, RPMTAG_DIRNAMES,
+ (const char **) dirNames, dirIndex + 1);
+ }
+
+ rpmtdFreeData(&fileNames);
+ for (i = 0; i <= dirIndex; i++) {
+ free(dirNames[i]);
+ }
+ free(dirNames);
+ free(baseNames);
+ free(dirIndexes);
+
+ xx = headerDel(h, RPMTAG_OLDFILENAMES);
+}
+
+static void expandFilelist(Header h)
+{
+ struct rpmtd_s filenames;
+
+ if (!headerIsEntry(h, RPMTAG_OLDFILENAMES)) {
+ (void) headerGet(h, RPMTAG_FILENAMES, &filenames, HEADERGET_EXT);
+ if (rpmtdCount(&filenames) < 1)
+ return;
+ rpmtdSetTag(&filenames, RPMTAG_OLDFILENAMES);
+ headerPut(h, &filenames, HEADERPUT_DEFAULT);
+ rpmtdFreeData(&filenames);
+ }
+
+ (void) headerDel(h, RPMTAG_DIRNAMES);
+ (void) headerDel(h, RPMTAG_BASENAMES);
+ (void) headerDel(h, RPMTAG_DIRINDEXES);
+}
+
+/*
+ * Up to rpm 3.0.4, packages implicitly provided their own name-version-release.
+ * Retrofit an explicit "Provides: name = epoch:version-release.
+ */
+static void providePackageNVR(Header h)
+{
+ const char *name = headerGetString(h, RPMTAG_NAME);
+ char *pEVR = headerGetAsString(h, RPMTAG_EVR);
+ rpmsenseFlags pFlags = RPMSENSE_EQUAL;
+ int bingo = 1;
+ struct rpmtd_s pnames;
+ rpmds hds, nvrds;
+
+ /* Generate provides for this package name-version-release. */
+ if (!(name && pEVR))
+ return;
+
+ /*
+ * Rpm prior to 3.0.3 does not have versioned provides.
+ * If no provides at all are available, we can just add.
+ */
+ if (!headerGet(h, RPMTAG_PROVIDENAME, &pnames, HEADERGET_MINMEM)) {
+ goto exit;
+ }
+
+ /*
+ * Otherwise, fill in entries on legacy packages.
+ */
+ if (!headerIsEntry(h, RPMTAG_PROVIDEVERSION)) {
+ while (rpmtdNext(&pnames) >= 0) {
+ rpmsenseFlags fdummy = RPMSENSE_ANY;
+
+ headerPutString(h, RPMTAG_PROVIDEVERSION, "");
+ headerPutUint32(h, RPMTAG_PROVIDEFLAGS, &fdummy, 1);
+ }
+ goto exit;
+ }
+
+ /* see if we already have this provide */
+ hds = rpmdsNew(h, RPMTAG_PROVIDENAME, 0);
+ nvrds = rpmdsSingle(RPMTAG_PROVIDENAME, name, pEVR, pFlags);
+ if (rpmdsFind(hds, nvrds) >= 0) {
+ bingo = 0;
+ }
+ rpmdsFree(hds);
+ rpmdsFree(nvrds);
+
+
+exit:
+ if (bingo) {
+ headerPutString(h, RPMTAG_PROVIDENAME, name);
+ headerPutString(h, RPMTAG_PROVIDEVERSION, pEVR);
+ headerPutUint32(h, RPMTAG_PROVIDEFLAGS, &pFlags, 1);
+ }
+ rpmtdFreeData(&pnames);
+ free(pEVR);
+}
+
+static void legacyRetrofit(Header h)
+{
+ /*
+ * The file list was moved to a more compressed format which not
+ * only saves memory (nice), but gives fingerprinting a nice, fat
+ * speed boost (very nice). Go ahead and convert old headers to
+ * the new style (this is a noop for new headers).
+ */
+ compressFilelist(h);
+
+ /* Retrofit "Provide: name = EVR" for binary packages. */
+ if (!headerIsSource(h)) {
+ providePackageNVR(h);
+ }
+}
+
+int headerConvert(Header h, int op)
+{
+ int rc = 1;
+
+ if (h == NULL)
+ return 0;
+
+ switch (op) {
+ case HEADERCONV_EXPANDFILELIST:
+ expandFilelist(h);
+ break;
+ case HEADERCONV_COMPRESSFILELIST:
+ compressFilelist(h);
+ break;
+ case HEADERCONV_RETROFIT_V3:
+ legacyRetrofit(h);
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+ return rc;
+};
+
+/*
+ * Backwards compatibility wrappers for legacy interfaces.
+ * Remove these some day...
+ */
+#define _RPM_4_4_COMPAT
+#include <rpm/rpmlegacy.h>
+
+/* dumb macro to avoid 50 copies of this code while converting... */
+#define TDWRAP() \
+ if (type) \
+ *type = td.type; \
+ if (p) \
+ *p = td.data; \
+ else \
+ rpmtdFreeData(&td); \
+ if (c) \
+ *c = td.count
+
+int headerRemoveEntry(Header h, rpm_tag_t tag)
+{
+ return headerDel(h, tag);
+}
+
+static void *_headerFreeData(rpm_data_t data, rpm_tagtype_t type)
+{
+ if (data) {
+ if (type == RPM_FORCEFREE_TYPE ||
+ type == RPM_STRING_ARRAY_TYPE ||
+ type == RPM_I18NSTRING_TYPE ||
+ type == RPM_BIN_TYPE)
+ free(data);
+ }
+ return NULL;
+}
+
+void * headerFreeData(rpm_data_t data, rpm_tagtype_t type)
+{
+ return _headerFreeData(data, type);
+}
+
+void * headerFreeTag(Header h, rpm_data_t data, rpm_tagtype_t type)
+{
+ return _headerFreeData(data, type);
+}
+
+static int headerGetWrap(Header h, rpm_tag_t tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c,
+ headerGetFlags flags)
+{
+ struct rpmtd_s td;
+ int rc;
+
+ rc = headerGet(h, tag, &td, flags);
+ TDWRAP();
+ return rc;
+}
+
+int headerGetEntry(Header h, rpm_tag_t tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c)
+{
+ return headerGetWrap(h, tag, type, p, c, HEADERGET_DEFAULT);
+}
+
+int headerGetEntryMinMemory(Header h, rpm_tag_t tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c)
+{
+ return headerGetWrap(h, tag, type, (rpm_data_t) p, c, HEADERGET_MINMEM);
+}
+
+/* XXX shut up compiler warning from missing prototype */
+int headerGetRawEntry(Header h, rpm_tag_t tag, rpm_tagtype_t * type, rpm_data_t * p,
+ rpm_count_t * c);
+
+int headerGetRawEntry(Header h, rpm_tag_t tag, rpm_tagtype_t * type, rpm_data_t * p,
+ rpm_count_t * c)
+{
+ if (p == NULL)
+ return headerIsEntry(h, tag);
+
+ return headerGetWrap(h, tag, type, p, c, HEADERGET_RAW);
+}
+
+int headerNextIterator(HeaderIterator hi,
+ rpm_tag_t * tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c)
+{
+ struct rpmtd_s td;
+ int rc;
+
+ rc = headerNext(hi, &td);
+ if (tag)
+ *tag = td.tag;
+ TDWRAP();
+ return rc;
+}
+
+int headerModifyEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ struct rpmtd_s td = {
+ .tag = tag,
+ .type = type,
+ .data = (void *) p,
+ .count = c,
+ };
+ return headerMod(h, &td);
+}
+
+static int headerPutWrap(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c, headerPutFlags flags)
+{
+ struct rpmtd_s td = {
+ .tag = tag,
+ .type = type,
+ .data = (void *) p,
+ .count = c,
+ };
+ return headerPut(h, &td, flags);
+}
+
+int headerAddOrAppendEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ return headerPutWrap(h, tag, type, p, c, HEADERPUT_APPEND);
+}
+
+int headerAppendEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ return headerPutWrap(h, tag, type, p, c, HEADERPUT_APPEND);
+}
+
+int headerAddEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ return headerPutWrap(h, tag, type, p, c, HEADERPUT_DEFAULT);
+}
+#undef _RPM_4_4_COMPAT
diff --git a/lib/manifest.c b/lib/manifest.c
new file mode 100644
index 0000000..5d71f3f
--- /dev/null
+++ b/lib/manifest.c
@@ -0,0 +1,175 @@
+/** \ingroup rpmcli
+ * \file lib/manifest.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmlog.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/argv.h>
+
+#include "lib/manifest.h"
+
+#include "debug.h"
+
+
+char * rpmPermsString(int mode)
+{
+ char *perms = xstrdup("----------");
+
+ if (S_ISREG(mode))
+ perms[0] = '-';
+ else if (S_ISDIR(mode))
+ perms[0] = 'd';
+ else if (S_ISLNK(mode))
+ perms[0] = 'l';
+ else if (S_ISFIFO(mode))
+ perms[0] = 'p';
+ else if (S_ISSOCK(mode))
+ perms[0] = 's';
+ else if (S_ISCHR(mode))
+ perms[0] = 'c';
+ else if (S_ISBLK(mode))
+ perms[0] = 'b';
+ else
+ perms[0] = '?';
+
+ if (mode & S_IRUSR) perms[1] = 'r';
+ if (mode & S_IWUSR) perms[2] = 'w';
+ if (mode & S_IXUSR) perms[3] = 'x';
+
+ if (mode & S_IRGRP) perms[4] = 'r';
+ if (mode & S_IWGRP) perms[5] = 'w';
+ if (mode & S_IXGRP) perms[6] = 'x';
+
+ if (mode & S_IROTH) perms[7] = 'r';
+ if (mode & S_IWOTH) perms[8] = 'w';
+ if (mode & S_IXOTH) perms[9] = 'x';
+
+ if (mode & S_ISUID)
+ perms[3] = ((mode & S_IXUSR) ? 's' : 'S');
+
+ if (mode & S_ISGID)
+ perms[6] = ((mode & S_IXGRP) ? 's' : 'S');
+
+ if (mode & S_ISVTX)
+ perms[9] = ((mode & S_IXOTH) ? 't' : 'T');
+
+ return perms;
+}
+
+/**@todo Infinite loops through manifest files exist, operator error for now. */
+rpmRC rpmReadPackageManifest(FD_t fd, int * argcPtr, char *** argvPtr)
+{
+ ARGV_t sb = NULL;
+ char * s = NULL;
+ char * se;
+ int ac = 0;
+ char ** av = NULL;
+ int argc = (argcPtr ? *argcPtr : 0);
+ char ** argv = (argvPtr ? *argvPtr : NULL);
+ FILE * f = fdopen(Fileno(fd), "r");
+ rpmRC rpmrc = RPMRC_OK;
+ int i, j, next, npre;
+
+ if (f != NULL)
+ while (1) {
+ char line[BUFSIZ];
+
+ /* Read next line. */
+ s = fgets(line, sizeof(line) - 1, f);
+ if (s == NULL) {
+ /* XXX Ferror check needed */
+ break;
+ }
+
+ /* Skip comments. */
+ if ((se = strchr(s, '#')) != NULL) *se = '\0';
+
+ /* Trim white space. */
+ se = s + strlen(s);
+ while (se > s && (se[-1] == '\n' || se[-1] == '\r'))
+ *(--se) = '\0';
+ while (*s && strchr(" \f\n\r\t\v", *s) != NULL)
+ s++;
+ if (*s == '\0') continue;
+
+ /* Sanity checks: skip obviously binary lines and dash (for stdin) */
+ if (*s < 32 || rstreq(s, "-")) {
+ s = NULL;
+ rpmrc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ /* Concatenate next line in buffer. */
+ *se = '\0';
+ argvAdd(&sb, s);
+ }
+
+ s = argvJoin(sb, " ");
+
+ if (!(s && *s)) {
+ rpmrc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ /* Glob manifest items. */
+ rpmrc = (rpmGlob(s, &ac, &av) == 0 ? RPMRC_OK : RPMRC_FAIL);
+ if (rpmrc != RPMRC_OK) goto exit;
+
+ rpmlog(RPMLOG_DEBUG, "adding %d args from manifest.\n", ac);
+
+ /* Count non-NULL args, keeping track of 1st arg after last NULL. */
+ npre = 0;
+ next = 0;
+ if (argv != NULL)
+ for (i = 0; i < argc; i++) {
+ if (argv[i] != NULL)
+ npre++;
+ else if (i >= next)
+ next = i + 1;
+ }
+
+ /* Copy old arg list, inserting manifest before argv[next]. */
+ if (argv != NULL) {
+ int nac = npre + ac;
+ char ** nav = xcalloc((nac + 1), sizeof(*nav));
+
+ for (i = 0, j = 0; i < next; i++) {
+ if (argv[i] != NULL)
+ nav[j++] = argv[i];
+ }
+
+ if (ac)
+ memcpy(nav + j, av, ac * sizeof(*nav));
+ if ((argc - next) > 0)
+ memcpy(nav + j + ac, argv + next, (argc - next) * sizeof(*nav));
+ nav[nac] = NULL;
+
+ if (argvPtr)
+ *argvPtr = argv = _free(argv);
+ av = _free(av);
+ av = nav;
+ ac = nac;
+ }
+
+ /* Save new argc/argv list. */
+ if (argvPtr) {
+ *argvPtr = _free(*argvPtr);
+ *argvPtr = av;
+ }
+ if (argcPtr)
+ *argcPtr = ac;
+
+exit:
+ if (argvPtr == NULL || (rpmrc != RPMRC_OK && av)) {
+ if (av)
+ for (i = 0; i < ac; i++)
+ av[i] = _free(av[i]);
+ av = _free(av);
+ }
+ argvFree(sb);
+ free(s);
+ /* FIX: *argvPtr may be NULL. */
+ return rpmrc;
+}
diff --git a/lib/manifest.h b/lib/manifest.h
new file mode 100644
index 0000000..227ca00
--- /dev/null
+++ b/lib/manifest.h
@@ -0,0 +1,34 @@
+#ifndef H_MANIFEST
+#define H_MANIFEST
+
+/**
+ * \file lib/manifest.h
+ * Routines to expand a manifest containing glob expressions into an argv list.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Return ls(1)-like formatted mode string.
+ * @param mode file mode
+ * @return (malloc'd) formatted mode string
+ */
+char * rpmPermsString(int mode)
+;
+
+/**
+ * Read manifest, glob items, and append to existing args.
+ * @param fd manifest file handle
+ * @retval argcPtr no. of args
+ * @retval argvPtr args themselves
+ * @return RPMRC_OK on success
+ */
+rpmRC rpmReadPackageManifest(FD_t fd, int * argcPtr, char *** argvPtr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_MANIFEST */
diff --git a/lib/merge.c b/lib/merge.c
new file mode 100644
index 0000000..738ad7a
--- /dev/null
+++ b/lib/merge.c
@@ -0,0 +1,347 @@
+#ifndef __APPLE__
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)merge.c 8.2 (Berkeley) 2/14/94";
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * Hybrid exponential search/linear search merge sort with hybrid
+ * natural/pairwise first pass. Requires about .3% more comparisons
+ * for random data than LSMS with pairwise first pass alone.
+ * It works for objects as small as two bytes.
+ */
+
+#define NATURAL
+#define THRESHOLD 16 /* Best choice for natural merge cut-off. */
+
+/* #define NATURAL to get hybrid natural merge.
+ * (The default is pairwise merging.)
+ */
+
+#include "system.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "lib/rpmdb_internal.h" /* XXX for mergesort */
+
+#define ISIZE sizeof(int)
+#define PSIZE sizeof(unsigned char *)
+#define ICOPY_LIST(src, dst, last) \
+ do \
+ *(int*)dst = *(int*)src, src += ISIZE, dst += ISIZE; \
+ while(src < last)
+#define ICOPY_ELT(src, dst, i) \
+ do \
+ *(int*) dst = *(int*) src, src += ISIZE, dst += ISIZE; \
+ while (i -= ISIZE)
+
+#define CCOPY_LIST(src, dst, last) \
+ do \
+ *dst++ = *src++; \
+ while (src < last)
+#define CCOPY_ELT(src, dst, i) \
+ do \
+ *dst++ = *src++; \
+ while (i -= 1)
+
+/*
+ * Find the next possible pointer head. (Trickery for forcing an array
+ * to do double duty as a linked list when objects do not align with word
+ * boundaries.
+ */
+/* Assumption: PSIZE is a power of 2. */
+#define EVAL(p) (unsigned char **) \
+ ((unsigned char *)0 + \
+ (((unsigned char *)p + PSIZE - 1 - (unsigned char *) 0) & ~(PSIZE - 1)))
+
+#define swap(a, b) { \
+ s = b; \
+ i = size; \
+ do { \
+ tmp = *a; *a++ = *s; *s++ = tmp; \
+ } while (--i); \
+ a -= size; \
+ }
+#define reverse(bot, top) { \
+ s = top; \
+ do { \
+ i = size; \
+ do { \
+ tmp = *bot; *bot++ = *s; *s++ = tmp; \
+ } while (--i); \
+ s -= size2; \
+ } while(bot < s); \
+}
+
+/*
+ * This is to avoid out-of-bounds addresses in sorting the
+ * last 4 elements.
+ */
+static void
+insertionsort(unsigned char *a, size_t n, size_t size,
+ int (*cmp) (const void *, const void *))
+{
+ unsigned char *ai, *s, *t, *u, tmp;
+ int i;
+
+ for (ai = a+size; --n >= 1; ai += size)
+ for (t = ai; t > a; t -= size) {
+ u = t - size;
+ if (cmp(u, t) <= 0)
+ break;
+ swap(u, t);
+ }
+}
+
+/*
+ * Optional hybrid natural/pairwise first pass. Eats up list1 in runs of
+ * increasing order, list2 in a corresponding linked list. Checks for runs
+ * when THRESHOLD/2 pairs compare with same sense. (Only used when NATURAL
+ * is defined. Otherwise simple pairwise merging is used.)
+ */
+static void
+setup(unsigned char *list1, unsigned char *list2,
+ size_t n, size_t size, int (*cmp) (const void *, const void *))
+{
+ int i, length, size2, tmp, sense;
+ unsigned char *f1, *f2, *s, *l2, *last, *p2;
+
+ size2 = size*2;
+ if (n <= 5) {
+ insertionsort(list1, n, size, cmp);
+ *EVAL(list2) = (unsigned char*) list2 + n*size;
+ return;
+ }
+ /*
+ * Avoid running pointers out of bounds; limit n to evens
+ * for simplicity.
+ */
+ i = 4 + (n & 1);
+ insertionsort(list1 + (n - i) * size, i, size, cmp);
+ last = list1 + size * (n - i);
+ *EVAL(list2 + (last - list1)) = list2 + n * size;
+
+#ifdef NATURAL
+ p2 = list2;
+ f1 = list1;
+ sense = (cmp(f1, f1 + size) > 0);
+ for (; f1 < last; sense = !sense) {
+ length = 2;
+ /* Find pairs with same sense. */
+ for (f2 = f1 + size2; f2 < last; f2 += size2) {
+ if ((cmp(f2, f2+ size) > 0) != sense)
+ break;
+ length += 2;
+ }
+ if (length < THRESHOLD) { /* Pairwise merge */
+ do {
+ p2 = *EVAL(p2) = f1 + size2 - list1 + list2;
+ if (sense > 0)
+ swap (f1, f1 + size);
+ } while ((f1 += size2) < f2);
+ } else { /* Natural merge */
+ l2 = f2;
+ for (f2 = f1 + size2; f2 < l2; f2 += size2) {
+ if ((cmp(f2-size, f2) > 0) != sense) {
+ p2 = *EVAL(p2) = f2 - list1 + list2;
+ if (sense > 0)
+ reverse(f1, f2-size);
+ f1 = f2;
+ }
+ }
+ if (sense > 0)
+ reverse (f1, f2-size);
+ f1 = f2;
+ if (f2 < last || cmp(f2 - size, f2) > 0)
+ p2 = *EVAL(p2) = f2 - list1 + list2;
+ else
+ p2 = *EVAL(p2) = list2 + n*size;
+ }
+ }
+#else /* pairwise merge only. */
+ for (f1 = list1, p2 = list2; f1 < last; f1 += size2) {
+ p2 = *EVAL(p2) = p2 + size2;
+ if (cmp (f1, f1 + size) > 0)
+ swap(f1, f1 + size);
+ }
+#endif /* NATURAL */
+}
+
+/*
+ * Arguments are as for qsort.
+ */
+int
+mergesort(void *base, size_t nmemb, size_t size,
+ int (*cmp) (const void *, const void *))
+{
+ register int i, sense;
+ int big, iflag;
+ register unsigned char *f1, *f2, *t, *b, *q, *l1, *l2;
+ register unsigned char *tp2;
+ unsigned char *list2;
+ unsigned char *list1;
+ unsigned char *p2, *p, *last, **p1;
+
+ if (size < PSIZE / 2) { /* Pointers must fit into 2 * size. */
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (nmemb == 0)
+ return (0);
+
+ /*
+ * XXX
+ * Stupid subtraction for the Cray.
+ */
+ iflag = 0;
+ if (!(size % ISIZE) && !(((char *)base - (char *)0) % ISIZE))
+ iflag = 1;
+
+ if ((list2 = malloc(nmemb * size + PSIZE)) == NULL)
+ return (-1);
+
+ list1 = base;
+ setup(list1, list2, nmemb, size, cmp);
+ last = list2 + nmemb * size;
+ i = big = 0;
+ while (*EVAL(list2) != last) {
+ l2 = list1;
+ p1 = EVAL(list1);
+ for (tp2 = p2 = list2; p2 != last; p1 = EVAL(l2)) {
+ p2 = *EVAL(p2);
+ f1 = l2;
+ f2 = l1 = list1 + (p2 - list2);
+ if (p2 != last)
+ p2 = *EVAL(p2);
+ l2 = list1 + (p2 - list2);
+ while (f1 < l1 && f2 < l2) {
+ if ((*cmp)(f1, f2) <= 0) {
+ q = f2;
+ b = f1, t = l1;
+ sense = -1;
+ } else {
+ q = f1;
+ b = f2, t = l2;
+ sense = 0;
+ }
+ if (!big) { /* here i = 0 */
+ while ((b += size) < t && cmp(q, b) >sense)
+ if (++i == 6) {
+ big = 1;
+ goto EXPONENTIAL;
+ }
+ } else {
+EXPONENTIAL: for (i = size; ; i <<= 1)
+ if ((p = (b + i)) >= t) {
+ if ((p = t - size) > b &&
+ (*cmp)(q, p) <= sense)
+ t = p;
+ else
+ b = p;
+ break;
+ } else if ((*cmp)(q, p) <= sense) {
+ t = p;
+ if (i == size)
+ big = 0;
+ goto FASTCASE;
+ } else
+ b = p;
+ while (t > b+size) {
+ i = (((t - b) / size) >> 1) * size;
+ if ((*cmp)(q, p = b + i) <= sense)
+ t = p;
+ else
+ b = p;
+ }
+ goto COPY;
+FASTCASE: while (i > size)
+ if ((*cmp)(q,
+ p = b + (i >>= 1)) <= sense)
+ t = p;
+ else
+ b = p;
+COPY: b = t;
+ }
+ i = size;
+ if (q == f1) {
+ if (iflag) {
+ ICOPY_LIST(f2, tp2, b);
+ ICOPY_ELT(f1, tp2, i);
+ } else {
+ CCOPY_LIST(f2, tp2, b);
+ CCOPY_ELT(f1, tp2, i);
+ }
+ } else {
+ if (iflag) {
+ ICOPY_LIST(f1, tp2, b);
+ ICOPY_ELT(f2, tp2, i);
+ } else {
+ CCOPY_LIST(f1, tp2, b);
+ CCOPY_ELT(f2, tp2, i);
+ }
+ }
+ }
+ if (f2 < l2) {
+ if (iflag)
+ ICOPY_LIST(f2, tp2, l2);
+ else
+ CCOPY_LIST(f2, tp2, l2);
+ } else if (f1 < l1) {
+ if (iflag)
+ ICOPY_LIST(f1, tp2, l1);
+ else
+ CCOPY_LIST(f1, tp2, l1);
+ }
+ *p1 = l2;
+ }
+ tp2 = list1; /* swap list1, list2 */
+ list1 = list2;
+ list2 = tp2;
+ last = list2 + nmemb*size;
+ }
+ if (base == list2) {
+ memmove(list2, list1, nmemb*size);
+ list2 = list1;
+ }
+ free(list2);
+ return (0);
+}
+#else
+/* mergesort is implemented in System on Mac OS X */
+#endif /* __APPLE__ */
diff --git a/lib/misc.c b/lib/misc.c
new file mode 100644
index 0000000..3ea41a5
--- /dev/null
+++ b/lib/misc.c
@@ -0,0 +1,24 @@
+/**
+ * \file lib/misc.c
+ */
+
+#include "system.h"
+#include "lib/misc.h"
+#include "debug.h"
+
+unsigned int hashFunctionString(const char * string)
+{
+ /* Jenkins One-at-a-time hash */
+ unsigned int hash = 0xe4721b68;
+
+ while (*string != '\0') {
+ hash += *string;
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ string++;
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+}
diff --git a/lib/misc.h b/lib/misc.h
new file mode 100644
index 0000000..958561c
--- /dev/null
+++ b/lib/misc.h
@@ -0,0 +1,46 @@
+#ifndef H_MISC
+#define H_MISC
+
+/**
+ * \file lib/misc.h
+ *
+ */
+
+#include <string.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/header.h> /* for headerGetFlags typedef, duh.. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* known arch? */
+RPM_GNUC_INTERNAL
+int rpmIsKnownArch(const char *name);
+
+RPM_GNUC_INTERNAL
+char * rpmVerifyString(uint32_t verifyResult, const char *pad);
+
+RPM_GNUC_INTERNAL
+char * rpmFFlagsString(uint32_t fflags, const char *pad);
+
+RPM_GNUC_INTERNAL
+unsigned int hashFunctionString(const char * string);
+
+typedef char * (*headerTagFormatFunction) (rpmtd td, char * formatPrefix);
+typedef int (*headerTagTagFunction) (Header h, rpmtd td, headerGetFlags hgflags);
+
+RPM_GNUC_INTERNAL
+headerTagTagFunction rpmHeaderTagFunc(rpmTagVal tag);
+
+RPM_GNUC_INTERNAL
+headerTagFormatFunction rpmHeaderFormatFuncByName(const char *fmt);
+
+RPM_GNUC_INTERNAL
+headerTagFormatFunction rpmHeaderFormatFuncByValue(rpmtdFormats fmt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_MISC */
diff --git a/lib/order.c b/lib/order.c
new file mode 100644
index 0000000..34ad795
--- /dev/null
+++ b/lib/order.c
@@ -0,0 +1,655 @@
+/** \ingroup rpmts
+ * \file lib/depends.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmtag.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmds.h>
+
+#include "lib/rpmte_internal.h" /* XXX tsortInfo_s */
+#include "lib/rpmts_internal.h"
+
+#include "debug.h"
+
+/*
+ * Strongly Connected Components
+ * set of packages (indirectly) requiering each other
+ */
+struct scc_s {
+ int count; /* # of external requires this SCC has */
+ /* int qcnt; # of external requires pointing to this SCC */
+ int size; /* # of members */
+ tsortInfo * members;
+};
+
+typedef struct scc_s * scc;
+
+struct relation_s {
+ tsortInfo rel_suc; // pkg requiring this package
+ rpmsenseFlags rel_flags; // accumulated flags of the requirements
+ struct relation_s * rel_next;
+};
+
+typedef struct relation_s * relation;
+
+struct tsortInfo_s {
+ rpmte te;
+ int tsi_count; // #pkgs this pkg requires
+ int tsi_qcnt; // #pkgs requiring this package
+ int tsi_reqx; // requires Idx/mark as (queued/loop)
+ struct relation_s * tsi_relations;
+ struct relation_s * tsi_forward_relations;
+ tsortInfo tsi_suc; // used for queuing (addQ)
+ int tsi_SccIdx; // # of the SCC the node belongs to
+ // (1 for trivial SCCs)
+ int tsi_SccLowlink; // used for SCC detection
+};
+
+static void rpmTSIFree(tsortInfo tsi)
+{
+ relation rel;
+
+ while (tsi->tsi_relations != NULL) {
+ rel = tsi->tsi_relations;
+ tsi->tsi_relations = tsi->tsi_relations->rel_next;
+ rel = _free(rel);
+ }
+ while (tsi->tsi_forward_relations != NULL) {
+ rel = tsi->tsi_forward_relations;
+ tsi->tsi_forward_relations = \
+ tsi->tsi_forward_relations->rel_next;
+ rel = _free(rel);
+ }
+}
+
+static inline int addSingleRelation(rpmte p,
+ rpmte q,
+ rpmsenseFlags dsflags)
+{
+ struct tsortInfo_s *tsi_p, *tsi_q;
+ relation rel;
+ rpmElementType teType = rpmteType(p);
+ rpmsenseFlags flags;
+
+ /* Avoid deps outside this transaction and self dependencies */
+ if (q == NULL || q == p)
+ return 0;
+
+ /* Erasures are reversed installs. */
+ if (teType == TR_REMOVED) {
+ rpmte r = p;
+ p = q;
+ q = r;
+ flags = isErasePreReq(dsflags);
+ } else {
+ flags = isInstallPreReq(dsflags);
+ }
+
+ /* map legacy prereq to pre/preun as needed */
+ if (isLegacyPreReq(dsflags)) {
+ flags |= (teType == TR_ADDED) ?
+ RPMSENSE_SCRIPT_PRE : RPMSENSE_SCRIPT_PREUN;
+ }
+
+ tsi_p = rpmteTSI(p);
+ tsi_q = rpmteTSI(q);
+
+ /* if relation got already added just update the flags */
+ if (tsi_q->tsi_relations && tsi_q->tsi_relations->rel_suc == tsi_p) {
+ tsi_q->tsi_relations->rel_flags |= flags;
+ tsi_p->tsi_forward_relations->rel_flags |= flags;
+ return 0;
+ }
+
+ /* Record next "q <- p" relation (i.e. "p" requires "q"). */
+ if (p != q) {
+ /* bump p predecessor count */
+ tsi_p->tsi_count++;
+ }
+
+ rel = xcalloc(1, sizeof(*rel));
+ rel->rel_suc = tsi_p;
+ rel->rel_flags = flags;
+
+ rel->rel_next = tsi_q->tsi_relations;
+ tsi_q->tsi_relations = rel;
+ if (p != q) {
+ /* bump q successor count */
+ tsi_q->tsi_qcnt++;
+ }
+
+ rel = xcalloc(1, sizeof(*rel));
+ rel->rel_suc = tsi_q;
+ rel->rel_flags = flags;
+
+ rel->rel_next = tsi_p->tsi_forward_relations;
+ tsi_p->tsi_forward_relations = rel;
+
+ return 0;
+}
+
+/**
+ * Record next "q <- p" relation (i.e. "p" requires "q").
+ * @param ts transaction set
+ * @param p predecessor (i.e. package that "Requires: q")
+ * @param requires relation
+ * @return 0 always
+ */
+static inline int addRelation(rpmts ts,
+ rpmal al,
+ rpmte p,
+ rpmds requires)
+{
+ rpmte q;
+ rpmsenseFlags dsflags;
+
+ dsflags = rpmdsFlags(requires);
+
+ /* Avoid dependendencies which are not relevant for ordering */
+ if (dsflags & (RPMSENSE_RPMLIB|RPMSENSE_CONFIG|RPMSENSE_PRETRANS|RPMSENSE_POSTTRANS))
+ return 0;
+
+ q = rpmalSatisfiesDepend(al, requires);
+
+ /* Avoid deps outside this transaction and self dependencies */
+ if (q == NULL || q == p)
+ return 0;
+
+ addSingleRelation(p, q, dsflags);
+
+ return 0;
+}
+
+/*
+ * Collections might have special ordering requirements. Notably
+ * sepolicy collection requires having all the bits in the collection
+ * close to each other. We try to ensure this by creating a strongly
+ * connected component of such "grouped" collections, by introducing
+ * an artificial relation loop across the all its members.
+ */
+static int addCollRelations(rpmal al, rpmte p, ARGV_t *seenColls)
+{
+ ARGV_const_t qcolls;
+
+ for (qcolls = rpmteCollections(p); qcolls && *qcolls; qcolls++) {
+ char * flags;
+ if (argvSearch(*seenColls, *qcolls, NULL))
+ continue;
+
+ flags = rstrscat(NULL, "%{__collection_", *qcolls, "_flags}", NULL);
+ if (rpmExpandNumeric(flags) & 0x1) {
+ rpmte *tes = rpmalAllInCollection(al, *qcolls);
+ for (rpmte *te = tes; te && *te; te++) {
+ rpmte next = (*(te + 1) != NULL) ? *(te + 1) : *tes;
+ addSingleRelation(*te, next, RPMSENSE_ANY);
+ }
+ _free(tes);
+ }
+ free(flags);
+
+ argvAdd(seenColls, *qcolls);
+ argvSort(*seenColls, NULL);
+ }
+
+ return 0;
+}
+
+/**
+ * Add element to list sorting by tsi_qcnt.
+ * @param p new element
+ * @retval qp address of first element
+ * @retval rp address of last element
+ * @param prefcolor
+ */
+static void addQ(tsortInfo p, tsortInfo * qp, tsortInfo * rp,
+ rpm_color_t prefcolor)
+{
+ tsortInfo q, qprev;
+ rpm_color_t pcolor = rpmteColor(p->te);
+ int tailcond;
+
+ /* Mark the package as queued. */
+ p->tsi_reqx = 1;
+
+ if ((*rp) == NULL) { /* 1st element */
+ /* FIX: double indirection */
+ (*rp) = (*qp) = p;
+ return;
+ }
+
+ if (rpmteType(p->te) == TR_ADDED)
+ tailcond = (pcolor && pcolor != prefcolor);
+ else
+ tailcond = (pcolor && pcolor == prefcolor);
+
+ /* Find location in queue using metric tsi_qcnt and color. */
+ for (qprev = NULL, q = (*qp);
+ q != NULL;
+ qprev = q, q = q->tsi_suc)
+ {
+ /* Place preferred color towards queue head on install, tail on erase */
+ if (tailcond && (pcolor != rpmteColor(q->te)))
+ continue;
+
+ if (q->tsi_qcnt <= p->tsi_qcnt)
+ break;
+ }
+
+ if (qprev == NULL) { /* insert at beginning of list */
+ p->tsi_suc = q;
+ (*qp) = p; /* new head */
+ } else if (q == NULL) { /* insert at end of list */
+ qprev->tsi_suc = p;
+ (*rp) = p; /* new tail */
+ } else { /* insert between qprev and q */
+ p->tsi_suc = q;
+ qprev->tsi_suc = p;
+ }
+}
+
+typedef struct sccData_s {
+ int index; /* DFS node number counter */
+ tsortInfo *stack; /* Stack of nodes */
+ int stackcnt; /* Stack top counter */
+ scc SCCs; /* Array of SCC's found */
+ int sccCnt; /* Number of SCC's found */
+} * sccData;
+
+static void tarjan(sccData sd, tsortInfo tsi) {
+ tsortInfo tsi_q;
+ relation rel;
+
+ /* use negative index numbers */
+ sd->index--;
+ /* Set the depth index for p */
+ tsi->tsi_SccIdx = sd->index;
+ tsi->tsi_SccLowlink = sd->index;
+
+ sd->stack[sd->stackcnt++] = tsi; /* Push p on the stack */
+ for (rel=tsi->tsi_relations; rel != NULL; rel=rel->rel_next) {
+ /* Consider successors of p */
+ tsi_q = rel->rel_suc;
+ if (tsi_q->tsi_SccIdx > 0)
+ /* Ignore already found SCCs */
+ continue;
+ if (tsi_q->tsi_SccIdx == 0){
+ /* Was successor q not yet visited? */
+ tarjan(sd, tsi_q); /* Recurse */
+ /* negative index numers: use max as it is closer to 0 */
+ tsi->tsi_SccLowlink = (
+ tsi->tsi_SccLowlink > tsi_q->tsi_SccLowlink
+ ? tsi->tsi_SccLowlink : tsi_q->tsi_SccLowlink);
+ } else {
+ tsi->tsi_SccLowlink = (
+ tsi->tsi_SccLowlink > tsi_q->tsi_SccIdx
+ ? tsi->tsi_SccLowlink : tsi_q->tsi_SccIdx);
+ }
+ }
+
+ if (tsi->tsi_SccLowlink == tsi->tsi_SccIdx) {
+ /* v is the root of an SCC? */
+ if (sd->stack[sd->stackcnt-1] == tsi) {
+ /* ignore trivial SCCs */
+ tsi_q = sd->stack[--sd->stackcnt];
+ tsi_q->tsi_SccIdx = 1;
+ } else {
+ int stackIdx = sd->stackcnt;
+ do {
+ tsi_q = sd->stack[--stackIdx];
+ tsi_q->tsi_SccIdx = sd->sccCnt;
+ } while (tsi_q != tsi);
+
+ stackIdx = sd->stackcnt;
+ do {
+ tsi_q = sd->stack[--stackIdx];
+ /* Calculate count for the SCC */
+ sd->SCCs[sd->sccCnt].count += tsi_q->tsi_count;
+ /* Subtract internal relations */
+ for (rel=tsi_q->tsi_relations; rel != NULL;
+ rel=rel->rel_next) {
+ if (rel->rel_suc != tsi_q &&
+ rel->rel_suc->tsi_SccIdx == sd->sccCnt)
+ sd->SCCs[sd->sccCnt].count--;
+ }
+ } while (tsi_q != tsi);
+ sd->SCCs[sd->sccCnt].size = sd->stackcnt - stackIdx;
+ /* copy members */
+ sd->SCCs[sd->sccCnt].members = xcalloc(sd->SCCs[sd->sccCnt].size,
+ sizeof(tsortInfo));
+ memcpy(sd->SCCs[sd->sccCnt].members, sd->stack + stackIdx,
+ sd->SCCs[sd->sccCnt].size * sizeof(tsortInfo));
+ sd->stackcnt = stackIdx;
+ sd->sccCnt++;
+ }
+ }
+}
+
+/* Search for SCCs and return an array last entry has a .size of 0 */
+static scc detectSCCs(tsortInfo orderInfo, int nelem, int debugloops)
+{
+ /* Set up data structures needed for the tarjan algorithm */
+ scc SCCs = xcalloc(nelem+3, sizeof(*SCCs));
+ tsortInfo *stack = xcalloc(nelem, sizeof(*stack));
+ struct sccData_s sd = { 0, stack, 0, SCCs, 2 };
+
+ for (int i = 0; i < nelem; i++) {
+ tsortInfo tsi = &orderInfo[i];
+ /* Start a DFS at each node */
+ if (tsi->tsi_SccIdx == 0)
+ tarjan(&sd, tsi);
+ }
+
+ free(stack);
+
+ SCCs = xrealloc(SCCs, (sd.sccCnt+1)*sizeof(struct scc_s));
+
+ /* Debug output */
+ if (sd.sccCnt > 2) {
+ int msglvl = debugloops ? RPMLOG_WARNING : RPMLOG_DEBUG;
+ rpmlog(msglvl, "%i Strongly Connected Components\n", sd.sccCnt-2);
+ for (int i = 2; i < sd.sccCnt; i++) {
+ rpmlog(msglvl, "SCC #%i: %i members (%i external dependencies)\n",
+ i-1, SCCs[i].size, SCCs[i].count);
+
+ /* loop over members */
+ for (int j = 0; j < SCCs[i].size; j++) {
+ tsortInfo member = SCCs[i].members[j];
+ rpmlog(msglvl, "\t%s\n", rpmteNEVRA(member->te));
+ /* show relations between members */
+ relation rel = member->tsi_forward_relations;
+ for (; rel != NULL; rel=rel->rel_next) {
+ if (rel->rel_suc->tsi_SccIdx!=i) continue;
+ rpmlog(msglvl, "\t\t%s %s\n",
+ rel->rel_flags ? "=>" : "->",
+ rpmteNEVRA(rel->rel_suc->te));
+ }
+ }
+ }
+ }
+ return SCCs;
+}
+
+static void collectTE(rpm_color_t prefcolor, tsortInfo q,
+ rpmte * newOrder, int * newOrderCount,
+ scc SCCs,
+ tsortInfo * queue_end,
+ tsortInfo * outer_queue,
+ tsortInfo * outer_queue_end)
+{
+ char deptypechar = (rpmteType(q->te) == TR_REMOVED ? '-' : '+');
+
+ if (rpmIsDebug()) {
+ int depth = 1;
+ /* figure depth in tree for nice formatting */
+ for (rpmte p = q->te; (p = rpmteParent(p)); depth++) {}
+ rpmlog(RPMLOG_DEBUG, "%5d%5d%5d%5d %*s%c%s\n",
+ *newOrderCount, q->tsi_count, q->tsi_qcnt,
+ depth, (2 * depth), "",
+ deptypechar, rpmteNEVRA(q->te));
+ }
+
+ newOrder[*newOrderCount] = q->te;
+ (*newOrderCount)++;
+
+ /* T6. Erase relations. */
+ for (relation rel = q->tsi_relations; rel != NULL; rel = rel->rel_next) {
+ tsortInfo p = rel->rel_suc;
+ /* ignore already collected packages */
+ if (p->tsi_SccIdx == 0) continue;
+ if (p == q) continue;
+
+ if (p && (--p->tsi_count) == 0) {
+ (void) rpmteSetParent(p->te, q->te);
+
+ if (q->tsi_SccIdx > 1 && q->tsi_SccIdx != p->tsi_SccIdx) {
+ /* Relation point outside of this SCC: add to outside queue */
+ assert(outer_queue != NULL && outer_queue_end != NULL);
+ addQ(p, outer_queue, outer_queue_end, prefcolor);
+ } else {
+ addQ(p, &q->tsi_suc, queue_end, prefcolor);
+ }
+ }
+ if (p && p->tsi_SccIdx > 1 &&
+ p->tsi_SccIdx != q->tsi_SccIdx) {
+ if (--SCCs[p->tsi_SccIdx].count == 0) {
+ /* New SCC is ready, add this package as representative */
+ (void) rpmteSetParent(p->te, q->te);
+
+ if (outer_queue != NULL) {
+ addQ(p, outer_queue, outer_queue_end, prefcolor);
+ } else {
+ addQ(p, &q->tsi_suc, queue_end, prefcolor);
+ }
+ }
+ }
+ }
+ q->tsi_SccIdx = 0;
+}
+
+static void collectSCC(rpm_color_t prefcolor, tsortInfo p_tsi,
+ rpmte * newOrder, int * newOrderCount,
+ scc SCCs, tsortInfo * queue_end)
+{
+ int sccNr = p_tsi->tsi_SccIdx;
+ struct scc_s SCC = SCCs[sccNr];
+ int i;
+ int start, end;
+ relation rel;
+
+ /* remove p from the outer queue */
+ tsortInfo outer_queue_start = p_tsi->tsi_suc;
+ p_tsi->tsi_suc = NULL;
+
+ /*
+ * Run a multi source Dijkstra's algorithm to find relations
+ * that can be zapped with least danger to pre reqs.
+ * As weight of the edges is always 1 it is not necessary to
+ * sort the vertices by distance as the queue gets them
+ * already in order
+ */
+
+ /* can use a simple queue as edge weights are always 1 */
+ tsortInfo * queue = xmalloc((SCC.size+1) * sizeof(*queue));
+
+ /*
+ * Find packages that are prerequired and use them as
+ * starting points for the Dijkstra algorithm
+ */
+ start = end = 0;
+ for (i = 0; i < SCC.size; i++) {
+ tsortInfo tsi = SCC.members[i];
+ tsi->tsi_SccLowlink = INT_MAX;
+ for (rel=tsi->tsi_forward_relations; rel != NULL; rel=rel->rel_next) {
+ if (rel->rel_flags && rel->rel_suc->tsi_SccIdx == sccNr) {
+ if (rel->rel_suc != tsi) {
+ tsi->tsi_SccLowlink = 0;
+ queue[end++] = tsi;
+ } else {
+ tsi->tsi_SccLowlink = INT_MAX/2;
+ }
+ break;
+ }
+ }
+ }
+
+ if (start == end) { /* no regular prereqs; add self prereqs to queue */
+ for (i = 0; i < SCC.size; i++) {
+ tsortInfo tsi = SCC.members[i];
+ if (tsi->tsi_SccLowlink != INT_MAX) {
+ queue[end++] = tsi;
+ }
+ }
+ }
+
+ /* Do Dijkstra */
+ while (start != end) {
+ tsortInfo tsi = queue[start++];
+ for (rel=tsi->tsi_forward_relations; rel != NULL; rel=rel->rel_next) {
+ tsortInfo next_tsi = rel->rel_suc;
+ if (next_tsi->tsi_SccIdx != sccNr) continue;
+ if (next_tsi->tsi_SccLowlink > tsi->tsi_SccLowlink+1) {
+ next_tsi->tsi_SccLowlink = tsi->tsi_SccLowlink + 1;
+ queue[end++] = rel->rel_suc;
+ }
+ }
+ }
+ queue = _free(queue);
+
+
+ while (1) {
+ tsortInfo best = NULL;
+ tsortInfo inner_queue_start, inner_queue_end;
+ int best_score = 0;
+
+ /* select best candidate to start with */
+ for (int i = 0; i < SCC.size; i++) {
+ tsortInfo tsi = SCC.members[i];
+ if (tsi->tsi_SccIdx == 0) /* package already collected */
+ continue;
+ if (tsi->tsi_SccLowlink >= best_score) {
+ best = tsi;
+ best_score = tsi->tsi_SccLowlink;
+ }
+ }
+
+ if (best == NULL) /* done */
+ break;
+
+ /* collect best candidate and all packages that get freed */
+ inner_queue_start = inner_queue_end = NULL;
+ addQ(best, &inner_queue_start, &inner_queue_end, prefcolor);
+
+ for (; inner_queue_start != NULL;
+ inner_queue_start = inner_queue_start->tsi_suc) {
+ /* Mark the package as unqueued. */
+ inner_queue_start->tsi_reqx = 0;
+ collectTE(prefcolor, inner_queue_start, newOrder, newOrderCount,
+ SCCs, &inner_queue_end, &outer_queue_start, queue_end);
+ }
+ }
+
+ /* restore outer queue */
+ p_tsi->tsi_suc = outer_queue_start;
+}
+
+int rpmtsOrder(rpmts ts)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpm_color_t prefcolor = rpmtsPrefColor(ts);
+ rpmtsi pi; rpmte p;
+ tsortInfo q, r;
+ rpmte * newOrder;
+ int newOrderCount = 0;
+ int rc;
+ rpmal erasedPackages = rpmalCreate(5, rpmtsColor(ts), prefcolor);
+ scc SCCs;
+ int nelem = rpmtsNElements(ts);
+ tsortInfo sortInfo = xcalloc(nelem, sizeof(struct tsortInfo_s));
+ ARGV_t seenColls = NULL;
+
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_ORDER), 0);
+
+ /* Create erased package index. */
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, TR_REMOVED)) != NULL) {
+ rpmalAdd(erasedPackages, p);
+ }
+ pi = rpmtsiFree(pi);
+
+ for (int i = 0; i < nelem; i++) {
+ sortInfo[i].te = tsmem->order[i];
+ rpmteSetTSI(tsmem->order[i], &sortInfo[i]);
+ }
+
+ /* Record relations. */
+ rpmlog(RPMLOG_DEBUG, "========== recording tsort relations\n");
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ rpmal al = (rpmteType(p) == TR_REMOVED) ?
+ erasedPackages : tsmem->addedPackages;
+ rpmds requires = rpmdsInit(rpmteDS(p, RPMTAG_REQUIRENAME));
+ rpmds order = rpmdsInit(rpmteDS(p, RPMTAG_ORDERNAME));
+
+ while (rpmdsNext(requires) >= 0) {
+ /* Record next "q <- p" relation (i.e. "p" requires "q"). */
+ (void) addRelation(ts, al, p, requires);
+ }
+
+ while (rpmdsNext(order) >= 0) {
+ /* Record next "q <- p" ordering request */
+ (void) addRelation(ts, al, p, order);
+ }
+
+ addCollRelations(al, p, &seenColls);
+ }
+
+ seenColls = argvFree(seenColls);
+ pi = rpmtsiFree(pi);
+
+ newOrder = xcalloc(tsmem->orderCount, sizeof(*newOrder));
+ SCCs = detectSCCs(sortInfo, nelem, (rpmtsFlags(ts) & RPMTRANS_FLAG_DEPLOOPS));
+
+ rpmlog(RPMLOG_DEBUG, "========== tsorting packages (order, #predecessors, #succesors, depth)\n");
+
+ for (int i = 0; i < 2; i++) {
+ /* Do two separate runs: installs first - then erases */
+ int oType = !i ? TR_ADDED : TR_REMOVED;
+ q = r = NULL;
+ /* Scan for zeroes and add them to the queue */
+ for (int e = 0; e < nelem; e++) {
+ tsortInfo p = &sortInfo[e];
+ if (rpmteType(p->te) != oType) continue;
+ if (p->tsi_count != 0)
+ continue;
+ p->tsi_suc = NULL;
+ addQ(p, &q, &r, prefcolor);
+ }
+
+ /* Add one member of each leaf SCC */
+ for (int i = 2; SCCs[i].members != NULL; i++) {
+ tsortInfo member = SCCs[i].members[0];
+ if (SCCs[i].count == 0 && rpmteType(member->te) == oType) {
+ addQ(member, &q, &r, prefcolor);
+ }
+ }
+
+ while (q != NULL) {
+ /* Mark the package as unqueued. */
+ q->tsi_reqx = 0;
+ if (q->tsi_SccIdx > 1) {
+ collectSCC(prefcolor, q, newOrder, &newOrderCount, SCCs, &r);
+ } else {
+ collectTE(prefcolor, q, newOrder, &newOrderCount, SCCs, &r,
+ NULL, NULL);
+ }
+ q = q->tsi_suc;
+ }
+ }
+
+ /* Clean up tsort data */
+ for (int i = 0; i < nelem; i++) {
+ rpmteSetTSI(tsmem->order[i], NULL);
+ rpmTSIFree(&sortInfo[i]);
+ }
+ free(sortInfo);
+
+ assert(newOrderCount == tsmem->orderCount);
+
+ tsmem->order = _free(tsmem->order);
+ tsmem->order = newOrder;
+ tsmem->orderAlloced = tsmem->orderCount;
+ rc = 0;
+
+ for (int i = 2; SCCs[i].members != NULL; i++) {
+ free(SCCs[i].members);
+ }
+ free(SCCs);
+ rpmalFree(erasedPackages);
+
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_ORDER), 0);
+
+ return rc;
+}
diff --git a/lib/package.c b/lib/package.c
new file mode 100644
index 0000000..e1795dd
--- /dev/null
+++ b/lib/package.c
@@ -0,0 +1,807 @@
+/** \ingroup header
+ * \file lib/package.c
+ */
+
+#include "system.h"
+
+#include <netinet/in.h>
+
+#include <rpm/rpmlib.h> /* XXX RPMSIGTAG, other sig stuff */
+#include <rpm/rpmts.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmkeyring.h>
+
+#include "lib/rpmlead.h"
+#include "lib/signature.h"
+#include "rpmio/digest.h"
+#include "rpmio/rpmio_internal.h" /* fd digest bits */
+#include "lib/header_internal.h" /* XXX headerCheck */
+
+#include "debug.h"
+
+static int _print_pkts = 0;
+
+static const unsigned int nkeyids_max = 256;
+static unsigned int nkeyids = 0;
+static unsigned int nextkeyid = 0;
+static unsigned int * keyids;
+
+void headerMergeLegacySigs(Header h, const Header sigh)
+{
+ HeaderIterator hi;
+ struct rpmtd_s td;
+
+ hi = headerInitIterator(sigh);
+ for (; headerNext(hi, &td); rpmtdFreeData(&td))
+ {
+ switch (td.tag) {
+ /* XXX Translate legacy signature tag values. */
+ case RPMSIGTAG_SIZE:
+ td.tag = RPMTAG_SIGSIZE;
+ break;
+ case RPMSIGTAG_PGP:
+ td.tag = RPMTAG_SIGPGP;
+ break;
+ case RPMSIGTAG_MD5:
+ td.tag = RPMTAG_SIGMD5;
+ break;
+ case RPMSIGTAG_GPG:
+ td.tag = RPMTAG_SIGGPG;
+ break;
+ case RPMSIGTAG_PGP5:
+ td.tag = RPMTAG_SIGPGP5;
+ break;
+ case RPMSIGTAG_PAYLOADSIZE:
+ td.tag = RPMTAG_ARCHIVESIZE;
+ break;
+ case RPMSIGTAG_SHA1:
+ case RPMSIGTAG_DSA:
+ case RPMSIGTAG_RSA:
+ default:
+ if (!(td.tag >= HEADER_SIGBASE && td.tag < HEADER_TAGBASE))
+ continue;
+ break;
+ }
+ if (td.data == NULL) continue; /* XXX can't happen */
+ if (!headerIsEntry(h, td.tag)) {
+ if (hdrchkType(td.type))
+ continue;
+ if (td.count < 0 || hdrchkData(td.count))
+ continue;
+ switch(td.type) {
+ case RPM_NULL_TYPE:
+ continue;
+ break;
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ case RPM_INT16_TYPE:
+ case RPM_INT32_TYPE:
+ case RPM_INT64_TYPE:
+ if (td.count != 1)
+ continue;
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_BIN_TYPE:
+ if (td.count >= 16*1024)
+ continue;
+ break;
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ continue;
+ break;
+ }
+ (void) headerPut(h, &td, HEADERPUT_DEFAULT);
+ }
+ }
+ hi = headerFreeIterator(hi);
+}
+
+Header headerRegenSigHeader(const Header h, int noArchiveSize)
+{
+ Header sigh = rpmNewSignature();
+ HeaderIterator hi;
+ struct rpmtd_s td;
+
+ for (hi = headerInitIterator(h); headerNext(hi, &td); rpmtdFreeData(&td)) {
+ switch (td.tag) {
+ /* XXX Translate legacy signature tag values. */
+ case RPMTAG_SIGSIZE:
+ td.tag = RPMSIGTAG_SIZE;
+ break;
+ case RPMTAG_SIGPGP:
+ td.tag = RPMSIGTAG_PGP;
+ break;
+ case RPMTAG_SIGMD5:
+ td.tag = RPMSIGTAG_MD5;
+ break;
+ case RPMTAG_SIGGPG:
+ td.tag = RPMSIGTAG_GPG;
+ break;
+ case RPMTAG_SIGPGP5:
+ td.tag = RPMSIGTAG_PGP5;
+ break;
+ case RPMTAG_ARCHIVESIZE:
+ /* XXX rpm-4.1 and later has archive size in signature header. */
+ if (noArchiveSize)
+ continue;
+ td.tag = RPMSIGTAG_PAYLOADSIZE;
+ break;
+ case RPMTAG_SHA1HEADER:
+ case RPMTAG_DSAHEADER:
+ case RPMTAG_RSAHEADER:
+ default:
+ if (!(td.tag >= HEADER_SIGBASE && td.tag < HEADER_TAGBASE))
+ continue;
+ break;
+ }
+ if (td.data == NULL) continue; /* XXX can't happen */
+ if (!headerIsEntry(sigh, td.tag))
+ (void) headerPut(sigh, &td, HEADERPUT_DEFAULT);
+ }
+ hi = headerFreeIterator(hi);
+ return sigh;
+}
+
+/**
+ * Remember current key id.
+ * @param dig OpenPGP packet containter
+ * @return 0 if new keyid, otherwise 1
+ */
+static int stashKeyid(pgpDig dig)
+{
+ pgpDigParams sigp = dig ? &dig->signature : NULL;
+ unsigned int keyid;
+ int i;
+
+ if (dig == NULL || sigp == NULL)
+ return 0;
+
+ keyid = pgpGrab(sigp->signid+4, 4);
+ if (keyid == 0)
+ return 0;
+
+ if (keyids != NULL)
+ for (i = 0; i < nkeyids; i++) {
+ if (keyid == keyids[i])
+ return 1;
+ }
+
+ if (nkeyids < nkeyids_max) {
+ nkeyids++;
+ keyids = xrealloc(keyids, nkeyids * sizeof(*keyids));
+ }
+ if (keyids) /* XXX can't happen */
+ keyids[nextkeyid] = keyid;
+ nextkeyid++;
+ nextkeyid %= nkeyids_max;
+
+ return 0;
+}
+
+/* Parse the parameters from the OpenPGP packets that will be needed. */
+static rpmRC parsePGP(rpmtd sigtd, const char *type, pgpDig dig)
+{
+ rpmRC rc = RPMRC_FAIL;
+ int debug = (_print_pkts & rpmIsDebug());
+ if ((pgpPrtPkts(sigtd->data, sigtd->count, dig, debug) == 0) &&
+ (dig->signature.version == 3 || dig->signature.version == 4)) {
+ rc = RPMRC_OK;
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("skipping %s with unverifiable V%u signature\n"), type,
+ dig->signature.version);
+ }
+ return rc;
+}
+
+static rpmRC headerVerify(rpmKeyring keyring, rpmVSFlags vsflags,
+ const void * uh, size_t uc, char ** msg)
+{
+ pgpDig dig = NULL;
+ char *buf = NULL;
+ int32_t * ei = (int32_t *) uh;
+ int32_t il = ntohl(ei[0]);
+ int32_t dl = ntohl(ei[1]);
+ entryInfo pe = (entryInfo) &ei[2];
+ int32_t ildl[2];
+ int32_t pvlen = sizeof(ildl) + (il * sizeof(*pe)) + dl;
+ unsigned char * dataStart = (unsigned char *) (pe + il);
+ struct indexEntry_s entry;
+ struct entryInfo_s info;
+ unsigned const char * b;
+ size_t siglen = 0;
+ size_t blen;
+ size_t nb;
+ int32_t ril = 0;
+ unsigned char * regionEnd = NULL;
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ int xx;
+ int i;
+ struct rpmtd_s sigtd;
+ DIGEST_CTX ctx = NULL;
+
+ /* Is the blob the right size? */
+ if (uc > 0 && pvlen != uc) {
+ rasprintf(&buf, _("blob size(%d): BAD, 8 + 16 * il(%d) + dl(%d)\n"),
+ (int)uc, (int)il, (int)dl);
+ goto exit;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ memset(&info, 0, sizeof(info));
+
+ /* Check (and convert) the 1st tag element. */
+ xx = headerVerifyInfo(1, dl, pe, &entry.info, 0);
+ if (xx != -1) {
+ rasprintf(&buf, _("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
+ 0, entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+
+ /* Is there an immutable header region tag? */
+ if (!(entry.info.tag == RPMTAG_HEADERIMMUTABLE
+ && entry.info.type == RPM_BIN_TYPE
+ && entry.info.count == REGION_TAG_COUNT))
+ {
+ rc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ /* Is the offset within the data area? */
+ if (entry.info.offset >= dl) {
+ rasprintf(&buf,
+ _("region offset: BAD, tag %d type %d offset %d count %d\n"),
+ entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+
+ /* Is there an immutable header region tag trailer? */
+ regionEnd = dataStart + entry.info.offset;
+ (void) memcpy(&info, regionEnd, REGION_TAG_COUNT);
+ regionEnd += REGION_TAG_COUNT;
+
+ xx = headerVerifyInfo(1, dl, &info, &entry.info, 1);
+ if (xx != -1 ||
+ !(entry.info.tag == RPMTAG_HEADERIMMUTABLE
+ && entry.info.type == RPM_BIN_TYPE
+ && entry.info.count == REGION_TAG_COUNT))
+ {
+ rasprintf(&buf,
+ _("region trailer: BAD, tag %d type %d offset %d count %d\n"),
+ entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+ memset(&info, 0, sizeof(info));
+
+ /* Is the no. of tags in the region less than the total no. of tags? */
+ ril = entry.info.offset/sizeof(*pe);
+ if ((entry.info.offset % sizeof(*pe)) || ril > il) {
+ rasprintf(&buf, _("region size: BAD, ril(%d) > il(%d)\n"), ril, il);
+ goto exit;
+ }
+
+ /* Find a header-only digest/signature tag. */
+ for (i = ril; i < il; i++) {
+ xx = headerVerifyInfo(1, dl, pe+i, &entry.info, 0);
+ if (xx != -1) {
+ rasprintf(&buf,
+ _("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
+ i, entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+
+ switch (entry.info.tag) {
+ case RPMTAG_SHA1HEADER:
+ if (vsflags & RPMVSF_NOSHA1HEADER)
+ break;
+ blen = 0;
+ for (b = dataStart + entry.info.offset; *b != '\0'; b++) {
+ if (strchr("0123456789abcdefABCDEF", *b) == NULL)
+ break;
+ blen++;
+ }
+ if (entry.info.type != RPM_STRING_TYPE || *b != '\0' || blen != 40)
+ {
+ rasprintf(&buf, _("hdr SHA1: BAD, not hex\n"));
+ goto exit;
+ }
+ if (info.tag == 0) {
+ info = entry.info; /* structure assignment */
+ siglen = blen + 1;
+ }
+ break;
+ case RPMTAG_RSAHEADER:
+ if (vsflags & RPMVSF_NORSAHEADER)
+ break;
+ if (entry.info.type != RPM_BIN_TYPE) {
+ rasprintf(&buf, _("hdr RSA: BAD, not binary\n"));
+ goto exit;
+ }
+ info = entry.info; /* structure assignment */
+ siglen = info.count;
+ break;
+ case RPMTAG_DSAHEADER:
+ if (vsflags & RPMVSF_NODSAHEADER)
+ break;
+ if (entry.info.type != RPM_BIN_TYPE) {
+ rasprintf(&buf, _("hdr DSA: BAD, not binary\n"));
+ goto exit;
+ }
+ info = entry.info; /* structure assignment */
+ siglen = info.count;
+ break;
+ default:
+ break;
+ }
+ }
+ rc = RPMRC_NOTFOUND;
+
+exit:
+ /* Return determined RPMRC_OK/RPMRC_FAIL conditions. */
+ if (rc != RPMRC_NOTFOUND) {
+ if (msg)
+ *msg = buf;
+ else
+ free(buf);
+ return rc;
+ }
+
+ /* If no header-only digest/signature, then do simple sanity check. */
+ if (info.tag == 0) {
+verifyinfo_exit:
+ xx = headerVerifyInfo(ril-1, dl, pe+1, &entry.info, 0);
+ if (xx != -1) {
+ rasprintf(&buf,
+ _("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
+ xx+1, entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ rc = RPMRC_FAIL;
+ } else {
+ rasprintf(&buf, "Header sanity check: OK\n");
+ rc = RPMRC_OK;
+ }
+ if (msg)
+ *msg = buf;
+ else
+ free(buf);
+ return rc;
+ }
+
+ /* Verify header-only digest/signature. */
+ dig = pgpNewDig();
+ if (dig == NULL)
+ goto verifyinfo_exit;
+
+ sigtd.tag = info.tag;
+ sigtd.type = info.type;
+ sigtd.count = info.count;
+ sigtd.data = memcpy(xmalloc(siglen), dataStart + info.offset, siglen);
+ sigtd.flags = RPMTD_ALLOCED;
+
+ switch (info.tag) {
+ case RPMTAG_RSAHEADER:
+ case RPMTAG_DSAHEADER:
+ if ((rc = parsePGP(&sigtd, "header", dig)) != RPMRC_OK) {
+ pgpFreeDig(dig);
+ goto exit;
+ }
+ /* fallthrough */
+ case RPMTAG_SHA1HEADER: {
+ int hashalgo = (info.tag == RPMTAG_SHA1HEADER) ?
+ PGPHASHALGO_SHA1 : dig->signature.hash_algo;
+ ildl[0] = htonl(ril);
+ ildl[1] = (regionEnd - dataStart);
+ ildl[1] = htonl(ildl[1]);
+
+ ctx = rpmDigestInit(hashalgo, RPMDIGEST_NONE);
+
+ b = (unsigned char *) rpm_header_magic;
+ nb = sizeof(rpm_header_magic);
+ (void) rpmDigestUpdate(ctx, b, nb);
+
+ b = (unsigned char *) ildl;
+ nb = sizeof(ildl);
+ (void) rpmDigestUpdate(ctx, b, nb);
+
+ b = (unsigned char *) pe;
+ nb = (htonl(ildl[0]) * sizeof(*pe));
+ (void) rpmDigestUpdate(ctx, b, nb);
+
+ b = (unsigned char *) dataStart;
+ nb = htonl(ildl[1]);
+ (void) rpmDigestUpdate(ctx, b, nb);
+ } break;
+ default:
+ sigtd.data = _free(sigtd.data); /* Hmm...? */
+ break;
+ }
+
+ rc = rpmVerifySignature(keyring, &sigtd, dig, ctx, &buf);
+
+ if (msg)
+ *msg = buf;
+ else
+ free(buf);
+
+ rpmtdFreeData(&sigtd);
+ pgpFreeDig(dig);
+ rpmDigestFinal(ctx, NULL, NULL, 0);
+ return rc;
+}
+
+rpmRC headerCheck(rpmts ts, const void * uh, size_t uc, char ** msg)
+{
+ rpmRC rc;
+ rpmVSFlags vsflags = rpmtsVSFlags(ts);
+ rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
+
+ rpmswEnter(rpmtsOp(ts, RPMTS_OP_DIGEST), 0);
+ rc = headerVerify(keyring, vsflags, uh, uc, msg);
+ rpmswExit(rpmtsOp(ts, RPMTS_OP_DIGEST), uc);
+ rpmKeyringFree(keyring);
+
+ return rc;
+}
+
+static rpmRC rpmpkgReadHeader(rpmKeyring keyring, rpmVSFlags vsflags,
+ FD_t fd, Header *hdrp, char ** msg)
+{
+ char *buf = NULL;
+ int32_t block[4];
+ int32_t il;
+ int32_t dl;
+ int32_t * ei = NULL;
+ size_t uc;
+ size_t nb;
+ Header h = NULL;
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ int xx;
+
+ if (hdrp)
+ *hdrp = NULL;
+ if (msg)
+ *msg = NULL;
+
+ memset(block, 0, sizeof(block));
+ if ((xx = timedRead(fd, (char *)block, sizeof(block))) != sizeof(block)) {
+ rasprintf(&buf,
+ _("hdr size(%d): BAD, read returned %d\n"), (int)sizeof(block), xx);
+ goto exit;
+ }
+ if (memcmp(block, rpm_header_magic, sizeof(rpm_header_magic))) {
+ rasprintf(&buf, _("hdr magic: BAD\n"));
+ goto exit;
+ }
+ il = ntohl(block[2]);
+ if (hdrchkTags(il)) {
+ rasprintf(&buf, _("hdr tags: BAD, no. of tags(%d) out of range\n"), il);
+ goto exit;
+ }
+ dl = ntohl(block[3]);
+ if (hdrchkData(dl)) {
+ rasprintf(&buf,
+ _("hdr data: BAD, no. of bytes(%d) out of range\n"), dl);
+ goto exit;
+ }
+
+ nb = (il * sizeof(struct entryInfo_s)) + dl;
+ uc = sizeof(il) + sizeof(dl) + nb;
+ ei = xmalloc(uc);
+ ei[0] = block[2];
+ ei[1] = block[3];
+ if ((xx = timedRead(fd, (char *)&ei[2], nb)) != nb) {
+ rasprintf(&buf, _("hdr blob(%zd): BAD, read returned %d\n"), nb, xx);
+ goto exit;
+ }
+
+ /* Sanity check header tags */
+ rc = headerVerify(keyring, vsflags, ei, uc, msg);
+ if (rc != RPMRC_OK)
+ goto exit;
+
+ /* OK, blob looks sane, load the header. */
+ h = headerLoad(ei);
+ if (h == NULL) {
+ rasprintf(&buf, _("hdr load: BAD\n"));
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+ ei = NULL; /* XXX will be freed with header */
+
+exit:
+ if (hdrp && h && rc == RPMRC_OK)
+ *hdrp = headerLink(h);
+ ei = _free(ei);
+ h = headerFree(h);
+
+ if (msg != NULL && *msg == NULL && buf != NULL) {
+ *msg = buf;
+ } else {
+ free(buf);
+ }
+
+ return rc;
+}
+
+rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg)
+{
+ rpmRC rc;
+ rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
+ rpmVSFlags vsflags = rpmtsVSFlags(ts);
+
+ rc = rpmpkgReadHeader(keyring, vsflags, fd, hdrp, msg);
+
+ rpmKeyringFree(keyring);
+ return rc;
+}
+
+static rpmRC rpmpkgRead(rpmKeyring keyring, rpmVSFlags vsflags,
+ FD_t fd, const char * fn, Header * hdrp)
+{
+ pgpDig dig = NULL;
+ char buf[8*BUFSIZ];
+ ssize_t count;
+ rpmlead l = NULL;
+ Header sigh = NULL;
+ rpmTagVal sigtag;
+ struct rpmtd_s sigtd;
+ Header h = NULL;
+ char * msg;
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ int leadtype = -1;
+ headerGetFlags hgeflags = HEADERGET_DEFAULT;
+ DIGEST_CTX ctx = NULL;
+
+ if (hdrp) *hdrp = NULL;
+
+ rpmtdReset(&sigtd);
+ l = rpmLeadNew();
+
+ if ((rc = rpmLeadRead(fd, l)) == RPMRC_OK) {
+ const char * err = NULL;
+ if ((rc = rpmLeadCheck(l, &err)) == RPMRC_FAIL) {
+ rpmlog(RPMLOG_ERR, "%s: %s\n", fn, err);
+ }
+ leadtype = rpmLeadType(l);
+ }
+ l = rpmLeadFree(l);
+
+ if (rc != RPMRC_OK)
+ goto exit;
+
+ /* Read the signature header. */
+ msg = NULL;
+ rc = rpmReadSignature(fd, &sigh, RPMSIGTYPE_HEADERSIG, &msg);
+ switch (rc) {
+ default:
+ rpmlog(RPMLOG_ERR, _("%s: rpmReadSignature failed: %s"), fn,
+ (msg && *msg ? msg : "\n"));
+ msg = _free(msg);
+ goto exit;
+ break;
+ case RPMRC_OK:
+ if (sigh == NULL) {
+ rpmlog(RPMLOG_ERR, _("%s: No signature available\n"), fn);
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+ break;
+ }
+ msg = _free(msg);
+
+#define _chk(_mask, _tag) \
+ (sigtag == 0 && !(vsflags & (_mask)) && headerIsEntry(sigh, (_tag)))
+
+ /*
+ * Figger the most effective available signature.
+ * Prefer signatures over digests, then header-only over header+payload.
+ * DSA will be preferred over RSA if both exist because tested first.
+ * Note that NEEDPAYLOAD prevents header+payload signatures and digests.
+ */
+ sigtag = 0;
+ if (_chk(RPMVSF_NODSAHEADER, RPMSIGTAG_DSA)) {
+ sigtag = RPMSIGTAG_DSA;
+ } else if (_chk(RPMVSF_NORSAHEADER, RPMSIGTAG_RSA)) {
+ sigtag = RPMSIGTAG_RSA;
+ } else if (_chk(RPMVSF_NODSA|RPMVSF_NEEDPAYLOAD, RPMSIGTAG_GPG)) {
+ sigtag = RPMSIGTAG_GPG;
+ fdInitDigest(fd, PGPHASHALGO_SHA1, 0);
+ } else if (_chk(RPMVSF_NORSA|RPMVSF_NEEDPAYLOAD, RPMSIGTAG_PGP)) {
+ sigtag = RPMSIGTAG_PGP;
+ fdInitDigest(fd, PGPHASHALGO_MD5, 0);
+ } else if (_chk(RPMVSF_NOSHA1HEADER, RPMSIGTAG_SHA1)) {
+ sigtag = RPMSIGTAG_SHA1;
+ } else if (_chk(RPMVSF_NOMD5|RPMVSF_NEEDPAYLOAD, RPMSIGTAG_MD5)) {
+ sigtag = RPMSIGTAG_MD5;
+ fdInitDigest(fd, PGPHASHALGO_MD5, 0);
+ }
+
+ /* Read the metadata, computing digest(s) on the fly. */
+ h = NULL;
+ msg = NULL;
+
+ rc = rpmpkgReadHeader(keyring, vsflags, fd, &h, &msg);
+
+ if (rc != RPMRC_OK || h == NULL) {
+ rpmlog(RPMLOG_ERR, _("%s: headerRead failed: %s"), fn,
+ (msg && *msg ? msg : "\n"));
+ msg = _free(msg);
+ goto exit;
+ }
+ msg = _free(msg);
+
+ /* Any digests or signatures to check? */
+ if (sigtag == 0) {
+ rc = RPMRC_OK;
+ goto exit;
+ }
+
+ dig = pgpNewDig();
+ if (dig == NULL) {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+
+ /* Retrieve the tag parameters from the signature header. */
+ if (!headerGet(sigh, sigtag, &sigtd, hgeflags)) {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+
+ switch (sigtag) {
+ case RPMSIGTAG_RSA:
+ case RPMSIGTAG_DSA:
+ if ((rc = parsePGP(&sigtd, "package", dig)) != RPMRC_OK) {
+ goto exit;
+ }
+ /* fallthrough */
+ case RPMSIGTAG_SHA1:
+ { struct rpmtd_s utd;
+ int hashalgo = (sigtag == RPMSIGTAG_SHA1) ?
+ PGPHASHALGO_SHA1 : dig->signature.hash_algo;
+
+ if (!headerGet(h, RPMTAG_HEADERIMMUTABLE, &utd, hgeflags))
+ break;
+ ctx = rpmDigestInit(hashalgo, RPMDIGEST_NONE);
+ (void) rpmDigestUpdate(ctx, rpm_header_magic, sizeof(rpm_header_magic));
+ (void) rpmDigestUpdate(ctx, utd.data, utd.count);
+ rpmtdFreeData(&utd);
+ } break;
+ case RPMSIGTAG_GPG:
+ case RPMSIGTAG_PGP5: /* XXX legacy */
+ case RPMSIGTAG_PGP:
+ if ((rc = parsePGP(&sigtd, "package", dig)) != RPMRC_OK) {
+ goto exit;
+ }
+ /* fallthrough */
+ case RPMSIGTAG_MD5:
+ /* Legacy signatures need the compressed payload in the digest too. */
+ while ((count = Fread(buf, sizeof(buf[0]), sizeof(buf), fd)) > 0) {}
+ if (count < 0) {
+ rpmlog(RPMLOG_ERR, _("%s: Fread failed: %s\n"),
+ fn, Fstrerror(fd));
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+
+ ctx = rpmDigestBundleDupCtx(fdGetBundle(fd), (sigtag == RPMSIGTAG_MD5) ?
+ PGPHASHALGO_MD5 : dig->signature.hash_algo);
+ break;
+ default:
+ break;
+ }
+
+ /** @todo Implement disable/enable/warn/error/anal policy. */
+ rc = rpmVerifySignature(keyring, &sigtd, dig, ctx, &msg);
+
+ switch (rc) {
+ case RPMRC_OK: /* Signature is OK. */
+ rpmlog(RPMLOG_DEBUG, "%s: %s", fn, msg);
+ break;
+ case RPMRC_NOTTRUSTED: /* Signature is OK, but key is not trusted. */
+ case RPMRC_NOKEY: /* Public key is unavailable. */
+ /* XXX Print NOKEY/NOTTRUSTED warning only once. */
+ { int lvl = (stashKeyid(dig) ? RPMLOG_DEBUG : RPMLOG_WARNING);
+ rpmlog(lvl, "%s: %s", fn, msg);
+ } break;
+ case RPMRC_NOTFOUND: /* Signature is unknown type. */
+ rpmlog(RPMLOG_WARNING, "%s: %s", fn, msg);
+ break;
+ default:
+ case RPMRC_FAIL: /* Signature does not verify. */
+ rpmlog(RPMLOG_ERR, "%s: %s", fn, msg);
+ break;
+ }
+ free(msg);
+
+exit:
+ if (rc != RPMRC_FAIL && h != NULL && hdrp != NULL) {
+ /* Retrofit RPMTAG_SOURCEPACKAGE to srpms for compatibility */
+ if (leadtype == RPMLEAD_SOURCE && headerIsSource(h)) {
+ if (!headerIsEntry(h, RPMTAG_SOURCEPACKAGE)) {
+ uint32_t one = 1;
+ headerPutUint32(h, RPMTAG_SOURCEPACKAGE, &one, 1);
+ }
+ }
+ /*
+ * Try to make sure binary rpms have RPMTAG_SOURCERPM set as that's
+ * what we use for differentiating binary vs source elsewhere.
+ */
+ if (!headerIsEntry(h, RPMTAG_SOURCEPACKAGE) && headerIsSource(h)) {
+ headerPutString(h, RPMTAG_SOURCERPM, "(none)");
+ }
+ /*
+ * Convert legacy headers on the fly. Not having "new" style compressed
+ * filenames is close enough estimate for legacy indication...
+ */
+ if (!headerIsEntry(h, RPMTAG_DIRNAMES)) {
+ headerConvert(h, HEADERCONV_RETROFIT_V3);
+ }
+
+ /* Append (and remap) signature tags to the metadata. */
+ headerMergeLegacySigs(h, sigh);
+
+ /* Bump reference count for return. */
+ *hdrp = headerLink(h);
+ }
+ rpmtdFreeData(&sigtd);
+ rpmDigestFinal(ctx, NULL, NULL, 0);
+ h = headerFree(h);
+ pgpFreeDig(dig);
+ sigh = rpmFreeSignature(sigh);
+ return rc;
+}
+
+rpmRC rpmReadPackageFile(rpmts ts, FD_t fd, const char * fn, Header * hdrp)
+{
+ rpmRC rc;
+ rpmVSFlags vsflags = rpmtsVSFlags(ts);
+ rpmKeyring keyring = 0;
+
+ if ((vsflags & _RPMVSF_NOSIGNATURES) != _RPMVSF_NOSIGNATURES)
+ keyring = rpmtsGetKeyring(ts, 1);
+
+ rc = rpmpkgRead(keyring, vsflags, fd, fn, hdrp);
+
+ if (keyring)
+ rpmKeyringFree(keyring);
+ return rc;
+}
+
+/**
+ * Check for supported payload format in header.
+ * @param h header to check
+ * @return RPMRC_OK if supported, RPMRC_FAIL otherwise
+ */
+rpmRC headerCheckPayloadFormat(Header h) {
+ rpmRC rc = RPMRC_OK;
+ const char *payloadfmt = headerGetString(h, RPMTAG_PAYLOADFORMAT);
+ /*
+ * XXX Ugh, rpm 3.x packages don't have payload format tag. Instead
+ * of blinly allowing, should check somehow (HDRID existence or... ?)
+ */
+ if (!payloadfmt) return rc;
+
+ if (!rstreq(payloadfmt, "cpio")) {
+ char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
+ if (payloadfmt && rstreq(payloadfmt, "drpm")) {
+ rpmlog(RPMLOG_ERR,
+ _("%s is a Delta RPM and cannot be directly installed\n"),
+ nevra);
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("Unsupported payload (%s) in package %s\n"),
+ payloadfmt ? payloadfmt : "none", nevra);
+ }
+ nevra = _free(nevra);
+ rc = RPMRC_FAIL;
+ }
+ return rc;
+}
+
+
diff --git a/lib/poptALL.c b/lib/poptALL.c
new file mode 100644
index 0000000..474af33
--- /dev/null
+++ b/lib/poptALL.c
@@ -0,0 +1,316 @@
+/** \ingroup rpmcli
+ * \file lib/poptALL.c
+ * Popt tables for all rpm modes.
+ */
+
+#include "system.h"
+const char *__progname;
+
+#if HAVE_MCHECK_H
+#include <mcheck.h>
+#endif
+
+#include <rpm/rpmcli.h>
+#include <rpm/rpmlib.h> /* rpmEVR, rpmReadConfigFiles etc */
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmfileutil.h>
+
+#include "debug.h"
+
+#define POPT_SHOWVERSION -999
+#define POPT_SHOWRC -998
+#define POPT_QUERYTAGS -997
+#define POPT_PREDEFINE -996
+#define POPT_DBPATH -995
+
+static int _debug = 0;
+
+extern int _rpmds_nopromote;
+
+extern int _fsm_debug;
+
+extern int _print_pkts;
+
+extern int _psm_debug;
+
+/* XXX avoid -lrpmbuild linkage. */
+ int _rpmfc_debug;
+
+extern int _rpmts_stats;
+
+const char * rpmcliPipeOutput = NULL;
+
+const char * rpmcliRcfile = NULL;
+
+const char * rpmcliRootDir = "/";
+
+rpmQueryFlags rpmcliQueryFlags;
+
+extern int _rpmio_debug;
+
+static int rpmcliInitialized = -1;
+
+/**
+ * Display rpm version.
+ */
+static void printVersion(FILE * fp)
+{
+ fprintf(fp, _("RPM version %s\n"), rpmEVR);
+}
+
+/**
+ * Make sure that config files have been read.
+ * @warning Options like --rcfile and --verbose must precede callers option.
+ */
+void rpmcliConfigured(void)
+{
+
+ if (rpmcliInitialized < 0)
+ rpmcliInitialized = rpmReadConfigFiles(rpmcliRcfile, NULL);
+ if (rpmcliInitialized)
+ exit(EXIT_FAILURE);
+}
+
+/**
+ */
+static void rpmcliAllArgCallback( poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption * opt, const char * arg,
+ const void * data)
+{
+
+ /* XXX avoid accidental collisions with POPT_BIT_SET for flags */
+ if (opt->arg == NULL)
+ switch (opt->val) {
+ case 'q':
+ rpmSetVerbosity(RPMLOG_WARNING);
+ break;
+ case 'v':
+ rpmIncreaseVerbosity();
+ break;
+ case POPT_PREDEFINE:
+ (void) rpmDefineMacro(NULL, arg, RMIL_CMDLINE);
+ break;
+ case 'D':
+ { char *s, *t;
+ /* XXX Convert '-' in macro name to underscore, skip leading %. */
+ s = t = xstrdup(arg);
+ while (*t && !risspace(*t)) {
+ if (*t == '-') *t = '_';
+ t++;
+ }
+ t = s;
+ if (*t == '%') t++;
+ /* XXX Predefine macro if not initialized yet. */
+ if (rpmcliInitialized < 0)
+ (void) rpmDefineMacro(NULL, t, RMIL_CMDLINE);
+ rpmcliConfigured();
+ (void) rpmDefineMacro(NULL, t, RMIL_CMDLINE);
+ (void) rpmDefineMacro(rpmCLIMacroContext, t, RMIL_CMDLINE);
+ s = _free(s);
+ break;
+ }
+ case 'E':
+ rpmcliConfigured();
+ { char *val = rpmExpand(arg, NULL);
+ fprintf(stdout, "%s\n", val);
+ val = _free(val);
+ }
+ break;
+ case POPT_DBPATH:
+ rpmcliConfigured();
+ addMacro(NULL, "_dbpath", NULL, arg, RMIL_CMDLINE);
+ break;
+ case POPT_SHOWVERSION:
+ printVersion(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case POPT_SHOWRC:
+ rpmcliConfigured();
+ (void) rpmShowRC(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case POPT_QUERYTAGS:
+ rpmDisplayQueryTags(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case RPMCLI_POPT_NODIGEST:
+ rpmcliQueryFlags |= VERIFY_DIGEST;
+ break;
+
+ case RPMCLI_POPT_NOSIGNATURE:
+ rpmcliQueryFlags |= VERIFY_SIGNATURE;
+ break;
+
+ case RPMCLI_POPT_NOHDRCHK:
+ rpmcliQueryFlags |= VERIFY_HDRCHK;
+ break;
+ }
+}
+
+struct poptOption rpmcliAllPoptTable[] = {
+/* FIX: cast? */
+ { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA | POPT_CBFLAG_CONTINUE,
+ rpmcliAllArgCallback, 0, NULL, NULL },
+
+ { "debug", 'd', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_debug, -1,
+ NULL, NULL },
+
+ { "predefine", '\0', POPT_ARG_STRING|POPT_ARGFLAG_DOC_HIDDEN, 0, POPT_PREDEFINE,
+ N_("predefine MACRO with value EXPR"),
+ N_("'MACRO EXPR'") },
+ { "define", 'D', POPT_ARG_STRING, 0, 'D',
+ N_("define MACRO with value EXPR"),
+ N_("'MACRO EXPR'") },
+ { "eval", 'E', POPT_ARG_STRING, 0, 'E',
+ N_("print macro expansion of EXPR"),
+ N_("'EXPR'") },
+ { "macros", '\0', POPT_ARG_STRING, &macrofiles, 0,
+ N_("read <FILE:...> instead of default file(s)"),
+ N_("<FILE:...>") },
+
+ { "nodigest", '\0', 0, 0, RPMCLI_POPT_NODIGEST,
+ N_("don't verify package digest(s)"), NULL },
+ { "nohdrchk", '\0', POPT_ARGFLAG_DOC_HIDDEN, 0, RPMCLI_POPT_NOHDRCHK,
+ N_("don't verify database header(s) when retrieved"), NULL },
+ { "nosignature", '\0', 0, 0, RPMCLI_POPT_NOSIGNATURE,
+ N_("don't verify package signature(s)"), NULL },
+
+ { "pipe", '\0', POPT_ARG_STRING|POPT_ARGFLAG_DOC_HIDDEN, &rpmcliPipeOutput, 0,
+ N_("send stdout to CMD"),
+ N_("CMD") },
+ { "rcfile", '\0', POPT_ARG_STRING, &rpmcliRcfile, 0,
+ N_("read <FILE:...> instead of default file(s)"),
+ N_("<FILE:...>") },
+ { "root", 'r', POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT, &rpmcliRootDir, 0,
+ N_("use ROOT as top level directory"),
+ N_("ROOT") },
+ { "dbpath", '\0', POPT_ARG_STRING, 0, POPT_DBPATH,
+ N_("use database in DIRECTORY"),
+ N_("DIRECTORY") },
+
+ { "querytags", '\0', 0, 0, POPT_QUERYTAGS,
+ N_("display known query tags"), NULL },
+ { "showrc", '\0', 0, NULL, POPT_SHOWRC,
+ N_("display final rpmrc and macro configuration"), NULL },
+ { "quiet", '\0', 0, NULL, 'q',
+ N_("provide less detailed output"), NULL},
+ { "verbose", 'v', 0, NULL, 'v',
+ N_("provide more detailed output"), NULL},
+ { "version", '\0', 0, NULL, POPT_SHOWVERSION,
+ N_("print the version of rpm being used"), NULL },
+
+ { "promoteepoch", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_rpmds_nopromote, 0,
+ NULL, NULL},
+
+ { "fsmdebug", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_fsm_debug, -1,
+ N_("debug payload file state machine"), NULL},
+ { "prtpkts", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_print_pkts, -1,
+ NULL, NULL},
+ { "rpmfcdebug", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_rpmfc_debug, -1,
+ NULL, NULL},
+ { "rpmiodebug", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_rpmio_debug, -1,
+ N_("debug rpmio I/O"), NULL},
+ { "stats", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &_rpmts_stats, -1,
+ NULL, NULL},
+
+ POPT_TABLEEND
+};
+
+poptContext
+rpmcliFini(poptContext optCon)
+{
+ poptFreeContext(optCon);
+ rpmFreeMacros(NULL);
+ rpmFreeMacros(rpmCLIMacroContext);
+ rpmFreeRpmrc();
+ rpmlogClose();
+ rpmcliInitialized = -1;
+
+#if HAVE_MCHECK_H && HAVE_MTRACE
+ muntrace(); /* Trace malloc only if MALLOC_TRACE=mtrace-output-file. */
+#endif
+
+ return NULL;
+}
+
+poptContext
+rpmcliInit(int argc, char *const argv[], struct poptOption * optionsTable)
+{
+ const char * optArg;
+ poptContext optCon;
+ int rc;
+ const char *ctx, *execPath;
+
+#if HAVE_MCHECK_H && HAVE_MTRACE
+ mtrace(); /* Trace malloc only if MALLOC_TRACE=mtrace-output-file. */
+#endif
+ setprogname(argv[0]); /* Retrofit glibc __progname */
+
+ /* XXX glibc churn sanity */
+ if (__progname == NULL) {
+ if ((__progname = strrchr(argv[0], '/')) != NULL) __progname++;
+ else __progname = argv[0];
+ }
+
+#if defined(ENABLE_NLS)
+ (void) setlocale(LC_ALL, "" );
+
+ (void) bindtextdomain(PACKAGE, LOCALEDIR);
+ (void) textdomain(PACKAGE);
+#endif
+
+ rpmSetVerbosity(RPMLOG_NOTICE);
+
+ if (optionsTable == NULL) {
+ /* Read rpm configuration (if not already read). */
+ rpmcliConfigured();
+ return NULL;
+ }
+
+ /* XXX hack to get popt working from build tree wrt lt-foo names */
+ ctx = rstreqn(__progname, "lt-", 3) ? __progname + 3 : __progname;
+
+ optCon = poptGetContext(ctx, argc, (const char **)argv, optionsTable, 0);
+ {
+ char *poptfile = rpmGenPath(rpmConfigDir(), LIBRPMALIAS_FILENAME, NULL);
+ (void) poptReadConfigFile(optCon, poptfile);
+ free(poptfile);
+ }
+ (void) poptReadDefaultConfig(optCon, 1);
+
+ if ((execPath = getenv("RPM_POPTEXEC_PATH")) == NULL)
+ execPath = LIBRPMALIAS_EXECPATH;
+ poptSetExecPath(optCon, execPath, 1);
+
+ /* Process all options, whine if unknown. */
+ while ((rc = poptGetNextOpt(optCon)) > 0) {
+ optArg = poptGetOptArg(optCon);
+ switch (rc) {
+ default:
+ fprintf(stderr, _("%s: option table misconfigured (%d)\n"),
+ __progname, rc);
+ exit(EXIT_FAILURE);
+
+ break;
+ }
+ }
+
+ if (rc < -1) {
+ fprintf(stderr, "%s: %s: %s\n", __progname,
+ poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
+ poptStrerror(rc));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Read rpm configuration (if not already read). */
+ rpmcliConfigured();
+
+ if (_debug) {
+ rpmIncreaseVerbosity();
+ rpmIncreaseVerbosity();
+ }
+
+ return optCon;
+}
diff --git a/lib/poptI.c b/lib/poptI.c
new file mode 100644
index 0000000..8c1ff6e
--- /dev/null
+++ b/lib/poptI.c
@@ -0,0 +1,252 @@
+/** \ingroup rpmcli
+ * \file lib/poptI.c
+ * Popt tables for install modes.
+ */
+
+#include "system.h"
+
+#include <rpm/rpmcli.h>
+
+#include "debug.h"
+
+struct rpmInstallArguments_s rpmIArgs = {
+ 0, /* transFlags */
+ 0, /* probFilter */
+ 0, /* installInterfaceFlags */
+ 0, /* numRelocations */
+ 0, /* noDeps */
+ 0, /* incldocs */
+ NULL, /* relocations */
+ NULL, /* prefix */
+};
+
+#define POPT_RELOCATE -1021
+#define POPT_EXCLUDEPATH -1022
+
+RPM_GNUC_NORETURN
+static void argerror(const char * desc)
+{
+ fprintf(stderr, _("%s: %s\n"), __progname, desc);
+ exit(EXIT_FAILURE);
+}
+
+/**
+ */
+static void installArgCallback( poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption * opt, const char * arg,
+ const void * data)
+{
+ struct rpmInstallArguments_s * ia = &rpmIArgs;
+
+ /* XXX avoid accidental collisions with POPT_BIT_SET for flags */
+ if (opt->arg == NULL)
+ switch (opt->val) {
+
+ case 'i':
+ ia->installInterfaceFlags |= INSTALL_INSTALL;
+ break;
+
+ case POPT_EXCLUDEPATH:
+ if (arg == NULL || *arg != '/')
+ argerror(_("exclude paths must begin with a /"));
+ ia->relocations = xrealloc(ia->relocations,
+ sizeof(*ia->relocations) * (ia->numRelocations + 1));
+ ia->relocations[ia->numRelocations].oldPath = xstrdup(arg);
+ ia->relocations[ia->numRelocations].newPath = NULL;
+ ia->numRelocations++;
+ break;
+ case POPT_RELOCATE:
+ { char * oldPath = NULL;
+ char * newPath = NULL;
+
+ if (arg == NULL || *arg != '/')
+ argerror(_("relocations must begin with a /"));
+ oldPath = xstrdup(arg);
+ if (!(newPath = strchr(oldPath, '=')))
+ argerror(_("relocations must contain a ="));
+ *newPath++ = '\0';
+ if (*newPath != '/')
+ argerror(_("relocations must have a / following the ="));
+ ia->relocations = xrealloc(ia->relocations,
+ sizeof(*ia->relocations) * (ia->numRelocations + 1));
+ ia->relocations[ia->numRelocations].oldPath = oldPath;
+ ia->relocations[ia->numRelocations].newPath = newPath;
+ ia->numRelocations++;
+ } break;
+
+ case RPMCLI_POPT_NODEPS:
+ ia->noDeps = 1;
+ break;
+
+ case RPMCLI_POPT_NOFILEDIGEST:
+ ia->transFlags |= RPMTRANS_FLAG_NOFILEDIGEST;
+ break;
+
+ case RPMCLI_POPT_NOCONTEXTS:
+ ia->transFlags |= RPMTRANS_FLAG_NOCONTEXTS;
+ break;
+
+ case RPMCLI_POPT_FORCE:
+ ia->probFilter |=
+ ( RPMPROB_FILTER_REPLACEPKG
+ | RPMPROB_FILTER_REPLACEOLDFILES
+ | RPMPROB_FILTER_REPLACENEWFILES
+ | RPMPROB_FILTER_OLDPACKAGE );
+ break;
+
+ case RPMCLI_POPT_NOSCRIPTS:
+ ia->transFlags |= (_noTransScripts | _noTransTriggers);
+ break;
+
+ }
+}
+
+/**
+ */
+struct poptOption rpmInstallPoptTable[] = {
+/* FIX: cast? */
+ { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA | POPT_CBFLAG_CONTINUE,
+ installArgCallback, 0, NULL, NULL },
+
+ { "allfiles", '\0', POPT_BIT_SET,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_ALLFILES,
+ N_("install all files, even configurations which might otherwise be skipped"),
+ NULL},
+ { "allmatches", '\0', POPT_BIT_SET,
+ &rpmIArgs.installInterfaceFlags, UNINSTALL_ALLMATCHES,
+ N_("remove all packages which match <package> (normally an error is generated if <package> specified multiple packages)"),
+ NULL},
+
+ { "badreloc", '\0', POPT_BIT_SET,
+ &rpmIArgs.probFilter, RPMPROB_FILTER_FORCERELOCATE,
+ N_("relocate files in non-relocatable package"), NULL},
+
+ { "deploops", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_DEPLOOPS,
+ N_("print dependency loops as warning"), NULL},
+
+ { "erase", 'e', POPT_BIT_SET,
+ &rpmIArgs.installInterfaceFlags, INSTALL_ERASE,
+ N_("erase (uninstall) package"), N_("<package>+") },
+ { "excludeconfigs", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOCONFIGS,
+ N_("do not install configuration files"), NULL},
+ { "excludedocs", '\0', POPT_BIT_SET,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NODOCS,
+ N_("do not install documentation"), NULL},
+ { "excludepath", '\0', POPT_ARG_STRING, 0, POPT_EXCLUDEPATH,
+ N_("skip files with leading component <path> "),
+ N_("<path>") },
+
+ { "force", '\0', 0, NULL, RPMCLI_POPT_FORCE,
+ N_("short hand for --replacepkgs --replacefiles"), NULL},
+
+ { "freshen", 'F', POPT_BIT_SET, &rpmIArgs.installInterfaceFlags,
+ (INSTALL_UPGRADE|INSTALL_FRESHEN|INSTALL_INSTALL),
+ N_("upgrade package(s) if already installed"),
+ N_("<packagefile>+") },
+ { "hash", 'h', POPT_BIT_SET, &rpmIArgs.installInterfaceFlags, INSTALL_HASH,
+ N_("print hash marks as package installs (good with -v)"), NULL},
+ { "ignorearch", '\0', POPT_BIT_SET,
+ &rpmIArgs.probFilter, RPMPROB_FILTER_IGNOREARCH,
+ N_("don't verify package architecture"), NULL},
+ { "ignoreos", '\0', POPT_BIT_SET,
+ &rpmIArgs.probFilter, RPMPROB_FILTER_IGNOREOS,
+ N_("don't verify package operating system"), NULL},
+ { "ignoresize", '\0', POPT_BIT_SET, &rpmIArgs.probFilter,
+ (RPMPROB_FILTER_DISKSPACE|RPMPROB_FILTER_DISKNODES),
+ N_("don't check disk space before installing"), NULL},
+ { "includedocs", '\0', POPT_ARGFLAG_DOC_HIDDEN, &rpmIArgs.incldocs, 0,
+ N_("install documentation"), NULL},
+
+ { "install", 'i', 0, NULL, 'i',
+ N_("install package(s)"), N_("<packagefile>+") },
+
+ { "justdb", '\0', POPT_BIT_SET, &rpmIArgs.transFlags, RPMTRANS_FLAG_JUSTDB,
+ N_("update the database, but do not modify the filesystem"), NULL},
+
+ { "noconfigs", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOCONFIGS,
+ N_("do not install configuration files"), NULL},
+ { "nodeps", '\0', 0, NULL, RPMCLI_POPT_NODEPS,
+ N_("do not verify package dependencies"), NULL },
+ { "nodocs", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NODOCS,
+ N_("do not install documentation"), NULL},
+
+ { "nofiledigest", '\0', 0, NULL, RPMCLI_POPT_NOFILEDIGEST,
+ N_("don't verify digest of files"), NULL },
+ { "nomd5", '\0', POPT_ARGFLAG_DOC_HIDDEN, NULL, RPMCLI_POPT_NOFILEDIGEST,
+ N_("don't verify digest of files (obsolete)"), NULL },
+ { "nocontexts", '\0',0, NULL, RPMCLI_POPT_NOCONTEXTS,
+ N_("don't install file security contexts"), NULL},
+
+ { "noorder", '\0', POPT_BIT_SET,
+ &rpmIArgs.installInterfaceFlags, INSTALL_NOORDER,
+ N_("do not reorder package installation to satisfy dependencies"),
+ NULL},
+
+ { "noscripts", '\0', 0, NULL, RPMCLI_POPT_NOSCRIPTS,
+ N_("do not execute package scriptlet(s)"), NULL },
+
+ { "nopre", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN, &rpmIArgs.transFlags,
+ RPMTRANS_FLAG_NOPRE,
+ N_("do not execute %%pre scriptlet (if any)"), NULL },
+ { "nopost", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN, &rpmIArgs.transFlags,
+ RPMTRANS_FLAG_NOPOST,
+ N_("do not execute %%post scriptlet (if any)"), NULL },
+ { "nopreun", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN, &rpmIArgs.transFlags,
+ RPMTRANS_FLAG_NOPREUN,
+ N_("do not execute %%preun scriptlet (if any)"), NULL },
+ { "nopostun", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN, &rpmIArgs.transFlags,
+ RPMTRANS_FLAG_NOPOSTUN,
+ N_("do not execute %%postun scriptlet (if any)"), NULL },
+
+ { "notriggers", '\0', POPT_BIT_SET, &rpmIArgs.transFlags, _noTransTriggers,
+ N_("do not execute any scriptlet(s) triggered by this package"), NULL},
+ { "notriggerprein", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOTRIGGERPREIN,
+ N_("do not execute any %%triggerprein scriptlet(s)"), NULL},
+ { "notriggerin", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOTRIGGERIN,
+ N_("do not execute any %%triggerin scriptlet(s)"), NULL},
+ { "notriggerun", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOTRIGGERUN,
+ N_("do not execute any %%triggerun scriptlet(s)"), NULL},
+ { "notriggerpostun", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOTRIGGERPOSTUN,
+ N_("do not execute any %%triggerpostun scriptlet(s)"), NULL},
+
+ { "nocollections", '\0', POPT_BIT_SET,
+ &rpmIArgs.transFlags, RPMTRANS_FLAG_NOCOLLECTIONS,
+ N_("do not perform any collection actions"), NULL},
+
+ { "oldpackage", '\0', POPT_BIT_SET,
+ &rpmIArgs.probFilter, RPMPROB_FILTER_OLDPACKAGE,
+ N_("upgrade to an old version of the package (--force on upgrades does this automatically)"),
+ NULL},
+ { "percent", '\0', POPT_BIT_SET,
+ &rpmIArgs.installInterfaceFlags, INSTALL_PERCENT,
+ N_("print percentages as package installs"), NULL},
+ { "prefix", '\0', POPT_ARG_STRING, &rpmIArgs.prefix, 0,
+ N_("relocate the package to <dir>, if relocatable"),
+ N_("<dir>") },
+ { "relocate", '\0', POPT_ARG_STRING, 0, POPT_RELOCATE,
+ N_("relocate files from path <old> to <new>"),
+ N_("<old>=<new>") },
+ { "replacefiles", '\0', POPT_BIT_SET, &rpmIArgs.probFilter,
+ (RPMPROB_FILTER_REPLACEOLDFILES | RPMPROB_FILTER_REPLACENEWFILES),
+ N_("ignore file conflicts between packages"), NULL},
+ { "replacepkgs", '\0', POPT_BIT_SET,
+ &rpmIArgs.probFilter, RPMPROB_FILTER_REPLACEPKG,
+ N_("reinstall if the package is already present"), NULL},
+ { "test", '\0', POPT_BIT_SET, &rpmIArgs.transFlags, RPMTRANS_FLAG_TEST,
+ N_("don't install, but tell if it would work or not"), NULL},
+ { "upgrade", 'U', POPT_BIT_SET,
+ &rpmIArgs.installInterfaceFlags, (INSTALL_UPGRADE|INSTALL_INSTALL),
+ N_("upgrade package(s)"),
+ N_("<packagefile>+") },
+
+ POPT_TABLEEND
+};
diff --git a/lib/poptQV.c b/lib/poptQV.c
new file mode 100644
index 0000000..1cb5517
--- /dev/null
+++ b/lib/poptQV.c
@@ -0,0 +1,246 @@
+/** \ingroup rpmcli
+ * \file lib/poptQV.c
+ * Popt tables for query/verify modes.
+ */
+
+#include "system.h"
+
+#include <rpm/rpmcli.h>
+#include "lib/rpmgi.h" /* XXX for giFlags */
+
+#include "debug.h"
+
+struct rpmQVKArguments_s rpmQVKArgs;
+
+#define POPT_QUERYFORMAT -1000
+#define POPT_WHATREQUIRES -1001
+#define POPT_WHATPROVIDES -1002
+#define POPT_QUERYBYNUMBER -1003
+#define POPT_TRIGGEREDBY -1004
+#define POPT_DUMP -1005
+#define POPT_QUERYBYPKGID -1007
+#define POPT_QUERYBYHDRID -1008
+#define POPT_QUERYBYTID -1010
+
+/* ========== Query/Verify/Signature source args */
+static void rpmQVSourceArgCallback( poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption * opt, const char * arg,
+ const void * data)
+{
+ QVA_t qva = &rpmQVKArgs;
+ rpmQVSources sources = qva->qva_source;;
+
+ switch (opt->val) {
+ case 'q': /* from --query, -q */
+ case 'Q': /* from --querytags (handled by poptALL) */
+ case 'V': /* from --verify, -V */
+ if (qva->qva_mode == '\0' || strchr("qQ ", qva->qva_mode)) {
+ qva->qva_mode = opt->val;
+ }
+ break;
+ case 'a': qva->qva_source |= RPMQV_ALL; break;
+ case 'f': qva->qva_source |= RPMQV_PATH; break;
+ case 'g': qva->qva_source |= RPMQV_GROUP; break;
+ case 'p': qva->qva_source |= RPMQV_RPM; break;
+ case POPT_WHATPROVIDES: qva->qva_source |= RPMQV_WHATPROVIDES; break;
+ case POPT_WHATREQUIRES: qva->qva_source |= RPMQV_WHATREQUIRES; break;
+ case POPT_TRIGGEREDBY: qva->qva_source |= RPMQV_TRIGGEREDBY; break;
+ case POPT_QUERYBYPKGID: qva->qva_source |= RPMQV_PKGID; break;
+ case POPT_QUERYBYHDRID: qva->qva_source |= RPMQV_HDRID; break;
+ case POPT_QUERYBYTID: qva->qva_source |= RPMQV_TID; break;
+ case POPT_QUERYBYNUMBER: qva->qva_source |= RPMQV_DBOFFSET; break;
+ }
+
+ if (sources != qva->qva_source)
+ qva->qva_sourceCount++;
+}
+
+/**
+ * Common query/verify mode options.
+ */
+struct poptOption rpmQVSourcePoptTable[] = {
+/* FIX: cast? */
+ { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA,
+ rpmQVSourceArgCallback, 0, NULL, NULL },
+ { "all", 'a', 0, 0, 'a',
+ N_("query/verify all packages"), NULL },
+ { "checksig", 'K', POPT_ARGFLAG_DOC_HIDDEN, NULL, 'K',
+ N_("rpm checksig mode"), NULL },
+ { "file", 'f', 0, 0, 'f',
+ N_("query/verify package(s) owning file"), "FILE" },
+ { "group", 'g', 0, 0, 'g',
+ N_("query/verify package(s) in group"), "GROUP" },
+ { "package", 'p', 0, 0, 'p',
+ N_("query/verify a package file"), NULL },
+
+ { "pkgid", '\0', 0, 0, POPT_QUERYBYPKGID,
+ N_("query/verify package(s) with package identifier"), "MD5" },
+ { "hdrid", '\0', 0, 0, POPT_QUERYBYHDRID,
+ N_("query/verify package(s) with header identifier"), "SHA1" },
+
+ { "query", 'q', POPT_ARGFLAG_DOC_HIDDEN, NULL, 'q',
+ N_("rpm query mode"), NULL },
+ { "querybynumber", '\0', POPT_ARGFLAG_DOC_HIDDEN, 0, POPT_QUERYBYNUMBER,
+ N_("query/verify a header instance"), "HDRNUM" },
+ { "tid", '\0', POPT_ARGFLAG_DOC_HIDDEN, 0, POPT_QUERYBYTID,
+ N_("query/verify package(s) from install transaction"), "TID" },
+ { "triggeredby", '\0', 0, 0, POPT_TRIGGEREDBY,
+ N_("query the package(s) triggered by the package"), "PACKAGE" },
+ { "verify", 'V', POPT_ARGFLAG_DOC_HIDDEN, NULL, 'V',
+ N_("rpm verify mode"), NULL },
+ { "whatrequires", '\0', 0, 0, POPT_WHATREQUIRES,
+ N_("query/verify the package(s) which require a dependency"), "CAPABILITY" },
+ { "whatprovides", '\0', 0, 0, POPT_WHATPROVIDES,
+ N_("query/verify the package(s) which provide a dependency"), "CAPABILITY" },
+
+ { "noglob", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN, &giFlags, RPMGI_NOGLOB,
+ N_("do not glob arguments"), NULL},
+ { "nomanifest", '\0', POPT_BIT_SET, &giFlags, RPMGI_NOMANIFEST,
+ N_("do not process non-package files as manifests"), NULL},
+
+ POPT_TABLEEND
+};
+
+/* ========== Query specific popt args */
+
+static void queryArgCallback(poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption * opt, const char * arg,
+ const void * data)
+{
+ QVA_t qva = &rpmQVKArgs;
+
+ switch (opt->val) {
+ case 'c': qva->qva_flags |= QUERY_FOR_CONFIG | QUERY_FOR_LIST; break;
+ case 'd': qva->qva_flags |= QUERY_FOR_DOCS | QUERY_FOR_LIST; break;
+ case 'l': qva->qva_flags |= QUERY_FOR_LIST; break;
+ case 's': qva->qva_flags |= QUERY_FOR_STATE | QUERY_FOR_LIST;
+ break;
+ case POPT_DUMP: qva->qva_flags |= QUERY_FOR_DUMPFILES | QUERY_FOR_LIST;
+ break;
+
+ case POPT_QUERYFORMAT:
+ rstrcat(&qva->qva_queryFormat, arg);
+ break;
+
+ case 'i':
+ if (qva->qva_mode == 'q') {
+ const char * infoCommand[] = { "--info", NULL };
+ (void) poptStuffArgs(con, infoCommand);
+ }
+ break;
+
+ case RPMCLI_POPT_NODEPS:
+ qva->qva_flags |= VERIFY_DEPS;
+ break;
+
+ case RPMCLI_POPT_NOFILEDIGEST:
+ qva->qva_flags |= VERIFY_FILEDIGEST;
+ break;
+
+ case RPMCLI_POPT_NOCONTEXTS:
+ qva->qva_flags |= VERIFY_CONTEXTS;
+ break;
+
+#ifdef NOTYET
+ case RPMCLI_POPT_FORCE:
+ ia->probFilter |=
+ ( RPMPROB_FILTER_REPLACEPKG
+ | RPMPROB_FILTER_REPLACEOLDFILES
+ | RPMPROB_FILTER_REPLACENEWFILES
+ | RPMPROB_FILTER_OLDPACKAGE );
+ break;
+#endif
+
+ case RPMCLI_POPT_NOSCRIPTS:
+ qva->qva_flags |= VERIFY_SCRIPT;
+ break;
+
+ }
+}
+
+/**
+ * Query mode options.
+ */
+struct poptOption rpmQueryPoptTable[] = {
+/* FIX: cast? */
+ { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA | POPT_CBFLAG_CONTINUE,
+ queryArgCallback, 0, NULL, NULL },
+ { "configfiles", 'c', 0, 0, 'c',
+ N_("list all configuration files"), NULL },
+ { "docfiles", 'd', 0, 0, 'd',
+ N_("list all documentation files"), NULL },
+ { "dump", '\0', 0, 0, POPT_DUMP,
+ N_("dump basic file information"), NULL },
+ { NULL, 'i', POPT_ARGFLAG_DOC_HIDDEN, 0, 'i',
+ NULL, NULL },
+ { "list", 'l', 0, 0, 'l',
+ N_("list files in package"), NULL },
+
+ /* Duplicate file attr flags from packages into command line options. */
+ { "noghost", '\0', POPT_BIT_CLR|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_fflags, RPMFILE_GHOST,
+ N_("skip %%ghost files"), NULL },
+
+ { "qf", '\0', POPT_ARG_STRING | POPT_ARGFLAG_DOC_HIDDEN, 0,
+ POPT_QUERYFORMAT, NULL, NULL },
+ { "queryformat", '\0', POPT_ARG_STRING, 0, POPT_QUERYFORMAT,
+ N_("use the following query format"), "QUERYFORMAT" },
+ { "state", 's', 0, 0, 's',
+ N_("display the states of the listed files"), NULL },
+ POPT_TABLEEND
+};
+
+/**
+ * Verify mode options.
+ */
+struct poptOption rpmVerifyPoptTable[] = {
+/* FIX: cast? */
+ { NULL, '\0', POPT_ARG_CALLBACK | POPT_CBFLAG_INC_DATA | POPT_CBFLAG_CONTINUE,
+ queryArgCallback, 0, NULL, NULL },
+
+ { "nofiledigest", '\0', 0, NULL, RPMCLI_POPT_NOFILEDIGEST,
+ N_("don't verify digest of files"), NULL },
+ { "nomd5", '\0', POPT_ARGFLAG_DOC_HIDDEN, NULL, RPMCLI_POPT_NOFILEDIGEST,
+ N_("don't verify digest of files"), NULL },
+ { "nosize", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_SIZE,
+ N_("don't verify size of files"), NULL },
+ { "nolinkto", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_LINKTO,
+ N_("don't verify symlink path of files"), NULL },
+ { "nouser", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_USER,
+ N_("don't verify owner of files"), NULL },
+ { "nogroup", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_GROUP,
+ N_("don't verify group of files"), NULL },
+ { "nomtime", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_MTIME,
+ N_("don't verify modification time of files"), NULL },
+ { "nomode", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_MODE,
+ N_("don't verify mode of files"), NULL },
+ { "nordev", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_RDEV,
+ N_("don't verify mode of files"), NULL },
+ { "nocaps", '\0', POPT_BIT_SET|POPT_ARGFLAG_DOC_HIDDEN,
+ &rpmQVKArgs.qva_flags, VERIFY_CAPS,
+ N_("don't verify capabilities of files"), NULL },
+
+ { "nocontexts", '\0', POPT_ARGFLAG_DOC_HIDDEN, NULL, RPMCLI_POPT_NOCONTEXTS,
+ N_("don't verify file security contexts"), NULL },
+ { "nofiles", '\0', POPT_BIT_SET, &rpmQVKArgs.qva_flags, VERIFY_FILES,
+ N_("don't verify files in package"), NULL},
+ { "nodeps", '\0', 0, NULL, RPMCLI_POPT_NODEPS,
+ N_("don't verify package dependencies"), NULL },
+
+ { "noscript", '\0', 0, NULL, RPMCLI_POPT_NOSCRIPTS,
+ N_("don't execute verify script(s)"), NULL },
+ /* XXX legacy had a trailing s on --noscript */
+ { "noscripts", '\0', POPT_ARGFLAG_DOC_HIDDEN, NULL, RPMCLI_POPT_NOSCRIPTS,
+ N_("don't execute verify script(s)"), NULL },
+
+ POPT_TABLEEND
+};
diff --git a/lib/psm.c b/lib/psm.c
new file mode 100644
index 0000000..1c761bf
--- /dev/null
+++ b/lib/psm.c
@@ -0,0 +1,1095 @@
+/** \ingroup rpmts payload
+ * \file lib/psm.c
+ * Package state machine to handle a package from a transaction set.
+ */
+
+#include "system.h"
+
+#include <errno.h>
+
+#include <rpm/rpmlib.h> /* rpmvercmp and others */
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+#include <rpm/argv.h>
+
+#include "lib/cpio.h"
+#include "lib/fsm.h" /* XXX CPIO_FOO/FSM_FOO constants */
+#include "lib/rpmchroot.h"
+#include "lib/rpmfi_internal.h" /* XXX replaced/states... */
+#include "lib/rpmte_internal.h" /* XXX internal apis */
+#include "lib/rpmdb_internal.h" /* rpmdbAdd/Remove */
+#include "lib/rpmscript.h"
+
+#include "debug.h"
+
+typedef enum pkgStage_e {
+ PSM_UNKNOWN = 0,
+ PSM_INIT = 1,
+ PSM_PRE = 2,
+ PSM_PROCESS = 3,
+ PSM_POST = 4,
+ PSM_UNDO = 5,
+ PSM_FINI = 6,
+
+ PSM_CREATE = 17,
+ PSM_NOTIFY = 22,
+ PSM_DESTROY = 23,
+
+ PSM_SCRIPT = 53,
+ PSM_TRIGGERS = 54,
+ PSM_IMMED_TRIGGERS = 55,
+
+ PSM_RPMDB_ADD = 98,
+ PSM_RPMDB_REMOVE = 99
+
+} pkgStage;
+
+typedef struct rpmpsm_s {
+ rpmts ts; /*!< transaction set */
+ rpmte te; /*!< current transaction element */
+ rpmfi fi; /*!< transaction element file info */
+ const char * goalName;
+ char * failedFile;
+ rpmTagVal scriptTag; /*!< Scriptlet data tag. */
+ int npkgs_installed; /*!< No. of installed instances. */
+ int scriptArg; /*!< Scriptlet package arg. */
+ rpmsenseFlags sense; /*!< One of RPMSENSE_TRIGGER{PREIN,IN,UN,POSTUN}. */
+ int countCorrection; /*!< 0 if installing, -1 if removing. */
+ rpmCallbackType what; /*!< Callback type. */
+ rpm_loff_t amount; /*!< Callback amount. */
+ rpm_loff_t total; /*!< Callback total. */
+ pkgGoal goal;
+ pkgStage stage; /*!< Current psm stage. */
+ pkgStage nstage; /*!< Next psm stage. */
+
+ int nrefs; /*!< Reference count. */
+} * rpmpsm;
+
+static rpmpsm rpmpsmNew(rpmts ts, rpmte te);
+static rpmpsm rpmpsmFree(rpmpsm psm);
+static rpmRC rpmpsmStage(rpmpsm psm, pkgStage stage);
+
+/**
+ * Macros to be defined from per-header tag values.
+ * @todo Should other macros be added from header when installing a package?
+ */
+static struct tagMacro {
+ const char *macroname; /*!< Macro name to define. */
+ rpmTag tag; /*!< Header tag to use for value. */
+} const tagMacros[] = {
+ { "name", RPMTAG_NAME },
+ { "version", RPMTAG_VERSION },
+ { "release", RPMTAG_RELEASE },
+ { "epoch", RPMTAG_EPOCH },
+ { NULL, 0 }
+};
+
+/**
+ * Define per-header macros.
+ * @param h header
+ * @return 0 always
+ */
+static void rpmInstallLoadMacros(Header h)
+{
+ const struct tagMacro * tagm;
+
+ for (tagm = tagMacros; tagm->macroname != NULL; tagm++) {
+ struct rpmtd_s td;
+ char *body;
+ if (!headerGet(h, tagm->tag, &td, HEADERGET_DEFAULT))
+ continue;
+
+ switch (rpmtdType(&td)) {
+ default:
+ body = rpmtdFormat(&td, RPMTD_FORMAT_STRING, NULL);
+ addMacro(NULL, tagm->macroname, NULL, body, -1);
+ free(body);
+ break;
+ case RPM_NULL_TYPE:
+ break;
+ }
+ rpmtdFreeData(&td);
+ }
+}
+
+/**
+ * Mark files in database shared with this package as "replaced".
+ * @param psm package state machine data
+ * @return 0 always
+ */
+static rpmRC markReplacedFiles(const rpmpsm psm)
+{
+ const rpmts ts = psm->ts;
+ rpmfs fs = rpmteGetFileStates(psm->te);
+ sharedFileInfo replaced = rpmfsGetReplaced(fs);
+ sharedFileInfo sfi;
+ rpmdbMatchIterator mi;
+ Header h;
+ int * offsets;
+ unsigned int prev;
+ int num, xx;
+
+ if (!replaced)
+ return RPMRC_OK;
+
+ num = prev = 0;
+ for (sfi = replaced; sfi; sfi=rpmfsNextReplaced(fs, sfi)) {
+ if (prev && prev == sfi->otherPkg)
+ continue;
+ prev = sfi->otherPkg;
+ num++;
+ }
+ if (num == 0)
+ return RPMRC_OK;
+
+ offsets = xmalloc(num * sizeof(*offsets));
+ offsets[0] = 0;
+ num = prev = 0;
+ for (sfi = replaced; sfi; sfi=rpmfsNextReplaced(fs, sfi)) {
+ if (prev && prev == sfi->otherPkg)
+ continue;
+ prev = sfi->otherPkg;
+ offsets[num++] = sfi->otherPkg;
+ }
+
+ mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+ xx = rpmdbAppendIterator(mi, offsets, num);
+ xx = rpmdbSetIteratorRewrite(mi, 1);
+
+ sfi = replaced;
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ int modified;
+ struct rpmtd_s secStates;
+ modified = 0;
+
+ if (!headerGet(h, RPMTAG_FILESTATES, &secStates, HEADERGET_MINMEM))
+ continue;
+
+ prev = rpmdbGetIteratorOffset(mi);
+ num = 0;
+ while (sfi && sfi->otherPkg == prev) {
+ int ix = rpmtdSetIndex(&secStates, sfi->otherFileNum);
+ assert(ix != -1);
+
+ char *state = rpmtdGetChar(&secStates);
+ if (state && *state != RPMFILE_STATE_REPLACED) {
+ *state = RPMFILE_STATE_REPLACED;
+ if (modified == 0) {
+ /* Modified header will be rewritten. */
+ modified = 1;
+ xx = rpmdbSetIteratorModified(mi, modified);
+ }
+ num++;
+ }
+ sfi=rpmfsNextReplaced(fs, sfi);
+ }
+ rpmtdFreeData(&secStates);
+ }
+ mi = rpmdbFreeIterator(mi);
+ free(offsets);
+
+ return RPMRC_OK;
+}
+
+static int rpmlibDeps(Header h)
+{
+ rpmds req = rpmdsInit(rpmdsNew(h, RPMTAG_REQUIRENAME, 0));
+ rpmds rpmlib = NULL;
+ rpmdsRpmlib(&rpmlib, NULL);
+ int rc = 1;
+ char *nvr = NULL;
+ while (rpmdsNext(req) >= 0) {
+ if (!(rpmdsFlags(req) & RPMSENSE_RPMLIB))
+ continue;
+ if (rpmdsSearch(rpmlib, req) < 0) {
+ if (!nvr) {
+ nvr = headerGetAsString(h, RPMTAG_NEVRA);
+ rpmlog(RPMLOG_ERR, _("Missing rpmlib features for %s:\n"), nvr);
+ }
+ rpmlog(RPMLOG_ERR, "\t%s\n", rpmdsDNEVR(req)+2);
+ rc = 0;
+ }
+ }
+ rpmdsFree(req);
+ rpmdsFree(rpmlib);
+ free(nvr);
+ return rc;
+}
+
+rpmRC rpmInstallSourcePackage(rpmts ts, FD_t fd,
+ char ** specFilePtr, char ** cookie)
+{
+ rpmfi fi = NULL;
+ char * specFile = NULL;
+ const char *rootdir = rpmtsRootDir(ts);
+ Header h = NULL;
+ rpmpsm psm = NULL;
+ rpmte te = NULL;
+ rpmRC rpmrc;
+ int specix = -1;
+ struct rpmtd_s filenames;
+
+ rpmtdReset(&filenames);
+ rpmrc = rpmReadPackageFile(ts, fd, RPMDBG_M("InstallSourcePackage"), &h);
+ switch (rpmrc) {
+ case RPMRC_NOTTRUSTED:
+ case RPMRC_NOKEY:
+ case RPMRC_OK:
+ break;
+ default:
+ goto exit;
+ break;
+ }
+ if (h == NULL)
+ goto exit;
+
+ rpmrc = RPMRC_FAIL; /* assume failure */
+
+ if (!headerIsSource(h)) {
+ rpmlog(RPMLOG_ERR, _("source package expected, binary found\n"));
+ goto exit;
+ }
+
+ /* src.rpm install can require specific rpmlib features, check them */
+ if (!rpmlibDeps(h))
+ goto exit;
+
+ if (headerGet(h, RPMTAG_BASENAMES, &filenames, HEADERGET_ALLOC)) {
+ struct rpmtd_s td;
+ const char *str;
+ const char *_cookie = headerGetString(h, RPMTAG_COOKIE);
+ if (cookie && _cookie) *cookie = xstrdup(_cookie);
+
+ /* Try to find spec by file flags */
+ if (_cookie && headerGet(h, RPMTAG_FILEFLAGS, &td, HEADERGET_MINMEM)) {
+ rpmfileAttrs *flags;
+ while (specix < 0 && (flags = rpmtdNextUint32(&td))) {
+ if (*flags & RPMFILE_SPECFILE)
+ specix = rpmtdGetIndex(&td);
+ }
+ }
+ /* Still no spec? Look by filename. */
+ while (specix < 0 && (str = rpmtdNextString(&filenames))) {
+ if (rpmFileHasSuffix(str, ".spec"))
+ specix = rpmtdGetIndex(&filenames);
+ }
+ }
+
+ if (rootdir && rstreq(rootdir, "/"))
+ rootdir = NULL;
+
+ /* Macros need to be added before trying to create directories */
+ rpmInstallLoadMacros(h);
+
+ if (specix >= 0) {
+ const char *bn;
+
+ headerDel(h, RPMTAG_BASENAMES);
+ headerDel(h, RPMTAG_DIRNAMES);
+ headerDel(h, RPMTAG_DIRINDEXES);
+
+ rpmtdInit(&filenames);
+ for (int i = 0; (bn = rpmtdNextString(&filenames)); i++) {
+ int spec = (i == specix);
+ char *fn = rpmGenPath(rpmtsRootDir(ts),
+ spec ? "%{_specdir}" : "%{_sourcedir}", bn);
+ headerPutString(h, RPMTAG_OLDFILENAMES, fn);
+ if (spec) specFile = xstrdup(fn);
+ free(fn);
+ }
+ headerConvert(h, HEADERCONV_COMPRESSFILELIST);
+ } else {
+ rpmlog(RPMLOG_ERR, _("source package contains no .spec file\n"));
+ goto exit;
+ };
+
+ if (rpmtsAddInstallElement(ts, h, NULL, 0, NULL)) {
+ goto exit;
+ }
+
+ te = rpmtsElement(ts, 0);
+ if (te == NULL) { /* XXX can't happen */
+ goto exit;
+ }
+ rpmteSetFd(te, fd);
+
+ rpmteSetHeader(te, h);
+ fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, RPMFI_KEEPHEADER);
+ h = headerFree(h);
+
+ if (fi == NULL) { /* XXX can't happen */
+ goto exit;
+ }
+ fi->apath = filenames.data; /* Ick */
+ rpmteSetFI(te, fi);
+ fi = rpmfiFree(fi);
+
+ if (rpmMkdirs(rpmtsRootDir(ts), "%{_topdir}:%{_sourcedir}:%{_specdir}")) {
+ goto exit;
+ }
+
+ {
+ /* set all files to be installed */
+ rpmfs fs = rpmteGetFileStates(te);
+ int i;
+ unsigned int fc = rpmfiFC(fi);
+ for (i=0; i<fc; i++) rpmfsSetAction(fs, i, FA_CREATE);
+ }
+
+ psm = rpmpsmNew(ts, te);
+ psm->goal = PKG_INSTALL;
+
+ /* FIX: psm->fi->dnl should be owned. */
+ if (rpmpsmStage(psm, PSM_PROCESS) == RPMRC_OK)
+ rpmrc = RPMRC_OK;
+
+ (void) rpmpsmStage(psm, PSM_FINI);
+ psm = rpmpsmFree(psm);
+
+exit:
+ if (specFilePtr && specFile && rpmrc == RPMRC_OK)
+ *specFilePtr = specFile;
+ else
+ specFile = _free(specFile);
+
+ if (h != NULL) h = headerFree(h);
+ if (fi != NULL) fi = rpmfiFree(fi);
+
+ /* XXX nuke the added package(s). */
+ rpmtsClean(ts);
+
+ return rpmrc;
+}
+
+static rpmTagVal triggertag(rpmsenseFlags sense)
+{
+ rpmTagVal tag = RPMTAG_NOT_FOUND;
+ switch (sense) {
+ case RPMSENSE_TRIGGERIN:
+ tag = RPMTAG_TRIGGERIN;
+ break;
+ case RPMSENSE_TRIGGERUN:
+ tag = RPMTAG_TRIGGERUN;
+ break;
+ case RPMSENSE_TRIGGERPOSTUN:
+ tag = RPMTAG_TRIGGERPOSTUN;
+ break;
+ case RPMSENSE_TRIGGERPREIN:
+ tag = RPMTAG_TRIGGERPREIN;
+ break;
+ default:
+ break;
+ }
+ return tag;
+}
+
+/**
+ * Run a scriptlet with args.
+ *
+ * Run a script with an interpreter. If the interpreter is not specified,
+ * /bin/sh will be used. If the interpreter is /bin/sh, then the args from
+ * the header will be ignored, passing instead arg1 and arg2.
+ *
+ * @param psm package state machine data
+ * @param prefixes install prefixes
+ * @param script scriptlet from header
+ * @param arg1 no. instances of package installed after scriptlet exec
+ * (-1 is no arg)
+ * @param arg2 ditto, but for the target package
+ * @return 0 on success
+ */
+static rpmRC runScript(rpmpsm psm, ARGV_const_t prefixes,
+ rpmScript script, int arg1, int arg2)
+{
+ rpmRC rc = RPMRC_OK;
+ int warn_only = (script->tag != RPMTAG_PREIN &&
+ script->tag != RPMTAG_PREUN &&
+ script->tag != RPMTAG_VERIFYSCRIPT);
+ int selinux = !(rpmtsFlags(psm->ts) & RPMTRANS_FLAG_NOCONTEXTS);
+
+ rpmswEnter(rpmtsOp(psm->ts, RPMTS_OP_SCRIPTLETS), 0);
+ rc = rpmScriptRun(script, arg1, arg2, rpmtsScriptFd(psm->ts),
+ prefixes, warn_only, selinux);
+ rpmswExit(rpmtsOp(psm->ts, RPMTS_OP_SCRIPTLETS), 0);
+
+ /*
+ * Notify callback for all errors. "total" abused for warning/error,
+ * rc only reflects whether the condition prevented install/erase
+ * (which is only happens with %prein and %preun scriptlets) or not.
+ */
+ if (rc != RPMRC_OK) {
+ if (warn_only) {
+ rc = RPMRC_OK;
+ }
+ rpmtsNotify(psm->ts, psm->te, RPMCALLBACK_SCRIPT_ERROR, script->tag, rc);
+ }
+
+ return rc;
+}
+
+static rpmRC runInstScript(rpmpsm psm)
+{
+ rpmRC rc = RPMRC_OK;
+ struct rpmtd_s pfx;
+ Header h = rpmteHeader(psm->te);
+ rpmScript script = rpmScriptFromTag(h, psm->scriptTag);
+
+ if (script) {
+ headerGet(h, RPMTAG_INSTPREFIXES, &pfx, HEADERGET_ALLOC|HEADERGET_ARGV);
+ rc = runScript(psm, pfx.data, script, psm->scriptArg, -1);
+ rpmtdFreeData(&pfx);
+ }
+
+ rpmScriptFree(script);
+ headerFree(h);
+
+ return rc;
+}
+
+/**
+ * Execute triggers.
+ * @todo Trigger on any provides, not just package NVR.
+ * @param psm package state machine data
+ * @param sourceH header of trigger source
+ * @param trigH header of triggered package
+ * @param arg2
+ * @param triggersAlreadyRun
+ * @return
+ */
+static rpmRC handleOneTrigger(const rpmpsm psm,
+ Header sourceH, Header trigH,
+ int arg2, unsigned char * triggersAlreadyRun)
+{
+ const rpmts ts = psm->ts;
+ rpmds trigger = rpmdsInit(rpmdsNew(trigH, RPMTAG_TRIGGERNAME, 0));
+ struct rpmtd_s pfx;
+ const char * sourceName = headerGetString(sourceH, RPMTAG_NAME);
+ const char * triggerName = headerGetString(trigH, RPMTAG_NAME);
+ rpmRC rc = RPMRC_OK;
+ int i;
+
+ if (trigger == NULL)
+ return rc;
+
+ headerGet(trigH, RPMTAG_INSTPREFIXES, &pfx, HEADERGET_ALLOC|HEADERGET_ARGV);
+ (void) rpmdsSetNoPromote(trigger, 1);
+
+ while ((i = rpmdsNext(trigger)) >= 0) {
+ struct rpmtd_s tscripts, tprogs, tindexes, tflags;
+ headerGetFlags hgflags = HEADERGET_MINMEM;
+
+ if (!(rpmdsFlags(trigger) & psm->sense))
+ continue;
+
+ if (!rstreq(rpmdsN(trigger), sourceName))
+ continue;
+
+ /* XXX Trigger on any provided dependency, not just the package NEVR */
+ if (!rpmdsAnyMatchesDep(sourceH, trigger, 1))
+ continue;
+
+ /* XXX FIXME: this leaks memory if scripts or progs retrieve fails */
+ if (!(headerGet(trigH, RPMTAG_TRIGGERINDEX, &tindexes, hgflags) &&
+ headerGet(trigH, RPMTAG_TRIGGERSCRIPTS, &tscripts, hgflags) &&
+ headerGet(trigH, RPMTAG_TRIGGERSCRIPTPROG, &tprogs, hgflags))) {
+ continue;
+ } else {
+ int arg1 = rpmdbCountPackages(rpmtsGetRdb(ts), triggerName);
+ char ** triggerScripts = tscripts.data;
+ char ** triggerProgs = tprogs.data;
+ uint32_t * triggerIndices = tindexes.data;
+ uint32_t * triggerFlags = NULL;
+ uint32_t ix = triggerIndices[i];
+
+ headerGet(trigH, RPMTAG_TRIGGERSCRIPTFLAGS, &tflags, hgflags);
+ triggerFlags = tflags.data;
+
+ if (arg1 < 0) {
+ /* XXX W2DO? fails as "execution of script failed" */
+ rc = RPMRC_FAIL;
+ } else {
+ arg1 += psm->countCorrection;
+
+ if (triggersAlreadyRun == NULL || triggersAlreadyRun[ix] == 0) {
+ /* XXX TODO add rpmScript API to handle this, ugh */
+ char *macro = NULL;
+ char *qformat = NULL;
+ char *args[2] = { triggerProgs[ix], NULL };
+ struct rpmScript_s script = {
+ .tag = triggertag(psm->sense),
+ .body = triggerScripts[ix],
+ .flags = triggerFlags ? triggerFlags[ix] : 0,
+ .args = args
+ };
+
+ if (script.body && (script.flags & RPMSCRIPT_EXPAND)) {
+ macro = rpmExpand(script.body, NULL);
+ script.body = macro;
+ }
+ if (script.body && (script.flags & RPMSCRIPT_QFORMAT)) {
+ qformat = headerFormat(trigH, script.body, NULL);
+ script.body = qformat;
+ }
+
+ rc = runScript(psm, pfx.data, &script, arg1, arg2);
+ if (triggersAlreadyRun != NULL)
+ triggersAlreadyRun[ix] = 1;
+ free(macro);
+ free(qformat);
+ }
+ }
+ }
+
+ rpmtdFreeData(&tindexes);
+ rpmtdFreeData(&tscripts);
+ rpmtdFreeData(&tprogs);
+
+ /*
+ * Each target/source header pair can only result in a single
+ * script being run.
+ */
+ break;
+ }
+
+ rpmtdFreeData(&pfx);
+ trigger = rpmdsFree(trigger);
+
+ return rc;
+}
+
+/**
+ * Run trigger scripts in the database that are fired by this header.
+ * @param psm package state machine data
+ * @return 0 on success
+ */
+static rpmRC runTriggers(rpmpsm psm)
+{
+ const rpmts ts = psm->ts;
+ int numPackage = -1;
+ const char * N = NULL;
+ int nerrors = 0;
+
+ if (psm->te) /* XXX can't happen */
+ N = rpmteN(psm->te);
+ if (N) /* XXX can't happen */
+ numPackage = rpmdbCountPackages(rpmtsGetRdb(ts), N)
+ + psm->countCorrection;
+ if (numPackage < 0)
+ return RPMRC_NOTFOUND;
+
+ { Header triggeredH;
+ Header h = rpmteHeader(psm->te);
+ rpmdbMatchIterator mi;
+ int countCorrection = psm->countCorrection;
+
+ psm->countCorrection = 0;
+ mi = rpmtsInitIterator(ts, RPMDBI_TRIGGERNAME, N, 0);
+ while((triggeredH = rpmdbNextIterator(mi)) != NULL)
+ nerrors += handleOneTrigger(psm, h, triggeredH, numPackage, NULL);
+ mi = rpmdbFreeIterator(mi);
+ psm->countCorrection = countCorrection;
+ headerFree(h);
+ }
+
+ return (nerrors == 0) ? RPMRC_OK : RPMRC_FAIL;
+}
+
+/**
+ * Run triggers from this header that are fired by headers in the database.
+ * @param psm package state machine data
+ * @return 0 on success
+ */
+static rpmRC runImmedTriggers(rpmpsm psm)
+{
+ const rpmts ts = psm->ts;
+ unsigned char * triggersRun;
+ struct rpmtd_s tnames, tindexes;
+ Header h = rpmteHeader(psm->te);
+ int nerrors = 0;
+
+ if (!(headerGet(h, RPMTAG_TRIGGERNAME, &tnames, HEADERGET_MINMEM) &&
+ headerGet(h, RPMTAG_TRIGGERINDEX, &tindexes, HEADERGET_MINMEM))) {
+ goto exit;
+ }
+
+ triggersRun = xcalloc(rpmtdCount(&tindexes), sizeof(*triggersRun));
+ { Header sourceH = NULL;
+ const char *trigName;
+ rpm_count_t *triggerIndices = tindexes.data;
+
+ while ((trigName = rpmtdNextString(&tnames))) {
+ rpmdbMatchIterator mi;
+ int i = rpmtdGetIndex(&tnames);
+
+ if (triggersRun[triggerIndices[i]] != 0) continue;
+
+ mi = rpmtsInitIterator(ts, RPMDBI_NAME, trigName, 0);
+
+ while((sourceH = rpmdbNextIterator(mi)) != NULL) {
+ nerrors += handleOneTrigger(psm, sourceH, h,
+ rpmdbGetIteratorCount(mi),
+ triggersRun);
+ }
+
+ mi = rpmdbFreeIterator(mi);
+ }
+ }
+ rpmtdFreeData(&tnames);
+ rpmtdFreeData(&tindexes);
+ free(triggersRun);
+
+exit:
+ headerFree(h);
+ return (nerrors == 0) ? RPMRC_OK : RPMRC_FAIL;
+}
+
+static rpmpsm rpmpsmFree(rpmpsm psm)
+{
+ if (psm == NULL)
+ return NULL;
+
+ psm->fi = rpmfiFree(psm->fi);
+#ifdef NOTYET
+ psm->te = rpmteFree(psm->te);
+#else
+ psm->te = NULL;
+#endif
+ psm->ts = rpmtsFree(psm->ts);
+
+ memset(psm, 0, sizeof(*psm)); /* XXX trash and burn */
+ psm = _free(psm);
+
+ return NULL;
+}
+
+static rpmpsm rpmpsmNew(rpmts ts, rpmte te)
+{
+ rpmpsm psm = xcalloc(1, sizeof(*psm));
+
+ if (ts) psm->ts = rpmtsLink(ts);
+ if (te) {
+#ifdef NOTYET
+ psm->te = rpmteLink(te, RPMDBG_M("rpmpsmNew")); 
+#else
+ psm->te = te;
+#endif
+ psm->fi = rpmfiLink(rpmteFI(te));
+ }
+
+ return psm;
+}
+
+static rpmRC rpmpsmNext(rpmpsm psm, pkgStage nstage)
+{
+ psm->nstage = nstage;
+ return rpmpsmStage(psm, psm->nstage);
+}
+
+static rpmRC rpmpsmStage(rpmpsm psm, pkgStage stage)
+{
+ const rpmts ts = psm->ts;
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpmfi fi = psm->fi;
+ rpmRC rc = RPMRC_OK;
+ int saveerrno;
+ int xx;
+
+ switch (stage) {
+ case PSM_UNKNOWN:
+ break;
+ case PSM_INIT:
+ rpmlog(RPMLOG_DEBUG, "%s: %s has %d files\n",
+ psm->goalName, rpmteNEVR(psm->te), rpmfiFC(fi));
+
+ /*
+ * When we run scripts, we pass an argument which is the number of
+ * versions of this package that will be installed when we are
+ * finished.
+ */
+ psm->npkgs_installed = rpmdbCountPackages(rpmtsGetRdb(ts), rpmteN(psm->te));
+ if (psm->npkgs_installed < 0) {
+ rc = RPMRC_FAIL;
+ break;
+ }
+
+ if (psm->goal == PKG_INSTALL) {
+ rpmdbMatchIterator mi;
+ Header oh;
+
+ psm->scriptArg = psm->npkgs_installed + 1;
+
+ mi = rpmtsInitIterator(ts, RPMDBI_NAME, rpmteN(psm->te), 0);
+ xx = rpmdbSetIteratorRE(mi, RPMTAG_EPOCH, RPMMIRE_STRCMP,
+ rpmteE(psm->te));
+ xx = rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_STRCMP,
+ rpmteV(psm->te));
+ xx = rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_STRCMP,
+ rpmteR(psm->te));
+ if (tscolor) {
+ xx = rpmdbSetIteratorRE(mi, RPMTAG_ARCH, RPMMIRE_STRCMP,
+ rpmteA(psm->te));
+ xx = rpmdbSetIteratorRE(mi, RPMTAG_OS, RPMMIRE_STRCMP,
+ rpmteO(psm->te));
+ }
+
+ while ((oh = rpmdbNextIterator(mi)) != NULL) {
+ rpmteSetDBInstance(psm->te, rpmdbGetIteratorOffset(mi));
+ oh = NULL;
+ break;
+ }
+ mi = rpmdbFreeIterator(mi);
+ rc = RPMRC_OK;
+
+ if (rpmtsFlags(ts) & RPMTRANS_FLAG_JUSTDB) break;
+
+ if (rpmfiFC(fi) > 0) {
+ struct rpmtd_s filenames;
+ rpmTag ftag = RPMTAG_FILENAMES;
+ Header h = rpmteHeader(psm->te);
+
+ if (headerIsEntry(h, RPMTAG_ORIGBASENAMES)) {
+ ftag = RPMTAG_ORIGFILENAMES;
+ }
+ headerGet(h, ftag, &filenames, HEADERGET_EXT);
+ fi->apath = filenames.data; /* Ick.. */
+ headerFree(h);
+ }
+ }
+ if (psm->goal == PKG_ERASE) {
+ psm->scriptArg = psm->npkgs_installed - 1;
+ }
+ break;
+ case PSM_PRE:
+ if (psm->goal == PKG_INSTALL) {
+ psm->scriptTag = RPMTAG_PREIN;
+ psm->sense = RPMSENSE_TRIGGERPREIN;
+ psm->countCorrection = 0; /* XXX is this correct?!? */
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOTRIGGERPREIN)) {
+ /* Run triggers in other package(s) this package sets off. */
+ rc = rpmpsmNext(psm, PSM_TRIGGERS);
+ if (rc) break;
+
+ /* Run triggers in this package other package(s) set off. */
+ rc = rpmpsmNext(psm, PSM_IMMED_TRIGGERS);
+ if (rc) break;
+ }
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOPRE)) {
+ rc = rpmpsmNext(psm, PSM_SCRIPT);
+ if (rc) break;
+ }
+ }
+
+ if (psm->goal == PKG_ERASE) {
+ psm->scriptTag = RPMTAG_PREUN;
+ psm->sense = RPMSENSE_TRIGGERUN;
+ psm->countCorrection = -1;
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOTRIGGERUN)) {
+ /* Run triggers in this package other package(s) set off. */
+ rc = rpmpsmNext(psm, PSM_IMMED_TRIGGERS);
+ if (rc) break;
+
+ /* Run triggers in other package(s) this package sets off. */
+ rc = rpmpsmNext(psm, PSM_TRIGGERS);
+ if (rc) break;
+ }
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOPREUN))
+ rc = rpmpsmNext(psm, PSM_SCRIPT);
+ }
+ break;
+ case PSM_PROCESS:
+ if (psm->goal == PKG_INSTALL) {
+ FD_t payload = NULL;
+
+ if (rpmtsFlags(ts) & RPMTRANS_FLAG_JUSTDB) break;
+
+ /* XXX Synthesize callbacks for packages with no files. */
+ if (rpmfiFC(fi) <= 0) {
+ void * ptr;
+ ptr = rpmtsNotify(ts, psm->te, RPMCALLBACK_INST_START, 0, 100);
+ ptr = rpmtsNotify(ts, psm->te, RPMCALLBACK_INST_PROGRESS, 100, 100);
+ break;
+ }
+
+ payload = rpmtePayload(psm->te);
+ if (payload == NULL) {
+ rc = RPMRC_FAIL;
+ break;
+ }
+
+ rc = fsmSetup(rpmfiFSM(fi), FSM_PKGINSTALL, ts, psm->te, fi,
+ payload, NULL, &psm->failedFile);
+ (void) rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS),
+ fdOp(payload, FDSTAT_READ));
+ (void) rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST),
+ fdOp(payload, FDSTAT_DIGEST));
+ xx = fsmTeardown(rpmfiFSM(fi));
+
+ saveerrno = errno; /* XXX FIXME: Fclose with libio destroys errno */
+ xx = Fclose(payload);
+ errno = saveerrno; /* XXX FIXME: Fclose with libio destroys errno */
+
+ /* XXX make sure progress is closed out */
+ psm->what = RPMCALLBACK_INST_PROGRESS;
+ psm->amount = (fi->archiveSize ? fi->archiveSize : 100);
+ psm->total = psm->amount;
+ xx = rpmpsmNext(psm, PSM_NOTIFY);
+
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("unpacking of archive failed%s%s: %s\n"),
+ (psm->failedFile != NULL ? _(" on file ") : ""),
+ (psm->failedFile != NULL ? psm->failedFile : ""),
+ cpioStrerror(rc));
+ rc = RPMRC_FAIL;
+
+ /* XXX notify callback on error. */
+ psm->what = RPMCALLBACK_UNPACK_ERROR;
+ psm->amount = 0;
+ psm->total = 0;
+ xx = rpmpsmNext(psm, PSM_NOTIFY);
+
+ break;
+ }
+ }
+ if (psm->goal == PKG_ERASE) {
+ int fc = rpmfiFC(fi);
+
+ if (rpmtsFlags(ts) & RPMTRANS_FLAG_JUSTDB) break;
+
+ /* XXX Synthesize callbacks for packages with no files. */
+ if (rpmfiFC(fi) <= 0) {
+ void * ptr;
+ ptr = rpmtsNotify(ts, psm->te, RPMCALLBACK_UNINST_START, 0, 100);
+ ptr = rpmtsNotify(ts, psm->te, RPMCALLBACK_UNINST_STOP, 0, 100);
+ break;
+ }
+
+ psm->what = RPMCALLBACK_UNINST_START;
+ psm->amount = fc; /* XXX W2DO? looks wrong. */
+ psm->total = fc;
+ xx = rpmpsmNext(psm, PSM_NOTIFY);
+
+ rc = fsmSetup(rpmfiFSM(fi), FSM_PKGERASE, ts, psm->te, fi,
+ NULL, NULL, &psm->failedFile);
+ xx = fsmTeardown(rpmfiFSM(fi));
+
+ psm->what = RPMCALLBACK_UNINST_STOP;
+ psm->amount = 0; /* XXX W2DO? looks wrong. */
+ psm->total = fc;
+ xx = rpmpsmNext(psm, PSM_NOTIFY);
+
+ }
+ break;
+ case PSM_POST:
+ if (psm->goal == PKG_INSTALL) {
+ rpm_time_t installTime = (rpm_time_t) time(NULL);
+ rpmfs fs = rpmteGetFileStates(psm->te);
+ rpm_count_t fc = rpmfsFC(fs);
+ rpm_fstate_t * fileStates = rpmfsGetStates(fs);
+ Header h = rpmteHeader(psm->te);
+
+ if (fileStates != NULL && fc > 0) {
+ headerPutChar(h, RPMTAG_FILESTATES, fileStates, fc);
+ }
+
+ headerPutUint32(h, RPMTAG_INSTALLTIME, &installTime, 1);
+ headerPutUint32(h, RPMTAG_INSTALLCOLOR, &tscolor, 1);
+ headerFree(h);
+
+ /*
+ * If this package has already been installed, remove it from
+ * the database before adding the new one.
+ */
+ if (rpmteDBInstance(psm->te)) {
+ rc = rpmpsmNext(psm, PSM_RPMDB_REMOVE);
+ if (rc) break;
+ }
+
+ rc = rpmpsmNext(psm, PSM_RPMDB_ADD);
+ if (rc) break;
+
+ psm->scriptTag = RPMTAG_POSTIN;
+ psm->sense = RPMSENSE_TRIGGERIN;
+ psm->countCorrection = 0;
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOPOST)) {
+ rc = rpmpsmNext(psm, PSM_SCRIPT);
+ if (rc) break;
+ }
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOTRIGGERIN)) {
+ /* Run triggers in other package(s) this package sets off. */
+ rc = rpmpsmNext(psm, PSM_TRIGGERS);
+ if (rc) break;
+
+ /* Run triggers in this package other package(s) set off. */
+ rc = rpmpsmNext(psm, PSM_IMMED_TRIGGERS);
+ if (rc) break;
+ }
+
+ rc = markReplacedFiles(psm);
+
+ }
+ if (psm->goal == PKG_ERASE) {
+
+ psm->scriptTag = RPMTAG_POSTUN;
+ psm->sense = RPMSENSE_TRIGGERPOSTUN;
+ psm->countCorrection = -1;
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOPOSTUN)) {
+ rc = rpmpsmNext(psm, PSM_SCRIPT);
+ if (rc) break;
+ }
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOTRIGGERPOSTUN)) {
+ /* Run triggers in other package(s) this package sets off. */
+ rc = rpmpsmNext(psm, PSM_TRIGGERS);
+ if (rc) break;
+ }
+
+ rc = rpmpsmNext(psm, PSM_RPMDB_REMOVE);
+ }
+ break;
+ case PSM_UNDO:
+ break;
+ case PSM_FINI:
+ if (rc) {
+ if (psm->failedFile)
+ rpmlog(RPMLOG_ERR,
+ _("%s failed on file %s: %s\n"),
+ psm->goalName, psm->failedFile, cpioStrerror(rc));
+ else
+ rpmlog(RPMLOG_ERR, _("%s failed: %s\n"),
+ psm->goalName, cpioStrerror(rc));
+
+ /* XXX notify callback on error. */
+ psm->what = RPMCALLBACK_CPIO_ERROR;
+ psm->amount = 0;
+ psm->total = 0;
+ xx = rpmpsmNext(psm, PSM_NOTIFY);
+ }
+
+ psm->failedFile = _free(psm->failedFile);
+
+ fi->apath = _free(fi->apath);
+ break;
+
+ case PSM_CREATE:
+ break;
+ case PSM_NOTIFY:
+ { void * ptr;
+/* FIX: psm->te may be NULL */
+ ptr = rpmtsNotify(ts, psm->te, psm->what, psm->amount, psm->total);
+ } break;
+ case PSM_DESTROY:
+ break;
+ case PSM_SCRIPT: /* Run current package scriptlets. */
+ rc = runInstScript(psm);
+ break;
+ case PSM_TRIGGERS:
+ /* Run triggers in other package(s) this package sets off. */
+ rc = runTriggers(psm);
+ break;
+ case PSM_IMMED_TRIGGERS:
+ /* Run triggers in this package other package(s) set off. */
+ rc = runImmedTriggers(psm);
+ break;
+
+ case PSM_RPMDB_ADD: {
+ Header h = rpmteHeader(psm->te);
+
+ if (!headerIsEntry(h, RPMTAG_INSTALLTID)) {
+ rpm_tid_t tid = rpmtsGetTid(ts);
+ if (tid != 0 && tid != (rpm_tid_t)-1)
+ headerPutUint32(h, RPMTAG_INSTALLTID, &tid, 1);
+ }
+
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_DBADD), 0);
+ rc = (rpmdbAdd(rpmtsGetRdb(ts), h) == 0) ? RPMRC_OK : RPMRC_FAIL;
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_DBADD), 0);
+
+ if (rc == RPMRC_OK)
+ rpmteSetDBInstance(psm->te, headerGetInstance(h));
+ headerFree(h);
+ } break;
+
+ case PSM_RPMDB_REMOVE:
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_DBREMOVE), 0);
+ rc = (rpmdbRemove(rpmtsGetRdb(ts), rpmteDBInstance(psm->te)) == 0) ?
+ RPMRC_OK : RPMRC_FAIL;
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_DBREMOVE), 0);
+ if (rc == RPMRC_OK)
+ rpmteSetDBInstance(psm->te, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static const char * pkgGoalString(pkgGoal goal)
+{
+ switch(goal) {
+ case PKG_INSTALL: return " install";
+ case PKG_ERASE: return " erase";
+ case PKG_VERIFY: return " verify";
+ case PKG_PRETRANS: return " pretrans";
+ case PKG_POSTTRANS: return "posttrans";
+ default: return "unknown";
+ }
+}
+
+rpmRC rpmpsmRun(rpmts ts, rpmte te, pkgGoal goal)
+{
+ rpmpsm psm = NULL;
+ rpmRC rc = RPMRC_FAIL;
+
+ /* Psm can't fail in test mode, just return early */
+ if (rpmtsFlags(ts) & RPMTRANS_FLAG_TEST)
+ return RPMRC_OK;
+
+ psm = rpmpsmNew(ts, te);
+ if (rpmChrootIn() == 0) {
+ rpmtsOpX op;
+ psm->goal = goal;
+ psm->goalName = pkgGoalString(goal);
+
+ switch (goal) {
+ case PKG_INSTALL:
+ case PKG_ERASE:
+ op = (goal == PKG_INSTALL) ? RPMTS_OP_INSTALL : RPMTS_OP_ERASE;
+ rpmswEnter(rpmtsOp(psm->ts, op), 0);
+
+ rc = rpmpsmNext(psm, PSM_INIT);
+ if (!rc) rc = rpmpsmNext(psm, PSM_PRE);
+ if (!rc) rc = rpmpsmNext(psm, PSM_PROCESS);
+ if (!rc) rc = rpmpsmNext(psm, PSM_POST);
+ (void) rpmpsmNext(psm, PSM_FINI);
+
+ rpmswExit(rpmtsOp(psm->ts, op), 0);
+ break;
+ case PKG_PRETRANS:
+ case PKG_POSTTRANS:
+ case PKG_VERIFY:
+ psm->scriptTag = goal;
+ rc = rpmpsmStage(psm, PSM_SCRIPT);
+ break;
+ default:
+ break;
+ }
+ /* XXX an error here would require a full abort */
+ (void) rpmChrootOut();
+ }
+ rpmpsmFree(psm);
+ return rc;
+}
diff --git a/lib/query.c b/lib/query.c
new file mode 100644
index 0000000..308fe65
--- /dev/null
+++ b/lib/query.c
@@ -0,0 +1,575 @@
+/** \ingroup rpmcli
+ * \file lib/query.c
+ * Display tag values from package metadata.
+ */
+
+#include "system.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <ctype.h>
+
+#include <rpm/rpmcli.h>
+#include <rpm/header.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmfileutil.h> /* rpmCleanPath */
+
+#include "lib/rpmgi.h"
+#include "lib/manifest.h"
+
+#include "debug.h"
+
+
+/**
+ */
+static void printFileInfo(const char * name,
+ rpm_loff_t size, unsigned short mode,
+ unsigned int mtime,
+ unsigned short rdev, unsigned int nlink,
+ const char * owner, const char * group,
+ const char * linkto)
+{
+ char sizefield[21];
+ char ownerfield[8+1], groupfield[8+1];
+ char timefield[100];
+ time_t when = mtime; /* important if sizeof(int32_t) ! sizeof(time_t) */
+ struct tm * tm;
+ static time_t now;
+ static struct tm nowtm;
+ char * perms = rpmPermsString(mode);
+ char *link = NULL;
+
+ /* On first call, grab snapshot of now */
+ if (now == 0) {
+ now = time(NULL);
+ tm = localtime(&now);
+ if (tm) nowtm = *tm; /* structure assignment */
+ }
+
+ rstrlcpy(ownerfield, owner, sizeof(ownerfield));
+ rstrlcpy(groupfield, group, sizeof(groupfield));
+
+ /* this is normally right */
+ snprintf(sizefield, sizeof(sizefield), "%20" PRIu64, size);
+
+ /* this knows too much about dev_t */
+
+ if (S_ISLNK(mode)) {
+ rasprintf(&link, "%s -> %s", name, linkto);
+ } else if (S_ISCHR(mode)) {
+ perms[0] = 'c';
+ snprintf(sizefield, sizeof(sizefield), "%3u, %3u", ((unsigned)(rdev >> 8) & 0xff),
+ ((unsigned)rdev & 0xff));
+ } else if (S_ISBLK(mode)) {
+ perms[0] = 'b';
+ snprintf(sizefield, sizeof(sizefield), "%3u, %3u", ((unsigned)(rdev >> 8) & 0xff),
+ ((unsigned)rdev & 0xff));
+ }
+
+ /* Convert file mtime to display format */
+ tm = localtime(&when);
+ timefield[0] = '\0';
+ if (tm != NULL)
+ { const char *fmt;
+ if (now > when + 6L * 30L * 24L * 60L * 60L || /* Old. */
+ now < when - 60L * 60L) /* In the future. */
+ {
+ /* The file is fairly old or in the future.
+ * POSIX says the cutoff is 6 months old;
+ * approximate this by 6*30 days.
+ * Allow a 1 hour slop factor for what is considered "the future",
+ * to allow for NFS server/client clock disagreement.
+ * Show the year instead of the time of day.
+ */
+ fmt = "%b %e %Y";
+ } else {
+ fmt = "%b %e %H:%M";
+ }
+ (void)strftime(timefield, sizeof(timefield) - 1, fmt, tm);
+ }
+
+ rpmlog(RPMLOG_NOTICE, "%s %4d %-8s%-8s %10s %s %s\n", perms,
+ (int)nlink, ownerfield, groupfield, sizefield, timefield,
+ link ? link : name);
+ free(perms);
+ free(link);
+}
+
+int showQueryPackage(QVA_t qva, rpmts ts, Header h)
+{
+ rpmfi fi = NULL;
+ rpmfiFlags fiflags = (RPMFI_NOHEADER | RPMFI_FLAGS_QUERY);
+ int rc = 0; /* XXX FIXME: need real return code */
+
+ if (qva->qva_queryFormat != NULL) {
+ const char *errstr;
+ char *str = headerFormat(h, qva->qva_queryFormat, &errstr);
+
+ if ( str != NULL ) {
+ rpmlog(RPMLOG_NOTICE, "%s", str);
+ free(str);
+ } else {
+ rpmlog(RPMLOG_ERR, _("incorrect format: %s\n"), errstr);
+ }
+ }
+
+ if (!(qva->qva_flags & QUERY_FOR_LIST))
+ goto exit;
+
+ if (!(qva->qva_flags & QUERY_FOR_DUMPFILES))
+ fiflags |= RPMFI_NOFILEDIGESTS;
+
+ fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, fiflags);
+ if (rpmfiFC(fi) <= 0) {
+ rpmlog(RPMLOG_NOTICE, _("(contains no files)\n"));
+ goto exit;
+ }
+
+ fi = rpmfiInit(fi, 0);
+ while (rpmfiNext(fi) >= 0) {
+ rpmfileAttrs fflags = rpmfiFFlags(fi);
+ rpm_mode_t fmode = rpmfiFMode(fi);
+ rpm_rdev_t frdev = rpmfiFRdev(fi);
+ rpm_time_t fmtime = rpmfiFMtime(fi);
+ rpmfileState fstate = rpmfiFState(fi);
+ rpm_loff_t fsize = rpmfiFSize(fi);
+ const char *fn = rpmfiFN(fi);
+ const char *fuser = rpmfiFUser(fi);
+ const char *fgroup = rpmfiFGroup(fi);
+ const char *flink = rpmfiFLink(fi);
+ char *buf = NULL;
+
+ /* If querying only docs, skip non-doc files. */
+ if ((qva->qva_flags & QUERY_FOR_DOCS) && !(fflags & RPMFILE_DOC))
+ continue;
+
+ /* If querying only configs, skip non-config files. */
+ if ((qva->qva_flags & QUERY_FOR_CONFIG) && !(fflags & RPMFILE_CONFIG))
+ continue;
+
+ /* If not querying %ghost, skip ghost files. */
+ if ((qva->qva_fflags & RPMFILE_GHOST) && (fflags & RPMFILE_GHOST))
+ continue;
+
+ if (qva->qva_flags & QUERY_FOR_STATE) {
+ switch (fstate) {
+ case RPMFILE_STATE_NORMAL:
+ rstrcat(&buf, _("normal "));
+ break;
+ case RPMFILE_STATE_REPLACED:
+ rstrcat(&buf, _("replaced "));
+ break;
+ case RPMFILE_STATE_NOTINSTALLED:
+ rstrcat(&buf, _("not installed "));
+ break;
+ case RPMFILE_STATE_NETSHARED:
+ rstrcat(&buf, _("net shared "));
+ break;
+ case RPMFILE_STATE_WRONGCOLOR:
+ rstrcat(&buf, _("wrong color "));
+ break;
+ case RPMFILE_STATE_MISSING:
+ rstrcat(&buf, _("(no state) "));
+ break;
+ default:
+ rasprintf(&buf, _("(unknown %3d) "), fstate);
+ break;
+ }
+ }
+
+ if (qva->qva_flags & QUERY_FOR_DUMPFILES) {
+ char *add, *fdigest;
+ fdigest = rpmfiFDigestHex(fi, NULL);
+ rasprintf(&add, "%s %" PRIu64 " %d %s 0%o ",
+ fn, fsize, fmtime, fdigest ? fdigest : "", fmode);
+ rstrcat(&buf, add);
+ free(add);
+ free(fdigest);
+
+ if (fuser && fgroup) {
+ rasprintf(&add, "%s %s", fuser, fgroup);
+ rstrcat(&buf, add);
+ free(add);
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("package has not file owner/group lists\n"));
+ }
+
+ rasprintf(&add, " %s %s %u %s",
+ fflags & RPMFILE_CONFIG ? "1" : "0",
+ fflags & RPMFILE_DOC ? "1" : "0",
+ frdev,
+ (flink && *flink ? flink : "X"));
+ rpmlog(RPMLOG_NOTICE, "%s%s\n", buf, add);
+ free(add);
+ } else
+ if (!rpmIsVerbose()) {
+ rpmlog(RPMLOG_NOTICE, "%s%s\n", buf ? buf : "", fn);
+ }
+ else {
+ uint32_t fnlink = rpmfiFNlink(fi);
+
+ /* XXX Adjust directory link count and size for display output. */
+ if (S_ISDIR(fmode)) {
+ fnlink++;
+ fsize = 0;
+ }
+
+ if (fuser && fgroup) {
+ if (buf) {
+ rpmlog(RPMLOG_NOTICE, "%s", buf);
+ }
+ printFileInfo(fn, fsize, fmode, fmtime, frdev, fnlink,
+ fuser, fgroup, flink);
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("package has neither file owner or id lists\n"));
+ }
+ }
+ free(buf);
+ }
+
+ rc = 0;
+
+exit:
+ fi = rpmfiFree(fi);
+ return rc;
+}
+
+void rpmDisplayQueryTags(FILE * fp)
+{
+ static const char * const tagTypeNames[] = {
+ "", "char", "int8", "int16", "int32", "int64",
+ "string", "blob", "argv", "i18nstring"
+ };
+ const char *tname, *sname;
+ rpmtd names = rpmtdNew();
+ (void) rpmTagGetNames(names, 1);
+
+ while ((tname = rpmtdNextString(names))) {
+ sname = tname + strlen("RPMTAG_");
+ if (rpmIsVerbose()) {
+ rpmTagVal tag = rpmTagGetValue(sname);
+ rpmTagType type = rpmTagGetTagType(tag);
+ fprintf(fp, "%-20s %6d", sname, tag);
+ if (type > RPM_NULL_TYPE && type <= RPM_MAX_TYPE)
+ fprintf(fp, " %s", tagTypeNames[type]);
+ } else {
+ fprintf(fp, "%s", sname);
+ }
+ fprintf(fp, "\n");
+ }
+ rpmtdFreeData(names);
+ rpmtdFree(names);
+}
+
+static int rpmgiShowMatches(QVA_t qva, rpmts ts, rpmgi gi)
+{
+ int ec = 0;
+ Header h;
+
+ while ((h = rpmgiNext(gi)) != NULL) {
+ int rc;
+
+ rpmdbCheckSignals();
+ if ((rc = qva->qva_showPackage(qva, ts, h)) != 0)
+ ec = rc;
+ headerFree(h);
+ }
+ return ec + rpmgiNumErrors(gi);
+}
+
+static int rpmcliShowMatches(QVA_t qva, rpmts ts, rpmdbMatchIterator mi)
+{
+ Header h;
+ int ec = 0;
+
+ if (mi == NULL)
+ return 1;
+
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ int rc;
+ rpmdbCheckSignals();
+ if ((rc = qva->qva_showPackage(qva, ts, h)) != 0)
+ ec = rc;
+ }
+ return ec;
+}
+
+static rpmdbMatchIterator initQueryIterator(QVA_t qva, rpmts ts, const char * arg)
+{
+ const char * s;
+ int i;
+ int provides_checked = 0;
+ rpmdbMatchIterator mi = NULL;
+
+ (void) rpmdbCheckSignals();
+
+ if (qva->qva_showPackage == NULL)
+ goto exit;
+
+ switch (qva->qva_source) {
+ case RPMQV_GROUP:
+ mi = rpmtsInitIterator(ts, RPMDBI_GROUP, arg, 0);
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE,
+ _("group %s does not contain any packages\n"), arg);
+ }
+ break;
+
+ case RPMQV_TRIGGEREDBY:
+ mi = rpmtsInitIterator(ts, RPMDBI_TRIGGERNAME, arg, 0);
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE, _("no package triggers %s\n"), arg);
+ }
+ break;
+
+ case RPMQV_PKGID:
+ { unsigned char MD5[16];
+ unsigned char * t;
+
+ for (i = 0, s = arg; *s && isxdigit(*s); s++, i++)
+ {};
+ if (i != 32) {
+ rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "pkgid", arg);
+ goto exit;
+ }
+
+ MD5[0] = '\0';
+ for (i = 0, t = MD5, s = arg; i < 16; i++, t++, s += 2)
+ *t = (rnibble(s[0]) << 4) | rnibble(s[1]);
+
+ mi = rpmtsInitIterator(ts, RPMDBI_SIGMD5, MD5, sizeof(MD5));
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
+ "pkgid", arg);
+ }
+ } break;
+
+ case RPMQV_HDRID:
+ for (i = 0, s = arg; *s && isxdigit(*s); s++, i++)
+ {};
+ if (i != 40) {
+ rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "hdrid", arg);
+ goto exit;
+ }
+
+ mi = rpmtsInitIterator(ts, RPMDBI_SHA1HEADER, arg, 0);
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
+ "hdrid", arg);
+ }
+ break;
+
+ case RPMQV_TID:
+ { char * end = NULL;
+ rpm_tid_t iid = strtoul(arg, &end, 0);
+
+ if ((*end) || (end == arg) || (iid == UINT_MAX)) {
+ rpmlog(RPMLOG_ERR, _("malformed %s: %s\n"), "tid", arg);
+ goto exit;
+ }
+ mi = rpmtsInitIterator(ts, RPMDBI_INSTALLTID, &iid, sizeof(iid));
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE, _("no package matches %s: %s\n"),
+ "tid", arg);
+ }
+ } break;
+
+ case RPMQV_WHATREQUIRES:
+ mi = rpmtsInitIterator(ts, RPMDBI_REQUIRENAME, arg, 0);
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE, _("no package requires %s\n"), arg);
+ }
+ break;
+
+ case RPMQV_WHATPROVIDES:
+ if (arg[0] != '/') {
+ provides_checked = 1;
+ mi = rpmtsInitIterator(ts, RPMDBI_PROVIDENAME, arg, 0);
+ if (mi == NULL) {
+ rpmlog(RPMLOG_NOTICE, _("no package provides %s\n"), arg);
+ }
+ break;
+ }
+ case RPMQV_PATH:
+ { char * fn;
+
+ for (s = arg; *s != '\0'; s++)
+ if (!(*s == '.' || *s == '/'))
+ break;
+
+ if (*s == '\0') {
+ char fnbuf[PATH_MAX];
+ fn = realpath(arg, fnbuf);
+ fn = xstrdup( (fn != NULL ? fn : arg) );
+ } else if (*arg != '/') {
+ char *curDir = rpmGetCwd();
+ fn = (char *) rpmGetPath(curDir, "/", arg, NULL);
+ curDir = _free(curDir);
+ } else
+ fn = xstrdup(arg);
+ (void) rpmCleanPath(fn);
+
+ mi = rpmtsInitIterator(ts, RPMDBI_BASENAMES, fn, 0);
+ if (mi == NULL && !provides_checked)
+ mi = rpmtsInitIterator(ts, RPMDBI_PROVIDENAME, fn, 0);
+
+ if (mi == NULL) {
+ struct stat sb;
+ if (lstat(fn, &sb) != 0)
+ rpmlog(RPMLOG_ERR, _("file %s: %s\n"), fn, strerror(errno));
+ else
+ rpmlog(RPMLOG_NOTICE,
+ _("file %s is not owned by any package\n"), fn);
+ }
+
+ fn = _free(fn);
+ } break;
+
+ case RPMQV_DBOFFSET:
+ { char * end = NULL;
+ unsigned int recOffset = strtoul(arg, &end, 0);
+
+ if ((*end) || (end == arg) || (recOffset == UINT_MAX)) {
+ rpmlog(RPMLOG_ERR, _("invalid package number: %s\n"), arg);
+ goto exit;
+ }
+ rpmlog(RPMLOG_DEBUG, "package record number: %u\n", recOffset);
+ /* RPMDBI_PACKAGES */
+ mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, &recOffset, sizeof(recOffset));
+ if (mi == NULL) {
+ rpmlog(RPMLOG_ERR, _("record %u could not be read\n"), recOffset);
+ }
+ } break;
+
+ case RPMQV_PACKAGE:
+ {
+ int matches = 0;
+ mi = rpmtsInitIterator(ts, RPMDBI_LABEL, arg, 0);
+ while (rpmdbNextIterator(mi) != NULL) {
+ matches++;
+ }
+ mi = rpmdbFreeIterator(mi);
+ if (! matches) {
+ rpmlog(RPMLOG_NOTICE, _("package %s is not installed\n"), arg);
+ } else {
+ mi = rpmtsInitIterator(ts, RPMDBI_LABEL, arg, 0);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+exit:
+ return mi;
+}
+
+/*
+ * Initialize db iterator with optional filters. By default patterns
+ * applied to package name, others can be specified with <tagname>=<pattern>
+ */
+static rpmdbMatchIterator initFilterIterator(rpmts ts, ARGV_const_t argv)
+{
+ rpmdbMatchIterator mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+
+ for (ARGV_const_t arg = argv; arg && *arg != NULL; arg++) {
+ rpmTagVal tag = RPMTAG_NAME;
+ char a[strlen(*arg)+1], *ae;
+ const char *pat = a;
+
+ strcpy(a, *arg);
+
+ /* Parse for "tag=pattern" args. */
+ if ((ae = strchr(a, '=')) != NULL) {
+ *ae++ = '\0';
+ tag = rpmTagGetValue(a);
+ if (tag == RPMTAG_NOT_FOUND) {
+ rpmlog(RPMLOG_ERR, _("unknown tag: \"%s\"\n"), a);
+ mi = rpmdbFreeIterator(mi);
+ break;
+ }
+ pat = ae;
+ }
+
+ rpmdbSetIteratorRE(mi, tag, RPMMIRE_DEFAULT, pat);
+ }
+
+ return mi;
+}
+
+int rpmcliArgIter(rpmts ts, QVA_t qva, ARGV_const_t argv)
+{
+ int ec = 0;
+
+ switch (qva->qva_source) {
+ case RPMQV_ALL: {
+ rpmdbMatchIterator mi = initFilterIterator(ts, argv);
+ ec = rpmcliShowMatches(qva, ts, mi);
+ mi = rpmdbFreeIterator(mi);
+ break;
+ }
+ case RPMQV_RPM: {
+ rpmgi gi = rpmgiNew(ts, giFlags, argv);
+ ec = rpmgiShowMatches(qva, ts, gi);
+ gi = rpmgiFree(gi);
+ break;
+ }
+ case RPMQV_SPECRPMS:
+ case RPMQV_SPECSRPM:
+ for (ARGV_const_t arg = argv; arg && *arg; arg++) {
+ ec += ((qva->qva_specQuery != NULL)
+ ? qva->qva_specQuery(ts, qva, *arg) : 1);
+ }
+ break;
+ default:
+ for (ARGV_const_t arg = argv; arg && *arg; arg++) {
+ rpmdbMatchIterator mi = initQueryIterator(qva, ts, *arg);
+ ec += rpmcliShowMatches(qva, ts, mi);
+ rpmdbFreeIterator(mi);
+ }
+ break;
+ }
+
+ return ec;
+}
+
+int rpmcliQuery(rpmts ts, QVA_t qva, char * const * argv)
+{
+ rpmVSFlags vsflags, ovsflags;
+ int ec = 0;
+
+ if (qva->qva_showPackage == NULL)
+ qva->qva_showPackage = showQueryPackage;
+
+ /* If --queryformat unspecified, then set default now. */
+ if (!(qva->qva_flags & _QUERY_FOR_BITS) && qva->qva_queryFormat == NULL) {
+ char * fmt = rpmExpand("%{?_query_all_fmt}\n", NULL);
+ if (fmt == NULL || strlen(fmt) <= 1) {
+ fmt = _free(fmt);
+ fmt = xstrdup("%{nvra}\n");
+ }
+ qva->qva_queryFormat = fmt;
+ }
+
+ vsflags = rpmExpandNumeric("%{?_vsflags_query}");
+ if (rpmcliQueryFlags & VERIFY_DIGEST)
+ vsflags |= _RPMVSF_NODIGESTS;
+ if (rpmcliQueryFlags & VERIFY_SIGNATURE)
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ if (rpmcliQueryFlags & VERIFY_HDRCHK)
+ vsflags |= RPMVSF_NOHDRCHK;
+
+ ovsflags = rpmtsSetVSFlags(ts, vsflags);
+ ec = rpmcliArgIter(ts, qva, argv);
+ vsflags = rpmtsSetVSFlags(ts, ovsflags);
+
+ if (qva->qva_showPackage == showQueryPackage)
+ qva->qva_showPackage = NULL;
+
+ return ec;
+}
diff --git a/lib/rpmal.c b/lib/rpmal.c
new file mode 100644
index 0000000..2f934c7
--- /dev/null
+++ b/lib/rpmal.c
@@ -0,0 +1,406 @@
+/** \ingroup rpmdep
+ * \file lib/rpmal.c
+ */
+
+#include "system.h"
+
+
+#include <rpm/rpmds.h>
+#include <rpm/rpmte.h>
+#include <rpm/rpmfi.h>
+
+#include "lib/rpmal.h"
+#include "lib/misc.h"
+#include "lib/rpmte_internal.h"
+
+#include "debug.h"
+
+typedef struct availablePackage_s * availablePackage;
+typedef int rpmalNum;
+
+/** \ingroup rpmdep
+ * Info about a single package to be installed.
+ */
+struct availablePackage_s {
+ rpmte p; /*!< transaction member */
+ rpmds provides; /*!< Provides: dependencies. */
+ rpmfi fi; /*!< File info set. */
+};
+
+/** \ingroup rpmdep
+ * A single available item (e.g. a Provides: dependency).
+ */
+typedef struct availableIndexEntry_s {
+ rpmalNum pkgNum; /*!< Containing package index. */
+ unsigned int entryIx; /*!< Dependency index. */
+} * availableIndexEntry;
+
+struct fileNameEntry_s {
+ const char * dirName;
+ const char * baseName;
+};
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+#define HASHTYPE rpmalProvidesHash
+#define HTKEYTYPE const char *
+#define HTDATATYPE struct availableIndexEntry_s
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+#define HASHTYPE rpmalFileHash
+#define HTKEYTYPE struct fileNameEntry_s
+#define HTDATATYPE struct availableIndexEntry_s
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+
+/** \ingroup rpmdep
+ * Set of available packages, items, and directories.
+ */
+struct rpmal_s {
+ availablePackage list; /*!< Set of packages. */
+ rpmalProvidesHash providesHash;
+ rpmalFileHash fileHash;
+ int delta; /*!< Delta for pkg list reallocation. */
+ int size; /*!< No. of pkgs in list. */
+ int alloced; /*!< No. of pkgs allocated for list. */
+ rpm_color_t tscolor; /*!< Transaction color. */
+ rpm_color_t prefcolor; /*!< Transaction preferred color. */
+};
+
+/**
+ * Destroy available item index.
+ * @param al available list
+ */
+static void rpmalFreeIndex(rpmal al)
+{
+ al->providesHash = rpmalProvidesHashFree(al->providesHash);
+ al->fileHash = rpmalFileHashFree(al->fileHash);
+}
+
+rpmal rpmalCreate(int delta, rpm_color_t tscolor, rpm_color_t prefcolor)
+{
+ rpmal al = xcalloc(1, sizeof(*al));
+
+ al->delta = delta;
+ al->size = 0;
+ al->alloced = al->delta;
+ al->list = xmalloc(sizeof(*al->list) * al->alloced);;
+
+ al->providesHash = NULL;
+ al->fileHash = NULL;
+ al->tscolor = tscolor;
+ al->prefcolor = prefcolor;
+
+ return al;
+}
+
+rpmal rpmalFree(rpmal al)
+{
+ availablePackage alp;
+ int i;
+
+ if (al == NULL)
+ return NULL;
+
+ if ((alp = al->list) != NULL)
+ for (i = 0; i < al->size; i++, alp++) {
+ alp->provides = rpmdsFree(alp->provides);
+ alp->fi = rpmfiFree(alp->fi);
+ }
+ al->list = _free(al->list);
+ al->alloced = 0;
+
+ rpmalFreeIndex(al);
+ al = _free(al);
+ return NULL;
+}
+
+static unsigned int fileHash(struct fileNameEntry_s file){
+ return hashFunctionString(file.dirName) ^ hashFunctionString(file.baseName);
+}
+
+static int fileCompare(struct fileNameEntry_s one, struct fileNameEntry_s two) {
+ int rc = 0;
+ rc = strcmp(one.dirName, two.dirName);
+ if (!rc)
+ rc = strcmp(one.baseName, two.baseName);
+ return rc;
+}
+
+void rpmalDel(rpmal al, rpmte p)
+{
+ availablePackage alp;
+ rpmalNum pkgNum;
+
+ if (al == NULL || al->list == NULL)
+ return; /* XXX can't happen */
+
+ // XXX use a search for self provide
+ for (pkgNum=0; pkgNum<al->size; pkgNum++) {
+ if (al->list[pkgNum].p == p) {
+ break;
+ }
+ }
+ if (pkgNum == al->size ) return; // Not found!
+
+ alp = al->list + pkgNum;
+ // do not actually delete, just set p to NULL
+ // and later filter that out of the results
+ alp->p = NULL;
+}
+
+static void rpmalAddFiles(rpmal al, rpmalNum pkgNum, rpmfi fi){
+ struct fileNameEntry_s fileName;
+ struct availableIndexEntry_s fileEntry;
+ int i;
+ rpm_color_t ficolor;
+
+ fileEntry.pkgNum = pkgNum;
+
+ fi = rpmfiInit(fi, 0);
+ while ((i = rpmfiNext(fi)) >= 0) {
+ /* Ignore colored provides not in our rainbow. */
+ ficolor = rpmfiFColor(fi);
+ if (al->tscolor && ficolor && !(al->tscolor & ficolor))
+ continue;
+
+ fileName.dirName = rpmfiDN(fi);
+ fileName.baseName = rpmfiBN(fi);
+
+ fileEntry.entryIx = i;
+
+ rpmalFileHashAddEntry(al->fileHash, fileName, fileEntry);
+ }
+}
+
+static void rpmalAddProvides(rpmal al, rpmalNum pkgNum, rpmds provides){
+ struct availableIndexEntry_s indexEntry;
+ rpm_color_t dscolor;
+
+ indexEntry.pkgNum = pkgNum;
+
+ if (rpmdsInit(provides) != NULL)
+ while (rpmdsNext(provides) >= 0) {
+ /* Ignore colored provides not in our rainbow. */
+ dscolor = rpmdsColor(provides);
+ if (al->tscolor && dscolor && !(al->tscolor & dscolor))
+ continue;
+
+ indexEntry.entryIx = rpmdsIx(provides);
+ rpmalProvidesHashAddEntry(al->providesHash, rpmdsN(provides), indexEntry);
+ }
+}
+
+void rpmalAdd(rpmal al, rpmte p)
+{
+ rpmalNum pkgNum;
+ availablePackage alp;
+
+ if (al->size == al->alloced) {
+ al->alloced += al->delta;
+ al->list = xrealloc(al->list, sizeof(*al->list) * al->alloced);
+ }
+ pkgNum = al->size++;
+
+ alp = al->list + pkgNum;
+
+ alp->p = p;
+
+ alp->provides = rpmdsLink(rpmteDS(p, RPMTAG_PROVIDENAME));
+ alp->fi = rpmfiLink(rpmteFI(p));
+
+ if (al->providesHash != NULL) { // index is already created
+ rpmalAddProvides(al, pkgNum, alp->provides);
+ rpmalAddFiles(al, pkgNum, alp->fi);
+ }
+
+ assert(((rpmalNum)(alp - al->list)) == pkgNum);
+}
+
+static void rpmalMakeIndex(rpmal al)
+{
+ availablePackage alp;
+ int i;
+ int providesCnt = 0;
+ int fileCnt = 0;
+
+ if (al == NULL || al->list == NULL) return;
+ if (al->providesHash != NULL || al->fileHash != NULL)
+ return;
+ for (i = 0; i < al->size; i++) {
+ alp = al->list + i;
+ if (alp->provides != NULL)
+ providesCnt += rpmdsCount(alp->provides);
+ if (alp->fi != NULL)
+ fileCnt += rpmfiFC(alp->fi);
+ }
+
+ al->providesHash = rpmalProvidesHashCreate(providesCnt/4+128, hashFunctionString,
+ strcmp, NULL, NULL);
+ al->fileHash = rpmalFileHashCreate(fileCnt/4+128, fileHash, fileCompare,
+ NULL, NULL);
+
+ for (i = 0; i < al->size; i++) {
+ alp = al->list + i;
+ rpmalAddProvides(al, i, alp->provides);
+ rpmalAddFiles(al, i, alp->fi);
+ }
+}
+
+static rpmte * rpmalAllFileSatisfiesDepend(const rpmal al, const rpmds ds)
+{
+ const char *fileName = rpmdsN(ds);
+ const char *slash;
+ rpmte * ret = NULL;
+
+ if (al == NULL || fileName == NULL || *fileName != '/')
+ return NULL;
+
+ /* Split path into dirname and basename components for lookup */
+ if ((slash = strrchr(fileName, '/')) != NULL) {
+ availableIndexEntry result;
+ int resultCnt = 0;
+ size_t bnStart = (slash - fileName) + 1;
+ char dirName[bnStart + 1];
+ struct fileNameEntry_s fne = {
+ .baseName = fileName + bnStart,
+ .dirName = dirName,
+ };
+ strncpy(dirName, fileName, bnStart);
+ dirName[bnStart] = '\0';
+
+ rpmalFileHashGetEntry(al->fileHash, fne, &result, &resultCnt, NULL);
+
+ if (resultCnt > 0) {
+ int i, found;
+ ret = xmalloc((resultCnt+1) * sizeof(*ret));
+
+ for (found = i = 0; i < resultCnt; i++) {
+ availablePackage alp = al->list + result[i].pkgNum;
+ if (alp->p == NULL) // deleted
+ continue;
+
+ rpmdsNotify(ds, "(added files)", 0);
+
+ ret[found] = alp->p;
+ found++;
+ }
+ ret[found] = NULL;
+ }
+ }
+
+ return ret;
+}
+
+static rpmte * rpmalAllSatisfiesDepend(const rpmal al, const rpmds ds)
+{
+ rpmte * ret = NULL;
+ int i, found;
+ const char * name;
+ availableIndexEntry result;
+ int resultCnt;
+
+ availablePackage alp;
+ int rc;
+
+ if (al == NULL || ds == NULL || (name = rpmdsN(ds)) == NULL)
+ return ret;
+
+ if (al->providesHash == NULL && al->fileHash == NULL)
+ rpmalMakeIndex(al);
+
+ if (*name == '/') {
+ /* First, look for files "contained" in package ... */
+ ret = rpmalAllFileSatisfiesDepend(al, ds);
+ if (ret != NULL && *ret != NULL)
+ return ret;
+ /* ... then, look for files "provided" by package. */
+ ret = _free(ret);
+ }
+
+ rpmalProvidesHashGetEntry(al->providesHash, name, &result,
+ &resultCnt, NULL);
+
+ if (resultCnt==0) return NULL;
+
+ ret = xmalloc((resultCnt+1) * sizeof(*ret));
+
+ for (found=i=0; i<resultCnt; i++) {
+ alp = al->list + result[i].pkgNum;
+ if (alp->p == NULL) // deleted
+ continue;
+ (void) rpmdsSetIx(alp->provides, result[i].entryIx);
+ rc = 0;
+ if (rpmdsIx(alp->provides) >= 0)
+ rc = rpmdsCompare(alp->provides, ds);
+
+ if (rc) {
+ rpmdsNotify(ds, "(added provide)", 0);
+ ret[found] = alp->p;
+ found++;
+ }
+ }
+ ret[found] = NULL;
+
+ return ret;
+}
+
+rpmte
+rpmalSatisfiesDepend(const rpmal al, const rpmds ds)
+{
+ rpmte *providers = rpmalAllSatisfiesDepend(al, ds);
+ rpmte best = NULL;
+
+ if (providers) {
+ if (al->tscolor) {
+ /*
+ * For colored dependencies, try to find a matching provider.
+ * Otherwise prefer provider of ts preferred color.
+ */
+ rpm_color_t dscolor = rpmdsColor(ds);
+ for (rpmte *p = providers; *p; p++) {
+ rpm_color_t tecolor = rpmteColor(*p);
+ if (dscolor) {
+ if (dscolor == tecolor) best = *p;
+ } else if (al->prefcolor) {
+ if (al->prefcolor == tecolor) best = *p;
+ }
+ if (best) break;
+ }
+ }
+ /* if not decided by now, just pick first match */
+ if (!best) best = providers[0];
+ free(providers);
+ }
+ return best;
+}
+
+rpmte *
+rpmalAllInCollection(const rpmal al, const char *collname)
+{
+ rpmte *ret = NULL;
+ int found = 0;
+ rpmalNum pkgNum;
+
+ if (!al || !al->list || !collname)
+ return NULL;
+
+ for (pkgNum = 0; pkgNum < al->size; pkgNum++) {
+ rpmte p = al->list[pkgNum].p;
+ if (rpmteHasCollection(p, collname)) {
+ ret = xrealloc(ret, sizeof(*ret) * (found + 1 + 1));
+ ret[found] = p;
+ found++;
+ }
+ }
+ if (ret) {
+ ret[found] = NULL;
+ }
+
+ return ret;
+}
diff --git a/lib/rpmal.h b/lib/rpmal.h
new file mode 100644
index 0000000..9a0e640
--- /dev/null
+++ b/lib/rpmal.h
@@ -0,0 +1,75 @@
+#ifndef H_RPMAL
+#define H_RPMAL
+
+/** \ingroup rpmdep rpmtrans
+ * \file lib/rpmal.h
+ * Structures used for managing added/available package lists.
+ */
+
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct rpmal_s * rpmal;
+
+/**
+ * Initialize available packckages, items, and directory list.
+ * @param delta no. of entries to add on each realloc
+ * @param tscolor transaction color bits
+ * @param prefcolor preferred color
+ * @return al new available list
+ */
+RPM_GNUC_INTERNAL
+rpmal rpmalCreate(int delta, rpm_color_t tscolor, rpm_color_t prefcolor);
+
+/**
+ * Free available packages, items, and directory members.
+ * @param al available list
+ * @return NULL always
+ */
+RPM_GNUC_INTERNAL
+rpmal rpmalFree(rpmal al);
+
+/**
+ * Delete package from available list.
+ * @param al available list
+ * @param p package
+ */
+RPM_GNUC_INTERNAL
+void rpmalDel(rpmal al, rpmte p);
+
+/**
+ * Add package to available list.
+ * @param al available list
+ * @param p package
+ */
+RPM_GNUC_INTERNAL
+void rpmalAdd(rpmal al, rpmte p);
+
+/**
+ * Lookup best provider for a dependency in the available list
+ * @param al available list
+ * @param ds dependency set
+ * @return best provider for the dependency, NULL if none
+ */
+RPM_GNUC_INTERNAL
+rpmte rpmalSatisfiesDepend(const rpmal al, const rpmds ds);
+
+/**
+ * Get a list of transaction elements that are memebers of a collection in the
+ * available list
+ * @param al available list
+ * @param collname collection name to search for
+ * @return NULL-terminated list of transaction elements that are
+ * members of the specified collection
+ */
+RPM_GNUC_INTERNAL
+rpmte * rpmalAllInCollection(const rpmal al, const char * collname);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMAL */
diff --git a/lib/rpmcallback.h b/lib/rpmcallback.h
new file mode 100644
index 0000000..3544567
--- /dev/null
+++ b/lib/rpmcallback.h
@@ -0,0 +1,47 @@
+#ifndef _RPMCALLBACK_H
+#define _RPMCALLBACK_H
+
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Bit(s) to identify progress callbacks.
+ */
+typedef enum rpmCallbackType_e {
+ RPMCALLBACK_UNKNOWN = 0,
+ RPMCALLBACK_INST_PROGRESS = (1 << 0),
+ RPMCALLBACK_INST_START = (1 << 1),
+ RPMCALLBACK_INST_OPEN_FILE = (1 << 2),
+ RPMCALLBACK_INST_CLOSE_FILE = (1 << 3),
+ RPMCALLBACK_TRANS_PROGRESS = (1 << 4),
+ RPMCALLBACK_TRANS_START = (1 << 5),
+ RPMCALLBACK_TRANS_STOP = (1 << 6),
+ RPMCALLBACK_UNINST_PROGRESS = (1 << 7),
+ RPMCALLBACK_UNINST_START = (1 << 8),
+ RPMCALLBACK_UNINST_STOP = (1 << 9),
+ RPMCALLBACK_REPACKAGE_PROGRESS = (1 << 10), /* obsolete, unused */
+ RPMCALLBACK_REPACKAGE_START = (1 << 11), /* obsolete, unused */
+ RPMCALLBACK_REPACKAGE_STOP = (1 << 12), /* obsolete, unused */
+ RPMCALLBACK_UNPACK_ERROR = (1 << 13),
+ RPMCALLBACK_CPIO_ERROR = (1 << 14),
+ RPMCALLBACK_SCRIPT_ERROR = (1 << 15)
+} rpmCallbackType;
+
+/**
+ */
+typedef void * (*rpmCallbackFunction)
+ (const void * h,
+ const rpmCallbackType what,
+ const rpm_loff_t amount,
+ const rpm_loff_t total,
+ fnpyKey key,
+ rpmCallbackData data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMCALLBACK_H */
diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c
new file mode 100644
index 0000000..850ca62
--- /dev/null
+++ b/lib/rpmchecksig.c
@@ -0,0 +1,469 @@
+/** \ingroup rpmcli
+ * \file lib/rpmchecksig.c
+ * Verify the signature of a package.
+ */
+
+#include "system.h"
+
+#include <rpm/rpmlib.h> /* RPMSIGTAG & related */
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmcli.h>
+#include <rpm/rpmfileutil.h> /* rpmMkTemp() */
+#include <rpm/rpmdb.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmkeyring.h>
+
+#include "rpmio/digest.h"
+#include "rpmio/rpmio_internal.h" /* fdSetBundle() */
+#include "lib/rpmlead.h"
+#include "lib/signature.h"
+
+#include "debug.h"
+
+int _print_pkts = 0;
+
+static int doImport(rpmts ts, const char *fn, char *buf, ssize_t blen)
+{
+ char const * const pgpmark = "-----BEGIN PGP ";
+ size_t marklen = strlen(pgpmark);
+ int res = 0;
+ int keyno = 1;
+ char *start = strstr(buf, pgpmark);
+
+ while (start) {
+ uint8_t *pkt = NULL;
+ size_t pktlen = 0;
+
+ /* Read pgp packet. */
+ if (pgpParsePkts(start, &pkt, &pktlen) == PGPARMOR_PUBKEY) {
+ /* Import pubkey packet(s). */
+ if (rpmtsImportPubkey(ts, pkt, pktlen) != RPMRC_OK) {
+ rpmlog(RPMLOG_ERR, _("%s: key %d import failed.\n"), fn, keyno);
+ res++;
+ }
+ } else {
+ rpmlog(RPMLOG_ERR, _("%s: key %d not an armored public key.\n"),
+ fn, keyno);
+ res++;
+ }
+
+ /* See if there are more keys in the buffer */
+ if (start + marklen < buf + blen) {
+ start = strstr(start + marklen, pgpmark);
+ } else {
+ start = NULL;
+ }
+
+ keyno++;
+ free(pkt);
+ }
+ return res;
+}
+
+int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv)
+{
+ int res = 0;
+ for (ARGV_const_t arg = argv; arg && *arg; arg++) {
+ const char *fn = *arg;
+ uint8_t *buf = NULL;
+ ssize_t blen = 0;
+ char *t = NULL;
+ int iorc;
+
+ /* If arg looks like a keyid, then attempt keyserver retrieve. */
+ if (rstreqn(fn, "0x", 2)) {
+ const char * s = fn + 2;
+ int i;
+ for (i = 0; *s && isxdigit(*s); s++, i++)
+ {};
+ if (i == 8 || i == 16) {
+ t = rpmExpand("%{_hkp_keyserver_query}", fn+2, NULL);
+ if (t && *t != '%')
+ fn = t;
+ }
+ }
+
+ /* Read the file and try to import all contained keys */
+ iorc = rpmioSlurp(fn, &buf, &blen);
+ if (iorc || buf == NULL || blen < 64) {
+ rpmlog(RPMLOG_ERR, _("%s: import read failed(%d).\n"), fn, iorc);
+ res++;
+ } else {
+ res += doImport(ts, fn, (char *)buf, blen);
+ }
+
+ free(t);
+ free(buf);
+ }
+ return res;
+}
+
+/**
+ * @todo If the GPG key was known available, the md5 digest could be skipped.
+ */
+static int readFile(FD_t fd, const char * fn, pgpDig dig,
+ rpmDigestBundle plbundle, rpmDigestBundle hdrbundle)
+{
+ unsigned char buf[4*BUFSIZ];
+ ssize_t count;
+ int rc = 1;
+ Header h = NULL;
+
+ /* Read the header from the package. */
+ if ((h = headerRead(fd, HEADER_MAGIC_YES)) == NULL) {
+ rpmlog(RPMLOG_ERR, _("%s: headerRead failed\n"), fn);
+ goto exit;
+ }
+
+ if (headerIsEntry(h, RPMTAG_HEADERIMMUTABLE)) {
+ struct rpmtd_s utd;
+
+ if (!headerGet(h, RPMTAG_HEADERIMMUTABLE, &utd, HEADERGET_DEFAULT)){
+ rpmlog(RPMLOG_ERR,
+ _("%s: Immutable header region could not be read. "
+ "Corrupted package?\n"), fn);
+ goto exit;
+ }
+ rpmDigestBundleUpdate(hdrbundle, rpm_header_magic, sizeof(rpm_header_magic));
+ rpmDigestBundleUpdate(hdrbundle, utd.data, utd.count);
+ rpmtdFreeData(&utd);
+ }
+
+ /* Read the payload from the package. */
+ while ((count = Fread(buf, sizeof(buf[0]), sizeof(buf), fd)) > 0) {}
+ if (count < 0) {
+ rpmlog(RPMLOG_ERR, _("%s: Fread failed: %s\n"), fn, Fstrerror(fd));
+ goto exit;
+ }
+
+ rc = 0;
+
+exit:
+ headerFree(h);
+ return rc;
+}
+
+/* Parse the parameters from the OpenPGP packets that will be needed. */
+/* XXX TODO: unify with similar parsePGP() in package.c */
+static rpmRC parsePGP(rpmtd sigtd, const char *fn, pgpDig dig)
+{
+ rpmRC rc = RPMRC_FAIL;
+ int debug = (_print_pkts & rpmIsDebug());
+ if ((pgpPrtPkts(sigtd->data, sigtd->count, dig, debug) == 0) &&
+ (dig->signature.version == 3 || dig->signature.version == 4)) {
+ rc = RPMRC_OK;
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("skipping package %s with unverifiable V%u signature\n"), fn,
+ dig->signature.version);
+ }
+ return rc;
+}
+
+/*
+ * Figure best available signature.
+ * XXX TODO: Similar detection in rpmReadPackageFile(), unify these.
+ */
+static rpmTagVal bestSig(Header sigh, int nosignatures, int nodigests)
+{
+ rpmTagVal sigtag = 0;
+ if (sigtag == 0 && !nosignatures) {
+ if (headerIsEntry(sigh, RPMSIGTAG_DSA))
+ sigtag = RPMSIGTAG_DSA;
+ else if (headerIsEntry(sigh, RPMSIGTAG_RSA))
+ sigtag = RPMSIGTAG_RSA;
+ else if (headerIsEntry(sigh, RPMSIGTAG_GPG))
+ sigtag = RPMSIGTAG_GPG;
+ else if (headerIsEntry(sigh, RPMSIGTAG_PGP))
+ sigtag = RPMSIGTAG_PGP;
+ }
+ if (sigtag == 0 && !nodigests) {
+ if (headerIsEntry(sigh, RPMSIGTAG_MD5))
+ sigtag = RPMSIGTAG_MD5;
+ else if (headerIsEntry(sigh, RPMSIGTAG_SHA1))
+ sigtag = RPMSIGTAG_SHA1; /* XXX never happens */
+ }
+ return sigtag;
+}
+
+static const char *sigtagname(rpmTagVal sigtag, int upper)
+{
+ const char *n = NULL;
+
+ switch (sigtag) {
+ case RPMSIGTAG_SIZE:
+ n = (upper ? "SIZE" : "size");
+ break;
+ case RPMSIGTAG_SHA1:
+ n = (upper ? "SHA1" : "sha1");
+ break;
+ case RPMSIGTAG_MD5:
+ n = (upper ? "MD5" : "md5");
+ break;
+ case RPMSIGTAG_RSA:
+ n = (upper ? "RSA" : "rsa");
+ break;
+ case RPMSIGTAG_PGP5: /* XXX legacy */
+ case RPMSIGTAG_PGP:
+ n = (upper ? "(MD5) PGP" : "(md5) pgp");
+ break;
+ case RPMSIGTAG_DSA:
+ n = (upper ? "(SHA1) DSA" : "(sha1) dsa");
+ break;
+ case RPMSIGTAG_GPG:
+ n = (upper ? "GPG" : "gpg");
+ break;
+ default:
+ n = (upper ? "?UnknownSigatureType?" : "???");
+ break;
+ }
+ return n;
+}
+
+/*
+ * Format sigcheck result for output, appending the message spew to buf and
+ * bad/missing keyids to keyprob.
+ *
+ * In verbose mode, just dump it all. Otherwise ok signatures
+ * are dumped lowercase, bad sigs uppercase and for PGP/GPG
+ * if misssing/untrusted key it's uppercase in parenthesis
+ * and stash the key id as <SIGTYPE>#<keyid>. Pfft.
+ */
+static void formatResult(rpmTagVal sigtag, rpmRC sigres, const char *result,
+ int havekey, char **keyprob, char **buf)
+{
+ char *msg = NULL;
+ if (rpmIsVerbose()) {
+ rasprintf(&msg, " %s", result);
+ } else {
+ /* Check for missing / untrusted keys in result. */
+ const char *signame = sigtagname(sigtag, (sigres != RPMRC_OK));
+
+ if (havekey && (sigres == RPMRC_NOKEY || sigres == RPMRC_NOTTRUSTED)) {
+ const char *tempKey = strstr(result, "ey ID");
+ if (tempKey) {
+ char keyid[sizeof(pgpKeyID_t) + 1];
+ rstrlcpy(keyid, tempKey + 6, sizeof(keyid));
+ rstrscat(keyprob, " ", signame, "#", keyid, NULL);
+ }
+ }
+ rasprintf(&msg, (*keyprob ? "(%s) " : "%s "), signame);
+ }
+ rstrcat(buf, msg);
+ free(msg);
+}
+
+static int rpmpkgVerifySigs(rpmKeyring keyring, rpmQueryFlags flags,
+ FD_t fd, const char *fn)
+{
+
+ char *buf = NULL;
+ char *missingKeys = NULL;
+ char *untrustedKeys = NULL;
+ struct rpmtd_s sigtd;
+ rpmTagVal sigtag;
+ pgpDig dig = NULL;
+ pgpDigParams sigp;
+ Header sigh = NULL;
+ HeaderIterator hi = NULL;
+ char * msg = NULL;
+ int res = 1; /* assume failure */
+ rpmRC rc;
+ int failed = 0;
+ int nodigests = !(flags & VERIFY_DIGEST);
+ int nosignatures = !(flags & VERIFY_SIGNATURE);
+ rpmDigestBundle plbundle = rpmDigestBundleNew();
+ rpmDigestBundle hdrbundle = rpmDigestBundleNew();
+
+ rpmlead lead = rpmLeadNew();
+ if ((rc = rpmLeadRead(fd, lead)) == RPMRC_OK) {
+ const char *lmsg = NULL;
+ rc = rpmLeadCheck(lead, &lmsg);
+ if (rc != RPMRC_OK)
+ rpmlog(RPMLOG_ERR, "%s: %s\n", fn, lmsg);
+ }
+ lead = rpmLeadFree(lead);
+
+ if (rc != RPMRC_OK) {
+ goto exit;
+ }
+
+ rc = rpmReadSignature(fd, &sigh, RPMSIGTYPE_HEADERSIG, &msg);
+ switch (rc) {
+ default:
+ rpmlog(RPMLOG_ERR, _("%s: rpmReadSignature failed: %s"), fn,
+ (msg && *msg ? msg : "\n"));
+ msg = _free(msg);
+ goto exit;
+ break;
+ case RPMRC_OK:
+ if (sigh == NULL) {
+ rpmlog(RPMLOG_ERR, _("%s: No signature available\n"), fn);
+ goto exit;
+ }
+ break;
+ }
+ msg = _free(msg);
+
+ /* Grab a hint of what needs doing to avoid duplication. */
+ sigtag = bestSig(sigh, nosignatures, nodigests);
+
+ dig = pgpNewDig();
+ sigp = &dig->signature;
+
+ /* XXX RSA needs the hash_algo, so decode early. */
+ if (sigtag == RPMSIGTAG_RSA || sigtag == RPMSIGTAG_PGP ||
+ sigtag == RPMSIGTAG_DSA || sigtag == RPMSIGTAG_GPG) {
+ int xx = -1;
+ if (headerGet(sigh, sigtag, &sigtd, HEADERGET_DEFAULT)) {
+ xx = pgpPrtPkts(sigtd.data, sigtd.count, dig, 0);
+ rpmtdFreeData(&sigtd);
+ }
+ if (xx) goto exit;
+
+ /* XXX assume same hash_algo in header-only and header+payload */
+ rpmDigestBundleAdd(plbundle, sigp->hash_algo, RPMDIGEST_NONE);
+ rpmDigestBundleAdd(hdrbundle, sigp->hash_algo, RPMDIGEST_NONE);
+ }
+
+ if (headerIsEntry(sigh, RPMSIGTAG_PGP) ||
+ headerIsEntry(sigh, RPMSIGTAG_PGP5) ||
+ headerIsEntry(sigh, RPMSIGTAG_MD5)) {
+ rpmDigestBundleAdd(plbundle, PGPHASHALGO_MD5, RPMDIGEST_NONE);
+ }
+ if (headerIsEntry(sigh, RPMSIGTAG_GPG)) {
+ rpmDigestBundleAdd(plbundle, PGPHASHALGO_SHA1, RPMDIGEST_NONE);
+ }
+
+ /* always do sha1 hash of header */
+ rpmDigestBundleAdd(hdrbundle, PGPHASHALGO_SHA1, RPMDIGEST_NONE);
+
+ /* Read the file, generating digest(s) on the fly. */
+ fdSetBundle(fd, plbundle);
+ if (readFile(fd, fn, dig, plbundle, hdrbundle)) {
+ goto exit;
+ }
+
+ rasprintf(&buf, "%s:%c", fn, (rpmIsVerbose() ? '\n' : ' ') );
+
+ hi = headerInitIterator(sigh);
+ for (; headerNext(hi, &sigtd) != 0; rpmtdFreeData(&sigtd)) {
+ char *result = NULL;
+ int havekey = 0;
+ DIGEST_CTX ctx = NULL;
+ if (sigtd.data == NULL) /* XXX can't happen */
+ continue;
+
+ /* Clean up parameters from previous sigtag. */
+ pgpCleanDig(dig);
+
+ switch (sigtd.tag) {
+ case RPMSIGTAG_GPG:
+ case RPMSIGTAG_PGP5: /* XXX legacy */
+ case RPMSIGTAG_PGP:
+ havekey = 1;
+ case RPMSIGTAG_RSA:
+ case RPMSIGTAG_DSA:
+ if (nosignatures)
+ continue;
+ if (parsePGP(&sigtd, fn, dig) != RPMRC_OK) {
+ goto exit;
+ }
+ ctx = rpmDigestBundleDupCtx(havekey ? plbundle : hdrbundle,
+ dig->signature.hash_algo);
+ break;
+ case RPMSIGTAG_SHA1:
+ if (nodigests)
+ continue;
+ ctx = rpmDigestBundleDupCtx(hdrbundle, PGPHASHALGO_SHA1);
+ break;
+ case RPMSIGTAG_MD5:
+ if (nodigests)
+ continue;
+ ctx = rpmDigestBundleDupCtx(plbundle, PGPHASHALGO_MD5);
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ rc = rpmVerifySignature(keyring, &sigtd, dig, ctx, &result);
+ rpmDigestFinal(ctx, NULL, NULL, 0);
+
+ formatResult(sigtd.tag, rc, result, havekey,
+ (rc == RPMRC_NOKEY ? &missingKeys : &untrustedKeys),
+ &buf);
+ free(result);
+
+ if (rc != RPMRC_OK) {
+ failed = 1;
+ }
+
+ }
+ res = failed;
+
+ if (rpmIsVerbose()) {
+ rpmlog(RPMLOG_NOTICE, "%s", buf);
+ } else {
+ const char *ok = (failed ? _("NOT OK") : _("OK"));
+ rpmlog(RPMLOG_NOTICE, "%s%s%s%s%s%s%s%s\n", buf, ok,
+ missingKeys ? _(" (MISSING KEYS:") : "",
+ missingKeys ? missingKeys : "",
+ missingKeys ? _(") ") : "",
+ untrustedKeys ? _(" (UNTRUSTED KEYS:") : "",
+ untrustedKeys ? untrustedKeys : "",
+ untrustedKeys ? _(")") : "");
+ }
+ free(missingKeys);
+ free(untrustedKeys);
+
+exit:
+ free(buf);
+ rpmDigestBundleFree(hdrbundle);
+ rpmDigestBundleFree(plbundle);
+ fdSetBundle(fd, NULL); /* XXX avoid double-free from fd close */
+ sigh = rpmFreeSignature(sigh);
+ hi = headerFreeIterator(hi);
+ pgpFreeDig(dig);
+ return res;
+}
+
+/* Wrapper around rpmkVerifySigs to preserve API */
+int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn)
+{
+ int rc = 1; /* assume failure */
+ if (ts && qva && fd && fn) {
+ rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
+ rc = rpmpkgVerifySigs(keyring, qva->qva_flags, fd, fn);
+ rpmKeyringFree(keyring);
+ }
+ return rc;
+}
+
+int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
+{
+ const char * arg;
+ int res = 0;
+ rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
+ rpmVerifyFlags verifyFlags = (VERIFY_DIGEST|VERIFY_SIGNATURE);
+
+ verifyFlags &= ~rpmcliQueryFlags;
+
+ while ((arg = *argv++) != NULL) {
+ FD_t fd = Fopen(arg, "r.ufdio");
+ if (fd == NULL || Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"),
+ arg, Fstrerror(fd));
+ res++;
+ } else if (rpmpkgVerifySigs(keyring, verifyFlags, fd, arg)) {
+ res++;
+ }
+
+ Fclose(fd);
+ rpmdbCheckSignals();
+ }
+ rpmKeyringFree(keyring);
+ return res;
+}
diff --git a/lib/rpmchroot.c b/lib/rpmchroot.c
new file mode 100644
index 0000000..81bb5e5
--- /dev/null
+++ b/lib/rpmchroot.c
@@ -0,0 +1,105 @@
+#include "system.h"
+#include <stdlib.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+#include "lib/rpmchroot.h"
+#include "debug.h"
+
+struct rootState_s {
+ char *rootDir;
+ int chrootDone;
+ int cwd;
+};
+
+/* Process global chroot state */
+static struct rootState_s rootState = {
+ .rootDir = NULL,
+ .chrootDone = 0,
+ .cwd = -1,
+};
+
+int rpmChrootSet(const char *rootDir)
+{
+ int rc = 0;
+
+ /* Setting same rootDir again is a no-op and not an error */
+ if (rootDir && rootState.rootDir && rstreq(rootDir, rootState.rootDir))
+ return 0;
+
+ /* Resetting only permitted in neutral state */
+ if (rootState.chrootDone != 0)
+ return -1;
+
+ rootState.rootDir = _free(rootState.rootDir);
+ if (rootState.cwd >= 0) {
+ close(rootState.cwd);
+ rootState.cwd = -1;
+ }
+
+ if (rootDir != NULL) {
+ rootState.rootDir = rstrdup(rootDir);
+ rootState.cwd = open(".", O_RDONLY);
+ if (rootState.cwd < 0) {
+ rpmlog(RPMLOG_ERR, _("Unable to open current directory: %m\n"));
+ rc = -1;
+ }
+ }
+
+ return rc;
+}
+
+int rpmChrootIn(void)
+{
+ int rc = 0;
+
+ if (rootState.rootDir == NULL || rstreq(rootState.rootDir, "/"))
+ return 0;
+
+ if (rootState.cwd < 0) {
+ rpmlog(RPMLOG_ERR, _("%s: chroot directory not set\n"), __func__);
+ return -1;
+ }
+
+ /* "refcounted" entry to chroot */
+ if (rootState.chrootDone > 0) {
+ rootState.chrootDone++;
+ } else if (rootState.chrootDone == 0) {
+ if (chdir("/") == 0 && chroot(rootState.rootDir) == 0) {
+ rootState.chrootDone = 1;
+ } else {
+ rpmlog(RPMLOG_ERR, _("Unable to change root directory: %m\n"));
+ rc = -1;
+ }
+ }
+ return rc;
+}
+
+int rpmChrootOut(void)
+{
+ int rc = 0;
+ if (rootState.rootDir == NULL || rstreq(rootState.rootDir, "/"))
+ return 0;
+
+ if (rootState.cwd < 0) {
+ rpmlog(RPMLOG_ERR, _("%s: chroot directory not set\n"), __func__);
+ return -1;
+ }
+
+ /* "refcounted" return from chroot */
+ if (rootState.chrootDone > 1) {
+ rootState.chrootDone--;
+ } else if (rootState.chrootDone == 1) {
+ if (chroot(".") == 0 && fchdir(rootState.cwd) == 0) {
+ rootState.chrootDone = 0;
+ } else {
+ rpmlog(RPMLOG_ERR, _("Unable to restore root directory: %m\n"));
+ rc = -1;
+ }
+ }
+ return rc;
+}
+
+int rpmChrootDone(void)
+{
+ return (rootState.chrootDone > 0);
+}
diff --git a/lib/rpmchroot.h b/lib/rpmchroot.h
new file mode 100644
index 0000000..ba7dea5
--- /dev/null
+++ b/lib/rpmchroot.h
@@ -0,0 +1,45 @@
+#ifndef _RPMCHROOT_H
+#define _RPMCHROOT_H
+
+#include <rpm/rpmutil.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmchroot
+ * Set or clear process-wide chroot directory.
+ * Calling this while chrooted is an error.
+ * param rootDir new chroot directory (or NULL to reset)
+ * return -1 on error, 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmChrootSet(const char *rootDir);
+
+/** \ingroup rpmchroot
+ * Enter chroot if necessary.
+ * return -1 on error, 0 on success.
+ */
+/* RPM_GNUC_INTERNAL */
+int rpmChrootIn(void);
+
+/** \ingroup rpmchroot
+ * Return from chroot if necessary.
+ * return -1 on error, 0 succes.
+ */
+/* RPM_GNUC_INTERNAL */
+int rpmChrootOut(void);
+
+/** \ingroup rpmchroot
+ * Return chrooted status.
+ * return 1 if chrooted, 0 otherwise
+ */
+/* RPM_GNUC_INTERNAL */
+int rpmChrootDone(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _RPMCHROOT_H */
diff --git a/lib/rpmcli.h b/lib/rpmcli.h
new file mode 100644
index 0000000..15a4c4c
--- /dev/null
+++ b/lib/rpmcli.h
@@ -0,0 +1,401 @@
+#ifndef H_RPMCLI
+#define H_RPMCLI
+
+/** \ingroup rpmcli rpmbuild
+ * \file lib/rpmcli.h
+ */
+
+#include <popt.h>
+
+#include <rpm/rpmlib.h>
+#include <rpm/rpmurl.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmcallback.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmvf.h>
+#include <rpm/argv.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmcli
+ * Popt option table for options shared by all modes and executables.
+ */
+extern struct poptOption rpmcliAllPoptTable[];
+
+extern const char * rpmcliPipeOutput;
+
+extern const char * rpmcliRcfile;
+
+extern const char * rpmcliRootDir;
+
+/** \ingroup rpmcli
+ * Initialize most everything needed by an rpm CLI executable context.
+ * @param argc no. of args
+ * @param argv arg array
+ * @param optionsTable popt option table
+ * @return popt context (or NULL)
+ */
+poptContext
+rpmcliInit(int argc, char *const argv[], struct poptOption * optionsTable);
+
+/** \ingroup rpmcli
+ * Make sure that rpm configuration has been read.
+ * @warning Options like --rcfile and --verbose must precede callers option.
+ */
+void rpmcliConfigured(void);
+
+/** \ingroup rpmcli
+ * Destroy most everything needed by an rpm CLI executable context.
+ * @param optCon popt context
+ * @return NULL always
+ */
+poptContext
+rpmcliFini(poptContext optCon);
+
+/**
+ * Common/global popt tokens used for command line option tables.
+ */
+#define RPMCLI_POPT_NODEPS -1025
+#define RPMCLI_POPT_FORCE -1026
+#define RPMCLI_POPT_NOMD5 -1027
+#define RPMCLI_POPT_NOFILEDIGEST -1027 /* same as obsolete RPMCLI_POPT_NOMD5 */
+#define RPMCLI_POPT_NOSCRIPTS -1028
+#define RPMCLI_POPT_NOSIGNATURE -1029
+#define RPMCLI_POPT_NODIGEST -1030
+#define RPMCLI_POPT_NOHDRCHK -1031
+#define RPMCLI_POPT_NOCONTEXTS -1032
+
+/* ==================================================================== */
+/** \name RPMQV */
+
+/** \ingroup rpmcli
+ * Query/Verify argument qualifiers.
+ * @todo Reassign to tag values.
+ */
+enum rpmQVSources_e {
+ RPMQV_PACKAGE = 0, /*!< ... from package name db search. */
+ RPMQV_PATH, /*!< ... from file path db search. */
+ RPMQV_ALL, /*!< ... from each installed package. */
+ RPMQV_RPM, /*!< ... from reading binary rpm package. */
+ RPMQV_GROUP, /*!< ... from group db search. */
+ RPMQV_WHATPROVIDES, /*!< ... from provides db search. */
+ RPMQV_WHATREQUIRES, /*!< ... from requires db search. */
+ RPMQV_TRIGGEREDBY, /*!< ... from trigger db search. */
+ RPMQV_DBOFFSET, /*!< ... from database header instance. */
+ RPMQV_SPECRPMS, /*!< ... from spec file binaries (query only). */
+ RPMQV_SPECFILE = RPMQV_SPECRPMS, /*!< ... backwards compatibility */
+ RPMQV_PKGID, /*!< ... from package id (header+payload MD5). */
+ RPMQV_HDRID, /*!< ... from header id (immutable header SHA1). */
+ RPMQV_TID, /*!< ... from install transaction id (time stamp). */
+ RPMQV_SPECSRPM, /*!< ... from spec file source (query only). */
+};
+
+typedef rpmFlags rpmQVSources;
+
+/** \ingroup rpmcli
+ * Bit(s) to control rpmQuery() operation, stored in qva_flags.
+ * @todo Merge rpmQueryFlags, rpmVerifyFlags, and rpmVerifyAttrs?.
+ */
+enum rpmQueryFlags_e {
+ QUERY_FOR_DEFAULT = 0, /*!< */
+ QUERY_MD5 = (1 << 0), /*!< from --nomd5 */
+ QUERY_FILEDIGEST = (1 << 0), /*!< from --nofiledigest, same as --nomd5 */
+ QUERY_SIZE = (1 << 1), /*!< from --nosize */
+ QUERY_LINKTO = (1 << 2), /*!< from --nolink */
+ QUERY_USER = (1 << 3), /*!< from --nouser) */
+ QUERY_GROUP = (1 << 4), /*!< from --nogroup) */
+ QUERY_MTIME = (1 << 5), /*!< from --nomtime) */
+ QUERY_MODE = (1 << 6), /*!< from --nomode) */
+ QUERY_RDEV = (1 << 7), /*!< from --nodev */
+ /* bits 8-14 unused, reserved for rpmVerifyAttrs */
+ QUERY_CONTEXTS = (1 << 15), /*!< verify: from --nocontexts */
+ QUERY_FILES = (1 << 16), /*!< verify: from --nofiles */
+ QUERY_DEPS = (1 << 17), /*!< verify: from --nodeps */
+ QUERY_SCRIPT = (1 << 18), /*!< verify: from --noscripts */
+ QUERY_DIGEST = (1 << 19), /*!< verify: from --nodigest */
+ QUERY_SIGNATURE = (1 << 20), /*!< verify: from --nosignature */
+ QUERY_PATCHES = (1 << 21), /*!< verify: from --nopatches */
+ QUERY_HDRCHK = (1 << 22), /*!< verify: from --nohdrchk */
+ QUERY_FOR_LIST = (1 << 23), /*!< query: from --list */
+ QUERY_FOR_STATE = (1 << 24), /*!< query: from --state */
+ QUERY_FOR_DOCS = (1 << 25), /*!< query: from --docfiles */
+ QUERY_FOR_CONFIG = (1 << 26), /*!< query: from --configfiles */
+ QUERY_FOR_DUMPFILES = (1 << 27) /*!< query: from --dump */
+};
+
+typedef rpmFlags rpmQueryFlags;
+
+#define _QUERY_FOR_BITS \
+ (QUERY_FOR_LIST|QUERY_FOR_STATE|QUERY_FOR_DOCS|QUERY_FOR_CONFIG|\
+ QUERY_FOR_DUMPFILES)
+
+/** \ingroup rpmcli
+ * Bit(s) from common command line options.
+ */
+extern rpmQueryFlags rpmcliQueryFlags;
+
+/** \ingroup rpmcli
+ */
+typedef struct rpmQVKArguments_s * QVA_t;
+
+/** \ingroup rpmcli
+ * Function to display iterator matches.
+ *
+ * @param qva parsed query/verify options
+ * @param ts transaction set
+ * @param h header to use for query/verify
+ * @return 0 on success
+ */
+typedef int (*QVF_t) (QVA_t qva, rpmts ts, Header h);
+
+/** \ingroup rpmcli
+ * Function to query spec file.
+ *
+ * @param ts transaction set
+ * @param qva parsed query/verify options
+ * @param arg query argument
+ * @return 0 on success
+ */
+typedef int (*QSpecF_t) (rpmts ts, QVA_t qva, const char * arg);
+
+/** \ingroup rpmcli
+ * Describe query/verify/signature command line operation.
+ */
+struct rpmQVKArguments_s {
+ rpmQVSources qva_source; /*!< Identify CLI arg type. */
+ int qva_sourceCount;/*!< Exclusive option check (>1 is error). */
+ rpmQueryFlags qva_flags; /*!< Bit(s) to control operation. */
+ rpmfileAttrs qva_fflags; /*!< Bit(s) to filter on attribute. */
+
+ QVF_t qva_showPackage; /*!< Function to display iterator matches. */
+ QSpecF_t qva_specQuery; /*!< Function to query spec file. */
+ char * qva_queryFormat; /*!< Format for headerFormat(). */
+ char qva_mode;
+ /*!<
+ - 'q' from --query, -q
+ - 'Q' from --querytags
+ - 'V' from --verify, -V
+ - 'I' from --import
+ - 'K' from --checksig, -K
+ */
+};
+
+/** \ingroup rpmcli
+ */
+extern struct rpmQVKArguments_s rpmQVKArgs;
+
+/** \ingroup rpmcli
+ */
+extern struct poptOption rpmQVSourcePoptTable[];
+
+/** \ingroup rpmcli
+ */
+extern struct poptOption rpmQueryPoptTable[];
+
+/** \ingroup rpmcli
+ */
+extern struct poptOption rpmVerifyPoptTable[];
+
+/** \ingroup rpmcli
+ * Display list of tags that can be used in --queryformat.
+ * @param fp file handle to use for display
+ */
+void rpmDisplayQueryTags(FILE * fp);
+
+/** \ingroup rpmcli
+ * Display results of package query.
+ * @todo Devise a meaningful return code.
+ * @param qva parsed query/verify options
+ * @param ts transaction set
+ * @param h header to use for query
+ * @return 0 always
+ */
+int showQueryPackage(QVA_t qva, rpmts ts, Header h);
+
+/** \ingroup rpmcli
+ * Iterate over query/verify arg list.
+ * @param ts transaction set
+ * @param qva parsed query/verify options
+ * @param argv query argument(s) (or NULL)
+ * @return 0 on success, else no. of failures
+ */
+int rpmcliArgIter(rpmts ts, QVA_t qva, ARGV_const_t argv);
+
+/** \ingroup rpmcli
+ * Display package information.
+ * @todo hack: RPMQV_ALL can pass char ** arglist = NULL, not char * arg. Union?
+ * @param ts transaction set
+ * @param qva parsed query/verify options
+ * @param argv query argument(s) (or NULL)
+ * @return 0 on success, else no. of failures
+ */
+int rpmcliQuery(rpmts ts, QVA_t qva, ARGV_const_t argv);
+
+/** \ingroup rpmcli
+ * Display results of package verify.
+ * @param qva parsed query/verify options
+ * @param ts transaction set
+ * @param h header to use for verify
+ * @return result of last non-zero verify return
+ */
+int showVerifyPackage(QVA_t qva, rpmts ts, Header h);
+
+/**
+ * Check package and header signatures.
+ * @param qva parsed query/verify options
+ * @param ts transaction set
+ * @param fd package file handle
+ * @param fn package file name
+ * @return 0 on success, 1 on failure
+ */
+int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn);
+
+/** \ingroup rpmcli
+ * Verify package install.
+ * @todo hack: RPMQV_ALL can pass char ** arglist = NULL, not char * arg. Union?
+ * @param ts transaction set
+ * @param qva parsed query/verify options
+ * @param argv verify argument(s) (or NULL)
+ * @return 0 on success, else no. of failures
+ */
+int rpmcliVerify(rpmts ts, QVA_t qva, ARGV_const_t argv);
+
+/* ==================================================================== */
+/** \name RPMEIU */
+/* --- install/upgrade/erase modes */
+
+/** \ingroup rpmcli
+ * Bit(s) to control rpmInstall() operation.
+ */
+enum rpmInstallFlags_e {
+ INSTALL_NONE = 0,
+ INSTALL_PERCENT = (1 << 0), /*!< from --percent */
+ INSTALL_HASH = (1 << 1), /*!< from --hash */
+ INSTALL_NODEPS = (1 << 2), /*!< from --nodeps */
+ INSTALL_NOORDER = (1 << 3), /*!< from --noorder */
+ INSTALL_LABEL = (1 << 4), /*!< from --verbose (notify) */
+ INSTALL_UPGRADE = (1 << 5), /*!< from --upgrade */
+ INSTALL_FRESHEN = (1 << 6), /*!< from --freshen */
+ INSTALL_INSTALL = (1 << 7), /*!< from --install */
+ INSTALL_ERASE = (1 << 8), /*!< from --erase */
+ INSTALL_ALLMATCHES = (1 << 9) /*!< from --allmatches */
+};
+
+typedef rpmFlags rpmInstallFlags;
+
+/** \ingroup rpmcli
+ * Bit(s) to control rpmErase() operation.
+ */
+#define UNINSTALL_NONE INSTALL_NONE
+#define UNINSTALL_NODEPS INSTALL_NODEPS
+#define UNINSTALL_ALLMATCHES INSTALL_ALLMATCHES
+
+extern int rpmcliPackagesTotal;
+extern int rpmcliHashesCurrent;
+extern int rpmcliHashesTotal;
+extern int rpmcliProgressCurrent;
+extern int rpmcliProgressTotal;
+
+/** \ingroup rpmcli
+ * The rpm CLI generic transaction callback handler.
+ * @todo Remove headerFormat() from the progress callback.
+ * @deprecated Transaction callback arguments need to change, so don't rely on
+ * this routine in the rpmcli API.
+ *
+ * @param arg per-callback private data (e.g. an rpm header)
+ * @param what callback identifier
+ * @param amount per-callback progress info
+ * @param total per-callback progress info
+ * @param key opaque header key (e.g. file name or PyObject)
+ * @param data private data (e.g. rpmInstallInterfaceFlags)
+ * @return per-callback data (e.g. an opened FD_t)
+ */
+void * rpmShowProgress(const void * arg,
+ const rpmCallbackType what,
+ const rpm_loff_t amount,
+ const rpm_loff_t total,
+ fnpyKey key,
+ void * data);
+
+/** \ingroup rpmcli
+ * Install source rpm package.
+ * @param ts transaction set
+ * @param arg source rpm file name
+ * @retval *specFilePtr (installed) spec file name
+ * @retval *cookie
+ * @return 0 on success
+ */
+int rpmInstallSource(rpmts ts, const char * arg,
+ char ** specFilePtr,
+ char ** cookie);
+
+
+/** \ingroup rpmcli
+ * Describe database command line requests.
+ */
+struct rpmInstallArguments_s {
+ rpmtransFlags transFlags;
+ rpmprobFilterFlags probFilter;
+ rpmInstallFlags installInterfaceFlags;
+ int numRelocations;
+ int noDeps;
+ int incldocs;
+ rpmRelocation * relocations;
+ char * prefix;
+};
+
+/** \ingroup rpmcli
+ * Install/upgrade/freshen binary rpm package.
+ * @param ts transaction set
+ * @param ia mode flags and parameters
+ * @param fileArgv array of package file names (NULL terminated)
+ * @return 0 on success
+ *
+ * @todo fileArgv is modified on errors, should be ARGV_const_t
+ */
+int rpmInstall(rpmts ts, struct rpmInstallArguments_s * ia, ARGV_t fileArgv);
+
+/** \ingroup rpmcli
+ * Erase binary rpm package.
+ * @param ts transaction set
+ * @param ia control args/bits
+ * @param argv array of package file names (NULL terminated)
+ * @return 0 on success
+ */
+
+int rpmErase(rpmts ts, struct rpmInstallArguments_s * ia, ARGV_const_t argv);
+
+/** \ingroup rpmcli
+ */
+extern struct rpmInstallArguments_s rpmIArgs;
+
+/** \ingroup rpmcli
+ */
+extern struct poptOption rpmInstallPoptTable[];
+
+/* ==================================================================== */
+/** \name RPMK */
+
+/** Import public key(s) to rpm keyring
+ * @param ts transaction set
+ * @param argv array of pubkey path arguments (NULL terminated)
+ * @return 0 on success
+ */
+int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv);
+
+/** \ingroup rpmcli
+ * Verify package signatures
+ * @param ts transaction set
+ * @param argv array of package path arguments (NULL terminated)
+ * @return 0 on success
+ */
+int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMCLI */
diff --git a/lib/rpmdb.c b/lib/rpmdb.c
new file mode 100644
index 0000000..50bb6b1
--- /dev/null
+++ b/lib/rpmdb.c
@@ -0,0 +1,2956 @@
+/** \ingroup rpmdb dbi
+ * \file lib/rpmdb.c
+ */
+
+#include "system.h"
+
+#define _USE_COPY_LOAD /* XXX don't use DB_DBT_MALLOC (yet) */
+
+#include <sys/file.h>
+#include <utime.h>
+#include <errno.h>
+
+#ifndef DYING /* XXX already in "system.h" */
+#include <fnmatch.h>
+#endif
+
+#include <regex.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmurl.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmsq.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmds.h> /* XXX isInstallPreReq macro only */
+#include <rpm/rpmlog.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmts.h>
+#include <rpm/argv.h>
+
+#include "lib/rpmchroot.h"
+#include "lib/rpmdb_internal.h"
+#include "lib/fprint.h"
+#include "lib/header_internal.h" /* XXX for headerSetInstance() */
+#include "debug.h"
+
+static rpmDbiTag const dbiTags[] = {
+ RPMDBI_PACKAGES,
+ RPMDBI_NAME,
+ RPMDBI_BASENAMES,
+ RPMDBI_GROUP,
+ RPMDBI_REQUIRENAME,
+ RPMDBI_PROVIDENAME,
+ RPMDBI_CONFLICTNAME,
+ RPMDBI_OBSOLETENAME,
+ RPMDBI_TRIGGERNAME,
+ RPMDBI_DIRNAMES,
+ RPMDBI_INSTALLTID,
+ RPMDBI_SIGMD5,
+ RPMDBI_SHA1HEADER,
+};
+
+#define dbiTagsMax (sizeof(dbiTags) / sizeof(rpmDbiTag))
+
+/* A single item from an index database (i.e. the "data returned"). */
+struct dbiIndexItem {
+ unsigned int hdrNum; /*!< header instance in db */
+ unsigned int tagNum; /*!< tag index in header */
+};
+
+/* Items retrieved from the index database.*/
+typedef struct _dbiIndexSet {
+ struct dbiIndexItem * recs; /*!< array of records */
+ unsigned int count; /*!< number of records */
+ size_t alloced; /*!< alloced size */
+} * dbiIndexSet;
+
+static int addToIndex(dbiIndex dbi, rpmTagVal rpmtag, unsigned int hdrNum, Header h);
+static unsigned int pkgInstance(dbiIndex dbi, int alloc);
+static rpmdb rpmdbUnlink(rpmdb db);
+
+static int buildIndexes(rpmdb db)
+{
+ int rc = 0;
+ Header h;
+ rpmdbMatchIterator mi;
+
+ rc += rpmdbOpenAll(db);
+
+ /* If the main db was just created, this is expected - dont whine */
+ if (!(dbiFlags(db->_dbi[0]) & DBI_CREATED)) {
+ rpmlog(RPMLOG_WARNING,
+ _("Generating %d missing index(es), please wait...\n"),
+ db->db_buildindex);
+ }
+
+ /* Don't call us again */
+ db->db_buildindex = 0;
+
+ dbSetFSync(db->db_dbenv, 0);
+
+ mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, NULL, 0);
+ while ((h = rpmdbNextIterator(mi))) {
+ unsigned int hdrNum = headerGetInstance(h);
+ /* Build all secondary indexes which were created on open */
+ for (int dbix = 1; dbix < dbiTagsMax; dbix++) {
+ dbiIndex dbi = db->_dbi[dbix];
+ if (dbi && (dbiFlags(dbi) & DBI_CREATED)) {
+ rc += addToIndex(dbi, dbiTags[dbix], hdrNum, h);
+ }
+ }
+ }
+ rpmdbFreeIterator(mi);
+ dbSetFSync(db->db_dbenv, !db->cfg.db_no_fsync);
+ return rc;
+}
+
+static int uintCmp(unsigned int a, unsigned int b)
+{
+ return (a != b);
+}
+
+static unsigned int uintId(unsigned int a)
+{
+ return a;
+}
+
+/** \ingroup dbi
+ * Return handle for an index database.
+ * @param db rpm database
+ * @param rpmtag rpm tag
+ * @param flags
+ * @return index database handle
+ */
+static dbiIndex rpmdbOpenIndex(rpmdb db, rpmDbiTagVal rpmtag, int flags)
+{
+ int dbix;
+ dbiIndex dbi = NULL;
+ int rc = 0;
+
+ if (db == NULL)
+ return NULL;
+
+ for (dbix = 0; dbix < dbiTagsMax; dbix++) {
+ if (rpmtag == dbiTags[dbix])
+ break;
+ }
+ if (dbix >= dbiTagsMax)
+ return NULL;
+
+ /* Is this index already open ? */
+ if ((dbi = db->_dbi[dbix]) != NULL)
+ return dbi;
+
+ errno = 0;
+ dbi = NULL;
+ rc = dbiOpen(db, rpmtag, &dbi, flags);
+
+ if (rc) {
+ static int _printed[32];
+ if (!_printed[dbix & 0x1f]++)
+ rpmlog(RPMLOG_ERR, _("cannot open %s index using db%d - %s (%d)\n"),
+ rpmTagGetName(rpmtag), db->db_ver,
+ (rc > 0 ? strerror(rc) : ""), rc);
+ } else {
+ db->_dbi[dbix] = dbi;
+ int verifyonly = (flags & RPMDB_FLAG_VERIFYONLY);
+ int rebuild = (db->db_flags & RPMDB_FLAG_REBUILD);
+ if (dbiType(dbi) == DBI_PRIMARY) {
+ /* Allocate based on max header instance number + some reserve */
+ if (!verifyonly && (db->db_checked == NULL)) {
+ db->db_checked = intHashCreate(1024 + pkgInstance(dbi, 0) / 4,
+ uintId, uintCmp, NULL);
+ }
+ /* If primary got created, we can safely run without fsync */
+ if ((!verifyonly && (dbiFlags(dbi) & DBI_CREATED)) || db->cfg.db_no_fsync) {
+ rpmlog(RPMLOG_DEBUG, "disabling fsync on database\n");
+ db->cfg.db_no_fsync = 1;
+ dbSetFSync(db->db_dbenv, 0);
+ }
+ } else { /* secondary index */
+ if (!rebuild && !verifyonly && (dbiFlags(dbi) & DBI_CREATED)) {
+ rpmlog(RPMLOG_DEBUG, "index %s needs creating\n", dbiName(dbi));
+ db->db_buildindex++;
+ if (db->db_buildindex == 1) {
+ buildIndexes(db);
+ }
+ }
+ }
+ }
+
+ return dbi;
+}
+
+union _dbswap {
+ unsigned int ui;
+ unsigned char uc[4];
+};
+
+#define _DBSWAP(_a) \
+\
+ { unsigned char _b, *_c = (_a).uc; \
+ _b = _c[3]; _c[3] = _c[0]; _c[0] = _b; \
+ _b = _c[2]; _c[2] = _c[1]; _c[1] = _b; \
+\
+ }
+
+/*
+ * Ensure sufficient memory for nrecs of new records in dbiIndexSet.
+ * Allocate in power of two sizes to avoid memory fragmentation, so
+ * realloc is not always needed.
+ */
+static inline void dbiGrowSet(dbiIndexSet set, unsigned int nrecs)
+{
+ size_t need = (set->count + nrecs) * sizeof(*(set->recs));
+ size_t alloced = set->alloced ? set->alloced : 1 << 4;
+
+ while (alloced < need)
+ alloced <<= 1;
+
+ if (alloced != set->alloced) {
+ set->recs = xrealloc(set->recs, alloced);
+ set->alloced = alloced;
+ }
+}
+
+/**
+ * Convert retrieved data to index set.
+ * @param dbi index database handle
+ * @param data retrieved data
+ * @retval setp (malloc'ed) index set
+ * @return 0 on success
+ */
+static int dbt2set(dbiIndex dbi, DBT * data, dbiIndexSet * setp)
+{
+ int _dbbyteswapped = dbiByteSwapped(dbi);
+ const char * sdbir;
+ dbiIndexSet set;
+ unsigned int i;
+ dbiIndexType itype = dbiType(dbi);
+
+ if (dbi == NULL || data == NULL || setp == NULL)
+ return -1;
+
+ if ((sdbir = data->data) == NULL) {
+ *setp = NULL;
+ return 0;
+ }
+
+ set = xcalloc(1, sizeof(*set));
+ dbiGrowSet(set, data->size / itype);
+ set->count = data->size / itype;
+
+ switch (itype) {
+ default:
+ case DBI_SECONDARY:
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum, tagNum;
+
+ memcpy(&hdrNum.ui, sdbir, sizeof(hdrNum.ui));
+ sdbir += sizeof(hdrNum.ui);
+ memcpy(&tagNum.ui, sdbir, sizeof(tagNum.ui));
+ sdbir += sizeof(tagNum.ui);
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ _DBSWAP(tagNum);
+ }
+ set->recs[i].hdrNum = hdrNum.ui;
+ set->recs[i].tagNum = tagNum.ui;
+ }
+ break;
+ case DBI_PRIMARY:
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum;
+
+ memcpy(&hdrNum.ui, sdbir, sizeof(hdrNum.ui));
+ sdbir += sizeof(hdrNum.ui);
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ }
+ set->recs[i].hdrNum = hdrNum.ui;
+ set->recs[i].tagNum = 0;
+ }
+ break;
+ }
+ *setp = set;
+ return 0;
+}
+
+/**
+ * Convert index set to database representation.
+ * @param dbi index database handle
+ * @param data retrieved data
+ * @param set index set
+ * @return 0 on success
+ */
+static int set2dbt(dbiIndex dbi, DBT * data, dbiIndexSet set)
+{
+ int _dbbyteswapped = dbiByteSwapped(dbi);
+ char * tdbir;
+ unsigned int i;
+ dbiIndexType itype = dbiType(dbi);
+
+ if (dbi == NULL || data == NULL || set == NULL)
+ return -1;
+
+ data->size = set->count * itype;
+ if (data->size == 0) {
+ data->data = NULL;
+ return 0;
+ }
+ tdbir = data->data = xmalloc(data->size);
+
+ switch (itype) {
+ default:
+ case DBI_SECONDARY:
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum, tagNum;
+
+ memset(&hdrNum, 0, sizeof(hdrNum));
+ memset(&tagNum, 0, sizeof(tagNum));
+ hdrNum.ui = set->recs[i].hdrNum;
+ tagNum.ui = set->recs[i].tagNum;
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ _DBSWAP(tagNum);
+ }
+ memcpy(tdbir, &hdrNum.ui, sizeof(hdrNum.ui));
+ tdbir += sizeof(hdrNum.ui);
+ memcpy(tdbir, &tagNum.ui, sizeof(tagNum.ui));
+ tdbir += sizeof(tagNum.ui);
+ }
+ break;
+ case DBI_PRIMARY:
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum;
+
+ memset(&hdrNum, 0, sizeof(hdrNum));
+ hdrNum.ui = set->recs[i].hdrNum;
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ }
+ memcpy(tdbir, &hdrNum.ui, sizeof(hdrNum.ui));
+ tdbir += sizeof(hdrNum.ui);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* XXX assumes hdrNum is first int in dbiIndexItem */
+static int hdrNumCmp(const void * one, const void * two)
+{
+ const unsigned int * a = one, * b = two;
+ return (*a - *b);
+}
+
+/**
+ * Append element(s) to set of index database items.
+ * @param set set of index database items
+ * @param recs array of items to append to set
+ * @param nrecs number of items
+ * @param recsize size of an array item
+ * @param sortset should resulting set be sorted?
+ * @return 0 success, 1 failure (bad args)
+ */
+static int dbiAppendSet(dbiIndexSet set, const void * recs,
+ int nrecs, size_t recsize, int sortset)
+{
+ const char * rptr = recs;
+ size_t rlen = (recsize < sizeof(*(set->recs)))
+ ? recsize : sizeof(*(set->recs));
+
+ if (set == NULL || recs == NULL || nrecs <= 0 || recsize == 0)
+ return 1;
+
+ dbiGrowSet(set, nrecs);
+ memset(set->recs + set->count, 0, nrecs * sizeof(*(set->recs)));
+
+ while (nrecs-- > 0) {
+ memcpy(set->recs + set->count, rptr, rlen);
+ rptr += recsize;
+ set->count++;
+ }
+
+ if (sortset && set->count > 1)
+ qsort(set->recs, set->count, sizeof(*(set->recs)), hdrNumCmp);
+
+ return 0;
+}
+
+/**
+ * Remove element(s) from set of index database items.
+ * @param set set of index database items
+ * @param recs array of items to remove from set
+ * @param nrecs number of items
+ * @param recsize size of an array item
+ * @param sorted array is already sorted?
+ * @return 0 success, 1 failure (no items found)
+ */
+static int dbiPruneSet(dbiIndexSet set, void * recs, int nrecs,
+ size_t recsize, int sorted)
+{
+ unsigned int from;
+ unsigned int to = 0;
+ unsigned int num = set->count;
+ unsigned int numCopied = 0;
+
+ assert(set->count > 0);
+ if (nrecs > 1 && !sorted)
+ qsort(recs, nrecs, recsize, hdrNumCmp);
+
+ for (from = 0; from < num; from++) {
+ if (bsearch(&set->recs[from], recs, nrecs, recsize, hdrNumCmp)) {
+ set->count--;
+ continue;
+ }
+ if (from != to)
+ set->recs[to] = set->recs[from]; /* structure assignment */
+ to++;
+ numCopied++;
+ }
+ return (numCopied == num);
+}
+
+/* Count items in index database set. */
+static unsigned int dbiIndexSetCount(dbiIndexSet set) {
+ return set->count;
+}
+
+/* Return record offset of header from element in index database set. */
+static unsigned int dbiIndexRecordOffset(dbiIndexSet set, int recno) {
+ return set->recs[recno].hdrNum;
+}
+
+/* Return file index from element in index database set. */
+static unsigned int dbiIndexRecordFileNumber(dbiIndexSet set, int recno) {
+ return set->recs[recno].tagNum;
+}
+
+/* Destroy set of index database items */
+static dbiIndexSet dbiFreeIndexSet(dbiIndexSet set) {
+ if (set) {
+ set->recs = _free(set->recs);
+ set = _free(set);
+ }
+ return set;
+}
+
+typedef struct miRE_s {
+ rpmTagVal tag; /*!< header tag */
+ rpmMireMode mode; /*!< pattern match mode */
+ char * pattern; /*!< pattern string */
+ int notmatch; /*!< like "grep -v" */
+ regex_t * preg; /*!< regex compiled pattern buffer */
+ int cflags; /*!< regcomp(3) flags */
+ int eflags; /*!< regexec(3) flags */
+ int fnflags; /*!< fnmatch(3) flags */
+} * miRE;
+
+struct rpmdbMatchIterator_s {
+ rpmdbMatchIterator mi_next;
+ void * mi_keyp;
+ size_t mi_keylen;
+ rpmdb mi_db;
+ rpmDbiTagVal mi_rpmtag;
+ dbiIndexSet mi_set;
+ DBC * mi_dbc;
+ int mi_setx;
+ Header mi_h;
+ int mi_sorted;
+ int mi_cflags;
+ int mi_modified;
+ unsigned int mi_prevoffset; /* header instance (native endian) */
+ unsigned int mi_offset; /* header instance (native endian) */
+ unsigned int mi_filenum; /* tag element (native endian) */
+ int mi_nre;
+ miRE mi_re;
+ rpmts mi_ts;
+ rpmRC (*mi_hdrchk) (rpmts ts, const void * uh, size_t uc, char ** msg);
+
+};
+
+struct rpmdbIndexIterator_s {
+ rpmdbIndexIterator ii_next;
+ rpmdb ii_db;
+ dbiIndex ii_dbi;
+ rpmDbiTag ii_rpmtag;
+ DBC * ii_dbc;
+ DBT ii_key;
+ dbiIndexSet ii_set;
+};
+
+static rpmdb rpmdbRock;
+static rpmdbMatchIterator rpmmiRock;
+static rpmdbIndexIterator rpmiiRock;
+
+int rpmdbCheckTerminate(int terminate)
+{
+ sigset_t newMask, oldMask;
+ static int terminating = 0;
+
+ if (terminating) return 0;
+
+ (void) sigfillset(&newMask); /* block all signals */
+ (void) sigprocmask(SIG_BLOCK, &newMask, &oldMask);
+
+ if (rpmsqIsCaught(SIGINT) > 0
+ || rpmsqIsCaught(SIGQUIT) > 0
+ || rpmsqIsCaught(SIGHUP) > 0
+ || rpmsqIsCaught(SIGTERM) > 0
+ || rpmsqIsCaught(SIGPIPE) > 0
+ || terminate)
+ terminating = 1;
+
+ if (terminating) {
+ rpmdb db;
+ rpmdbMatchIterator mi;
+ rpmdbIndexIterator ii;
+
+ while ((mi = rpmmiRock) != NULL) {
+ rpmmiRock = mi->mi_next;
+ mi->mi_next = NULL;
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ while ((ii = rpmiiRock) != NULL) {
+ rpmiiRock = ii->ii_next;
+ ii->ii_next = NULL;
+ ii = rpmdbIndexIteratorFree(ii);
+ }
+
+ while ((db = rpmdbRock) != NULL) {
+ rpmdbRock = db->db_next;
+ db->db_next = NULL;
+ (void) rpmdbClose(db);
+ }
+ }
+ sigprocmask(SIG_SETMASK, &oldMask, NULL);
+ return terminating;
+}
+
+int rpmdbCheckSignals(void)
+{
+ if (rpmdbCheckTerminate(0)) {
+ rpmlog(RPMLOG_DEBUG, "Exiting on signal...\n");
+ exit(EXIT_FAILURE);
+ }
+ return 0;
+}
+
+/**
+ * Block all signals, returning previous signal mask.
+ */
+static int blockSignals(sigset_t * oldMask)
+{
+ sigset_t newMask;
+
+ (void) sigfillset(&newMask); /* block all signals */
+ (void) sigprocmask(SIG_BLOCK, &newMask, oldMask);
+ (void) sigdelset(&newMask, SIGINT);
+ (void) sigdelset(&newMask, SIGQUIT);
+ (void) sigdelset(&newMask, SIGHUP);
+ (void) sigdelset(&newMask, SIGTERM);
+ (void) sigdelset(&newMask, SIGPIPE);
+ return sigprocmask(SIG_BLOCK, &newMask, NULL);
+}
+
+/**
+ * Restore signal mask.
+ */
+static int unblockSignals(sigset_t * oldMask)
+{
+ (void) rpmdbCheckSignals();
+ return sigprocmask(SIG_SETMASK, oldMask, NULL);
+}
+
+rpmop rpmdbOp(rpmdb rpmdb, rpmdbOpX opx)
+{
+ rpmop op = NULL;
+ switch (opx) {
+ case RPMDB_OP_DBGET:
+ op = &rpmdb->db_getops;
+ break;
+ case RPMDB_OP_DBPUT:
+ op = &rpmdb->db_putops;
+ break;
+ case RPMDB_OP_DBDEL:
+ op = &rpmdb->db_delops;
+ break;
+ default:
+ break;
+ }
+ return op;
+}
+
+const char *rpmdbHome(rpmdb db)
+{
+ const char *dbdir = NULL;
+ if (db) {
+ dbdir = rpmChrootDone() ? db->db_home : db->db_fullpath;
+ }
+ return dbdir;
+}
+
+int rpmdbOpenAll(rpmdb db)
+{
+ int rc = 0;
+
+ if (db == NULL) return -2;
+
+ for (int dbix = 0; dbix < dbiTagsMax; dbix++) {
+ dbiIndex dbi = db->_dbi[dbix];
+ if (dbi == NULL) {
+ rc += (rpmdbOpenIndex(db, dbiTags[dbix], db->db_flags) == NULL);
+ }
+ }
+ return rc;
+}
+
+static int dbiForeach(dbiIndex *dbis,
+ int (*func) (dbiIndex, unsigned int), int del)
+{
+ int xx, rc = 0;
+ for (int dbix = dbiTagsMax; --dbix >= 0; ) {
+ if (dbis[dbix] == NULL)
+ continue;
+ xx = func(dbis[dbix], 0);
+ if (xx && rc == 0) rc = xx;
+ if (del)
+ dbis[dbix] = NULL;
+ }
+ return rc;
+}
+
+int rpmdbClose(rpmdb db)
+{
+ rpmdb * prev, next;
+ int rc = 0;
+
+ if (db == NULL)
+ goto exit;
+
+ (void) rpmdbUnlink(db);
+
+ if (db->nrefs > 0)
+ goto exit;
+
+ /* Always re-enable fsync on close of rw-database */
+ if ((db->db_mode & O_ACCMODE) != O_RDONLY)
+ dbSetFSync(db->db_dbenv, 1);
+
+ rc = dbiForeach(db->_dbi, dbiClose, 1);
+
+ db->db_root = _free(db->db_root);
+ db->db_home = _free(db->db_home);
+ db->db_fullpath = _free(db->db_fullpath);
+ db->db_checked = intHashFree(db->db_checked);
+ db->_dbi = _free(db->_dbi);
+
+ prev = &rpmdbRock;
+ while ((next = *prev) != NULL && next != db)
+ prev = &next->db_next;
+ if (next) {
+ *prev = next->db_next;
+ next->db_next = NULL;
+ }
+
+ db = _free(db);
+
+exit:
+ (void) rpmsqEnable(-SIGHUP, NULL);
+ (void) rpmsqEnable(-SIGINT, NULL);
+ (void) rpmsqEnable(-SIGTERM,NULL);
+ (void) rpmsqEnable(-SIGQUIT,NULL);
+ (void) rpmsqEnable(-SIGPIPE,NULL);
+ return rc;
+}
+
+int rpmdbSync(rpmdb db)
+{
+ if (db == NULL) return 0;
+
+ return dbiForeach(db->_dbi, dbiSync, 0);
+}
+
+static rpmdb newRpmdb(const char * root, const char * home,
+ int mode, int perms, int flags)
+{
+ rpmdb db = NULL;
+ char * db_home = rpmGetPath((home && *home) ? home : "%{_dbpath}", NULL);
+
+ if (!(db_home && db_home[0] != '%')) {
+ rpmlog(RPMLOG_ERR, _("no dbpath has been set\n"));
+ free(db_home);
+ return NULL;
+ }
+
+ db = xcalloc(sizeof(*db), 1);
+
+ if (!(perms & 0600)) perms = 0644; /* XXX sanity */
+
+ db->db_mode = (mode >= 0) ? mode : 0;
+ db->db_perms = (perms >= 0) ? perms : 0644;
+ db->db_flags = (flags >= 0) ? flags : 0;
+
+ db->db_home = db_home;
+ db->db_root = rpmGetPath((root && *root) ? root : "/", NULL);
+ db->db_fullpath = rpmGenPath(db->db_root, db->db_home, NULL);
+ /* XXX remove environment after chrooted operations, for now... */
+ db->db_remove_env = (!rstreq(db->db_root, "/") ? 1 : 0);
+ db->_dbi = xcalloc(dbiTagsMax, sizeof(*db->_dbi));
+ db->db_ver = DB_VERSION_MAJOR; /* XXX just to put something in messages */
+ db->nrefs = 0;
+ return rpmdbLink(db);
+}
+
+static int openDatabase(const char * prefix,
+ const char * dbpath, rpmdb *dbp,
+ int mode, int perms, int flags)
+{
+ rpmdb db;
+ int rc, xx;
+ int justCheck = flags & RPMDB_FLAG_JUSTCHECK;
+
+ if (dbp)
+ *dbp = NULL;
+ if ((mode & O_ACCMODE) == O_WRONLY)
+ return 1;
+
+ db = newRpmdb(prefix, dbpath, mode, perms, flags);
+ if (db == NULL)
+ return 1;
+
+ /* Try to ensure db home exists, error out if we cant even create */
+ rc = rpmioMkpath(rpmdbHome(db), 0755, getuid(), getgid());
+ if (rc == 0) {
+ (void) rpmsqEnable(SIGHUP, NULL);
+ (void) rpmsqEnable(SIGINT, NULL);
+ (void) rpmsqEnable(SIGTERM,NULL);
+ (void) rpmsqEnable(SIGQUIT,NULL);
+ (void) rpmsqEnable(SIGPIPE,NULL);
+
+ /* Just the primary Packages database opened here */
+ rc = (rpmdbOpenIndex(db, RPMDBI_PACKAGES, db->db_flags) != NULL) ? 0 : -2;
+ }
+
+ if (rc || justCheck || dbp == NULL)
+ xx = rpmdbClose(db);
+ else {
+ db->db_next = rpmdbRock;
+ rpmdbRock = db;
+ *dbp = db;
+ }
+
+ return rc;
+}
+
+static rpmdb rpmdbUnlink(rpmdb db)
+{
+ if (db)
+ db->nrefs--;
+ return NULL;
+}
+
+rpmdb rpmdbLink(rpmdb db)
+{
+ if (db)
+ db->nrefs++;
+ return db;
+}
+
+int rpmdbOpen (const char * prefix, rpmdb *dbp, int mode, int perms)
+{
+ return openDatabase(prefix, NULL, dbp, mode, perms, 0);
+}
+
+int rpmdbInit (const char * prefix, int perms)
+{
+ rpmdb db = NULL;
+ int rc;
+
+ rc = openDatabase(prefix, NULL, &db, (O_CREAT | O_RDWR), perms, 0);
+ if (db != NULL) {
+ int xx;
+ xx = rpmdbOpenAll(db);
+ if (xx && rc == 0) rc = xx;
+ xx = rpmdbClose(db);
+ if (xx && rc == 0) rc = xx;
+ db = NULL;
+ }
+ return rc;
+}
+
+int rpmdbVerify(const char * prefix)
+{
+ rpmdb db = NULL;
+ int rc = 0;
+
+ rc = openDatabase(prefix, NULL, &db, O_RDONLY, 0644, RPMDB_FLAG_VERIFYONLY);
+
+ if (db != NULL) {
+ int xx;
+ rc = rpmdbOpenAll(db);
+
+ rc = dbiForeach(db->_dbi, dbiVerify, 0);
+
+ xx = rpmdbClose(db);
+ if (xx && rc == 0) rc = xx;
+ db = NULL;
+ }
+ return rc;
+}
+
+static Header rpmdbGetHeaderAt(rpmdb db, unsigned int offset)
+{
+ rpmdbMatchIterator mi = rpmdbInitIterator(db, RPMDBI_PACKAGES,
+ &offset, sizeof(offset));
+ Header h = headerLink(rpmdbNextIterator(mi));
+ rpmdbFreeIterator(mi);
+ return h;
+}
+
+/**
+ * Find file matches in database.
+ * @param db rpm database
+ * @param filespec
+ * @param key
+ * @param data
+ * @param matches
+ * @return 0 on success, 1 on not found, -2 on error
+ */
+static int rpmdbFindByFile(rpmdb db, const char * filespec,
+ DBT * key, DBT * data, dbiIndexSet * matches)
+{
+ char * dirName = NULL;
+ const char * baseName;
+ fingerPrintCache fpc = NULL;
+ fingerPrint fp1;
+ dbiIndex dbi = NULL;
+ DBC * dbcursor;
+ dbiIndexSet allMatches = NULL;
+ rpmDbiTag dbtag = RPMDBI_BASENAMES;
+ unsigned int i;
+ int rc = -2; /* assume error */
+ int xx;
+
+ *matches = NULL;
+ if (filespec == NULL) return rc; /* nothing alloced yet */
+
+ if ((baseName = strrchr(filespec, '/')) != NULL) {
+ size_t len = baseName - filespec + 1;
+ dirName = strncpy(xmalloc(len + 1), filespec, len);
+ dirName[len] = '\0';
+ baseName++;
+ } else {
+ dirName = xstrdup("");
+ baseName = filespec;
+ }
+ if (baseName == NULL)
+ goto exit;
+
+ dbi = rpmdbOpenIndex(db, dbtag, 0);
+ if (dbi != NULL) {
+ dbcursor = NULL;
+ xx = dbiCopen(dbi, &dbcursor, 0);
+
+ key->data = (void *) baseName;
+ key->size = strlen(baseName);
+ if (key->size == 0)
+ key->size++; /* XXX "/" fixup. */
+
+ rc = dbiGet(dbi, dbcursor, key, data, DB_SET);
+ if (rc > 0) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key->data, dbiName(dbi));
+ }
+
+ if (rc == 0)
+ (void) dbt2set(dbi, data, &allMatches);
+
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+ } else
+ rc = -2;
+
+ if (rc || allMatches == NULL) goto exit;
+
+ *matches = xcalloc(1, sizeof(**matches));
+ fpc = fpCacheCreate(allMatches->count);
+ fp1 = fpLookup(fpc, dirName, baseName, 1);
+
+ i = 0;
+ while (i < allMatches->count) {
+ struct rpmtd_s bn, dn, di;
+ const char ** baseNames, ** dirNames;
+ uint32_t * dirIndexes;
+ unsigned int offset = dbiIndexRecordOffset(allMatches, i);
+ unsigned int prevoff;
+ Header h = rpmdbGetHeaderAt(db, offset);
+
+ if (h == NULL) {
+ i++;
+ continue;
+ }
+
+ headerGet(h, RPMTAG_BASENAMES, &bn, HEADERGET_MINMEM);
+ headerGet(h, RPMTAG_DIRNAMES, &dn, HEADERGET_MINMEM);
+ headerGet(h, RPMTAG_DIRINDEXES, &di, HEADERGET_MINMEM);
+ baseNames = bn.data;
+ dirNames = dn.data;
+ dirIndexes = di.data;
+
+ do {
+ fingerPrint fp2;
+ int num = dbiIndexRecordFileNumber(allMatches, i);
+
+ fp2 = fpLookup(fpc, dirNames[dirIndexes[num]], baseNames[num], 1);
+ if (FP_EQUAL(fp1, fp2)) {
+ struct dbiIndexItem rec = {
+ .hdrNum = dbiIndexRecordOffset(allMatches, i),
+ .tagNum = dbiIndexRecordFileNumber(allMatches, i),
+ };
+ xx = dbiAppendSet(*matches, &rec, 1, sizeof(rec), 0);
+ }
+
+ prevoff = offset;
+ i++;
+ if (i < allMatches->count)
+ offset = dbiIndexRecordOffset(allMatches, i);
+ } while (i < allMatches->count && offset == prevoff);
+
+ rpmtdFreeData(&bn);
+ rpmtdFreeData(&dn);
+ rpmtdFreeData(&di);
+ h = headerFree(h);
+ }
+
+ fpCacheFree(fpc);
+
+ if ((*matches)->count == 0) {
+ *matches = dbiFreeIndexSet(*matches);
+ rc = 1;
+ } else {
+ rc = 0;
+ }
+
+exit:
+ dbiFreeIndexSet(allMatches);
+ free(dirName);
+ return rc;
+}
+
+int rpmdbCountPackages(rpmdb db, const char * name)
+{
+ DBC * dbcursor = NULL;
+ DBT key, data;
+ dbiIndex dbi;
+ rpmDbiTag dbtag = RPMDBI_NAME;
+ int rc;
+ int xx;
+
+ if (db == NULL)
+ return 0;
+
+ dbi = rpmdbOpenIndex(db, dbtag, 0);
+ if (dbi == NULL)
+ return 0;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ key.data = (void *) name;
+ key.size = strlen(name);
+
+ xx = dbiCopen(dbi, &dbcursor, 0);
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+
+ if (rc == 0) { /* success */
+ dbiIndexSet matches = NULL;
+ (void) dbt2set(dbi, &data, &matches);
+ if (matches) {
+ rc = dbiIndexSetCount(matches);
+ matches = dbiFreeIndexSet(matches);
+ }
+ } else if (rc == DB_NOTFOUND) { /* not found */
+ rc = 0;
+ } else { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ rc = -1;
+ }
+
+ return rc;
+}
+
+/**
+ * Attempt partial matches on name[-version[-release]] strings.
+ * @param db rpmdb handle
+ * @param dbi index database handle (always RPMDBI_NAME)
+ * @param dbcursor index database cursor
+ * @param key search key/length/flags
+ * @param data search data/length/flags
+ * @param name package name
+ * @param version package version (can be a pattern)
+ * @param release package release (can be a pattern)
+ * @retval matches set of header instances that match
+ * @return RPMRC_OK on match, RPMRC_NOMATCH or RPMRC_FAIL
+ */
+static rpmRC dbiFindMatches(rpmdb db, dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * data,
+ const char * name,
+ const char * version,
+ const char * release,
+ dbiIndexSet * matches)
+{
+ unsigned int gotMatches = 0;
+ int rc;
+ unsigned int i;
+
+ key->data = (void *) name;
+ key->size = strlen(name);
+
+ rc = dbiGet(dbi, dbcursor, key, data, DB_SET);
+
+ if (rc == 0) { /* success */
+ (void) dbt2set(dbi, data, matches);
+ if (version == NULL && release == NULL)
+ return RPMRC_OK;
+ } else if (rc == DB_NOTFOUND) { /* not found */
+ return RPMRC_NOTFOUND;
+ } else { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key->data, dbiName(dbi));
+ return RPMRC_FAIL;
+ }
+
+ /* Make sure the version and release match. */
+ for (i = 0; i < dbiIndexSetCount(*matches); i++) {
+ unsigned int recoff = dbiIndexRecordOffset(*matches, i);
+ rpmdbMatchIterator mi;
+ Header h;
+
+ if (recoff == 0)
+ continue;
+
+ mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, &recoff, sizeof(recoff));
+
+ /* Set iterator selectors for version/release if available. */
+ if (version &&
+ rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_DEFAULT, version))
+ {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+ if (release &&
+ rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_DEFAULT, release))
+ {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+
+ h = rpmdbNextIterator(mi);
+ if (h)
+ (*matches)->recs[gotMatches++] = (*matches)->recs[i];
+ else
+ (*matches)->recs[i].hdrNum = 0;
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ if (gotMatches) {
+ (*matches)->count = gotMatches;
+ rc = RPMRC_OK;
+ } else
+ rc = RPMRC_NOTFOUND;
+
+exit:
+/* FIX: double indirection */
+ if (rc && matches && *matches)
+ *matches = dbiFreeIndexSet(*matches);
+ return rc;
+}
+
+/**
+ * Lookup by name, name-version, and finally by name-version-release.
+ * Both version and release can be patterns.
+ * @todo Name must be an exact match, as name is a db key.
+ * @param db rpmdb handle
+ * @param dbi index database handle (always RPMDBI_NAME)
+ * @param dbcursor index database cursor
+ * @param key search key/length/flags
+ * @param data search data/length/flags
+ * @param arg name[-version[-release]] string
+ * @retval matches set of header instances that match
+ * @return RPMRC_OK on match, RPMRC_NOMATCH or RPMRC_FAIL
+ */
+static rpmRC dbiFindByLabel(rpmdb db, dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * data, const char * arg, dbiIndexSet * matches)
+{
+ const char * release;
+ char * localarg;
+ char * s;
+ char c;
+ int brackets;
+ rpmRC rc;
+
+ if (arg == NULL || strlen(arg) == 0) return RPMRC_NOTFOUND;
+
+ /* did they give us just a name? */
+ rc = dbiFindMatches(db, dbi, dbcursor, key, data, arg, NULL, NULL, matches);
+ if (rc != RPMRC_NOTFOUND) return rc;
+
+ /* FIX: double indirection */
+ *matches = dbiFreeIndexSet(*matches);
+
+ /* maybe a name and a release */
+ localarg = xmalloc(strlen(arg) + 1);
+ s = stpcpy(localarg, arg);
+
+ c = '\0';
+ brackets = 0;
+ for (s -= 1; s > localarg; s--) {
+ switch (*s) {
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *s;
+ if (!brackets && *s == '-')
+ break;
+ }
+
+ /* FIX: *matches may be NULL. */
+ if (s == localarg) {
+ rc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ *s = '\0';
+ rc = dbiFindMatches(db, dbi, dbcursor, key, data,
+ localarg, s + 1, NULL, matches);
+ if (rc != RPMRC_NOTFOUND) goto exit;
+
+ /* FIX: double indirection */
+ *matches = dbiFreeIndexSet(*matches);
+
+ /* how about name-version-release? */
+
+ release = s + 1;
+
+ c = '\0';
+ brackets = 0;
+ for (; s > localarg; s--) {
+ switch (*s) {
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *s;
+ if (!brackets && *s == '-')
+ break;
+ }
+
+ if (s == localarg) {
+ rc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ *s = '\0';
+ /* FIX: *matches may be NULL. */
+ rc = dbiFindMatches(db, dbi, dbcursor, key, data,
+ localarg, s + 1, release, matches);
+exit:
+ free(localarg);
+ return rc;
+}
+
+/**
+ * Rewrite a header into packages (if necessary) and free the header.
+ * Note: this is called from a markReplacedFiles iteration, and *must*
+ * preserve the "join key" (i.e. offset) for the header.
+ * @param mi database iterator
+ * @param dbi index database handle
+ * @return 0 on success
+ */
+static int miFreeHeader(rpmdbMatchIterator mi, dbiIndex dbi)
+{
+ int rc = 0;
+
+ if (mi == NULL || mi->mi_h == NULL)
+ return 0;
+
+ if (dbi && mi->mi_dbc && mi->mi_modified && mi->mi_prevoffset) {
+ DBT key, data;
+ sigset_t signalMask;
+ rpmRC rpmrc = RPMRC_NOTFOUND;
+ int xx;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+ key.data = (void *) &mi->mi_prevoffset;
+ key.size = sizeof(mi->mi_prevoffset);
+ data.data = headerUnload(mi->mi_h);
+ data.size = headerSizeof(mi->mi_h, HEADER_MAGIC_NO);
+
+ /* Check header digest/signature on blob export (if requested). */
+ if (mi->mi_hdrchk && mi->mi_ts) {
+ char * msg = NULL;
+ int lvl;
+
+ rpmrc = (*mi->mi_hdrchk) (mi->mi_ts, data.data, data.size, &msg);
+ lvl = (rpmrc == RPMRC_FAIL ? RPMLOG_ERR : RPMLOG_DEBUG);
+ rpmlog(lvl, "%s h#%8u %s",
+ (rpmrc == RPMRC_FAIL ? _("miFreeHeader: skipping") : "write"),
+ mi->mi_prevoffset, (msg ? msg : "\n"));
+ msg = _free(msg);
+ }
+
+ if (data.data != NULL && rpmrc != RPMRC_FAIL) {
+ (void) blockSignals(&signalMask);
+ rc = dbiPut(dbi, mi->mi_dbc, &key, &data, DB_KEYLAST);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record #%d into %s\n"),
+ rc, mi->mi_prevoffset, dbiName(dbi));
+ }
+ xx = dbiSync(dbi, 0);
+ (void) unblockSignals(&signalMask);
+ }
+ data.data = _free(data.data);
+ data.size = 0;
+ }
+
+ mi->mi_h = headerFree(mi->mi_h);
+
+ return rc;
+}
+
+rpmdbMatchIterator rpmdbFreeIterator(rpmdbMatchIterator mi)
+{
+ rpmdbMatchIterator * prev, next;
+ dbiIndex dbi;
+ int xx;
+ int i;
+
+ if (mi == NULL)
+ return NULL;
+
+ prev = &rpmmiRock;
+ while ((next = *prev) != NULL && next != mi)
+ prev = &next->mi_next;
+ if (next) {
+ *prev = next->mi_next;
+ next->mi_next = NULL;
+ }
+
+ dbi = rpmdbOpenIndex(mi->mi_db, RPMDBI_PACKAGES, 0);
+
+ xx = miFreeHeader(mi, dbi);
+
+ if (mi->mi_dbc)
+ xx = dbiCclose(dbi, mi->mi_dbc, 0);
+ mi->mi_dbc = NULL;
+
+ if (mi->mi_re != NULL)
+ for (i = 0; i < mi->mi_nre; i++) {
+ miRE mire = mi->mi_re + i;
+ mire->pattern = _free(mire->pattern);
+ if (mire->preg != NULL) {
+ regfree(mire->preg);
+ mire->preg = _free(mire->preg);
+ }
+ }
+ mi->mi_re = _free(mi->mi_re);
+
+ mi->mi_set = dbiFreeIndexSet(mi->mi_set);
+ mi->mi_keyp = _free(mi->mi_keyp);
+ rpmdbClose(mi->mi_db);
+ mi->mi_ts = rpmtsFree(mi->mi_ts);
+
+ mi = _free(mi);
+
+ (void) rpmdbCheckSignals();
+
+ return mi;
+}
+
+unsigned int rpmdbGetIteratorOffset(rpmdbMatchIterator mi) {
+ return (mi ? mi->mi_offset : 0);
+}
+
+unsigned int rpmdbGetIteratorFileNum(rpmdbMatchIterator mi) {
+ return (mi ? mi->mi_filenum : 0);
+}
+
+int rpmdbGetIteratorCount(rpmdbMatchIterator mi) {
+ return (mi && mi->mi_set ? mi->mi_set->count : 0);
+}
+
+/**
+ * Return pattern match.
+ * @param mire match iterator regex
+ * @param val value to match
+ * @return 0 if pattern matches, >0 on nomatch, <0 on error
+ */
+static int miregexec(miRE mire, const char * val)
+{
+ int rc = 0;
+
+ switch (mire->mode) {
+ case RPMMIRE_STRCMP:
+ rc = (!rstreq(mire->pattern, val));
+ break;
+ case RPMMIRE_DEFAULT:
+ case RPMMIRE_REGEX:
+ rc = regexec(mire->preg, val, 0, NULL, mire->eflags);
+ if (rc && rc != REG_NOMATCH) {
+ char msg[256];
+ (void) regerror(rc, mire->preg, msg, sizeof(msg)-1);
+ msg[sizeof(msg)-1] = '\0';
+ rpmlog(RPMLOG_ERR, _("%s: regexec failed: %s\n"),
+ mire->pattern, msg);
+ rc = -1;
+ }
+ break;
+ case RPMMIRE_GLOB:
+ rc = fnmatch(mire->pattern, val, mire->fnflags);
+ if (rc && rc != FNM_NOMATCH)
+ rc = -1;
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Compare iterator selectors by rpm tag (qsort/bsearch).
+ * @param a 1st iterator selector
+ * @param b 2nd iterator selector
+ * @return result of comparison
+ */
+static int mireCmp(const void * a, const void * b)
+{
+ const miRE mireA = (const miRE) a;
+ const miRE mireB = (const miRE) b;
+ return (mireA->tag - mireB->tag);
+}
+
+/**
+ * Copy pattern, escaping for appropriate mode.
+ * @param tag rpm tag
+ * @retval modep type of pattern match
+ * @param pattern pattern to duplicate
+ * @return duplicated pattern
+ */
+static char * mireDup(rpmTagVal tag, rpmMireMode *modep,
+ const char * pattern)
+{
+ const char * s;
+ char * pat;
+ char * t;
+ int brackets;
+ size_t nb;
+ int c;
+
+ switch (*modep) {
+ default:
+ case RPMMIRE_DEFAULT:
+ if (tag == RPMTAG_DIRNAMES || tag == RPMTAG_BASENAMES) {
+ *modep = RPMMIRE_GLOB;
+ pat = xstrdup(pattern);
+ break;
+ }
+
+ nb = strlen(pattern) + sizeof("^$");
+
+ /* Find no. of bytes needed for pattern. */
+ /* periods and plusses are escaped, splats become '.*' */
+ c = '\0';
+ brackets = 0;
+ for (s = pattern; *s != '\0'; s++) {
+ switch (*s) {
+ case '.':
+ case '+':
+ case '*':
+ if (!brackets) nb++;
+ break;
+ case '\\':
+ s++;
+ break;
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *s;
+ }
+
+ pat = t = xmalloc(nb);
+
+ if (pattern[0] != '^') *t++ = '^';
+
+ /* Copy pattern, escaping periods, prefixing splats with period. */
+ c = '\0';
+ brackets = 0;
+ for (s = pattern; *s != '\0'; s++, t++) {
+ switch (*s) {
+ case '.':
+ case '+':
+ if (!brackets) *t++ = '\\';
+ break;
+ case '*':
+ if (!brackets) *t++ = '.';
+ break;
+ case '\\':
+ *t++ = *s++;
+ break;
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *t = *s;
+ }
+
+ if (s > pattern && s[-1] != '$') *t++ = '$';
+ *t = '\0';
+ *modep = RPMMIRE_REGEX;
+ break;
+ case RPMMIRE_STRCMP:
+ case RPMMIRE_REGEX:
+ case RPMMIRE_GLOB:
+ pat = xstrdup(pattern);
+ break;
+ }
+
+ return pat;
+}
+
+int rpmdbSetIteratorRE(rpmdbMatchIterator mi, rpmTagVal tag,
+ rpmMireMode mode, const char * pattern)
+{
+ static rpmMireMode defmode = (rpmMireMode)-1;
+ miRE mire = NULL;
+ char * allpat = NULL;
+ int notmatch = 0;
+ regex_t * preg = NULL;
+ int cflags = 0;
+ int eflags = 0;
+ int fnflags = 0;
+ int rc = 0;
+
+ if (defmode == (rpmMireMode)-1) {
+ char *t = rpmExpand("%{?_query_selector_match}", NULL);
+
+ if (*t == '\0' || rstreq(t, "default"))
+ defmode = RPMMIRE_DEFAULT;
+ else if (rstreq(t, "strcmp"))
+ defmode = RPMMIRE_STRCMP;
+ else if (rstreq(t, "regex"))
+ defmode = RPMMIRE_REGEX;
+ else if (rstreq(t, "glob"))
+ defmode = RPMMIRE_GLOB;
+ else
+ defmode = RPMMIRE_DEFAULT;
+ t = _free(t);
+ }
+
+ if (mi == NULL || pattern == NULL)
+ return rc;
+
+ /* Leading '!' inverts pattern match sense, like "grep -v". */
+ if (*pattern == '!') {
+ notmatch = 1;
+ pattern++;
+ }
+
+ allpat = mireDup(tag, &mode, pattern);
+
+ if (mode == RPMMIRE_DEFAULT)
+ mode = defmode;
+
+ switch (mode) {
+ case RPMMIRE_DEFAULT:
+ case RPMMIRE_STRCMP:
+ break;
+ case RPMMIRE_REGEX:
+ preg = xcalloc(1, sizeof(*preg));
+ cflags = (REG_EXTENDED | REG_NOSUB);
+ rc = regcomp(preg, allpat, cflags);
+ if (rc) {
+ char msg[256];
+ (void) regerror(rc, preg, msg, sizeof(msg)-1);
+ msg[sizeof(msg)-1] = '\0';
+ rpmlog(RPMLOG_ERR, _("%s: regcomp failed: %s\n"), allpat, msg);
+ }
+ break;
+ case RPMMIRE_GLOB:
+ fnflags = FNM_PATHNAME | FNM_PERIOD;
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ if (rc) {
+ /* FIX: mire has kept values */
+ allpat = _free(allpat);
+ if (preg) {
+ regfree(preg);
+ preg = _free(preg);
+ }
+ return rc;
+ }
+
+ mi->mi_re = xrealloc(mi->mi_re, (mi->mi_nre + 1) * sizeof(*mi->mi_re));
+ mire = mi->mi_re + mi->mi_nre;
+ mi->mi_nre++;
+
+ mire->tag = tag;
+ mire->mode = mode;
+ mire->pattern = allpat;
+ mire->notmatch = notmatch;
+ mire->preg = preg;
+ mire->cflags = cflags;
+ mire->eflags = eflags;
+ mire->fnflags = fnflags;
+
+ if (mi->mi_nre > 1)
+ qsort(mi->mi_re, mi->mi_nre, sizeof(*mi->mi_re), mireCmp);
+
+ return rc;
+}
+
+/**
+ * Return iterator selector match.
+ * @param mi rpm database iterator
+ * @return 1 if header should be skipped
+ */
+static int mireSkip (const rpmdbMatchIterator mi)
+{
+ miRE mire;
+ uint32_t zero = 0;
+ int ntags = 0;
+ int nmatches = 0;
+ int rc;
+
+ if (mi->mi_h == NULL) /* XXX can't happen */
+ return 0;
+
+ /*
+ * Apply tag tests, implicitly "||" for multiple patterns/values of a
+ * single tag, implicitly "&&" between multiple tag patterns.
+ */
+ if ((mire = mi->mi_re) != NULL)
+ for (int i = 0; i < mi->mi_nre; i++, mire++) {
+ int anymatch;
+ struct rpmtd_s td;
+
+ if (!headerGet(mi->mi_h, mire->tag, &td, HEADERGET_MINMEM)) {
+ if (mire->tag != RPMTAG_EPOCH) {
+ ntags++;
+ continue;
+ }
+ /* "is package already installed" checks rely on this behavior */
+ td.count = 1;
+ td.type = RPM_INT32_TYPE;
+ td.data = &zero;
+ }
+
+ anymatch = 0; /* no matches yet */
+ while (1) {
+ rpmtdInit(&td);
+ while (rpmtdNext(&td) >= 0) {
+ char *str = rpmtdFormat(&td, RPMTD_FORMAT_STRING, NULL);
+ if (str) {
+ rc = miregexec(mire, str);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch))
+ anymatch++;
+ free(str);
+ }
+ }
+ if ((i+1) < mi->mi_nre && mire[0].tag == mire[1].tag) {
+ i++;
+ mire++;
+ continue;
+ }
+ break;
+ }
+ rpmtdFreeData(&td);
+
+ ntags++;
+ if (anymatch)
+ nmatches++;
+ }
+
+ return (ntags == nmatches ? 0 : 1);
+}
+
+int rpmdbSetIteratorRewrite(rpmdbMatchIterator mi, int rewrite)
+{
+ int rc;
+ if (mi == NULL)
+ return 0;
+ rc = (mi->mi_cflags & DB_WRITECURSOR) ? 1 : 0;
+ if (rewrite)
+ mi->mi_cflags |= DB_WRITECURSOR;
+ else
+ mi->mi_cflags &= ~DB_WRITECURSOR;
+ return rc;
+}
+
+int rpmdbSetIteratorModified(rpmdbMatchIterator mi, int modified)
+{
+ int rc;
+ if (mi == NULL)
+ return 0;
+ rc = mi->mi_modified;
+ mi->mi_modified = modified;
+ return rc;
+}
+
+int rpmdbSetHdrChk(rpmdbMatchIterator mi, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg))
+{
+ int rc = 0;
+ if (mi == NULL)
+ return 0;
+ mi->mi_ts = rpmtsLink(ts);
+ mi->mi_hdrchk = hdrchk;
+ return rc;
+}
+
+static rpmRC miVerifyHeader(rpmdbMatchIterator mi, const void *uh, size_t uhlen)
+{
+ rpmRC rpmrc = RPMRC_NOTFOUND;
+
+ if (!(mi->mi_hdrchk && mi->mi_ts))
+ return rpmrc;
+
+ /* Don't bother re-checking a previously read header. */
+ if (mi->mi_db->db_checked) {
+ if (intHashHasEntry(mi->mi_db->db_checked, mi->mi_offset))
+ rpmrc = RPMRC_OK;
+ }
+
+ /* If blob is unchecked, check blob import consistency now. */
+ if (rpmrc != RPMRC_OK) {
+ char * msg = NULL;
+ int lvl;
+
+ rpmrc = (*mi->mi_hdrchk) (mi->mi_ts, uh, uhlen, &msg);
+ lvl = (rpmrc == RPMRC_FAIL ? RPMLOG_ERR : RPMLOG_DEBUG);
+ rpmlog(lvl, "%s h#%8u %s",
+ (rpmrc == RPMRC_FAIL ? _("rpmdbNextIterator: skipping") : " read"),
+ mi->mi_offset, (msg ? msg : "\n"));
+ msg = _free(msg);
+
+ /* Mark header checked. */
+ if (mi->mi_db && mi->mi_db->db_checked && rpmrc == RPMRC_OK) {
+ intHashAddEntry(mi->mi_db->db_checked, mi->mi_offset);
+ }
+ }
+ return rpmrc;
+}
+
+/* FIX: mi->mi_key.data may be NULL */
+Header rpmdbNextIterator(rpmdbMatchIterator mi)
+{
+ dbiIndex dbi;
+ void * uh;
+ size_t uhlen;
+ DBT key, data;
+ void * keyp;
+ size_t keylen;
+ int rc;
+ int xx;
+
+ if (mi == NULL)
+ return NULL;
+
+ dbi = rpmdbOpenIndex(mi->mi_db, RPMDBI_PACKAGES, 0);
+ if (dbi == NULL)
+ return NULL;
+
+ /*
+ * Cursors are per-iterator, not per-dbi, so get a cursor for the
+ * iterator on 1st call. If the iteration is to rewrite headers, and the
+ * CDB model is used for the database, then the cursor needs to
+ * marked with DB_WRITECURSOR as well.
+ */
+ if (mi->mi_dbc == NULL)
+ xx = dbiCopen(dbi, &mi->mi_dbc, mi->mi_cflags);
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+top:
+ uh = NULL;
+ uhlen = 0;
+
+ do {
+ union _dbswap mi_offset;
+
+ if (mi->mi_set) {
+ if (!(mi->mi_setx < mi->mi_set->count))
+ return NULL;
+ mi->mi_offset = dbiIndexRecordOffset(mi->mi_set, mi->mi_setx);
+ mi->mi_filenum = dbiIndexRecordFileNumber(mi->mi_set, mi->mi_setx);
+ mi_offset.ui = mi->mi_offset;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ keyp = &mi_offset;
+ keylen = sizeof(mi_offset.ui);
+ } else {
+
+ key.data = keyp = (void *)mi->mi_keyp;
+ key.size = keylen = mi->mi_keylen;
+ data.data = uh;
+ data.size = uhlen;
+#if !defined(_USE_COPY_LOAD)
+ data.flags |= DB_DBT_MALLOC;
+#endif
+ rc = dbiGet(dbi, mi->mi_dbc, &key, &data,
+ (key.data == NULL ? DB_NEXT : DB_SET));
+ data.flags = 0;
+ keyp = key.data;
+ keylen = key.size;
+ uh = data.data;
+ uhlen = data.size;
+
+ /*
+ * If we got the next key, save the header instance number.
+ *
+ * Instance 0 (i.e. mi->mi_setx == 0) is the
+ * largest header instance in the database, and should be
+ * skipped.
+ */
+ if (keyp && mi->mi_setx && rc == 0) {
+ memcpy(&mi_offset, keyp, sizeof(mi_offset.ui));
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ mi->mi_offset = mi_offset.ui;
+ }
+
+ /* Terminate on error or end of keys */
+ if (rc || (mi->mi_setx && mi->mi_offset == 0))
+ return NULL;
+ }
+ mi->mi_setx++;
+ } while (mi->mi_offset == 0);
+
+ /* If next header is identical, return it now. */
+ if (mi->mi_prevoffset && mi->mi_offset == mi->mi_prevoffset) {
+ /* ...but rpmdb record numbers are unique, avoid endless loop */
+ return (mi->mi_rpmtag == RPMDBI_PACKAGES) ? NULL : mi->mi_h;
+ }
+
+ /* Retrieve next header blob for index iterator. */
+ if (uh == NULL) {
+ key.data = keyp;
+ key.size = keylen;
+#if !defined(_USE_COPY_LOAD)
+ data.flags |= DB_DBT_MALLOC;
+#endif
+ rc = dbiGet(dbi, mi->mi_dbc, &key, &data, DB_SET);
+ data.flags = 0;
+ keyp = key.data;
+ keylen = key.size;
+ uh = data.data;
+ uhlen = data.size;
+ if (rc)
+ return NULL;
+ }
+
+ /* Rewrite current header (if necessary) and unlink. */
+ xx = miFreeHeader(mi, dbi);
+
+ /* Is this the end of the iteration? */
+ if (uh == NULL)
+ return NULL;
+
+ /* Verify header if enabled, skip damaged and inconsistent headers */
+ if (miVerifyHeader(mi, uh, uhlen) == RPMRC_FAIL) {
+ goto top;
+ }
+
+ /* Did the header blob load correctly? */
+#if !defined(_USE_COPY_LOAD)
+ mi->mi_h = headerLoad(uh);
+#else
+ mi->mi_h = headerCopyLoad(uh);
+#endif
+ if (mi->mi_h == NULL || !headerIsEntry(mi->mi_h, RPMTAG_NAME)) {
+ rpmlog(RPMLOG_ERR,
+ _("rpmdb: damaged header #%u retrieved -- skipping.\n"),
+ mi->mi_offset);
+ goto top;
+ }
+
+ /*
+ * Skip this header if iterator selector (if any) doesn't match.
+ */
+ if (mireSkip(mi)) {
+ /* XXX hack, can't restart with Packages locked on single instance. */
+ if (mi->mi_set || mi->mi_keyp == NULL)
+ goto top;
+ return NULL;
+ }
+ headerSetInstance(mi->mi_h, mi->mi_offset);
+
+ mi->mi_prevoffset = mi->mi_offset;
+ mi->mi_modified = 0;
+
+ return mi->mi_h;
+}
+
+/** \ingroup rpmdb
+ * sort the iterator by (recnum, filenum)
+ * Return database iterator.
+ * @param mi rpm database iterator
+ */
+void rpmdbSortIterator(rpmdbMatchIterator mi)
+{
+ if (mi && mi->mi_set && mi->mi_set->recs && mi->mi_set->count > 0) {
+ /*
+ * mergesort is much (~10x with lots of identical basenames) faster
+ * than pure quicksort, but glibc uses msort_with_tmp() on stack.
+ */
+#if defined(__GLIBC__)
+ qsort(mi->mi_set->recs, mi->mi_set->count,
+ sizeof(*mi->mi_set->recs), hdrNumCmp);
+#else
+ mergesort(mi->mi_set->recs, mi->mi_set->count,
+ sizeof(*mi->mi_set->recs), hdrNumCmp);
+#endif
+ mi->mi_sorted = 1;
+ }
+}
+
+int rpmdbExtendIterator(rpmdbMatchIterator mi,
+ const void * keyp, size_t keylen)
+{
+ DBC * dbcursor;
+ DBT data, key;
+ dbiIndex dbi = NULL;
+ dbiIndexSet set;
+ int rc;
+ int xx;
+
+ if (mi == NULL || keyp == NULL)
+ return 1;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+ key.data = (void *) keyp;
+ key.size = keylen ? keylen : strlen(keyp);
+
+ dbcursor = mi->mi_dbc;
+
+ dbi = rpmdbOpenIndex(mi->mi_db, mi->mi_rpmtag, 0);
+ if (dbi == NULL)
+ return 1;
+
+ xx = dbiCopen(dbi, &dbcursor, 0);
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+
+ if (rc) { /* error/not found */
+ if (rc != DB_NOTFOUND)
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ return rc;
+ }
+
+ set = NULL;
+ (void) dbt2set(dbi, &data, &set);
+
+ if (mi->mi_set == NULL) {
+ mi->mi_set = set;
+ } else {
+ dbiGrowSet(mi->mi_set, set->count);
+ memcpy(mi->mi_set->recs + mi->mi_set->count, set->recs,
+ set->count * sizeof(*(mi->mi_set->recs)));
+ mi->mi_set->count += set->count;
+ set = dbiFreeIndexSet(set);
+ }
+
+ return rc;
+}
+
+int rpmdbPruneIterator(rpmdbMatchIterator mi, intHash hdrNums)
+{
+ if (mi == NULL || hdrNums == NULL || intHashNumKeys(hdrNums) <= 0)
+ return 1;
+
+ if (!mi->mi_set)
+ return 0;
+
+ unsigned int from;
+ unsigned int to = 0;
+ unsigned int num = mi->mi_set->count;
+
+ assert(mi->mi_set->count > 0);
+
+ for (from = 0; from < num; from++) {
+ if (intHashHasEntry(hdrNums, mi->mi_set->recs[from].hdrNum)) {
+ mi->mi_set->count--;
+ continue;
+ }
+ if (from != to)
+ mi->mi_set->recs[to] = mi->mi_set->recs[from]; /* structure assignment */
+ to++;
+ }
+ return 0;
+}
+
+int rpmdbAppendIterator(rpmdbMatchIterator mi, const int * hdrNums, int nHdrNums)
+{
+ if (mi == NULL || hdrNums == NULL || nHdrNums <= 0)
+ return 1;
+
+ if (mi->mi_set == NULL)
+ mi->mi_set = xcalloc(1, sizeof(*mi->mi_set));
+ (void) dbiAppendSet(mi->mi_set, hdrNums, nHdrNums, sizeof(*hdrNums), 0);
+ return 0;
+}
+
+rpmdbMatchIterator rpmdbNewIterator(rpmdb db, rpmDbiTagVal dbitag)
+{
+ rpmdbMatchIterator mi = NULL;
+
+ if (rpmdbOpenIndex(db, dbitag, 0) == NULL)
+ return NULL;
+
+ mi = xcalloc(1, sizeof(*mi));
+ mi->mi_keyp = NULL;
+ mi->mi_keylen = 0;
+ mi->mi_set = NULL;
+ mi->mi_db = rpmdbLink(db);
+ mi->mi_rpmtag = dbitag;
+
+ mi->mi_dbc = NULL;
+ mi->mi_setx = 0;
+ mi->mi_h = NULL;
+ mi->mi_sorted = 0;
+ mi->mi_cflags = 0;
+ mi->mi_modified = 0;
+ mi->mi_prevoffset = 0;
+ mi->mi_offset = 0;
+ mi->mi_filenum = 0;
+ mi->mi_nre = 0;
+ mi->mi_re = NULL;
+
+ mi->mi_ts = NULL;
+ mi->mi_hdrchk = NULL;
+
+ /* Chain cursors for teardown on abnormal exit. */
+ mi->mi_next = rpmmiRock;
+ rpmmiRock = mi;
+
+ return mi;
+};
+
+rpmdbMatchIterator rpmdbInitIterator(rpmdb db, rpmDbiTagVal rpmtag,
+ const void * keyp, size_t keylen)
+{
+ rpmdbMatchIterator mi = NULL;
+ dbiIndexSet set = NULL;
+ dbiIndex dbi;
+ void * mi_keyp = NULL;
+ int isLabel = 0;
+
+ if (db == NULL)
+ return NULL;
+
+ (void) rpmdbCheckSignals();
+
+ /* XXX HACK to remove rpmdbFindByLabel/findMatches from the API */
+ if (rpmtag == RPMDBI_LABEL) {
+ rpmtag = RPMDBI_NAME;
+ isLabel = 1;
+ }
+
+ dbi = rpmdbOpenIndex(db, rpmtag, 0);
+ if (dbi == NULL)
+ return NULL;
+
+ /*
+ * Handle label and file name special cases.
+ * Otherwise, retrieve join keys for secondary lookup.
+ */
+ if (rpmtag != RPMDBI_PACKAGES) {
+ DBT key, data;
+ DBC * dbcursor = NULL;
+ int rc = 0;
+ int xx;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ if (keyp) {
+
+ if (isLabel) {
+ xx = dbiCopen(dbi, &dbcursor, 0);
+ rc = dbiFindByLabel(db, dbi, dbcursor, &key, &data, keyp, &set);
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+ } else if (rpmtag == RPMDBI_BASENAMES) {
+ rc = rpmdbFindByFile(db, keyp, &key, &data, &set);
+ } else {
+ xx = dbiCopen(dbi, &dbcursor, 0);
+
+ key.data = (void *) keyp;
+ key.size = keylen;
+ if (key.data && key.size == 0)
+ key.size = strlen((char *)key.data);
+ if (key.data && key.size == 0)
+ key.size++; /* XXX "/" fixup. */
+
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc > 0) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (key.data ? (char *)key.data : "???"),
+ dbiName(dbi));
+ }
+
+ /* Join keys need to be native endian internally. */
+ if (rc == 0)
+ (void) dbt2set(dbi, &data, &set);
+
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+ }
+ if (rc) { /* error/not found */
+ set = dbiFreeIndexSet(set);
+ goto exit;
+ }
+ } else {
+ /* get all entries from index */
+ xx = dbiCopen(dbi, &dbcursor, 0);
+
+ while ((rc = dbiGet(dbi, dbcursor, &key, &data, DB_NEXT)) == 0) {
+ dbiIndexSet newset = NULL;
+
+ (void) dbt2set(dbi, &data, &newset);
+ if (set == NULL) {
+ set = newset;
+ } else {
+ dbiAppendSet(set, newset->recs, newset->count, sizeof(*(set->recs)), 0);
+ dbiFreeIndexSet(newset);
+ }
+ }
+
+ if (rc != DB_NOTFOUND) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (key.data ? (char *)key.data : "???"), dbiName(dbi));
+ }
+
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+
+ if (rc != DB_NOTFOUND) { /* error */
+ set = dbiFreeIndexSet(set);
+ goto exit;
+ }
+ }
+ }
+
+ /* Copy the retrieval key, byte swapping header instance if necessary. */
+ if (keyp) {
+ switch (rpmtag) {
+ case RPMDBI_PACKAGES:
+ { union _dbswap *k;
+
+ assert(keylen == sizeof(k->ui)); /* xxx programmer error */
+ k = xmalloc(sizeof(*k));
+ memcpy(k, keyp, keylen);
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(*k);
+ mi_keyp = k;
+ } break;
+ default:
+ { char * k;
+ if (keylen == 0)
+ keylen = strlen(keyp);
+ k = xmalloc(keylen + 1);
+ memcpy(k, keyp, keylen);
+ k[keylen] = '\0'; /* XXX assumes strings */
+ mi_keyp = k;
+ } break;
+ }
+ }
+
+ mi = rpmdbNewIterator(db, rpmtag);
+ mi->mi_keyp = mi_keyp;
+ mi->mi_keylen = keylen;
+ mi->mi_set = set;
+
+ if (rpmtag != RPMDBI_PACKAGES && keyp == NULL) {
+ rpmdbSortIterator(mi);
+ }
+
+exit:
+ return mi;
+}
+
+/*
+ * Convert current tag data to db key
+ * @param tagdata Tag data container
+ * @retval key DB key struct
+ * @retval freedata Should key.data be freed afterwards
+ * Return 0 to signal this item should be discarded (ie continue)
+ */
+static int td2key(rpmtd tagdata, DBT *key, int *freedata)
+{
+ const char *str = NULL;
+
+ *freedata = 0;
+ switch (rpmtdType(tagdata)) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ key->size = sizeof(uint8_t);
+ key->data = rpmtdGetChar(tagdata);
+ break;
+ case RPM_INT16_TYPE:
+ key->size = sizeof(uint16_t);
+ key->data = rpmtdGetUint16(tagdata);
+ break;
+ case RPM_INT32_TYPE:
+ key->size = sizeof(uint32_t);
+ key->data = rpmtdGetUint32(tagdata);
+ break;
+ case RPM_INT64_TYPE:
+ key->size = sizeof(uint64_t);
+ key->data = rpmtdGetUint64(tagdata);
+ break;
+ case RPM_BIN_TYPE:
+ key->size = tagdata->count;
+ key->data = tagdata->data;
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ default:
+ str = rpmtdGetString(tagdata);
+ key->data = (char *) str; /* XXX discards const */
+ key->size = strlen(str);
+ break;
+ }
+
+ if (key->size == 0)
+ key->size = strlen((char *)key->data);
+ if (key->size == 0)
+ key->size++; /* XXX "/" fixup. */
+
+ return 1;
+}
+/*
+ * rpmdbIndexIterator
+ */
+
+rpmdbIndexIterator rpmdbIndexIteratorInit(rpmdb db, rpmDbiTag rpmtag)
+{
+ rpmdbIndexIterator ii;
+ dbiIndex dbi = NULL;
+
+ if (db == NULL)
+ return NULL;
+
+ (void) rpmdbCheckSignals();
+
+ dbi = rpmdbOpenIndex(db, rpmtag, 0);
+ if (dbi == NULL)
+ return NULL;
+
+ /* Chain cursors for teardown on abnormal exit. */
+ ii = xcalloc(1, sizeof(*ii));
+ ii->ii_next = rpmiiRock;
+ rpmiiRock = ii;
+
+ ii->ii_db = rpmdbLink(db);
+ ii->ii_rpmtag = rpmtag;
+ ii->ii_dbi = dbi;
+ ii->ii_set = NULL;
+
+ return ii;
+}
+
+int rpmdbIndexIteratorNext(rpmdbIndexIterator ii, const void ** key, size_t * keylen)
+{
+ int rc, xx;
+ DBT data;
+
+ if (ii == NULL)
+ return -1;
+
+ if (ii->ii_dbc == NULL)
+ xx = dbiCopen(ii->ii_dbi, &ii->ii_dbc, 0);
+
+ /* free old data */
+ ii->ii_set = dbiFreeIndexSet(ii->ii_set);
+
+ memset(&data, 0, sizeof(data));
+ rc = dbiGet(ii->ii_dbi, ii->ii_dbc, &ii->ii_key, &data, DB_NEXT);
+
+ if (rc != 0) {
+ *key = NULL;
+ *keylen = 0;
+
+ if (rc != DB_NOTFOUND) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d:%s) getting next key from %s index\n"),
+ rc, db_strerror(rc), rpmTagGetName(ii->ii_rpmtag));
+ }
+ return -1;
+ }
+
+ (void) dbt2set(ii->ii_dbi, &data, &ii->ii_set);
+ *key = ii->ii_key.data;
+ *keylen = ii->ii_key.size;
+
+ return 0;
+}
+
+unsigned int rpmdbIndexIteratorNumPkgs(rpmdbIndexIterator ii)
+{
+ return (ii && ii->ii_set) ? dbiIndexSetCount(ii->ii_set) : 0;
+}
+
+unsigned int rpmdbIndexIteratorPkgOffset(rpmdbIndexIterator ii, unsigned int nr)
+{
+ if (!ii || !ii->ii_set)
+ return 0;
+ if (dbiIndexSetCount(ii->ii_set) <= nr)
+ return 0;
+ return dbiIndexRecordOffset(ii->ii_set, nr);
+}
+
+unsigned int rpmdbIndexIteratorTagNum(rpmdbIndexIterator ii, unsigned int nr)
+{
+ if (!ii || !ii->ii_set)
+ return 0;
+ if (dbiIndexSetCount(ii->ii_set) <= nr)
+ return 0;
+ return dbiIndexRecordFileNumber(ii->ii_set, nr);
+}
+
+rpmdbIndexIterator rpmdbIndexIteratorFree(rpmdbIndexIterator ii)
+{
+ rpmdbIndexIterator * prev, next;
+ int xx;
+
+ if (ii == NULL)
+ return ii;
+
+ prev = &rpmiiRock;
+ while ((next = *prev) != NULL && next != ii)
+ prev = &next->ii_next;
+ if (next) {
+ *prev = next->ii_next;
+ next->ii_next = NULL;
+ }
+
+ if (ii->ii_dbc)
+ xx = dbiCclose(ii->ii_dbi, ii->ii_dbc, 0);
+ ii->ii_dbc = NULL;
+ ii->ii_dbi = NULL;
+ rpmdbClose(ii->ii_db);
+ ii->ii_set = dbiFreeIndexSet(ii->ii_set);
+
+ ii = _free(ii);
+ return ii;
+}
+
+
+
+
+static void logAddRemove(const char *dbiname, int removing, rpmtd tagdata)
+{
+ rpm_count_t c = rpmtdCount(tagdata);
+ if (c == 1 && rpmtdType(tagdata) == RPM_STRING_TYPE) {
+ rpmlog(RPMLOG_DEBUG, "%s \"%s\" %s %s index.\n",
+ removing ? "removing" : "adding", rpmtdGetString(tagdata),
+ removing ? "from" : "to", dbiname);
+ } else if (c > 0) {
+ rpmlog(RPMLOG_DEBUG, "%s %d entries %s %s index.\n",
+ removing ? "removing" : "adding", c,
+ removing ? "from" : "to", dbiname);
+ }
+}
+
+/* Update primary Packages index. NULL hdr means remove */
+static int updatePackages(dbiIndex dbi, unsigned int hdrNum, DBT *hdr)
+{
+ union _dbswap mi_offset;
+ int rc = 0;
+ int xx;
+ DBC * dbcursor = NULL;
+ DBT key;
+
+ if (dbi == NULL || hdrNum == 0)
+ return 1;
+
+ memset(&key, 0, sizeof(key));
+
+ xx = dbiCopen(dbi, &dbcursor, DB_WRITECURSOR);
+
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ key.data = (void *) &mi_offset;
+ key.size = sizeof(mi_offset.ui);
+
+ if (hdr) {
+ rc = dbiPut(dbi, dbcursor, &key, hdr, DB_KEYLAST);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) adding header #%d record\n"), rc, hdrNum);
+ }
+ } else {
+ DBT data;
+
+ memset(&data, 0, sizeof(data));
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) removing header #%d record\n"), rc, hdrNum);
+ } else
+ rc = dbiDel(dbi, dbcursor, &key, &data, 0);
+ }
+
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ xx = dbiSync(dbi, 0);
+
+ return rc;
+}
+
+int rpmdbRemove(rpmdb db, unsigned int hdrNum)
+{
+ dbiIndex dbi;
+ Header h;
+ sigset_t signalMask;
+ int ret = 0;
+
+ if (db == NULL)
+ return 0;
+
+ h = rpmdbGetHeaderAt(db, hdrNum);
+
+ if (h == NULL) {
+ rpmlog(RPMLOG_ERR, _("%s: cannot read header at 0x%x\n"),
+ "rpmdbRemove", hdrNum);
+ return 1;
+ } else {
+ char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
+ rpmlog(RPMLOG_DEBUG, " --- h#%8u %s\n", hdrNum, nevra);
+ free(nevra);
+ }
+
+ (void) blockSignals(&signalMask);
+
+ dbi = rpmdbOpenIndex(db, RPMDBI_PACKAGES, 0);
+ /* Remove header from primary index */
+ ret = updatePackages(dbi, hdrNum, NULL);
+
+ /* Remove associated data from secondary indexes */
+ if (ret == 0) {
+ struct dbiIndexItem rec = { .hdrNum = hdrNum, .tagNum = 0 };
+ int rc = 0;
+ DBC * dbcursor = NULL;
+ DBT key, data;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ for (int dbix = 1; dbix < dbiTagsMax; dbix++) {
+ rpmDbiTag rpmtag = dbiTags[dbix];
+ int xx = 0;
+ struct rpmtd_s tagdata;
+
+ if (!(dbi = rpmdbOpenIndex(db, rpmtag, 0)))
+ continue;
+
+ if (!headerGet(h, rpmtag, &tagdata, HEADERGET_MINMEM))
+ continue;
+
+ xx = dbiCopen(dbi, &dbcursor, DB_WRITECURSOR);
+
+ logAddRemove(dbiName(dbi), 1, &tagdata);
+ while (rpmtdNext(&tagdata) >= 0) {
+ dbiIndexSet set;
+ int freedata = 0;
+
+ if (!td2key(&tagdata, &key, &freedata)) {
+ continue;
+ }
+
+ /* XXX
+ * This is almost right, but, if there are duplicate tag
+ * values, there will be duplicate attempts to remove
+ * the header instance. It's faster to just ignore errors
+ * than to do things correctly.
+ */
+
+ /*
+ * XXX with duplicates, an accurate data value and
+ * DB_GET_BOTH is needed.
+ * */
+ set = NULL;
+
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc == 0) { /* success */
+ (void) dbt2set(dbi, &data, &set);
+ } else if (rc == DB_NOTFOUND) { /* not found */
+ goto cont;
+ } else { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) setting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ ret += 1;
+ goto cont;
+ }
+
+ rc = dbiPruneSet(set, &rec, 1, sizeof(rec), 1);
+
+ /* If nothing was pruned, then don't bother updating. */
+ if (rc) {
+ set = dbiFreeIndexSet(set);
+ goto cont;
+ }
+
+ if (set->count > 0) {
+ (void) set2dbt(dbi, &data, set);
+ rc = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record \"%s\" into %s\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ ret += 1;
+ }
+ data.data = _free(data.data);
+ data.size = 0;
+ } else {
+ rc = dbiDel(dbi, dbcursor, &key, &data, 0);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) removing record \"%s\" from %s\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ ret += 1;
+ }
+ }
+ set = dbiFreeIndexSet(set);
+cont:
+ if (freedata) {
+ free(key.data);
+ }
+ }
+
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+
+ xx = dbiSync(dbi, 0);
+
+ rpmtdFreeData(&tagdata);
+ }
+ }
+
+ (void) unblockSignals(&signalMask);
+
+ h = headerFree(h);
+
+ /* XXX return ret; */
+ return 0;
+}
+
+/* Get current header instance number or try to allocate a new one */
+static unsigned int pkgInstance(dbiIndex dbi, int alloc)
+{
+ unsigned int hdrNum = 0;
+
+ if (dbi != NULL && dbiType(dbi) == DBI_PRIMARY) {
+ DBC * dbcursor = NULL;
+ DBT key, data;
+ unsigned int firstkey = 0;
+ union _dbswap mi_offset;
+ int ret;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ ret = dbiCopen(dbi, &dbcursor, alloc ? DB_WRITECURSOR : 0);
+
+ /* Key 0 holds the current largest instance, fetch it */
+ key.data = &firstkey;
+ key.size = sizeof(firstkey);
+ ret = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+
+ if (ret == 0 && data.data) {
+ memcpy(&mi_offset, data.data, sizeof(mi_offset.ui));
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ hdrNum = mi_offset.ui;
+ }
+
+ if (alloc) {
+ /* Rather complicated "increment by one", bswapping as needed */
+ ++hdrNum;
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ if (ret == 0 && data.data) {
+ memcpy(data.data, &mi_offset, sizeof(mi_offset.ui));
+ } else {
+ data.data = &mi_offset;
+ data.size = sizeof(mi_offset.ui);
+ }
+
+ /* Unless we manage to insert the new instance number, we failed */
+ ret = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+ if (ret) {
+ hdrNum = 0;
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) allocating new package instance\n"), ret);
+ }
+
+ ret = dbiSync(dbi, 0);
+ }
+ ret = dbiCclose(dbi, dbcursor, 0);
+ }
+
+ return hdrNum;
+}
+
+/* Add data to secondary index */
+static int addToIndex(dbiIndex dbi, rpmTagVal rpmtag, unsigned int hdrNum, Header h)
+{
+ int xx, i, rc = 0;
+ struct rpmtd_s tagdata, reqflags;
+ DBC * dbcursor = NULL;
+
+ switch (rpmtag) {
+ case RPMTAG_REQUIRENAME:
+ headerGet(h, RPMTAG_REQUIREFLAGS, &reqflags, HEADERGET_MINMEM);
+ /* fallthrough */
+ default:
+ headerGet(h, rpmtag, &tagdata, HEADERGET_MINMEM);
+ break;
+ }
+
+ if (rpmtdCount(&tagdata) == 0) {
+ if (rpmtag != RPMTAG_GROUP)
+ goto exit;
+
+ /* XXX preserve legacy behavior */
+ tagdata.type = RPM_STRING_TYPE;
+ tagdata.data = (const char **) "Unknown";
+ tagdata.count = 1;
+ }
+
+ xx = dbiCopen(dbi, &dbcursor, DB_WRITECURSOR);
+
+ logAddRemove(dbiName(dbi), 0, &tagdata);
+ while ((i = rpmtdNext(&tagdata)) >= 0) {
+ dbiIndexSet set;
+ int freedata = 0, j;
+ DBT key, data;
+ /* Include the tagNum in all indices (only files use though) */
+ struct dbiIndexItem rec = { .hdrNum = hdrNum, .tagNum = i };
+
+ switch (rpmtag) {
+ case RPMTAG_REQUIRENAME: {
+ /* Filter out install prerequisites. */
+ rpm_flag_t *rflag = rpmtdNextUint32(&reqflags);
+ if (rflag && isInstallPreReq(*rflag) &&
+ !isErasePreReq(*rflag))
+ continue;
+ break;
+ }
+ case RPMTAG_TRIGGERNAME:
+ if (i > 0) { /* don't add duplicates */
+ const char **tnames = tagdata.data;
+ const char *str = rpmtdGetString(&tagdata);
+ for (j = 0; j < i; j++) {
+ if (rstreq(str, tnames[j]))
+ break;
+ }
+ if (j < i)
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ if (!td2key(&tagdata, &key, &freedata)) {
+ continue;
+ }
+
+ /*
+ * XXX with duplicates, an accurate data value and
+ * DB_GET_BOTH is needed.
+ */
+
+ set = NULL;
+
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc == 0) { /* success */
+ /* With duplicates, cursor is positioned, discard the record. */
+ if (!dbi->dbi_permit_dups)
+ (void) dbt2set(dbi, &data, &set);
+ } else if (rc != DB_NOTFOUND) { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ rc += 1;
+ goto cont;
+ }
+
+ if (set == NULL) /* not found or duplicate */
+ set = xcalloc(1, sizeof(*set));
+
+ (void) dbiAppendSet(set, &rec, 1, sizeof(rec), 0);
+
+ (void) set2dbt(dbi, &data, set);
+ rc = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record %s into %s\n"),
+ rc, (char*)key.data, dbiName(dbi));
+ rc += 1;
+ }
+ data.data = _free(data.data);
+ data.size = 0;
+ set = dbiFreeIndexSet(set);
+cont:
+ if (freedata) {
+ free(key.data);
+ }
+ }
+
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+
+ xx = dbiSync(dbi, 0);
+
+exit:
+ rpmtdFreeData(&tagdata);
+ return rc;
+}
+
+int rpmdbAdd(rpmdb db, Header h)
+{
+ DBT hdr;
+ sigset_t signalMask;
+ dbiIndex dbi;
+ unsigned int hdrNum = 0;
+ int ret = 0;
+ int hdrOk;
+
+ if (db == NULL)
+ return 0;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ hdr.size = headerSizeof(h, HEADER_MAGIC_NO);
+ hdr.data = headerUnload(h);
+ hdrOk = (hdr.data != NULL && hdr.size > 0);
+
+ if (!hdrOk) {
+ ret = -1;
+ goto exit;
+ }
+
+ (void) blockSignals(&signalMask);
+
+ dbi = rpmdbOpenIndex(db, RPMDBI_PACKAGES, 0);
+ hdrNum = pkgInstance(dbi, 1);
+
+ /* Add header to primary index */
+ ret = updatePackages(dbi, hdrNum, &hdr);
+
+ /* Add associated data to secondary indexes */
+ if (ret == 0) {
+ for (int dbix = 1; dbix < dbiTagsMax; dbix++) {
+ rpmDbiTag rpmtag = dbiTags[dbix];
+
+ if (!(dbi = rpmdbOpenIndex(db, rpmtag, 0)))
+ continue;
+
+ ret += addToIndex(dbi, rpmtag, hdrNum, h);
+ }
+ }
+
+ /* If everthing ok, mark header as installed now */
+ if (ret == 0) {
+ headerSetInstance(h, hdrNum);
+ }
+
+exit:
+ free(hdr.data);
+ (void) unblockSignals(&signalMask);
+
+ return ret;
+}
+
+/*
+ * Remove DB4 environment (and lock), ie the equivalent of
+ * rm -f <prefix>/<dbpath>/__db.???
+ * Environment files not existing is not an error, failure to unlink is,
+ * return zero on success.
+ * TODO/FIX: push this down to db3.c where it belongs
+ */
+static int cleanDbenv(const char *prefix, const char *dbpath)
+{
+ ARGV_t paths = NULL, p;
+ int rc = 0;
+ char *pattern = rpmGetPath(prefix, "/", dbpath, "/__db.???", NULL);
+
+ if (rpmGlob(pattern, NULL, &paths) == 0) {
+ for (p = paths; *p; p++) {
+ rc += unlink(*p);
+ }
+ argvFree(paths);
+ }
+ free(pattern);
+ return rc;
+}
+
+static int rpmdbRemoveDatabase(const char * prefix, const char * dbpath)
+{
+ int i;
+ char *path;
+ int xx;
+
+ for (i = 0; i < dbiTagsMax; i++) {
+ const char * base = rpmTagGetName(dbiTags[i]);
+ path = rpmGetPath(prefix, "/", dbpath, "/", base, NULL);
+ if (access(path, F_OK) == 0)
+ xx = unlink(path);
+ free(path);
+ }
+ cleanDbenv(prefix, dbpath);
+
+ path = rpmGetPath(prefix, "/", dbpath, NULL);
+ xx = rmdir(path);
+ free(path);
+
+ return 0;
+}
+
+static int rpmdbMoveDatabase(const char * prefix,
+ const char * olddbpath, const char * newdbpath)
+{
+ int i;
+ struct stat st;
+ int rc = 0;
+ int xx;
+ int selinux = is_selinux_enabled() && (matchpathcon_init(NULL) != -1);
+ sigset_t sigMask;
+
+ blockSignals(&sigMask);
+ for (i = 0; i < dbiTagsMax; i++) {
+ rpmDbiTag rpmtag = dbiTags[i];
+ const char *base = rpmTagGetName(rpmtag);
+ char *src = rpmGetPath(prefix, "/", olddbpath, "/", base, NULL);
+ char *dest = rpmGetPath(prefix, "/", newdbpath, "/", base, NULL);
+
+ if (access(src, F_OK) != 0)
+ goto cont;
+
+ /*
+ * Restore uid/gid/mode/security context if possible.
+ */
+ if (stat(dest, &st) < 0)
+ if (stat(src, &st) < 0)
+ goto cont;
+
+ if ((xx = rename(src, dest)) != 0) {
+ rc = 1;
+ goto cont;
+ }
+ xx = chown(dest, st.st_uid, st.st_gid);
+ xx = chmod(dest, (st.st_mode & 07777));
+
+ if (selinux) {
+ security_context_t scon = NULL;
+ if (matchpathcon(dest, st.st_mode, &scon) != -1) {
+ (void) setfilecon(dest, scon);
+ freecon(scon);
+ }
+ }
+
+cont:
+ free(src);
+ free(dest);
+ }
+
+ cleanDbenv(prefix, olddbpath);
+ cleanDbenv(prefix, newdbpath);
+
+ unblockSignals(&sigMask);
+
+ if (selinux) {
+ (void) matchpathcon_fini();
+ }
+ return rc;
+}
+
+int rpmdbRebuild(const char * prefix, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg))
+{
+ rpmdb olddb;
+ char * dbpath = NULL;
+ char * rootdbpath = NULL;
+ rpmdb newdb;
+ char * newdbpath = NULL;
+ char * newrootdbpath = NULL;
+ int nocleanup = 1;
+ int failed = 0;
+ int removedir = 0;
+ int rc = 0, xx;
+
+ dbpath = rpmGetPath("%{?_dbpath}", NULL);
+ if (rstreq(dbpath, "")) {
+ rpmlog(RPMLOG_ERR, _("no dbpath has been set"));
+ rc = 1;
+ goto exit;
+ }
+ rootdbpath = rpmGetPath(prefix, dbpath, NULL);
+
+ newdbpath = rpmGetPath("%{?_dbpath_rebuild}", NULL);
+ if (rstreq(newdbpath, "") || rstreq(newdbpath, dbpath)) {
+ newdbpath = _free(newdbpath);
+ rasprintf(&newdbpath, "%srebuilddb.%d", dbpath, (int) getpid());
+ nocleanup = 0;
+ }
+ newrootdbpath = rpmGetPath(prefix, newdbpath, NULL);
+
+ rpmlog(RPMLOG_DEBUG, "rebuilding database %s into %s\n",
+ rootdbpath, newrootdbpath);
+
+ if (mkdir(newrootdbpath, 0755)) {
+ rpmlog(RPMLOG_ERR, _("failed to create directory %s: %s\n"),
+ newrootdbpath, strerror(errno));
+ rc = 1;
+ goto exit;
+ }
+ removedir = 1;
+
+ if (openDatabase(prefix, dbpath, &olddb, O_RDONLY, 0644, 0)) {
+ rc = 1;
+ goto exit;
+ }
+ if (openDatabase(prefix, newdbpath, &newdb,
+ (O_RDWR | O_CREAT), 0644, RPMDB_FLAG_REBUILD)) {
+ rc = 1;
+ goto exit;
+ }
+
+ { Header h = NULL;
+ rpmdbMatchIterator mi;
+#define _RECNUM rpmdbGetIteratorOffset(mi)
+
+ mi = rpmdbInitIterator(olddb, RPMDBI_PACKAGES, NULL, 0);
+ if (ts && hdrchk)
+ (void) rpmdbSetHdrChk(mi, ts, hdrchk);
+
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+
+ /* let's sanity check this record a bit, otherwise just skip it */
+ if (!(headerIsEntry(h, RPMTAG_NAME) &&
+ headerIsEntry(h, RPMTAG_VERSION) &&
+ headerIsEntry(h, RPMTAG_RELEASE) &&
+ headerIsEntry(h, RPMTAG_BUILDTIME)))
+ {
+ rpmlog(RPMLOG_ERR,
+ _("header #%u in the database is bad -- skipping.\n"),
+ _RECNUM);
+ continue;
+ }
+
+ /* Deleted entries are eliminated in legacy headers by copy. */
+ { Header nh = (headerIsEntry(h, RPMTAG_HEADERIMAGE)
+ ? headerCopy(h) : NULL);
+ rc = rpmdbAdd(newdb, (nh ? nh : h));
+ nh = headerFree(nh);
+ }
+
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("cannot add record originally at %u\n"), _RECNUM);
+ failed = 1;
+ break;
+ }
+ }
+
+ mi = rpmdbFreeIterator(mi);
+
+ }
+
+ xx = rpmdbClose(olddb);
+ xx = rpmdbClose(newdb);
+
+ if (failed) {
+ rpmlog(RPMLOG_WARNING,
+ _("failed to rebuild database: original database "
+ "remains in place\n"));
+
+ xx = rpmdbRemoveDatabase(prefix, newdbpath);
+ rc = 1;
+ goto exit;
+ } else if (!nocleanup) {
+ if (rpmdbMoveDatabase(prefix, newdbpath, dbpath)) {
+ rpmlog(RPMLOG_ERR, _("failed to replace old database with new "
+ "database!\n"));
+ rpmlog(RPMLOG_ERR, _("replace files in %s with files from %s "
+ "to recover"), dbpath, newdbpath);
+ rc = 1;
+ goto exit;
+ }
+ }
+ rc = 0;
+
+exit:
+ if (removedir && !(rc == 0 && nocleanup)) {
+ if (rmdir(newrootdbpath))
+ rpmlog(RPMLOG_ERR, _("failed to remove directory %s: %s\n"),
+ newrootdbpath, strerror(errno));
+ }
+ free(newdbpath);
+ free(dbpath);
+ free(newrootdbpath);
+ free(rootdbpath);
+
+ return rc;
+}
diff --git a/lib/rpmdb.h b/lib/rpmdb.h
new file mode 100644
index 0000000..3c7aac2
--- /dev/null
+++ b/lib/rpmdb.h
@@ -0,0 +1,215 @@
+#ifndef H_RPMDB
+#define H_RPMDB
+
+/** \ingroup rpmdb dbi
+ * \file lib/rpmdb.h
+ * Access RPM indices using Berkeley DB interface(s).
+ */
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmsw.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Tag value pattern match mode.
+ */
+typedef enum rpmMireMode_e {
+ RPMMIRE_DEFAULT = 0, /*!< regex with \., .* and ^...$ added */
+ RPMMIRE_STRCMP = 1, /*!< strings using strcmp(3) */
+ RPMMIRE_REGEX = 2, /*!< regex(7) patterns through regcomp(3) */
+ RPMMIRE_GLOB = 3 /*!< glob(7) patterns through fnmatch(3) */
+} rpmMireMode;
+
+typedef enum rpmdbOpX_e {
+ RPMDB_OP_DBGET = 1,
+ RPMDB_OP_DBPUT = 2,
+ RPMDB_OP_DBDEL = 3,
+ RPMDB_OP_MAX = 4
+} rpmdbOpX;
+
+/** \ingroup rpmdb
+ * Retrieve operation timestamp from rpm database.
+ * @param db rpm database
+ * @param opx operation timestamp index
+ * @return pointer to operation timestamp.
+ */
+rpmop rpmdbOp(rpmdb db, rpmdbOpX opx);
+
+/** \ingroup rpmdb
+ * Open all database indices.
+ * @param db rpm database
+ * @return 0 on success
+ */
+int rpmdbOpenAll (rpmdb db);
+
+/** \ingroup rpmdb
+ * Return number of instances of package in rpm database.
+ * @param db rpm database
+ * @param name rpm package name
+ * @return number of instances
+ */
+int rpmdbCountPackages(rpmdb db, const char * name);
+
+/** \ingroup rpmdb
+ * Return header join key for current position of rpm database iterator.
+ * @param mi rpm database iterator
+ * @return current header join key
+ */
+unsigned int rpmdbGetIteratorOffset(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Return number of elements in rpm database iterator.
+ * @param mi rpm database iterator
+ * @return number of elements
+ */
+int rpmdbGetIteratorCount(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ */
+unsigned int rpmdbGetIteratorFileNum(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Append items to set of package instances to iterate.
+ * @param mi rpm database iterator
+ * @param hdrNums array of package instances
+ * @param nHdrNums number of elements in array
+ * @return 0 on success, 1 on failure (bad args)
+ */
+int rpmdbAppendIterator(rpmdbMatchIterator mi,
+ const int * hdrNums, int nHdrNums);
+
+/** \ingroup rpmdb
+ * Add pattern to iterator selector.
+ * @param mi rpm database iterator
+ * @param tag rpm tag
+ * @param mode type of pattern match
+ * @param pattern pattern to match
+ * @return 0 on success
+ */
+int rpmdbSetIteratorRE(rpmdbMatchIterator mi, rpmTagVal tag,
+ rpmMireMode mode, const char * pattern);
+
+/** \ingroup rpmdb
+ * Prepare iterator for lazy writes.
+ * @note Must be called before rpmdbNextIterator() with CDB model database.
+ * @param mi rpm database iterator
+ * @param rewrite new value of rewrite
+ * @return previous value
+ */
+int rpmdbSetIteratorRewrite(rpmdbMatchIterator mi, int rewrite);
+
+/** \ingroup rpmdb
+ * Modify iterator to mark header for lazy write on release.
+ * @param mi rpm database iterator
+ * @param modified new value of modified
+ * @return previous value
+ */
+int rpmdbSetIteratorModified(rpmdbMatchIterator mi, int modified);
+
+/** \ingroup rpmdb
+ * Modify iterator to verify retrieved header blobs.
+ * @param mi rpm database iterator
+ * @param ts transaction set
+ * @param (*hdrchk) headerCheck() vector
+ * @return 0 always
+ */
+int rpmdbSetHdrChk(rpmdbMatchIterator mi, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void * uh, size_t uc, char ** msg));
+
+/** \ingroup rpmdb
+ * Return database iterator.
+ * @param db rpm database
+ * @param rpmtag database index tag
+ * @param keyp key data (NULL for sequential access)
+ * @param keylen key data length (0 will use strlen(keyp))
+ * @return NULL on failure
+ */
+rpmdbMatchIterator rpmdbInitIterator(rpmdb db, rpmDbiTagVal rpmtag,
+ const void * keyp, size_t keylen);
+
+/** \ingroup rpmdb
+ * Return next package header from iteration.
+ * @param mi rpm database iterator
+ * @return NULL on end of iteration.
+ */
+Header rpmdbNextIterator(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Check for and exit on termination signals.
+ */
+int rpmdbCheckSignals(void);
+
+/** \ingroup rpmdb
+ * Check rpmdb signal handler for trapped signal and/or requested exit,
+ * clean up any open iterators and databases on termination condition.
+ * On non-zero exit any open references to rpmdb are invalid and cannot
+ * be accessed anymore, calling process should terminate immediately.
+ * @param terminate 0 to only check for signals, 1 to terminate anyway
+ * @return 0 to continue, 1 if termination cleanup was done.
+ */
+int rpmdbCheckTerminate(int terminate);
+
+/** \ingroup rpmdb
+ * Destroy rpm database iterator.
+ * @param mi rpm database iterator
+ * @return NULL always
+ */
+rpmdbMatchIterator rpmdbFreeIterator(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Get an iterator for an index
+ * @param db rpm database
+ * @param rpmtag the index to iterate over
+ * @return the index iterator
+ */
+rpmdbIndexIterator rpmdbIndexIteratorInit(rpmdb db, rpmDbiTag rpmtag);
+
+/** \ingroup rpmdb
+ * Get the next key - Warning! Keys are not zero terminated!
+ * Binary tags may even contain zero bytes
+ * @param ii index iterator
+ * @param key adress to save the pointer to the key
+ * @param keylen adress to save the length of the key to
+ * @return 0 on success; != 0 on error or end of index
+ */
+int rpmdbIndexIteratorNext(rpmdbIndexIterator ii, const void ** key, size_t * keylen);
+
+/** \ingroup rpmdb
+ * Get number of entries for current key
+ * @param ii index iterator
+ * @return number of entries. 0 on error.
+ */
+unsigned int rpmdbIndexIteratorNumPkgs(rpmdbIndexIterator ii);
+
+/** \ingroup rpmdb
+ * Get package offset of entry
+ * @param ii index iterator
+ * @param nr number of the entry
+ * @return db offset of pkg
+ */
+unsigned int rpmdbIndexIteratorPkgOffset(rpmdbIndexIterator ii, unsigned int nr);
+
+/** \ingroup rpmdb
+ * Get tag number of entry
+ * @param ii index iterator
+ * @param nr number of the entry
+ * @return number of tag within the package
+ */
+unsigned int rpmdbIndexIteratorTagNum(rpmdbIndexIterator ii, unsigned int nr);
+
+/** \ingroup rpmdb
+ * Free index iterator
+ * @param ii index iterator
+ * return NULL
+ */
+rpmdbIndexIterator rpmdbIndexIteratorFree(rpmdbIndexIterator ii);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMDB */
diff --git a/lib/rpmdb_internal.h b/lib/rpmdb_internal.h
new file mode 100644
index 0000000..de70282
--- /dev/null
+++ b/lib/rpmdb_internal.h
@@ -0,0 +1,155 @@
+#ifndef H_RPMDB_INTERNAL
+#define H_RPMDB_INTERNAL
+
+#include <assert.h>
+#include <db.h>
+
+#include <rpm/rpmsw.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmutil.h>
+#include "lib/backend/dbi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmdb
+ * Reference a database instance.
+ * @param db rpm database
+ * @return new rpm database reference
+ */
+rpmdb rpmdbLink(rpmdb db);
+
+/** \ingroup rpmdb
+ * Open rpm database.
+ * @param prefix path to top of install tree
+ * @retval dbp address of rpm database
+ * @param mode open(2) flags: O_RDWR or O_RDONLY (O_CREAT also)
+ * @param perms database permissions
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbOpen (const char * prefix, rpmdb * dbp, int mode, int perms);
+
+/** \ingroup rpmdb
+ * Initialize database.
+ * @param prefix path to top of install tree
+ * @param perms database permissions
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbInit(const char * prefix, int perms);
+
+/** \ingroup rpmdb
+ * Close all database indices and free rpmdb.
+ * @param db rpm database
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbClose (rpmdb db);
+
+/** \ingroup rpmdb
+ * Sync all database indices.
+ * @param db rpm database
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbSync (rpmdb db);
+
+/** \ingroup rpmdb
+ * Rebuild database indices from package headers.
+ * @param prefix path to top of install tree
+ * @param ts transaction set (or NULL)
+ * @param (*hdrchk) headerCheck() vector (or NULL)
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbRebuild(const char * prefix, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg));
+
+/** \ingroup rpmdb
+ * Verify database components.
+ * @param prefix path to top of install tree
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbVerify(const char * prefix);
+
+/** \ingroup rpmdb
+ * Add package header to rpm database and indices.
+ * @param db rpm database
+ * @param h header
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbAdd(rpmdb db, Header h);
+
+/** \ingroup rpmdb
+ * Remove package header from rpm database and indices.
+ * @param db rpm database
+ * @param hdrNum package instance number in database
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int rpmdbRemove(rpmdb db, unsigned int hdrNum);
+
+/** \ingroup rpmdb
+ * Return rpmdb home directory (depending on chroot state)
+ * param db rpmdb handle
+ * return db home directory (or NULL on error)
+ */
+RPM_GNUC_INTERNAL
+const char *rpmdbHome(rpmdb db);
+
+/** \ingroup rpmdb
+ * Return database iterator.
+ * @param mi rpm database iterator
+ * @param keyp key data (NULL for sequential access)
+ * @param keylen key data length (0 will use strlen(keyp))
+ * @return 0 on success
+ */
+int rpmdbExtendIterator(rpmdbMatchIterator mi,
+ const void * keyp, size_t keylen);
+
+/** \ingroup rpmdb
+ * sort the iterator by (recnum, filenum)
+ * Return database iterator.
+ * @param mi rpm database iterator
+ */
+void rpmdbSortIterator(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Remove items from set of package instances to iterate.
+ * @note Sorted hdrNums are always passed in rpmlib.
+ * @param mi rpm database iterator
+ * @param hdrNums hash of package instances
+ * @return 0 on success, 1 on failure (bad args)
+ */
+int rpmdbPruneIterator(rpmdbMatchIterator mi, intHash hdrNums);
+
+/** \ingroup rpmdb
+ * Create a new, empty match iterator (for purposes of extending it
+ * through other means)
+ * @param db rpm database
+ * @param dbitag database index tag
+ * @return empty match iterator
+ */
+RPM_GNUC_INTERNAL
+rpmdbMatchIterator rpmdbNewIterator(rpmdb db, rpmDbiTagVal dbitag);
+
+#ifndef __APPLE__
+/**
+ * * Mergesort, same arguments as qsort(2).
+ * */
+RPM_GNUC_INTERNAL
+int mergesort(void *base, size_t nmemb, size_t size,
+ int (*cmp) (const void *, const void *));
+#else
+/* mergesort is defined in stdlib.h on Mac OS X */
+#endif /* __APPLE__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/rpmds.c b/lib/rpmds.c
new file mode 100644
index 0000000..c9c3b5d
--- /dev/null
+++ b/lib/rpmds.c
@@ -0,0 +1,933 @@
+/** \ingroup rpmdep
+ * \file lib/rpmds.c
+ */
+#include "system.h"
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmlib.h> /* rpmvercmp */
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmds.h>
+
+#include "debug.h"
+
+int _rpmds_debug = 0;
+
+int _rpmds_nopromote = 1;
+
+/**
+ * A package dependency set.
+ */
+struct rpmds_s {
+ const char * Type; /*!< Tag name. */
+ char * DNEVR; /*!< Formatted dependency string. */
+ const char ** N; /*!< Name. */
+ const char ** EVR; /*!< Epoch-Version-Release. */
+ rpmsenseFlags * Flags; /*!< Bit(s) identifying context/comparison. */
+ rpm_color_t * Color; /*!< Bit(s) calculated from file color(s). */
+ rpmTagVal tagN; /*!< Header tag. */
+ int32_t Count; /*!< No. of elements */
+ unsigned int instance; /*!< From rpmdb instance? */
+ int i; /*!< Element index. */
+ unsigned l; /*!< Low element (bsearch). */
+ unsigned u; /*!< High element (bsearch). */
+ int nopromote; /*!< Don't promote Epoch: in rpmdsCompare()? */
+ int nrefs; /*!< Reference count. */
+};
+
+static const char ** rpmdsDupArgv(const char ** argv, int argc);
+
+static int dsType(rpmTagVal tag,
+ const char ** Type, rpmTagVal * tagEVR, rpmTagVal * tagF)
+{
+ int rc = 0;
+ const char *t = NULL;
+ rpmTagVal evr = RPMTAG_NOT_FOUND;
+ rpmTagVal f = RPMTAG_NOT_FOUND;
+
+ if (tag == RPMTAG_PROVIDENAME) {
+ t = "Provides";
+ evr = RPMTAG_PROVIDEVERSION;
+ f = RPMTAG_PROVIDEFLAGS;
+ } else if (tag == RPMTAG_REQUIRENAME) {
+ t = "Requires";
+ evr = RPMTAG_REQUIREVERSION;
+ f = RPMTAG_REQUIREFLAGS;
+ } else if (tag == RPMTAG_CONFLICTNAME) {
+ t = "Conflicts";
+ evr = RPMTAG_CONFLICTVERSION;
+ f = RPMTAG_CONFLICTFLAGS;
+ } else if (tag == RPMTAG_OBSOLETENAME) {
+ t = "Obsoletes";
+ evr = RPMTAG_OBSOLETEVERSION;
+ f = RPMTAG_OBSOLETEFLAGS;
+ } else if (tag == RPMTAG_ORDERNAME) {
+ t = "Order";
+ evr = RPMTAG_ORDERVERSION;
+ f = RPMTAG_ORDERFLAGS;
+ } else if (tag == RPMTAG_TRIGGERNAME) {
+ t = "Trigger";
+ evr = RPMTAG_TRIGGERVERSION;
+ f = RPMTAG_TRIGGERFLAGS;
+ } else {
+ rc = 1;
+ }
+ if (Type) *Type = t;
+ if (tagEVR) *tagEVR = evr;
+ if (tagF) *tagF = f;
+ return rc;
+}
+
+static rpmds rpmdsUnlink(rpmds ds)
+{
+ if (ds)
+ ds->nrefs--;
+ return NULL;
+}
+
+rpmds rpmdsLink(rpmds ds)
+{
+ if (ds)
+ ds->nrefs++;
+ return ds;
+}
+
+rpmds rpmdsFree(rpmds ds)
+{
+ rpmTagVal tagEVR, tagF;
+
+ if (ds == NULL)
+ return NULL;
+
+ if (ds->nrefs > 1)
+ return rpmdsUnlink(ds);
+
+ if (dsType(ds->tagN, NULL, &tagEVR, &tagF))
+ return NULL;
+
+ if (ds->Count > 0) {
+ ds->N = _free(ds->N);
+ ds->EVR = _free(ds->EVR);
+ ds->Flags = _free(ds->Flags);
+ }
+
+ ds->DNEVR = _free(ds->DNEVR);
+ ds->Color = _free(ds->Color);
+
+ (void) rpmdsUnlink(ds);
+ memset(ds, 0, sizeof(*ds)); /* XXX trash and burn */
+ ds = _free(ds);
+ return NULL;
+}
+
+rpmds rpmdsNew(Header h, rpmTagVal tagN, int flags)
+{
+ rpmTagVal tagEVR, tagF;
+ rpmds ds = NULL;
+ const char * Type;
+ struct rpmtd_s names;
+ headerGetFlags hgflags = HEADERGET_ALLOC|HEADERGET_ARGV;
+
+ if (dsType(tagN, &Type, &tagEVR, &tagF))
+ goto exit;
+
+ if (headerGet(h, tagN, &names, hgflags) && rpmtdCount(&names) > 0) {
+ struct rpmtd_s evr, flags;
+
+ ds = xcalloc(1, sizeof(*ds));
+ ds->Type = Type;
+ ds->i = -1;
+ ds->DNEVR = NULL;
+ ds->tagN = tagN;
+ ds->N = names.data;
+ ds->Count = rpmtdCount(&names);
+ ds->nopromote = _rpmds_nopromote;
+ ds->instance = headerGetInstance(h);
+
+ headerGet(h, tagEVR, &evr, hgflags);
+ ds->EVR = evr.data;
+ headerGet(h, tagF, &flags, hgflags);
+ ds->Flags = flags.data;
+ /* ensure rpmlib() requires always have RPMSENSE_RPMLIB flag set */
+ if (tagN == RPMTAG_REQUIRENAME && ds->Flags) {
+ for (int i = 0; i < ds->Count; i++) {
+ if (!(ds->Flags[i] & RPMSENSE_RPMLIB) &&
+ rstreqn(ds->N[i], "rpmlib(", sizeof("rpmlib(")-1))
+ ds->Flags[i] |= RPMSENSE_RPMLIB;
+ }
+ }
+
+ ds = rpmdsLink(ds);
+ }
+
+exit:
+ return ds;
+}
+
+char * rpmdsNewDNEVR(const char * dspfx, const rpmds ds)
+{
+ char * tbuf, * t;
+ size_t nb;
+
+ nb = 0;
+ if (dspfx) nb += strlen(dspfx) + 1;
+ if (ds->N[ds->i]) nb += strlen(ds->N[ds->i]);
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (ds->Flags != NULL && (ds->Flags[ds->i] & RPMSENSE_SENSEMASK)) {
+ if (nb) nb++;
+ if (ds->Flags[ds->i] & RPMSENSE_LESS) nb++;
+ if (ds->Flags[ds->i] & RPMSENSE_GREATER) nb++;
+ if (ds->Flags[ds->i] & RPMSENSE_EQUAL) nb++;
+ }
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (ds->EVR != NULL && ds->EVR[ds->i] && *ds->EVR[ds->i]) {
+ if (nb) nb++;
+ nb += strlen(ds->EVR[ds->i]);
+ }
+
+ t = tbuf = xmalloc(nb + 1);
+ if (dspfx) {
+ t = stpcpy(t, dspfx);
+ *t++ = ' ';
+ }
+ if (ds->N[ds->i])
+ t = stpcpy(t, ds->N[ds->i]);
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (ds->Flags != NULL && (ds->Flags[ds->i] & RPMSENSE_SENSEMASK)) {
+ if (t != tbuf) *t++ = ' ';
+ if (ds->Flags[ds->i] & RPMSENSE_LESS) *t++ = '<';
+ if (ds->Flags[ds->i] & RPMSENSE_GREATER) *t++ = '>';
+ if (ds->Flags[ds->i] & RPMSENSE_EQUAL) *t++ = '=';
+ }
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (ds->EVR != NULL && ds->EVR[ds->i] && *ds->EVR[ds->i]) {
+ if (t != tbuf) *t++ = ' ';
+ t = stpcpy(t, ds->EVR[ds->i]);
+ }
+ *t = '\0';
+ return tbuf;
+}
+
+static rpmds singleDS(rpmTagVal tagN, const char * N, const char * EVR,
+ rpmsenseFlags Flags, unsigned int instance)
+{
+ rpmds ds = NULL;
+ const char * Type;
+
+ if (dsType(tagN, &Type, NULL, NULL))
+ goto exit;
+
+ ds = xcalloc(1, sizeof(*ds));
+ ds->Type = Type;
+ ds->tagN = tagN;
+ ds->Count = 1;
+ ds->nopromote = _rpmds_nopromote;
+ ds->instance = instance;
+
+ ds->N = rpmdsDupArgv(&N, 1);
+ ds->EVR = rpmdsDupArgv(&EVR, 1);
+
+ ds->Flags = xmalloc(sizeof(*ds->Flags));
+ ds->Flags[0] = Flags;
+ ds->i = 0;
+
+exit:
+ return rpmdsLink(ds);
+}
+
+rpmds rpmdsThis(Header h, rpmTagVal tagN, rpmsenseFlags Flags)
+{
+ char *evr = headerGetAsString(h, RPMTAG_EVR);
+ rpmds ds = singleDS(tagN, headerGetString(h, RPMTAG_NAME),
+ evr, Flags, headerGetInstance(h));
+ free(evr);
+ return ds;
+}
+
+rpmds rpmdsSingle(rpmTagVal tagN, const char * N, const char * EVR, rpmsenseFlags Flags)
+{
+ return singleDS(tagN, N, EVR, Flags, 0);
+}
+
+rpmds rpmdsCurrent(rpmds ds)
+{
+ rpmds cds = NULL;
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ cds = singleDS(ds->tagN, ds->N[ds->i], ds->EVR[ds->i],
+ ds->Flags[ds->i], ds->instance);
+ }
+ return cds;
+}
+
+int rpmdsCount(const rpmds ds)
+{
+ return (ds != NULL ? ds->Count : 0);
+}
+
+int rpmdsIx(const rpmds ds)
+{
+ return (ds != NULL ? ds->i : -1);
+}
+
+int rpmdsSetIx(rpmds ds, int ix)
+{
+ int i = -1;
+
+ if (ds != NULL) {
+ i = ds->i;
+ ds->i = ix;
+ ds->DNEVR = _free(ds->DNEVR);
+ }
+ return i;
+}
+
+const char * rpmdsDNEVR(const rpmds ds)
+{
+ const char * DNEVR = NULL;
+
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ if (ds->DNEVR == NULL) {
+ char t[2] = { ds->Type[0], '\0' };
+ ds->DNEVR = rpmdsNewDNEVR(t, ds);
+ }
+ DNEVR = ds->DNEVR;
+ }
+ return DNEVR;
+}
+
+const char * rpmdsN(const rpmds ds)
+{
+ const char * N = NULL;
+
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ if (ds->N != NULL)
+ N = ds->N[ds->i];
+ }
+ return N;
+}
+
+const char * rpmdsEVR(const rpmds ds)
+{
+ const char * EVR = NULL;
+
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ if (ds->EVR != NULL)
+ EVR = ds->EVR[ds->i];
+ }
+ return EVR;
+}
+
+rpmsenseFlags rpmdsFlags(const rpmds ds)
+{
+ rpmsenseFlags Flags = 0;
+
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ if (ds->Flags != NULL)
+ Flags = ds->Flags[ds->i];
+ }
+ return Flags;
+}
+
+rpmTagVal rpmdsTagN(const rpmds ds)
+{
+ rpmTagVal tagN = RPMTAG_NOT_FOUND;
+
+ if (ds != NULL)
+ tagN = ds->tagN;
+ return tagN;
+}
+
+unsigned int rpmdsInstance(rpmds ds)
+{
+ return (ds != NULL) ? ds->instance : 0;
+}
+
+int rpmdsNoPromote(const rpmds ds)
+{
+ int nopromote = 0;
+
+ if (ds != NULL)
+ nopromote = ds->nopromote;
+ return nopromote;
+}
+
+int rpmdsSetNoPromote(rpmds ds, int nopromote)
+{
+ int onopromote = 0;
+
+ if (ds != NULL) {
+ onopromote = ds->nopromote;
+ ds->nopromote = nopromote;
+ }
+ return onopromote;
+}
+
+rpm_color_t rpmdsColor(const rpmds ds)
+{
+ rpm_color_t Color = 0;
+
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ if (ds->Color != NULL)
+ Color = ds->Color[ds->i];
+ }
+ return Color;
+}
+
+rpm_color_t rpmdsSetColor(const rpmds ds, rpm_color_t color)
+{
+ rpm_color_t ocolor = 0;
+
+ if (ds != NULL && ds->i >= 0 && ds->i < ds->Count) {
+ if (ds->Color == NULL) {
+ ds->Color = xcalloc(ds->Count, sizeof(*ds->Color));
+ }
+ ocolor = ds->Color[ds->i];
+ ds->Color[ds->i] = color;
+ }
+ return ocolor;
+}
+
+void rpmdsNotify(rpmds ds, const char * where, int rc)
+{
+ const char *DNEVR;
+
+ if (!rpmIsDebug())
+ return;
+ if (!(ds != NULL && ds->i >= 0 && ds->i < ds->Count))
+ return;
+ if (!(ds->Type != NULL && (DNEVR = rpmdsDNEVR(ds)) != NULL))
+ return;
+
+ rpmlog(RPMLOG_DEBUG, "%9s: %-45s %-s %s\n", ds->Type,
+ (rstreq(DNEVR, "cached") ? DNEVR : DNEVR+2),
+ (rc ? _("NO ") : _("YES")),
+ (where != NULL ? where : ""));
+}
+
+int rpmdsNext(rpmds ds)
+{
+ int i = -1;
+
+ if (ds != NULL && ++ds->i >= 0) {
+ if (ds->i < ds->Count) {
+ i = ds->i;
+ ds->DNEVR = _free(ds->DNEVR);
+ } else
+ ds->i = -1;
+
+if (_rpmds_debug < 0 && i != -1)
+fprintf(stderr, "*** ds %p\t%s[%d]: %s\n", ds, (ds->Type ? ds->Type : "?Type?"), i, (ds->DNEVR ? ds->DNEVR : "?DNEVR?"));
+
+ }
+
+ return i;
+}
+
+rpmds rpmdsInit(rpmds ds)
+{
+ if (ds != NULL) {
+ ds->i = -1;
+ ds->DNEVR = _free(ds->DNEVR);
+ }
+ return ds;
+}
+
+static
+const char ** rpmdsDupArgv(const char ** argv, int argc)
+{
+ const char ** av;
+ size_t nb = 0;
+ int ac = 0;
+ char * t;
+
+ if (argv == NULL)
+ return NULL;
+ for (ac = 0; ac < argc && argv[ac]; ac++) {
+ nb += strlen(argv[ac]) + 1;
+ }
+ nb += (ac + 1) * sizeof(*av);
+
+ av = xmalloc(nb);
+ t = (char *) (av + ac + 1);
+ for (ac = 0; ac < argc && argv[ac]; ac++) {
+ av[ac] = t;
+ t = stpcpy(t, argv[ac]) + 1;
+ }
+ av[ac] = NULL;
+ return av;
+}
+
+static rpmds rpmdsDup(const rpmds ods)
+{
+ rpmds ds = xcalloc(1, sizeof(*ds));
+ size_t nb;
+
+ ds->Type = ods->Type;
+ ds->tagN = ods->tagN;
+ ds->Count = ods->Count;
+ ds->i = ods->i;
+ ds->l = ods->l;
+ ds->u = ods->u;
+ ds->nopromote = ods->nopromote;
+
+ ds->N = rpmdsDupArgv(ods->N, ods->Count);
+
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+assert(ods->EVR != NULL);
+assert(ods->Flags != NULL);
+
+ ds->EVR = rpmdsDupArgv(ods->EVR, ods->Count);
+
+ nb = (ds->Count * sizeof(*ds->Flags));
+ ds->Flags = memcpy(xmalloc(nb), ods->Flags, nb);
+
+ return rpmdsLink(ds);
+
+}
+
+int rpmdsFind(rpmds ds, const rpmds ods)
+{
+ int comparison;
+
+ if (ds == NULL || ods == NULL)
+ return -1;
+
+ ds->l = 0;
+ ds->u = ds->Count;
+ while (ds->l < ds->u) {
+ ds->i = (ds->l + ds->u) / 2;
+
+ comparison = strcmp(ods->N[ods->i], ds->N[ds->i]);
+
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (comparison == 0 && ods->EVR && ds->EVR)
+ comparison = strcmp(ods->EVR[ods->i], ds->EVR[ds->i]);
+ if (comparison == 0 && ods->Flags && ds->Flags)
+ comparison = (ods->Flags[ods->i] - ds->Flags[ds->i]);
+
+ if (comparison < 0)
+ ds->u = ds->i;
+ else if (comparison > 0)
+ ds->l = ds->i + 1;
+ else
+ return ds->i;
+ }
+ return -1;
+}
+
+int rpmdsMerge(rpmds * dsp, rpmds ods)
+{
+ rpmds ds;
+ const char ** N;
+ const char ** EVR;
+ rpmsenseFlags * Flags;
+ int j;
+ int save;
+
+ if (dsp == NULL || ods == NULL)
+ return -1;
+
+ /* If not initialized yet, dup the 1st entry. */
+ if (*dsp == NULL) {
+ save = ods->Count;
+ ods->Count = 1;
+ *dsp = rpmdsDup(ods);
+ ods->Count = save;
+ }
+ ds = *dsp;
+ if (ds == NULL)
+ return -1;
+
+ /*
+ * Add new entries.
+ */
+ save = ods->i;
+ ods = rpmdsInit(ods);
+ while (rpmdsNext(ods) >= 0) {
+ /*
+ * If this entry is already present, don't bother.
+ */
+ if (rpmdsFind(ds, ods) >= 0)
+ continue;
+
+ /*
+ * Insert new entry.
+ */
+ for (j = ds->Count; j > ds->u; j--)
+ ds->N[j] = ds->N[j-1];
+ ds->N[ds->u] = ods->N[ods->i];
+ N = rpmdsDupArgv(ds->N, ds->Count+1);
+ ds->N = _free(ds->N);
+ ds->N = N;
+
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+assert(ods->EVR != NULL);
+assert(ods->Flags != NULL);
+
+ for (j = ds->Count; j > ds->u; j--)
+ ds->EVR[j] = ds->EVR[j-1];
+ ds->EVR[ds->u] = ods->EVR[ods->i];
+ EVR = rpmdsDupArgv(ds->EVR, ds->Count+1);
+ ds->EVR = _free(ds->EVR);
+ ds->EVR = EVR;
+
+ Flags = xmalloc((ds->Count+1) * sizeof(*Flags));
+ if (ds->u > 0)
+ memcpy(Flags, ds->Flags, ds->u * sizeof(*Flags));
+ if (ds->u < ds->Count)
+ memcpy(Flags + ds->u + 1, ds->Flags + ds->u,
+ (ds->Count - ds->u) * sizeof(*Flags));
+ Flags[ds->u] = ods->Flags[ods->i];
+ ds->Flags = _free(ds->Flags);
+ ds->Flags = Flags;
+
+ ds->i = ds->Count;
+ ds->Count++;
+
+ }
+ ods->i = save;
+ return 0;
+}
+
+
+int rpmdsSearch(rpmds ds, rpmds ods)
+{
+ int comparison;
+ int i, l, u;
+
+ if (ds == NULL || ods == NULL)
+ return -1;
+
+ /* Binary search to find the [l,u) subset that contains N */
+ i = -1;
+ l = 0;
+ u = ds->Count;
+ while (l < u) {
+ i = (l + u) / 2;
+
+ comparison = strcmp(ods->N[ods->i], ds->N[i]);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else {
+ /* Set l to 1st member of set that contains N. */
+ if (!rstreq(ods->N[ods->i], ds->N[l]))
+ l = i;
+ while (l > 0 && rstreq(ods->N[ods->i], ds->N[l-1]))
+ l--;
+ /* Set u to 1st member of set that does not contain N. */
+ if (u >= ds->Count || !rstreq(ods->N[ods->i], ds->N[u]))
+ u = i;
+ while (++u < ds->Count) {
+ if (!rstreq(ods->N[ods->i], ds->N[u]))
+ /*@innerbreak@*/ break;
+ }
+ break;
+ }
+ }
+
+ /* Check each member of [l,u) subset for ranges overlap. */
+ i = -1;
+ if (l < u) {
+ int save = rpmdsSetIx(ds, l-1);
+ while ((l = rpmdsNext(ds)) >= 0 && (l < u)) {
+ if ((i = rpmdsCompare(ods, ds)) != 0)
+ break;
+ }
+ /* Return element index that overlaps, or -1. */
+ if (i)
+ i = rpmdsIx(ds);
+ else {
+ (void) rpmdsSetIx(ds, save);
+ i = -1;
+ }
+ }
+ return i;
+}
+/**
+ * Split EVR into epoch, version, and release components.
+ * @param evr [epoch:]version[-release] string
+ * @retval *ep pointer to epoch
+ * @retval *vp pointer to version
+ * @retval *rp pointer to release
+ */
+static
+void parseEVR(char * evr,
+ const char ** ep,
+ const char ** vp,
+ const char ** rp)
+{
+ const char *epoch;
+ const char *version; /* assume only version is present */
+ const char *release;
+ char *s, *se;
+
+ s = evr;
+ while (*s && risdigit(*s)) s++; /* s points to epoch terminator */
+ se = strrchr(s, '-'); /* se points to version terminator */
+
+ if (*s == ':') {
+ epoch = evr;
+ *s++ = '\0';
+ version = s;
+ if (*epoch == '\0') epoch = "0";
+ } else {
+ epoch = NULL; /* XXX disable epoch compare if missing */
+ version = evr;
+ }
+ if (se) {
+ *se++ = '\0';
+ release = se;
+ } else {
+ release = NULL;
+ }
+
+ if (ep) *ep = epoch;
+ if (vp) *vp = version;
+ if (rp) *rp = release;
+}
+
+int rpmdsCompare(const rpmds A, const rpmds B)
+{
+ char *aEVR, *bEVR;
+ const char *aE, *aV, *aR, *bE, *bV, *bR;
+ int result;
+ int sense;
+
+ /* Different names don't overlap. */
+ if (!rstreq(A->N[A->i], B->N[B->i])) {
+ result = 0;
+ goto exit;
+ }
+
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (!(A->EVR && A->Flags && B->EVR && B->Flags)) {
+ result = 1;
+ goto exit;
+ }
+
+ /* Same name. If either A or B is an existence test, always overlap. */
+ if (!((A->Flags[A->i] & RPMSENSE_SENSEMASK) && (B->Flags[B->i] & RPMSENSE_SENSEMASK))) {
+ result = 1;
+ goto exit;
+ }
+
+ /* If either EVR is non-existent or empty, always overlap. */
+ if (!(A->EVR[A->i] && *A->EVR[A->i] && B->EVR[B->i] && *B->EVR[B->i])) {
+ result = 1;
+ goto exit;
+ }
+
+ /* Both AEVR and BEVR exist. */
+ aEVR = xstrdup(A->EVR[A->i]);
+ parseEVR(aEVR, &aE, &aV, &aR);
+ bEVR = xstrdup(B->EVR[B->i]);
+ parseEVR(bEVR, &bE, &bV, &bR);
+
+ /* Compare {A,B} [epoch:]version[-release] */
+ sense = 0;
+ if (aE && *aE && bE && *bE)
+ sense = rpmvercmp(aE, bE);
+ else if (aE && *aE && atol(aE) > 0) {
+ if (!B->nopromote) {
+ sense = 0;
+ } else
+ sense = 1;
+ } else if (bE && *bE && atol(bE) > 0)
+ sense = -1;
+
+ if (sense == 0) {
+ sense = rpmvercmp(aV, bV);
+ if (sense == 0) {
+ if (aR && *aR && bR && *bR) {
+ sense = rpmvercmp(aR, bR);
+ } else {
+ /* always matches if the side with no release has SENSE_EQUAL */
+ if ((aR && *aR && (B->Flags[B->i] & RPMSENSE_EQUAL)) ||
+ (bR && *bR && (A->Flags[A->i] & RPMSENSE_EQUAL))) {
+ aEVR = _free(aEVR);
+ bEVR = _free(bEVR);
+ result = 1;
+ goto exit;
+ }
+ }
+ }
+ }
+ aEVR = _free(aEVR);
+ bEVR = _free(bEVR);
+
+ /* Detect overlap of {A,B} range. */
+ result = 0;
+ if (sense < 0 && ((A->Flags[A->i] & RPMSENSE_GREATER) || (B->Flags[B->i] & RPMSENSE_LESS))) {
+ result = 1;
+ } else if (sense > 0 && ((A->Flags[A->i] & RPMSENSE_LESS) || (B->Flags[B->i] & RPMSENSE_GREATER))) {
+ result = 1;
+ } else if (sense == 0 &&
+ (((A->Flags[A->i] & RPMSENSE_EQUAL) && (B->Flags[B->i] & RPMSENSE_EQUAL)) ||
+ ((A->Flags[A->i] & RPMSENSE_LESS) && (B->Flags[B->i] & RPMSENSE_LESS)) ||
+ ((A->Flags[A->i] & RPMSENSE_GREATER) && (B->Flags[B->i] & RPMSENSE_GREATER)))) {
+ result = 1;
+ }
+
+exit:
+ return result;
+}
+
+int rpmdsAnyMatchesDep (const Header h, const rpmds req, int nopromote)
+{
+ rpmds provides = NULL;
+ int result = 0;
+
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (req->EVR == NULL || req->Flags == NULL)
+ return 1;
+
+ if (!(req->Flags[req->i] & RPMSENSE_SENSEMASK) || !req->EVR[req->i] || *req->EVR[req->i] == '\0')
+ return 1;
+
+ /* Get provides information from header */
+ provides = rpmdsInit(rpmdsNew(h, RPMTAG_PROVIDENAME, 0));
+ if (provides == NULL)
+ goto exit; /* XXX should never happen */
+ if (nopromote)
+ (void) rpmdsSetNoPromote(provides, nopromote);
+
+ /*
+ * Rpm prior to 3.0.3 did not have versioned provides.
+ * If no provides version info is available, match any/all requires
+ * with same name.
+ */
+ if (provides->EVR == NULL) {
+ result = 1;
+ goto exit;
+ }
+
+ result = 0;
+ while (rpmdsNext(provides) >= 0) {
+
+ /* Filter out provides that came along for the ride. */
+ if (!rstreq(provides->N[provides->i], req->N[req->i]))
+ continue;
+
+ result = rpmdsCompare(provides, req);
+
+ /* If this provide matches the require, we're done. */
+ if (result)
+ break;
+ }
+
+exit:
+ provides = rpmdsFree(provides);
+
+ return result;
+}
+
+int rpmdsNVRMatchesDep(const Header h, const rpmds req, int nopromote)
+{
+ rpmds pkg;
+ int rc = 1; /* XXX assume match, names already match here */
+
+ /* XXX rpm prior to 3.0.2 did not always supply EVR and Flags. */
+ if (req->EVR == NULL || req->Flags == NULL)
+ return rc;
+
+ if (!((req->Flags[req->i] & RPMSENSE_SENSEMASK) && req->EVR[req->i] && *req->EVR[req->i]))
+ return rc;
+
+ /* Get package information from header */
+ pkg = rpmdsThis(h, RPMTAG_PROVIDENAME, RPMSENSE_EQUAL);
+ if (nopromote)
+ rpmdsSetNoPromote(pkg, nopromote);
+ rc = rpmdsCompare(pkg, req);
+ rpmdsFree(pkg);
+
+ return rc;
+}
+
+/**
+ */
+struct rpmlibProvides_s {
+ const char * featureName;
+ const char * featureEVR;
+ rpmsenseFlags featureFlags;
+ const char * featureDescription;
+};
+
+static const struct rpmlibProvides_s rpmlibProvides[] = {
+ { "rpmlib(VersionedDependencies)", "3.0.3-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("PreReq:, Provides:, and Obsoletes: dependencies support versions.") },
+ { "rpmlib(CompressedFileNames)", "3.0.4-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("file name(s) stored as (dirName,baseName,dirIndex) tuple, not as path.")},
+#if HAVE_BZLIB_H
+ { "rpmlib(PayloadIsBzip2)", "3.0.5-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("package payload can be compressed using bzip2.") },
+#endif
+#if HAVE_LZMA_H
+ { "rpmlib(PayloadIsXz)", "5.2-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("package payload can be compressed using xz.") },
+ { "rpmlib(PayloadIsLzma)", "4.4.2-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("package payload can be compressed using lzma.") },
+#endif
+ { "rpmlib(PayloadFilesHavePrefix)", "4.0-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("package payload file(s) have \"./\" prefix.") },
+ { "rpmlib(ExplicitPackageProvide)", "4.0-1",
+ (RPMSENSE_RPMLIB|RPMSENSE_EQUAL),
+ N_("package name-version-release is not implicitly provided.") },
+ { "rpmlib(HeaderLoadSortsTags)", "4.0.1-1",
+ ( RPMSENSE_EQUAL),
+ N_("header tags are always sorted after being loaded.") },
+ { "rpmlib(ScriptletInterpreterArgs)", "4.0.3-1",
+ ( RPMSENSE_EQUAL),
+ N_("the scriptlet interpreter can use arguments from header.") },
+ { "rpmlib(PartialHardlinkSets)", "4.0.4-1",
+ ( RPMSENSE_EQUAL),
+ N_("a hardlink file set may be installed without being complete.") },
+ { "rpmlib(ConcurrentAccess)", "4.1-1",
+ ( RPMSENSE_EQUAL),
+ N_("package scriptlets may access the rpm database while installing.") },
+#ifdef WITH_LUA
+ { "rpmlib(BuiltinLuaScripts)", "4.2.2-1",
+ ( RPMSENSE_EQUAL),
+ N_("internal support for lua scripts.") },
+#endif
+ { "rpmlib(FileDigests)", "4.6.0-1",
+ ( RPMSENSE_EQUAL),
+ N_("file digest algorithm is per package configurable") },
+#ifdef WITH_CAP
+ { "rpmlib(FileCaps)", "4.6.1-1",
+ ( RPMSENSE_EQUAL),
+ N_("support for POSIX.1e file capabilities") },
+#endif
+ { "rpmlib(ScriptletExpansion)", "4.9.0-1",
+ ( RPMSENSE_EQUAL),
+ N_("package scriptlets can be expanded at install time.") },
+ { NULL, NULL, 0, NULL }
+};
+
+
+int rpmdsRpmlib(rpmds * dsp, const void * tblp)
+{
+ const struct rpmlibProvides_s * rltblp = tblp;
+ const struct rpmlibProvides_s * rlp;
+ int xx;
+
+ if (rltblp == NULL)
+ rltblp = rpmlibProvides;
+
+ for (rlp = rltblp; rlp->featureName != NULL; rlp++) {
+ rpmds ds = rpmdsSingle(RPMTAG_PROVIDENAME, rlp->featureName,
+ rlp->featureEVR, rlp->featureFlags);
+ xx = rpmdsMerge(dsp, ds);
+ ds = rpmdsFree(ds);
+ }
+ return 0;
+}
+
diff --git a/lib/rpmds.h b/lib/rpmds.h
new file mode 100644
index 0000000..bf3ee2c
--- /dev/null
+++ b/lib/rpmds.h
@@ -0,0 +1,333 @@
+#ifndef H_RPMDS
+#define H_RPMDS
+
+/** \ingroup rpmdep rpmtrans
+ * \file lib/rpmds.h
+ * Structure(s) used for dependency tag sets.
+ */
+
+#include <time.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmps.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ */
+extern int _rpmds_nopromote;
+
+/** \ingroup rpmds
+ * Dependency Attributes.
+ */
+enum rpmsenseFlags_e {
+ RPMSENSE_ANY = 0,
+ RPMSENSE_LESS = (1 << 1),
+ RPMSENSE_GREATER = (1 << 2),
+ RPMSENSE_EQUAL = (1 << 3),
+ /* bit 4 unused */
+ RPMSENSE_POSTTRANS = (1 << 5), /*!< %posttrans dependency */
+ RPMSENSE_PREREQ = (1 << 6), /* legacy prereq dependency */
+ RPMSENSE_PRETRANS = (1 << 7), /*!< Pre-transaction dependency. */
+ RPMSENSE_INTERP = (1 << 8), /*!< Interpreter used by scriptlet. */
+ RPMSENSE_SCRIPT_PRE = (1 << 9), /*!< %pre dependency. */
+ RPMSENSE_SCRIPT_POST = (1 << 10), /*!< %post dependency. */
+ RPMSENSE_SCRIPT_PREUN = (1 << 11), /*!< %preun dependency. */
+ RPMSENSE_SCRIPT_POSTUN = (1 << 12), /*!< %postun dependency. */
+ RPMSENSE_SCRIPT_VERIFY = (1 << 13), /*!< %verify dependency. */
+ RPMSENSE_FIND_REQUIRES = (1 << 14), /*!< find-requires generated dependency. */
+ RPMSENSE_FIND_PROVIDES = (1 << 15), /*!< find-provides generated dependency. */
+
+ RPMSENSE_TRIGGERIN = (1 << 16), /*!< %triggerin dependency. */
+ RPMSENSE_TRIGGERUN = (1 << 17), /*!< %triggerun dependency. */
+ RPMSENSE_TRIGGERPOSTUN = (1 << 18), /*!< %triggerpostun dependency. */
+ RPMSENSE_MISSINGOK = (1 << 19), /*!< suggests/enhances hint. */
+ /* bits 20-23 unused */
+ RPMSENSE_RPMLIB = (1 << 24), /*!< rpmlib(feature) dependency. */
+ RPMSENSE_TRIGGERPREIN = (1 << 25), /*!< %triggerprein dependency. */
+ RPMSENSE_KEYRING = (1 << 26),
+ /* bit 27 unused */
+ RPMSENSE_CONFIG = (1 << 28)
+};
+
+typedef rpmFlags rpmsenseFlags;
+
+#define RPMSENSE_SENSEMASK 15 /* Mask to get senses, ie serial, */
+ /* less, greater, equal. */
+
+#define RPMSENSE_TRIGGER \
+ (RPMSENSE_TRIGGERPREIN | RPMSENSE_TRIGGERIN | RPMSENSE_TRIGGERUN | RPMSENSE_TRIGGERPOSTUN)
+
+#define _ALL_REQUIRES_MASK (\
+ RPMSENSE_INTERP | \
+ RPMSENSE_SCRIPT_PRE | \
+ RPMSENSE_SCRIPT_POST | \
+ RPMSENSE_SCRIPT_PREUN | \
+ RPMSENSE_SCRIPT_POSTUN | \
+ RPMSENSE_SCRIPT_VERIFY | \
+ RPMSENSE_FIND_REQUIRES | \
+ RPMSENSE_RPMLIB | \
+ RPMSENSE_KEYRING | \
+ RPMSENSE_PRETRANS | \
+ RPMSENSE_POSTTRANS | \
+ RPMSENSE_PREREQ)
+
+#define _notpre(_x) ((_x) & ~RPMSENSE_PREREQ)
+#define _INSTALL_ONLY_MASK \
+ _notpre(RPMSENSE_SCRIPT_PRE|RPMSENSE_SCRIPT_POST|RPMSENSE_RPMLIB|RPMSENSE_KEYRING|RPMSENSE_PRETRANS|RPMSENSE_POSTTRANS)
+#define _ERASE_ONLY_MASK \
+ _notpre(RPMSENSE_SCRIPT_PREUN|RPMSENSE_SCRIPT_POSTUN)
+
+#define isLegacyPreReq(_x) (((_x) & _ALL_REQUIRES_MASK) == RPMSENSE_PREREQ)
+#define isInstallPreReq(_x) ((_x) & _INSTALL_ONLY_MASK)
+#define isErasePreReq(_x) ((_x) & _ERASE_ONLY_MASK)
+
+/** \ingroup rpmds
+ * Reference a dependency set instance.
+ * @param ds dependency set
+ * @return new dependency set reference
+ */
+rpmds rpmdsLink(rpmds ds);
+
+/** \ingroup rpmds
+ * Destroy a dependency set.
+ * @param ds dependency set
+ * @return NULL always
+ */
+rpmds rpmdsFree(rpmds ds);
+/** \ingroup rpmds
+ * Create and load a dependency set.
+ * @param h header
+ * @param tagN type of dependency
+ * @param flags unused
+ * @return new dependency set
+ */
+rpmds rpmdsNew(Header h, rpmTagVal tagN, int flags);
+
+/** \ingroup rpmds
+ * Return new formatted dependency string.
+ * @param dspfx formatted dependency string prefix
+ * @param ds dependency set
+ * @return new formatted dependency (malloc'ed)
+ */
+char * rpmdsNewDNEVR(const char * dspfx, const rpmds ds);
+
+/** \ingroup rpmds
+ * Create, load and initialize a dependency for this header.
+ * @param h header
+ * @param tagN type of dependency
+ * @param Flags comparison flags
+ * @return new dependency set
+ */
+rpmds rpmdsThis(Header h, rpmTagVal tagN, rpmsenseFlags Flags);
+
+/** \ingroup rpmds
+ * Create, load and initialize a dependency set of size 1.
+ * @param tagN type of dependency
+ * @param N name
+ * @param EVR epoch:version-release
+ * @param Flags comparison flags
+ * @return new dependency set
+ */
+rpmds rpmdsSingle(rpmTagVal tagN, const char * N, const char * EVR, rpmsenseFlags Flags);
+
+/** \ingroup rpmds
+ * Return a new dependency set of size 1 from the current iteration index
+ * @param ds dependency set
+ * @return new dependency set
+ */
+rpmds rpmdsCurrent(rpmds ds);
+
+/** \ingroup rpmds
+ * Return dependency set count.
+ * @param ds dependency set
+ * @return current count
+ */
+int rpmdsCount(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return dependency set index.
+ * @param ds dependency set
+ * @return current index
+ */
+int rpmdsIx(const rpmds ds);
+
+/** \ingroup rpmds
+ * Set dependency set index.
+ * @param ds dependency set
+ * @param ix new index
+ * @return current index
+ */
+int rpmdsSetIx(rpmds ds, int ix);
+
+/** \ingroup rpmds
+ * Return current formatted dependency string.
+ * @param ds dependency set
+ * @return current dependency DNEVR, NULL on invalid
+ */
+const char * rpmdsDNEVR(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return current dependency name.
+ * @param ds dependency set
+ * @return current dependency name, NULL on invalid
+ */
+const char * rpmdsN(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return current dependency epoch-version-release.
+ * @param ds dependency set
+ * @return current dependency EVR, NULL on invalid
+ */
+const char * rpmdsEVR(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return current dependency flags.
+ * @param ds dependency set
+ * @return current dependency flags, 0 on invalid
+ */
+rpmsenseFlags rpmdsFlags(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return current dependency type.
+ * @param ds dependency set
+ * @return current dependency type, 0 on invalid
+ */
+rpmTagVal rpmdsTagN(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return dependency header instance, ie whether the dependency comes from
+ * an installed header or not.
+ * @param ds dependency set
+ * @return header instance of dependency (0 for not installed)
+ */
+unsigned int rpmdsInstance(rpmds ds);
+
+/** \ingroup rpmds
+ * Return current "Don't promote Epoch:" flag.
+ *
+ * This flag controls for Epoch: promotion when a dependency set is
+ * compared. If the flag is set (for already installed packages), then
+ * an unspecified value will be treated as Epoch: 0. Otherwise (for added
+ * packages), the Epoch: portion of the comparison is skipped if the value
+ * is not specified, i.e. an unspecified Epoch: is assumed to be equal
+ * in dependency comparisons.
+ *
+ * @param ds dependency set
+ * @return current "Don't promote Epoch:" flag
+ */
+int rpmdsNoPromote(const rpmds ds);
+
+/** \ingroup rpmds
+ * Set "Don't promote Epoch:" flag.
+ * @param ds dependency set
+ * @param nopromote Should an unspecified Epoch: be treated as Epoch: 0?
+ * @return previous "Don't promote Epoch:" flag
+ */
+int rpmdsSetNoPromote(rpmds ds, int nopromote);
+
+/** \ingroup rpmds
+ * Return current dependency color.
+ * @param ds dependency set
+ * @return current dependency color
+ */
+rpm_color_t rpmdsColor(const rpmds ds);
+
+/** \ingroup rpmds
+ * Return current dependency color.
+ * @param ds dependency set
+ * @param color new dependency color
+ * @return previous dependency color
+ */
+rpm_color_t rpmdsSetColor(const rpmds ds, rpm_color_t color);
+
+/** \ingroup rpmds
+ * Notify of results of dependency match.
+ * @param ds dependency set
+ * @param where where dependency was resolved (or NULL)
+ * @param rc 0 == YES, otherwise NO
+ */
+/* FIX: rpmMessage annotation is a lie */
+void rpmdsNotify(rpmds ds, const char * where, int rc);
+
+/** \ingroup rpmds
+ * Return next dependency set iterator index.
+ * @param ds dependency set
+ * @return dependency set iterator index, -1 on termination
+ */
+int rpmdsNext(rpmds ds);
+
+/** \ingroup rpmds
+ * Initialize dependency set iterator.
+ * @param ds dependency set
+ * @return dependency set
+ */
+rpmds rpmdsInit(rpmds ds);
+
+/** \ingroup rpmds
+ * Find a dependency set element using binary search.
+ * @param ds dependency set to search
+ * @param ods dependency set element to find.
+ * @return dependency index (or -1 if not found)
+ */
+int rpmdsFind(rpmds ds, const rpmds ods);
+
+/** \ingroup rpmds
+ * Merge a dependency set maintaining (N,EVR,Flags) sorted order.
+ * @retval *dsp (merged) dependency set
+ * @param ods dependency set to merge
+ * @return (merged) dependency index
+ */
+int rpmdsMerge(rpmds * dsp, rpmds ods);
+
+/** \ingroup rpmds
+ * Search a sorted dependency set for an element that overlaps.
+ * A boolean result is saved (if allocated) and accessible through
+ * rpmdsResult(ods) afterwards.
+ * @param ds dependency set to search
+ * @param ods dependency set element to find.
+ * @return dependency index (or -1 if not found)
+ **/
+int rpmdsSearch(rpmds ds, rpmds ods);
+
+/** \ingroup rpmds
+ * Compare two versioned dependency ranges, looking for overlap.
+ * @param A 1st dependency
+ * @param B 2nd dependency
+ * @return 1 if dependencies overlap, 0 otherwise
+ */
+int rpmdsCompare(const rpmds A, const rpmds B);
+
+/** \ingroup rpmds
+ * Compare package provides dependencies from header with a single dependency.
+ * @param h header
+ * @param req dependency set
+ * @param nopromote Don't promote Epoch: in comparison?
+ * @return 1 if any dependency overlaps, 0 otherwise
+ */
+int rpmdsAnyMatchesDep (const Header h, const rpmds req, int nopromote);
+
+/** \ingroup rpmds
+ * Compare package name-version-release from header with a single dependency.
+ * @param h header
+ * @param req dependency set
+ * @param nopromote Don't promote Epoch: in comparison?
+ * @return 1 if dependency overlaps, 0 otherwise
+ */
+int rpmdsNVRMatchesDep(const Header h, const rpmds req, int nopromote);
+
+/**
+ * Load rpmlib provides into a dependency set.
+ * @retval *dsp (loaded) depedency set
+ * @param tblp rpmlib provides table (NULL uses internal table)
+ * @return 0 on success
+ */
+int rpmdsRpmlib(rpmds * dsp, const void * tblp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMDS */
diff --git a/lib/rpmfi.c b/lib/rpmfi.c
new file mode 100644
index 0000000..f8be4c9
--- /dev/null
+++ b/lib/rpmfi.c
@@ -0,0 +1,1349 @@
+/** \ingroup rpmdep
+ * \file lib/rpmfi.c
+ * Routines to handle file info tag sets.
+ */
+
+#include "system.h"
+
+#include <rpm/rpmlog.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmfileutil.h> /* XXX rpmDoDigest */
+#include <rpm/rpmstring.h>
+#include <rpm/rpmmacro.h> /* XXX rpmCleanPath */
+#include <rpm/rpmds.h>
+
+#include "lib/rpmfi_internal.h"
+#include "lib/rpmte_internal.h" /* relocations */
+#include "lib/cpio.h" /* XXX CPIO_FOO */
+#include "lib/fsm.h" /* XXX newFSM() */
+
+#include "debug.h"
+
+/*
+ * Simple and stupid string "cache."
+ * Store each unique string just once, retrieve by index value.
+ * For data where number of unique names is typically very low,
+ * the dumb linear lookup appears to be fast enough and hash table seems
+ * like an overkill.
+ */
+struct strcache_s {
+ char **uniq;
+ scidx_t num;
+};
+
+static struct strcache_s _ugcache = { NULL, 0 };
+static strcache ugcache = &_ugcache;
+static struct strcache_s _langcache = { NULL, 0 };
+static strcache langcache = &_langcache;
+
+static scidx_t strcachePut(strcache cache, const char *str)
+{
+ int found = 0;
+ scidx_t ret;
+
+ for (scidx_t i = 0; i < cache->num; i++) {
+ if (rstreq(str, cache->uniq[i])) {
+ ret = i;
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ /* blow up on index wraparound */
+ assert((scidx_t)(cache->num + 1) > cache->num);
+ cache->uniq = xrealloc(cache->uniq,
+ sizeof(cache->uniq) * (cache->num+1));
+ cache->uniq[cache->num] = xstrdup(str);
+ ret = cache->num;
+ cache->num++;
+ }
+ return ret;
+}
+
+static const char *strcacheGet(strcache cache, scidx_t idx)
+{
+ const char *name = NULL;
+ if (idx >= 0 && idx < cache->num && cache->uniq != NULL)
+ name = cache->uniq[idx];
+ return name;
+}
+
+static strcache strcacheNew(void)
+{
+ strcache cache = xcalloc(1, sizeof(*cache));
+ return cache;
+}
+
+static strcache strcacheFree(strcache cache)
+{
+ if (cache != NULL) {
+ for (scidx_t i = 0; i < cache->num; i++) {
+ free(cache->uniq[i]);
+ }
+ cache->uniq = _free(cache->uniq);
+ free(cache);
+ }
+ return NULL;
+}
+
+static rpmfi rpmfiUnlink(rpmfi fi)
+{
+ if (fi)
+ fi->nrefs--;
+ return NULL;
+}
+
+rpmfi rpmfiLink(rpmfi fi)
+{
+ if (fi)
+ fi->nrefs++;
+ return fi;
+}
+
+rpm_count_t rpmfiFC(rpmfi fi)
+{
+ return (fi != NULL ? fi->fc : 0);
+}
+
+rpm_count_t rpmfiDC(rpmfi fi)
+{
+ return (fi != NULL ? fi->dc : 0);
+}
+
+#ifdef NOTYET
+int rpmfiDI(rpmfi fi)
+{
+}
+#endif
+
+int rpmfiFX(rpmfi fi)
+{
+ return (fi != NULL ? fi->i : -1);
+}
+
+int rpmfiSetFX(rpmfi fi, int fx)
+{
+ int i = -1;
+
+ if (fi != NULL && fx >= 0 && fx < fi->fc) {
+ i = fi->i;
+ fi->i = fx;
+ fi->j = fi->dil[fi->i];
+ }
+ return i;
+}
+
+int rpmfiDX(rpmfi fi)
+{
+ return (fi != NULL ? fi->j : -1);
+}
+
+int rpmfiSetDX(rpmfi fi, int dx)
+{
+ int j = -1;
+
+ if (fi != NULL && dx >= 0 && dx < fi->dc) {
+ j = fi->j;
+ fi->j = dx;
+ }
+ return j;
+}
+
+int rpmfiDIIndex(rpmfi fi, int dx)
+{
+ int j = -1;
+ if (fi != NULL && dx >= 0 && dx < fi->fc) {
+ if (fi->dil != NULL)
+ j = fi->dil[dx];
+ }
+ return j;
+}
+
+const char * rpmfiBNIndex(rpmfi fi, int ix)
+{
+ const char * BN = NULL;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->bnl != NULL)
+ BN = fi->bnl[ix];
+ }
+ return BN;
+}
+
+const char * rpmfiDNIndex(rpmfi fi, int jx)
+{
+ const char * DN = NULL;
+
+ if (fi != NULL && jx >= 0 && jx < fi->dc) {
+ if (fi->dnl != NULL)
+ DN = fi->dnl[jx];
+ }
+ return DN;
+}
+
+const char * rpmfiFNIndex(rpmfi fi, int ix)
+{
+ const char * FN = "";
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ char * t;
+ if (fi->fn == NULL) {
+ size_t dnlmax = 0, bnlmax = 0, len;
+ for (int i = 0; i < fi->dc; i++) {
+ if ((len = strlen(fi->dnl[i])) > dnlmax)
+ dnlmax = len;
+ }
+ for (int i = 0; i < fi->fc; i++) {
+ if ((len = strlen(fi->bnl[i])) > bnlmax)
+ bnlmax = len;
+ }
+ fi->fn = xmalloc(dnlmax + bnlmax + 1);
+ }
+ FN = t = fi->fn;
+ *t = '\0';
+ t = stpcpy(t, fi->dnl[fi->dil[ix]]);
+ t = stpcpy(t, fi->bnl[ix]);
+ }
+ return FN;
+}
+
+rpmfileAttrs rpmfiFFlagsIndex(rpmfi fi, int ix)
+{
+ rpmfileAttrs FFlags = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fflags != NULL)
+ FFlags = fi->fflags[ix];
+ }
+ return FFlags;
+}
+
+rpmVerifyAttrs rpmfiVFlagsIndex(rpmfi fi, int ix)
+{
+ rpmVerifyAttrs VFlags = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->vflags != NULL)
+ VFlags = fi->vflags[ix];
+ }
+ return VFlags;
+}
+
+rpm_mode_t rpmfiFModeIndex(rpmfi fi, int ix)
+{
+ rpm_mode_t fmode = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fmodes != NULL)
+ fmode = fi->fmodes[ix];
+ }
+ return fmode;
+}
+
+rpmfileState rpmfiFStateIndex(rpmfi fi, int ix)
+{
+ rpmfileState fstate = RPMFILE_STATE_MISSING;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fstates != NULL)
+ fstate = fi->fstates[ix];
+ }
+ return fstate;
+}
+
+const unsigned char * rpmfiMD5(rpmfi fi)
+{
+ const unsigned char *digest;
+ int algo = 0;
+
+ digest = rpmfiFDigest(fi, &algo, NULL);
+ return (algo == PGPHASHALGO_MD5) ? digest : NULL;
+}
+
+int rpmfiDigestAlgo(rpmfi fi)
+{
+ return fi ? fi->digestalgo : 0;
+}
+
+const unsigned char * rpmfiFDigestIndex(rpmfi fi, int ix, int *algo, size_t *len)
+{
+ const unsigned char *digest = NULL;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ size_t diglen = rpmDigestLength(fi->digestalgo);
+ if (fi->digests != NULL)
+ digest = fi->digests + (diglen * ix);
+ if (len)
+ *len = diglen;
+ if (algo)
+ *algo = fi->digestalgo;
+ }
+ return digest;
+}
+
+char * rpmfiFDigestHex(rpmfi fi, int *algo)
+{
+ size_t diglen = 0;
+ char *fdigest = NULL;
+ const unsigned char *digest = rpmfiFDigest(fi, algo, &diglen);
+ if (digest) {
+ fdigest = pgpHexStr(digest, diglen);
+ }
+ return fdigest;
+}
+
+const char * rpmfiFLinkIndex(rpmfi fi, int ix)
+{
+ const char * flink = NULL;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->flinks != NULL)
+ flink = strcacheGet(fi->flinkcache, fi->flinks[ix]);
+ }
+ return flink;
+}
+
+rpm_loff_t rpmfiFSizeIndex(rpmfi fi, int ix)
+{
+ rpm_loff_t fsize = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fsizes != NULL)
+ fsize = fi->fsizes[ix];
+ }
+ return fsize;
+}
+
+rpm_rdev_t rpmfiFRdevIndex(rpmfi fi, int ix)
+{
+ rpm_rdev_t frdev = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->frdevs != NULL)
+ frdev = fi->frdevs[ix];
+ }
+ return frdev;
+}
+
+rpm_ino_t rpmfiFInodeIndex(rpmfi fi, int ix)
+{
+ rpm_ino_t finode = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->finodes != NULL)
+ finode = fi->finodes[ix];
+ }
+ return finode;
+}
+
+rpm_color_t rpmfiColor(rpmfi fi)
+{
+ rpm_color_t color = 0;
+
+ if (fi != NULL && fi->fcolors != NULL) {
+ for (int i = 0; i < fi->fc; i++)
+ color |= fi->fcolors[i];
+ /* XXX ignore all but lsnibble for now. */
+ color &= 0xf;
+ }
+ return color;
+}
+
+rpm_color_t rpmfiFColorIndex(rpmfi fi, int ix)
+{
+ rpm_color_t fcolor = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fcolors != NULL)
+ /* XXX ignore all but lsnibble for now. */
+ fcolor = (fi->fcolors[ix] & 0x0f);
+ }
+ return fcolor;
+}
+
+const char * rpmfiFClassIndex(rpmfi fi, int ix)
+{
+ const char * fclass = NULL;
+ int cdictx;
+
+ if (fi != NULL && fi->fcdictx != NULL && ix >= 0 && ix < fi->fc) {
+ cdictx = fi->fcdictx[ix];
+ if (fi->cdict != NULL && cdictx >= 0 && cdictx < fi->ncdict)
+ fclass = fi->cdict[cdictx];
+ }
+ return fclass;
+}
+
+uint32_t rpmfiFDependsIndex(rpmfi fi, int ix, const uint32_t ** fddictp)
+{
+ int fddictx = -1;
+ int fddictn = 0;
+ const uint32_t * fddict = NULL;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fddictn != NULL)
+ fddictn = fi->fddictn[ix];
+ if (fddictn > 0 && fi->fddictx != NULL)
+ fddictx = fi->fddictx[ix];
+ if (fi->ddict != NULL && fddictx >= 0 && (fddictx+fddictn) <= fi->nddict)
+ fddict = fi->ddict + fddictx;
+ }
+ if (fddictp)
+ *fddictp = fddict;
+ return fddictn;
+}
+
+uint32_t rpmfiFNlinkIndex(rpmfi fi, int ix)
+{
+ uint32_t nlink = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ /* XXX rpm-2.3.12 has not RPMTAG_FILEINODES */
+ if (fi->finodes && fi->frdevs) {
+ rpm_ino_t finode = fi->finodes[ix];
+ rpm_rdev_t frdev = fi->frdevs[ix];
+ int j;
+
+ for (j = 0; j < fi->fc; j++) {
+ if (fi->frdevs[j] == frdev && fi->finodes[j] == finode)
+ nlink++;
+ }
+ }
+ }
+ return nlink;
+}
+
+rpm_time_t rpmfiFMtimeIndex(rpmfi fi, int ix)
+{
+ rpm_time_t fmtime = 0;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fmtimes != NULL)
+ fmtime = fi->fmtimes[ix];
+ }
+ return fmtime;
+}
+
+const char * rpmfiFUserIndex(rpmfi fi, int ix)
+{
+ const char * fuser = NULL;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fuser != NULL)
+ fuser = strcacheGet(ugcache, fi->fuser[ix]);
+ }
+ return fuser;
+}
+
+const char * rpmfiFGroupIndex(rpmfi fi, int ix)
+{
+ const char * fgroup = NULL;
+
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ if (fi->fgroup != NULL)
+ fgroup = strcacheGet(ugcache, fi->fgroup[ix]);
+ }
+ return fgroup;
+}
+
+const char * rpmfiFCapsIndex(rpmfi fi, int ix)
+{
+ const char *fcaps = NULL;
+ if (fi != NULL && ix >= 0 && ix < fi->fc) {
+ fcaps = fi->fcapcache ? strcacheGet(fi->fcapcache, fi->fcaps[ix]) : "";
+ }
+ return fcaps;
+}
+
+const char * rpmfiFLangsIndex(rpmfi fi, int ix)
+{
+ const char *flangs = NULL;
+ if (fi != NULL && fi->flangs != NULL && ix >= 0 && ix < fi->fc) {
+ flangs = strcacheGet(langcache, fi->flangs[ix]);
+ }
+ return flangs;
+}
+
+struct fingerPrint_s *rpmfiFpsIndex(rpmfi fi, int ix)
+{
+ struct fingerPrint_s * fps = NULL;
+ if (fi != NULL && fi->fps != NULL && ix >= 0 && ix < fi->fc) {
+ fps = fi->fps + ix;
+ }
+ return fps;
+}
+
+int rpmfiNext(rpmfi fi)
+{
+ int i = -1;
+
+ if (fi != NULL && ++fi->i >= 0) {
+ if (fi->i < fi->fc) {
+ i = fi->i;
+ if (fi->dil != NULL)
+ fi->j = fi->dil[fi->i];
+ } else
+ fi->i = -1;
+ }
+
+ return i;
+}
+
+rpmfi rpmfiInit(rpmfi fi, int fx)
+{
+ if (fi != NULL) {
+ if (fx >= 0 && fx < fi->fc) {
+ fi->i = fx - 1;
+ fi->j = -1;
+ }
+ }
+
+ return fi;
+}
+
+int rpmfiNextD(rpmfi fi)
+{
+ int j = -1;
+
+ if (fi != NULL && ++fi->j >= 0) {
+ if (fi->j < fi->dc)
+ j = fi->j;
+ else
+ fi->j = -1;
+ }
+
+ return j;
+}
+
+rpmfi rpmfiInitD(rpmfi fi, int dx)
+{
+ if (fi != NULL) {
+ if (dx >= 0 && dx < fi->fc)
+ fi->j = dx - 1;
+ else
+ fi = NULL;
+ }
+
+ return fi;
+}
+
+/**
+ * Identify a file type.
+ * @param ft file type
+ * @return string to identify a file type
+ */
+static
+const char * ftstring (rpmFileTypes ft)
+{
+ switch (ft) {
+ case XDIR: return "directory";
+ case CDEV: return "char dev";
+ case BDEV: return "block dev";
+ case LINK: return "link";
+ case SOCK: return "sock";
+ case PIPE: return "fifo/pipe";
+ case REG: return "file";
+ default: return "unknown file type";
+ }
+}
+
+rpmFileTypes rpmfiWhatis(rpm_mode_t mode)
+{
+ if (S_ISDIR(mode)) return XDIR;
+ if (S_ISCHR(mode)) return CDEV;
+ if (S_ISBLK(mode)) return BDEV;
+ if (S_ISLNK(mode)) return LINK;
+ if (S_ISSOCK(mode)) return SOCK;
+ if (S_ISFIFO(mode)) return PIPE;
+ return REG;
+}
+
+int rpmfiCompare(const rpmfi afi, const rpmfi bfi)
+{
+ rpmFileTypes awhat = rpmfiWhatis(rpmfiFMode(afi));
+ rpmFileTypes bwhat = rpmfiWhatis(rpmfiFMode(bfi));
+
+ if ((rpmfiFFlags(afi) & RPMFILE_GHOST) ||
+ (rpmfiFFlags(bfi) & RPMFILE_GHOST)) return 0;
+
+ if (awhat != bwhat) return 1;
+
+ if (awhat == LINK) {
+ const char * alink = rpmfiFLink(afi);
+ const char * blink = rpmfiFLink(bfi);
+ if (alink == blink) return 0;
+ if (alink == NULL) return 1;
+ if (blink == NULL) return -1;
+ return strcmp(alink, blink);
+ } else if (awhat == REG) {
+ size_t adiglen, bdiglen;
+ int aalgo, balgo;
+ const unsigned char * adigest = rpmfiFDigest(afi, &aalgo, &adiglen);
+ const unsigned char * bdigest = rpmfiFDigest(bfi, &balgo, &bdiglen);
+ if (adigest == bdigest) return 0;
+ if (adigest == NULL) return 1;
+ if (bdigest == NULL) return -1;
+ /* can't meaningfully compare different hash types */
+ if (aalgo != balgo || adiglen != bdiglen) return -1;
+ return memcmp(adigest, bdigest, adiglen);
+ }
+
+ return 0;
+}
+
+rpmFileAction rpmfiDecideFate(const rpmfi ofi, rpmfi nfi, int skipMissing)
+{
+ const char * fn = rpmfiFN(nfi);
+ rpmfileAttrs newFlags = rpmfiFFlags(nfi);
+ char buffer[1024];
+ rpmFileTypes dbWhat, newWhat, diskWhat;
+ struct stat sb;
+ int save = (newFlags & RPMFILE_NOREPLACE) ? FA_ALTNAME : FA_SAVE;
+
+ if (lstat(fn, &sb)) {
+ /*
+ * The file doesn't exist on the disk. Create it unless the new
+ * package has marked it as missingok, or allfiles is requested.
+ */
+ if (skipMissing && (newFlags & RPMFILE_MISSINGOK)) {
+ rpmlog(RPMLOG_DEBUG, "%s skipped due to missingok flag\n",
+ fn);
+ return FA_SKIP;
+ } else {
+ return FA_CREATE;
+ }
+ }
+
+ diskWhat = rpmfiWhatis((rpm_mode_t)sb.st_mode);
+ dbWhat = rpmfiWhatis(rpmfiFMode(ofi));
+ newWhat = rpmfiWhatis(rpmfiFMode(nfi));
+
+ /*
+ * RPM >= 2.3.10 shouldn't create config directories -- we'll ignore
+ * them in older packages as well.
+ */
+ if (newWhat == XDIR)
+ return FA_CREATE;
+
+ if (diskWhat != newWhat && dbWhat != REG && dbWhat != LINK)
+ return save;
+ else if (newWhat != dbWhat && diskWhat != dbWhat)
+ return save;
+ else if (dbWhat != newWhat)
+ return FA_CREATE;
+ else if (dbWhat != LINK && dbWhat != REG)
+ return FA_CREATE;
+
+ /*
+ * This order matters - we'd prefer to CREATE the file if at all
+ * possible in case something else (like the timestamp) has changed.
+ */
+ memset(buffer, 0, sizeof(buffer));
+ if (dbWhat == REG) {
+ int oalgo, nalgo;
+ size_t odiglen, ndiglen;
+ const unsigned char * odigest, * ndigest;
+ odigest = rpmfiFDigest(ofi, &oalgo, &odiglen);
+ if (diskWhat == REG) {
+ if (rpmDoDigest(oalgo, fn, 0,
+ (unsigned char *)buffer, NULL))
+ return FA_CREATE; /* assume file has been removed */
+ if (odigest && !memcmp(odigest, buffer, odiglen))
+ return FA_CREATE; /* unmodified config file, replace. */
+ }
+ ndigest = rpmfiFDigest(nfi, &nalgo, &ndiglen);
+ /* Can't compare different hash types, backup to avoid data loss */
+ if (oalgo != nalgo || odiglen != ndiglen)
+ return save;
+ if (odigest && ndigest && !memcmp(odigest, ndigest, odiglen))
+ return FA_SKIP; /* identical file, don't bother. */
+ } else /* dbWhat == LINK */ {
+ const char * oFLink, * nFLink;
+ oFLink = rpmfiFLink(ofi);
+ if (diskWhat == LINK) {
+ if (readlink(fn, buffer, sizeof(buffer) - 1) == -1)
+ return FA_CREATE; /* assume file has been removed */
+ if (oFLink && rstreq(oFLink, buffer))
+ return FA_CREATE; /* unmodified config file, replace. */
+ }
+ nFLink = rpmfiFLink(nfi);
+ if (oFLink && nFLink && rstreq(oFLink, nFLink))
+ return FA_SKIP; /* identical file, don't bother. */
+ }
+
+ /*
+ * The config file on the disk has been modified, but
+ * the ones in the two packages are different. It would
+ * be nice if RPM was smart enough to at least try and
+ * merge the difference ala CVS, but...
+ */
+ return save;
+}
+
+int rpmfiConfigConflict(const rpmfi fi)
+{
+ const char * fn = rpmfiFN(fi);
+ rpmfileAttrs flags = rpmfiFFlags(fi);
+ char buffer[1024];
+ rpmFileTypes newWhat, diskWhat;
+ struct stat sb;
+
+ if (!(flags & RPMFILE_CONFIG) || lstat(fn, &sb)) {
+ return 0;
+ }
+
+ diskWhat = rpmfiWhatis((rpm_mode_t)sb.st_mode);
+ newWhat = rpmfiWhatis(rpmfiFMode(fi));
+
+ if (newWhat != LINK && newWhat != REG)
+ return 1;
+
+ if (diskWhat != newWhat)
+ return 1;
+
+ memset(buffer, 0, sizeof(buffer));
+ if (newWhat == REG) {
+ int algo;
+ size_t diglen;
+ const unsigned char *ndigest = rpmfiFDigest(fi, &algo, &diglen);
+ if (rpmDoDigest(algo, fn, 0, (unsigned char *)buffer, NULL))
+ return 0; /* assume file has been removed */
+ if (ndigest && !memcmp(ndigest, buffer, diglen))
+ return 0; /* unmodified config file */
+ } else /* newWhat == LINK */ {
+ const char * nFLink;
+ if (readlink(fn, buffer, sizeof(buffer) - 1) == -1)
+ return 0; /* assume file has been removed */
+ nFLink = rpmfiFLink(fi);
+ if (nFLink && rstreq(nFLink, buffer))
+ return 0; /* unmodified config file */
+ }
+
+ return 1;
+}
+
+static char **duparray(char ** src, int size)
+{
+ char **dest = xmalloc((size+1) * sizeof(*dest));
+ for (int i = 0; i < size; i++) {
+ dest[i] = xstrdup(src[i]);
+ }
+ free(src);
+ return dest;
+}
+
+static int addPrefixes(Header h, rpmRelocation *relocations, int numRelocations)
+{
+ struct rpmtd_s validRelocs;
+ const char *validprefix;
+ const char ** actualRelocations;
+ int numActual = 0;
+
+ headerGet(h, RPMTAG_PREFIXES, &validRelocs, HEADERGET_MINMEM);
+ /*
+ * If no relocations are specified (usually the case), then return the
+ * original header. If there are prefixes, however, then INSTPREFIXES
+ * should be added for RPM_INSTALL_PREFIX environ variables in scriptlets,
+ * but, since relocateFileList() can be called more than once for
+ * the same header, don't bother if already present.
+ */
+ if (relocations == NULL || numRelocations == 0) {
+ if (rpmtdCount(&validRelocs) > 0) {
+ if (!headerIsEntry(h, RPMTAG_INSTPREFIXES)) {
+ rpmtdSetTag(&validRelocs, RPMTAG_INSTPREFIXES);
+ headerPut(h, &validRelocs, HEADERPUT_DEFAULT);
+ }
+ rpmtdFreeData(&validRelocs);
+ }
+ return 0;
+ }
+
+ actualRelocations = xmalloc(rpmtdCount(&validRelocs) * sizeof(*actualRelocations));
+ rpmtdInit(&validRelocs);
+ while ((validprefix = rpmtdNextString(&validRelocs))) {
+ int j;
+ for (j = 0; j < numRelocations; j++) {
+ if (relocations[j].oldPath == NULL || /* XXX can't happen */
+ !rstreq(validprefix, relocations[j].oldPath))
+ continue;
+ /* On install, a relocate to NULL means skip the path. */
+ if (relocations[j].newPath) {
+ actualRelocations[numActual] = relocations[j].newPath;
+ numActual++;
+ }
+ break;
+ }
+ if (j == numRelocations) {
+ actualRelocations[numActual] = validprefix;
+ numActual++;
+ }
+ }
+ rpmtdFreeData(&validRelocs);
+
+ if (numActual) {
+ headerPutStringArray(h, RPMTAG_INSTPREFIXES, actualRelocations, numActual);
+ }
+ actualRelocations = _free(actualRelocations);
+ return numActual;
+}
+
+static void saveRelocs(Header h, rpmtd bnames, rpmtd dnames, rpmtd dindexes)
+{
+ struct rpmtd_s td;
+ headerGet(h, RPMTAG_BASENAMES, &td, HEADERGET_MINMEM);
+ rpmtdSetTag(&td, RPMTAG_ORIGBASENAMES);
+ headerPut(h, &td, HEADERPUT_DEFAULT);
+ rpmtdFreeData(&td);
+
+ headerGet(h, RPMTAG_DIRNAMES, &td, HEADERGET_MINMEM);
+ rpmtdSetTag(&td, RPMTAG_ORIGDIRNAMES);
+ headerPut(h, &td, HEADERPUT_DEFAULT);
+ rpmtdFreeData(&td);
+
+ headerGet(h, RPMTAG_DIRINDEXES, &td, HEADERGET_MINMEM);
+ rpmtdSetTag(&td, RPMTAG_ORIGDIRINDEXES);
+ headerPut(h, &td, HEADERPUT_DEFAULT);
+ rpmtdFreeData(&td);
+
+ headerMod(h, bnames);
+ headerMod(h, dnames);
+ headerMod(h, dindexes);
+}
+
+void rpmRelocateFileList(rpmRelocation *relocations, int numRelocations,
+ rpmfs fs, Header h)
+{
+ static int _printed = 0;
+ char ** baseNames;
+ char ** dirNames;
+ uint32_t * dirIndexes;
+ rpm_count_t fileCount, dirCount;
+ int nrelocated = 0;
+ int fileAlloced = 0;
+ char * fn = NULL;
+ int haveRelocatedBase = 0;
+ size_t maxlen = 0;
+ int i, j;
+ struct rpmtd_s bnames, dnames, dindexes, fmodes;
+
+ addPrefixes(h, relocations, numRelocations);
+
+ if (!_printed) {
+ _printed = 1;
+ rpmlog(RPMLOG_DEBUG, "========== relocations\n");
+ for (i = 0; i < numRelocations; i++) {
+ if (relocations[i].oldPath == NULL) continue; /* XXX can't happen */
+ if (relocations[i].newPath == NULL)
+ rpmlog(RPMLOG_DEBUG, "%5d exclude %s\n",
+ i, relocations[i].oldPath);
+ else
+ rpmlog(RPMLOG_DEBUG, "%5d relocate %s -> %s\n",
+ i, relocations[i].oldPath, relocations[i].newPath);
+ }
+ }
+
+ for (i = 0; i < numRelocations; i++) {
+ if (relocations[i].newPath == NULL) continue;
+ size_t len = strlen(relocations[i].newPath);
+ if (len > maxlen) maxlen = len;
+ }
+
+ headerGet(h, RPMTAG_BASENAMES, &bnames, HEADERGET_MINMEM);
+ headerGet(h, RPMTAG_DIRINDEXES, &dindexes, HEADERGET_ALLOC);
+ headerGet(h, RPMTAG_DIRNAMES, &dnames, HEADERGET_MINMEM);
+ headerGet(h, RPMTAG_FILEMODES, &fmodes, HEADERGET_MINMEM);
+ /* TODO XXX ugh.. use rpmtd iterators & friends instead */
+ baseNames = bnames.data;
+ dirIndexes = dindexes.data;
+ fileCount = rpmtdCount(&bnames);
+ dirCount = rpmtdCount(&dnames);
+ /* XXX TODO: use rpmtdDup() instead */
+ dirNames = dnames.data = duparray(dnames.data, dirCount);
+ dnames.flags |= RPMTD_PTR_ALLOCED;
+
+ /*
+ * For all relocations, we go through sorted file/relocation lists
+ * backwards so that /usr/local relocations take precedence over /usr
+ * ones.
+ */
+
+ /* Relocate individual paths. */
+
+ for (i = fileCount - 1; i >= 0; i--) {
+ rpmFileTypes ft;
+ int fnlen;
+
+ size_t len = maxlen +
+ strlen(dirNames[dirIndexes[i]]) + strlen(baseNames[i]) + 1;
+ if (len >= fileAlloced) {
+ fileAlloced = len * 2;
+ fn = xrealloc(fn, fileAlloced);
+ }
+
+assert(fn != NULL); /* XXX can't happen */
+ *fn = '\0';
+ fnlen = stpcpy( stpcpy(fn, dirNames[dirIndexes[i]]), baseNames[i]) - fn;
+
+ /*
+ * See if this file path needs relocating.
+ */
+ /*
+ * XXX FIXME: Would a bsearch of the (already sorted)
+ * relocation list be a good idea?
+ */
+ for (j = numRelocations - 1; j >= 0; j--) {
+ if (relocations[j].oldPath == NULL) /* XXX can't happen */
+ continue;
+ len = !rstreq(relocations[j].oldPath, "/")
+ ? strlen(relocations[j].oldPath)
+ : 0;
+
+ if (fnlen < len)
+ continue;
+ /*
+ * Only subdirectories or complete file paths may be relocated. We
+ * don't check for '\0' as our directory names all end in '/'.
+ */
+ if (!(fn[len] == '/' || fnlen == len))
+ continue;
+
+ if (!rstreqn(relocations[j].oldPath, fn, len))
+ continue;
+ break;
+ }
+ if (j < 0) continue;
+
+ rpmtdSetIndex(&fmodes, i);
+ ft = rpmfiWhatis(rpmtdGetNumber(&fmodes));
+
+ /* On install, a relocate to NULL means skip the path. */
+ if (relocations[j].newPath == NULL) {
+ if (ft == XDIR) {
+ /* Start with the parent, looking for directory to exclude. */
+ for (j = dirIndexes[i]; j < dirCount; j++) {
+ len = strlen(dirNames[j]) - 1;
+ while (len > 0 && dirNames[j][len-1] == '/') len--;
+ if (fnlen != len)
+ continue;
+ if (!rstreqn(fn, dirNames[j], fnlen))
+ continue;
+ break;
+ }
+ }
+ rpmfsSetAction(fs, i, FA_SKIPNSTATE);
+ rpmlog(RPMLOG_DEBUG, "excluding %s %s\n",
+ ftstring(ft), fn);
+ continue;
+ }
+
+ /* Relocation on full paths only, please. */
+ if (fnlen != len) continue;
+
+ rpmlog(RPMLOG_DEBUG, "relocating %s to %s\n",
+ fn, relocations[j].newPath);
+ nrelocated++;
+
+ strcpy(fn, relocations[j].newPath);
+ { char * te = strrchr(fn, '/');
+ if (te) {
+ if (te > fn) te++; /* root is special */
+ fnlen = te - fn;
+ } else
+ te = fn + strlen(fn);
+ if (!rstreq(baseNames[i], te)) { /* basename changed too? */
+ if (!haveRelocatedBase) {
+ /* XXX TODO: use rpmtdDup() instead */
+ bnames.data = baseNames = duparray(baseNames, fileCount);
+ bnames.flags |= RPMTD_PTR_ALLOCED;
+ haveRelocatedBase = 1;
+ }
+ free(baseNames[i]);
+ baseNames[i] = xstrdup(te);
+ }
+ *te = '\0'; /* terminate new directory name */
+ }
+
+ /* Does this directory already exist in the directory list? */
+ for (j = 0; j < dirCount; j++) {
+ if (fnlen != strlen(dirNames[j]))
+ continue;
+ if (!rstreqn(fn, dirNames[j], fnlen))
+ continue;
+ break;
+ }
+
+ if (j < dirCount) {
+ dirIndexes[i] = j;
+ continue;
+ }
+
+ /* Creating new paths is a pita */
+ dirNames = dnames.data = xrealloc(dnames.data,
+ sizeof(*dirNames) * (dirCount + 1));
+
+ dirNames[dirCount] = xstrdup(fn);
+ dirIndexes[i] = dirCount;
+ dirCount++;
+ dnames.count++;
+ }
+
+ /* Finish off by relocating directories. */
+ for (i = dirCount - 1; i >= 0; i--) {
+ for (j = numRelocations - 1; j >= 0; j--) {
+
+ if (relocations[j].oldPath == NULL) /* XXX can't happen */
+ continue;
+ size_t len = !rstreq(relocations[j].oldPath, "/")
+ ? strlen(relocations[j].oldPath)
+ : 0;
+
+ if (len && !rstreqn(relocations[j].oldPath, dirNames[i], len))
+ continue;
+
+ /*
+ * Only subdirectories or complete file paths may be relocated. We
+ * don't check for '\0' as our directory names all end in '/'.
+ */
+ if (dirNames[i][len] != '/')
+ continue;
+
+ if (relocations[j].newPath) { /* Relocate the path */
+ char *t = NULL;
+ rstrscat(&t, relocations[j].newPath, (dirNames[i] + len), NULL);
+ /* Unfortunatly rpmCleanPath strips the trailing slash.. */
+ (void) rpmCleanPath(t);
+ rstrcat(&t, "/");
+
+ rpmlog(RPMLOG_DEBUG,
+ "relocating directory %s to %s\n", dirNames[i], t);
+ free(dirNames[i]);
+ dirNames[i] = t;
+ nrelocated++;
+ }
+ }
+ }
+
+ /* Save original filenames in header and replace (relocated) filenames. */
+ if (nrelocated) {
+ saveRelocs(h, &bnames, &dnames, &dindexes);
+ }
+
+ rpmtdFreeData(&bnames);
+ rpmtdFreeData(&dnames);
+ rpmtdFreeData(&dindexes);
+ rpmtdFreeData(&fmodes);
+ free(fn);
+}
+
+rpmfi rpmfiFree(rpmfi fi)
+{
+ if (fi == NULL) return NULL;
+
+ if (fi->nrefs > 1)
+ return rpmfiUnlink(fi);
+
+ if (fi->fc > 0) {
+ fi->bnl = _free(fi->bnl);
+ fi->dnl = _free(fi->dnl);
+
+ fi->flinkcache = strcacheFree(fi->flinkcache);
+ fi->flinks = _free(fi->flinks);
+ fi->flangs = _free(fi->flangs);
+ fi->digests = _free(fi->digests);
+ fi->fcapcache = strcacheFree(fi->fcapcache);
+ fi->fcaps = _free(fi->fcaps);
+
+ fi->cdict = _free(fi->cdict);
+
+ fi->fuser = _free(fi->fuser);
+ fi->fgroup = _free(fi->fgroup);
+
+ fi->fstates = _free(fi->fstates);
+ fi->fps = _free(fi->fps);
+
+ /* these point to header memory if KEEPHEADER is used, dont free */
+ if (!(fi->fiflags & RPMFI_KEEPHEADER) && fi->h == NULL) {
+ fi->fmtimes = _free(fi->fmtimes);
+ fi->fmodes = _free(fi->fmodes);
+ fi->fflags = _free(fi->fflags);
+ fi->vflags = _free(fi->vflags);
+ fi->fsizes = _free(fi->fsizes);
+ fi->frdevs = _free(fi->frdevs);
+ fi->finodes = _free(fi->finodes);
+ fi->dil = _free(fi->dil);
+
+ fi->fcolors = _free(fi->fcolors);
+ fi->fcdictx = _free(fi->fcdictx);
+ fi->ddict = _free(fi->ddict);
+ fi->fddictx = _free(fi->fddictx);
+ fi->fddictn = _free(fi->fddictn);
+
+ }
+ }
+
+ fi->fsm = freeFSM(fi->fsm);
+
+ fi->fn = _free(fi->fn);
+ fi->apath = _free(fi->apath);
+
+ fi->replacedSizes = _free(fi->replacedSizes);
+
+ fi->h = headerFree(fi->h);
+
+ (void) rpmfiUnlink(fi);
+ memset(fi, 0, sizeof(*fi)); /* XXX trash and burn */
+ fi = _free(fi);
+
+ return NULL;
+}
+
+/* Helper to push header tag data into a string cache */
+static scidx_t *cacheTag(strcache cache, Header h, rpmTag tag)
+{
+ scidx_t *idx = NULL;
+ struct rpmtd_s td;
+ if (headerGet(h, tag, &td, HEADERGET_MINMEM)) {
+ idx = xmalloc(sizeof(*idx) * rpmtdCount(&td));
+ int i = 0;
+ const char *str;
+ while ((str = rpmtdNextString(&td))) {
+ idx[i++] = strcachePut(cache, str);
+ }
+ rpmtdFreeData(&td);
+ }
+ return idx;
+}
+
+#define _hgfi(_h, _tag, _td, _flags, _data) \
+ if (headerGet((_h), (_tag), (_td), (_flags))) \
+ _data = (td.data)
+
+rpmfi rpmfiNew(const rpmts ts, Header h, rpmTagVal tagN, rpmfiFlags flags)
+{
+ rpmfi fi = NULL;
+ rpm_loff_t *asize = NULL;
+ unsigned char * t;
+ int isBuild, isSource;
+ struct rpmtd_s fdigests, digalgo;
+ struct rpmtd_s td;
+ headerGetFlags scareFlags = (flags & RPMFI_KEEPHEADER) ?
+ HEADERGET_MINMEM : HEADERGET_ALLOC;
+ headerGetFlags defFlags = HEADERGET_ALLOC;
+
+ fi = xcalloc(1, sizeof(*fi));
+ if (fi == NULL) /* XXX can't happen */
+ goto exit;
+
+ fi->magic = RPMFIMAGIC;
+ fi->i = -1;
+
+ fi->fiflags = flags;
+ fi->scareFlags = scareFlags;
+
+ if (headerGet(h, RPMTAG_LONGARCHIVESIZE, &td, HEADERGET_EXT)) {
+ asize = rpmtdGetUint64(&td);
+ }
+ /* 0 means unknown */
+ fi->archiveSize = asize ? *asize : 0;
+ rpmtdFreeData(&td);
+
+ /* Archive size is not set when this gets called from build */
+ isBuild = (asize == NULL);
+ isSource = headerIsSource(h);
+ if (isBuild) fi->fiflags |= RPMFI_ISBUILD;
+ if (isSource) fi->fiflags |= RPMFI_ISSOURCE;
+
+ _hgfi(h, RPMTAG_BASENAMES, &td, defFlags, fi->bnl);
+ fi->fc = rpmtdCount(&td);
+ if (fi->fc == 0) {
+ goto exit;
+ }
+
+ _hgfi(h, RPMTAG_DIRNAMES, &td, defFlags, fi->dnl);
+ fi->dc = rpmtdCount(&td);
+ _hgfi(h, RPMTAG_DIRINDEXES, &td, scareFlags, fi->dil);
+ if (!(flags & RPMFI_NOFILEMODES))
+ _hgfi(h, RPMTAG_FILEMODES, &td, scareFlags, fi->fmodes);
+ if (!(flags & RPMFI_NOFILEFLAGS))
+ _hgfi(h, RPMTAG_FILEFLAGS, &td, scareFlags, fi->fflags);
+ if (!(flags & RPMFI_NOFILEVERIFYFLAGS))
+ _hgfi(h, RPMTAG_FILEVERIFYFLAGS, &td, scareFlags, fi->vflags);
+ if (!(flags & RPMFI_NOFILESIZES))
+ _hgfi(h, RPMTAG_FILESIZES, &td, scareFlags, fi->fsizes);
+
+ if (!(flags & RPMFI_NOFILECOLORS))
+ _hgfi(h, RPMTAG_FILECOLORS, &td, scareFlags, fi->fcolors);
+
+ if (!(flags & RPMFI_NOFILECLASS)) {
+ _hgfi(h, RPMTAG_CLASSDICT, &td, scareFlags, fi->cdict);
+ fi->ncdict = rpmtdCount(&td);
+ _hgfi(h, RPMTAG_FILECLASS, &td, scareFlags, fi->fcdictx);
+ }
+ if (!(flags & RPMFI_NOFILEDEPS)) {
+ _hgfi(h, RPMTAG_DEPENDSDICT, &td, scareFlags, fi->ddict);
+ fi->nddict = rpmtdCount(&td);
+ _hgfi(h, RPMTAG_FILEDEPENDSX, &td, scareFlags, fi->fddictx);
+ _hgfi(h, RPMTAG_FILEDEPENDSN, &td, scareFlags, fi->fddictn);
+ }
+
+ if (!(flags & RPMFI_NOFILESTATES))
+ _hgfi(h, RPMTAG_FILESTATES, &td, defFlags, fi->fstates);
+
+ if (!(flags & RPMFI_NOFILECAPS) && headerIsEntry(h, RPMTAG_FILECAPS)) {
+ fi->fcapcache = strcacheNew();
+ fi->fcaps = cacheTag(fi->fcapcache, h, RPMTAG_FILECAPS);
+ }
+
+ if (!(flags & RPMFI_NOFILELINKTOS)) {
+ fi->flinkcache = strcacheNew();
+ fi->flinks = cacheTag(fi->flinkcache, h, RPMTAG_FILELINKTOS);
+ }
+ /* FILELANGS are only interesting when installing */
+ if ((headerGetInstance(h) == 0) && !(flags & RPMFI_NOFILELANGS))
+ fi->flangs = cacheTag(langcache, h, RPMTAG_FILELANGS);
+
+ /* See if the package has non-md5 file digests */
+ fi->digestalgo = PGPHASHALGO_MD5;
+ if (headerGet(h, RPMTAG_FILEDIGESTALGO, &digalgo, HEADERGET_MINMEM)) {
+ uint32_t *algo = rpmtdGetUint32(&digalgo);
+ /* Hmm, what to do with unknown digest algorithms? */
+ if (algo && rpmDigestLength(*algo) != 0) {
+ fi->digestalgo = *algo;
+ }
+ }
+
+ fi->digests = NULL;
+ /* grab hex digests from header and store in binary format */
+ if (!(flags & RPMFI_NOFILEDIGESTS) &&
+ headerGet(h, RPMTAG_FILEDIGESTS, &fdigests, HEADERGET_MINMEM)) {
+ const char *fdigest;
+ size_t diglen = rpmDigestLength(fi->digestalgo);
+ fi->digests = t = xmalloc(rpmtdCount(&fdigests) * diglen);
+
+ while ((fdigest = rpmtdNextString(&fdigests))) {
+ if (!(fdigest && *fdigest != '\0')) {
+ memset(t, 0, diglen);
+ t += diglen;
+ continue;
+ }
+ for (int j = 0; j < diglen; j++, t++, fdigest += 2)
+ *t = (rnibble(fdigest[0]) << 4) | rnibble(fdigest[1]);
+ }
+ rpmtdFreeData(&fdigests);
+ }
+
+ /* XXX TR_REMOVED doesn;t need fmtimes, frdevs, finodes */
+ if (!(flags & RPMFI_NOFILEMTIMES))
+ _hgfi(h, RPMTAG_FILEMTIMES, &td, scareFlags, fi->fmtimes);
+ if (!(flags & RPMFI_NOFILERDEVS))
+ _hgfi(h, RPMTAG_FILERDEVS, &td, scareFlags, fi->frdevs);
+ if (!(flags & RPMFI_NOFILEINODES))
+ _hgfi(h, RPMTAG_FILEINODES, &td, scareFlags, fi->finodes);
+
+ if (!(flags & RPMFI_NOFILEUSER))
+ fi->fuser = cacheTag(ugcache, h, RPMTAG_FILEUSERNAME);
+ if (!(flags & RPMFI_NOFILEGROUP))
+ fi->fgroup = cacheTag(ugcache, h, RPMTAG_FILEGROUPNAME);
+
+ /* lazily alloced from rpmfiFN() */
+ fi->fn = NULL;
+
+exit:
+
+ if (fi != NULL) {
+ fi->h = (fi->fiflags & RPMFI_KEEPHEADER) ? headerLink(h) : NULL;
+ }
+
+ /* FIX: rpmfi null annotations */
+ return rpmfiLink(fi);
+}
+
+void rpmfiSetFReplacedSize(rpmfi fi, rpm_loff_t newsize)
+{
+ if (fi != NULL && fi->i >= 0 && fi->i < fi->fc) {
+ if (fi->replacedSizes == NULL) {
+ fi->replacedSizes = xcalloc(fi->fc, sizeof(*fi->replacedSizes));
+ }
+ /* XXX watch out, replacedSizes is not rpm_loff_t (yet) */
+ fi->replacedSizes[fi->i] = (rpm_off_t) newsize;
+ }
+}
+
+rpm_loff_t rpmfiFReplacedSize(rpmfi fi)
+{
+ rpm_loff_t rsize = 0;
+ if (fi != NULL && fi->i >= 0 && fi->i < fi->fc) {
+ if (fi->replacedSizes) {
+ rsize = fi->replacedSizes[fi->i];
+ }
+ }
+ return rsize;
+}
+
+void rpmfiFpLookup(rpmfi fi, fingerPrintCache fpc)
+{
+ if (fi->fc > 0 && fi->fps == NULL) {
+ fi->fps = xcalloc(fi->fc, sizeof(*fi->fps));
+ }
+ fpLookupList(fpc, fi->dnl, fi->bnl, fi->dil, fi->fc, fi->fps);
+}
+
+FSM_t rpmfiFSM(rpmfi fi)
+{
+ if (fi != NULL && fi->fsm == NULL) {
+ cpioMapFlags mapflags;
+ /* Figure out mapflags:
+ * - path, mode, uid and gid are used by everything
+ * - all binary packages get SBIT_CHECK set
+ * - if archive size is not known, we're only building this package,
+ * different rules apply
+ */
+ mapflags = CPIO_MAP_PATH | CPIO_MAP_MODE | CPIO_MAP_UID | CPIO_MAP_GID;
+ if (fi->fiflags & RPMFI_ISBUILD) {
+ mapflags |= CPIO_MAP_TYPE;
+ if (fi->fiflags & RPMFI_ISSOURCE) mapflags |= CPIO_FOLLOW_SYMLINKS;
+ } else {
+ if (!(fi->fiflags & RPMFI_ISSOURCE)) mapflags |= CPIO_SBIT_CHECK;
+ }
+ fi->fsm = newFSM(mapflags);
+ }
+ return (fi != NULL) ? fi->fsm : NULL;
+}
+
+/*
+ * Generate iterator accessors function wrappers, these do nothing but
+ * call the corresponding rpmfiFooIndex(fi, fi->[ij])
+ */
+
+#define RPMFI_ITERFUNC(TYPE, NAME, IXV) \
+ TYPE rpmfi ## NAME(rpmfi fi) { return rpmfi ## NAME ## Index(fi, fi ? fi->IXV : -1); }
+
+RPMFI_ITERFUNC(const char *, BN, i)
+RPMFI_ITERFUNC(const char *, DN, j)
+RPMFI_ITERFUNC(const char *, FN, i)
+RPMFI_ITERFUNC(const char *, FLink, i)
+RPMFI_ITERFUNC(const char *, FUser, i)
+RPMFI_ITERFUNC(const char *, FGroup, i)
+RPMFI_ITERFUNC(const char *, FCaps, i)
+RPMFI_ITERFUNC(const char *, FLangs, i)
+RPMFI_ITERFUNC(const char *, FClass, i)
+RPMFI_ITERFUNC(rpmfileState, FState, i)
+RPMFI_ITERFUNC(rpmfileAttrs, FFlags, i)
+RPMFI_ITERFUNC(rpmVerifyAttrs, VFlags, i)
+RPMFI_ITERFUNC(rpm_mode_t, FMode, i)
+RPMFI_ITERFUNC(rpm_rdev_t, FRdev, i)
+RPMFI_ITERFUNC(rpm_time_t, FMtime, i)
+RPMFI_ITERFUNC(rpm_ino_t, FInode, i)
+RPMFI_ITERFUNC(rpm_loff_t, FSize, i)
+RPMFI_ITERFUNC(rpm_color_t, FColor, i)
+RPMFI_ITERFUNC(uint32_t, FNlink, i)
+
+const unsigned char * rpmfiFDigest(rpmfi fi, int *algo, size_t *len)
+{
+ return rpmfiFDigestIndex(fi, fi ? fi->i : -1, algo, len);
+}
+
+uint32_t rpmfiFDepends(rpmfi fi, const uint32_t ** fddictp)
+{
+ return rpmfiFDependsIndex(fi, fi ? fi->i : -1, fddictp);
+}
diff --git a/lib/rpmfi.h b/lib/rpmfi.h
new file mode 100644
index 0000000..3dcf61b
--- /dev/null
+++ b/lib/rpmfi.h
@@ -0,0 +1,459 @@
+#ifndef H_RPMFI
+#define H_RPMFI
+
+/** \ingroup rpmdep rpmtrans
+ * \file lib/rpmfi.h
+ * Structure(s) used for file info tag sets.
+ */
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmvf.h>
+#include <rpm/rpmpgp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmfi
+ * File types.
+ * These are the file types used internally by rpm. The file
+ * type is determined by applying stat(2) macros like S_ISDIR to
+ * the file mode tag from a header. The values are arbitrary,
+ * but are identical to the linux stat(2) file types.
+ */
+typedef enum rpmFileTypes_e {
+ PIPE = 1, /*!< pipe/fifo */
+ CDEV = 2, /*!< character device */
+ XDIR = 4, /*!< directory */
+ BDEV = 6, /*!< block device */
+ REG = 8, /*!< regular file */
+ LINK = 10, /*!< hard link */
+ SOCK = 12 /*!< socket */
+} rpmFileTypes;
+
+/**
+ * File States (when installed).
+ */
+typedef enum rpmfileState_e {
+ RPMFILE_STATE_MISSING = -1, /* used for unavailable data */
+ RPMFILE_STATE_NORMAL = 0,
+ RPMFILE_STATE_REPLACED = 1,
+ RPMFILE_STATE_NOTINSTALLED = 2,
+ RPMFILE_STATE_NETSHARED = 3,
+ RPMFILE_STATE_WRONGCOLOR = 4
+} rpmfileState;
+
+/**
+ * File Attributes.
+ */
+enum rpmfileAttrs_e {
+ RPMFILE_NONE = 0,
+ RPMFILE_CONFIG = (1 << 0), /*!< from %%config */
+ RPMFILE_DOC = (1 << 1), /*!< from %%doc */
+ RPMFILE_ICON = (1 << 2), /*!< from %%donotuse. */
+ RPMFILE_MISSINGOK = (1 << 3), /*!< from %%config(missingok) */
+ RPMFILE_NOREPLACE = (1 << 4), /*!< from %%config(noreplace) */
+ RPMFILE_SPECFILE = (1 << 5), /*!< @todo (unnecessary) marks 1st file in srpm. */
+ RPMFILE_GHOST = (1 << 6), /*!< from %%ghost */
+ RPMFILE_LICENSE = (1 << 7), /*!< from %%license */
+ RPMFILE_README = (1 << 8), /*!< from %%readme */
+ RPMFILE_EXCLUDE = (1 << 9), /*!< from %%exclude, internal */
+ RPMFILE_UNPATCHED = (1 << 10), /*!< placeholder (SuSE) */
+ RPMFILE_PUBKEY = (1 << 11), /*!< from %%pubkey */
+};
+
+typedef rpmFlags rpmfileAttrs;
+
+#define RPMFILE_ALL ~(RPMFILE_NONE)
+
+/** \ingroup rpmfi
+ * File disposition(s) during package install/erase transaction.
+ */
+typedef enum rpmFileAction_e {
+ FA_UNKNOWN = 0, /*!< initial action for file ... */
+ FA_CREATE, /*!< ... copy in from payload. */
+ FA_COPYIN, /*!< ... copy in from payload. */
+ FA_COPYOUT, /*!< ... copy out to payload. */
+ FA_BACKUP, /*!< ... renamed with ".rpmorig" extension. */
+ FA_SAVE, /*!< ... renamed with ".rpmsave" extension. */
+ FA_SKIP, /*!< ... already replaced, don't remove. */
+ FA_ALTNAME, /*!< ... create with ".rpmnew" extension. */
+ FA_ERASE, /*!< ... to be removed. */
+ FA_SKIPNSTATE, /*!< ... untouched, state "not installed". */
+ FA_SKIPNETSHARED, /*!< ... untouched, state "netshared". */
+ FA_SKIPCOLOR /*!< ... untouched, state "wrong color". */
+} rpmFileAction;
+
+#define XFA_SKIPPING(_a) \
+ ((_a) == FA_SKIP || (_a) == FA_SKIPNSTATE || (_a) == FA_SKIPNETSHARED || (_a) == FA_SKIPCOLOR)
+
+/**
+ * We pass these around as an array with a sentinel.
+ */
+struct rpmRelocation_s {
+ char * oldPath; /*!< NULL here evals to RPMTAG_DEFAULTPREFIX, */
+ char * newPath; /*!< NULL means to omit the file completely! */
+};
+
+/** \ingroup rpmfi
+ * Reference a file info set instance.
+ * @param fi file info set
+ * @return new file info set reference
+ */
+rpmfi rpmfiLink (rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return file count from file info set.
+ * @param fi file info set
+ * @return current file count
+ */
+rpm_count_t rpmfiFC(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file index from file info set.
+ * @param fi file info set
+ * @return current file index
+ */
+int rpmfiFX(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Set current file index in file info set.
+ * @param fi file info set
+ * @param fx new file index
+ * @return current file index
+ */
+int rpmfiSetFX(rpmfi fi, int fx);
+
+/** \ingroup rpmfi
+ * Return directory count from file info set.
+ * @param fi file info set
+ * @return current directory count
+ */
+rpm_count_t rpmfiDC(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current directory index from file info set.
+ * @param fi file info set
+ * @return current directory index
+ */
+int rpmfiDX(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Set current directory index in file info set.
+ * @param fi file info set
+ * @param dx new directory index
+ * @return current directory index
+ */
+int rpmfiSetDX(rpmfi fi, int dx);
+
+/** \ingroup rpmfi
+ * Return current base name from file info set.
+ * @param fi file info set
+ * @return current base name, NULL on invalid
+ */
+const char * rpmfiBN(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current directory name from file info set.
+ * @param fi file info set
+ * @return current directory, NULL on invalid
+ */
+const char * rpmfiDN(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file name from file info set.
+ * @param fi file info set
+ * @return current file name
+ */
+const char * rpmfiFN(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file flags from file info set.
+ * @param fi file info set
+ * @return current file flags, 0 on invalid
+ */
+rpmfileAttrs rpmfiFFlags(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file verify flags from file info set.
+ * @param fi file info set
+ * @return current file verify flags, 0 on invalid
+ */
+rpmVerifyAttrs rpmfiVFlags(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file mode from file info set.
+ * @param fi file info set
+ * @return current file mode, 0 on invalid
+ */
+rpm_mode_t rpmfiFMode(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file state from file info set.
+ * @param fi file info set
+ * @return current file state, 0 on invalid
+ */
+rpmfileState rpmfiFState(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return digest algorithm of a file info set.
+ * @param fi file info set
+ * @return digest algorithm of file info set, 0 on invalid
+ */
+int rpmfiDigestAlgo(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file (binary) digest of file info set.
+ * @param fi file info set
+ * @retval algo digest hash algoritm used (pass NULL to ignore)
+ * @retval diglen digest hash length (pass NULL to ignore)
+ * @return current file digest, NULL on invalid
+ */
+const unsigned char * rpmfiFDigest(rpmfi fi, int *algo, size_t *diglen);
+
+/** \ingroup rpmfi
+ * Return current file (hex) digest of file info set.
+ * The file info set stores file digests in binary format to conserve
+ * memory, this converts the binary data back to hex presentation used in
+ * headers.
+ * @param fi file info set
+ * @retval algo digest hash algoritm used (pass NULL to ignore)
+ * @return current file digest (malloc'ed), NULL on invalid
+ */
+char * rpmfiFDigestHex(rpmfi fi, int *algo);
+
+/** \ingroup rpmfi
+ * Return current file (binary) md5 digest from file info set.
+ * @deprecated Use rpmfiFDigest() instead
+ * @param fi file info set
+ * @return current file md5 digest, NULL on invalid
+ */
+const unsigned char * rpmfiMD5(rpmfi fi) RPM_GNUC_DEPRECATED;
+
+/** \ingroup rpmfi
+ * Return current file linkto (i.e. symlink(2) target) from file info set.
+ * @param fi file info set
+ * @return current file linkto, NULL on invalid
+ */
+const char * rpmfiFLink(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file size from file info set.
+ * @param fi file info set
+ * @return current file size, 0 on invalid
+ */
+rpm_loff_t rpmfiFSize(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file rdev from file info set.
+ * @param fi file info set
+ * @return current file rdev, 0 on invalid
+ */
+rpm_rdev_t rpmfiFRdev(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file inode from file info set.
+ * @param fi file info set
+ * @return current file inode, 0 on invalid
+ */
+rpm_ino_t rpmfiFInode(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return union of all file color bits from file info set.
+ * @param fi file info set
+ * @return current color
+ */
+rpm_color_t rpmfiColor(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file color bits from file info set.
+ * @param fi file info set
+ * @return current file color
+ */
+rpm_color_t rpmfiFColor(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file class from file info set.
+ * @param fi file info set
+ * @return current file class, 0 on invalid
+ */
+const char * rpmfiFClass(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file depends dictionary from file info set.
+ * @param fi file info set
+ * @retval *fddictp file depends dictionary array (or NULL)
+ * @return no. of file depends entries, 0 on invalid
+ */
+uint32_t rpmfiFDepends(rpmfi fi,
+ const uint32_t ** fddictp);
+
+/** \ingroup rpmfi
+ * Return (calculated) current file nlink count from file info set.
+ * @param fi file info set
+ * @return current file nlink count, 0 on invalid
+ */
+uint32_t rpmfiFNlink(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file modify time from file info set.
+ * @param fi file info set
+ * @return current file modify time, 0 on invalid
+ */
+rpm_time_t rpmfiFMtime(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file owner from file info set.
+ * @param fi file info set
+ * @return current file owner, NULL on invalid
+ */
+const char * rpmfiFUser(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file group from file info set.
+ * @param fi file info set
+ * @return current file group, NULL on invalid
+ */
+const char * rpmfiFGroup(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return textual representation of current file capabilities
+ * from file info set. See cap_from_text(3) for details.
+ * @param fi file info set
+ * @return file capability description, "" for no capabilities
+ * and NULL on invalid
+ */
+const char * rpmfiFCaps(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return current file language(s) from file info set.
+ * @param fi file info set
+ * @return current file language(s), NULL on invalid
+ */
+const char * rpmfiFLangs(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Return next file iterator index.
+ * @param fi file info set
+ * @return file iterator index, -1 on termination
+ */
+int rpmfiNext(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Initialize file iterator index.
+ * @param fi file info set
+ * @param fx file iterator index
+ * @return file info set
+ */
+rpmfi rpmfiInit(rpmfi fi, int fx);
+
+/** \ingroup rpmfi
+ * Return next directory iterator index.
+ * @param fi file info set
+ * @return directory iterator index, -1 on termination
+ */
+int rpmfiNextD(rpmfi fi);
+
+/** \ingroup rpmfi
+ * Initialize directory iterator index.
+ * @param fi file info set
+ * @param dx directory iterator index
+ * @return file info set, NULL if dx is out of range
+ */
+rpmfi rpmfiInitD(rpmfi fi, int dx);
+
+/** \ingroup rpmfi
+ * Destroy a file info set.
+ * @param fi file info set
+ * @return NULL always
+ */
+rpmfi rpmfiFree(rpmfi fi);
+
+enum rpmfiFlags_e {
+ RPMFI_NOHEADER = 0,
+ RPMFI_KEEPHEADER = (1 << 0),
+ RPMFI_NOFILECLASS = (1 << 1),
+ RPMFI_NOFILEDEPS = (1 << 2),
+ RPMFI_NOFILELANGS = (1 << 3),
+ RPMFI_NOFILEUSER = (1 << 4),
+ RPMFI_NOFILEGROUP = (1 << 5),
+ RPMFI_NOFILEMODES = (1 << 6),
+ RPMFI_NOFILESIZES = (1 << 7),
+ RPMFI_NOFILECAPS = (1 << 8),
+ RPMFI_NOFILELINKTOS = (1 << 9),
+ RPMFI_NOFILEDIGESTS = (1 << 10),
+ RPMFI_NOFILEMTIMES = (1 << 11),
+ RPMFI_NOFILERDEVS = (1 << 12),
+ RPMFI_NOFILEINODES = (1 << 13),
+ RPMFI_NOFILESTATES = (1 << 14),
+ RPMFI_NOFILECOLORS = (1 << 15),
+ RPMFI_NOFILEVERIFYFLAGS = (1 << 16),
+ RPMFI_NOFILEFLAGS = (1 << 17),
+ RPMFI_ISBUILD = (1 << 30), /* internal */
+ RPMFI_ISSOURCE = (1 << 31), /* internal */
+};
+
+typedef rpmFlags rpmfiFlags;
+
+#define RPMFI_FLAGS_ERASE \
+ (RPMFI_NOFILECLASS | RPMFI_NOFILELANGS | \
+ RPMFI_NOFILEMTIMES | RPMFI_NOFILERDEVS | RPMFI_NOFILEINODES | \
+ RPMFI_NOFILEVERIFYFLAGS)
+
+#define RPMFI_FLAGS_INSTALL \
+ (RPMFI_NOFILECLASS | RPMFI_NOFILEVERIFYFLAGS)
+
+#define RPMFI_FLAGS_VERIFY \
+ (RPMFI_NOFILECLASS | RPMFI_NOFILEDEPS | RPMFI_NOFILELANGS | \
+ RPMFI_NOFILECOLORS)
+
+#define RPMFI_FLAGS_QUERY \
+ (RPMFI_NOFILECLASS | RPMFI_NOFILEDEPS | RPMFI_NOFILELANGS | \
+ RPMFI_NOFILECOLORS | RPMFI_NOFILEVERIFYFLAGS)
+
+/** \ingroup rpmfi
+ * Create and load a file info set.
+ * @param ts unused
+ * @param h header
+ * @param tagN unused
+ * @param flags Flags to control what information is loaded.
+ * @return new file info set
+ */
+rpmfi rpmfiNew(const rpmts ts, Header h, rpmTagVal tagN, rpmfiFlags flags);
+
+/** \ingroup rpmfi
+ * Return file type from mode_t.
+ * @param mode file mode bits (from header)
+ * @return file type
+ */
+rpmFileTypes rpmfiWhatis(rpm_mode_t mode);
+
+/** \ingroup rpmfi
+ * Return file info comparison.
+ * @param afi 1st file info
+ * @param bfi 2nd file info
+ * @return 0 if identical
+ */
+int rpmfiCompare(const rpmfi afi, const rpmfi bfi);
+
+/** \ingroup rpmfi
+ * Return file disposition.
+ * @param ofi old file info
+ * @param nfi new file info
+ * @param skipMissing OK to skip missing files?
+ * @return file dispostion
+ */
+rpmFileAction rpmfiDecideFate(const rpmfi ofi, rpmfi nfi, int skipMissing);
+
+/** \ingroup rpmfi
+ * Return whether file is conflicting config
+ * @param fi file info
+ * @return 1 if config file and file on disk conflicts
+ */
+int rpmfiConfigConflict(const rpmfi fi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMDS */
diff --git a/lib/rpmfi_internal.h b/lib/rpmfi_internal.h
new file mode 100644
index 0000000..dc02eaf
--- /dev/null
+++ b/lib/rpmfi_internal.h
@@ -0,0 +1,172 @@
+#ifndef _RPMFI_INTERNAL_H
+#define _RPMFI_INTERNAL_H
+
+#include <rpm/header.h>
+#include <rpm/rpmfi.h>
+#include "lib/fsm.h" /* for FSM_t */
+#include "lib/fprint.h"
+
+/*
+ * This limits maximum unique strings (user + group names) from packages to
+ * 65535, should be plenty but easy to bump if ever needed.
+ */
+typedef uint16_t scidx_t;
+typedef struct strcache_s *strcache;
+
+#define RPMFIMAGIC 0x09697923
+
+/**
+ * A package filename set.
+ */
+struct rpmfi_s {
+ int i; /*!< Current file index. */
+ int j; /*!< Current directory index. */
+
+ Header h; /*!< Header for file info set (or NULL) */
+
+ const char ** bnl; /*!< Base name(s) (from header) */
+ const char ** dnl; /*!< Directory name(s) (from header) */
+
+ strcache flinkcache; /*!< File link cache */
+ scidx_t * flinks; /*!< Index to file link(s) cache */
+ scidx_t * flangs; /*!< Index to file lang(s) cache */
+
+ uint32_t * dil; /*!< Directory indice(s) (from header) */
+ rpm_flag_t * fflags; /*!< File flag(s) (from header) */
+ rpm_off_t * fsizes; /*!< File size(s) (from header) */
+ rpm_time_t * fmtimes; /*!< File modification time(s) (from header) */
+ rpm_mode_t * fmodes; /*!< File mode(s) (from header) */
+ rpm_rdev_t * frdevs; /*!< File rdev(s) (from header) */
+ rpm_ino_t * finodes; /*!< File inodes(s) (from header) */
+
+ scidx_t *fuser; /*!< Index to file owner(s) cache */
+ scidx_t *fgroup; /*!< Index to file group(s) cache */
+
+ char * fstates; /*!< File state(s) (from header) */
+
+ rpm_color_t * fcolors; /*!< File color bits (header) */
+ strcache fcapcache; /*!< File capabilities cache */
+ scidx_t * fcaps; /*!< Index to file cap(s) cache */
+
+ char ** cdict; /*!< File class dictionary (header) */
+ rpm_count_t ncdict; /*!< No. of class entries. */
+ uint32_t * fcdictx; /*!< File class dictionary index (header) */
+
+ uint32_t * ddict; /*!< File depends dictionary (header) */
+ rpm_count_t nddict; /*!< No. of depends entries. */
+ uint32_t * fddictx; /*!< File depends dictionary start (header) */
+ uint32_t * fddictn; /*!< File depends dictionary count (header) */
+ rpm_flag_t * vflags; /*!< File verify flag(s) (from header) */
+
+ rpm_count_t dc; /*!< No. of directories. */
+ rpm_count_t fc; /*!< No. of files. */
+
+ rpmfiFlags fiflags; /*!< file info set control flags */
+ headerGetFlags scareFlags; /*!< headerGet flags wrt scareMem */
+
+ struct fingerPrint_s * fps; /*!< File fingerprint(s). */
+
+ int digestalgo; /*!< File digest algorithm */
+ unsigned char * digests; /*!< File digests in binary. */
+
+ char * fn; /*!< File name buffer. */
+
+ rpm_loff_t archiveSize;
+ char ** apath;
+ FSM_t fsm; /*!< File state machine data. */
+ rpm_off_t * replacedSizes; /*!< (TR_ADDED) */
+ int magic;
+ int nrefs; /*!< Reference count. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+RPM_GNUC_INTERNAL
+int rpmfiDIIndex(rpmfi fi, int dx);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiBNIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiDNIndex(rpmfi fi, int jx);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFNIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpmVerifyAttrs rpmfiVFlagsIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpmfileState rpmfiFStateIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFLinkIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpm_loff_t rpmfiFSizeIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpm_color_t rpmfiFColorIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFClassIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+uint32_t rpmfiFDependsIndex(rpmfi fi, int ix, const uint32_t ** fddictp);
+
+RPM_GNUC_INTERNAL
+uint32_t rpmfiFNlinkIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFLangsIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpmfileAttrs rpmfiFFlagsIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpm_mode_t rpmfiFModeIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const unsigned char * rpmfiFDigestIndex(rpmfi fi, int ix, int *algo, size_t *len);
+
+RPM_GNUC_INTERNAL
+rpm_rdev_t rpmfiFRdevIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpm_ino_t rpmfiFInodeIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+rpm_time_t rpmfiFMtimeIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFUserIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFGroupIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+const char * rpmfiFCapsIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+struct fingerPrint_s *rpmfiFpsIndex(rpmfi fi, int ix);
+
+RPM_GNUC_INTERNAL
+void rpmfiSetFReplacedSize(rpmfi fi, rpm_loff_t newsize);
+
+RPM_GNUC_INTERNAL
+rpm_loff_t rpmfiFReplacedSize(rpmfi fi);
+
+RPM_GNUC_INTERNAL
+void rpmfiFpLookup(rpmfi fi, fingerPrintCache fpc);
+
+/* XXX can't be internal as build code needs this */
+FSM_t rpmfiFSM(rpmfi fi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMFI_INTERNAL_H */
+
diff --git a/lib/rpmfs.c b/lib/rpmfs.c
new file mode 100644
index 0000000..a8b1b5b
--- /dev/null
+++ b/lib/rpmfs.c
@@ -0,0 +1,120 @@
+#include "system.h"
+#include <rpm/header.h>
+#include "lib/rpmfs.h"
+#include "debug.h"
+
+struct rpmfs_s {
+ unsigned int fc;
+
+ rpm_fstate_t * states;
+ rpmFileAction * actions; /*!< File disposition(s). */
+
+ sharedFileInfo replaced; /*!< (TR_ADDED) to be replaced files in the rpmdb */
+ int numReplaced;
+ int allocatedReplaced;
+};
+
+rpmfs rpmfsNew(Header h, rpmElementType type)
+{
+ struct rpmtd_s bnames;
+ rpmfs fs = xcalloc(1, sizeof(*fs));
+
+ headerGet(h, RPMTAG_BASENAMES, &bnames, HEADERGET_MINMEM);
+ fs->fc = rpmtdCount(&bnames);
+ rpmtdFreeData(&bnames);
+
+ fs->actions = xmalloc(fs->fc * sizeof(*fs->actions));
+ memset(fs->actions, FA_UNKNOWN, fs->fc * sizeof(*fs->actions));
+ if (type == TR_ADDED) {
+ fs->states = xmalloc(sizeof(*fs->states) * fs->fc);
+ memset(fs->states, RPMFILE_STATE_NORMAL, fs->fc);
+ }
+ return fs;
+}
+
+rpmfs rpmfsFree(rpmfs fs)
+{
+ if (fs != NULL) {
+ fs->replaced = _free(fs->replaced);
+ fs->states = _free(fs->states);
+ fs->actions = _free(fs->actions);
+ fs = _free(fs);
+ }
+ return NULL;
+}
+
+rpm_count_t rpmfsFC(rpmfs fs)
+{
+ return (fs != NULL) ? fs->fc : 0;
+}
+
+void rpmfsAddReplaced(rpmfs fs, int pkgFileNum, int otherPkg, int otherFileNum)
+{
+ if (!fs->replaced) {
+ fs->replaced = xcalloc(3, sizeof(*fs->replaced));
+ fs->allocatedReplaced = 3;
+ }
+ if (fs->numReplaced>=fs->allocatedReplaced) {
+ fs->allocatedReplaced += (fs->allocatedReplaced>>1) + 2;
+ fs->replaced = xrealloc(fs->replaced, fs->allocatedReplaced*sizeof(*fs->replaced));
+ }
+ fs->replaced[fs->numReplaced].pkgFileNum = pkgFileNum;
+ fs->replaced[fs->numReplaced].otherPkg = otherPkg;
+ fs->replaced[fs->numReplaced].otherFileNum = otherFileNum;
+
+ fs->numReplaced++;
+}
+
+sharedFileInfo rpmfsGetReplaced(rpmfs fs)
+{
+ if (fs && fs->numReplaced)
+ return fs->replaced;
+ else
+ return NULL;
+}
+
+sharedFileInfo rpmfsNextReplaced(rpmfs fs , sharedFileInfo replaced)
+{
+ if (fs && replaced) {
+ replaced++;
+ if (replaced - fs->replaced < fs->numReplaced)
+ return replaced;
+ }
+ return NULL;
+}
+
+void rpmfsSetState(rpmfs fs, unsigned int ix, rpmfileState state)
+{
+ assert(ix < fs->fc);
+ fs->states[ix] = state;
+}
+
+rpmfileState rpmfsGetState(rpmfs fs, unsigned int ix)
+{
+ assert(ix < fs->fc);
+ if (fs->states) return fs->states[ix];
+ return RPMFILE_STATE_MISSING;
+}
+
+rpm_fstate_t * rpmfsGetStates(rpmfs fs)
+{
+ return fs->states;
+}
+
+rpmFileAction rpmfsGetAction(rpmfs fs, unsigned int ix)
+{
+ rpmFileAction action;
+ if (fs->actions != NULL && ix < fs->fc) {
+ action = fs->actions[ix];
+ } else {
+ action = FA_UNKNOWN;
+ }
+ return action;
+}
+
+void rpmfsSetAction(rpmfs fs, unsigned int ix, rpmFileAction action)
+{
+ if (fs->actions != NULL && ix < fs->fc) {
+ fs->actions[ix] = action;
+ }
+}
diff --git a/lib/rpmfs.h b/lib/rpmfs.h
new file mode 100644
index 0000000..129bc65
--- /dev/null
+++ b/lib/rpmfs.h
@@ -0,0 +1,63 @@
+#ifndef _RPMFS_H
+#define _RPMFS_H
+
+#include <rpm/rpmfi.h>
+#include <rpm/rpmte.h>
+
+/** \ingroup rpmfs
+ * Transaction element file states.
+ */
+typedef struct rpmfs_s * rpmfs;
+typedef struct sharedFileInfo_s * sharedFileInfo;
+typedef char rpm_fstate_t;
+
+/* XXX psm needs access to these */
+struct sharedFileInfo_s {
+ int pkgFileNum;
+ int otherPkg;
+ int otherFileNum;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+RPM_GNUC_INTERNAL
+rpmfs rpmfsNew(Header h, rpmElementType type);
+
+RPM_GNUC_INTERNAL
+rpmfs rpmfsFree(rpmfs fs);
+
+RPM_GNUC_INTERNAL
+rpm_count_t rpmfsFC(rpmfs fs);
+
+RPM_GNUC_INTERNAL
+void rpmfsAddReplaced(rpmfs fs, int pkgFileNum, int otherPkg, int otherFileNum);
+
+RPM_GNUC_INTERNAL
+sharedFileInfo rpmfsGetReplaced(rpmfs fs);
+
+RPM_GNUC_INTERNAL
+sharedFileInfo rpmfsNextReplaced(rpmfs fs , sharedFileInfo replaced);
+
+RPM_GNUC_INTERNAL
+void rpmfsSetState(rpmfs fs, unsigned int ix, rpmfileState state);
+
+RPM_GNUC_INTERNAL
+rpmfileState rpmfsGetState(rpmfs fs, unsigned int ix);
+
+/* May return NULL */
+RPM_GNUC_INTERNAL
+rpm_fstate_t * rpmfsGetStates(rpmfs fs);
+
+RPM_GNUC_INTERNAL
+rpmFileAction rpmfsGetAction(rpmfs fs, unsigned int ix);
+
+/* XXX this should be internal too but build code needs for now */
+void rpmfsSetAction(rpmfs fs, unsigned int ix, rpmFileAction action);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMFS_H */
diff --git a/lib/rpmgi.c b/lib/rpmgi.c
new file mode 100644
index 0000000..19f8f9f
--- /dev/null
+++ b/lib/rpmgi.c
@@ -0,0 +1,241 @@
+/** \ingroup rpmio
+ * \file lib/rpmgi.c
+ */
+#include "system.h"
+
+#include <errno.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmlib.h> /* rpmReadPackageFile */
+#include <rpm/rpmts.h>
+#include <rpm/rpmmacro.h> /* XXX rpmExpand */
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmlog.h>
+
+#include "lib/rpmgi.h"
+#include "lib/manifest.h"
+
+#include "debug.h"
+
+RPM_GNUC_INTERNAL
+rpmgiFlags giFlags = RPMGI_NONE;
+
+/** \ingroup rpmgi
+ */
+struct rpmgi_s {
+ rpmts ts; /*!< Iterator transaction set. */
+
+ rpmgiFlags flags; /*!< Iterator control bits. */
+ int i; /*!< Element index. */
+ int errors;
+
+ ARGV_t argv;
+ int argc;
+};
+
+/**
+ * Open a file after macro expanding path.
+ * @todo There are two error messages printed on header, then manifest failures.
+ * @param path file path
+ * @param fmode open mode
+ * @return file handle
+ */
+static FD_t rpmgiOpen(const char * path, const char * fmode)
+{
+ char * fn = rpmExpand(path, NULL);
+ FD_t fd = Fopen(fn, fmode);
+
+ if (fd == NULL || Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), fn, Fstrerror(fd));
+ if (fd != NULL) (void) Fclose(fd);
+ fd = NULL;
+ }
+ fn = _free(fn);
+
+ return fd;
+}
+
+/**
+ * Load manifest into iterator arg list.
+ * @param gi generalized iterator
+ * @param path file path
+ * @return RPMRC_OK on success
+ */
+static rpmRC rpmgiLoadManifest(rpmgi gi, const char * path)
+{
+ FD_t fd = rpmgiOpen(path, "r.ufdio");
+ rpmRC rpmrc = RPMRC_FAIL;
+
+ if (fd != NULL) {
+ rpmrc = rpmReadPackageManifest(fd, &gi->argc, &gi->argv);
+ (void) Fclose(fd);
+ }
+ return rpmrc;
+}
+
+/**
+ * Return header from package.
+ * @param gi generalized iterator
+ * @param path file path
+ * @retval hdrp header (NULL on failure)
+ * @return 1 if path could be opened, 0 if not
+ */
+static int rpmgiReadHeader(rpmgi gi, const char * path, Header * hdrp)
+{
+ FD_t fd = rpmgiOpen(path, "r.ufdio");
+ Header h = NULL;
+
+ if (fd != NULL) {
+ /* XXX what if path needs expansion? */
+ rpmRC rpmrc = rpmReadPackageFile(gi->ts, fd, path, &h);
+
+ (void) Fclose(fd);
+
+ switch (rpmrc) {
+ case RPMRC_NOTFOUND:
+ /* XXX Read a package manifest. Restart ftswalk on success. */
+ case RPMRC_FAIL:
+ default:
+ h = headerFree(h);
+ break;
+ case RPMRC_NOTTRUSTED:
+ case RPMRC_NOKEY:
+ case RPMRC_OK:
+ break;
+ }
+ }
+
+ *hdrp = h;
+ return (fd != NULL);
+}
+
+/**
+ * Read next header from package, lazily expanding manifests as found.
+ * @todo An empty file read as manifest truncates argv returning RPMRC_NOTFOUND.
+ * @todo Chained manifests lose an arg someplace.
+ * @param gi generalized iterator
+ * @return header on success
+ */
+static Header rpmgiLoadReadHeader(rpmgi gi)
+{
+ Header h = NULL;
+
+ if (gi->argv != NULL && gi->argv[gi->i] != NULL)
+ do {
+ char * fn = gi->argv[gi->i];
+ int rc = rpmgiReadHeader(gi, fn, &h);
+
+ if (h != NULL || (gi->flags & RPMGI_NOMANIFEST) || rc == 0)
+ break;
+
+ /* Not a header, so try for a manifest. */
+ gi->argv[gi->i] = NULL; /* Mark the insertion point */
+ if (rpmgiLoadManifest(gi, fn) != RPMRC_OK) {
+ gi->argv[gi->i] = fn; /* Manifest failed, restore fn */
+ rpmlog(RPMLOG_ERR,
+ _("%s: not an rpm package (or package manifest)\n"), fn);
+ break;
+ }
+ fn = _free(fn);
+ } while (1);
+
+ return h;
+}
+
+
+/**
+ * Append globbed arg list to iterator.
+ * @param gi generalized iterator
+ * @param argv arg list to be globbed (or NULL)
+ */
+static void rpmgiGlobArgv(rpmgi gi, ARGV_const_t argv)
+{
+ const char * arg;
+ int ac = 0;
+ int xx;
+
+ /* XXX Expand globs only if requested */
+ if ((gi->flags & RPMGI_NOGLOB)) {
+ if (argv != NULL) {
+ while (argv[ac] != NULL)
+ ac++;
+ xx = argvAppend(&gi->argv, argv);
+ }
+ gi->argc = ac;
+ return;
+ }
+
+ if (argv != NULL)
+ while ((arg = *argv++) != NULL) {
+ char * t = rpmEscapeSpaces(arg);
+ char ** av = NULL;
+
+ xx = rpmGlob(t, &ac, &av);
+ xx = argvAppend(&gi->argv, av);
+ gi->argc += ac;
+ av = argvFree(av);
+ t = _free(t);
+ ac = 0;
+ }
+ return;
+}
+
+rpmgi rpmgiFree(rpmgi gi)
+{
+ if (gi == NULL)
+ return NULL;
+
+ gi->ts = rpmtsFree(gi->ts);
+ gi->argv = argvFree(gi->argv);
+
+ memset(gi, 0, sizeof(*gi)); /* XXX trash and burn */
+ gi = _free(gi);
+ return NULL;
+}
+
+rpmgi rpmgiNew(rpmts ts, rpmgiFlags flags, ARGV_const_t argv)
+{
+ rpmgi gi = xcalloc(1, sizeof(*gi));
+
+ gi->ts = rpmtsLink(ts);
+
+ gi->flags = flags;
+ gi->i = -1;
+ gi->errors = 0;
+
+ gi->flags = flags;
+ gi->argv = argvNew();
+ gi->argc = 0;
+ rpmgiGlobArgv(gi, argv);
+
+ return gi;
+}
+
+Header rpmgiNext(rpmgi gi)
+{
+ Header h = NULL;
+
+ if (gi != NULL && ++gi->i >= 0) {
+ /*
+ * Read next header, lazily expanding manifests as found,
+ * count + skip errors.
+ */
+ while (gi->i < gi->argc) {
+ if ((h = rpmgiLoadReadHeader(gi)) != NULL)
+ break;
+ gi->errors++;
+ gi->i++;
+ }
+
+ /* Out of things to try, end of iteration */
+ if (h == NULL)
+ gi->i = -1;
+ }
+
+ return h;
+}
+
+int rpmgiNumErrors(rpmgi gi)
+{
+ return (gi != NULL ? gi->errors : -1);
+}
diff --git a/lib/rpmgi.h b/lib/rpmgi.h
new file mode 100644
index 0000000..32b8806
--- /dev/null
+++ b/lib/rpmgi.h
@@ -0,0 +1,65 @@
+#ifndef H_RPMGI
+#define H_RPMGI
+
+/** \ingroup rpmio
+ * \file lib/rpmgi.h
+ */
+
+#include <rpm/rpmtypes.h>
+#include <rpm/argv.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmgi
+ */
+enum rpmgiFlags_e {
+ RPMGI_NONE = 0,
+ RPMGI_NOGLOB = (1 << 2),
+ RPMGI_NOMANIFEST = (1 << 3),
+};
+
+typedef rpmFlags rpmgiFlags;
+
+extern rpmgiFlags giFlags;
+
+/** \ingroup rpmgi
+ * Destroy a generalized iterator.
+ * @param gi generalized iterator
+ * @return NULL always
+ */
+RPM_GNUC_INTERNAL
+rpmgi rpmgiFree(rpmgi gi);
+
+/** \ingroup rpmgi
+ * Return a generalized iterator.
+ * @param ts transaction set
+ * @param flags iterator flags
+ * @param argv arg list
+ * @return new iterator
+ */
+RPM_GNUC_INTERNAL
+rpmgi rpmgiNew(rpmts ts, rpmgiFlags flags, ARGV_const_t argv);
+
+/** \ingroup rpmgi
+ * Perform next iteration step.
+ * @param gi generalized iterator
+ * @returns next header (new reference), NULL on end of iteration
+ */
+RPM_GNUC_INTERNAL
+Header rpmgiNext(rpmgi gi);
+
+/** \ingroup rpmgi
+ * Return number of errors (file not found etc) encountered during iteration
+ * @param gi generalized iterator
+ * @return number of errors
+ */
+RPM_GNUC_INTERNAL
+int rpmgiNumErrors(rpmgi gi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMGI */
diff --git a/lib/rpmhash.C b/lib/rpmhash.C
new file mode 100644
index 0000000..628f2dd
--- /dev/null
+++ b/lib/rpmhash.C
@@ -0,0 +1,272 @@
+/**
+ * \file lib/rpmhash.c
+ * Hash table implemenation
+ */
+
+#include "system.h"
+#include "debug.h"
+
+#define Bucket JOIN(HASHTYPE,Buket)
+#define Bucket_s JOIN(HASHTYPE,Buket_s)
+
+typedef struct Bucket_s * Bucket;
+
+/**
+ */
+struct Bucket_s {
+ Bucket next; /*!< pointer to next item in bucket */
+ HTKEYTYPE key; /*!< hash key */
+#ifdef HTDATATYPE
+ int dataCount; /*!< data entries */
+ HTDATATYPE data[1]; /*!< data - grows by resizing whole bucket */
+#endif
+};
+
+/**
+ */
+struct HASHSTRUCT {
+ int numBuckets; /*!< number of hash buckets */
+ Bucket * buckets; /*!< hash bucket array */
+ hashFunctionType fn; /*!< generate hash value for key */
+ hashEqualityType eq; /*!< compare hash keys for equality */
+ hashFreeKey freeKey;
+ int bucketCount; /*!< number of used buckets */
+ int keyCount; /*!< number of keys */
+#ifdef HTDATATYPE
+ int dataCount; /*!< number of data entries */
+ hashFreeData freeData;
+#endif
+};
+
+/**
+ * Find entry in hash table.
+ * @param ht pointer to hash table
+ * @param key pointer to key value
+ * @return pointer to hash bucket of key (or NULL)
+ */
+static
+Bucket HASHPREFIX(findEntry)(HASHTYPE ht, HTKEYTYPE key)
+{
+ unsigned int hash;
+ Bucket b;
+
+ hash = ht->fn(key) % ht->numBuckets;
+ b = ht->buckets[hash];
+
+ while (b && ht->eq(b->key, key))
+ b = b->next;
+
+ return b;
+}
+
+HASHTYPE HASHPREFIX(Create)(int numBuckets,
+ hashFunctionType fn, hashEqualityType eq,
+ hashFreeKey freeKey
+#ifdef HTDATATYPE
+, hashFreeData freeData
+#endif
+)
+{
+ HASHTYPE ht;
+
+ ht = xmalloc(sizeof(*ht));
+ ht->numBuckets = numBuckets;
+ ht->buckets = xcalloc(numBuckets, sizeof(*ht->buckets));
+ ht->freeKey = freeKey;
+#ifdef HTDATATYPE
+ ht->freeData = freeData;
+ ht->dataCount = 0;
+#endif
+ ht->fn = fn;
+ ht->eq = eq;
+ ht->bucketCount = ht->keyCount = 0;
+ return ht;
+}
+
+static void HASHPREFIX(Resize)(HASHTYPE ht, int numBuckets) {
+ Bucket * buckets = xcalloc(numBuckets, sizeof(*ht->buckets));
+
+ for (int i=0; i<ht->numBuckets; i++) {
+ Bucket b = ht->buckets[i];
+ Bucket nextB;
+ while (b != NULL) {
+ unsigned int hash = ht->fn(b->key) % numBuckets;
+ nextB = b->next;
+ b->next = buckets[hash];
+ buckets[hash] = b;
+ b = nextB;
+ }
+ }
+ free(ht->buckets);
+ ht->buckets = buckets;
+ ht->numBuckets = numBuckets;
+}
+
+void HASHPREFIX(AddEntry)(HASHTYPE ht, HTKEYTYPE key
+#ifdef HTDATATYPE
+, HTDATATYPE data
+#endif
+)
+{
+ unsigned int hash;
+ Bucket b;
+ Bucket * b_addr;
+
+ hash = ht->fn(key) % ht->numBuckets;
+ b = ht->buckets[hash];
+ b_addr = ht->buckets + hash;
+
+ if (b == NULL) {
+ ht->bucketCount += 1;
+ }
+
+ while (b && ht->eq(b->key, key)) {
+ b_addr = &(b->next);
+ b = b->next;
+ }
+
+ if (b == NULL) {
+ ht->keyCount += 1;
+ b = xmalloc(sizeof(*b));
+ b->key = key;
+#ifdef HTDATATYPE
+ b->dataCount = 1;
+ b->data[0] = data;
+#endif
+ b->next = ht->buckets[hash];
+ ht->buckets[hash] = b;
+ }
+#ifdef HTDATATYPE
+ else {
+ // resizing bucket TODO: increase exponentially
+ // Bucket_s already contains space for one dataset
+ b = *b_addr = xrealloc(
+ b, sizeof(*b) + sizeof(b->data[0]) * (b->dataCount));
+ // though increasing dataCount after the resize
+ b->data[b->dataCount++] = data;
+ }
+ ht->dataCount += 1;
+#endif
+ if (ht->keyCount > ht->numBuckets) {
+ HASHPREFIX(Resize)(ht, ht->numBuckets * 2);
+ }
+}
+
+void HASHPREFIX(Empty)( HASHTYPE ht)
+{
+ Bucket b, n;
+ int i;
+
+ if (ht->bucketCount == 0) return;
+
+ for (i = 0; i < ht->numBuckets; i++) {
+ b = ht->buckets[i];
+ if (b == NULL)
+ continue;
+ ht->buckets[i] = NULL;
+
+ do {
+ n = b->next;
+ if (ht->freeKey)
+ b->key = ht->freeKey(b->key);
+#ifdef HTDATATYPE
+ if (ht->freeData) {
+ int j;
+ for (j=0; j < b->dataCount; j++ ) {
+ b->data[j] = ht->freeData(b->data[j]);
+ }
+ }
+#endif
+ b = _free(b);
+ } while ((b = n) != NULL);
+ }
+ ht->bucketCount = 0;
+ ht->keyCount = 0;
+#ifdef HTDATATYPE
+ ht->dataCount = 0;
+#endif
+}
+
+HASHTYPE HASHPREFIX(Free)(HASHTYPE ht)
+{
+ if (ht==NULL)
+ return ht;
+ HASHPREFIX(Empty)(ht);
+ ht->buckets = _free(ht->buckets);
+ ht = _free(ht);
+
+ return NULL;
+}
+
+int HASHPREFIX(HasEntry)(HASHTYPE ht, HTKEYTYPE key)
+{
+ Bucket b;
+
+ if (!(b = HASHPREFIX(findEntry)(ht, key))) return 0; else return 1;
+}
+
+int HASHPREFIX(GetEntry)(HASHTYPE ht, HTKEYTYPE key,
+#ifdef HTDATATYPE
+ HTDATATYPE** data, int * dataCount,
+#endif
+ HTKEYTYPE* tableKey)
+{
+ Bucket b;
+ int rc = ((b = HASHPREFIX(findEntry)(ht, key)) != NULL);
+
+#ifdef HTDATATYPE
+ if (data)
+ *data = rc ? b->data : NULL;
+ if (dataCount)
+ *dataCount = rc ? b->dataCount : 0;
+#endif
+ if (tableKey && rc)
+ *tableKey = b->key;
+
+ return rc;
+}
+
+unsigned int HASHPREFIX(NumBuckets)(HASHTYPE ht) {
+ return ht->numBuckets;
+}
+
+unsigned int HASHPREFIX(UsedBuckets)(HASHTYPE ht) {
+ return ht->bucketCount;
+}
+
+unsigned int HASHPREFIX(NumKeys)(HASHTYPE ht) {
+ return ht->keyCount;
+}
+
+#ifdef HTDATATYPE
+unsigned int HASHPREFIX(NumData)(HASHTYPE ht) {
+ return ht->dataCount;
+}
+#endif
+
+
+void HASHPREFIX(PrintStats)(HASHTYPE ht) {
+ int i;
+ Bucket bucket;
+
+ int hashcnt=0, bucketcnt=0, datacnt=0;
+ int maxbuckets=0;
+
+ for (i=0; i<ht->numBuckets; i++) {
+ int buckets = 0;
+ for (bucket=ht->buckets[i]; bucket; bucket=bucket->next){
+ buckets++;
+#ifdef HTDATATYPE
+ datacnt += bucket->dataCount;
+#endif
+ }
+ if (maxbuckets < buckets) maxbuckets = buckets;
+ if (buckets) hashcnt++;
+ bucketcnt += buckets;
+ }
+ fprintf(stderr, "Hashsize: %i\n", ht->numBuckets);
+ fprintf(stderr, "Hashbuckets: %i\n", hashcnt);
+ fprintf(stderr, "Keys: %i\n", bucketcnt);
+ fprintf(stderr, "Values: %i\n", datacnt);
+ fprintf(stderr, "Max Keys/Bucket: %i\n", maxbuckets);
+}
diff --git a/lib/rpmhash.H b/lib/rpmhash.H
new file mode 100644
index 0000000..e9db2ea
--- /dev/null
+++ b/lib/rpmhash.H
@@ -0,0 +1,145 @@
+/**
+ * \file lib/rpmhash.h
+ * Hash table implemenation.
+ */
+
+#include <string.h>
+// Hackery to make sure that macros get expanded
+#define __JOIN(a,b) a##b
+#define JOIN(a,b) __JOIN(a,b)
+#define HASHPREFIX(name) JOIN(HASHTYPE,name)
+#define HASHSTRUCT JOIN(HASHTYPE,_s)
+
+typedef struct HASHSTRUCT * HASHTYPE;
+
+/* function pointer types to deal with the datatypes the hash works with */
+
+#define hashFunctionType JOIN(HASHTYPE,HashFunctionType)
+#define hashEqualityType JOIN(HASHTYPE,HashEqualityType)
+#define hashFreeKey JOIN(HASHTYPE,FreeKey)
+
+typedef unsigned int (*hashFunctionType) (HTKEYTYPE string);
+typedef int (*hashEqualityType) (HTKEYTYPE key1, HTKEYTYPE key2);
+typedef HTKEYTYPE (*hashFreeKey) (HTKEYTYPE);
+
+#ifdef HTDATATYPE
+#define hashFreeData JOIN(HASHTYPE,FreeData)
+typedef HTDATATYPE (*hashFreeData) (HTDATATYPE);
+#endif
+
+/**
+ * Create hash table.
+ * If keySize > 0, the key is duplicated within the table (which costs
+ * memory, but may be useful anyway.
+ * @param numBuckets number of hash buckets
+ * @param fn function to generate hash value for key
+ * @param eq function to compare hash keys for equality
+ * @param freeKey function to free the keys or NULL
+ * @param freeData function to free the data or NULL
+ * @return pointer to initialized hash table
+ */
+RPM_GNUC_INTERNAL
+HASHTYPE HASHPREFIX(Create)(int numBuckets,
+ hashFunctionType fn, hashEqualityType eq,
+ hashFreeKey freeKey
+#ifdef HTDATATYPE
+, hashFreeData freeData
+#endif
+);
+
+/**
+ * Destroy hash table.
+ * @param ht pointer to hash table
+ * @return NULL always
+ */
+RPM_GNUC_INTERNAL
+HASHTYPE HASHPREFIX(Free)( HASHTYPE ht);
+
+/**
+ * Remove all entries from the hash table.
+ * @param ht pointer to hash table
+ */
+RPM_GNUC_INTERNAL
+void HASHPREFIX(Empty)(HASHTYPE ht);
+
+/**
+ * Add item to hash table.
+ * @param ht pointer to hash table
+ * @param key key
+ * @param data data value
+ */
+RPM_GNUC_INTERNAL
+void HASHPREFIX(AddEntry)(HASHTYPE ht, HTKEYTYPE key
+#ifdef HTDATATYPE
+, HTDATATYPE data
+#endif
+);
+
+/**
+ * Retrieve item from hash table.
+ * @param ht pointer to hash table
+ * @param key key value
+ * @retval data address to store data value from bucket
+ * @retval dataCount address to store data value size from bucket
+ * @retval tableKey address to store key value from bucket (may be NULL)
+ * @return 1 on success, 0 if the item is not found.
+ */
+RPM_GNUC_INTERNAL
+int HASHPREFIX(GetEntry)(HASHTYPE ht, HTKEYTYPE key,
+#ifdef HTDATATYPE
+ HTDATATYPE** data,
+ int * dataCount,
+#endif
+ HTKEYTYPE* tableKey);
+
+/**
+ * Check for key in hash table.
+ * @param ht pointer to hash table
+ * @param key key value
+ * @return 1 if the key is present, 0 otherwise
+ */
+RPM_GNUC_INTERNAL
+int HASHPREFIX(HasEntry)(HASHTYPE ht, HTKEYTYPE key);
+
+/**
+ * How many buckets are currently allocated (result is implementation
+ * dependent)
+ * @param ht pointer to hash table
+ * @result number of buckets allocated
+ */
+RPM_GNUC_INTERNAL
+unsigned int HASHPREFIX(NumBuckets)(HASHTYPE ht);
+
+/**
+ * How many buckets are used (result is implementation dependent)
+ * @param ht pointer to hash table
+ * @result number of buckets used
+ */
+RPM_GNUC_INTERNAL
+unsigned int HASHPREFIX(UsedBuckets)(HASHTYPE ht);
+
+/**
+ * How many (unique) keys have been added to the hash table
+ * @param ht pointer to hash table
+ * @result number of unique keys
+ */
+RPM_GNUC_INTERNAL
+unsigned int HASHPREFIX(NumKeys)(HASHTYPE ht);
+
+#ifdef HTDATATYPE
+/**
+ * How many data entries have been added to the hash table
+ * @param ht pointer to hash table
+ * @result number of data entries
+ */
+RPM_GNUC_INTERNAL
+unsigned int HASHPREFIX(NumData)(HASHTYPE ht);
+#endif
+
+/**
+ * Print statistics about the hash to stderr
+ * This is for debugging only
+ * @param ht pointer to hash table
+ */
+RPM_GNUC_INTERNAL
+void HASHPREFIX(PrintStats)(HASHTYPE ht);
diff --git a/lib/rpminstall.c b/lib/rpminstall.c
new file mode 100644
index 0000000..b4ffd10
--- /dev/null
+++ b/lib/rpminstall.c
@@ -0,0 +1,735 @@
+/** \ingroup rpmcli
+ * \file lib/rpminstall.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmcli.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmlib.h> /* rpmReadPackageFile, vercmp etc */
+#include <rpm/rpmdb.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmfileutil.h>
+
+#include "lib/rpmgi.h"
+#include "lib/manifest.h"
+#include "debug.h"
+
+int rpmcliPackagesTotal = 0;
+int rpmcliHashesCurrent = 0;
+int rpmcliHashesTotal = 0;
+int rpmcliProgressCurrent = 0;
+int rpmcliProgressTotal = 0;
+
+/**
+ * Print a CLI progress bar.
+ * @todo Unsnarl isatty(STDOUT_FILENO) from the control flow.
+ * @param amount current
+ * @param total final
+ */
+static void printHash(const rpm_loff_t amount, const rpm_loff_t total)
+{
+ int hashesNeeded;
+
+ rpmcliHashesTotal = (isatty (STDOUT_FILENO) ? 44 : 50);
+
+ if (rpmcliHashesCurrent != rpmcliHashesTotal) {
+ float pct = (total ? (((float) amount) / total) : 1.0);
+ hashesNeeded = (rpmcliHashesTotal * pct) + 0.5;
+ while (hashesNeeded > rpmcliHashesCurrent) {
+ if (isatty (STDOUT_FILENO)) {
+ int i;
+ for (i = 0; i < rpmcliHashesCurrent; i++)
+ (void) putchar ('#');
+ for (; i < rpmcliHashesTotal; i++)
+ (void) putchar (' ');
+ fprintf(stdout, "(%3d%%)", (int)((100 * pct) + 0.5));
+ for (i = 0; i < (rpmcliHashesTotal + 6); i++)
+ (void) putchar ('\b');
+ } else
+ fprintf(stdout, "#");
+
+ rpmcliHashesCurrent++;
+ }
+ (void) fflush(stdout);
+
+ if (rpmcliHashesCurrent == rpmcliHashesTotal) {
+ int i;
+ rpmcliProgressCurrent++;
+ if (isatty(STDOUT_FILENO)) {
+ for (i = 1; i < rpmcliHashesCurrent; i++)
+ (void) putchar ('#');
+ pct = (rpmcliProgressTotal
+ ? (((float) rpmcliProgressCurrent) / rpmcliProgressTotal)
+ : 1);
+ fprintf(stdout, " [%3d%%]", (int)((100 * pct) + 0.5));
+ }
+ fprintf(stdout, "\n");
+ }
+ (void) fflush(stdout);
+ }
+}
+
+static rpmVSFlags setvsFlags(struct rpmInstallArguments_s * ia)
+{
+ rpmVSFlags vsflags;
+
+ if (ia->installInterfaceFlags & (INSTALL_UPGRADE | INSTALL_ERASE))
+ vsflags = rpmExpandNumeric("%{?_vsflags_erase}");
+ else
+ vsflags = rpmExpandNumeric("%{?_vsflags_install}");
+
+ if (rpmcliQueryFlags & VERIFY_DIGEST)
+ vsflags |= _RPMVSF_NODIGESTS;
+ if (rpmcliQueryFlags & VERIFY_SIGNATURE)
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ if (rpmcliQueryFlags & VERIFY_HDRCHK)
+ vsflags |= RPMVSF_NOHDRCHK;
+
+ return vsflags;
+}
+
+void * rpmShowProgress(const void * arg,
+ const rpmCallbackType what,
+ const rpm_loff_t amount,
+ const rpm_loff_t total,
+ fnpyKey key,
+ void * data)
+{
+ Header h = (Header) arg;
+ char * s;
+ int flags = (int) ((long)data);
+ void * rc = NULL;
+ const char * filename = (const char *)key;
+ static FD_t fd = NULL;
+
+ switch (what) {
+ case RPMCALLBACK_INST_OPEN_FILE:
+ if (filename == NULL || filename[0] == '\0')
+ return NULL;
+ fd = Fopen(filename, "r.ufdio");
+ /* FIX: still necessary? */
+ if (fd == NULL || Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), filename,
+ Fstrerror(fd));
+ if (fd != NULL) {
+ Fclose(fd);
+ fd = NULL;
+ }
+ } else
+ fd = fdLink(fd);
+ return (void *)fd;
+ break;
+
+ case RPMCALLBACK_INST_CLOSE_FILE:
+ /* FIX: still necessary? */
+ fd = fdFree(fd);
+ if (fd != NULL) {
+ Fclose(fd);
+ fd = NULL;
+ }
+ break;
+
+ case RPMCALLBACK_INST_START:
+ rpmcliHashesCurrent = 0;
+ if (h == NULL || !(flags & INSTALL_LABEL))
+ break;
+ /* @todo Remove headerFormat() on a progress callback. */
+ if (flags & INSTALL_HASH) {
+ s = headerFormat(h, "%{NAME}", NULL);
+ if (isatty (STDOUT_FILENO))
+ fprintf(stdout, "%4d:%-23.23s", rpmcliProgressCurrent + 1, s);
+ else
+ fprintf(stdout, "%-28.28s", s);
+ (void) fflush(stdout);
+ s = _free(s);
+ } else {
+ s = headerFormat(h, "%{NAME}-%{VERSION}-%{RELEASE}", NULL);
+ fprintf(stdout, "%s\n", s);
+ (void) fflush(stdout);
+ s = _free(s);
+ }
+ break;
+
+ case RPMCALLBACK_TRANS_PROGRESS:
+ case RPMCALLBACK_INST_PROGRESS:
+ if (flags & INSTALL_PERCENT)
+ fprintf(stdout, "%%%% %f\n", (double) (total
+ ? ((((float) amount) / total) * 100)
+ : 100.0));
+ else if (flags & INSTALL_HASH)
+ printHash(amount, total);
+ (void) fflush(stdout);
+ break;
+
+ case RPMCALLBACK_TRANS_START:
+ rpmcliHashesCurrent = 0;
+ rpmcliProgressTotal = 1;
+ rpmcliProgressCurrent = 0;
+ if (!(flags & INSTALL_LABEL))
+ break;
+ if (flags & INSTALL_HASH)
+ fprintf(stdout, "%-28s", _("Preparing..."));
+ else
+ fprintf(stdout, "%s\n", _("Preparing packages for installation..."));
+ (void) fflush(stdout);
+ break;
+
+ case RPMCALLBACK_TRANS_STOP:
+ if (flags & INSTALL_HASH)
+ printHash(1, 1); /* Fixes "preparing..." progress bar */
+ rpmcliProgressTotal = rpmcliPackagesTotal;
+ rpmcliProgressCurrent = 0;
+ break;
+
+ case RPMCALLBACK_UNINST_PROGRESS:
+ break;
+ case RPMCALLBACK_UNINST_START:
+ break;
+ case RPMCALLBACK_UNINST_STOP:
+ break;
+ case RPMCALLBACK_UNPACK_ERROR:
+ break;
+ case RPMCALLBACK_CPIO_ERROR:
+ break;
+ case RPMCALLBACK_SCRIPT_ERROR:
+ break;
+ case RPMCALLBACK_UNKNOWN:
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static void setNotifyFlag(struct rpmInstallArguments_s * ia,
+ rpmts ts)
+{
+ int notifyFlags;
+
+ notifyFlags = ia->installInterfaceFlags | (rpmIsVerbose() ? INSTALL_LABEL : 0 );
+ rpmtsSetNotifyCallback(ts, rpmShowProgress, (void *) ((long)notifyFlags));
+}
+
+struct rpmEIU {
+ Header h;
+ int numFailed;
+ int numPkgs;
+ char ** pkgURL;
+ char ** fnp;
+ char * pkgState;
+ int prevx;
+ int pkgx;
+ int numRPMS;
+ int numSRPMS;
+ char ** sourceURL;
+ int isSource;
+ int argc;
+ char ** argv;
+ rpmRelocation * relocations;
+ rpmRC rpmrc;
+};
+
+static int rpmcliTransaction(rpmts ts, struct rpmInstallArguments_s * ia,
+ int numPackages)
+{
+ rpmps ps;
+
+ int rc = 0;
+ int stop = 0;
+
+ int eflags = ia->installInterfaceFlags & INSTALL_ERASE;
+
+ if (!(ia->installInterfaceFlags & INSTALL_NODEPS)) {
+
+ if (rpmtsCheck(ts)) {
+ rc = numPackages;
+ stop = 1;
+ }
+
+ ps = rpmtsProblems(ts);
+ if (!stop && rpmpsNumProblems(ps) > 0) {
+ rpmlog(RPMLOG_ERR, _("Failed dependencies:\n"));
+ rpmpsPrint(NULL, ps);
+ rc = numPackages;
+ stop = 1;
+ }
+ ps = rpmpsFree(ps);
+ }
+
+ if (!stop && !(ia->installInterfaceFlags & INSTALL_NOORDER)) {
+ if (rpmtsOrder(ts)) {
+ rc = numPackages;
+ stop = 1;
+ }
+ }
+
+ if (numPackages && !stop) {
+ rpmlog(RPMLOG_DEBUG, eflags ? "erasing packages\n" :
+ "installing binary packages\n");
+ rpmtsClean(ts);
+ rc = rpmtsRun(ts, NULL, ia->probFilter);
+
+ ps = rpmtsProblems(ts);
+
+ if ((rpmpsNumProblems(ps) > 0) && (eflags? 1 : (rc > 0)))
+ rpmpsPrint((eflags? NULL : stderr), ps);
+ ps = rpmpsFree(ps);
+ }
+
+ return rc;
+}
+
+static int tryReadManifest(struct rpmEIU * eiu)
+{
+ int rc;
+
+ /* Try to read a package manifest. */
+ FD_t fd = Fopen(*eiu->fnp, "r.fpio");
+ if (fd == NULL || Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), *eiu->fnp,
+ Fstrerror(fd));
+ if (fd != NULL) {
+ Fclose(fd);
+ fd = NULL;
+ }
+ eiu->numFailed++; *eiu->fnp = NULL;
+ return RPMRC_FAIL;
+ }
+
+ /* Read list of packages from manifest. */
+ rc = rpmReadPackageManifest(fd, &eiu->argc, &eiu->argv);
+ if (rc != RPMRC_OK)
+ rpmlog(RPMLOG_ERR, _("%s: not an rpm package (or package manifest): %s\n"),
+ *eiu->fnp, Fstrerror(fd));
+ Fclose(fd);
+ fd = NULL;
+
+ if (rc != RPMRC_OK)
+ eiu->numFailed++; *eiu->fnp = NULL;
+
+ return rc;
+}
+
+static int tryReadHeader(rpmts ts, struct rpmEIU * eiu, rpmVSFlags vsflags)
+{
+ rpmVSFlags tvsflags;
+
+ /* Try to read the header from a package file. */
+ FD_t fd = Fopen(*eiu->fnp, "r.ufdio");
+ if (fd == NULL || Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("open of %s failed: %s\n"), *eiu->fnp,
+ Fstrerror(fd));
+ if (fd != NULL) {
+ Fclose(fd);
+ fd = NULL;
+ }
+ eiu->numFailed++; *eiu->fnp = NULL;
+ return RPMRC_FAIL;
+ }
+
+ /* Read the header, verifying signatures (if present). */
+ tvsflags = rpmtsSetVSFlags(ts, vsflags);
+ eiu->rpmrc = rpmReadPackageFile(ts, fd, *eiu->fnp, &eiu->h);
+ tvsflags = rpmtsSetVSFlags(ts, tvsflags);
+ Fclose(fd);
+ fd = NULL;
+
+ /* Honor --nomanifest */
+ if (eiu->rpmrc == RPMRC_NOTFOUND && (giFlags & RPMGI_NOMANIFEST))
+ eiu->rpmrc = RPMRC_FAIL;
+
+ if(eiu->rpmrc == RPMRC_FAIL) {
+ rpmlog(RPMLOG_ERR, _("%s cannot be installed\n"), *eiu->fnp);
+ eiu->numFailed++; *eiu->fnp = NULL;
+ }
+
+ return RPMRC_OK;
+}
+
+
+/* On --freshen, verify package is installed and newer */
+static int checkFreshenStatus(rpmts ts, struct rpmEIU * eiu)
+{
+ rpmdbMatchIterator mi = NULL;
+ const char * name = headerGetString(eiu->h, RPMTAG_NAME);
+ const char *arch = headerGetString(eiu->h, RPMTAG_ARCH);
+ Header oldH = NULL;
+
+ if (name != NULL)
+ mi = rpmtsInitIterator(ts, RPMDBI_NAME, name, 0);
+ if (rpmtsColor(ts) && arch)
+ rpmdbSetIteratorRE(mi, RPMTAG_ARCH, RPMMIRE_DEFAULT, arch);
+
+ while ((oldH = rpmdbNextIterator(mi)) != NULL) {
+ /* Package is newer than those currently installed. */
+ if (rpmVersionCompare(oldH, eiu->h) < 0)
+ break;
+ }
+
+ mi = rpmdbFreeIterator(mi);
+ if (oldH == NULL) {
+ eiu->h = headerFree(eiu->h);
+ }
+ return (oldH != NULL);
+}
+
+/** @todo Generalize --freshen policies. */
+int rpmInstall(rpmts ts, struct rpmInstallArguments_s * ia, ARGV_t fileArgv)
+{
+ struct rpmEIU * eiu = xcalloc(1, sizeof(*eiu));
+ rpmRelocation * relocations;
+ char * fileURL = NULL;
+ rpmVSFlags vsflags, ovsflags;
+ int rc;
+ int i;
+
+ if (fileArgv == NULL) goto exit;
+
+ rpmcliPackagesTotal = 0;
+
+ (void) rpmtsSetFlags(ts, ia->transFlags);
+
+ relocations = ia->relocations;
+
+ vsflags = setvsFlags(ia);
+ ovsflags = rpmtsSetVSFlags(ts, (vsflags | RPMVSF_NEEDPAYLOAD));
+
+ setNotifyFlag(ia, ts);
+
+ if ((eiu->relocations = relocations) != NULL) {
+ while (eiu->relocations->oldPath)
+ eiu->relocations++;
+ if (eiu->relocations->newPath == NULL)
+ eiu->relocations = NULL;
+ }
+
+ /* Build fully globbed list of arguments in argv[argc]. */
+ for (eiu->fnp = fileArgv; *eiu->fnp != NULL; eiu->fnp++) {
+ ARGV_t av = NULL;
+ int ac = 0;
+ char * fn;
+
+ fn = rpmEscapeSpaces(*eiu->fnp);
+ rc = rpmGlob(fn, &ac, &av);
+ fn = _free(fn);
+ if (rc || ac == 0) {
+ rpmlog(RPMLOG_ERR, _("File not found by glob: %s\n"), *eiu->fnp);
+ eiu->numFailed++;
+ continue;
+ }
+
+ argvAppend(&(eiu->argv), av);
+ argvFree(av);
+ eiu->argc += ac;
+ }
+
+restart:
+ /* Allocate sufficient storage for next set of args. */
+ if (eiu->pkgx >= eiu->numPkgs) {
+ eiu->numPkgs = eiu->pkgx + eiu->argc;
+ eiu->pkgURL = xrealloc(eiu->pkgURL,
+ (eiu->numPkgs + 1) * sizeof(*eiu->pkgURL));
+ memset(eiu->pkgURL + eiu->pkgx, 0,
+ ((eiu->argc + 1) * sizeof(*eiu->pkgURL)));
+ eiu->pkgState = xrealloc(eiu->pkgState,
+ (eiu->numPkgs + 1) * sizeof(*eiu->pkgState));
+ memset(eiu->pkgState + eiu->pkgx, 0,
+ ((eiu->argc + 1) * sizeof(*eiu->pkgState)));
+ }
+
+ /* Retrieve next set of args, cache on local storage. */
+ for (i = 0; i < eiu->argc; i++) {
+ fileURL = _free(fileURL);
+ fileURL = eiu->argv[i];
+ eiu->argv[i] = NULL;
+
+ switch (urlIsURL(fileURL)) {
+ case URL_IS_HTTPS:
+ case URL_IS_HTTP:
+ case URL_IS_FTP:
+ { char *tfn = NULL;
+ FD_t tfd;
+
+ if (rpmIsVerbose())
+ fprintf(stdout, _("Retrieving %s\n"), fileURL);
+
+ tfd = rpmMkTempFile(rpmtsRootDir(ts), &tfn);
+ if (tfd && tfn) {
+ Fclose(tfd);
+ rc = urlGetFile(fileURL, tfn);
+ } else {
+ rc = -1;
+ }
+
+ if (rc != 0) {
+ rpmlog(RPMLOG_ERR,
+ _("skipping %s - transfer failed\n"), fileURL);
+ eiu->numFailed++;
+ eiu->pkgURL[eiu->pkgx] = NULL;
+ tfn = _free(tfn);
+ break;
+ }
+ eiu->pkgState[eiu->pkgx] = 1;
+ eiu->pkgURL[eiu->pkgx] = tfn;
+ eiu->pkgx++;
+ } break;
+ case URL_IS_PATH:
+ case URL_IS_DASH: /* WRONG WRONG WRONG */
+ case URL_IS_HKP: /* WRONG WRONG WRONG */
+ default:
+ eiu->pkgURL[eiu->pkgx] = fileURL;
+ fileURL = NULL;
+ eiu->pkgx++;
+ break;
+ }
+ }
+ fileURL = _free(fileURL);
+
+ if (eiu->numFailed) goto exit;
+
+ /* Continue processing file arguments, building transaction set. */
+ for (eiu->fnp = eiu->pkgURL+eiu->prevx;
+ *eiu->fnp != NULL;
+ eiu->fnp++, eiu->prevx++)
+ {
+ const char * fileName;
+
+ rpmlog(RPMLOG_DEBUG, "============== %s\n", *eiu->fnp);
+ (void) urlPath(*eiu->fnp, &fileName);
+
+ if (tryReadHeader(ts, eiu, vsflags) == RPMRC_FAIL)
+ continue;
+
+ if (eiu->rpmrc == RPMRC_NOTFOUND) {
+ rc = tryReadManifest(eiu);
+ if (rc == RPMRC_OK) {
+ eiu->prevx++;
+ goto restart;
+ }
+ }
+
+ eiu->isSource = headerIsSource(eiu->h);
+
+ if (eiu->isSource) {
+ rpmlog(RPMLOG_DEBUG, "\tadded source package [%d]\n",
+ eiu->numSRPMS);
+ eiu->sourceURL = xrealloc(eiu->sourceURL,
+ (eiu->numSRPMS + 2) * sizeof(*eiu->sourceURL));
+ eiu->sourceURL[eiu->numSRPMS] = *eiu->fnp;
+ *eiu->fnp = NULL;
+ eiu->numSRPMS++;
+ eiu->sourceURL[eiu->numSRPMS] = NULL;
+ continue;
+ }
+
+ if (eiu->relocations) {
+ struct rpmtd_s prefixes;
+
+ headerGet(eiu->h, RPMTAG_PREFIXES, &prefixes, HEADERGET_DEFAULT);
+ if (rpmtdCount(&prefixes) == 1) {
+ eiu->relocations->oldPath = xstrdup(rpmtdGetString(&prefixes));
+ rpmtdFreeData(&prefixes);
+ } else {
+ rpmlog(RPMLOG_ERR, _("package %s is not relocatable\n"),
+ headerGetString(eiu->h, RPMTAG_NAME));
+ eiu->numFailed++;
+ goto exit;
+ }
+ }
+
+ if (ia->installInterfaceFlags & INSTALL_FRESHEN)
+ if (checkFreshenStatus(ts, eiu) != 1)
+ continue;
+
+ rc = rpmtsAddInstallElement(ts, eiu->h, (fnpyKey)fileName,
+ (ia->installInterfaceFlags & INSTALL_UPGRADE) != 0,
+ relocations);
+
+ /* XXX reference held by transaction set */
+ eiu->h = headerFree(eiu->h);
+ if (eiu->relocations)
+ eiu->relocations->oldPath = _free(eiu->relocations->oldPath);
+
+ switch(rc) {
+ case 0:
+ rpmlog(RPMLOG_DEBUG, "\tadded binary package [%d]\n",
+ eiu->numRPMS);
+ break;
+ case 1:
+ rpmlog(RPMLOG_ERR,
+ _("error reading from file %s\n"), *eiu->fnp);
+ eiu->numFailed++;
+ goto exit;
+ break;
+ case 2:
+ rpmlog(RPMLOG_ERR,
+ _("file %s requires a newer version of RPM\n"),
+ *eiu->fnp);
+ eiu->numFailed++;
+ goto exit;
+ break;
+ default:
+ eiu->numFailed++;
+ goto exit;
+ break;
+ }
+
+ eiu->numRPMS++;
+ }
+
+ rpmlog(RPMLOG_DEBUG, "found %d source and %d binary packages\n",
+ eiu->numSRPMS, eiu->numRPMS);
+
+ if (eiu->numFailed) goto exit;
+
+ if (eiu->numRPMS) {
+ int rc = rpmcliTransaction(ts, ia, eiu->numPkgs);
+ if (rc < 0)
+ eiu->numFailed += eiu->numRPMS;
+ else if (rc > 0)
+ eiu->numFailed += rc;
+ }
+
+ if (eiu->numSRPMS && (eiu->sourceURL != NULL)) {
+ for (i = 0; i < eiu->numSRPMS; i++) {
+ rpmdbCheckSignals();
+ if (eiu->sourceURL[i] != NULL) {
+ rc = RPMRC_OK;
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_TEST))
+ rc = rpmInstallSource(ts, eiu->sourceURL[i], NULL, NULL);
+ if (rc != 0)
+ eiu->numFailed++;
+ }
+ }
+ }
+
+exit:
+ if (eiu->pkgURL != NULL) {
+ for (i = 0; i < eiu->numPkgs; i++) {
+ if (eiu->pkgURL[i] == NULL) continue;
+ if (eiu->pkgState[i] == 1)
+ (void) unlink(eiu->pkgURL[i]);
+ eiu->pkgURL[i] = _free(eiu->pkgURL[i]);
+ }
+ }
+ eiu->pkgState = _free(eiu->pkgState);
+ eiu->pkgURL = _free(eiu->pkgURL);
+ eiu->argv = _free(eiu->argv);
+ rc = eiu->numFailed;
+ free(eiu);
+
+ rpmtsEmpty(ts);
+
+ return rc;
+}
+
+int rpmErase(rpmts ts, struct rpmInstallArguments_s * ia, ARGV_const_t argv)
+{
+ char * const * arg;
+ char *qfmt = NULL;
+ int numFailed = 0;
+ int numPackages = 0;
+ rpmVSFlags vsflags, ovsflags;
+
+ if (argv == NULL) return 0;
+
+ vsflags = setvsFlags(ia);
+ ovsflags = rpmtsSetVSFlags(ts, vsflags);
+
+ (void) rpmtsSetFlags(ts, ia->transFlags);
+
+#ifdef NOTYET /* XXX no callbacks on erase yet */
+ setNotifyFlag(ia, ts);
+#endif
+
+ qfmt = rpmExpand("%{?_query_all_fmt}\n", NULL);
+ for (arg = argv; *arg; arg++) {
+ rpmdbMatchIterator mi;
+ int matches = 0;
+ int erasing = 1;
+
+ /* Iterator count isn't reliable with labels, count manually... */
+ mi = rpmtsInitIterator(ts, RPMDBI_LABEL, *arg, 0);
+ while (rpmdbNextIterator(mi) != NULL) {
+ matches++;
+ }
+ rpmdbFreeIterator(mi);
+
+ if (! matches) {
+ rpmlog(RPMLOG_ERR, _("package %s is not installed\n"), *arg);
+ numFailed++;
+ } else {
+ Header h; /* XXX iterator owns the reference */
+
+ if (matches > 1 &&
+ !(ia->installInterfaceFlags & UNINSTALL_ALLMATCHES)) {
+ rpmlog(RPMLOG_ERR, _("\"%s\" specifies multiple packages:\n"),
+ *arg);
+ numFailed++;
+ erasing = 0;
+ }
+
+ mi = rpmtsInitIterator(ts, RPMDBI_LABEL, *arg, 0);
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ if (erasing) {
+ (void) rpmtsAddEraseElement(ts, h, -1);
+ numPackages++;
+ } else {
+ char *nevra = headerFormat(h, qfmt, NULL);
+ rpmlog(RPMLOG_NOTICE, " %s", nevra);
+ free(nevra);
+ }
+ }
+ mi = rpmdbFreeIterator(mi);
+ }
+ }
+ free(qfmt);
+
+ if (numFailed) goto exit;
+ numFailed = rpmcliTransaction(ts, ia, numPackages);
+exit:
+ rpmtsEmpty(ts);
+
+ return numFailed;
+}
+
+int rpmInstallSource(rpmts ts, const char * arg,
+ char ** specFilePtr, char ** cookie)
+{
+ FD_t fd;
+ int rc;
+
+
+ fd = Fopen(arg, "r.ufdio");
+ if (fd == NULL || Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("cannot open %s: %s\n"), arg, Fstrerror(fd));
+ if (fd != NULL) (void) Fclose(fd);
+ return 1;
+ }
+
+ if (rpmIsVerbose() && specFilePtr != NULL)
+ fprintf(stdout, _("Installing %s\n"), arg);
+
+ {
+ rpmVSFlags ovsflags =
+ rpmtsSetVSFlags(ts, (specFilePtr) ? (rpmtsVSFlags(ts) | RPMVSF_NEEDPAYLOAD) : rpmtsVSFlags(ts));
+ rpmRC rpmrc = rpmInstallSourcePackage(ts, fd, specFilePtr, cookie);
+ rc = (rpmrc == RPMRC_OK ? 0 : 1);
+ ovsflags = rpmtsSetVSFlags(ts, ovsflags);
+ }
+ if (rc != 0) {
+ rpmlog(RPMLOG_ERR, _("%s cannot be installed\n"), arg);
+ if (specFilePtr && *specFilePtr)
+ *specFilePtr = _free(*specFilePtr);
+ if (cookie && *cookie)
+ *cookie = _free(*cookie);
+ }
+
+ (void) Fclose(fd);
+
+ return rc;
+}
+
diff --git a/lib/rpmlead.c b/lib/rpmlead.c
new file mode 100644
index 0000000..d41a413
--- /dev/null
+++ b/lib/rpmlead.c
@@ -0,0 +1,141 @@
+/** \ingroup lead
+ * \file lib/rpmlead.c
+ */
+
+#include "system.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+
+#include <rpm/rpmlib.h> /* rpmGetOs/ArchInfo() */
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+
+#include "lib/signature.h"
+#include "lib/rpmlead.h"
+
+#include "debug.h"
+
+static unsigned char const lead_magic[] = {
+ RPMLEAD_MAGIC0, RPMLEAD_MAGIC1, RPMLEAD_MAGIC2, RPMLEAD_MAGIC3
+};
+
+/** \ingroup lead
+ * The lead data structure.
+ * The lead needs to be 8 byte aligned.
+ * @deprecated The lead (except for signature_type) is legacy.
+ * @todo Don't use any information from lead.
+ */
+struct rpmlead_s {
+ unsigned char magic[4];
+ unsigned char major;
+ unsigned char minor;
+ short type;
+ short archnum;
+ char name[66];
+ short osnum;
+ short signature_type; /*!< Signature header type (RPMSIG_HEADERSIG) */
+ char reserved[16]; /*!< Pad to 96 bytes -- 8 byte aligned! */
+};
+
+rpmlead rpmLeadNew(void)
+{
+ int archnum, osnum;
+ rpmlead l = xcalloc(1, sizeof(*l));
+
+ rpmGetArchInfo(NULL, &archnum);
+ rpmGetOsInfo(NULL, &osnum);
+
+ l->major = 3;
+ l->minor = 0;
+ l->archnum = archnum;
+ l->osnum = osnum;
+ l->signature_type = RPMSIGTYPE_HEADERSIG;
+ return l;
+}
+
+rpmlead rpmLeadFromHeader(Header h)
+{
+ char * nevr;
+ assert(h != NULL);
+ rpmlead l = rpmLeadNew();
+
+ l->type = (headerIsSource(h) ? 1 : 0);
+ nevr = headerGetAsString(h, RPMTAG_NEVR);
+ rstrlcpy(l->name, nevr, sizeof(l->name));
+ free(nevr);
+
+ return l;
+}
+
+rpmlead rpmLeadFree(rpmlead lead)
+{
+ assert(lead != NULL);
+ free(lead);
+ return NULL;
+}
+
+/* The lead needs to be 8 byte aligned */
+rpmRC rpmLeadWrite(FD_t fd, rpmlead lead)
+{
+ struct rpmlead_s l;
+ assert(lead != NULL);
+
+ memcpy(&l, lead, sizeof(l));
+
+ memcpy(&l.magic, lead_magic, sizeof(l.magic));
+ l.type = htons(lead->type);
+ l.archnum = htons(lead->archnum);
+ l.osnum = htons(lead->osnum);
+ l.signature_type = htons(lead->signature_type);
+
+ if (Fwrite(&l, 1, sizeof(l), fd) != sizeof(l))
+ return RPMRC_FAIL;
+
+ return RPMRC_OK;
+}
+
+rpmRC rpmLeadCheck(rpmlead lead, const char **msg)
+{
+ if (memcmp(lead->magic, lead_magic, sizeof(lead_magic))) {
+ if (msg) *msg = _("not an rpm package");
+ return RPMRC_NOTFOUND;
+ }
+ if (lead->signature_type != RPMSIGTYPE_HEADERSIG) {
+ if (msg) *msg = _("illegal signature type");
+ return RPMRC_FAIL;
+ }
+ if (lead->major < 3 || lead->major > 4) {
+ if (msg) *msg = _("unsupported RPM package version");
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+rpmRC rpmLeadRead(FD_t fd, rpmlead lead)
+{
+ assert(lead != NULL);
+ memset(lead, 0, sizeof(*lead));
+ /* FIX: remove timed read */
+ if (timedRead(fd, (char *)lead, sizeof(*lead)) != sizeof(*lead)) {
+ if (Ferror(fd)) {
+ rpmlog(RPMLOG_ERR, _("read failed: %s (%d)\n"),
+ Fstrerror(fd), errno);
+ return RPMRC_FAIL;
+ } else {
+ rpmlog(RPMLOG_ERR, _("not an rpm package\n"));
+ return RPMRC_NOTFOUND;
+ }
+ }
+ lead->type = ntohs(lead->type);
+ lead->archnum = ntohs(lead->archnum);
+ lead->osnum = ntohs(lead->osnum);
+ lead->signature_type = ntohs(lead->signature_type);
+
+ return RPMRC_OK;
+}
+
+int rpmLeadType(rpmlead lead)
+{
+ return lead ? lead->type : -1;
+}
diff --git a/lib/rpmlead.h b/lib/rpmlead.h
new file mode 100644
index 0000000..768ea28
--- /dev/null
+++ b/lib/rpmlead.h
@@ -0,0 +1,82 @@
+#ifndef _H_RPMLEAD
+#define _H_RPMLEAD
+
+/** \ingroup lead
+ * \file lib/rpmlead.h
+ * Routines to read and write an rpm lead structure for a a package.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RPMLEAD_BINARY 0
+#define RPMLEAD_SOURCE 1
+
+#define RPMLEAD_MAGIC0 0xed
+#define RPMLEAD_MAGIC1 0xab
+#define RPMLEAD_MAGIC2 0xee
+#define RPMLEAD_MAGIC3 0xdb
+
+#define RPMLEAD_SIZE 96 /*!< Don't rely on sizeof(struct) */
+
+typedef struct rpmlead_s * rpmlead;
+
+
+/** \ingroup lead
+ * Initialize a lead structure
+ * @return Pointer to empty lead structure
+ */
+rpmlead rpmLeadNew(void);
+
+/** \ingroup lead
+ * Initialize a lead structure from header
+ * param h Header
+ * @return Pointer to populated lead structure (malloced)
+ */
+rpmlead rpmLeadFromHeader(Header h);
+
+/** \ingroup lead
+ * Free a lead structure
+ * @param lead Pointer to lead structure
+ * @return NULL always
+ */
+rpmlead rpmLeadFree(rpmlead lead);
+
+/** \ingroup lead
+ * Write lead to file handle.
+ * @param fd file handle
+ * @param lead package lead
+ * @return RPMRC_OK on success, RPMRC_FAIL on error
+ */
+rpmRC rpmLeadWrite(FD_t fd, rpmlead lead);
+
+/** \ingroup lead
+ * Read lead from file handle.
+ * @param fd file handle
+ * @retval lead package lead
+ * @return RPMRC_OK on success, RPMRC_FAIL/RPMRC_NOTFOUND on error
+ */
+rpmRC rpmLeadRead(FD_t fd, rpmlead lead);
+
+/** \ingroup lead
+ * Check lead for compatibility.
+ * @param lead Pointer to lead handle
+ * @retval fn Pointer to error message, NULL on success
+ * @return RPMRC_OK on success,
+ * RPMRC_NOTFOUND if not an rpm,
+ * RPMRC_FAIL on invalid/incompatible rpm
+ */
+rpmRC rpmLeadCheck(rpmlead lead, const char **msg);
+
+/** \ingroup lead
+ * Returen type (source vs binary) of lead
+ * @param lead Pointer to lead handle
+ * @return RPMLEAD_BINARY or RPMLEAD_SOURCE, -1 on invalid
+ */
+int rpmLeadType(rpmlead lead);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _H_RPMLEAD */
diff --git a/lib/rpmlegacy.h b/lib/rpmlegacy.h
new file mode 100644
index 0000000..2f9e0a0
--- /dev/null
+++ b/lib/rpmlegacy.h
@@ -0,0 +1,241 @@
+#ifndef _RPMLEGACY_H
+#define _RPMLEGACY_H
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmutil.h>
+
+/* ==================================================================== */
+/* LEGACY INTERFACES AND TYPES, DO NOT USE IN NEW CODE! */
+/* ==================================================================== */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _RPM_4_4_COMPAT
+
+/* mappings for legacy types */
+typedef int32_t int_32 RPM_GNUC_DEPRECATED;
+typedef int16_t int_16 RPM_GNUC_DEPRECATED;
+typedef int8_t int_8 RPM_GNUC_DEPRECATED;
+typedef uint32_t uint_32 RPM_GNUC_DEPRECATED;
+typedef uint16_t uint_16 RPM_GNUC_DEPRECATED;
+typedef uint8_t uint_8 RPM_GNUC_DEPRECATED;
+
+typedef rpm_tag_t * hTAG_t RPM_GNUC_DEPRECATED;
+typedef rpm_tagtype_t * hTYP_t RPM_GNUC_DEPRECATED;
+typedef const void * hPTR_t RPM_GNUC_DEPRECATED;
+typedef rpm_count_t * hCNT_t RPM_GNUC_DEPRECATED;
+
+typedef rpmSpec Spec RPM_GNUC_DEPRECATED;
+
+/* legacy header interfaces */
+
+/** \ingroup header_legacy
+ * Retrieve tag value.
+ * Will never return RPM_I18NSTRING_TYPE! RPM_STRING_TYPE elements with
+ * RPM_I18NSTRING_TYPE equivalent entries are translated (if HEADER_I18NTABLE
+ * entry is present).
+ * @deprecated Use headerGet() instead
+ *
+ * @param h header
+ * @param tag tag
+ * @retval *type tag value data type (or NULL)
+ * @retval *p pointer to tag value(s) (or NULL)
+ * @retval *c number of values (or NULL)
+ * @return 1 on success, 0 on failure
+ */
+int headerGetEntry(Header h, rpm_tag_t tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Retrieve tag value using header internal array.
+ * Get an entry using as little extra RAM as possible to return the tag value.
+ * This is only an issue for RPM_STRING_ARRAY_TYPE.
+ * @deprecated Use headerGet() instead
+ *
+ * @param h header
+ * @param tag tag
+ * @retval *type tag value data type (or NULL)
+ * @retval *p pointer to tag value(s) (or NULL)
+ * @retval *c number of values (or NULL)
+ * @return 1 on success, 0 on failure
+ */
+int headerGetEntryMinMemory(Header h, rpm_tag_t tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Add tag to header.
+ * Duplicate tags are okay, but only defined for iteration (with the
+ * exceptions noted below). While you are allowed to add i18n string
+ * arrays through this function, you probably don't mean to. See
+ * headerAddI18NString() instead.
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerAddEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Append element to tag array in header.
+ * Appends item p to entry w/ tag and type as passed. Won't work on
+ * RPM_STRING_TYPE. Any pointers into header memory returned from
+ * headerGetEntryMinMemory() for this entry are invalid after this
+ * call has been made!
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerAppendEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Add or append element to tag array in header.
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerAddOrAppendEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Modify tag in header.
+ * If there are multiple entries with this tag, the first one gets replaced.
+ * @deprecated Use headerMod() instead
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerModifyEntry(Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Delete tag in header.
+ * Removes all entries of type tag from the header, returns 1 if none were
+ * found.
+ * @deprecated Use headerDel() instead
+ *
+ * @param h header
+ * @param tag tag
+ * @return 0 on success, 1 on failure (INCONSISTENT)
+ */
+int headerRemoveEntry(Header h, rpm_tag_t tag) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Return formatted output string from header tags.
+ * The returned string must be free()d.
+ * @deprecated Use headerFormat() instead
+ *
+ * @param _h header
+ * @param _fmt format to use
+ * @param _tbltags array of tag name/value pairs (unused)
+ * @param _exts chained table of formatting extensions. (unused)
+ * @retval _emsg error message (if any)
+ * @return formatted output string (malloc'ed)
+ */
+#define headerSprintf(_h, _fmt, _tbltags, _exts, _emsg) \
+ headerFormat((_h), (_fmt), (_emsg))
+
+/** \ingroup header_legacy
+ * Return next tag from header.
+ * @deprecated Use headerNext() instead.
+ *
+ * @param hi header tag iterator
+ * @retval *tag tag
+ * @retval *type tag value data type
+ * @retval *p pointer to tag value(s)
+ * @retval *c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerNextIterator(HeaderIterator hi,
+ rpm_tag_t * tag,
+ rpm_tagtype_t * type,
+ rpm_data_t * p,
+ rpm_count_t * c) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Free data allocated when retrieved from header.
+ * @deprecated Use rpmtdFreeData() instead
+ *
+ * @param h header
+ * @param data pointer to tag value(s)
+ * @param type type of data (or -1 to force free)
+ * @return NULL always
+ */
+void * headerFreeTag(Header h, rpm_data_t data, rpm_tagtype_t type) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Free data allocated when retrieved from header.
+ * @deprecated Use rpmtdFreeData() instead.
+ *
+ * @param data address of data (or NULL)
+ * @param type type of data (or RPM_FORCEFREE_TYPE to force free)
+ * @return NULL always
+ */
+void * headerFreeData(rpm_data_t data, rpm_tagtype_t type) RPM_GNUC_DEPRECATED;
+
+/** \ingroup header_legacy
+ * Prototypes for headerGetEntry(), headerFreeData() etc vectors.
+ * @{
+ */
+typedef void * (*HFD_t) (rpm_data_t data, rpm_tagtype_t type) RPM_GNUC_DEPRECATED;
+typedef int (*HGE_t) (Header h, rpm_tag_t tag, rpm_tagtype_t * type,
+ rpm_data_t * p, rpm_count_t * c) RPM_GNUC_DEPRECATED;
+typedef int (*HAE_t) (Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c) RPM_GNUC_DEPRECATED;
+typedef int (*HME_t) (Header h, rpm_tag_t tag, rpm_tagtype_t type,
+ rpm_constdata_t p, rpm_count_t c) RPM_GNUC_DEPRECATED;
+typedef int (*HRE_t) (Header h, rpm_tag_t tag) RPM_GNUC_DEPRECATED;
+/** @} */
+
+/* other misc renamed / namespaced functions */
+/* TODO: arrange deprecation warnings on these too... */
+#define isCompressed rpmFileIsCompressed
+#define makeTempFile rpmMkTempFile
+#define whatis rpmfiWhatis
+#define tagName rpmTagGetName
+#define tagType rpmTagGetType
+#define tagValue rpmTagGetValue
+
+#define xislower rislower
+#define xisupper risupper
+#define xisalpha risalpha
+#define xisdigit risdigit
+#define xisalnum risalnum
+#define xisblank risblank
+#define xisspace risspace
+#define xtolower rtolower
+#define xtoupper rtoupper
+#define xstrcasecmp rstrcasecmp
+#define xstrncasecmp rstrncasecmp
+
+#define rpmMessage rpmlog
+#define rpmError rpmlog
+
+#endif /* _RPM_4_4_COMPAT */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMLEGACY_H */
diff --git a/lib/rpmlib.h b/lib/rpmlib.h
new file mode 100644
index 0000000..6ccdeb7
--- /dev/null
+++ b/lib/rpmlib.h
@@ -0,0 +1,210 @@
+#ifndef H_RPMLIB
+#define H_RPMLIB
+
+/** \ingroup rpmcli rpmrc rpmdep rpmtrans rpmdb lead signature header payload dbi
+ * \file lib/rpmlib.h
+ *
+ * In Memoriam: Steve Taylor <staylor@redhat.com> was here, now he's not.
+ *
+ */
+
+#include <popt.h>
+
+#include <rpm/rpmio.h>
+#include <rpm/header.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmds.h> /* XXX move rpmlib provides to rpmds instead */
+#include <rpm/rpmpgp.h>
+#ifdef _RPM_4_4_COMPAT
+#include <rpm/rpmlegacy.h> /* legacy compat definitions if enabled */
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern struct rpmMacroContext_s * rpmGlobalMacroContext;
+
+extern struct rpmMacroContext_s * rpmCLIMacroContext;
+
+extern const char * const RPMVERSION;
+
+extern const char * const rpmNAME;
+
+extern const char * const rpmEVR;
+
+extern const int rpmFLAGS;
+
+/** \ingroup header
+ * Translate and merge legacy signature tags into header.
+ * @todo Remove headerSort() through headerInitIterator() modifies sig.
+ * @param h header
+ * @param sigh signature header
+ */
+void headerMergeLegacySigs(Header h, const Header sigh);
+
+/** \ingroup header
+ * Regenerate signature header.
+ * @todo Remove headerSort() through headerInitIterator() modifies h.
+ * @param h header
+ * @param noArchiveSize don't copy archive size tag (pre rpm-4.1)
+ * @return regenerated signature header
+ */
+Header headerRegenSigHeader(const Header h, int noArchiveSize);
+
+/* ==================================================================== */
+/** \name RPMRC */
+
+/** \ingroup rpmrc
+ * Build and install arch/os table identifiers.
+ * @todo Eliminate from API.
+ */
+enum rpm_machtable_e {
+ RPM_MACHTABLE_INSTARCH = 0, /*!< Install platform architecture. */
+ RPM_MACHTABLE_INSTOS = 1, /*!< Install platform operating system. */
+ RPM_MACHTABLE_BUILDARCH = 2, /*!< Build platform architecture. */
+ RPM_MACHTABLE_BUILDOS = 3 /*!< Build platform operating system. */
+};
+#define RPM_MACHTABLE_COUNT 4 /*!< No. of arch/os tables. */
+
+/** \ingroup rpmrc
+ * Read macro configuration file(s) for a target.
+ * @param file colon separated files to read (NULL uses default)
+ * @param target target platform (NULL uses default)
+ * @return 0 on success, -1 on error
+ */
+int rpmReadConfigFiles(const char * file,
+ const char * target);
+
+/** \ingroup rpmrc
+ * Return current arch name and/or number.
+ * @todo Generalize to extract arch component from target_platform macro.
+ * @retval name address of arch name (or NULL)
+ * @retval num address of arch number (or NULL)
+ */
+void rpmGetArchInfo( const char ** name,
+ int * num);
+
+/** \ingroup rpmrc
+ * Return current os name and/or number.
+ * @todo Generalize to extract os component from target_platform macro.
+ * @retval name address of os name (or NULL)
+ * @retval num address of os number (or NULL)
+ */
+void rpmGetOsInfo( const char ** name,
+ int * num);
+
+/** \ingroup rpmrc
+ * Return arch/os score of a name.
+ * An arch/os score measures the "nearness" of a name to the currently
+ * running (or defined) platform arch/os. For example, the score of arch
+ * "i586" on an i686 platform is (usually) 2. The arch/os score is used
+ * to select one of several otherwise identical packages using the arch/os
+ * tags from the header as hints of the intended platform for the package.
+ * @todo Rewrite to use RE's against config.guess target platform output.
+ *
+ * @param type any of the RPM_MACHTABLE_* constants
+ * @param name name
+ * @return arch score (0 is no match, lower is preferred)
+ */
+int rpmMachineScore(int type, const char * name);
+
+/** \ingroup rpmrc
+ * Display current rpmrc (and macro) configuration.
+ * @param fp output file handle
+ * @return 0 always
+ */
+int rpmShowRC(FILE * fp);
+
+/** \ingroup rpmrc
+ * @deprecated Use addMacro to set _target_* macros.
+ * @todo Eliminate from API.
+ # @note Only used by build code.
+ * @param archTable
+ * @param osTable
+ */
+void rpmSetTables(int archTable, int osTable);
+
+/** \ingroup rpmrc
+ * Destroy rpmrc arch/os compatibility tables.
+ * @todo Eliminate from API.
+ */
+void rpmFreeRpmrc(void);
+
+/**
+ * Compare headers to determine which header is "newer".
+ * @param first 1st header
+ * @param second 2nd header
+ * @return result of comparison
+ */
+int rpmVersionCompare(Header first, Header second);
+
+/** \ingroup header
+ * Check for supported payload format in header.
+ * @param h header to check
+ * @return RPMRC_OK if supported, RPMRC_FAIL otherwise
+ */
+rpmRC headerCheckPayloadFormat(Header h);
+
+/** \ingroup header
+ * Check header consistency, performing headerGetEntry() the hard way.
+ *
+ * Sanity checks on the header are performed while looking for a
+ * header-only digest or signature to verify the blob. If found,
+ * the digest or signature is verified.
+ *
+ * @param ts transaction set
+ * @param uh unloaded header blob
+ * @param uc no. of bytes in blob (or 0 to disable)
+ * @retval *msg verification error message (or NULL)
+ * @return RPMRC_OK on success
+ */
+rpmRC headerCheck(rpmts ts, const void * uh, size_t uc, char ** msg);
+
+/** \ingroup header
+ * Return checked and loaded header.
+ * @param ts transaction set
+ * @param fd file handle
+ * @retval hdrp address of header (or NULL)
+ * @retval *msg verification error message (or NULL)
+ * @return RPMRC_OK on success
+ */
+rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg);
+
+/** \ingroup header
+ * Return package header from file handle, verifying digests/signatures.
+ * @param ts transaction set
+ * @param fd file handle
+ * @param fn file name
+ * @retval hdrp address of header (or NULL)
+ * @return RPMRC_OK on success
+ */
+rpmRC rpmReadPackageFile(rpmts ts, FD_t fd,
+ const char * fn, Header * hdrp);
+
+/** \ingroup rpmtrans
+ * Install source package.
+ * @param ts transaction set
+ * @param fd file handle
+ * @retval specFilePtr address of spec file name (or NULL)
+ * @retval cookie address of cookie pointer (or NULL)
+ * @return rpmRC return code
+ */
+rpmRC rpmInstallSourcePackage(rpmts ts, FD_t fd,
+ char ** specFilePtr,
+ char ** cookie);
+
+/** \ingroup rpmtrans
+ * Segmented string compare for version or release strings.
+ *
+ * @param a 1st string
+ * @param b 2nd string
+ * @return +1 if a is "newer", 0 if equal, -1 if b is "newer"
+ */
+int rpmvercmp(const char * a, const char * b);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMLIB */
diff --git a/lib/rpmliblua.c b/lib/rpmliblua.c
new file mode 100644
index 0000000..046ed31
--- /dev/null
+++ b/lib/rpmliblua.c
@@ -0,0 +1,45 @@
+#include "system.h"
+
+#ifdef WITH_LUA
+#include <lua.h>
+#include <lauxlib.h>
+#include <rpm/rpmlib.h>
+
+#define _RPMLUA_INTERNAL
+#include "rpmio/rpmlua.h"
+#include "lib/rpmliblua.h"
+
+static int rpm_vercmp(lua_State *L)
+{
+ const char *v1, *v2;
+ int rc = 0;
+
+ v1 = luaL_checkstring(L, 1);
+ v2 = luaL_checkstring(L, 2);
+ if (v1 && v2) {
+ lua_pushinteger(L, rpmvercmp(v1, v2));
+ rc = 1;
+ }
+ return rc;
+}
+
+static const luaL_reg luarpmlib_f[] = {
+ {"vercmp", rpm_vercmp},
+ {NULL, NULL}
+};
+
+void rpmLuaInit(void)
+{
+ rpmlua lua = rpmluaGetGlobalState();
+ lua_pushvalue(lua->L, LUA_GLOBALSINDEX);
+ luaL_register(lua->L, "rpm", luarpmlib_f);
+ return;
+}
+
+void rpmLuaFree(void)
+{
+ rpmlua lua = rpmluaGetGlobalState();
+ rpmluaFree(lua);
+}
+
+#endif /* WITH_LUA */
diff --git a/lib/rpmliblua.h b/lib/rpmliblua.h
new file mode 100644
index 0000000..730503f
--- /dev/null
+++ b/lib/rpmliblua.h
@@ -0,0 +1,18 @@
+#ifndef _RPMLIBLUA_H
+#define _RPMLIBLUA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Initialize Lua subsystem & register all our extensions */
+void rpmLuaInit(void);
+
+/* Shutdown Lua subsystem */
+void rpmLuaFree(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMLIBLUA_H */
diff --git a/lib/rpmlock.c b/lib/rpmlock.c
new file mode 100644
index 0000000..cf9947e
--- /dev/null
+++ b/lib/rpmlock.c
@@ -0,0 +1,125 @@
+
+#include "system.h"
+
+#include <errno.h>
+
+#include <rpm/rpmlog.h>
+#include <rpm/rpmfileutil.h>
+
+#include "lib/rpmlock.h"
+
+#include "debug.h"
+
+/* Internal interface */
+
+enum {
+ RPMLOCK_READ = 1 << 0,
+ RPMLOCK_WRITE = 1 << 1,
+ RPMLOCK_WAIT = 1 << 2,
+};
+
+struct rpmlock_s {
+ int fd;
+ int openmode;
+};
+
+static rpmlock rpmlock_new(const char *lock_path)
+{
+ rpmlock lock = (rpmlock) malloc(sizeof(*lock));
+
+ if (lock != NULL) {
+ mode_t oldmask = umask(022);
+ lock->fd = open(lock_path, O_RDWR|O_CREAT, 0644);
+ (void) umask(oldmask);
+
+ if (lock->fd == -1) {
+ lock->fd = open(lock_path, O_RDONLY);
+ if (lock->fd == -1) {
+ free(lock);
+ lock = NULL;
+ } else {
+ lock->openmode = RPMLOCK_READ;
+ }
+ } else {
+ lock->openmode = RPMLOCK_WRITE | RPMLOCK_READ;
+ }
+ }
+ return lock;
+}
+
+static void rpmlock_free(rpmlock lock)
+{
+ if (lock) {
+ (void) close(lock->fd);
+ free(lock);
+ }
+}
+
+static int rpmlock_acquire(rpmlock lock, int mode)
+{
+ int res = 0;
+ if (lock && (mode & lock->openmode)) {
+ struct flock info;
+ int cmd;
+ if (mode & RPMLOCK_WAIT)
+ cmd = F_SETLKW;
+ else
+ cmd = F_SETLK;
+ if (mode & RPMLOCK_READ)
+ info.l_type = F_RDLCK;
+ else
+ info.l_type = F_WRLCK;
+ info.l_whence = SEEK_SET;
+ info.l_start = 0;
+ info.l_len = 0;
+ info.l_pid = 0;
+ if (fcntl(lock->fd, cmd, &info) != -1)
+ res = 1;
+ }
+ return res;
+}
+
+static void rpmlock_release(rpmlock lock)
+{
+ if (lock) {
+ struct flock info;
+ info.l_type = F_UNLCK;
+ info.l_whence = SEEK_SET;
+ info.l_start = 0;
+ info.l_len = 0;
+ info.l_pid = 0;
+ (void) fcntl(lock->fd, F_SETLK, &info);
+ }
+}
+
+
+/* External interface */
+
+rpmlock rpmlockAcquire(const char *lock_path, const char *descr)
+{
+ rpmlock lock = rpmlock_new(lock_path);
+ if (!lock) {
+ rpmlog(RPMLOG_ERR, _("can't create %s lock on %s (%s)\n"),
+ descr, lock_path, strerror(errno));
+ } else if (!rpmlock_acquire(lock, RPMLOCK_WRITE)) {
+ if (lock->openmode & RPMLOCK_WRITE)
+ rpmlog(RPMLOG_WARNING, _("waiting for %s lock on %s\n"),
+ descr, lock_path);
+ if (!rpmlock_acquire(lock, RPMLOCK_WRITE|RPMLOCK_WAIT)) {
+ rpmlog(RPMLOG_ERR, _("can't create %s lock on %s (%s)\n"),
+ descr, lock_path, strerror(errno));
+ rpmlock_free(lock);
+ lock = NULL;
+ }
+ }
+ return lock;
+}
+
+rpmlock rpmlockFree(rpmlock lock)
+{
+ rpmlock_release(lock); /* Not really needed here. */
+ rpmlock_free(lock);
+ return NULL;
+}
+
+
diff --git a/lib/rpmlock.h b/lib/rpmlock.h
new file mode 100644
index 0000000..aa85451
--- /dev/null
+++ b/lib/rpmlock.h
@@ -0,0 +1,22 @@
+#ifndef RPMLOCK_H
+#define RPMLOCK_H
+
+#include <rpm/rpmutil.h>
+
+typedef struct rpmlock_s * rpmlock;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+RPM_GNUC_INTERNAL
+rpmlock rpmlockAcquire(const char *lock_path, const char *descr);
+
+RPM_GNUC_INTERNAL
+rpmlock rpmlockFree(rpmlock lock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
new file mode 100644
index 0000000..e34019a
--- /dev/null
+++ b/lib/rpmplugins.c
@@ -0,0 +1,198 @@
+
+#include "system.h"
+
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmts.h>
+
+#include "lib/rpmplugins.h"
+
+#define STR1(x) #x
+#define STR(x) STR1(x)
+
+struct rpmPlugins_s {
+ void **handles;
+ ARGV_t names;
+ int count;
+ rpmts ts;
+};
+
+static int rpmpluginsGetPluginIndex(rpmPlugins plugins, const char *name)
+{
+ int i;
+ for (i = 0; i < plugins->count; i++) {
+ if (rstreq(plugins->names[i], name)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static int rpmpluginsHookIsSupported(void *handle, rpmPluginHook hook)
+{
+ rpmPluginHook *supportedHooks =
+ (rpmPluginHook *) dlsym(handle, STR(PLUGIN_HOOKS));
+ return (*supportedHooks & hook);
+}
+
+int rpmpluginsPluginAdded(rpmPlugins plugins, const char *name)
+{
+ return (rpmpluginsGetPluginIndex(plugins, name) >= 0);
+}
+
+rpmPlugins rpmpluginsNew(rpmts ts)
+{
+ rpmPlugins plugins = xcalloc(1, sizeof(*plugins));
+ plugins->ts = ts;
+ return plugins;
+}
+
+rpmRC rpmpluginsAdd(rpmPlugins plugins, const char *name, const char *path,
+ const char *opts)
+{
+ rpmPluginHook *supportedHooks;
+ char *error;
+
+ void *handle = dlopen(path, RTLD_LAZY);
+ if (!handle) {
+ rpmlog(RPMLOG_ERR, _("Failed to dlopen %s %s\n"), path, dlerror());
+ return RPMRC_FAIL;
+ }
+
+ /* make sure the plugin has the supported hooks flag */
+ supportedHooks = (rpmPluginHook *) dlsym(handle, STR(PLUGIN_HOOKS));
+ if ((error = dlerror()) != NULL) {
+ rpmlog(RPMLOG_ERR, _("Failed to resolve symbol %s: %s\n"),
+ STR(PLUGIN_HOOKS), error);
+ return RPMRC_FAIL;
+ }
+
+ argvAdd(&plugins->names, name);
+ plugins->handles = xrealloc(plugins->handles, (plugins->count + 1) * sizeof(*plugins->handles));
+ plugins->handles[plugins->count] = handle;
+ plugins->count++;
+
+ return rpmpluginsCallInit(plugins, name, opts);
+}
+
+rpmRC rpmpluginsAddCollectionPlugin(rpmPlugins plugins, const char *name)
+{
+ char *path;
+ char *options;
+ rpmRC rc = RPMRC_FAIL;
+
+ path = rpmExpand("%{?__collection_", name, "}", NULL);
+ if (!path || rstreq(path, "")) {
+ rpmlog(RPMLOG_ERR, _("Failed to expand %%__collection_%s macro\n"),
+ name);
+ goto exit;
+ }
+
+ /* split the options from the path */
+#define SKIPSPACE(s) { while (*(s) && risspace(*(s))) (s)++; }
+#define SKIPNONSPACE(s) { while (*(s) && !risspace(*(s))) (s)++; }
+ options = path;
+ SKIPNONSPACE(options);
+ if (risspace(*options)) {
+ *options = '\0';
+ options++;
+ SKIPSPACE(options);
+ }
+ if (*options == '\0') {
+ options = NULL;
+ }
+
+ rc = rpmpluginsAdd(plugins, name, path, options);
+
+ exit:
+ _free(path);
+ return rc;
+}
+
+rpmPlugins rpmpluginsFree(rpmPlugins plugins)
+{
+ int i;
+ for (i = 0; i < plugins->count; i++) {
+ rpmpluginsCallCleanup(plugins, plugins->names[i]);
+ dlclose(plugins->handles[i]);
+ }
+ plugins->handles = _free(plugins->handles);
+ plugins->names = argvFree(plugins->names);
+ plugins->ts = NULL;
+ _free(plugins);
+
+ return NULL;
+}
+
+
+/* Common define for all rpmpluginsCall* hook functions */
+#define RPMPLUGINS_SET_HOOK_FUNC(hook) \
+ void *handle = NULL; \
+ int index; \
+ char * error; \
+ index = rpmpluginsGetPluginIndex(plugins, name); \
+ if (index < 0) { \
+ rpmlog(RPMLOG_ERR, _("Plugin %s not loaded\n"), name); \
+ return RPMRC_FAIL; \
+ } \
+ handle = plugins->handles[index]; \
+ if (!handle) { \
+ rpmlog(RPMLOG_ERR, _("Plugin %s not loaded\n"), name); \
+ return RPMRC_FAIL; \
+ } \
+ if (!rpmpluginsHookIsSupported(handle, hook)) { \
+ return RPMRC_OK; \
+ } \
+ *(void **)(&hookFunc) = dlsym(handle, STR(hook##_FUNC)); \
+ if ((error = dlerror()) != NULL) { \
+ rpmlog(RPMLOG_ERR, _("Failed to resolve %s plugin symbol %s: %s\n"), name, STR(hook##_FUNC), error); \
+ return RPMRC_FAIL; \
+ } \
+ if (rpmtsFlags(plugins->ts) & (RPMTRANS_FLAG_TEST | RPMTRANS_FLAG_JUSTDB)) { \
+ return RPMRC_OK; \
+ } \
+ rpmlog(RPMLOG_DEBUG, "Plugin: calling hook %s in %s plugin\n", STR(hook##_FUNC), name);
+
+rpmRC rpmpluginsCallInit(rpmPlugins plugins, const char *name, const char *opts)
+{
+ rpmRC (*hookFunc)(rpmts, const char *, const char *);
+ RPMPLUGINS_SET_HOOK_FUNC(PLUGINHOOK_INIT);
+ return hookFunc(plugins->ts, name, opts);
+}
+
+rpmRC rpmpluginsCallCleanup(rpmPlugins plugins, const char *name)
+{
+ rpmRC (*hookFunc)(void);
+ RPMPLUGINS_SET_HOOK_FUNC(PLUGINHOOK_CLEANUP);
+ return hookFunc();
+}
+
+rpmRC rpmpluginsCallOpenTE(rpmPlugins plugins, const char *name, rpmte te)
+{
+ rpmRC (*hookFunc)(rpmte);
+ RPMPLUGINS_SET_HOOK_FUNC(PLUGINHOOK_OPENTE);
+ return hookFunc(te);
+}
+
+rpmRC rpmpluginsCallCollectionPostAdd(rpmPlugins plugins, const char *name)
+{
+ rpmRC (*hookFunc)(void);
+ RPMPLUGINS_SET_HOOK_FUNC(PLUGINHOOK_COLL_POST_ADD);
+ return hookFunc();
+}
+
+rpmRC rpmpluginsCallCollectionPostAny(rpmPlugins plugins, const char *name)
+{
+ rpmRC (*hookFunc)(void);
+ RPMPLUGINS_SET_HOOK_FUNC(PLUGINHOOK_COLL_POST_ANY);
+ return hookFunc();
+}
+
+rpmRC rpmpluginsCallCollectionPreRemove(rpmPlugins plugins, const char *name)
+{
+ rpmRC (*hookFunc)(void);
+ RPMPLUGINS_SET_HOOK_FUNC(PLUGINHOOK_COLL_PRE_REMOVE);
+ return hookFunc();
+}
diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h
new file mode 100644
index 0000000..7985559
--- /dev/null
+++ b/lib/rpmplugins.h
@@ -0,0 +1,125 @@
+#ifndef _PLUGINS_H
+#define _PLUGINS_H
+
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PLUGIN_HOOKS plugin_hooks
+
+#define PLUGINHOOK_INIT_FUNC pluginhook_init
+#define PLUGINHOOK_CLEANUP_FUNC pluginhook_cleanup
+#define PLUGINHOOK_OPENTE_FUNC pluginhook_opente
+#define PLUGINHOOK_COLL_POST_ADD_FUNC pluginhook_coll_post_add
+#define PLUGINHOOK_COLL_POST_ANY_FUNC pluginhook_coll_post_any
+#define PLUGINHOOK_COLL_PRE_REMOVE_FUNC pluginhook_coll_pre_remove
+
+enum rpmPluginHook_e {
+ PLUGINHOOK_NONE = 0,
+ PLUGINHOOK_INIT = 1 << 0,
+ PLUGINHOOK_CLEANUP = 1 << 1,
+ PLUGINHOOK_OPENTE = 1 << 2,
+ PLUGINHOOK_COLL_POST_ADD = 1 << 3,
+ PLUGINHOOK_COLL_POST_ANY = 1 << 4,
+ PLUGINHOOK_COLL_PRE_REMOVE = 1 << 5
+};
+
+typedef rpmFlags rpmPluginHook;
+
+/** \ingroup rpmplugins
+ * Create a new plugins structure
+ * @param ts transaction set
+ * @return new plugin structure
+ */
+rpmPlugins rpmpluginsNew(rpmts ts);
+
+/** \ingroup rpmplugins
+ * Destroy a plugins structure
+ * @param plugins plugins structure to destroy
+ * @return NULL always
+ */
+rpmPlugins rpmpluginsFree(rpmPlugins plugins);
+
+/** \ingroup rpmplugins
+ * Add and open a plugin
+ * @param plugins plugins structure to add a plugin to
+ * @param name name to access plugin
+ * @param path path of plugin to open
+ * @param opts options to pass to the plugin
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsAdd(rpmPlugins plugins, const char *name, const char *path, const char *opts);
+
+/** \ingroup rpmplugins
+ * Add and open a collection plugin
+ * @param plugins plugins structure to add a collection plugin to
+ * @param name name of collection to open
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsAddCollectionPlugin(rpmPlugins plugins, const char *name);
+
+/** \ingroup rpmplugins
+ * Determine if a plugin has been added already
+ * @param plugins plugins structure
+ * @param name name of plugin to check
+ * @return 1 if plugin name has already been added, 0 otherwise
+ */
+int rpmpluginsPluginAdded(rpmPlugins plugins, const char *name);
+
+
+/** \ingroup rpmplugins
+ * Call the init plugin hook
+ * @param plugins plugins structure
+ * @param name name of plugin
+ * @param opts plugin options
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsCallInit(rpmPlugins plugins, const char *name, const char *opts);
+
+/** \ingroup rpmplugins
+ * Call the cleanup plugin hook
+ * @param plugins plugins structure
+ * @param name name of plugin
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsCallCleanup(rpmPlugins plugins, const char *name);
+
+/** \ingroup rpmplugins
+ * Call the open te plugin hook
+ * @param plugins plugins structure
+ * @param name name of plugin
+ * @param te transaction element opened
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsCallOpenTE(rpmPlugins plugins, const char *name, rpmte te);
+
+/** \ingroup rpmplugins
+ * Call the collection post add plugin hook
+ * @param plugins plugins structure
+ * @param name name of plugin
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsCallCollectionPostAdd(rpmPlugins plugins, const char *name);
+
+/** \ingroup rpmplugins
+ * Call the collection post any plugin hook
+ * @param plugins plugins structure
+ * @param name name of plugin
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsCallCollectionPostAny(rpmPlugins plugins, const char *name);
+
+/** \ingroup rpmplugins
+ * Call the collection pre remove plugin hook
+ * @param plugins plugins structure
+ * @param name name of plugin
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmpluginsCallCollectionPreRemove(rpmPlugins plugins, const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _PLUGINS_H */
diff --git a/lib/rpmpol.h b/lib/rpmpol.h
new file mode 100644
index 0000000..f5aef28
--- /dev/null
+++ b/lib/rpmpol.h
@@ -0,0 +1,28 @@
+#ifndef H_RPMPOL
+#define H_RPMPOL
+
+/** \ingroup rpmpol
+ * \file lib/rpmpol.h
+ * Structure(s) used for policy sets.
+ */
+
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum rpmpolFlags_e {
+ RPMPOL_FLAG_NONE = 0,
+ RPMPOL_FLAG_BASE = (1 << 0)
+};
+
+typedef rpmFlags rpmpolFlags;
+
+#define RPMPOL_TYPE_DEFAULT "default"
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* H_rpmpol */
diff --git a/lib/rpmprob.c b/lib/rpmprob.c
new file mode 100644
index 0000000..df9c578
--- /dev/null
+++ b/lib/rpmprob.c
@@ -0,0 +1,213 @@
+/**
+ * \file lib/rpmps.c
+ */
+
+#include "system.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <rpm/rpmstring.h>
+#include <rpm/rpmprob.h>
+
+#include "debug.h"
+
+struct rpmProblem_s {
+ char * pkgNEVR;
+ char * altNEVR;
+ fnpyKey key;
+ rpmProblemType type;
+ char * str1;
+ uint64_t num1;
+ int nrefs;
+};
+
+static rpmProblem rpmProblemUnlink(rpmProblem prob);
+
+rpmProblem rpmProblemCreate(rpmProblemType type,
+ const char * pkgNEVR, fnpyKey key,
+ const char * altNEVR,
+ const char * str, uint64_t number)
+{
+ rpmProblem p = xcalloc(1, sizeof(*p));
+
+ p->type = type;
+ p->key = key;
+ p->num1 = number;
+
+ p->pkgNEVR = (pkgNEVR ? xstrdup(pkgNEVR) : NULL);
+ p->altNEVR = (altNEVR ? xstrdup(altNEVR) : NULL);
+ p->str1 = (str ? xstrdup(str) : NULL);
+
+ return rpmProblemLink(p);
+}
+
+rpmProblem rpmProblemFree(rpmProblem prob)
+{
+ if (prob == NULL) return NULL;
+
+ if (prob->nrefs > 1) {
+ return rpmProblemUnlink(prob);
+ }
+ prob->pkgNEVR = _free(prob->pkgNEVR);
+ prob->altNEVR = _free(prob->altNEVR);
+ prob->str1 = _free(prob->str1);
+ free(prob);
+ return NULL;
+}
+
+rpmProblem rpmProblemLink(rpmProblem prob)
+{
+ if (prob) {
+ prob->nrefs++;
+ }
+ return prob;
+}
+
+static rpmProblem rpmProblemUnlink(rpmProblem prob)
+{
+ if (prob) {
+ prob->nrefs--;
+ }
+ return NULL;
+}
+
+const char * rpmProblemGetPkgNEVR(rpmProblem p)
+{
+ return (p->pkgNEVR);
+}
+
+const char * rpmProblemGetAltNEVR(rpmProblem p)
+{
+ return (p->altNEVR);
+}
+
+fnpyKey rpmProblemGetKey(rpmProblem p)
+{
+ return (p->key);
+}
+
+rpmProblemType rpmProblemGetType(rpmProblem p)
+{
+ return (p->type);
+}
+
+const char * rpmProblemGetStr(rpmProblem p)
+{
+ return (p->str1);
+}
+
+rpm_loff_t rpmProblemGetDiskNeed(rpmProblem p)
+{
+ return (p->num1);
+}
+
+char * rpmProblemString(rpmProblem prob)
+{
+ const char * pkgNEVR = (prob->pkgNEVR ? prob->pkgNEVR : "?pkgNEVR?");
+ const char * altNEVR = (prob->altNEVR ? prob->altNEVR : "? ?altNEVR?");
+ const char * str1 = (prob->str1 ? prob->str1 : N_("different"));
+ char * buf = NULL;
+ int rc;
+
+ switch (prob->type) {
+ case RPMPROB_BADARCH:
+ rc = rasprintf(&buf, _("package %s is intended for a %s architecture"),
+ pkgNEVR, str1);
+ break;
+ case RPMPROB_BADOS:
+ rc = rasprintf(&buf,
+ _("package %s is intended for a %s operating system"),
+ pkgNEVR, str1);
+ break;
+ case RPMPROB_PKG_INSTALLED:
+ rc = rasprintf(&buf, _("package %s is already installed"),
+ pkgNEVR);
+ break;
+ case RPMPROB_BADRELOCATE:
+ rc = rasprintf(&buf, _("path %s in package %s is not relocatable"),
+ str1, pkgNEVR);
+ break;
+ case RPMPROB_NEW_FILE_CONFLICT:
+ rc = rasprintf(&buf,
+ _("file %s conflicts between attempted installs of %s and %s"),
+ str1, pkgNEVR, altNEVR);
+ break;
+ case RPMPROB_FILE_CONFLICT:
+ rc = rasprintf(&buf,
+ _("file %s from install of %s conflicts with file from package %s"),
+ str1, pkgNEVR, altNEVR);
+ break;
+ case RPMPROB_OLDPACKAGE:
+ rc = rasprintf(&buf,
+ _("package %s (which is newer than %s) is already installed"),
+ altNEVR, pkgNEVR);
+ break;
+ case RPMPROB_DISKSPACE:
+ rc = rasprintf(&buf,
+ _("installing package %s needs %" PRIu64 "%cB on the %s filesystem"),
+ pkgNEVR,
+ prob->num1 > (1024*1024)
+ ? (prob->num1 + 1024 * 1024 - 1) / (1024 * 1024)
+ : (prob->num1 + 1023) / 1024,
+ prob->num1 > (1024*1024) ? 'M' : 'K',
+ str1);
+ break;
+ case RPMPROB_DISKNODES:
+ rc = rasprintf(&buf,
+ _("installing package %s needs %" PRIu64 " inodes on the %s filesystem"),
+ pkgNEVR, prob->num1, str1);
+ break;
+ case RPMPROB_REQUIRES:
+ rc = rasprintf(&buf, _("%s is needed by %s%s"),
+ prob->str1,
+ (prob->num1 ? _("(installed) ") : ""), altNEVR);
+ break;
+ case RPMPROB_CONFLICT:
+ rc = rasprintf(&buf, _("%s conflicts with %s%s"),
+ prob->str1,
+ (prob->num1 ? _("(installed) ") : ""), altNEVR);
+ break;
+ case RPMPROB_OBSOLETES:
+ rc = rasprintf(&buf, _("%s is obsoleted by %s%s"),
+ prob->str1,
+ (prob->num1 ? _("(installed) ") : ""), altNEVR);
+ break;
+ default:
+ rc = rasprintf(&buf,
+ _("unknown error %d encountered while manipulating package %s"),
+ prob->type, pkgNEVR);
+ break;
+ }
+
+ return buf;
+}
+
+static int cmpStr(const char *s1, const char *s2)
+{
+ if (s1 == s2) return 0;
+ if (s1 && s2) return strcmp(s1, s2);
+ return 1;
+}
+
+int rpmProblemCompare(rpmProblem ap, rpmProblem bp)
+{
+ if (ap == bp)
+ return 0;
+ if (ap == NULL || bp == NULL)
+ return 1;
+ if (ap->type != bp->type)
+ return 1;
+ if (ap->key != bp->key)
+ return 1;
+ if (ap->num1 != bp->num1)
+ return 1;
+ if (cmpStr(ap->pkgNEVR, bp->pkgNEVR))
+ return 1;
+ if (cmpStr(ap->altNEVR, bp->altNEVR))
+ return 1;
+ if (cmpStr(ap->str1, bp->str1))
+ return 1;
+
+ return 0;
+}
diff --git a/lib/rpmprob.h b/lib/rpmprob.h
new file mode 100644
index 0000000..2b89f15
--- /dev/null
+++ b/lib/rpmprob.h
@@ -0,0 +1,148 @@
+#ifndef _RPMPROB_H
+#define _RPMPROB_H
+
+/** \ingroup rpmprob
+ * \file lib/rpmprob.h
+ * Structures and prototypes used for an rpm problem item.
+ */
+
+#include <stdio.h>
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct rpmProblem_s * rpmProblem;
+
+/** \ingroup rpmprob
+ * @todo Generalize filter mechanism.
+ */
+enum rpmprobFilterFlags_e {
+ RPMPROB_FILTER_NONE = 0,
+ RPMPROB_FILTER_IGNOREOS = (1 << 0), /*!< from --ignoreos */
+ RPMPROB_FILTER_IGNOREARCH = (1 << 1), /*!< from --ignorearch */
+ RPMPROB_FILTER_REPLACEPKG = (1 << 2), /*!< from --replacepkgs */
+ RPMPROB_FILTER_FORCERELOCATE= (1 << 3), /*!< from --badreloc */
+ RPMPROB_FILTER_REPLACENEWFILES= (1 << 4), /*!< from --replacefiles */
+ RPMPROB_FILTER_REPLACEOLDFILES= (1 << 5), /*!< from --replacefiles */
+ RPMPROB_FILTER_OLDPACKAGE = (1 << 6), /*!< from --oldpackage */
+ RPMPROB_FILTER_DISKSPACE = (1 << 7), /*!< from --ignoresize */
+ RPMPROB_FILTER_DISKNODES = (1 << 8) /*!< from --ignoresize */
+};
+
+typedef rpmFlags rpmprobFilterFlags;
+
+/** \ingroup rpmprob
+ * Enumerate transaction set problem types.
+ */
+typedef enum rpmProblemType_e {
+ RPMPROB_BADARCH, /*!< package ... is for a different architecture */
+ RPMPROB_BADOS, /*!< package ... is for a different operating system */
+ RPMPROB_PKG_INSTALLED, /*!< package ... is already installed */
+ RPMPROB_BADRELOCATE,/*!< path ... is not relocatable for package ... */
+ RPMPROB_REQUIRES, /*!< package ... has unsatisfied Requires: ... */
+ RPMPROB_CONFLICT, /*!< package ... has unsatisfied Conflicts: ... */
+ RPMPROB_NEW_FILE_CONFLICT, /*!< file ... conflicts between attemped installs of ... */
+ RPMPROB_FILE_CONFLICT,/*!< file ... from install of ... conflicts with file from package ... */
+ RPMPROB_OLDPACKAGE, /*!< package ... (which is newer than ...) is already installed */
+ RPMPROB_DISKSPACE, /*!< installing package ... needs ... on the ... filesystem */
+ RPMPROB_DISKNODES, /*!< installing package ... needs ... on the ... filesystem */
+ RPMPROB_OBSOLETES, /*!< package ... is obsoleted by ... */
+ } rpmProblemType;
+
+/** \ingroup rpmprob
+ * Create a problem item.
+ * @param type type of problem
+ * @param pkgNEVR package name
+ * @param key filename or python object address
+ * @param altNEVR related (e.g. through a dependency) package name
+ * @param str generic string attribute
+ * @param number generic number attribute
+ * @return rpmProblem
+ */
+rpmProblem rpmProblemCreate(rpmProblemType type,
+ const char * pkgNEVR, fnpyKey key,
+ const char * altNEVR,
+ const char * str, uint64_t number);
+
+/** \ingroup rpmprob
+ * Destroy a problem item.
+ * @param prob rpm problem
+ * @return rpm problem (NULL)
+ */
+rpmProblem rpmProblemFree(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Reference an rpmProblem instance
+ * @param prob rpm problem
+ * @return rpm problem
+ */
+rpmProblem rpmProblemLink(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Compare two problems for equality.
+ * @param ap 1st problem
+ * @param bp 2nd problem
+ * @return 1 if the problems differ, 0 otherwise
+ */
+int rpmProblemCompare(rpmProblem ap, rpmProblem bp);
+
+/** \ingroup rpmprob
+ * Return package NEVR
+ * @param prob rpm problem
+ * @return package NEVR
+ */
+
+const char * rpmProblemGetPkgNEVR(rpmProblem prob);
+/** \ingroup rpmprob
+ * Return related (e.g. through a dependency) package NEVR
+ * @param prob rpm problem
+ * @return related (e.g. through a dependency) package NEVR
+ */
+const char * rpmProblemGetAltNEVR(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Return type of problem (dependency, diskpace etc)
+ * @param prob rpm problem
+ * @return type of problem
+ */
+
+rpmProblemType rpmProblemGetType(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Return filename or python object address of a problem
+ * @param prob rpm problem
+ * @return filename or python object address
+ */
+fnpyKey rpmProblemGetKey(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Return a generic data string from a problem
+ * @param prob rpm problem
+ * @return a generic data string
+ * @todo needs a better name
+ */
+const char * rpmProblemGetStr(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Return disk requirement (needed disk space / number of inodes)
+ * depending on problem type. On problem types other than RPMPROB_DISKSPACE
+ * and RPMPROB_DISKNODES return value is undefined.
+ * @param prob rpm problem
+ * @return disk requirement
+ */
+rpm_loff_t rpmProblemGetDiskNeed(rpmProblem prob);
+
+/** \ingroup rpmprob
+ * Return formatted string representation of a problem.
+ * @param prob rpm problem
+ * @return formatted string (malloc'd)
+ */
+char * rpmProblemString(rpmProblem prob);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMPROB_H */
diff --git a/lib/rpmps.c b/lib/rpmps.c
new file mode 100644
index 0000000..6e1ab18
--- /dev/null
+++ b/lib/rpmps.c
@@ -0,0 +1,172 @@
+/**
+ * \file lib/rpmps.c
+ */
+
+#include "system.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <rpm/rpmstring.h>
+#include <rpm/rpmps.h>
+
+#include "debug.h"
+
+struct rpmps_s {
+ int numProblems; /*!< Current probs array size. */
+ int numProblemsAlloced; /*!< Allocated probs array size. */
+ rpmProblem *probs; /*!< Array of pointers to specific problems. */
+ int nrefs; /*!< Reference count. */
+};
+
+struct rpmpsi_s {
+ int ix;
+ rpmps ps;
+};
+
+
+static rpmps rpmpsUnlink(rpmps ps)
+{
+ if (ps) {
+ ps->nrefs--;
+ }
+ return NULL;
+}
+
+rpmps rpmpsLink(rpmps ps)
+{
+ if (ps) {
+ ps->nrefs++;
+ }
+ return ps;
+}
+
+int rpmpsNumProblems(rpmps ps)
+{
+ int numProblems = 0;
+ if (ps && ps->probs)
+ numProblems = ps->numProblems;
+ return numProblems;
+}
+
+rpmpsi rpmpsInitIterator(rpmps ps)
+{
+ rpmpsi psi = NULL;
+ if (ps != NULL && ps->numProblems > 0) {
+ psi = xcalloc(1, sizeof(*psi));
+ psi->ps = rpmpsLink(ps);
+ psi->ix = -1;
+ }
+ return psi;
+}
+
+rpmpsi rpmpsFreeIterator(rpmpsi psi)
+{
+ if (psi != NULL) {
+ rpmpsUnlink(psi->ps);
+ free(psi);
+ }
+ return NULL;
+}
+
+rpmProblem rpmpsiNext(rpmpsi psi)
+{
+ rpmProblem p = NULL;
+ if (psi != NULL && psi->ps != NULL && ++psi->ix >= 0) {
+ rpmps ps = psi->ps;
+ if (psi->ix < ps->numProblems) {
+ p = ps->probs[psi->ix];
+ } else {
+ psi->ix = -1;
+ }
+ }
+ return p;
+}
+
+int rpmpsNextIterator(rpmpsi psi)
+{
+ return (rpmpsiNext(psi) != NULL) ? psi->ix : -1;
+}
+
+rpmProblem rpmpsGetProblem(rpmpsi psi)
+{
+ rpmProblem p = NULL;
+ if (psi != NULL && psi->ix >= 0 && psi->ix < rpmpsNumProblems(psi->ps)) {
+ p = psi->ps->probs[psi->ix];
+ }
+ return p;
+}
+
+rpmps rpmpsCreate(void)
+{
+ rpmps ps = xcalloc(1, sizeof(*ps));
+ return rpmpsLink(ps);
+}
+
+rpmps rpmpsFree(rpmps ps)
+{
+ if (ps == NULL) return NULL;
+ if (ps->nrefs > 1) {
+ return rpmpsUnlink(ps);
+ }
+
+ if (ps->probs) {
+ rpmpsi psi = rpmpsInitIterator(ps);
+ while (rpmpsNextIterator(psi) >= 0) {
+ rpmProblemFree(rpmpsGetProblem(psi));
+ }
+ rpmpsFreeIterator(psi);
+ ps->probs = _free(ps->probs);
+ }
+ ps = _free(ps);
+ return NULL;
+}
+
+void rpmpsAppendProblem(rpmps ps, rpmProblem prob)
+{
+ if (ps == NULL || prob == NULL) return;
+
+ if (ps->numProblems == ps->numProblemsAlloced) {
+ if (ps->numProblemsAlloced)
+ ps->numProblemsAlloced *= 2;
+ else
+ ps->numProblemsAlloced = 2;
+ ps->probs = xrealloc(ps->probs,
+ ps->numProblemsAlloced * sizeof(ps->probs));
+ }
+
+ ps->probs[ps->numProblems] = rpmProblemLink(prob);
+ ps->numProblems++;
+}
+
+/*
+ * TODO: filter out duplicates while merging. Also horribly inefficient... */
+int rpmpsMerge(rpmps dest, rpmps src)
+{
+ int rc = 0;
+ if (dest != NULL) {
+ rpmProblem p;
+ rpmpsi spi = rpmpsInitIterator(src);
+ while ((p = rpmpsiNext(spi)) != NULL) {
+ rpmpsAppendProblem(dest, p);
+ rc++;
+ }
+ rpmpsFreeIterator(spi);
+ }
+ return rc;
+}
+
+void rpmpsPrint(FILE *fp, rpmps ps)
+{
+ rpmProblem p;
+ rpmpsi psi = rpmpsInitIterator(ps);
+ FILE *f = (fp != NULL) ? fp : stderr;
+
+ while ((p = rpmpsiNext(psi))) {
+ char *msg = rpmProblemString(p);
+ fprintf(f, "\t%s\n", msg);
+ free(msg);
+ }
+ rpmpsFreeIterator(psi);
+}
+
diff --git a/lib/rpmps.h b/lib/rpmps.h
new file mode 100644
index 0000000..1b1142b
--- /dev/null
+++ b/lib/rpmps.h
@@ -0,0 +1,110 @@
+#ifndef H_RPMPS
+#define H_RPMPS
+
+/** \ingroup rpmps
+ * \file lib/rpmps.h
+ * Structures and prototypes used for an "rpmps" problem set.
+ */
+
+#include <stdio.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmprob.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmps
+ * Problem set iterator
+ */
+typedef struct rpmpsi_s * rpmpsi;
+
+/** \ingroup rpmps
+ * Reference a problem set instance.
+ * @param ps transaction set
+ * @return new transaction set reference
+ */
+rpmps rpmpsLink (rpmps ps);
+
+/** \ingroup rpmps
+ * Return number of problems in set.
+ * @param ps problem set
+ * @return number of problems
+ */
+int rpmpsNumProblems(rpmps ps);
+
+/** \ingroup rpmps
+ * Initialize problem set iterator.
+ * @param ps problem set
+ * @return problem set iterator
+ */
+rpmpsi rpmpsInitIterator(rpmps ps);
+
+/** \ingroup rpmps
+ * Destroy problem set iterator.
+ * @param psi problem set iterator
+ * @return problem set iterator (NULL)
+ */
+rpmpsi rpmpsFreeIterator(rpmpsi psi);
+
+/** \ingroup rpmps
+ * Return next problem from iterator
+ * @param psi problem set iterator
+ * @return next problem (weak ref), NULL on termination
+ */
+rpmProblem rpmpsiNext(rpmpsi psi);
+
+/** \ingroup rpmps
+ * Return next problem set iterator index
+ * @param psi problem set iterator
+ * @return iterator index, -1 on termination
+ */
+int rpmpsNextIterator(rpmpsi psi);
+
+/** \ingroup rpmps
+ * Return current problem from problem set
+ * @param psi problem set iterator
+ * @return current rpmProblem
+ */
+rpmProblem rpmpsGetProblem(rpmpsi psi);
+
+/** \ingroup rpmps
+ * Create a problem set.
+ * @return new problem set
+ */
+rpmps rpmpsCreate(void);
+
+/** \ingroup rpmps
+ * Destroy a problem set.
+ * @param ps problem set
+ * @return NULL always
+ */
+rpmps rpmpsFree(rpmps ps);
+
+/** \ingroup rpmps
+ * Print problems to file handle.
+ * @param fp file handle (NULL uses stderr)
+ * @param ps problem set
+ */
+void rpmpsPrint(FILE *fp, rpmps ps);
+
+/** \ingroup rpmps
+ * Append a problem to current set of problems.
+ * @param ps problem set
+ * @param prob rpmProblem
+ */
+void rpmpsAppendProblem(rpmps ps, rpmProblem prob);
+
+/** \ingroup rpmps
+ * Merge problem set into another.
+ * @param dest destination problem set
+ * @param src source problem set
+ * @return number of problems merged
+ */
+int rpmpsMerge(rpmps dest, rpmps src);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMPS */
diff --git a/lib/rpmrc.c b/lib/rpmrc.c
new file mode 100644
index 0000000..1b47dc3
--- /dev/null
+++ b/lib/rpmrc.c
@@ -0,0 +1,1710 @@
+#include "system.h"
+
+#include <stdarg.h>
+#if defined(__linux__) && defined(__powerpc__)
+#include <setjmp.h>
+#endif
+
+#if HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+#include <netdb.h>
+#include <ctype.h> /* XXX for /etc/rpm/platform contents */
+
+#if HAVE_SYS_SYSTEMCFG_H
+#include <sys/systemcfg.h>
+#else
+#define __power_pc() 0
+#endif
+
+#include <rpm/rpmlib.h> /* RPM_MACTABLE*, Rc-prototypes */
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+
+#include "rpmio/rpmlua.h"
+#include "rpmio/rpmio_internal.h" /* XXX for rpmioSlurp */
+#include "lib/misc.h"
+#include "lib/rpmliblua.h"
+
+#include "debug.h"
+
+static const char * defrcfiles = NULL;
+const char * macrofiles = NULL;
+
+static const char * const platform = SYSCONFDIR "/rpm/platform";
+static char ** platpat = NULL;
+static int nplatpat = 0;
+
+typedef char * cptr_t;
+
+typedef struct machCacheEntry_s {
+ char * name;
+ int count;
+ cptr_t * equivs;
+ int visited;
+} * machCacheEntry;
+
+typedef struct machCache_s {
+ machCacheEntry cache;
+ int size;
+} * machCache;
+
+typedef struct machEquivInfo_s {
+ char * name;
+ int score;
+} * machEquivInfo;
+
+typedef struct machEquivTable_s {
+ int count;
+ machEquivInfo list;
+} * machEquivTable;
+
+struct rpmvarValue {
+ char * value;
+ /* eventually, this arch will be replaced with a generic condition */
+ char * arch;
+struct rpmvarValue * next;
+};
+
+struct rpmOption {
+ char * name;
+ int var;
+ int archSpecific;
+int required;
+ int macroize;
+ int localize;
+struct rpmOptionValue * value;
+};
+
+typedef struct defaultEntry_s {
+ char * name;
+ char * defName;
+} * defaultEntry;
+
+typedef struct canonEntry_s {
+ char * name;
+ char * short_name;
+ short num;
+} * canonEntry;
+
+/* tags are 'key'canon, 'key'translate, 'key'compat
+ *
+ * for giggles, 'key'_canon, 'key'_compat, and 'key'_canon will also work
+ */
+typedef struct tableType_s {
+ char * const key;
+ const int hasCanon;
+ const int hasTranslate;
+ struct machEquivTable_s equiv;
+ struct machCache_s cache;
+ defaultEntry defaults;
+ canonEntry canons;
+ int defaultsLength;
+ int canonsLength;
+} * tableType;
+
+static struct tableType_s tables[RPM_MACHTABLE_COUNT] = {
+ { "arch", 1, 0 },
+ { "os", 1, 0 },
+ { "buildarch", 0, 1 },
+ { "buildos", 0, 1 }
+};
+
+/* XXX get rid of this stuff... */
+/* Stuff for maintaining "variables" like SOURCEDIR, BUILDDIR, etc */
+#define RPMVAR_OPTFLAGS 3
+#define RPMVAR_INCLUDE 43
+#define RPMVAR_MACROFILES 49
+
+#define RPMVAR_NUM 55 /* number of RPMVAR entries */
+
+/* this *must* be kept in alphabetical order */
+/* The order of the flags is archSpecific, required, macroize, localize */
+
+static const struct rpmOption optionTable[] = {
+ { "include", RPMVAR_INCLUDE, 0, 1, 0, 2 },
+ { "macrofiles", RPMVAR_MACROFILES, 0, 0, 0, 1 },
+ { "optflags", RPMVAR_OPTFLAGS, 1, 0, 1, 0 },
+};
+
+static const size_t optionTableSize = sizeof(optionTable) / sizeof(*optionTable);
+
+#define OS 0
+#define ARCH 1
+
+static cptr_t current[2];
+
+static int currTables[2] = { RPM_MACHTABLE_INSTOS, RPM_MACHTABLE_INSTARCH };
+
+static struct rpmvarValue values[RPMVAR_NUM];
+
+static int defaultsInitialized = 0;
+
+/* prototypes */
+static rpmRC doReadRC(const char * urlfn);
+
+static void rpmSetVarArch(int var, const char * val,
+ const char * arch);
+
+static void rebuildCompatTables(int type, const char * name);
+
+static void rpmRebuildTargetVars(const char **target, const char ** canontarget);
+
+static int optionCompare(const void * a, const void * b)
+{
+ return rstrcasecmp(((const struct rpmOption *) a)->name,
+ ((const struct rpmOption *) b)->name);
+}
+
+static machCacheEntry
+machCacheFindEntry(const machCache cache, const char * key)
+{
+ int i;
+
+ for (i = 0; i < cache->size; i++)
+ if (rstreq(cache->cache[i].name, key)) return cache->cache + i;
+
+ return NULL;
+}
+
+static int machCompatCacheAdd(char * name, const char * fn, int linenum,
+ machCache cache)
+{
+ machCacheEntry entry = NULL;
+ char * chptr;
+ char * equivs;
+ int delEntry = 0;
+ int i;
+
+ while (*name && risspace(*name)) name++;
+
+ chptr = name;
+ while (*chptr && *chptr != ':') chptr++;
+ if (!*chptr) {
+ rpmlog(RPMLOG_ERR, _("missing second ':' at %s:%d\n"), fn, linenum);
+ return 1;
+ } else if (chptr == name) {
+ rpmlog(RPMLOG_ERR, _("missing architecture name at %s:%d\n"), fn,
+ linenum);
+ return 1;
+ }
+
+ while (*chptr == ':' || risspace(*chptr)) chptr--;
+ *(++chptr) = '\0';
+ equivs = chptr + 1;
+ while (*equivs && risspace(*equivs)) equivs++;
+ if (!*equivs) {
+ delEntry = 1;
+ }
+
+ if (cache->size) {
+ entry = machCacheFindEntry(cache, name);
+ if (entry) {
+ for (i = 0; i < entry->count; i++)
+ entry->equivs[i] = _free(entry->equivs[i]);
+ entry->equivs = _free(entry->equivs);
+ entry->count = 0;
+ }
+ }
+
+ if (!entry) {
+ cache->cache = xrealloc(cache->cache,
+ (cache->size + 1) * sizeof(*cache->cache));
+ entry = cache->cache + cache->size++;
+ entry->name = xstrdup(name);
+ entry->count = 0;
+ entry->visited = 0;
+ }
+
+ if (delEntry) return 0;
+
+ while ((chptr = strtok(equivs, " ")) != NULL) {
+ equivs = NULL;
+ if (chptr[0] == '\0') /* does strtok() return "" ever?? */
+ continue;
+ if (entry->count)
+ entry->equivs = xrealloc(entry->equivs, sizeof(*entry->equivs)
+ * (entry->count + 1));
+ else
+ entry->equivs = xmalloc(sizeof(*entry->equivs));
+
+ entry->equivs[entry->count] = xstrdup(chptr);
+ entry->count++;
+ }
+
+ return 0;
+}
+
+static machEquivInfo
+machEquivSearch(const machEquivTable table, const char * name)
+{
+ int i;
+
+ for (i = 0; i < table->count; i++)
+ if (!rstrcasecmp(table->list[i].name, name))
+ return table->list + i;
+
+ return NULL;
+}
+
+static void machAddEquiv(machEquivTable table, const char * name,
+ int distance)
+{
+ machEquivInfo equiv;
+
+ equiv = machEquivSearch(table, name);
+ if (!equiv) {
+ if (table->count)
+ table->list = xrealloc(table->list, (table->count + 1)
+ * sizeof(*table->list));
+ else
+ table->list = xmalloc(sizeof(*table->list));
+
+ table->list[table->count].name = xstrdup(name);
+ table->list[table->count++].score = distance;
+ }
+}
+
+static void machCacheEntryVisit(machCache cache,
+ machEquivTable table, const char * name, int distance)
+{
+ machCacheEntry entry;
+ int i;
+
+ entry = machCacheFindEntry(cache, name);
+ if (!entry || entry->visited) return;
+
+ entry->visited = 1;
+
+ for (i = 0; i < entry->count; i++) {
+ machAddEquiv(table, entry->equivs[i], distance);
+ }
+
+ for (i = 0; i < entry->count; i++) {
+ machCacheEntryVisit(cache, table, entry->equivs[i], distance + 1);
+ }
+}
+
+static void machFindEquivs(machCache cache, machEquivTable table,
+ const char * key)
+{
+ int i;
+
+ for (i = 0; i < cache->size; i++)
+ cache->cache[i].visited = 0;
+
+ while (table->count > 0) {
+ --table->count;
+ table->list[table->count].name = _free(table->list[table->count].name);
+ }
+ table->count = 0;
+ table->list = _free(table->list);
+
+ /*
+ * We have a general graph built using strings instead of pointers.
+ * Yuck. We have to start at a point at traverse it, remembering how
+ * far away everything is.
+ */
+ /* FIX: table->list may be NULL. */
+ machAddEquiv(table, key, 1);
+ machCacheEntryVisit(cache, table, key, 2);
+ return;
+}
+
+static rpmRC addCanon(canonEntry * table, int * tableLen, char * line,
+ const char * fn, int lineNum)
+{
+ canonEntry t;
+ char *s, *s1;
+ const char * tname;
+ const char * tshort_name;
+ int tnum;
+
+ (*tableLen) += 2;
+ *table = xrealloc(*table, sizeof(**table) * (*tableLen));
+
+ t = & ((*table)[*tableLen - 2]);
+
+ tname = strtok(line, ": \t");
+ tshort_name = strtok(NULL, " \t");
+ s = strtok(NULL, " \t");
+ if (! (tname && tshort_name && s)) {
+ rpmlog(RPMLOG_ERR, _("Incomplete data line at %s:%d\n"),
+ fn, lineNum);
+ return RPMRC_FAIL;
+ }
+ if (strtok(NULL, " \t")) {
+ rpmlog(RPMLOG_ERR, _("Too many args in data line at %s:%d\n"),
+ fn, lineNum);
+ return RPMRC_FAIL;
+ }
+
+ tnum = strtoul(s, &s1, 10);
+ if ((*s1) || (s1 == s) || (tnum == ULONG_MAX)) {
+ rpmlog(RPMLOG_ERR, _("Bad arch/os number: %s (%s:%d)\n"), s,
+ fn, lineNum);
+ return RPMRC_FAIL;
+ }
+
+ t[0].name = xstrdup(tname);
+ t[0].short_name = (tshort_name ? xstrdup(tshort_name) : xstrdup(""));
+ t[0].num = tnum;
+
+ /* From A B C entry */
+ /* Add B B C entry */
+ t[1].name = (tshort_name ? xstrdup(tshort_name) : xstrdup(""));
+ t[1].short_name = (tshort_name ? xstrdup(tshort_name) : xstrdup(""));
+ t[1].num = tnum;
+
+ return RPMRC_OK;
+}
+
+static rpmRC addDefault(defaultEntry * table, int * tableLen, char * line,
+ const char * fn, int lineNum)
+{
+ defaultEntry t;
+
+ (*tableLen)++;
+ *table = xrealloc(*table, sizeof(**table) * (*tableLen));
+
+ t = & ((*table)[*tableLen - 1]);
+
+ t->name = strtok(line, ": \t");
+ t->defName = strtok(NULL, " \t");
+ if (! (t->name && t->defName)) {
+ rpmlog(RPMLOG_ERR, _("Incomplete default line at %s:%d\n"),
+ fn, lineNum);
+ return RPMRC_FAIL;
+ }
+ if (strtok(NULL, " \t")) {
+ rpmlog(RPMLOG_ERR, _("Too many args in default line at %s:%d\n"),
+ fn, lineNum);
+ return RPMRC_FAIL;
+ }
+
+ t->name = xstrdup(t->name);
+ t->defName = (t->defName ? xstrdup(t->defName) : NULL);
+
+ return RPMRC_OK;
+}
+
+static canonEntry lookupInCanonTable(const char * name,
+ const canonEntry table, int tableLen)
+{
+ while (tableLen) {
+ tableLen--;
+ if (!rstreq(name, table[tableLen].name))
+ continue;
+ return &(table[tableLen]);
+ }
+
+ return NULL;
+}
+
+static
+const char * lookupInDefaultTable(const char * name,
+ const defaultEntry table, int tableLen)
+{
+ while (tableLen) {
+ tableLen--;
+ if (table[tableLen].name && rstreq(name, table[tableLen].name))
+ return table[tableLen].defName;
+ }
+
+ return name;
+}
+
+static void setDefaults(void)
+{
+ const char *confdir = rpmConfigDir();
+ if (!defrcfiles) {
+ defrcfiles = rstrscat(NULL, confdir, "/rpmrc", ":",
+ confdir, "/" RPMCANONVENDOR "/rpmrc", ":",
+ SYSCONFDIR "/rpmrc", ":",
+ "~/.rpmrc", NULL);
+ }
+
+#ifndef MACROFILES
+ if (!macrofiles) {
+ macrofiles = rstrscat(NULL, confdir, "/macros", ":",
+ confdir, "/platform/%{_target}/macros", ":",
+ confdir, "/fileattrs/*.attr", ":",
+ confdir, "/" RPMCANONVENDOR "/macros", ":",
+ SYSCONFDIR "/rpm/macros.*", ":",
+ SYSCONFDIR "/rpm/macros", ":",
+ SYSCONFDIR "/rpm/%{_target}/macros", ":",
+ "~/.rpmmacros", NULL);
+ }
+#else
+ macrofiles = MACROFILES;
+#endif
+}
+
+/* FIX: se usage inconsistent, W2DO? */
+static rpmRC doReadRC(const char * urlfn)
+{
+ char *s;
+ char *se, *next, *buf = NULL, *fn;
+ int linenum = 0;
+ struct rpmOption searchOption, * option;
+ rpmRC rc = RPMRC_FAIL;
+
+ fn = rpmGetPath(urlfn, NULL);
+ if (rpmioSlurp(fn, (uint8_t **) &buf, NULL) || buf == NULL) {
+ goto exit;
+ }
+
+ next = buf;
+ while (*next != '\0') {
+ linenum++;
+
+ s = se = next;
+
+ /* Find end-of-line. */
+ while (*se && *se != '\n') se++;
+ if (*se != '\0') *se++ = '\0';
+ next = se;
+
+ /* Trim leading spaces */
+ while (*s && risspace(*s)) s++;
+
+ /* We used to allow comments to begin anywhere, but not anymore. */
+ if (*s == '#' || *s == '\0') continue;
+
+ /* Find end-of-keyword. */
+ se = (char *)s;
+ while (*se && !risspace(*se) && *se != ':') se++;
+
+ if (risspace(*se)) {
+ *se++ = '\0';
+ while (*se && risspace(*se) && *se != ':') se++;
+ }
+
+ if (*se != ':') {
+ rpmlog(RPMLOG_ERR, _("missing ':' (found 0x%02x) at %s:%d\n"),
+ (unsigned)(0xff & *se), fn, linenum);
+ goto exit;
+ }
+ *se++ = '\0'; /* terminate keyword or option, point to value */
+ while (*se && risspace(*se)) se++;
+
+ /* Find keyword in table */
+ searchOption.name = s;
+ option = bsearch(&searchOption, optionTable, optionTableSize,
+ sizeof(optionTable[0]), optionCompare);
+
+ if (option) { /* For configuration variables ... */
+ const char *arch, *val;
+
+ arch = val = NULL;
+ if (*se == '\0') {
+ rpmlog(RPMLOG_ERR, _("missing argument for %s at %s:%d\n"),
+ option->name, fn, linenum);
+ goto exit;
+ }
+
+ switch (option->var) {
+ case RPMVAR_INCLUDE:
+ s = se;
+ while (*se && !risspace(*se)) se++;
+ if (*se != '\0') *se++ = '\0';
+
+#if 0 /* XXX doesn't seem to do anything useful, only break things... */
+ rpmRebuildTargetVars(NULL, NULL);
+#endif
+
+ if (doReadRC(s)) {
+ rpmlog(RPMLOG_ERR, _("cannot open %s at %s:%d: %m\n"),
+ s, fn, linenum);
+ goto exit;
+ } else {
+ continue; /* XXX don't save include value as var/macro */
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (option->archSpecific) {
+ arch = se;
+ while (*se && !risspace(*se)) se++;
+ if (*se == '\0') {
+ rpmlog(RPMLOG_ERR,
+ _("missing architecture for %s at %s:%d\n"),
+ option->name, fn, linenum);
+ goto exit;
+ }
+ *se++ = '\0';
+ while (*se && risspace(*se)) se++;
+ if (*se == '\0') {
+ rpmlog(RPMLOG_ERR,
+ _("missing argument for %s at %s:%d\n"),
+ option->name, fn, linenum);
+ goto exit;
+ }
+ }
+
+ val = se;
+
+ /* Only add macros if appropriate for this arch */
+ if (option->macroize &&
+ (arch == NULL || rstreq(arch, current[ARCH]))) {
+ char *n, *name;
+ n = name = xmalloc(strlen(option->name)+2);
+ if (option->localize)
+ *n++ = '_';
+ strcpy(n, option->name);
+ addMacro(NULL, name, NULL, val, RMIL_RPMRC);
+ free(name);
+ }
+ rpmSetVarArch(option->var, val, arch);
+ fn = _free(fn);
+
+ } else { /* For arch/os compatibilty tables ... */
+ int gotit;
+ int i;
+
+ gotit = 0;
+
+ for (i = 0; i < RPM_MACHTABLE_COUNT; i++) {
+ if (rstreqn(tables[i].key, s, strlen(tables[i].key)))
+ break;
+ }
+
+ if (i < RPM_MACHTABLE_COUNT) {
+ const char *rest = s + strlen(tables[i].key);
+ if (*rest == '_') rest++;
+
+ if (rstreq(rest, "compat")) {
+ if (machCompatCacheAdd(se, fn, linenum,
+ &tables[i].cache))
+ goto exit;
+ gotit = 1;
+ } else if (tables[i].hasTranslate &&
+ rstreq(rest, "translate")) {
+ if (addDefault(&tables[i].defaults,
+ &tables[i].defaultsLength,
+ se, fn, linenum))
+ goto exit;
+ gotit = 1;
+ } else if (tables[i].hasCanon &&
+ rstreq(rest, "canon")) {
+ if (addCanon(&tables[i].canons, &tables[i].canonsLength,
+ se, fn, linenum))
+ goto exit;
+ gotit = 1;
+ }
+ }
+
+ if (!gotit) {
+ rpmlog(RPMLOG_ERR, _("bad option '%s' at %s:%d\n"),
+ s, fn, linenum);
+ goto exit;
+ }
+ }
+ }
+ rc = RPMRC_OK;
+
+exit:
+ free(fn);
+ free(buf);
+
+ return rc;
+}
+
+
+/**
+ */
+static rpmRC rpmPlatform(const char * platform)
+{
+ const char *cpu = NULL, *vendor = NULL, *os = NULL, *gnu = NULL;
+ uint8_t * b = NULL;
+ ssize_t blen = 0;
+ int init_platform = 0;
+ char * p, * pe;
+ rpmRC rc;
+
+ rc = (rpmioSlurp(platform, &b, &blen) == 0) ? RPMRC_OK : RPMRC_FAIL;
+
+ if (rc || b == NULL || blen <= 0) {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+
+ p = (char *)b;
+ for (pe = p; p && *p; p = pe) {
+ pe = strchr(p, '\n');
+ if (pe)
+ *pe++ = '\0';
+
+ while (*p && isspace(*p))
+ p++;
+ if (*p == '\0' || *p == '#')
+ continue;
+
+ if (init_platform) {
+ char * t = p + strlen(p);
+
+ while (--t > p && isspace(*t))
+ *t = '\0';
+ if (t > p) {
+ platpat = xrealloc(platpat, (nplatpat + 2) * sizeof(*platpat));
+ platpat[nplatpat] = xstrdup(p);
+ nplatpat++;
+ platpat[nplatpat] = NULL;
+ }
+ continue;
+ }
+
+ cpu = p;
+ vendor = "unknown";
+ os = "unknown";
+ gnu = NULL;
+ while (*p && !(*p == '-' || isspace(*p)))
+ p++;
+ if (*p != '\0') *p++ = '\0';
+
+ vendor = p;
+ while (*p && !(*p == '-' || isspace(*p)))
+ p++;
+ if (*p != '-') {
+ if (*p != '\0') *p++ = '\0';
+ os = vendor;
+ vendor = "unknown";
+ } else {
+ if (*p != '\0') *p++ = '\0';
+
+ os = p;
+ while (*p && !(*p == '-' || isspace(*p)))
+ p++;
+ if (*p == '-') {
+ *p++ = '\0';
+
+ gnu = p;
+ while (*p && !(*p == '-' || isspace(*p)))
+ p++;
+ }
+ if (*p != '\0') *p++ = '\0';
+ }
+
+ addMacro(NULL, "_host_cpu", NULL, cpu, -1);
+ addMacro(NULL, "_host_vendor", NULL, vendor, -1);
+ addMacro(NULL, "_host_os", NULL, os, -1);
+
+ platpat = xrealloc(platpat, (nplatpat + 2) * sizeof(*platpat));
+ platpat[nplatpat] = rpmExpand("%{_host_cpu}-%{_host_vendor}-%{_host_os}", (gnu && *gnu ? "-" : NULL), gnu, NULL);
+ nplatpat++;
+ platpat[nplatpat] = NULL;
+
+ init_platform++;
+ }
+ rc = (init_platform ? RPMRC_OK : RPMRC_FAIL);
+
+exit:
+ b = _free(b);
+ return rc;
+}
+
+
+# if defined(__linux__) && defined(__i386__)
+#include <setjmp.h>
+#include <signal.h>
+
+/*
+ * Generic CPUID function
+ */
+static inline void cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx)
+{
+ asm volatile (
+ "pushl %%ebx \n"
+ "cpuid \n"
+ "movl %%ebx, %%esi \n"
+ "popl %%ebx \n"
+ : "=a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx)
+ : "a" (op));
+}
+
+/*
+ * CPUID functions returning a single datum
+ */
+static inline unsigned int cpuid_eax(unsigned int op)
+{
+ unsigned int tmp, val;
+ cpuid(op, &val, &tmp, &tmp, &tmp);
+ return val;
+}
+
+static inline unsigned int cpuid_ebx(unsigned int op)
+{
+ unsigned int tmp, val;
+ cpuid(op, &tmp, &val, &tmp, &tmp);
+ return val;
+}
+
+static inline unsigned int cpuid_ecx(unsigned int op)
+{
+ unsigned int tmp, val;
+ cpuid(op, &tmp, &tmp, &val, &tmp);
+ return val;
+}
+
+static inline unsigned int cpuid_edx(unsigned int op)
+{
+ unsigned int tmp, val;
+ cpuid(op, &tmp, &tmp, &tmp, &val);
+ return val;
+}
+
+static sigjmp_buf jenv;
+
+static inline void model3(int _unused)
+{
+ siglongjmp(jenv, 1);
+}
+
+static inline int RPMClass(void)
+{
+ int cpu;
+ unsigned int tfms, junk, cap, capamd;
+ struct sigaction oldsa;
+
+ sigaction(SIGILL, NULL, &oldsa);
+ signal(SIGILL, model3);
+
+ if (sigsetjmp(jenv, 1)) {
+ sigaction(SIGILL, &oldsa, NULL);
+ return 3;
+ }
+
+ if (cpuid_eax(0x000000000)==0) {
+ sigaction(SIGILL, &oldsa, NULL);
+ return 4;
+ }
+
+ cpuid(0x00000001, &tfms, &junk, &junk, &cap);
+ cpuid(0x80000001, &junk, &junk, &junk, &capamd);
+
+ cpu = (tfms>>8)&15;
+
+ sigaction(SIGILL, &oldsa, NULL);
+
+ if (cpu < 6)
+ return cpu;
+
+ if (cap & (1<<15)) {
+ /* CMOV supported? */
+ if (capamd & (1<<30))
+ return 7; /* 3DNOWEXT supported */
+ return 6;
+ }
+
+ return 5;
+}
+
+/* should only be called for model 6 CPU's */
+static int is_athlon(void)
+{
+ unsigned int eax, ebx, ecx, edx;
+ char vendor[16];
+ int i;
+
+ cpuid (0, &eax, &ebx, &ecx, &edx);
+
+ /* If you care about space, you can just check ebx, ecx and edx directly
+ instead of forming a string first and then doing a strcmp */
+ memset(vendor, 0, sizeof(vendor));
+
+ for (i=0; i<4; i++)
+ vendor[i] = (unsigned char) (ebx >>(8*i));
+ for (i=0; i<4; i++)
+ vendor[4+i] = (unsigned char) (edx >>(8*i));
+ for (i=0; i<4; i++)
+ vendor[8+i] = (unsigned char) (ecx >>(8*i));
+
+ if (!rstreqn(vendor, "AuthenticAMD", 12))
+ return 0;
+
+ return 1;
+}
+
+static int is_pentium3()
+{
+ unsigned int eax, ebx, ecx, edx, family, model;
+ char vendor[16];
+ cpuid(0, &eax, &ebx, &ecx, &edx);
+ memset(vendor, 0, sizeof(vendor));
+ *((unsigned int *)&vendor[0]) = ebx;
+ *((unsigned int *)&vendor[4]) = edx;
+ *((unsigned int *)&vendor[8]) = ecx;
+ if (!rstreqn(vendor, "GenuineIntel", 12))
+ return 0;
+ cpuid(1, &eax, &ebx, &ecx, &edx);
+ family = (eax >> 8) & 0x0f;
+ model = (eax >> 4) & 0x0f;
+ if (family == 6)
+ switch (model)
+ {
+ case 7: // Pentium III, Pentium III Xeon (model 7)
+ case 8: // Pentium III, Pentium III Xeon, Celeron (model 8)
+ case 9: // Pentium M
+ /*
+ Intel recently announced its new technology for mobile platforms,
+ named Centrino, and presents it as a big advance in mobile PCs.
+ One of the main part of Centrino consists in a brand new CPU,
+ the Pentium M, codenamed Banias, that we'll study in this review.
+ A particularity of this CPU is that it was designed for mobile platform
+ exclusively, unlike previous mobile CPU (Pentium III-M, Pentium 4-M)
+ that share the same micro-architecture as their desktop counterparts.
+ The Pentium M introduces a new micro-architecture, adapted for mobility
+ constraints, and that is halfway between the Pentium III and the Pentium 4.
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ */
+ case 10: // Pentium III Xeon (model A)
+ case 11: // Pentium III (model B)
+ return 1;
+ }
+ return 0;
+}
+
+static int is_pentium4()
+{
+ unsigned int eax, ebx, ecx, edx, family, model;
+ char vendor[16];
+ cpuid(0, &eax, &ebx, &ecx, &edx);
+ memset(vendor, 0, sizeof(vendor));
+ *((unsigned int *)&vendor[0]) = ebx;
+ *((unsigned int *)&vendor[4]) = edx;
+ *((unsigned int *)&vendor[8]) = ecx;
+ if (!rstreqn(vendor, "GenuineIntel", 12))
+ return 0;
+ cpuid(1, &eax, &ebx, &ecx, &edx);
+ family = (eax >> 8) & 0x0f;
+ model = (eax >> 4) & 0x0f;
+ if (family == 15)
+ switch (model)
+ {
+ case 0: // Pentium 4, Pentium 4 Xeon (0.18um)
+ case 1: // Pentium 4, Pentium 4 Xeon MP, Celeron (0.18um)
+ case 2: // Pentium 4, Mobile Pentium 4-M,
+ // Pentium 4 Xeon, Pentium 4 Xeon MP,
+ // Celeron, Mobile Celron (0.13um)
+ case 3: // Pentium 4, Celeron (0.09um)
+ return 1;
+ }
+ return 0;
+}
+
+static int is_geode()
+{
+ unsigned int eax, ebx, ecx, edx, family, model;
+ char vendor[16];
+ /* If you care about space, you can just check ebx, ecx and edx directly
+ instead of forming a string first and then doing a strcmp */
+ memset(vendor, 0, sizeof(vendor));
+
+ cpuid(0, &eax, &ebx, &ecx, &edx);
+ memset(vendor, 0, sizeof(vendor));
+ *((unsigned int *)&vendor[0]) = ebx;
+ *((unsigned int *)&vendor[4]) = edx;
+ *((unsigned int *)&vendor[8]) = ecx;
+ if (!rstreqn(vendor, "AuthenticAMD", 12))
+ return 0;
+ cpuid(1, &eax, &ebx, &ecx, &edx);
+ family = (eax >> 8) & 0x0f;
+ model = (eax >> 4) & 0x0f;
+ if (family == 5)
+ switch (model)
+ {
+ case 10: // Geode
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+#if defined(__linux__) && defined(__powerpc__)
+static jmp_buf mfspr_jmpbuf;
+
+static void mfspr_ill(int notused)
+{
+ longjmp(mfspr_jmpbuf, -1);
+}
+#endif
+
+/**
+ */
+static void defaultMachine(const char ** arch,
+ const char ** os)
+{
+ static struct utsname un;
+ static int gotDefaults = 0;
+ char * chptr;
+ canonEntry canon;
+ int rc;
+
+ while (!gotDefaults) {
+ if (!rpmPlatform(platform)) {
+ char * s;
+ s = rpmExpand("%{_host_cpu}", NULL);
+ if (s) {
+ rstrlcpy(un.machine, s, sizeof(un.machine));
+ s = _free(s);
+ }
+ s = rpmExpand("%{_host_os}", NULL);
+ if (s) {
+ rstrlcpy(un.sysname, s, sizeof(un.sysname));
+ s = _free(s);
+ }
+ gotDefaults = 1;
+ break;
+ }
+ rc = uname(&un);
+ if (rc < 0) return;
+
+#if !defined(__linux__)
+#ifdef SNI
+ /* USUALLY un.sysname on sinix does start with the word "SINIX"
+ * let's be absolutely sure
+ */
+ strncpy(un.sysname, "SINIX", sizeof(un.sysname));
+#endif
+ if (rstreq(un.sysname, "AIX")) {
+ strcpy(un.machine, __power_pc() ? "ppc" : "rs6000");
+ sprintf(un.sysname,"aix%s.%s", un.version, un.release);
+ }
+ else if(rstreq(un.sysname, "Darwin")) {
+#ifdef __ppc__
+ strcpy(un.machine, "ppc");
+#else ifdef __i386__
+ strcpy(un.machine, "i386");
+#endif
+ }
+ else if (rstreq(un.sysname, "SunOS")) {
+ if (rstreqn(un.release,"4", 1)) /* SunOS 4.x */ {
+ int fd;
+ for (fd = 0;
+ (un.release[fd] != 0 && (fd < sizeof(un.release)));
+ fd++) {
+ if (!risdigit(un.release[fd]) && (un.release[fd] != '.')) {
+ un.release[fd] = 0;
+ break;
+ }
+ }
+ sprintf(un.sysname,"sunos%s",un.release);
+ }
+
+ else /* Solaris 2.x: n.x.x becomes n-3.x.x */
+ sprintf(un.sysname, "solaris%1d%s", atoi(un.release)-3,
+ un.release+1+(atoi(un.release)/10));
+
+ /* Solaris on Intel hardware reports i86pc instead of i386
+ * (at least on 2.6 and 2.8)
+ */
+ if (rstreq(un.machine, "i86pc"))
+ sprintf(un.machine, "i386");
+ }
+ else if (rstreq(un.sysname, "HP-UX"))
+ /*make un.sysname look like hpux9.05 for example*/
+ sprintf(un.sysname, "hpux%s", strpbrk(un.release, "123456789"));
+ else if (rstreq(un.sysname, "OSF1"))
+ /*make un.sysname look like osf3.2 for example*/
+ sprintf(un.sysname, "osf%s", strpbrk(un.release, "123456789"));
+ else if (rstreqn(un.sysname, "IP", 2))
+ un.sysname[2] = '\0';
+ else if (rstreqn(un.sysname, "SINIX", 5)) {
+ sprintf(un.sysname, "sinix%s",un.release);
+ if (rstreqn(un.machine, "RM", 2))
+ sprintf(un.machine, "mips");
+ }
+ else if ((rstreqn(un.machine, "34", 2) ||
+ rstreqn(un.machine, "33", 2)) && \
+ rstreqn(un.release, "4.0", 3))
+ {
+ /* we are on ncr-sysv4 */
+ char * prelid = NULL;
+ FD_t fd = Fopen("/etc/.relid", "r.fdio");
+ int gotit = 0;
+ if (fd != NULL && !Ferror(fd)) {
+ chptr = xcalloc(1, 256);
+ { int irelid = Fread(chptr, sizeof(*chptr), 256, fd);
+ (void) Fclose(fd);
+ /* example: "112393 RELEASE 020200 Version 01 OS" */
+ if (irelid > 0) {
+ if ((prelid = strstr(chptr, "RELEASE "))){
+ prelid += strlen("RELEASE ")+1;
+ sprintf(un.sysname,"ncr-sysv4.%.*s",1,prelid);
+ gotit = 1;
+ }
+ }
+ }
+ chptr = _free (chptr);
+ }
+ if (!gotit) /* parsing /etc/.relid file failed? */
+ strcpy(un.sysname,"ncr-sysv4");
+ /* wrong, just for now, find out how to look for i586 later*/
+ strcpy(un.machine,"i486");
+ }
+#endif /* __linux__ */
+
+ /* get rid of the hyphens in the sysname */
+ for (chptr = un.machine; *chptr != '\0'; chptr++)
+ if (*chptr == '/') *chptr = '-';
+
+# if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL)
+ /* little endian */
+ strcpy(un.machine, "mipsel");
+# elif defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB)
+ /* big endian */
+ strcpy(un.machine, "mips");
+# endif
+
+# if defined(__hpux) && defined(_SC_CPU_VERSION)
+ {
+# if !defined(CPU_PA_RISC1_2)
+# define CPU_PA_RISC1_2 0x211 /* HP PA-RISC1.2 */
+# endif
+# if !defined(CPU_PA_RISC2_0)
+# define CPU_PA_RISC2_0 0x214 /* HP PA-RISC2.0 */
+# endif
+ int cpu_version = sysconf(_SC_CPU_VERSION);
+
+# if defined(CPU_HP_MC68020)
+ if (cpu_version == CPU_HP_MC68020)
+ strcpy(un.machine, "m68k");
+# endif
+# if defined(CPU_HP_MC68030)
+ if (cpu_version == CPU_HP_MC68030)
+ strcpy(un.machine, "m68k");
+# endif
+# if defined(CPU_HP_MC68040)
+ if (cpu_version == CPU_HP_MC68040)
+ strcpy(un.machine, "m68k");
+# endif
+
+# if defined(CPU_PA_RISC1_0)
+ if (cpu_version == CPU_PA_RISC1_0)
+ strcpy(un.machine, "hppa1.0");
+# endif
+# if defined(CPU_PA_RISC1_1)
+ if (cpu_version == CPU_PA_RISC1_1)
+ strcpy(un.machine, "hppa1.1");
+# endif
+# if defined(CPU_PA_RISC1_2)
+ if (cpu_version == CPU_PA_RISC1_2)
+ strcpy(un.machine, "hppa1.2");
+# endif
+# if defined(CPU_PA_RISC2_0)
+ if (cpu_version == CPU_PA_RISC2_0)
+ strcpy(un.machine, "hppa2.0");
+# endif
+ }
+# endif /* hpux */
+
+# if defined(__linux__) && defined(__sparc__)
+ if (rstreq(un.machine, "sparc")) {
+ #define PERS_LINUX 0x00000000
+ #define PERS_LINUX_32BIT 0x00800000
+ #define PERS_LINUX32 0x00000008
+
+ extern int personality(unsigned long);
+ int oldpers;
+
+ oldpers = personality(PERS_LINUX_32BIT);
+ if (oldpers != -1) {
+ if (personality(PERS_LINUX) != -1) {
+ uname(&un);
+ if (rstreq(un.machine, "sparc64")) {
+ strcpy(un.machine, "sparcv9");
+ oldpers = PERS_LINUX32;
+ }
+ }
+ personality(oldpers);
+ }
+ }
+# endif /* sparc*-linux */
+
+# if defined(__GNUC__) && defined(__alpha__)
+ {
+ unsigned long amask, implver;
+ register long v0 __asm__("$0") = -1;
+ __asm__ (".long 0x47e00c20" : "=r"(v0) : "0"(v0));
+ amask = ~v0;
+ __asm__ (".long 0x47e03d80" : "=r"(v0));
+ implver = v0;
+ switch (implver) {
+ case 1:
+ switch (amask) {
+ case 0: strcpy(un.machine, "alphaev5"); break;
+ case 1: strcpy(un.machine, "alphaev56"); break;
+ case 0x101: strcpy(un.machine, "alphapca56"); break;
+ }
+ break;
+ case 2:
+ switch (amask) {
+ case 0x303: strcpy(un.machine, "alphaev6"); break;
+ case 0x307: strcpy(un.machine, "alphaev67"); break;
+ }
+ break;
+ }
+ }
+# endif
+
+# if defined(__linux__) && defined(__i386__)
+ {
+ char mclass = (char) (RPMClass() | '0');
+
+ if ((mclass == '6' && is_athlon()) || mclass == '7')
+ strcpy(un.machine, "athlon");
+ else if (is_pentium4())
+ strcpy(un.machine, "pentium4");
+ else if (is_pentium3())
+ strcpy(un.machine, "pentium3");
+ else if (is_geode())
+ strcpy(un.machine, "geode");
+ else if (strchr("3456", un.machine[1]) && un.machine[1] != mclass)
+ un.machine[1] = mclass;
+ }
+# endif
+
+ /* the uname() result goes through the arch_canon table */
+ canon = lookupInCanonTable(un.machine,
+ tables[RPM_MACHTABLE_INSTARCH].canons,
+ tables[RPM_MACHTABLE_INSTARCH].canonsLength);
+ if (canon)
+ rstrlcpy(un.machine, canon->short_name, sizeof(un.machine));
+
+ canon = lookupInCanonTable(un.sysname,
+ tables[RPM_MACHTABLE_INSTOS].canons,
+ tables[RPM_MACHTABLE_INSTOS].canonsLength);
+ if (canon)
+ rstrlcpy(un.sysname, canon->short_name, sizeof(un.sysname));
+ gotDefaults = 1;
+ break;
+ }
+
+ if (arch) *arch = un.machine;
+ if (os) *os = un.sysname;
+}
+
+static
+const char * rpmGetVarArch(int var, const char * arch)
+{
+ const struct rpmvarValue * next;
+
+ if (arch == NULL) arch = current[ARCH];
+
+ if (arch) {
+ next = &values[var];
+ while (next) {
+ if (next->arch && rstreq(next->arch, arch)) return next->value;
+ next = next->next;
+ }
+ }
+
+ next = values + var;
+ while (next && next->arch) next = next->next;
+
+ return next ? next->value : NULL;
+}
+
+static const char *rpmGetVar(int var)
+{
+ return rpmGetVarArch(var, NULL);
+}
+
+static void rpmSetVarArch(int var, const char * val, const char * arch)
+{
+ struct rpmvarValue * next = values + var;
+
+ if (next->value) {
+ if (arch) {
+ while (next->next) {
+ if (next->arch && rstreq(next->arch, arch)) break;
+ next = next->next;
+ }
+ } else {
+ while (next->next) {
+ if (!next->arch) break;
+ next = next->next;
+ }
+ }
+
+ if (next->arch && arch && rstreq(next->arch, arch)) {
+ next->value = _free(next->value);
+ next->arch = _free(next->arch);
+ } else if (next->arch || arch) {
+ next->next = xmalloc(sizeof(*next->next));
+ next = next->next;
+ next->value = NULL;
+ next->arch = NULL;
+ next->next = NULL;
+ }
+ }
+
+ next->value = _free(next->value);
+ next->value = xstrdup(val);
+ next->arch = (arch ? xstrdup(arch) : NULL);
+}
+
+void rpmSetTables(int archTable, int osTable)
+{
+ const char * arch, * os;
+
+ defaultMachine(&arch, &os);
+
+ if (currTables[ARCH] != archTable) {
+ currTables[ARCH] = archTable;
+ rebuildCompatTables(ARCH, arch);
+ }
+
+ if (currTables[OS] != osTable) {
+ currTables[OS] = osTable;
+ rebuildCompatTables(OS, os);
+ }
+}
+
+int rpmMachineScore(int type, const char * name)
+{
+ machEquivInfo info = NULL;
+ if (name)
+ info = machEquivSearch(&tables[type].equiv, name);
+ return info ? info->score : 0;
+}
+
+int rpmIsKnownArch(const char *name)
+{
+ canonEntry canon = lookupInCanonTable(name,
+ tables[RPM_MACHTABLE_INSTARCH].canons,
+ tables[RPM_MACHTABLE_INSTARCH].canonsLength);
+ return (canon != NULL || rstreq(name, "noarch"));
+}
+
+/** \ingroup rpmrc
+ * Set current arch/os names.
+ * NULL as argument is set to the default value (munged uname())
+ * pushed through a translation table (if appropriate).
+ * @deprecated Use addMacro to set _target_* macros.
+ * @todo Eliminate
+ *
+ * @param arch arch name (or NULL)
+ * @param os os name (or NULL)
+ * */
+
+static void rpmSetMachine(const char * arch, const char * os)
+{
+ const char * host_cpu, * host_os;
+
+ defaultMachine(&host_cpu, &host_os);
+
+ if (arch == NULL) {
+ arch = host_cpu;
+ if (tables[currTables[ARCH]].hasTranslate)
+ arch = lookupInDefaultTable(arch,
+ tables[currTables[ARCH]].defaults,
+ tables[currTables[ARCH]].defaultsLength);
+ }
+ if (arch == NULL) return; /* XXX can't happen */
+
+ if (os == NULL) {
+ os = host_os;
+ if (tables[currTables[OS]].hasTranslate)
+ os = lookupInDefaultTable(os,
+ tables[currTables[OS]].defaults,
+ tables[currTables[OS]].defaultsLength);
+ }
+ if (os == NULL) return; /* XXX can't happen */
+
+ if (!current[ARCH] || !rstreq(arch, current[ARCH])) {
+ current[ARCH] = _free(current[ARCH]);
+ current[ARCH] = xstrdup(arch);
+ rebuildCompatTables(ARCH, host_cpu);
+ }
+
+ if (!current[OS] || !rstreq(os, current[OS])) {
+ char * t = xstrdup(os);
+ current[OS] = _free(current[OS]);
+ /*
+ * XXX Capitalizing the 'L' is needed to insure that old
+ * XXX os-from-uname (e.g. "Linux") is compatible with the new
+ * XXX os-from-platform (e.g "linux" from "sparc-*-linux").
+ * XXX A copy of this string is embedded in headers and is
+ * XXX used by rpmInstallPackage->{os,arch}Okay->rpmMachineScore->
+ * XXX to verify correct arch/os from headers.
+ */
+ if (rstreq(t, "linux"))
+ *t = 'L';
+ current[OS] = t;
+
+ rebuildCompatTables(OS, host_os);
+ }
+}
+
+static void rebuildCompatTables(int type, const char * name)
+{
+ machFindEquivs(&tables[currTables[type]].cache,
+ &tables[currTables[type]].equiv,
+ name);
+}
+
+static void getMachineInfo(int type, const char ** name,
+ int * num)
+{
+ canonEntry canon;
+ int which = currTables[type];
+
+ /* use the normal canon tables, even if we're looking up build stuff */
+ if (which >= 2) which -= 2;
+
+ canon = lookupInCanonTable(current[type],
+ tables[which].canons,
+ tables[which].canonsLength);
+
+ if (canon) {
+ if (num) *num = canon->num;
+ if (name) *name = canon->short_name;
+ } else {
+ if (num) *num = 255;
+ if (name) *name = current[type];
+
+ if (tables[currTables[type]].hasCanon) {
+ rpmlog(RPMLOG_WARNING, _("Unknown system: %s\n"), current[type]);
+ rpmlog(RPMLOG_WARNING, _("Please contact %s\n"), PACKAGE_BUGREPORT);
+ }
+ }
+}
+
+void rpmGetArchInfo(const char ** name, int * num)
+{
+ getMachineInfo(ARCH, name, num);
+}
+
+void rpmGetOsInfo(const char ** name, int * num)
+{
+ getMachineInfo(OS, name, num);
+}
+
+static void rpmRebuildTargetVars(const char ** target, const char ** canontarget)
+{
+
+ char *ca = NULL, *co = NULL, *ct = NULL;
+ int x;
+
+ /* Rebuild the compat table to recalculate the current target arch. */
+
+ rpmSetMachine(NULL, NULL);
+ rpmSetTables(RPM_MACHTABLE_INSTARCH, RPM_MACHTABLE_INSTOS);
+ rpmSetTables(RPM_MACHTABLE_BUILDARCH, RPM_MACHTABLE_BUILDOS);
+
+ if (target && *target) {
+ char *c;
+ /* Set arch and os from specified build target */
+ ca = xstrdup(*target);
+ if ((c = strchr(ca, '-')) != NULL) {
+ *c++ = '\0';
+
+ if ((co = strrchr(c, '-')) == NULL) {
+ co = c;
+ } else {
+ if (!rstrcasecmp(co, "-gnu"))
+ *co = '\0';
+ if ((co = strrchr(c, '-')) == NULL)
+ co = c;
+ else
+ co++;
+ }
+ if (co != NULL) co = xstrdup(co);
+ }
+ } else {
+ const char *a = NULL;
+ const char *o = NULL;
+ /* Set build target from rpm arch and os */
+ rpmGetArchInfo(&a, NULL);
+ ca = (a) ? xstrdup(a) : NULL;
+ rpmGetOsInfo(&o, NULL);
+ co = (o) ? xstrdup(o) : NULL;
+ }
+
+ /* If still not set, Set target arch/os from default uname(2) values */
+ if (ca == NULL) {
+ const char *a = NULL;
+ defaultMachine(&a, NULL);
+ ca = xstrdup(a ? a : "(arch)");
+ }
+ for (x = 0; ca[x] != '\0'; x++)
+ ca[x] = rtolower(ca[x]);
+
+ if (co == NULL) {
+ const char *o = NULL;
+ defaultMachine(NULL, &o);
+ co = xstrdup(o ? o : "(os)");
+ }
+ for (x = 0; co[x] != '\0'; x++)
+ co[x] = rtolower(co[x]);
+
+ /* XXX For now, set canonical target to arch-os */
+ if (ct == NULL) {
+ rasprintf(&ct, "%s-%s", ca, co);
+ }
+
+/*
+ * XXX All this macro pokery/jiggery could be achieved by doing a delayed
+ * rpmInitMacros(NULL, PER-PLATFORM-MACRO-FILE-NAMES);
+ */
+ delMacro(NULL, "_target");
+ addMacro(NULL, "_target", NULL, ct, RMIL_RPMRC);
+ delMacro(NULL, "_target_cpu");
+ addMacro(NULL, "_target_cpu", NULL, ca, RMIL_RPMRC);
+ delMacro(NULL, "_target_os");
+ addMacro(NULL, "_target_os", NULL, co, RMIL_RPMRC);
+/*
+ * XXX Make sure that per-arch optflags is initialized correctly.
+ */
+ { const char *optflags = rpmGetVarArch(RPMVAR_OPTFLAGS, ca);
+ if (optflags != NULL) {
+ delMacro(NULL, "optflags");
+ addMacro(NULL, "optflags", NULL, optflags, RMIL_RPMRC);
+ }
+ }
+
+ if (canontarget)
+ *canontarget = ct;
+ else
+ ct = _free(ct);
+ ca = _free(ca);
+ co = _free(co);
+}
+
+void rpmFreeRpmrc(void)
+{
+ int i, j, k;
+
+ if (platpat)
+ for (i = 0; i < nplatpat; i++)
+ platpat[i] = _free(platpat[i]);
+ platpat = _free(platpat);
+ nplatpat = 0;
+
+ for (i = 0; i < RPM_MACHTABLE_COUNT; i++) {
+ tableType t;
+ t = tables + i;
+ if (t->equiv.list) {
+ for (j = 0; j < t->equiv.count; j++)
+ t->equiv.list[j].name = _free(t->equiv.list[j].name);
+ t->equiv.list = _free(t->equiv.list);
+ t->equiv.count = 0;
+ }
+ if (t->cache.cache) {
+ for (j = 0; j < t->cache.size; j++) {
+ machCacheEntry e;
+ e = t->cache.cache + j;
+ if (e == NULL)
+ continue;
+ e->name = _free(e->name);
+ if (e->equivs) {
+ for (k = 0; k < e->count; k++)
+ e->equivs[k] = _free(e->equivs[k]);
+ e->equivs = _free(e->equivs);
+ }
+ }
+ t->cache.cache = _free(t->cache.cache);
+ t->cache.size = 0;
+ }
+ if (t->defaults) {
+ for (j = 0; j < t->defaultsLength; j++) {
+ t->defaults[j].name = _free(t->defaults[j].name);
+ t->defaults[j].defName = _free(t->defaults[j].defName);
+ }
+ t->defaults = _free(t->defaults);
+ t->defaultsLength = 0;
+ }
+ if (t->canons) {
+ for (j = 0; j < t->canonsLength; j++) {
+ t->canons[j].name = _free(t->canons[j].name);
+ t->canons[j].short_name = _free(t->canons[j].short_name);
+ }
+ t->canons = _free(t->canons);
+ t->canonsLength = 0;
+ }
+ }
+
+ for (i = 0; i < RPMVAR_NUM; i++) {
+ struct rpmvarValue * vp;
+ while ((vp = values[i].next) != NULL) {
+ values[i].next = vp->next;
+ vp->value = _free(vp->value);
+ vp->arch = _free(vp->arch);
+ vp = _free(vp);
+ }
+ values[i].value = _free(values[i].value);
+ values[i].arch = _free(values[i].arch);
+ }
+ current[OS] = _free(current[OS]);
+ current[ARCH] = _free(current[ARCH]);
+ defaultsInitialized = 0;
+/* FIX: platpat/current may be NULL */
+
+ /* XXX doesn't really belong here but... */
+ rpmFreeCrypto();
+#ifdef WITH_LUA
+ rpmLuaFree();
+#endif
+
+ return;
+}
+
+/** \ingroup rpmrc
+ * Read rpmrc (and macro) configuration file(s).
+ * @param rcfiles colon separated files to read (NULL uses default)
+ * @return RPMRC_OK on success
+ */
+static rpmRC rpmReadRC(const char * rcfiles)
+{
+ ARGV_t p, globs = NULL, files = NULL;
+ rpmRC rc = RPMRC_FAIL;
+
+ if (!defaultsInitialized) {
+ setDefaults();
+ defaultsInitialized = 1;
+ }
+
+ if (rcfiles == NULL)
+ rcfiles = defrcfiles;
+
+ /* Expand any globs in rcfiles. Missing files are ok here. */
+ argvSplit(&globs, rcfiles, ":");
+ for (p = globs; *p; p++) {
+ ARGV_t av = NULL;
+ if (rpmGlob(*p, NULL, &av) == 0) {
+ argvAppend(&files, av);
+ argvFree(av);
+ }
+ }
+ argvFree(globs);
+
+ /* Read each file in rcfiles. */
+ for (p = files; p && *p; p++) {
+ /* XXX Only /usr/lib/rpm/rpmrc must exist in default rcfiles list */
+ if (access(*p, R_OK) != 0) {
+ if (rcfiles == defrcfiles && p != files)
+ continue;
+ rpmlog(RPMLOG_ERR, _("Unable to open %s for reading: %m.\n"), *p);
+ goto exit;
+ break;
+ } else {
+ rc = doReadRC(*p);
+ }
+ }
+ rc = RPMRC_OK;
+ rpmSetMachine(NULL, NULL); /* XXX WTFO? Why bother? */
+
+exit:
+ argvFree(files);
+ return rc;
+}
+
+int rpmReadConfigFiles(const char * file, const char * target)
+{
+ /* Force preloading of dlopen()'ed libraries in case we go chrooting */
+ (void) gethostbyname("localhost");
+ (void) rpmInitCrypto();
+
+ /* Preset target macros */
+ /* FIX: target can be NULL */
+ rpmRebuildTargetVars(&target, NULL);
+
+ /* Read the files */
+ if (rpmReadRC(file)) return -1;
+
+ if (macrofiles != NULL) {
+ char *mf = rpmGetPath(macrofiles, NULL);
+ rpmInitMacros(NULL, mf);
+ _free(mf);
+ }
+
+ /* Reset target macros */
+ rpmRebuildTargetVars(&target, NULL);
+
+ /* Finally set target platform */
+ { char *cpu = rpmExpand("%{_target_cpu}", NULL);
+ char *os = rpmExpand("%{_target_os}", NULL);
+ rpmSetMachine(cpu, os);
+ cpu = _free(cpu);
+ os = _free(os);
+ }
+
+#ifdef WITH_LUA
+ /* Force Lua state initialization */
+ rpmLuaInit();
+#endif
+
+ return 0;
+}
+
+int rpmShowRC(FILE * fp)
+{
+ const struct rpmOption *opt;
+ rpmds ds = NULL;
+ int i, xx;
+ machEquivTable equivTable;
+
+ /* the caller may set the build arch which should be printed here */
+ fprintf(fp, "ARCHITECTURE AND OS:\n");
+ fprintf(fp, "build arch : %s\n", current[ARCH]);
+
+ fprintf(fp, "compatible build archs:");
+ equivTable = &tables[RPM_MACHTABLE_BUILDARCH].equiv;
+ for (i = 0; i < equivTable->count; i++)
+ fprintf(fp," %s", equivTable->list[i].name);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "build os : %s\n", current[OS]);
+
+ fprintf(fp, "compatible build os's :");
+ equivTable = &tables[RPM_MACHTABLE_BUILDOS].equiv;
+ for (i = 0; i < equivTable->count; i++)
+ fprintf(fp," %s", equivTable->list[i].name);
+ fprintf(fp, "\n");
+
+ rpmSetTables(RPM_MACHTABLE_INSTARCH, RPM_MACHTABLE_INSTOS);
+ rpmSetMachine(NULL, NULL); /* XXX WTFO? Why bother? */
+
+ fprintf(fp, "install arch : %s\n", current[ARCH]);
+ fprintf(fp, "install os : %s\n", current[OS]);
+
+ fprintf(fp, "compatible archs :");
+ equivTable = &tables[RPM_MACHTABLE_INSTARCH].equiv;
+ for (i = 0; i < equivTable->count; i++)
+ fprintf(fp," %s", equivTable->list[i].name);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "compatible os's :");
+ equivTable = &tables[RPM_MACHTABLE_INSTOS].equiv;
+ for (i = 0; i < equivTable->count; i++)
+ fprintf(fp," %s", equivTable->list[i].name);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "\nRPMRC VALUES:\n");
+ for (i = 0, opt = optionTable; i < optionTableSize; i++, opt++) {
+ const char *s = rpmGetVar(opt->var);
+ if (s != NULL || rpmIsVerbose())
+ fprintf(fp, "%-21s : %s\n", opt->name, s ? s : "(not set)");
+ }
+ fprintf(fp, "\n");
+
+ fprintf(fp, "Features supported by rpmlib:\n");
+ xx = rpmdsRpmlib(&ds, NULL);
+ ds = rpmdsInit(ds);
+ while (rpmdsNext(ds) >= 0) {
+ const char * DNEVR = rpmdsDNEVR(ds);
+ if (DNEVR != NULL)
+ fprintf(fp, " %s\n", DNEVR+2);
+ }
+ ds = rpmdsFree(ds);
+ fprintf(fp, "\n");
+
+ rpmDumpMacroTable(NULL, fp);
+
+ return 0;
+}
diff --git a/lib/rpmscript.c b/lib/rpmscript.c
new file mode 100644
index 0000000..f24f865
--- /dev/null
+++ b/lib/rpmscript.c
@@ -0,0 +1,415 @@
+#include "system.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#define _RPMSQ_INTERNAL
+#include <rpm/rpmsq.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmio.h>
+#include <rpm/rpmlog.h>
+#include <rpm/header.h>
+
+#include "rpmio/rpmlua.h"
+#include "lib/rpmscript.h"
+
+#include "debug.h"
+
+/**
+ * Run internal Lua script.
+ */
+static rpmRC runLuaScript(int selinux, ARGV_const_t prefixes,
+ const char *sname, rpmlogLvl lvl, FD_t scriptFd,
+ ARGV_t * argvp, const char *script, int arg1, int arg2)
+{
+ rpmRC rc = RPMRC_FAIL;
+#ifdef WITH_LUA
+ ARGV_t argv = argvp ? *argvp : NULL;
+ rpmlua lua = NULL; /* Global state. */
+ rpmluav var;
+ int cwd = -1;
+
+ rpmlog(RPMLOG_DEBUG, "%s: running <lua> scriptlet.\n", sname);
+
+ /* Create arg variable */
+ rpmluaPushTable(lua, "arg");
+ var = rpmluavNew();
+ rpmluavSetListMode(var, 1);
+ if (argv) {
+ char **p;
+ for (p = argv; *p; p++) {
+ rpmluavSetValue(var, RPMLUAV_STRING, *p);
+ rpmluaSetVar(lua, var);
+ }
+ }
+ if (arg1 >= 0) {
+ rpmluavSetValueNum(var, arg1);
+ rpmluaSetVar(lua, var);
+ }
+ if (arg2 >= 0) {
+ rpmluavSetValueNum(var, arg2);
+ rpmluaSetVar(lua, var);
+ }
+ var = rpmluavFree(var);
+ rpmluaPop(lua);
+
+ /* Lua scripts can change our cwd and umask, save and restore */
+ /* XXX TODO: use cwd from chroot state to save unnecessary open here */
+ cwd = open(".", O_RDONLY);
+ if (cwd != -1) {
+ mode_t oldmask = umask(0);
+ umask(oldmask);
+
+ if (chdir("/") == 0 && rpmluaRunScript(lua, script, sname) == 0) {
+ rc = RPMRC_OK;
+ }
+ /* This failing would be fatal, return something different for it... */
+ if (fchdir(cwd)) {
+ rpmlog(RPMLOG_ERR, _("Unable to restore current directory: %m"));
+ rc = RPMRC_NOTFOUND;
+ }
+ close(cwd);
+ umask(oldmask);
+ }
+
+ rpmluaDelVar(lua, "arg");
+
+#else
+ rpmlog(lvl, _("<lua> scriptlet support not built in\n"));
+#endif
+
+ return rc;
+}
+
+static const char * const SCRIPT_PATH = "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin";
+
+static void doScriptExec(int selinux, ARGV_const_t argv, ARGV_const_t prefixes,
+ FD_t scriptFd, FD_t out)
+{
+ int pipes[2];
+ int flag;
+ int fdno;
+ int xx;
+ int open_max;
+
+ (void) signal(SIGPIPE, SIG_DFL);
+ pipes[0] = pipes[1] = 0;
+ /* make stdin inaccessible */
+ xx = pipe(pipes);
+ xx = close(pipes[1]);
+ xx = dup2(pipes[0], STDIN_FILENO);
+ xx = close(pipes[0]);
+
+ /* XXX Force FD_CLOEXEC on all inherited fdno's. */
+ open_max = sysconf(_SC_OPEN_MAX);
+ if (open_max == -1) {
+ open_max = 1024;
+ }
+ for (fdno = 3; fdno < open_max; fdno++) {
+ flag = fcntl(fdno, F_GETFD);
+ if (flag == -1 || (flag & FD_CLOEXEC))
+ continue;
+ xx = fcntl(fdno, F_SETFD, FD_CLOEXEC);
+ /* XXX W2DO? debug msg for inheirited fdno w/o FD_CLOEXEC */
+ }
+
+ if (scriptFd != NULL) {
+ int sfdno = Fileno(scriptFd);
+ int ofdno = Fileno(out);
+ if (sfdno != STDERR_FILENO)
+ xx = dup2(sfdno, STDERR_FILENO);
+ if (ofdno != STDOUT_FILENO)
+ xx = dup2(ofdno, STDOUT_FILENO);
+ /* make sure we don't close stdin/stderr/stdout by mistake! */
+ if (ofdno > STDERR_FILENO && ofdno != sfdno)
+ xx = Fclose (out);
+ if (sfdno > STDERR_FILENO && ofdno != sfdno)
+ xx = Fclose (scriptFd);
+ }
+
+ { char *ipath = rpmExpand("%{_install_script_path}", NULL);
+ const char *path = SCRIPT_PATH;
+
+ if (ipath && ipath[5] != '%')
+ path = ipath;
+
+ xx = setenv("PATH", path, 1);
+ ipath = _free(ipath);
+ }
+
+ for (ARGV_const_t pf = prefixes; pf && *pf; pf++) {
+ char *name = NULL;
+ int num = (pf - prefixes);
+
+ rasprintf(&name, "RPM_INSTALL_PREFIX%d", num);
+ setenv(name, *pf, 1);
+ free(name);
+
+ /* scripts might still be using the old style prefix */
+ if (num == 0) {
+ setenv("RPM_INSTALL_PREFIX", *pf, 1);
+ }
+ }
+
+ if (chdir("/") == 0) {
+ /* XXX Don't mtrace into children. */
+ unsetenv("MALLOC_CHECK_");
+
+ /* Permit libselinux to do the scriptlet exec. */
+ if (selinux == 1) {
+ xx = rpm_execcon(0, argv[0], argv, environ);
+ }
+
+ if (xx == 0) {
+ xx = execv(argv[0], argv);
+ }
+ }
+ _exit(127); /* exit 127 for compatibility with bash(1) */
+}
+
+static char * writeScript(const char *cmd, const char *script)
+{
+ char *fn = NULL;
+ size_t slen = strlen(script);
+ int ok = 0;
+ FD_t fd = rpmMkTempFile("/", &fn);
+
+ if (Ferror(fd))
+ goto exit;
+
+ if (rpmIsDebug() && (rstreq(cmd, "/bin/sh") || rstreq(cmd, "/bin/bash"))) {
+ static const char set_x[] = "set -x\n";
+ /* Assume failures will be caught by the write below */
+ Fwrite(set_x, sizeof(set_x[0]), sizeof(set_x)-1, fd);
+ }
+
+ ok = (Fwrite(script, sizeof(script[0]), slen, fd) == slen);
+
+exit:
+ if (!ok) fn = _free(fn);
+ Fclose(fd);
+ return fn;
+}
+
+/**
+ * Run an external script.
+ */
+static rpmRC runExtScript(int selinux, ARGV_const_t prefixes,
+ const char *sname, rpmlogLvl lvl, FD_t scriptFd,
+ ARGV_t * argvp, const char *script, int arg1, int arg2)
+{
+ FD_t out = NULL;
+ char * fn = NULL;
+ int xx;
+ rpmRC rc = RPMRC_FAIL;
+ struct rpmsqElem sq;
+
+ memset(&sq, 0, sizeof(sq));
+ sq.reaper = 1;
+
+ rpmlog(RPMLOG_DEBUG, "%s: scriptlet start\n", sname);
+
+ if (script) {
+ fn = writeScript(*argvp[0], script);
+ if (fn == NULL) {
+ rpmlog(RPMLOG_ERR,
+ _("Couldn't create temporary file for %s: %s\n"),
+ sname, strerror(errno));
+ goto exit;
+ }
+
+ argvAdd(argvp, fn);
+ if (arg1 >= 0) {
+ argvAddNum(argvp, arg1);
+ }
+ if (arg2 >= 0) {
+ argvAddNum(argvp, arg2);
+ }
+ }
+
+ if (scriptFd != NULL) {
+ if (rpmIsVerbose()) {
+ out = fdDup(Fileno(scriptFd));
+ } else {
+ out = Fopen("/dev/null", "w.fdio");
+ if (Ferror(out)) {
+ out = fdDup(Fileno(scriptFd));
+ }
+ }
+ } else {
+ out = fdDup(STDOUT_FILENO);
+ }
+ if (out == NULL) {
+ rpmlog(RPMLOG_ERR, _("Couldn't duplicate file descriptor: %s: %s\n"),
+ sname, strerror(errno));
+ goto exit;
+ }
+
+ xx = rpmsqFork(&sq);
+ if (sq.child == 0) {
+ rpmlog(RPMLOG_DEBUG, "%s: execv(%s) pid %d\n",
+ sname, *argvp[0], (unsigned)getpid());
+ doScriptExec(selinux, *argvp, prefixes, scriptFd, out);
+ }
+
+ if (sq.child == (pid_t)-1) {
+ rpmlog(RPMLOG_ERR, _("Couldn't fork %s: %s\n"), sname, strerror(errno));
+ goto exit;
+ }
+
+ rpmsqWait(&sq);
+
+ rpmlog(RPMLOG_DEBUG, "%s: waitpid(%d) rc %d status %x\n",
+ sname, (unsigned)sq.child, (unsigned)sq.reaped, sq.status);
+
+ if (sq.reaped < 0) {
+ rpmlog(lvl, _("%s scriptlet failed, waitpid(%d) rc %d: %s\n"),
+ sname, sq.child, sq.reaped, strerror(errno));
+ } else if (!WIFEXITED(sq.status) || WEXITSTATUS(sq.status)) {
+ if (WIFSIGNALED(sq.status)) {
+ rpmlog(lvl, _("%s scriptlet failed, signal %d\n"),
+ sname, WTERMSIG(sq.status));
+ } else {
+ rpmlog(lvl, _("%s scriptlet failed, exit status %d\n"),
+ sname, WEXITSTATUS(sq.status));
+ }
+ } else {
+ /* if we get this far we're clear */
+ rc = RPMRC_OK;
+ }
+
+exit:
+ if (out)
+ xx = Fclose(out); /* XXX dup'd STDOUT_FILENO */
+
+ if (fn) {
+ if (!rpmIsDebug())
+ xx = unlink(fn);
+ fn = _free(fn);
+ }
+ return rc;
+}
+
+rpmRC rpmScriptRun(rpmScript script, int arg1, int arg2, FD_t scriptFd,
+ ARGV_const_t prefixes, int warn_only, int selinux)
+{
+ ARGV_t args = NULL;
+ rpmlogLvl lvl = warn_only ? RPMLOG_WARNING : RPMLOG_ERR;
+ rpmRC rc;
+
+ if (script == NULL) return RPMRC_OK;
+
+ /* construct a new argv as we can't modify the one from header */
+ if (script->args) {
+ argvAppend(&args, script->args);
+ } else {
+ argvAdd(&args, "/bin/sh");
+ }
+
+ if (rstreq(args[0], "<lua>")) {
+ rc = runLuaScript(selinux, prefixes, script->descr, lvl, scriptFd, &args, script->body, arg1, arg2);
+ } else {
+ rc = runExtScript(selinux, prefixes, script->descr, lvl, scriptFd, &args, script->body, arg1, arg2);
+ }
+ argvFree(args);
+
+ return rc;
+}
+
+static rpmTagVal getProgTag(rpmTagVal scriptTag)
+{
+ switch (scriptTag) {
+ case RPMTAG_PREIN: return RPMTAG_PREINPROG;
+ case RPMTAG_POSTIN: return RPMTAG_POSTINPROG;
+ case RPMTAG_PREUN: return RPMTAG_PREUNPROG;
+ case RPMTAG_POSTUN: return RPMTAG_POSTUNPROG;
+ case RPMTAG_PRETRANS: return RPMTAG_PRETRANSPROG;
+ case RPMTAG_POSTTRANS: return RPMTAG_POSTTRANSPROG;
+ case RPMTAG_VERIFYSCRIPT: return RPMTAG_VERIFYSCRIPTPROG;
+ default: return RPMTAG_NOT_FOUND;
+ }
+}
+
+static rpmTagVal getFlagTag(rpmTagVal scriptTag)
+{
+ switch (scriptTag) {
+ case RPMTAG_PRETRANS: return RPMTAG_PRETRANSFLAGS;
+ case RPMTAG_POSTTRANS: return RPMTAG_POSTTRANSFLAGS;
+ case RPMTAG_PREUN: return RPMTAG_PREUNFLAGS;
+ case RPMTAG_POSTUN: return RPMTAG_POSTUNFLAGS;
+ case RPMTAG_PREIN: return RPMTAG_PREINFLAGS;
+ case RPMTAG_POSTIN: return RPMTAG_POSTINFLAGS;
+ case RPMTAG_VERIFYSCRIPT: return RPMTAG_VERIFYSCRIPTFLAGS;
+ case RPMTAG_TRIGGERSCRIPTS: return RPMTAG_TRIGGERSCRIPTFLAGS;
+ default:
+ break;
+ }
+ return RPMTAG_NOT_FOUND;
+}
+
+static const char * tag2sln(rpmTagVal tag)
+{
+ switch (tag) {
+ case RPMTAG_PRETRANS: return "%pretrans";
+ case RPMTAG_TRIGGERPREIN: return "%triggerprein";
+ case RPMTAG_PREIN: return "%pre";
+ case RPMTAG_POSTIN: return "%post";
+ case RPMTAG_TRIGGERIN: return "%triggerin";
+ case RPMTAG_TRIGGERUN: return "%triggerun";
+ case RPMTAG_PREUN: return "%preun";
+ case RPMTAG_POSTUN: return "%postun";
+ case RPMTAG_POSTTRANS: return "%posttrans";
+ case RPMTAG_TRIGGERPOSTUN: return "%triggerpostun";
+ case RPMTAG_VERIFYSCRIPT: return "%verify";
+ default: break;
+ }
+ return "%unknownscript";
+}
+
+rpmScript rpmScriptFromTag(Header h, rpmTagVal scriptTag)
+{
+ rpmScript script = NULL;
+ rpmTagVal progTag = getProgTag(scriptTag);
+
+ if (headerIsEntry(h, scriptTag) || headerIsEntry(h, progTag)) {
+ struct rpmtd_s prog;
+ char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
+ rpmscriptFlags flags = headerGetNumber(h, getFlagTag(scriptTag));
+
+ script = xcalloc(1, sizeof(*script));
+ script->tag = scriptTag;
+ rasprintf(&script->descr, "%s(%s)", tag2sln(scriptTag), nevra);
+ script->body = headerGetAsString(h, scriptTag);
+
+ /* macros need to be expanded before possible queryformat */
+ if (script->body && (flags & RPMSCRIPT_EXPAND)) {
+ char *body = rpmExpand(script->body, NULL);
+ free(script->body);
+ script->body = body;
+ }
+ if (script->body && (flags & RPMSCRIPT_QFORMAT)) {
+ /* XXX TODO: handle queryformat errors */
+ char *body = headerFormat(h, script->body, NULL);
+ free(script->body);
+ script->body = body;
+ }
+
+ if (headerGet(h, progTag, &prog, (HEADERGET_ALLOC|HEADERGET_ARGV))) {
+ script->args = prog.data;
+ }
+ free(nevra);
+ }
+ return script;
+}
+
+rpmScript rpmScriptFree(rpmScript script)
+{
+ if (script) {
+ free(script->args);
+ free(script->body);
+ free(script->descr);
+ free(script);
+ }
+ return NULL;
+}
diff --git a/lib/rpmscript.h b/lib/rpmscript.h
new file mode 100644
index 0000000..f10770c
--- /dev/null
+++ b/lib/rpmscript.h
@@ -0,0 +1,42 @@
+#ifndef _RPMSCRIPT_H
+#define _RPMSCRIPT_H
+
+#include <rpm/rpmtypes.h>
+#include <rpm/argv.h>
+
+enum rpmscriptFlags_e {
+ RPMSCRIPT_NONE = 0,
+ RPMSCRIPT_EXPAND = (1 << 0), /* macro expansion */
+ RPMSCRIPT_QFORMAT = (1 << 1), /* header queryformat expansion */
+};
+
+typedef rpmFlags rpmscriptFlags;
+
+typedef struct rpmScript_s * rpmScript;
+
+struct rpmScript_s {
+ rpmTagVal tag; /* script tag */
+ char **args; /* scriptlet call arguments */
+ char *body; /* script body */
+ char *descr; /* description for logging */
+ rpmscriptFlags flags; /* flags to control operation */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+RPM_GNUC_INTERNAL
+rpmScript rpmScriptFromTag(Header h, rpmTagVal scriptTag);
+
+RPM_GNUC_INTERNAL
+rpmScript rpmScriptFree(rpmScript script);
+
+RPM_GNUC_INTERNAL
+rpmRC rpmScriptRun(rpmScript script, int arg1, int arg2, FD_t scriptFd,
+ ARGV_const_t prefixes, int warn_only, int selinux);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _RPMSCRIPT_H */
diff --git a/lib/rpmtag.h b/lib/rpmtag.h
new file mode 100644
index 0000000..50939c6
--- /dev/null
+++ b/lib/rpmtag.h
@@ -0,0 +1,472 @@
+#ifndef _RPMTAG_H
+#define _RPMTAG_H
+
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Header private tags.
+ * @note General use tags should start at 1000 (RPM's tag space starts there).
+ */
+#define HEADER_IMAGE 61
+#define HEADER_SIGNATURES 62
+#define HEADER_IMMUTABLE 63
+#define HEADER_REGIONS 64
+#define HEADER_I18NTABLE 100
+#define HEADER_SIGBASE 256
+#define HEADER_TAGBASE 1000
+
+/** \ingroup rpmtag
+ * Tags identify data in package headers.
+ * @note tags should not have value 0!
+ * @note all new tags should be added above 5000
+ */
+/** @todo: Somehow supply type **/
+typedef enum rpmTag_e {
+ RPMTAG_NOT_FOUND = -1, /*!< Unknown tag */
+
+ RPMTAG_HEADERIMAGE = HEADER_IMAGE, /*!< Current image. */
+ RPMTAG_HEADERSIGNATURES = HEADER_SIGNATURES, /*!< Signatures. */
+ RPMTAG_HEADERIMMUTABLE = HEADER_IMMUTABLE, /*!< Original image. */
+ RPMTAG_HEADERREGIONS = HEADER_REGIONS, /*!< Regions. */
+
+ RPMTAG_HEADERI18NTABLE = HEADER_I18NTABLE, /* s[] !< I18N string locales. */
+
+/* Retrofit (and uniqify) signature tags for use by rpmTagGetName() and rpmQuery. */
+/* the md5 sum was broken *twice* on big endian machines */
+/* XXX 2nd underscore prevents tagTable generation */
+ RPMTAG_SIG_BASE = HEADER_SIGBASE,
+ RPMTAG_SIGSIZE = RPMTAG_SIG_BASE+1, /* i */
+ RPMTAG_SIGLEMD5_1 = RPMTAG_SIG_BASE+2, /* internal - obsolete */
+ RPMTAG_SIGPGP = RPMTAG_SIG_BASE+3, /* x */
+ RPMTAG_SIGLEMD5_2 = RPMTAG_SIG_BASE+4, /* x internal - obsolete */
+ RPMTAG_SIGMD5 = RPMTAG_SIG_BASE+5, /* x */
+#define RPMTAG_PKGID RPMTAG_SIGMD5 /* x */
+ RPMTAG_SIGGPG = RPMTAG_SIG_BASE+6, /* x */
+ RPMTAG_SIGPGP5 = RPMTAG_SIG_BASE+7, /* internal - obsolete */
+
+ RPMTAG_BADSHA1_1 = RPMTAG_SIG_BASE+8, /* internal - obsolete */
+ RPMTAG_BADSHA1_2 = RPMTAG_SIG_BASE+9, /* internal - obsolete */
+ RPMTAG_PUBKEYS = RPMTAG_SIG_BASE+10, /* s[] */
+ RPMTAG_DSAHEADER = RPMTAG_SIG_BASE+11, /* x */
+ RPMTAG_RSAHEADER = RPMTAG_SIG_BASE+12, /* x */
+ RPMTAG_SHA1HEADER = RPMTAG_SIG_BASE+13, /* s */
+#define RPMTAG_HDRID RPMTAG_SHA1HEADER /* s */
+ RPMTAG_LONGSIGSIZE = RPMTAG_SIG_BASE+14, /* l */
+ RPMTAG_LONGARCHIVESIZE = RPMTAG_SIG_BASE+15, /* l */
+
+ RPMTAG_NAME = 1000, /* s */
+#define RPMTAG_N RPMTAG_NAME /* s */
+ RPMTAG_VERSION = 1001, /* s */
+#define RPMTAG_V RPMTAG_VERSION /* s */
+ RPMTAG_RELEASE = 1002, /* s */
+#define RPMTAG_R RPMTAG_RELEASE /* s */
+ RPMTAG_EPOCH = 1003, /* i */
+#define RPMTAG_E RPMTAG_EPOCH /* i */
+ RPMTAG_SUMMARY = 1004, /* s{} */
+ RPMTAG_DESCRIPTION = 1005, /* s{} */
+ RPMTAG_BUILDTIME = 1006, /* i */
+ RPMTAG_BUILDHOST = 1007, /* s */
+ RPMTAG_INSTALLTIME = 1008, /* i */
+ RPMTAG_SIZE = 1009, /* i */
+ RPMTAG_DISTRIBUTION = 1010, /* s */
+ RPMTAG_VENDOR = 1011, /* s */
+ RPMTAG_GIF = 1012, /* x */
+ RPMTAG_XPM = 1013, /* x */
+ RPMTAG_LICENSE = 1014, /* s */
+ RPMTAG_PACKAGER = 1015, /* s */
+ RPMTAG_GROUP = 1016, /* s{} */
+ RPMTAG_CHANGELOG = 1017, /* s[] internal */
+ RPMTAG_SOURCE = 1018, /* s[] */
+ RPMTAG_PATCH = 1019, /* s[] */
+ RPMTAG_URL = 1020, /* s */
+ RPMTAG_OS = 1021, /* s legacy used int */
+ RPMTAG_ARCH = 1022, /* s legacy used int */
+ RPMTAG_PREIN = 1023, /* s */
+ RPMTAG_POSTIN = 1024, /* s */
+ RPMTAG_PREUN = 1025, /* s */
+ RPMTAG_POSTUN = 1026, /* s */
+ RPMTAG_OLDFILENAMES = 1027, /* s[] obsolete */
+ RPMTAG_FILESIZES = 1028, /* i[] */
+ RPMTAG_FILESTATES = 1029, /* c[] */
+ RPMTAG_FILEMODES = 1030, /* h[] */
+ RPMTAG_FILEUIDS = 1031, /* i[] internal - obsolete */
+ RPMTAG_FILEGIDS = 1032, /* i[] internal - obsolete */
+ RPMTAG_FILERDEVS = 1033, /* h[] */
+ RPMTAG_FILEMTIMES = 1034, /* i[] */
+ RPMTAG_FILEDIGESTS = 1035, /* s[] */
+#define RPMTAG_FILEMD5S RPMTAG_FILEDIGESTS /* s[] */
+ RPMTAG_FILELINKTOS = 1036, /* s[] */
+ RPMTAG_FILEFLAGS = 1037, /* i[] */
+ RPMTAG_ROOT = 1038, /* internal - obsolete */
+ RPMTAG_FILEUSERNAME = 1039, /* s[] */
+ RPMTAG_FILEGROUPNAME = 1040, /* s[] */
+ RPMTAG_EXCLUDE = 1041, /* internal - obsolete */
+ RPMTAG_EXCLUSIVE = 1042, /* internal - obsolete */
+ RPMTAG_ICON = 1043, /* x */
+ RPMTAG_SOURCERPM = 1044, /* s */
+ RPMTAG_FILEVERIFYFLAGS = 1045, /* i[] */
+ RPMTAG_ARCHIVESIZE = 1046, /* i */
+ RPMTAG_PROVIDENAME = 1047, /* s[] */
+#define RPMTAG_PROVIDES RPMTAG_PROVIDENAME /* s[] */
+#define RPMTAG_P RPMTAG_PROVIDENAME /* s[] */
+ RPMTAG_REQUIREFLAGS = 1048, /* i[] */
+ RPMTAG_REQUIRENAME = 1049, /* s[] */
+#define RPMTAG_REQUIRES RPMTAG_REQUIRENAME /* s[] */
+ RPMTAG_REQUIREVERSION = 1050, /* s[] */
+ RPMTAG_NOSOURCE = 1051, /* i */
+ RPMTAG_NOPATCH = 1052, /* i */
+ RPMTAG_CONFLICTFLAGS = 1053, /* i[] */
+ RPMTAG_CONFLICTNAME = 1054, /* s[] */
+#define RPMTAG_CONFLICTS RPMTAG_CONFLICTNAME /* s[] */
+#define RPMTAG_C RPMTAG_CONFLICTNAME /* s[] */
+ RPMTAG_CONFLICTVERSION = 1055, /* s[] */
+ RPMTAG_DEFAULTPREFIX = 1056, /* s internal - deprecated */
+ RPMTAG_BUILDROOT = 1057, /* s internal - obsolete */
+ RPMTAG_INSTALLPREFIX = 1058, /* s internal - deprecated */
+ RPMTAG_EXCLUDEARCH = 1059, /* s[] */
+ RPMTAG_EXCLUDEOS = 1060, /* s[] */
+ RPMTAG_EXCLUSIVEARCH = 1061, /* s[] */
+ RPMTAG_EXCLUSIVEOS = 1062, /* s[] */
+ RPMTAG_AUTOREQPROV = 1063, /* s internal */
+ RPMTAG_RPMVERSION = 1064, /* s */
+ RPMTAG_TRIGGERSCRIPTS = 1065, /* s[] */
+ RPMTAG_TRIGGERNAME = 1066, /* s[] */
+ RPMTAG_TRIGGERVERSION = 1067, /* s[] */
+ RPMTAG_TRIGGERFLAGS = 1068, /* i[] */
+ RPMTAG_TRIGGERINDEX = 1069, /* i[] */
+ RPMTAG_VERIFYSCRIPT = 1079, /* s */
+ RPMTAG_CHANGELOGTIME = 1080, /* i[] */
+ RPMTAG_CHANGELOGNAME = 1081, /* s[] */
+ RPMTAG_CHANGELOGTEXT = 1082, /* s[] */
+ RPMTAG_BROKENMD5 = 1083, /* internal - obsolete */
+ RPMTAG_PREREQ = 1084, /* internal */
+ RPMTAG_PREINPROG = 1085, /* s */
+ RPMTAG_POSTINPROG = 1086, /* s */
+ RPMTAG_PREUNPROG = 1087, /* s */
+ RPMTAG_POSTUNPROG = 1088, /* s */
+ RPMTAG_BUILDARCHS = 1089, /* s[] */
+ RPMTAG_OBSOLETENAME = 1090, /* s[] */
+#define RPMTAG_OBSOLETES RPMTAG_OBSOLETENAME /* s[] */
+#define RPMTAG_O RPMTAG_OBSOLETENAME /* s[] */
+ RPMTAG_VERIFYSCRIPTPROG = 1091, /* s */
+ RPMTAG_TRIGGERSCRIPTPROG = 1092, /* s[] */
+ RPMTAG_DOCDIR = 1093, /* internal */
+ RPMTAG_COOKIE = 1094, /* s */
+ RPMTAG_FILEDEVICES = 1095, /* i[] */
+ RPMTAG_FILEINODES = 1096, /* i[] */
+ RPMTAG_FILELANGS = 1097, /* s[] */
+ RPMTAG_PREFIXES = 1098, /* s[] */
+ RPMTAG_INSTPREFIXES = 1099, /* s[] */
+ RPMTAG_TRIGGERIN = 1100, /* internal */
+ RPMTAG_TRIGGERUN = 1101, /* internal */
+ RPMTAG_TRIGGERPOSTUN = 1102, /* internal */
+ RPMTAG_AUTOREQ = 1103, /* internal */
+ RPMTAG_AUTOPROV = 1104, /* internal */
+ RPMTAG_CAPABILITY = 1105, /* i internal - obsolete */
+ RPMTAG_SOURCEPACKAGE = 1106, /* i */
+ RPMTAG_OLDORIGFILENAMES = 1107, /* internal - obsolete */
+ RPMTAG_BUILDPREREQ = 1108, /* internal */
+ RPMTAG_BUILDREQUIRES = 1109, /* internal */
+ RPMTAG_BUILDCONFLICTS = 1110, /* internal */
+ RPMTAG_BUILDMACROS = 1111, /* internal - unused */
+ RPMTAG_PROVIDEFLAGS = 1112, /* i[] */
+ RPMTAG_PROVIDEVERSION = 1113, /* s[] */
+ RPMTAG_OBSOLETEFLAGS = 1114, /* i[] */
+ RPMTAG_OBSOLETEVERSION = 1115, /* s[] */
+ RPMTAG_DIRINDEXES = 1116, /* i[] */
+ RPMTAG_BASENAMES = 1117, /* s[] */
+ RPMTAG_DIRNAMES = 1118, /* s[] */
+ RPMTAG_ORIGDIRINDEXES = 1119, /* i[] relocation */
+ RPMTAG_ORIGBASENAMES = 1120, /* s[] relocation */
+ RPMTAG_ORIGDIRNAMES = 1121, /* s[] relocation */
+ RPMTAG_OPTFLAGS = 1122, /* s */
+ RPMTAG_DISTURL = 1123, /* s */
+ RPMTAG_PAYLOADFORMAT = 1124, /* s */
+ RPMTAG_PAYLOADCOMPRESSOR = 1125, /* s */
+ RPMTAG_PAYLOADFLAGS = 1126, /* s */
+ RPMTAG_INSTALLCOLOR = 1127, /* i transaction color when installed */
+ RPMTAG_INSTALLTID = 1128, /* i */
+ RPMTAG_REMOVETID = 1129, /* i */
+ RPMTAG_SHA1RHN = 1130, /* internal - obsolete */
+ RPMTAG_RHNPLATFORM = 1131, /* s internal - obsolete */
+ RPMTAG_PLATFORM = 1132, /* s */
+ RPMTAG_PATCHESNAME = 1133, /* s[] deprecated placeholder (SuSE) */
+ RPMTAG_PATCHESFLAGS = 1134, /* i[] deprecated placeholder (SuSE) */
+ RPMTAG_PATCHESVERSION = 1135, /* s[] deprecated placeholder (SuSE) */
+ RPMTAG_CACHECTIME = 1136, /* i internal - obsolete */
+ RPMTAG_CACHEPKGPATH = 1137, /* s internal - obsolete */
+ RPMTAG_CACHEPKGSIZE = 1138, /* i internal - obsolete */
+ RPMTAG_CACHEPKGMTIME = 1139, /* i internal - obsolete */
+ RPMTAG_FILECOLORS = 1140, /* i[] */
+ RPMTAG_FILECLASS = 1141, /* i[] */
+ RPMTAG_CLASSDICT = 1142, /* s[] */
+ RPMTAG_FILEDEPENDSX = 1143, /* i[] */
+ RPMTAG_FILEDEPENDSN = 1144, /* i[] */
+ RPMTAG_DEPENDSDICT = 1145, /* i[] */
+ RPMTAG_SOURCEPKGID = 1146, /* x */
+ RPMTAG_FILECONTEXTS = 1147, /* s[] - obsolete */
+ RPMTAG_FSCONTEXTS = 1148, /* s[] extension */
+ RPMTAG_RECONTEXTS = 1149, /* s[] extension */
+ RPMTAG_POLICIES = 1150, /* s[] selinux *.te policy file. */
+ RPMTAG_PRETRANS = 1151, /* s */
+ RPMTAG_POSTTRANS = 1152, /* s */
+ RPMTAG_PRETRANSPROG = 1153, /* s */
+ RPMTAG_POSTTRANSPROG = 1154, /* s */
+ RPMTAG_DISTTAG = 1155, /* s */
+ RPMTAG_SUGGESTSNAME = 1156, /* s[] extension (unimplemented) */
+#define RPMTAG_SUGGESTS RPMTAG_SUGGESTSNAME /* s[] (unimplemented) */
+ RPMTAG_SUGGESTSVERSION = 1157, /* s[] extension (unimplemented) */
+ RPMTAG_SUGGESTSFLAGS = 1158, /* i[] extension (unimplemented) */
+ RPMTAG_ENHANCESNAME = 1159, /* s[] extension placeholder (unimplemented) */
+#define RPMTAG_ENHANCES RPMTAG_ENHANCESNAME /* s[] (unimplemented) */
+ RPMTAG_ENHANCESVERSION = 1160, /* s[] extension placeholder (unimplemented) */
+ RPMTAG_ENHANCESFLAGS = 1161, /* i[] extension placeholder (unimplemented) */
+ RPMTAG_PRIORITY = 1162, /* i[] extension placeholder (unimplemented) */
+ RPMTAG_CVSID = 1163, /* s (unimplemented) */
+#define RPMTAG_SVNID RPMTAG_CVSID /* s (unimplemented) */
+ RPMTAG_BLINKPKGID = 1164, /* s[] (unimplemented) */
+ RPMTAG_BLINKHDRID = 1165, /* s[] (unimplemented) */
+ RPMTAG_BLINKNEVRA = 1166, /* s[] (unimplemented) */
+ RPMTAG_FLINKPKGID = 1167, /* s[] (unimplemented) */
+ RPMTAG_FLINKHDRID = 1168, /* s[] (unimplemented) */
+ RPMTAG_FLINKNEVRA = 1169, /* s[] (unimplemented) */
+ RPMTAG_PACKAGEORIGIN = 1170, /* s (unimplemented) */
+ RPMTAG_TRIGGERPREIN = 1171, /* internal */
+ RPMTAG_BUILDSUGGESTS = 1172, /* internal (unimplemented) */
+ RPMTAG_BUILDENHANCES = 1173, /* internal (unimplemented) */
+ RPMTAG_SCRIPTSTATES = 1174, /* i[] scriptlet exit codes (unimplemented) */
+ RPMTAG_SCRIPTMETRICS = 1175, /* i[] scriptlet execution times (unimplemented) */
+ RPMTAG_BUILDCPUCLOCK = 1176, /* i (unimplemented) */
+ RPMTAG_FILEDIGESTALGOS = 1177, /* i[] (unimplemented) */
+ RPMTAG_VARIANTS = 1178, /* s[] (unimplemented) */
+ RPMTAG_XMAJOR = 1179, /* i (unimplemented) */
+ RPMTAG_XMINOR = 1180, /* i (unimplemented) */
+ RPMTAG_REPOTAG = 1181, /* s (unimplemented) */
+ RPMTAG_KEYWORDS = 1182, /* s[] (unimplemented) */
+ RPMTAG_BUILDPLATFORMS = 1183, /* s[] (unimplemented) */
+ RPMTAG_PACKAGECOLOR = 1184, /* i (unimplemented) */
+ RPMTAG_PACKAGEPREFCOLOR = 1185, /* i (unimplemented) */
+ RPMTAG_XATTRSDICT = 1186, /* s[] (unimplemented) */
+ RPMTAG_FILEXATTRSX = 1187, /* i[] (unimplemented) */
+ RPMTAG_DEPATTRSDICT = 1188, /* s[] (unimplemented) */
+ RPMTAG_CONFLICTATTRSX = 1189, /* i[] (unimplemented) */
+ RPMTAG_OBSOLETEATTRSX = 1190, /* i[] (unimplemented) */
+ RPMTAG_PROVIDEATTRSX = 1191, /* i[] (unimplemented) */
+ RPMTAG_REQUIREATTRSX = 1192, /* i[] (unimplemented) */
+ RPMTAG_BUILDPROVIDES = 1193, /* internal (unimplemented) */
+ RPMTAG_BUILDOBSOLETES = 1194, /* internal (unimplemented) */
+ RPMTAG_DBINSTANCE = 1195, /* i extension */
+ RPMTAG_NVRA = 1196, /* s extension */
+ /* tags 1997-4999 reserved */
+ RPMTAG_FILENAMES = 5000, /* s[] extension */
+ RPMTAG_FILEPROVIDE = 5001, /* s[] extension */
+ RPMTAG_FILEREQUIRE = 5002, /* s[] extension */
+ RPMTAG_FSNAMES = 5003, /* s[] (unimplemented) */
+ RPMTAG_FSSIZES = 5004, /* l[] (unimplemented) */
+ RPMTAG_TRIGGERCONDS = 5005, /* s[] extension */
+ RPMTAG_TRIGGERTYPE = 5006, /* s[] extension */
+ RPMTAG_ORIGFILENAMES = 5007, /* s[] extension */
+ RPMTAG_LONGFILESIZES = 5008, /* l[] */
+ RPMTAG_LONGSIZE = 5009, /* l */
+ RPMTAG_FILECAPS = 5010, /* s[] */
+ RPMTAG_FILEDIGESTALGO = 5011, /* i file digest algorithm */
+ RPMTAG_BUGURL = 5012, /* s */
+ RPMTAG_EVR = 5013, /* s extension */
+ RPMTAG_NVR = 5014, /* s extension */
+ RPMTAG_NEVR = 5015, /* s extension */
+ RPMTAG_NEVRA = 5016, /* s extension */
+ RPMTAG_HEADERCOLOR = 5017, /* i extension */
+ RPMTAG_VERBOSE = 5018, /* i extension */
+ RPMTAG_EPOCHNUM = 5019, /* i extension */
+ RPMTAG_PREINFLAGS = 5020, /* i */
+ RPMTAG_POSTINFLAGS = 5021, /* i */
+ RPMTAG_PREUNFLAGS = 5022, /* i */
+ RPMTAG_POSTUNFLAGS = 5023, /* i */
+ RPMTAG_PRETRANSFLAGS = 5024, /* i */
+ RPMTAG_POSTTRANSFLAGS = 5025, /* i */
+ RPMTAG_VERIFYSCRIPTFLAGS = 5026, /* i */
+ RPMTAG_TRIGGERSCRIPTFLAGS = 5027, /* i[] */
+ RPMTAG_COLLECTIONS = 5029, /* s[] list of collections */
+ RPMTAG_POLICYNAMES = 5030, /* s[] */
+ RPMTAG_POLICYTYPES = 5031, /* s[] */
+ RPMTAG_POLICYTYPESINDEXES = 5032, /* i[] */
+ RPMTAG_POLICYFLAGS = 5033, /* i[] */
+ RPMTAG_VCS = 5034, /* s */
+ RPMTAG_ORDERNAME = 5035, /* s[] */
+ RPMTAG_ORDERVERSION = 5036, /* s[] */
+ RPMTAG_ORDERFLAGS = 5037, /* i[] */
+
+ RPMTAG_FIRSTFREE_TAG /*!< internal */
+} rpmTag;
+
+#define RPMTAG_EXTERNAL_TAG 1000000
+
+/** \ingroup rpmtag
+ * Rpm database index tags.
+ */
+typedef enum rpmDbiTag_e {
+ RPMDBI_PACKAGES = 0, /* Installed package headers. */
+ RPMDBI_LABEL = 2, /* NEVRA label pseudo index */
+ RPMDBI_NAME = RPMTAG_NAME,
+ RPMDBI_BASENAMES = RPMTAG_BASENAMES,
+ RPMDBI_GROUP = RPMTAG_GROUP,
+ RPMDBI_REQUIRENAME = RPMTAG_REQUIRENAME,
+ RPMDBI_PROVIDENAME = RPMTAG_PROVIDENAME,
+ RPMDBI_CONFLICTNAME = RPMTAG_CONFLICTNAME,
+ RPMDBI_OBSOLETENAME = RPMTAG_OBSOLETENAME,
+ RPMDBI_TRIGGERNAME = RPMTAG_TRIGGERNAME,
+ RPMDBI_DIRNAMES = RPMTAG_DIRNAMES,
+ RPMDBI_INSTALLTID = RPMTAG_INSTALLTID,
+ RPMDBI_SIGMD5 = RPMTAG_SIGMD5,
+ RPMDBI_SHA1HEADER = RPMTAG_SHA1HEADER,
+} rpmDbiTag;
+
+/** \ingroup signature
+ * Tags found in signature header from package.
+ */
+typedef enum rpmSigTag_e {
+ RPMSIGTAG_SIZE = 1000, /*!< internal Header+Payload size (32bit) in bytes. */
+ RPMSIGTAG_LEMD5_1 = 1001, /*!< internal Broken MD5, take 1 @deprecated legacy. */
+ RPMSIGTAG_PGP = 1002, /*!< internal PGP 2.6.3 signature. */
+ RPMSIGTAG_LEMD5_2 = 1003, /*!< internal Broken MD5, take 2 @deprecated legacy. */
+ RPMSIGTAG_MD5 = 1004, /*!< internal MD5 signature. */
+ RPMSIGTAG_GPG = 1005, /*!< internal GnuPG signature. */
+ RPMSIGTAG_PGP5 = 1006, /*!< internal PGP5 signature @deprecated legacy. */
+ RPMSIGTAG_PAYLOADSIZE = 1007,/*!< internal uncompressed payload size (32bit) in bytes. */
+ RPMSIGTAG_BADSHA1_1 = RPMTAG_BADSHA1_1, /*!< internal Broken SHA1, take 1. */
+ RPMSIGTAG_BADSHA1_2 = RPMTAG_BADSHA1_2, /*!< internal Broken SHA1, take 2. */
+ RPMSIGTAG_SHA1 = RPMTAG_SHA1HEADER, /*!< internal sha1 header digest. */
+ RPMSIGTAG_DSA = RPMTAG_DSAHEADER, /*!< internal DSA header signature. */
+ RPMSIGTAG_RSA = RPMTAG_RSAHEADER, /*!< internal RSA header signature. */
+ RPMSIGTAG_LONGSIZE = RPMTAG_LONGSIGSIZE, /*!< internal Header+Payload size (64bit) in bytes. */
+ RPMSIGTAG_LONGARCHIVESIZE = RPMTAG_LONGARCHIVESIZE, /*!< internal uncompressed payload size (64bit) in bytes. */
+} rpmSigTag;
+
+
+/** \ingroup header
+ * The basic types of data in tags from headers.
+ */
+typedef enum rpmTagType_e {
+#define RPM_MIN_TYPE 0
+ RPM_NULL_TYPE = 0,
+ RPM_CHAR_TYPE = 1,
+ RPM_INT8_TYPE = 2,
+ RPM_INT16_TYPE = 3,
+ RPM_INT32_TYPE = 4,
+ RPM_INT64_TYPE = 5,
+ RPM_STRING_TYPE = 6,
+ RPM_BIN_TYPE = 7,
+ RPM_STRING_ARRAY_TYPE = 8,
+ RPM_I18NSTRING_TYPE = 9,
+#define RPM_MAX_TYPE 9
+#define RPM_FORCEFREE_TYPE 0xff
+#define RPM_MASK_TYPE 0x0000ffff
+} rpmTagType;
+
+/** \ingroup rpmtag
+ * The classes of data in tags from headers.
+ */
+typedef enum rpmTagClass_e {
+ RPM_NULL_CLASS = 0,
+ RPM_NUMERIC_CLASS = 1,
+ RPM_STRING_CLASS = 2,
+ RPM_BINARY_CLASS = 3,
+} rpmTagClass;
+
+/** \ingroup header
+ * New rpm data types under consideration/development.
+ * These data types may (or may not) be added to rpm at some point. In order
+ * to avoid incompatibility with legacy versions of rpm, these data (sub-)types
+ * are introduced into the header by overloading RPM_BIN_TYPE, with the binary
+ * value of the tag a 16 byte image of what should/will be in the header index,
+ * followed by per-tag private data.
+ */
+typedef enum rpmSubTagType_e {
+ RPM_REGION_TYPE = -10,
+ RPM_BIN_ARRAY_TYPE = -11,
+ /*!<@todo Implement, kinda like RPM_STRING_ARRAY_TYPE for known (but variable)
+ length binary data. */
+ RPM_XREF_TYPE = -12
+ /*!<@todo Implement, intent is to to carry a (???,tagNum,valNum) cross
+ reference to retrieve data from other tags. */
+} rpmSubTagType;
+
+/** \ingroup header
+ * * Identify how to return the header data type.
+ * */
+enum rpmTagReturnType_e {
+ RPM_ANY_RETURN_TYPE = 0,
+ RPM_SCALAR_RETURN_TYPE = 0x00010000,
+ RPM_ARRAY_RETURN_TYPE = 0x00020000,
+ RPM_MAPPING_RETURN_TYPE = 0x00040000,
+ RPM_MASK_RETURN_TYPE = 0xffff0000
+};
+
+typedef rpmFlags rpmTagReturnType;
+
+/** \ingroup rpmtag
+ * Return tag name from value.
+ * @param tag tag value
+ * @return tag name, "(unknown)" on not found
+ */
+const char * rpmTagGetName(rpmTagVal tag);
+
+/** \ingroup rpmtag
+ * Return tag data type from value.
+ * @param tag tag value
+ * @return tag data type + return type, RPM_NULL_TYPE on not found.
+ */
+rpmTagType rpmTagGetType(rpmTagVal tag);
+
+/** \ingroup rpmtag
+ * Return tag data type from value.
+ * @param tag tag value
+ * @return tag data type, RPM_NULL_TYPE on not found.
+ */
+rpmTagType rpmTagGetTagType(rpmTagVal tag);
+
+/** \ingroup rpmtag
+ * Return tag data type from value.
+ * @param tag tag value
+ * @return tag data return type, RPM_NULL_TYPE on not found.
+ */
+rpmTagReturnType rpmTagGetReturnType(rpmTagVal tag);
+
+/** \ingroup rpmtag
+ * Return tag data class from value.
+ * @param tag tag value
+ * @return tag data class, RPM_NULL_CLASS on not found.
+ */
+rpmTagClass rpmTagGetClass(rpmTagVal tag);
+
+/** \ingroup rpmtag
+ * Return tag value from name.
+ * @param tagstr name of tag
+ * @return tag value, -1 on not found
+ */
+rpmTagVal rpmTagGetValue(const char * tagstr);
+
+/** \ingroup rpmtag
+ * Return data class of type
+ * @param type tag type
+ * @return data class, RPM_NULL_CLASS on unknown.
+ */
+rpmTagClass rpmTagTypeGetClass(rpmTagType type);
+
+/** \ingroup rpmtag
+ * Return known rpm tag names, sorted by name.
+ * @retval tagnames tag container of string array type
+ * @param fullname return short or full name
+ * @return number of tag names, 0 on error
+ */
+int rpmTagGetNames(rpmtd tagnames, int fullname);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMTAG_H */
diff --git a/lib/rpmtd.c b/lib/rpmtd.c
new file mode 100644
index 0000000..d941113
--- /dev/null
+++ b/lib/rpmtd.c
@@ -0,0 +1,443 @@
+#include "system.h"
+
+#include <rpm/rpmtd.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmpgp.h>
+#include "lib/misc.h" /* format function prototypes */
+
+#include "debug.h"
+
+rpmtd rpmtdNew(void)
+{
+ rpmtd td = xmalloc(sizeof(*td));
+ rpmtdReset(td);
+ return td;
+}
+
+rpmtd rpmtdFree(rpmtd td)
+{
+ /* permit free on NULL td */
+ if (td != NULL) {
+ /* XXX should we free data too - a flag maybe? */
+ free(td);
+ }
+ return NULL;
+}
+
+void rpmtdReset(rpmtd td)
+{
+ assert(td != NULL);
+
+ memset(td, 0, sizeof(*td));
+ td->ix = -1;
+}
+
+void rpmtdFreeData(rpmtd td)
+{
+ assert(td != NULL);
+
+ if (td->flags & RPMTD_ALLOCED) {
+ if (td->flags & RPMTD_PTR_ALLOCED) {
+ assert(td->data != NULL);
+ char **data = td->data;
+ for (int i = 0; i < td->count; i++) {
+ free(data[i]);
+ }
+ }
+ free(td->data);
+ }
+ rpmtdReset(td);
+}
+
+rpm_count_t rpmtdCount(rpmtd td)
+{
+ assert(td != NULL);
+ /* fix up for binary type abusing count as data length */
+ return (td->type == RPM_BIN_TYPE) ? 1 : td->count;
+}
+
+rpmTagVal rpmtdTag(rpmtd td)
+{
+ assert(td != NULL);
+ return td->tag;
+}
+
+rpmTagType rpmtdType(rpmtd td)
+{
+ assert(td != NULL);
+ return td->type;
+}
+
+rpmTagClass rpmtdClass(rpmtd td)
+{
+ assert(td != NULL);
+ return rpmTagTypeGetClass(td->type);
+}
+
+int rpmtdGetIndex(rpmtd td)
+{
+ assert(td != NULL);
+ return td->ix;
+}
+
+int rpmtdSetIndex(rpmtd td, int index)
+{
+ assert(td != NULL);
+
+ if (index < 0 || index >= rpmtdCount(td)) {
+ return -1;
+ }
+ td->ix = index;
+ return td->ix;
+}
+
+int rpmtdInit(rpmtd td)
+{
+ assert(td != NULL);
+
+ /* XXX check that this is an array type? */
+ td->ix = -1;
+ return 0;
+}
+
+int rpmtdNext(rpmtd td)
+{
+ assert(td != NULL);
+
+ int i = -1;
+
+ if (++td->ix >= 0) {
+ if (td->ix < rpmtdCount(td)) {
+ i = td->ix;
+ } else {
+ td->ix = i;
+ }
+ }
+ return i;
+}
+
+uint32_t *rpmtdNextUint32(rpmtd td)
+{
+ assert(td != NULL);
+ uint32_t *res = NULL;
+ if (rpmtdNext(td) >= 0) {
+ res = rpmtdGetUint32(td);
+ }
+ return res;
+}
+
+uint64_t *rpmtdNextUint64(rpmtd td)
+{
+ assert(td != NULL);
+ uint64_t *res = NULL;
+ if (rpmtdNext(td) >= 0) {
+ res = rpmtdGetUint64(td);
+ }
+ return res;
+}
+
+const char *rpmtdNextString(rpmtd td)
+{
+ assert(td != NULL);
+ const char *res = NULL;
+ if (rpmtdNext(td) >= 0) {
+ res = rpmtdGetString(td);
+ }
+ return res;
+}
+
+char * rpmtdGetChar(rpmtd td)
+{
+ char *res = NULL;
+
+ assert(td != NULL);
+
+ if (td->type == RPM_CHAR_TYPE) {
+ int ix = (td->ix >= 0 ? td->ix : 0);
+ res = (char *) td->data + ix;
+ }
+ return res;
+}
+uint16_t * rpmtdGetUint16(rpmtd td)
+{
+ uint16_t *res = NULL;
+
+ assert(td != NULL);
+
+ if (td->type == RPM_INT16_TYPE) {
+ int ix = (td->ix >= 0 ? td->ix : 0);
+ res = (uint16_t *) td->data + ix;
+ }
+ return res;
+}
+
+uint32_t * rpmtdGetUint32(rpmtd td)
+{
+ uint32_t *res = NULL;
+
+ assert(td != NULL);
+
+ if (td->type == RPM_INT32_TYPE) {
+ int ix = (td->ix >= 0 ? td->ix : 0);
+ res = (uint32_t *) td->data + ix;
+ }
+ return res;
+}
+
+uint64_t * rpmtdGetUint64(rpmtd td)
+{
+ uint64_t *res = NULL;
+
+ assert(td != NULL);
+
+ if (td->type == RPM_INT64_TYPE) {
+ int ix = (td->ix >= 0 ? td->ix : 0);
+ res = (uint64_t *) td->data + ix;
+ }
+ return res;
+}
+
+const char * rpmtdGetString(rpmtd td)
+{
+ const char *str = NULL;
+
+ assert(td != NULL);
+
+ if (td->type == RPM_STRING_TYPE) {
+ str = (const char *) td->data;
+ } else if (td->type == RPM_STRING_ARRAY_TYPE ||
+ td->type == RPM_I18NSTRING_TYPE) {
+ /* XXX TODO: check for array bounds */
+ int ix = (td->ix >= 0 ? td->ix : 0);
+ str = *((const char**) td->data + ix);
+ }
+ return str;
+}
+
+uint64_t rpmtdGetNumber(rpmtd td)
+{
+ assert(td != NULL);
+ uint64_t val = 0;
+ int ix = (td->ix >= 0 ? td->ix : 0);
+
+ switch (td->type) {
+ case RPM_INT64_TYPE:
+ val = *((uint64_t *) td->data + ix);
+ break;
+ case RPM_INT32_TYPE:
+ val = *((uint32_t *) td->data + ix);
+ break;
+ case RPM_INT16_TYPE:
+ val = *((uint16_t *) td->data + ix);
+ break;
+ case RPM_INT8_TYPE:
+ case RPM_CHAR_TYPE:
+ val = *((uint8_t *) td->data + ix);
+ break;
+ default:
+ break;
+ }
+ return val;
+}
+
+char *rpmtdFormat(rpmtd td, rpmtdFormats fmt, const char *errmsg)
+{
+ headerTagFormatFunction func = rpmHeaderFormatFuncByValue(fmt);
+ const char *err = NULL;
+ char *str = NULL;
+
+ if (func) {
+ char fmtbuf[50]; /* yuck, get rid of this */
+ strcpy(fmtbuf, "%");
+ str = func(td, fmtbuf);
+ } else {
+ err = _("Unknown format");
+ }
+
+ if (err && errmsg) {
+ errmsg = err;
+ }
+
+ return str;
+}
+
+int rpmtdSetTag(rpmtd td, rpmTagVal tag)
+{
+ assert(td != NULL);
+ rpmTagType newtype = rpmTagGetTagType(tag);
+ int rc = 0;
+
+ /*
+ * Sanity checks:
+ * - is the new tag valid at all
+ * - if changing tag of non-empty container, require matching type
+ */
+ if (newtype == RPM_NULL_TYPE)
+ goto exit;
+
+ if (td->data || td->count > 0) {
+ if (rpmTagGetTagType(td->tag) != rpmTagGetTagType(tag)) {
+ goto exit;
+ }
+ }
+
+ td->tag = tag;
+ td->type = newtype;
+ rc = 1;
+
+exit:
+ return rc;
+}
+
+static inline int rpmtdSet(rpmtd td, rpmTagVal tag, rpmTagType type,
+ rpm_constdata_t data, rpm_count_t count)
+{
+ rpmtdReset(td);
+ td->tag = tag;
+ td->type = type;
+ td->count = count;
+ /*
+ * Discards const, but we won't touch the data (even rpmtdFreeData()
+ * wont free it as allocation flags aren't set) so it's "ok".
+ * XXX: Should there be a separate RPMTD_FOO flag for "user data"?
+ */
+ td->data = (void *) data;
+ return 1;
+}
+
+int rpmtdFromUint8(rpmtd td, rpmTagVal tag, uint8_t *data, rpm_count_t count)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ rpmTagReturnType retype = rpmTagGetReturnType(tag);
+
+ if (count < 1)
+ return 0;
+
+ /*
+ * BIN type is really just an uint8_t array internally, it's just
+ * treated specially otherwise.
+ */
+ switch (type) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ if (retype != RPM_ARRAY_RETURN_TYPE && count > 1)
+ return 0;
+ /* fallthrough */
+ case RPM_BIN_TYPE:
+ break;
+ default:
+ return 0;
+ }
+
+ return rpmtdSet(td, tag, type, data, count);
+}
+
+int rpmtdFromUint16(rpmtd td, rpmTagVal tag, uint16_t *data, rpm_count_t count)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ rpmTagReturnType retype = rpmTagGetReturnType(tag);
+ if (type != RPM_INT16_TYPE || count < 1)
+ return 0;
+ if (retype != RPM_ARRAY_RETURN_TYPE && count > 1)
+ return 0;
+
+ return rpmtdSet(td, tag, type, data, count);
+}
+
+int rpmtdFromUint32(rpmtd td, rpmTagVal tag, uint32_t *data, rpm_count_t count)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ rpmTagReturnType retype = rpmTagGetReturnType(tag);
+ if (type != RPM_INT32_TYPE || count < 1)
+ return 0;
+ if (retype != RPM_ARRAY_RETURN_TYPE && count > 1)
+ return 0;
+
+ return rpmtdSet(td, tag, type, data, count);
+}
+
+int rpmtdFromUint64(rpmtd td, rpmTagVal tag, uint64_t *data, rpm_count_t count)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ rpmTagReturnType retype = rpmTagGetReturnType(tag);
+ if (type != RPM_INT64_TYPE || count < 1)
+ return 0;
+ if (retype != RPM_ARRAY_RETURN_TYPE && count > 1)
+ return 0;
+
+ return rpmtdSet(td, tag, type, data, count);
+}
+
+int rpmtdFromString(rpmtd td, rpmTagVal tag, const char *data)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ int rc = 0;
+
+ if (type == RPM_STRING_TYPE) {
+ rc = rpmtdSet(td, tag, type, data, 1);
+ } else if (type == RPM_STRING_ARRAY_TYPE) {
+ rc = rpmtdSet(td, tag, type, &data, 1);
+ }
+
+ return rc;
+}
+
+int rpmtdFromStringArray(rpmtd td, rpmTagVal tag, const char **data, rpm_count_t count)
+{
+ rpmTagType type = rpmTagGetTagType(tag);
+ if (type != RPM_STRING_ARRAY_TYPE || count < 1)
+ return 0;
+ if (type == RPM_STRING_TYPE && count != 1)
+ return 0;
+
+ return rpmtdSet(td, tag, type, data, count);
+}
+
+int rpmtdFromArgv(rpmtd td, rpmTagVal tag, ARGV_t argv)
+{
+ int count = argvCount(argv);
+ rpmTagType type = rpmTagGetTagType(tag);
+
+ if (type != RPM_STRING_ARRAY_TYPE || count < 1)
+ return 0;
+
+ return rpmtdSet(td, tag, type, argv, count);
+}
+
+int rpmtdFromArgi(rpmtd td, rpmTagVal tag, ARGI_t argi)
+{
+ int count = argiCount(argi);
+ rpmTagType type = rpmTagGetTagType(tag);
+ rpmTagReturnType retype = rpmTagGetReturnType(tag);
+
+ if (type != RPM_INT32_TYPE || retype != RPM_ARRAY_RETURN_TYPE || count < 1)
+ return 0;
+
+ return rpmtdSet(td, tag, type, argiData(argi), count);
+}
+
+rpmtd rpmtdDup(rpmtd td)
+{
+ rpmtd newtd = NULL;
+ char **data = NULL;
+ int i;
+
+ assert(td != NULL);
+ /* TODO: permit other types too */
+ if (td->type != RPM_STRING_ARRAY_TYPE && td->type != RPM_I18NSTRING_TYPE) {
+ return NULL;
+ }
+
+ /* deep-copy container and data, drop immutable flag */
+ newtd = rpmtdNew();
+ memcpy(newtd, td, sizeof(*td));
+ newtd->flags &= ~(RPMTD_IMMUTABLE);
+
+ newtd->flags |= (RPMTD_ALLOCED | RPMTD_PTR_ALLOCED);
+ newtd->data = data = xmalloc(td->count * sizeof(*data));
+ while ((i = rpmtdNext(td)) >= 0) {
+ data[i] = xstrdup(rpmtdGetString(td));
+ }
+
+ return newtd;
+}
diff --git a/lib/rpmtd.h b/lib/rpmtd.h
new file mode 100644
index 0000000..99c94ba
--- /dev/null
+++ b/lib/rpmtd.h
@@ -0,0 +1,355 @@
+#ifndef _RPMTD_H
+#define _RPMTD_H
+
+#include <rpm/rpmtypes.h>
+#include <rpm/argv.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum rpmtdFlags_e {
+ RPMTD_NONE = 0,
+ RPMTD_ALLOCED = (1 << 0), /* was memory allocated? */
+ RPMTD_PTR_ALLOCED = (1 << 1), /* were array pointers allocated? */
+ RPMTD_IMMUTABLE = (1 << 2), /* header data or modifiable? */
+ RPMTD_ARGV = (1 << 3), /* string array is NULL-terminated? */
+};
+
+typedef rpmFlags rpmtdFlags;
+
+/** \ingroup rpmtd
+ * Container for rpm tag data (from headers or extensions).
+ * @todo Make this opaque (at least outside rpm itself)
+ */
+struct rpmtd_s {
+ rpm_tag_t tag; /* rpm tag of this data entry*/
+ rpm_tagtype_t type; /* data type */
+ rpm_count_t count; /* number of entries */
+ rpm_data_t data; /* pointer to actual data */
+ rpmtdFlags flags; /* flags on memory allocation etc */
+ int ix; /* iteration index */
+};
+
+/** \ingroup rpmtd
+ * Create new tag data container
+ * @return New, initialized tag data container.
+ */
+rpmtd rpmtdNew(void);
+
+/** \ingroup rpmtd
+ * Destroy tag data container.
+ * @param td Tag data container
+ * @return NULL always
+ */
+rpmtd rpmtdFree(rpmtd td);
+
+/** \ingroup rpmtd
+ * (Re-)initialize tag data container. Contents will be zeroed out
+ * and iteration index reset.
+ * @param td Tag data container
+ */
+void rpmtdReset(rpmtd td);
+
+/** \ingroup rpmtd
+ * Free contained data. This is always safe to call as the container knows
+ * if data was malloc'ed or not. Container is reinitialized.
+ * @param td Tag data container
+ */
+void rpmtdFreeData(rpmtd td);
+
+/** \ingroup rpmtd
+ * Retrieve array size of the container. For non-array types this is always 1.
+ * @param td Tag data container
+ * @return Number of entries in contained data.
+ */
+rpm_count_t rpmtdCount(rpmtd td);
+
+/** \ingroup rpmtd
+ * Retrieve tag of the container.
+ * @param td Tag data container
+ * @return Rpm tag.
+ */
+rpmTagVal rpmtdTag(rpmtd td);
+
+/** \ingroup rpmtd
+ * Retrieve type of the container.
+ * @param td Tag data container
+ * @return Rpm tag type.
+ */
+rpmTagType rpmtdType(rpmtd td);
+
+/** \ingroup rpmtd
+ * Retrieve class of the container.
+ * @param td Tag data container
+ * @return Rpm tag class
+ */
+rpmTagClass rpmtdClass(rpmtd td);
+
+/** \ingroup rpmtd
+ * Retrieve current iteration index of the container.
+ * @param td Tag data container
+ * @return Iteration index (or -1 if not iterating)
+ */
+int rpmtdGetIndex(rpmtd td);
+
+/** \ingroup rpmtd
+ * Set iteration index of the container.
+ * If new index is out of bounds for the container, -1 is returned and
+ * iteration index is left untouched.
+ * @param td Tag data container
+ * @param index New index
+ * @return New index, or -1 if index out of bounds
+ */
+int rpmtdSetIndex(rpmtd td, int index);
+
+/** \ingroup rpmtd
+ * Initialize tag container for iteration
+ * @param td Tag data container
+ * @return 0 on success
+ */
+int rpmtdInit(rpmtd td);
+
+/** \ingroup rpmtd
+ * Iterate over tag data container.
+ * @param td Tag data container
+ * @return Tag data container iterator index, -1 on termination
+ */
+int rpmtdNext(rpmtd td);
+
+/** \ingroup rpmtd
+ * Iterate over uint32_t type tag data container.
+ * @param td Tag data container
+ * @return Pointer to next value, NULL on termination or error
+ */
+uint32_t *rpmtdNextUint32(rpmtd td);
+
+/** \ingroup rpmtd
+ * Iterate over uint64_t type tag data container.
+ * @param td Tag data container
+ * @return Pointer to next value, NULL on termination or error
+ */
+uint64_t *rpmtdNextUint64(rpmtd td);
+
+/** \ingroup rpmtd
+ * Iterate over string / string array type tag data container.
+ * @param td Tag data container
+ * @return Pointer to next value, NULL on termination or error
+ */
+const char *rpmtdNextString(rpmtd td);
+
+/** \ingroup rpmtd
+ * Return char data from tag container.
+ * For scalar return type, just return pointer to the integer. On array
+ * types, return pointer to current iteration index. If the tag container
+ * is not for char type, NULL is returned.
+ * @param td Tag data container
+ * @return Pointer to uint16_t, NULL on error
+ */
+char *rpmtdGetChar(rpmtd td);
+
+/** \ingroup rpmtd
+ * Return uint16_t data from tag container.
+ * For scalar return type, just return pointer to the integer. On array
+ * types, return pointer to current iteration index. If the tag container
+ * is not for int16 type, NULL is returned.
+ * @param td Tag data container
+ * @return Pointer to uint16_t, NULL on error
+ */
+uint16_t * rpmtdGetUint16(rpmtd td);
+
+/** \ingroup rpmtd
+ * Return uint32_t data from tag container.
+ * For scalar return type, just return pointer to the integer. On array
+ * types, return pointer to current iteration index. If the tag container
+ * is not for int32 type, NULL is returned.
+ * @param td Tag data container
+ * @return Pointer to uint32_t, NULL on error
+ */
+uint32_t * rpmtdGetUint32(rpmtd td);
+
+/** \ingroup rpmtd
+ * Return uint64_t data from tag container.
+ * For scalar return type, just return pointer to the integer. On array
+ * types, return pointer to current iteration index. If the tag container
+ * is not for int64 type, NULL is returned.
+ * @param td Tag data container
+ * @return Pointer to uint64_t, NULL on error
+ */
+uint64_t * rpmtdGetUint64(rpmtd td);
+
+/** \ingroup rpmtd
+ * Return string data from tag container.
+ * For string types, just return the string. On string array types,
+ * return the string from current iteration index. If the tag container
+ * is not for a string type, NULL is returned.
+ * @param td Tag data container
+ * @return String constant from container, NULL on error
+ */
+const char * rpmtdGetString(rpmtd td);
+
+/** \ingroup rpmtd
+ * Return numeric value from tag container.
+ * Returns the value of numeric container (RPM_NUMERIC_CLASS) from
+ * current iteration index as uint64_t regardless of its internal
+ * presentation (8/16/32/64-bit integer).
+ * @param td Tag data container
+ * @return Value of current iteration item as uint64_t,
+ * 0 for non-numeric types (error)
+ */
+uint64_t rpmtdGetNumber(rpmtd td);
+
+typedef enum rpmtdFormats_e {
+ RPMTD_FORMAT_STRING = 0, /* plain string (any type) */
+ RPMTD_FORMAT_ARMOR = 1, /* ascii armor format (bin types) */
+ RPMTD_FORMAT_BASE64 = 2, /* base64 encoding (bin types) */
+ RPMTD_FORMAT_PGPSIG = 3, /* pgp/gpg signature (bin types) */
+ RPMTD_FORMAT_DEPFLAGS = 4, /* dependency flags (int types) */
+ RPMTD_FORMAT_FFLAGS = 5, /* file flags (int types) */
+ RPMTD_FORMAT_PERMS = 6, /* permission string (int types) */
+ RPMTD_FORMAT_TRIGGERTYPE = 7, /* trigger types (int types) */
+ RPMTD_FORMAT_XML = 8, /* xml format (any type) */
+ RPMTD_FORMAT_OCTAL = 9, /* octal format (int types) */
+ RPMTD_FORMAT_HEX = 10, /* hex format (int types) */
+ RPMTD_FORMAT_DATE = 11, /* date format (int types) */
+ RPMTD_FORMAT_DAY = 12, /* day format (int types) */
+ RPMTD_FORMAT_SHESCAPE = 13, /* shell escaped (any type) */
+ RPMTD_FORMAT_ARRAYSIZE = 14, /* size of contained array (any type) */
+ RPMTD_FORMAT_DEPTYPE = 15, /* dependency types (int types) */
+ RPMTD_FORMAT_FSTATE = 16, /* file states (int types) */
+ RPMTD_FORMAT_VFLAGS = 17, /* file verify flags (int types) */
+ RPMTD_FORMAT_EXPAND = 18, /* macro expansion (string types) */
+ RPMTD_FORMAT_FSTATUS = 19, /* file verify status (int types) */
+} rpmtdFormats;
+
+/** \ingroup rpmtd
+ * Format data from tag container to string presentation of given format.
+ * Return malloced string presentation of current data in container,
+ * converting from integers etc as necessary. On array types, data from
+ * current iteration index is used for formatting.
+ * @param td Tag data container
+ * @param fmt Format to apply
+ * @param errmsg Error message from conversion (or NULL)
+ * @return String representation of current data (malloc'ed),
+ * NULL on error
+ */
+char *rpmtdFormat(rpmtd td, rpmtdFormats fmt, const char *errmsg);
+
+/** \ingroup rpmtd
+ * Set container tag and type.
+ * For empty container, any valid tag can be set. If the container has
+ * data, changing is only permitted to tag of same type.
+ * @param td Tag data container
+ * @param tag New tag
+ * @return 1 on success, 0 on error
+ */
+int rpmtdSetTag(rpmtd td, rpmTagVal tag);
+
+/** \ingroup rpmtd
+ * Construct tag container from uint8_t pointer.
+ * Tag type is checked to be of compatible type (CHAR, INT8 or BIN).
+ * For non-array types (BIN is a special case of INT8 array)
+ * count must be exactly 1.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param data Pointer to uint8_t (value or array)
+ * @param count Number of entries
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromUint8(rpmtd td, rpmTagVal tag, uint8_t *data, rpm_count_t count);
+
+/** \ingroup rpmtd
+ * Construct tag container from uint16_t pointer.
+ * Tag type is checked to be of INT16 type. For non-array types count
+ * must be exactly 1.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param data Pointer to uint16_t (value or array)
+ * @param count Number of entries
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromUint16(rpmtd td, rpmTagVal tag, uint16_t *data, rpm_count_t count);
+
+/** \ingroup rpmtd
+ * Construct tag container from uint32_t pointer.
+ * Tag type is checked to be of INT32 type. For non-array types count
+ * must be exactly 1.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param data Pointer to uint32_t (value or array)
+ * @param count Number of entries
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromUint32(rpmtd td, rpmTagVal tag, uint32_t *data, rpm_count_t count);
+
+/** \ingroup rpmtd
+ * Construct tag container from uint64_t pointer.
+ * Tag type is checked to be of INT64 type. For non-array types count
+ * must be exactly 1.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param data Pointer to uint64_t (value or array)
+ * @param count Number of entries
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromUint64(rpmtd td, rpmTagVal tag, uint64_t *data, rpm_count_t count);
+
+/** \ingroup rpmtd
+ * Construct tag container from a string.
+ * Tag type is checked to be of string type.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param data String to use
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromString(rpmtd td, rpmTagVal tag, const char *data);
+
+/** \ingroup rpmtd
+ * Construct tag container from a string array.
+ * Tag type is checked to be of string or string array type. For non-array
+ * types count must be exactly 1.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param data Pointer to string array
+ * @param count Number of entries
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromStringArray(rpmtd td, rpmTagVal tag, const char **data, rpm_count_t count);
+
+/** \ingroup rpmtd
+ * Construct tag container from ARGV_t array.
+ * Tag type is checked to be of string array type and array is checked
+ * to be non-empty.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param argv ARGV array
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromArgv(rpmtd td, rpmTagVal tag, ARGV_t argv);
+
+/** \ingroup rpmtd
+ * Construct tag container from ARGI_t array.
+ * Tag type is checked to be of integer array type and array is checked
+ * to be non-empty.
+ * @param td Tag data container
+ * @param tag Rpm tag to construct
+ * @param argi ARGI array
+ * @return 1 on success, 0 on error (eg wrong type)
+ */
+int rpmtdFromArgi(rpmtd td, rpmTagVal tag, ARGI_t argi);
+
+/* \ingroup rpmtd
+ * Perform deep copy of container.
+ * Create a modifiable copy of tag data container (on string arrays each
+ * string is separately allocated)
+ * @todo Only string arrays types are supported currently
+ * @param td Container to copy
+ * @return New container or NULL on error
+ */
+rpmtd rpmtdDup(rpmtd td);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMTD_H */
diff --git a/lib/rpmte.c b/lib/rpmte.c
new file mode 100644
index 0000000..d13575a
--- /dev/null
+++ b/lib/rpmte.c
@@ -0,0 +1,927 @@
+/** \ingroup rpmdep
+ * \file lib/rpmte.c
+ * Routine(s) to handle an "rpmte" transaction element.
+ */
+#include "system.h"
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmlib.h> /* RPM_MACHTABLE_* */
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+
+#include "lib/rpmplugins.h"
+#include "lib/rpmte_internal.h"
+
+#include "debug.h"
+
+/** \ingroup rpmte
+ * A single package instance to be installed/removed atomically.
+ */
+struct rpmte_s {
+ rpmElementType type; /*!< Package disposition (installed/removed). */
+
+ Header h; /*!< Package header. */
+ char * NEVR; /*!< Package name-version-release. */
+ char * NEVRA; /*!< Package name-version-release.arch. */
+ char * name; /*!< Name: */
+ char * epoch;
+ char * version; /*!< Version: */
+ char * release; /*!< Release: */
+ char * arch; /*!< Architecture hint. */
+ char * os; /*!< Operating system hint. */
+ int isSource; /*!< (TR_ADDED) source rpm? */
+
+ rpmte depends; /*!< Package updated by this package (ERASE te) */
+ rpmte parent; /*!< Parent transaction element. */
+ unsigned int db_instance; /*!< Database instance (of removed pkgs) */
+ tsortInfo tsi; /*!< Dependency ordering chains. */
+
+ rpmds thisds; /*!< This package's provided NEVR. */
+ rpmds provides; /*!< Provides: dependencies. */
+ rpmds requires; /*!< Requires: dependencies. */
+ rpmds conflicts; /*!< Conflicts: dependencies. */
+ rpmds obsoletes; /*!< Obsoletes: dependencies. */
+ rpmds order; /*!< Order: dependencies. */
+ rpmfi fi; /*!< File information. */
+ rpmps probs; /*!< Problems (relocations) */
+ rpmts ts; /*!< Parent transaction */
+
+ rpm_color_t color; /*!< Color bit(s) from package dependencies. */
+ rpm_loff_t pkgFileSize; /*!< No. of bytes in package file (approx). */
+ unsigned int headerSize; /*!< No. of bytes in package header */
+
+ fnpyKey key; /*!< (TR_ADDED) Retrieval key. */
+ rpmRelocation * relocs; /*!< (TR_ADDED) Payload file relocations. */
+ int nrelocs; /*!< (TR_ADDED) No. of relocations. */
+ FD_t fd; /*!< (TR_ADDED) Payload file descriptor. */
+
+#define RPMTE_HAVE_PRETRANS (1 << 0)
+#define RPMTE_HAVE_POSTTRANS (1 << 1)
+ int transscripts; /*!< pre/posttrans script existence */
+ int failed; /*!< (parent) install/erase failed */
+
+ rpmfs fs;
+
+ ARGV_t lastInCollectionsAny; /*!< list of collections this te is the last to be installed or removed */
+ ARGV_t lastInCollectionsAdd; /*!< list of collections this te is the last to be only installed */
+ ARGV_t firstInCollectionsRemove; /*!< list of collections this te is the first to be only removed */
+ ARGV_t collections; /*!< list of collections */
+};
+
+/* forward declarations */
+static void rpmteColorDS(rpmte te, rpmTag tag);
+static int rpmteClose(rpmte te, int reset_fi);
+
+void rpmteCleanDS(rpmte te)
+{
+ te->thisds = rpmdsFree(te->thisds);
+ te->provides = rpmdsFree(te->provides);
+ te->requires = rpmdsFree(te->requires);
+ te->conflicts = rpmdsFree(te->conflicts);
+ te->obsoletes = rpmdsFree(te->obsoletes);
+ te->order = rpmdsFree(te->order);
+}
+
+static rpmfi getFI(rpmte p, Header h)
+{
+ rpmfiFlags fiflags;
+ fiflags = (p->type == TR_ADDED) ? (RPMFI_NOHEADER | RPMFI_FLAGS_INSTALL) :
+ (RPMFI_NOHEADER | RPMFI_FLAGS_ERASE);
+
+ /* relocate stuff in header if necessary */
+ if (rpmteType(p) == TR_ADDED && rpmfsFC(p->fs) > 0) {
+ if (!headerIsSource(h) && !headerIsEntry(h, RPMTAG_ORIGBASENAMES)) {
+ rpmRelocateFileList(p->relocs, p->nrelocs, p->fs, h);
+ }
+ }
+ return rpmfiNew(NULL, h, RPMTAG_BASENAMES, fiflags);
+}
+
+/* stupid bubble sort, but it's probably faster here */
+static void sortRelocs(rpmRelocation *relocations, int numRelocations)
+{
+ for (int i = 0; i < numRelocations; i++) {
+ int madeSwap = 0;
+ for (int j = 1; j < numRelocations; j++) {
+ rpmRelocation tmpReloc;
+ if (relocations[j - 1].oldPath == NULL || /* XXX can't happen */
+ relocations[j ].oldPath == NULL || /* XXX can't happen */
+ strcmp(relocations[j - 1].oldPath, relocations[j].oldPath) <= 0)
+ continue;
+ tmpReloc = relocations[j - 1];
+ relocations[j - 1] = relocations[j];
+ relocations[j] = tmpReloc;
+ madeSwap = 1;
+ }
+ if (!madeSwap) break;
+ }
+}
+
+static char * stripTrailingChar(char * s, char c)
+{
+ char * t;
+ for (t = s + strlen(s) - 1; *t == c && t >= s; t--)
+ *t = '\0';
+ return s;
+}
+
+static void buildRelocs(rpmte p, Header h, rpmRelocation *relocs)
+{
+ int i;
+ struct rpmtd_s validRelocs;
+
+ for (rpmRelocation *r = relocs; r->oldPath || r->newPath; r++)
+ p->nrelocs++;
+
+ headerGet(h, RPMTAG_PREFIXES, &validRelocs, HEADERGET_MINMEM);
+ p->relocs = xmalloc(sizeof(*p->relocs) * (p->nrelocs+1));
+
+ /* Build sorted relocation list from raw relocations. */
+ for (i = 0; i < p->nrelocs; i++) {
+ char * t;
+
+ /*
+ * Default relocations (oldPath == NULL) are handled in the UI,
+ * not rpmlib.
+ */
+ if (relocs[i].oldPath == NULL) continue; /* XXX can't happen */
+
+ /* FIXME: Trailing /'s will confuse us greatly. Internal ones will
+ too, but those are more trouble to fix up. :-( */
+ t = xstrdup(relocs[i].oldPath);
+ p->relocs[i].oldPath = (t[0] == '/' && t[1] == '\0')
+ ? t
+ : stripTrailingChar(t, '/');
+
+ /* An old path w/o a new path is valid, and indicates exclusion */
+ if (relocs[i].newPath) {
+ int valid = 0;
+ const char *validprefix;
+
+ t = xstrdup(relocs[i].newPath);
+ p->relocs[i].newPath = (t[0] == '/' && t[1] == '\0')
+ ? t
+ : stripTrailingChar(t, '/');
+
+ /* FIX: relocations[i].oldPath == NULL */
+ /* Verify that the relocation's old path is in the header. */
+ rpmtdInit(&validRelocs);
+ while ((validprefix = rpmtdNextString(&validRelocs))) {
+ if (rstreq(validprefix, p->relocs[i].oldPath)) {
+ valid = 1;
+ break;
+ }
+ }
+
+ if (!valid) {
+ rpmteAddProblem(p, RPMPROB_BADRELOCATE, NULL,
+ p->relocs[i].oldPath, 0);
+ }
+ } else {
+ p->relocs[i].newPath = NULL;
+ }
+ }
+ p->relocs[i].oldPath = NULL;
+ p->relocs[i].newPath = NULL;
+ sortRelocs(p->relocs, p->nrelocs);
+
+ rpmtdFreeData(&validRelocs);
+}
+
+/**
+ * Initialize transaction element data from header.
+ * @param p transaction element
+ * @param h header
+ * @param key (TR_ADDED) package retrieval key (e.g. file name)
+ * @param relocs (TR_ADDED) package file relocations
+ */
+static void addTE(rpmte p, Header h, fnpyKey key, rpmRelocation * relocs)
+{
+ struct rpmtd_s colls;
+
+ p->name = headerGetAsString(h, RPMTAG_NAME);
+ p->version = headerGetAsString(h, RPMTAG_VERSION);
+ p->release = headerGetAsString(h, RPMTAG_RELEASE);
+
+ p->epoch = headerGetAsString(h, RPMTAG_EPOCH);
+
+ p->arch = headerGetAsString(h, RPMTAG_ARCH);
+ p->os = headerGetAsString(h, RPMTAG_OS);
+
+ p->isSource = headerIsSource(h);
+
+ p->NEVR = headerGetAsString(h, RPMTAG_NEVR);
+ p->NEVRA = headerGetAsString(h, RPMTAG_NEVRA);
+
+ p->nrelocs = 0;
+ p->relocs = NULL;
+ if (relocs != NULL)
+ buildRelocs(p, h, relocs);
+
+ p->db_instance = headerGetInstance(h);
+ p->key = key;
+ p->fd = NULL;
+
+ p->pkgFileSize = 0;
+ p->headerSize = headerSizeof(h, HEADER_MAGIC_NO);
+
+ p->thisds = rpmdsThis(h, RPMTAG_PROVIDENAME, RPMSENSE_EQUAL);
+ p->provides = rpmdsNew(h, RPMTAG_PROVIDENAME, 0);
+ p->requires = rpmdsNew(h, RPMTAG_REQUIRENAME, 0);
+ p->conflicts = rpmdsNew(h, RPMTAG_CONFLICTNAME, 0);
+ p->obsoletes = rpmdsNew(h, RPMTAG_OBSOLETENAME, 0);
+ p->order = rpmdsNew(h, RPMTAG_ORDERNAME, 0);
+
+ p->fs = rpmfsNew(h, p->type);
+ p->fi = getFI(p, h);
+
+ /* See if we have pre/posttrans scripts. */
+ p->transscripts |= (headerIsEntry(h, RPMTAG_PRETRANS) &&
+ headerIsEntry(h, RPMTAG_PRETRANSPROG)) ?
+ RPMTE_HAVE_PRETRANS : 0;
+ p->transscripts |= (headerIsEntry(h, RPMTAG_POSTTRANS) &&
+ headerIsEntry(h, RPMTAG_POSTTRANSPROG)) ?
+ RPMTE_HAVE_POSTTRANS : 0;
+
+ p->lastInCollectionsAny = NULL;
+ p->lastInCollectionsAdd = NULL;
+ p->firstInCollectionsRemove = NULL;
+ p->collections = NULL;
+ if (headerGet(h, RPMTAG_COLLECTIONS, &colls, HEADERGET_MINMEM)) {
+ const char *collname;
+ while ((collname = rpmtdNextString(&colls))) {
+ argvAdd(&p->collections, collname);
+ }
+ argvSort(p->collections, NULL);
+ rpmtdFreeData(&colls);
+ }
+
+ rpmteColorDS(p, RPMTAG_PROVIDENAME);
+ rpmteColorDS(p, RPMTAG_REQUIRENAME);
+ return;
+}
+
+rpmte rpmteFree(rpmte te)
+{
+ if (te != NULL) {
+ if (te->relocs) {
+ for (int i = 0; i < te->nrelocs; i++) {
+ free(te->relocs[i].oldPath);
+ free(te->relocs[i].newPath);
+ }
+ free(te->relocs);
+ }
+
+ free(te->os);
+ free(te->arch);
+ free(te->epoch);
+ free(te->name);
+ free(te->version);
+ free(te->release);
+ free(te->NEVR);
+ free(te->NEVRA);
+
+ fdFree(te->fd);
+ rpmfiFree(te->fi);
+ headerFree(te->h);
+ rpmfsFree(te->fs);
+ rpmpsFree(te->probs);
+ rpmteCleanDS(te);
+
+ argvFree(te->collections);
+ argvFree(te->lastInCollectionsAny);
+ argvFree(te->lastInCollectionsAdd);
+ argvFree(te->firstInCollectionsRemove);
+
+ memset(te, 0, sizeof(*te)); /* XXX trash and burn */
+ free(te);
+ }
+ return NULL;
+}
+
+rpmte rpmteNew(rpmts ts, Header h, rpmElementType type, fnpyKey key,
+ rpmRelocation * relocs)
+{
+ rpmte p = xcalloc(1, sizeof(*p));
+ p->ts = ts;
+ p->type = type;
+ addTE(p, h, key, relocs);
+ switch (type) {
+ case TR_ADDED:
+ p->pkgFileSize = headerGetNumber(h, RPMTAG_LONGSIGSIZE) + 96 + 256;
+ break;
+ case TR_REMOVED:
+ /* nothing to do */
+ break;
+ }
+
+ return p;
+}
+
+unsigned int rpmteDBInstance(rpmte te)
+{
+ return (te != NULL ? te->db_instance : 0);
+}
+
+void rpmteSetDBInstance(rpmte te, unsigned int instance)
+{
+ if (te != NULL)
+ te->db_instance = instance;
+}
+
+Header rpmteHeader(rpmte te)
+{
+ return (te != NULL && te->h != NULL ? headerLink(te->h) : NULL);
+}
+
+Header rpmteSetHeader(rpmte te, Header h)
+{
+ if (te != NULL) {
+ te->h = headerFree(te->h);
+ if (h != NULL)
+ te->h = headerLink(h);
+ }
+ return NULL;
+}
+
+rpmElementType rpmteType(rpmte te)
+{
+ /* XXX returning negative for unsigned type */
+ return (te != NULL ? te->type : -1);
+}
+
+const char * rpmteN(rpmte te)
+{
+ return (te != NULL ? te->name : NULL);
+}
+
+const char * rpmteE(rpmte te)
+{
+ return (te != NULL ? te->epoch : NULL);
+}
+
+const char * rpmteV(rpmte te)
+{
+ return (te != NULL ? te->version : NULL);
+}
+
+const char * rpmteR(rpmte te)
+{
+ return (te != NULL ? te->release : NULL);
+}
+
+const char * rpmteA(rpmte te)
+{
+ return (te != NULL ? te->arch : NULL);
+}
+
+const char * rpmteO(rpmte te)
+{
+ return (te != NULL ? te->os : NULL);
+}
+
+int rpmteIsSource(rpmte te)
+{
+ return (te != NULL ? te->isSource : 0);
+}
+
+rpm_color_t rpmteColor(rpmte te)
+{
+ return (te != NULL ? te->color : 0);
+}
+
+rpm_color_t rpmteSetColor(rpmte te, rpm_color_t color)
+{
+ rpm_color_t ocolor = 0;
+ if (te != NULL) {
+ ocolor = te->color;
+ te->color = color;
+ }
+ return ocolor;
+}
+
+ARGV_const_t rpmteCollections(rpmte te)
+{
+ return (te != NULL) ? te->collections : NULL;
+}
+
+int rpmteHasCollection(rpmte te, const char *collname)
+{
+ return (argvSearch(rpmteCollections(te), collname, NULL) != NULL);
+}
+
+int rpmteAddToLastInCollectionAdd(rpmte te, const char *collname)
+{
+ if (te != NULL) {
+ argvAdd(&te->lastInCollectionsAdd, collname);
+ argvSort(te->lastInCollectionsAdd, NULL);
+ return 0;
+ }
+ return -1;
+}
+
+int rpmteAddToLastInCollectionAny(rpmte te, const char *collname)
+{
+ if (te != NULL) {
+ argvAdd(&te->lastInCollectionsAny, collname);
+ argvSort(te->lastInCollectionsAny, NULL);
+ return 0;
+ }
+ return -1;
+}
+
+int rpmteAddToFirstInCollectionRemove(rpmte te, const char *collname)
+{
+ if (te != NULL) {
+ argvAdd(&te->firstInCollectionsRemove, collname);
+ argvSort(te->firstInCollectionsRemove, NULL);
+ return 0;
+ }
+ return -1;
+}
+
+rpm_loff_t rpmtePkgFileSize(rpmte te)
+{
+ return (te != NULL ? te->pkgFileSize : 0);
+}
+
+unsigned int rpmteHeaderSize(rpmte te) {
+ return (te != NULL ? te->headerSize : 0);
+}
+
+rpmte rpmteParent(rpmte te)
+{
+ return (te != NULL ? te->parent : NULL);
+}
+
+rpmte rpmteSetParent(rpmte te, rpmte pte)
+{
+ rpmte opte = NULL;
+ if (te != NULL) {
+ opte = te->parent;
+ te->parent = pte;
+ }
+ return opte;
+}
+
+tsortInfo rpmteTSI(rpmte te)
+{
+ return te->tsi;
+}
+
+void rpmteSetTSI(rpmte te, tsortInfo tsi)
+{
+ te->tsi = tsi;
+}
+
+void rpmteSetDependsOn(rpmte te, rpmte depends) {
+ te->depends = depends;
+}
+
+rpmte rpmteDependsOn(rpmte te)
+{
+ return te->depends;
+}
+
+int rpmteDBOffset(rpmte te)
+{
+ return rpmteDBInstance(te);
+}
+
+const char * rpmteEVR(rpmte te)
+{
+ return (te != NULL ? te->NEVR + strlen(te->name) + 1 : NULL);
+}
+
+const char * rpmteNEVR(rpmte te)
+{
+ return (te != NULL ? te->NEVR : NULL);
+}
+
+const char * rpmteNEVRA(rpmte te)
+{
+ return (te != NULL ? te->NEVRA : NULL);
+}
+
+FD_t rpmteSetFd(rpmte te, FD_t fd)
+{
+ if (te != NULL) {
+ if (te->fd != NULL)
+ te->fd = fdFree(te->fd);
+ if (fd != NULL)
+ te->fd = fdLink(fd);
+ }
+ return NULL;
+}
+
+fnpyKey rpmteKey(rpmte te)
+{
+ return (te != NULL ? te->key : NULL);
+}
+
+rpmds rpmteDS(rpmte te, rpmTagVal tag)
+{
+ if (te == NULL)
+ return NULL;
+
+ switch (tag) {
+ case RPMTAG_NAME: return te->thisds;
+ case RPMTAG_PROVIDENAME: return te->provides;
+ case RPMTAG_REQUIRENAME: return te->requires;
+ case RPMTAG_CONFLICTNAME: return te->conflicts;
+ case RPMTAG_OBSOLETENAME: return te->obsoletes;
+ case RPMTAG_ORDERNAME: return te->order;
+ default: break;
+ }
+ return NULL;
+}
+
+rpmfi rpmteSetFI(rpmte te, rpmfi fi)
+{
+ if (te != NULL) {
+ te->fi = rpmfiFree(te->fi);
+ if (fi != NULL)
+ te->fi = rpmfiLink(fi);
+ }
+ return NULL;
+}
+
+rpmfi rpmteFI(rpmte te)
+{
+ if (te == NULL)
+ return NULL;
+
+ return te->fi; /* XXX take fi reference here? */
+}
+
+static void rpmteColorDS(rpmte te, rpmTag tag)
+{
+ rpmfi fi = rpmteFI(te);
+ rpmds ds = rpmteDS(te, tag);
+ char deptype = 'R';
+ char mydt;
+ const uint32_t * ddict;
+ rpm_color_t * colors;
+ rpm_color_t val;
+ int Count;
+ unsigned ix;
+ int ndx, i;
+
+ if (!(te && (Count = rpmdsCount(ds)) > 0 && rpmfiFC(fi) > 0))
+ return;
+
+ switch (tag) {
+ default:
+ return;
+ break;
+ case RPMTAG_PROVIDENAME:
+ deptype = 'P';
+ break;
+ case RPMTAG_REQUIRENAME:
+ deptype = 'R';
+ break;
+ }
+
+ colors = xcalloc(Count, sizeof(*colors));
+
+ /* Calculate dependency color. */
+ fi = rpmfiInit(fi, 0);
+ if (fi != NULL)
+ while (rpmfiNext(fi) >= 0) {
+ val = rpmfiFColor(fi);
+ ddict = NULL;
+ ndx = rpmfiFDepends(fi, &ddict);
+ if (ddict != NULL)
+ while (ndx-- > 0) {
+ ix = *ddict++;
+ mydt = ((ix >> 24) & 0xff);
+ if (mydt != deptype)
+ continue;
+ ix &= 0x00ffffff;
+assert (ix < Count);
+ colors[ix] |= val;
+ }
+ }
+
+ /* Set color values in dependency set. */
+ ds = rpmdsInit(ds);
+ while ((i = rpmdsNext(ds)) >= 0) {
+ val = colors[i];
+ te->color |= val;
+ (void) rpmdsSetColor(ds, val);
+ }
+ free(colors);
+}
+
+static Header rpmteDBHeader(rpmte te)
+{
+ Header h = NULL;
+ rpmdbMatchIterator mi;
+
+ mi = rpmtsInitIterator(te->ts, RPMDBI_PACKAGES,
+ &te->db_instance, sizeof(te->db_instance));
+ /* iterator returns weak refs, grab hold of header */
+ if ((h = rpmdbNextIterator(mi)))
+ h = headerLink(h);
+ mi = rpmdbFreeIterator(mi);
+ return h;
+}
+
+static Header rpmteFDHeader(rpmte te)
+{
+ Header h = NULL;
+ te->fd = rpmtsNotify(te->ts, te, RPMCALLBACK_INST_OPEN_FILE, 0, 0);
+ if (te->fd != NULL) {
+ rpmVSFlags ovsflags;
+ rpmRC pkgrc;
+
+ ovsflags = rpmtsSetVSFlags(te->ts,
+ rpmtsVSFlags(te->ts) | RPMVSF_NEEDPAYLOAD);
+ pkgrc = rpmReadPackageFile(te->ts, te->fd, rpmteNEVRA(te), &h);
+ rpmtsSetVSFlags(te->ts, ovsflags);
+ switch (pkgrc) {
+ default:
+ rpmteClose(te, 1);
+ break;
+ case RPMRC_NOTTRUSTED:
+ case RPMRC_NOKEY:
+ case RPMRC_OK:
+ break;
+ }
+ }
+ return h;
+}
+
+static int rpmteOpen(rpmte te, int reload_fi)
+{
+ Header h = NULL;
+ if (te == NULL || te->ts == NULL || rpmteFailed(te))
+ goto exit;
+
+ rpmteSetHeader(te, NULL);
+
+ switch (rpmteType(te)) {
+ case TR_ADDED:
+ h = rpmteDBInstance(te) ? rpmteDBHeader(te) : rpmteFDHeader(te);
+ break;
+ case TR_REMOVED:
+ h = rpmteDBHeader(te);
+ break;
+ }
+ if (h != NULL) {
+ if (reload_fi) {
+ te->fi = getFI(te, h);
+ }
+
+ rpmteSetHeader(te, h);
+ headerFree(h);
+ }
+
+exit:
+ return (h != NULL);
+}
+
+static int rpmteClose(rpmte te, int reset_fi)
+{
+ if (te == NULL || te->ts == NULL)
+ return 0;
+
+ switch (te->type) {
+ case TR_ADDED:
+ if (te->fd) {
+ rpmtsNotify(te->ts, te, RPMCALLBACK_INST_CLOSE_FILE, 0, 0);
+ te->fd = NULL;
+ }
+ break;
+ case TR_REMOVED:
+ /* eventually we'll want notifications for erase open too */
+ break;
+ }
+ rpmteSetHeader(te, NULL);
+ if (reset_fi) {
+ rpmteSetFI(te, NULL);
+ }
+ return 1;
+}
+
+FD_t rpmtePayload(rpmte te)
+{
+ FD_t payload = NULL;
+ if (te->fd && te->h) {
+ const char *compr = headerGetString(te->h, RPMTAG_PAYLOADCOMPRESSOR);
+ char *ioflags = rstrscat(NULL, "r.", compr ? compr : "gzip", NULL);
+ payload = Fdopen(fdDup(Fileno(te->fd)), ioflags);
+ free(ioflags);
+ }
+ return payload;
+}
+
+static int rpmteMarkFailed(rpmte te)
+{
+ rpmtsi pi = rpmtsiInit(te->ts);
+ rpmte p;
+
+ te->failed++;
+ /* XXX we can do a much better here than this... */
+ while ((p = rpmtsiNext(pi, TR_REMOVED))) {
+ if (rpmteDependsOn(p) == te) {
+ p->failed++;
+ }
+ }
+ rpmtsiFree(pi);
+ return te->failed;
+}
+
+int rpmteFailed(rpmte te)
+{
+ return (te != NULL) ? te->failed : -1;
+}
+
+static int rpmteHaveTransScript(rpmte te, rpmTagVal tag)
+{
+ int rc = 0;
+ if (tag == RPMTAG_PRETRANS) {
+ rc = (te->transscripts & RPMTE_HAVE_PRETRANS);
+ } else if (tag == RPMTAG_POSTTRANS) {
+ rc = (te->transscripts & RPMTE_HAVE_POSTTRANS);
+ }
+ return rc;
+}
+
+rpmps rpmteProblems(rpmte te)
+{
+ return (te != NULL) ? rpmpsLink(te->probs) : NULL;
+}
+
+void rpmteCleanProblems(rpmte te)
+{
+ if (te != NULL && te->probs != NULL) {
+ te->probs = rpmpsFree(te->probs);
+ }
+}
+
+static void appendProblem(rpmte te, rpmProblemType type,
+ fnpyKey key, const char * altNEVR,
+ const char * str, uint64_t number)
+{
+ rpmProblem o;
+ rpmProblem p = rpmProblemCreate(type, te->NEVRA, key, altNEVR, str, number);
+ rpmpsi psi = rpmpsInitIterator(te->probs);
+
+ /* Only add new, unique problems to the set */
+ while ((o = rpmpsiNext(psi))) {
+ if (rpmProblemCompare(p, o) == 0)
+ break;
+ }
+ rpmpsFreeIterator(psi);
+
+ if (o == NULL) {
+ if (te->probs == NULL)
+ te->probs = rpmpsCreate();
+ rpmpsAppendProblem(te->probs, p);
+ }
+ rpmProblemFree(p);
+}
+
+void rpmteAddProblem(rpmte te, rpmProblemType type,
+ const char *altNEVR, const char *str, uint64_t number)
+{
+ if (te != NULL) {
+ appendProblem(te, type, rpmteKey(te), altNEVR, str, number);
+ }
+}
+
+void rpmteAddDepProblem(rpmte te, const char * altNEVR, rpmds ds,
+ fnpyKey * suggestedKeys)
+{
+ if (te != NULL) {
+ const char * DNEVR = rpmdsDNEVR(ds);
+ rpmProblemType type;
+ fnpyKey key = (suggestedKeys ? suggestedKeys[0] : NULL);
+
+ switch ((unsigned)DNEVR[0]) {
+ case 'O': type = RPMPROB_OBSOLETES; break;
+ case 'C': type = RPMPROB_CONFLICT; break;
+ default:
+ case 'R': type = RPMPROB_REQUIRES; break;
+ }
+
+ appendProblem(te, type, key, altNEVR, DNEVR+2, rpmdsInstance(ds));
+ }
+}
+
+const char * rpmteTypeString(rpmte te)
+{
+ switch(rpmteType(te)) {
+ case TR_ADDED: return _("install");
+ case TR_REMOVED: return _("erase");
+ default: return "???";
+ }
+}
+
+rpmfs rpmteGetFileStates(rpmte te) {
+ return te->fs;
+}
+
+rpmRC rpmteSetupCollectionPlugins(rpmte te)
+{
+ ARGV_const_t colls = rpmteCollections(te);
+ rpmPlugins plugins = rpmtsPlugins(te->ts);
+ rpmRC rc = RPMRC_OK;
+
+ if (!colls) {
+ return rc;
+ }
+
+ rpmteOpen(te, 0);
+ for (; colls && *colls; colls++) {
+ if (!rpmpluginsPluginAdded(plugins, *colls)) {
+ rc = rpmpluginsAddCollectionPlugin(plugins, *colls);
+ if (rc != RPMRC_OK) {
+ break;
+ }
+ }
+ rc = rpmpluginsCallOpenTE(plugins, *colls, te);
+ if (rc != RPMRC_OK) {
+ break;
+ }
+ }
+ rpmteClose(te, 0);
+
+ return rc;
+}
+
+static rpmRC rpmteRunAllCollections(rpmte te, rpmPluginHook hook)
+{
+ ARGV_const_t colls;
+ rpmRC(*collHook) (rpmPlugins, const char *);
+ rpmRC rc = RPMRC_OK;
+
+ if (rpmtsFlags(te->ts) & RPMTRANS_FLAG_NOCOLLECTIONS) {
+ goto exit;
+ }
+
+ switch (hook) {
+ case PLUGINHOOK_COLL_POST_ADD:
+ colls = te->lastInCollectionsAdd;
+ collHook = rpmpluginsCallCollectionPostAdd;
+ break;
+ case PLUGINHOOK_COLL_POST_ANY:
+ colls = te->lastInCollectionsAny;
+ collHook = rpmpluginsCallCollectionPostAny;
+ break;
+ case PLUGINHOOK_COLL_PRE_REMOVE:
+ colls = te->firstInCollectionsRemove;
+ collHook = rpmpluginsCallCollectionPreRemove;
+ break;
+ default:
+ goto exit;
+ }
+
+ for (; colls && *colls; colls++) {
+ rc = collHook(rpmtsPlugins(te->ts), *colls);
+ }
+
+ exit:
+ return rc;
+}
+
+int rpmteProcess(rpmte te, pkgGoal goal)
+{
+ /* Only install/erase resets pkg file info */
+ int scriptstage = (goal != PKG_INSTALL && goal != PKG_ERASE);
+ int reset_fi = (scriptstage == 0);
+ int failed = 1;
+
+ /* Dont bother opening for elements without pre/posttrans scripts */
+ if (goal == PKG_PRETRANS || goal == PKG_POSTTRANS) {
+ if (!rpmteHaveTransScript(te, goal)) {
+ return 0;
+ }
+ }
+
+ if (!scriptstage) {
+ rpmteRunAllCollections(te, PLUGINHOOK_COLL_PRE_REMOVE);
+ }
+
+ if (rpmteOpen(te, reset_fi)) {
+ failed = rpmpsmRun(te->ts, te, goal);
+ rpmteClose(te, reset_fi);
+ }
+
+ if (!scriptstage) {
+ rpmteRunAllCollections(te, PLUGINHOOK_COLL_POST_ADD);
+ rpmteRunAllCollections(te, PLUGINHOOK_COLL_POST_ANY);
+ }
+
+ /* XXX should %pretrans failure fail the package install? */
+ if (failed && !scriptstage) {
+ failed = rpmteMarkFailed(te);
+ }
+
+ return failed;
+}
diff --git a/lib/rpmte.h b/lib/rpmte.h
new file mode 100644
index 0000000..a66c1e9
--- /dev/null
+++ b/lib/rpmte.h
@@ -0,0 +1,262 @@
+#ifndef H_RPMTE
+#define H_RPMTE
+
+/** \ingroup rpmts rpmte
+ * \file lib/rpmte.h
+ * Structures used for an "rpmte" transaction element.
+ */
+
+#include <rpm/rpmtypes.h>
+#include <rpm/argv.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmte
+ * Transaction element type.
+ */
+typedef enum rpmElementType_e {
+ TR_ADDED = (1 << 0), /*!< Package will be installed. */
+ TR_REMOVED = (1 << 1) /*!< Package will be removed. */
+} rpmElementType;
+
+typedef rpmFlags rpmElementTypes;
+
+/** \ingroup rpmte
+ * Retrieve header from transaction element.
+ * @param te transaction element
+ * @return header (new reference)
+ */
+Header rpmteHeader(rpmte te);
+
+/** \ingroup rpmte
+ * Save header into transaction element.
+ * @param te transaction element
+ * @param h header
+ * @return NULL always
+ */
+Header rpmteSetHeader(rpmte te, Header h);
+
+/** \ingroup rpmte
+ * Retrieve type of transaction element.
+ * @param te transaction element
+ * @return type
+ */
+rpmElementType rpmteType(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve name string of transaction element.
+ * @param te transaction element
+ * @return name string
+ */
+const char * rpmteN(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve epoch string of transaction element.
+ * @param te transaction element
+ * @return epoch string
+ */
+const char * rpmteE(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve version string of transaction element.
+ * @param te transaction element
+ * @return version string
+ */
+const char * rpmteV(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve release string of transaction element.
+ * @param te transaction element
+ * @return release string
+ */
+const char * rpmteR(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve arch string of transaction element.
+ * @param te transaction element
+ * @return arch string
+ */
+const char * rpmteA(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve os string of transaction element.
+ * @param te transaction element
+ * @return os string
+ */
+const char * rpmteO(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve isSource attribute of transaction element.
+ * @param te transaction element
+ * @return isSource attribute
+ */
+int rpmteIsSource(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve color bits of transaction element.
+ * @param te transaction element
+ * @return color bits
+ */
+rpm_color_t rpmteColor(rpmte te);
+
+/** \ingroup rpmte
+ * Set color bits of transaction element.
+ * @param te transaction element
+ * @param color new color bits
+ * @return previous color bits
+ */
+rpm_color_t rpmteSetColor(rpmte te, rpm_color_t color);
+
+/** \ingroup rpmte
+ * Retrieve last instance installed to the database.
+ * @param te transaction element
+ * @return last install instance.
+ */
+unsigned int rpmteDBInstance(rpmte te);
+
+/** \ingroup rpmte
+ * Set last instance installed to the database.
+ * @param te transaction element
+ * @param instance Database instance of last install element.
+ * @return last install instance.
+ */
+void rpmteSetDBInstance(rpmte te, unsigned int instance);
+
+/** \ingroup rpmte
+ * Retrieve size in bytes of package file.
+ * @todo Signature header is estimated at 256b.
+ * @param te transaction element
+ * @return size in bytes of package file.
+ */
+rpm_loff_t rpmtePkgFileSize(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve parent transaction element.
+ * @param te transaction element
+ * @return parent transaction element
+ */
+rpmte rpmteParent(rpmte te);
+
+/** \ingroup rpmte
+ * Set parent transaction element.
+ * @param te transaction element
+ * @param pte new parent transaction element
+ * @return previous parent transaction element
+ */
+rpmte rpmteSetParent(rpmte te, rpmte pte);
+
+/** \ingroup rpmte
+ * Return problem set info of transaction element.
+ * @param te transaction element
+ * @return problem set (or NULL if none)
+ */
+rpmps rpmteProblems(rpmte te);
+
+/** \ingroup rpmte
+ * Destroy problem set info of transaction element.
+ * @param te transaction element
+ */
+void rpmteCleanProblems(rpmte te);
+
+/** \ingroup rpmte
+ * Destroy dependency set info of transaction element.
+ * @param te transaction element
+ */
+void rpmteCleanDS(rpmte te);
+
+/** \ingroup rpmte
+ * Set dependent element of TR_REMOVED transaction element.
+ * @param te transaction element
+ * @param depends dependent transaction element
+ */
+void rpmteSetDependsOn(rpmte te, rpmte depends);
+
+/** \ingroup rpmte
+ * Retrieve dependent element of TR_REMOVED transaction element.
+ * @param te transaction element
+ * @return dependent transaction element
+ */
+rpmte rpmteDependsOn(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve rpmdb instance of TR_REMOVED transaction element.
+ * @param te transaction element
+ * @return rpmdb instance
+ */
+int rpmteDBOffset(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve [epoch:]version-release string from transaction element.
+ * @param te transaction element
+ * @return [epoch:]version-release string
+ */
+const char * rpmteEVR(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve name-[epoch:]version-release string from transaction element.
+ * @param te transaction element
+ * @return name-[epoch:]version-release string
+ */
+const char * rpmteNEVR(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve name-[epoch:]version-release.arch string from transaction element.
+ * @param te transaction element
+ * @return name-[epoch:]version-release.arch string
+ */
+const char * rpmteNEVRA(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve key from transaction element.
+ * @param te transaction element
+ * @return key
+ */
+fnpyKey rpmteKey(rpmte te);
+
+/** \ingroup rpmte
+ * Return failure status of transaction element.
+ * If the element itself failed, this is 1, larger count means one of
+ * it's parents failed.
+ * @param te transaction element
+ * @return number of failures for this transaction element
+ */
+int rpmteFailed(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve dependency tag set from transaction element.
+ * @param te transaction element
+ * @param tag dependency tag
+ * @return dependency tag set
+ */
+rpmds rpmteDS(rpmte te, rpmTagVal tag);
+
+/** \ingroup rpmte
+ * Retrieve file info tag set from transaction element.
+ * @param te transaction element
+ * @return file info tag set
+ */
+rpmfi rpmteFI(rpmte te);
+
+/** \ingroup rpmte
+ * Retrieve list of collections
+ * @param te transaction element
+ * @return list of collections
+ */
+ARGV_const_t rpmteCollections(rpmte te);
+
+/** \ingroup rpmte
+ * Determine a transaction element is part of a collection
+ * @param te transaction element
+ * @param collname collection name
+ * @return 1 if collname is part of a collection, 0 if not
+ */
+int rpmteHasCollection(rpmte te, const char * collname);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMTE */
diff --git a/lib/rpmte_internal.h b/lib/rpmte_internal.h
new file mode 100644
index 0000000..8a8df2a
--- /dev/null
+++ b/lib/rpmte_internal.h
@@ -0,0 +1,154 @@
+#ifndef _RPMTE_INTERNAL_H
+#define _RPMTE_INTERNAL_H
+
+#include <rpm/rpmte.h>
+#include <rpm/rpmds.h>
+#include "lib/rpmfs.h"
+
+typedef enum pkgGoal_e {
+ PKG_NONE = 0,
+ /* permit using rpmteType() for install + erase goals */
+ PKG_INSTALL = TR_ADDED,
+ PKG_ERASE = TR_REMOVED,
+ /* permit using scriptname for these for now... */
+ PKG_VERIFY = RPMTAG_VERIFYSCRIPT,
+ PKG_PRETRANS = RPMTAG_PRETRANS,
+ PKG_POSTTRANS = RPMTAG_POSTTRANS,
+} pkgGoal;
+
+/** \ingroup rpmte
+ * Transaction element ordering chain linkage.
+ */
+typedef struct tsortInfo_s * tsortInfo;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmte
+ * Create a transaction element.
+ * @param ts transaction set
+ * @param h header
+ * @param type TR_ADDED/TR_REMOVED
+ * @param key (TR_ADDED) package retrieval key (e.g. file name)
+ * @param relocs (TR_ADDED) package file relocations
+ * @return new transaction element
+ */
+RPM_GNUC_INTERNAL
+rpmte rpmteNew(rpmts ts, Header h, rpmElementType type, fnpyKey key,
+ rpmRelocation * relocs);
+
+/** \ingroup rpmte
+ * Destroy a transaction element.
+ * @param te transaction element
+ * @return NULL always
+ */
+RPM_GNUC_INTERNAL
+rpmte rpmteFree(rpmte te);
+
+RPM_GNUC_INTERNAL
+rpmfi rpmteSetFI(rpmte te, rpmfi fi);
+
+RPM_GNUC_INTERNAL
+FD_t rpmteSetFd(rpmte te, FD_t fd);
+
+RPM_GNUC_INTERNAL
+FD_t rpmtePayload(rpmte te);
+
+RPM_GNUC_INTERNAL
+int rpmteProcess(rpmte te, pkgGoal goal);
+
+RPM_GNUC_INTERNAL
+void rpmteAddProblem(rpmte te, rpmProblemType type,
+ const char *altNEVR, const char *str, uint64_t number);
+
+RPM_GNUC_INTERNAL
+void rpmteAddDepProblem(rpmte te, const char * pkgNEVR, rpmds ds,
+ fnpyKey * suggestedKeys);
+
+RPM_GNUC_INTERNAL
+const char * rpmteTypeString(rpmte te);
+
+RPM_GNUC_INTERNAL
+tsortInfo rpmteTSI(rpmte te);
+
+RPM_GNUC_INTERNAL
+void rpmteSetTSI(rpmte te, tsortInfo tsi);
+
+/* XXX should be internal too but build code needs for now... */
+rpmfs rpmteGetFileStates(rpmte te);
+
+/* XXX here for now... */
+/**
+ * Relocate files in header.
+ * @todo multilib file dispositions need to be checked.
+ * @param relocations relocations
+ * @param numRelocations number of relocations
+ * @param fs file state set
+ * @param h package header to relocate
+ */
+RPM_GNUC_INTERNAL
+void rpmRelocateFileList(rpmRelocation *relocs, int numRelocations, rpmfs fs, Header h);
+
+/** \ingroup rpmte
+ * Retrieve size in bytes of package header.
+ * @param te transaction element
+ * @return size in bytes of package file.
+ */
+RPM_GNUC_INTERNAL
+unsigned int rpmteHeaderSize(rpmte te);
+
+/**
+ * Package state machine driver.
+ * @param ts transaction set
+ * @param te transaction element
+ * @param goal state machine goal
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+rpmRC rpmpsmRun(rpmts ts, rpmte te, pkgGoal goal);
+
+/** \ingroup rpmte
+ * Add a collection to the list of last collections for the installation
+ * section of a transaction element
+ * @param te transaction element
+ * @param collname collection name
+ * @return 0 on success, non-zero on error
+ */
+RPM_GNUC_INTERNAL
+int rpmteAddToLastInCollectionAdd(rpmte te, const char * collname);
+
+/** \ingroup rpmte
+ * Add a collection to the list of last collections for the installation
+ * or removal section of a transaction element
+ * @param te transaction element
+ * @param collname collection name
+ * @return 0 on success, non-zero on error
+ */
+RPM_GNUC_INTERNAL
+int rpmteAddToLastInCollectionAny(rpmte te, const char * collname);
+
+/** \ingroup rpmte
+ * Add a collection to the list of first collections for the removal
+ * section of a transaction element
+ * @param te transaction element
+ * @param collname collection name
+ * @return 0 on success, non-zero on error
+ */
+RPM_GNUC_INTERNAL
+int rpmteAddToFirstInCollectionRemove(rpmte te, const char * collname);
+
+/** \ingroup rpmte
+ * Sends the open te plugin hook for each plugins with the transaction element open
+ * @param te transaction element
+ * @return 0 on success, non-zero on error
+ */
+RPM_GNUC_INTERNAL
+rpmRC rpmteSetupCollectionPlugins(rpmte te);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMTE_INTERNAL_H */
+
diff --git a/lib/rpmts.c b/lib/rpmts.c
new file mode 100644
index 0000000..d782ecf
--- /dev/null
+++ b/lib/rpmts.c
@@ -0,0 +1,1036 @@
+/** \ingroup rpmdep
+ * \file lib/rpmts.c
+ * Routine(s) to handle a "rpmts" transaction sets.
+ */
+#include "system.h"
+
+#include <inttypes.h>
+#include <libgen.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmlib.h> /* rpmReadPackage etc */
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmfileutil.h> /* rpmtsOpenDB() needs rpmGetPath */
+#include <rpm/rpmstring.h>
+#include <rpm/rpmkeyring.h>
+
+#include <rpm/rpmdb.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmte.h>
+
+#include "rpmio/digest.h"
+#include "lib/rpmal.h"
+#include "lib/rpmchroot.h"
+#include "lib/rpmplugins.h"
+#include "lib/rpmts_internal.h"
+#include "lib/rpmte_internal.h"
+#include "lib/misc.h"
+
+#include "debug.h"
+
+/**
+ * Iterator across transaction elements, forward on install, backward on erase.
+ */
+struct rpmtsi_s {
+ rpmts ts; /*!< transaction set. */
+ int oc; /*!< iterator index. */
+};
+
+static void loadKeyring(rpmts ts);
+
+int _rpmts_stats = 0;
+
+static rpmts rpmtsUnlink(rpmts ts)
+{
+ if (ts)
+ ts->nrefs--;
+ return NULL;
+}
+
+rpmts rpmtsLink(rpmts ts)
+{
+ if (ts)
+ ts->nrefs++;
+ return ts;
+}
+
+int rpmtsCloseDB(rpmts ts)
+{
+ int rc = 0;
+
+ if (ts->rdb != NULL) {
+ (void) rpmswAdd(rpmtsOp(ts, RPMTS_OP_DBGET),
+ rpmdbOp(ts->rdb, RPMDB_OP_DBGET));
+ (void) rpmswAdd(rpmtsOp(ts, RPMTS_OP_DBPUT),
+ rpmdbOp(ts->rdb, RPMDB_OP_DBPUT));
+ (void) rpmswAdd(rpmtsOp(ts, RPMTS_OP_DBDEL),
+ rpmdbOp(ts->rdb, RPMDB_OP_DBDEL));
+ rc = rpmdbClose(ts->rdb);
+ ts->rdb = NULL;
+ }
+ return rc;
+}
+
+int rpmtsOpenDB(rpmts ts, int dbmode)
+{
+ int rc = 0;
+
+ if (ts->rdb != NULL && ts->dbmode == dbmode)
+ return 0;
+
+ (void) rpmtsCloseDB(ts);
+
+ /* XXX there's a potential db lock race here. */
+
+ ts->dbmode = dbmode;
+ rc = rpmdbOpen(ts->rootDir, &ts->rdb, ts->dbmode, 0644);
+ if (rc) {
+ char * dn = rpmGetPath(ts->rootDir, "%{_dbpath}", NULL);
+ rpmlog(RPMLOG_ERR,
+ _("cannot open Packages database in %s\n"), dn);
+ dn = _free(dn);
+ }
+ return rc;
+}
+
+int rpmtsInitDB(rpmts ts, int dbmode)
+{
+ rpmlock lock = rpmtsAcquireLock(ts);
+ int rc = -1;
+ if (lock)
+ rc = rpmdbInit(ts->rootDir, dbmode);
+ rpmlockFree(lock);
+ return rc;
+}
+
+int rpmtsGetDBMode(rpmts ts)
+{
+ assert(ts != NULL);
+ return (ts->dbmode);
+}
+
+int rpmtsSetDBMode(rpmts ts, int dbmode)
+{
+ int rc = 1;
+ /* mode setting only permitted on non-open db */
+ if (ts != NULL && rpmtsGetRdb(ts) == NULL) {
+ ts->dbmode = dbmode;
+ rc = 0;
+ }
+ return rc;
+}
+
+
+int rpmtsRebuildDB(rpmts ts)
+{
+ int rc = -1;
+ rpmlock lock = NULL;
+
+ /* Cannot do this on a populated transaction set */
+ if (rpmtsNElements(ts) > 0)
+ return -1;
+
+ lock = rpmtsAcquireLock(ts);
+ if (lock) {
+ if (!(ts->vsflags & RPMVSF_NOHDRCHK))
+ rc = rpmdbRebuild(ts->rootDir, ts, headerCheck);
+ else
+ rc = rpmdbRebuild(ts->rootDir, NULL, NULL);
+ rpmlockFree(lock);
+ }
+ return rc;
+}
+
+int rpmtsVerifyDB(rpmts ts)
+{
+ int rc = -1;
+ rpmlock lock = rpmtsAcquireLock(ts);
+ if (lock) {
+ rc = rpmdbVerify(ts->rootDir);
+ rpmlockFree(lock);
+ }
+ return rc;
+}
+
+/* keyp might no be defined. */
+rpmdbMatchIterator rpmtsInitIterator(const rpmts ts, rpmDbiTagVal rpmtag,
+ const void * keyp, size_t keylen)
+{
+ rpmdbMatchIterator mi = NULL;
+ const char * arch = NULL;
+ char *tmp = NULL;
+ int xx;
+
+ if (ts == NULL)
+ return NULL;
+
+ if (ts && ts->keyring == NULL)
+ loadKeyring(ts);
+
+ if (ts->rdb == NULL && rpmtsOpenDB(ts, ts->dbmode))
+ return NULL;
+
+ /* Parse out "N(EVR).A" tokens from a label key. */
+ if (rpmtag == RPMDBI_LABEL && keyp != NULL) {
+ const char *se, *s = keyp;
+ char *t;
+ size_t slen = strlen(s);
+ int level = 0;
+ int c;
+
+ tmp = xmalloc(slen+1);
+ keyp = t = tmp;
+ while ((c = *s++) != '\0') {
+ switch (c) {
+ default:
+ *t++ = c;
+ break;
+ case '(':
+ /* XXX Fail if nested parens. */
+ if (level++ != 0) {
+ rpmlog(RPMLOG_ERR, _("extra '(' in package label: %s\n"), (const char*)keyp);
+ goto exit;
+ }
+ /* Parse explicit epoch. */
+ for (se = s; *se && risdigit(*se); se++)
+ {};
+ if (*se == ':') {
+ /* XXX skip explicit epoch's (for now) */
+ *t++ = '-';
+ s = se + 1;
+ } else {
+ /* No Epoch: found. Convert '(' to '-' and chug. */
+ *t++ = '-';
+ }
+ break;
+ case ')':
+ /* XXX Fail if nested parens. */
+ if (--level != 0) {
+ rpmlog(RPMLOG_ERR, _("missing '(' in package label: %s\n"), (const char*)keyp);
+ goto exit;
+ }
+ /* Don't copy trailing ')' */
+ break;
+ }
+ }
+ if (level) {
+ rpmlog(RPMLOG_ERR, _("missing ')' in package label: %s\n"), (const char*)keyp);
+ goto exit;
+ }
+ *t = '\0';
+ t = (char *) keyp;
+ t = strrchr(t, '.');
+ /* Is this a valid ".arch" suffix? */
+ if (t != NULL && rpmIsKnownArch(t+1)) {
+ *t++ = '\0';
+ arch = t;
+ }
+ }
+
+ mi = rpmdbInitIterator(ts->rdb, rpmtag, keyp, keylen);
+
+ /* Verify header signature/digest during retrieve (if not disabled). */
+ if (mi && !(ts->vsflags & RPMVSF_NOHDRCHK))
+ (void) rpmdbSetHdrChk(mi, ts, headerCheck);
+
+ /* Select specified arch only. */
+ if (arch != NULL)
+ xx = rpmdbSetIteratorRE(mi, RPMTAG_ARCH, RPMMIRE_DEFAULT, arch);
+
+exit:
+ free(tmp);
+
+ return mi;
+}
+
+rpmKeyring rpmtsGetKeyring(rpmts ts, int autoload)
+{
+ rpmKeyring keyring = NULL;
+ if (ts) {
+ if (ts->keyring == NULL && autoload) {
+ loadKeyring(ts);
+ }
+ keyring = rpmKeyringLink(ts->keyring);
+ }
+ return keyring;
+}
+
+int rpmtsSetKeyring(rpmts ts, rpmKeyring keyring)
+{
+ /*
+ * Should we permit switching keyring on the fly? For now, require
+ * rpmdb isn't open yet (fairly arbitrary limitation)...
+ */
+ if (ts == NULL || rpmtsGetRdb(ts) != NULL)
+ return -1;
+
+ rpmKeyringFree(ts->keyring);
+ ts->keyring = rpmKeyringLink(keyring);
+ return 0;
+}
+
+static int loadKeyringFromFiles(rpmts ts)
+{
+ ARGV_t files = NULL;
+ /* XXX TODO: deal with chroot path issues */
+ char *pkpath = rpmGetPath(ts->rootDir, "%{_keyringpath}/*.key", NULL);
+ int nkeys = 0;
+
+ rpmlog(RPMLOG_DEBUG, "loading keyring from pubkeys in %s\n", pkpath);
+ if (rpmGlob(pkpath, NULL, &files)) {
+ rpmlog(RPMLOG_DEBUG, "couldn't find any keys in %s\n", pkpath);
+ goto exit;
+ }
+
+ for (char **f = files; *f; f++) {
+ rpmPubkey key = rpmPubkeyRead(*f);
+ if (!key) {
+ rpmlog(RPMLOG_ERR, _("%s: reading of public key failed.\n"), *f);
+ continue;
+ }
+ if (rpmKeyringAddKey(ts->keyring, key) == 0) {
+ nkeys++;
+ rpmlog(RPMLOG_DEBUG, "added key %s to keyring\n", *f);
+ }
+ rpmPubkeyFree(key);
+ }
+exit:
+ free(pkpath);
+ argvFree(files);
+ return nkeys;
+}
+
+static int loadKeyringFromDB(rpmts ts)
+{
+ Header h;
+ rpmdbMatchIterator mi;
+ int nkeys = 0;
+
+ rpmlog(RPMLOG_DEBUG, "loading keyring from rpmdb\n");
+ mi = rpmtsInitIterator(ts, RPMDBI_NAME, "gpg-pubkey", 0);
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ struct rpmtd_s pubkeys;
+ const char *key;
+
+ if (!headerGet(h, RPMTAG_PUBKEYS, &pubkeys, HEADERGET_MINMEM))
+ continue;
+
+ while ((key = rpmtdNextString(&pubkeys))) {
+ uint8_t *pkt;
+ size_t pktlen;
+
+ if (b64decode(key, (void **) &pkt, &pktlen) == 0) {
+ rpmPubkey key = rpmPubkeyNew(pkt, pktlen);
+ if (rpmKeyringAddKey(ts->keyring, key) == 0) {
+ char *nvr = headerGetAsString(h, RPMTAG_NVR);
+ rpmlog(RPMLOG_DEBUG, "added key %s to keyring\n", nvr);
+ free(nvr);
+ nkeys++;
+ }
+ rpmPubkeyFree(key);
+ free(pkt);
+ }
+ }
+ rpmtdFreeData(&pubkeys);
+ }
+ rpmdbFreeIterator(mi);
+
+ return nkeys;
+}
+
+static void loadKeyring(rpmts ts)
+{
+ ts->keyring = rpmKeyringNew();
+ if (loadKeyringFromFiles(ts) == 0) {
+ if (loadKeyringFromDB(ts) > 0) {
+ /* XXX make this a warning someday... */
+ rpmlog(RPMLOG_DEBUG, "Using legacy gpg-pubkey(s) from rpmdb\n");
+ }
+ }
+}
+
+/* Build pubkey header. */
+static int makePubkeyHeader(rpmts ts, rpmPubkey key, Header h)
+{
+ const char * afmt = "%{pubkeys:armor}";
+ const char * group = "Public Keys";
+ const char * license = "pubkey";
+ const char * buildhost = "localhost";
+ rpmsenseFlags pflags = (RPMSENSE_KEYRING|RPMSENSE_EQUAL);
+ uint32_t zero = 0;
+ pgpDig dig = NULL;
+ pgpDigParams pubp = NULL;
+ char * d = NULL;
+ char * enc = NULL;
+ char * n = NULL;
+ char * u = NULL;
+ char * v = NULL;
+ char * r = NULL;
+ char * evr = NULL;
+ int rc = -1;
+
+ if ((enc = rpmPubkeyBase64(key)) == NULL)
+ goto exit;
+ if ((dig = rpmPubkeyDig(key)) == NULL)
+ goto exit;
+
+ /* Build header elements. */
+ pubp = &dig->pubkey;
+ v = pgpHexStr(pubp->signid, sizeof(pubp->signid));
+ r = pgpHexStr(pubp->time, sizeof(pubp->time));
+
+ rasprintf(&n, "gpg(%s)", v+8);
+ rasprintf(&u, "gpg(%s)", pubp->userid ? pubp->userid : "none");
+ rasprintf(&evr, "%d:%s-%s", pubp->version, v, r);
+
+ headerPutString(h, RPMTAG_PUBKEYS, enc);
+
+ if ((d = headerFormat(h, afmt, NULL)) == NULL)
+ goto exit;
+
+ headerPutString(h, RPMTAG_NAME, "gpg-pubkey");
+ headerPutString(h, RPMTAG_VERSION, v+8);
+ headerPutString(h, RPMTAG_RELEASE, r);
+ headerPutString(h, RPMTAG_DESCRIPTION, d);
+ headerPutString(h, RPMTAG_GROUP, group);
+ headerPutString(h, RPMTAG_LICENSE, license);
+ headerPutString(h, RPMTAG_SUMMARY, u);
+
+ headerPutUint32(h, RPMTAG_SIZE, &zero, 1);
+
+ headerPutString(h, RPMTAG_PROVIDENAME, u);
+ headerPutString(h, RPMTAG_PROVIDEVERSION, evr);
+ headerPutUint32(h, RPMTAG_PROVIDEFLAGS, &pflags, 1);
+
+ headerPutString(h, RPMTAG_PROVIDENAME, n);
+ headerPutString(h, RPMTAG_PROVIDEVERSION, evr);
+ headerPutUint32(h, RPMTAG_PROVIDEFLAGS, &pflags, 1);
+
+ headerPutString(h, RPMTAG_RPMVERSION, RPMVERSION);
+ headerPutString(h, RPMTAG_BUILDHOST, buildhost);
+ headerPutString(h, RPMTAG_SOURCERPM, "(none)");
+
+ { rpm_tid_t tid = rpmtsGetTid(ts);
+ headerPutUint32(h, RPMTAG_INSTALLTIME, &tid, 1);
+ headerPutUint32(h, RPMTAG_INSTALLTID, &tid, 1);
+ headerPutUint32(h, RPMTAG_BUILDTIME, &tid, 1);
+ }
+ rc = 0;
+
+exit:
+ pgpFreeDig(dig);
+ free(n);
+ free(u);
+ free(v);
+ free(r);
+ free(evr);
+ free(enc);
+ free(d);
+
+ return rc;
+}
+
+rpmRC rpmtsImportPubkey(const rpmts ts, const unsigned char * pkt, size_t pktlen)
+{
+ Header h = headerNew();
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ rpmPubkey pubkey = NULL;
+ rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
+ int krc;
+
+ if ((pubkey = rpmPubkeyNew(pkt, pktlen)) == NULL)
+ goto exit;
+ krc = rpmKeyringAddKey(keyring, pubkey);
+ if (krc < 0)
+ goto exit;
+
+ /* If we dont already have the key, make a persistent record of it */
+ if (krc == 0) {
+ if (makePubkeyHeader(ts, pubkey, h) != 0)
+ goto exit;
+
+ /* Add header to database. */
+ if (rpmtsOpenDB(ts, (O_RDWR|O_CREAT)))
+ goto exit;
+ if (rpmdbAdd(rpmtsGetRdb(ts), h) != 0)
+ goto exit;
+ }
+ rc = RPMRC_OK;
+
+exit:
+ /* Clean up. */
+ headerFree(h);
+ rpmPubkeyFree(pubkey);
+ rpmKeyringFree(keyring);
+ return rc;
+}
+
+int rpmtsSetSolveCallback(rpmts ts,
+ int (*solve) (rpmts ts, rpmds key, const void * data),
+ const void * solveData)
+{
+ int rc = 0;
+
+ if (ts) {
+ ts->solve = solve;
+ ts->solveData = solveData;
+ }
+ return rc;
+}
+
+int rpmtsSolve(rpmts ts, rpmds key)
+{
+ int rc = 1; /* assume not found */
+ if (ts && ts->solve) {
+ rc = (*ts->solve)(ts, key, ts->solveData);
+ }
+ return rc;
+}
+
+rpmps rpmtsProblems(rpmts ts)
+{
+ rpmps ps = rpmpsCreate();
+ rpmtsi pi = rpmtsiInit(ts);
+ rpmte p;
+
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ rpmps teprobs = rpmteProblems(p);
+ rpmpsMerge(ps, teprobs);
+ rpmpsFree(teprobs);
+ }
+ pi = rpmtsiFree(pi);
+
+ /* Return NULL on no problems instead of an empty set */
+ if (rpmpsNumProblems(ps) == 0) {
+ ps = rpmpsFree(ps);
+ }
+
+ return ps;
+}
+
+void rpmtsCleanProblems(rpmts ts)
+{
+ rpmte p;
+ rpmtsi pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL)
+ rpmteCleanProblems(p);
+ pi = rpmtsiFree(pi);
+}
+
+void rpmtsClean(rpmts ts)
+{
+ rpmtsi pi; rpmte p;
+ tsMembers tsmem = rpmtsMembers(ts);
+
+ if (ts == NULL)
+ return;
+
+ /* Clean up after dependency checks. */
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL)
+ rpmteCleanDS(p);
+ pi = rpmtsiFree(pi);
+
+ tsmem->addedPackages = rpmalFree(tsmem->addedPackages);
+
+ rpmtsCleanProblems(ts);
+}
+
+/* hash comparison function */
+static int uintCmp(unsigned int a, unsigned int b)
+{
+ return (a != b);
+}
+
+/* "hash"function*/
+static unsigned int uintId(unsigned int a)
+{
+ return a;
+}
+
+void rpmtsEmpty(rpmts ts)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ if (ts == NULL)
+ return;
+
+ rpmtsClean(ts);
+
+ for (int oc = 0; oc < tsmem->orderCount; oc++) {
+ tsmem->order[oc] = rpmteFree(tsmem->order[oc]);
+ }
+
+ tsmem->orderCount = 0;
+ intHashEmpty(tsmem->removedPackages);
+ return;
+}
+
+static void rpmtsPrintStat(const char * name, struct rpmop_s * op)
+{
+ static const unsigned int scale = (1000 * 1000);
+ if (op != NULL && op->count > 0)
+ fprintf(stderr, " %s %6d %6lu.%06lu MB %6lu.%06lu secs\n",
+ name, op->count,
+ (unsigned long)op->bytes/scale, (unsigned long)op->bytes%scale,
+ op->usecs/scale, op->usecs%scale);
+}
+
+static void rpmtsPrintStats(rpmts ts)
+{
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_TOTAL), 0);
+
+ rpmtsPrintStat("total: ", rpmtsOp(ts, RPMTS_OP_TOTAL));
+ rpmtsPrintStat("check: ", rpmtsOp(ts, RPMTS_OP_CHECK));
+ rpmtsPrintStat("order: ", rpmtsOp(ts, RPMTS_OP_ORDER));
+ rpmtsPrintStat("fingerprint: ", rpmtsOp(ts, RPMTS_OP_FINGERPRINT));
+ rpmtsPrintStat("install: ", rpmtsOp(ts, RPMTS_OP_INSTALL));
+ rpmtsPrintStat("erase: ", rpmtsOp(ts, RPMTS_OP_ERASE));
+ rpmtsPrintStat("scriptlets: ", rpmtsOp(ts, RPMTS_OP_SCRIPTLETS));
+ rpmtsPrintStat("compress: ", rpmtsOp(ts, RPMTS_OP_COMPRESS));
+ rpmtsPrintStat("uncompress: ", rpmtsOp(ts, RPMTS_OP_UNCOMPRESS));
+ rpmtsPrintStat("digest: ", rpmtsOp(ts, RPMTS_OP_DIGEST));
+ rpmtsPrintStat("signature: ", rpmtsOp(ts, RPMTS_OP_SIGNATURE));
+ rpmtsPrintStat("dbadd: ", rpmtsOp(ts, RPMTS_OP_DBADD));
+ rpmtsPrintStat("dbremove: ", rpmtsOp(ts, RPMTS_OP_DBREMOVE));
+ rpmtsPrintStat("dbget: ", rpmtsOp(ts, RPMTS_OP_DBGET));
+ rpmtsPrintStat("dbput: ", rpmtsOp(ts, RPMTS_OP_DBPUT));
+ rpmtsPrintStat("dbdel: ", rpmtsOp(ts, RPMTS_OP_DBDEL));
+}
+
+rpmts rpmtsFree(rpmts ts)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ if (ts == NULL)
+ return NULL;
+
+ if (ts->nrefs > 1)
+ return rpmtsUnlink(ts);
+
+ rpmtsEmpty(ts);
+
+ (void) rpmtsCloseDB(ts);
+
+ tsmem->removedPackages = intHashFree(tsmem->removedPackages);
+ tsmem->order = _free(tsmem->order);
+ ts->members = _free(ts->members);
+
+ ts->dsi = _free(ts->dsi);
+
+ if (ts->scriptFd != NULL) {
+ ts->scriptFd = fdFree(ts->scriptFd);
+ ts->scriptFd = NULL;
+ }
+ ts->rootDir = _free(ts->rootDir);
+ ts->lockPath = _free(ts->lockPath);
+
+ ts->keyring = rpmKeyringFree(ts->keyring);
+ ts->netsharedPaths = argvFree(ts->netsharedPaths);
+ ts->installLangs = argvFree(ts->installLangs);
+
+ ts->plugins = rpmpluginsFree(ts->plugins);
+
+ if (_rpmts_stats)
+ rpmtsPrintStats(ts);
+
+ (void) rpmtsUnlink(ts);
+
+ ts = _free(ts);
+
+ return NULL;
+}
+
+rpmVSFlags rpmtsVSFlags(rpmts ts)
+{
+ rpmVSFlags vsflags = 0;
+ if (ts != NULL)
+ vsflags = ts->vsflags;
+ return vsflags;
+}
+
+rpmVSFlags rpmtsSetVSFlags(rpmts ts, rpmVSFlags vsflags)
+{
+ rpmVSFlags ovsflags = 0;
+ if (ts != NULL) {
+ ovsflags = ts->vsflags;
+ ts->vsflags = vsflags;
+ }
+ return ovsflags;
+}
+
+const char * rpmtsRootDir(rpmts ts)
+{
+ return ts ? ts->rootDir : NULL;
+}
+
+int rpmtsSetRootDir(rpmts ts, const char * rootDir)
+{
+ if (ts == NULL || (rootDir && rootDir[0] != '/')) {
+ return -1;
+ }
+
+ ts->rootDir = _free(ts->rootDir);
+ /* Ensure clean path with a trailing slash */
+ ts->rootDir = rootDir ? rpmGetPath(rootDir, NULL) : xstrdup("/");
+ if (!rstreq(ts->rootDir, "/")) {
+ rstrcat(&ts->rootDir, "/");
+ }
+ return 0;
+}
+
+FD_t rpmtsScriptFd(rpmts ts)
+{
+ FD_t scriptFd = NULL;
+ if (ts != NULL) {
+ scriptFd = ts->scriptFd;
+ }
+ return scriptFd;
+}
+
+void rpmtsSetScriptFd(rpmts ts, FD_t scriptFd)
+{
+
+ if (ts != NULL) {
+ if (ts->scriptFd != NULL) {
+ ts->scriptFd = fdFree(ts->scriptFd);
+ ts->scriptFd = NULL;
+ }
+ if (scriptFd != NULL)
+ ts->scriptFd = fdLink(scriptFd);
+ }
+}
+
+struct selabel_handle * rpmtsSELabelHandle(rpmts ts)
+{
+#if WITH_SELINUX
+ if (ts != NULL) {
+ return ts->selabelHandle;
+ }
+#endif
+ return NULL;
+}
+
+rpmRC rpmtsSELabelInit(rpmts ts, const char *path)
+{
+#if WITH_SELINUX
+ if (ts == NULL || path == NULL) {
+ return RPMRC_FAIL;
+ }
+
+ struct selinux_opt opts[] = {
+ {SELABEL_OPT_PATH, path}
+ };
+
+ if (ts->selabelHandle) {
+ rpmtsSELabelFini(ts);
+ }
+ ts->selabelHandle = selabel_open(SELABEL_CTX_FILE, opts, 1);
+
+ if (!ts->selabelHandle) {
+ return RPMRC_FAIL;
+ }
+#endif
+ return RPMRC_OK;
+}
+
+void rpmtsSELabelFini(rpmts ts)
+{
+#if WITH_SELINUX
+ if (ts && ts->selabelHandle) {
+ selabel_close(ts->selabelHandle);
+ ts->selabelHandle = NULL;
+ }
+#endif
+}
+
+rpm_tid_t rpmtsGetTid(rpmts ts)
+{
+ rpm_tid_t tid = (rpm_tid_t)-1; /* XXX -1 is time(2) error return. */
+ if (ts != NULL) {
+ tid = ts->tid;
+ }
+ return tid;
+}
+
+rpm_tid_t rpmtsSetTid(rpmts ts, rpm_tid_t tid)
+{
+ rpm_tid_t otid = (rpm_tid_t)-1; /* XXX -1 is time(2) error return. */
+ if (ts != NULL) {
+ otid = ts->tid;
+ ts->tid = tid;
+ }
+ return otid;
+}
+
+rpmdb rpmtsGetRdb(rpmts ts)
+{
+ rpmdb rdb = NULL;
+ if (ts != NULL) {
+ rdb = ts->rdb;
+ }
+ return rdb;
+}
+
+void * rpmtsNotify(rpmts ts, rpmte te,
+ rpmCallbackType what, rpm_loff_t amount, rpm_loff_t total)
+{
+ void * ptr = NULL;
+ if (ts && ts->notify) {
+ Header h = NULL;
+ fnpyKey cbkey = NULL;
+ if (te) {
+ h = rpmteHeader(te);
+ cbkey = rpmteKey(te);
+ }
+ ptr = ts->notify(h, what, amount, total, cbkey, ts->notifyData);
+
+ if (h) {
+ headerFree(h); /* undo rpmteHeader() ref */
+ }
+ }
+ return ptr;
+}
+
+int rpmtsNElements(rpmts ts)
+{
+ int nelements = 0;
+ tsMembers tsmem = rpmtsMembers(ts);
+ if (tsmem != NULL && tsmem->order != NULL) {
+ nelements = tsmem->orderCount;
+ }
+ return nelements;
+}
+
+rpmte rpmtsElement(rpmts ts, int ix)
+{
+ rpmte te = NULL;
+ tsMembers tsmem = rpmtsMembers(ts);
+ if (tsmem != NULL && tsmem->order != NULL) {
+ if (ix >= 0 && ix < tsmem->orderCount)
+ te = tsmem->order[ix];
+ }
+ return te;
+}
+
+rpmprobFilterFlags rpmtsFilterFlags(rpmts ts)
+{
+ return (ts != NULL ? ts->ignoreSet : 0);
+}
+
+rpmtransFlags rpmtsFlags(rpmts ts)
+{
+ return (ts != NULL ? ts->transFlags : 0);
+}
+
+rpmtransFlags rpmtsSetFlags(rpmts ts, rpmtransFlags transFlags)
+{
+ rpmtransFlags otransFlags = 0;
+ if (ts != NULL) {
+ otransFlags = ts->transFlags;
+ ts->transFlags = transFlags;
+ }
+ return otransFlags;
+}
+
+rpm_color_t rpmtsColor(rpmts ts)
+{
+ return (ts != NULL ? ts->color : 0);
+}
+
+rpm_color_t rpmtsSetColor(rpmts ts, rpm_color_t color)
+{
+ rpm_color_t ocolor = 0;
+ if (ts != NULL) {
+ ocolor = ts->color;
+ ts->color = color;
+ }
+ return ocolor;
+}
+
+rpm_color_t rpmtsPrefColor(rpmts ts)
+{
+ return (ts != NULL ? ts->prefcolor : 0);
+}
+
+rpm_color_t rpmtsSetPrefColor(rpmts ts, rpm_color_t color)
+{
+ rpm_color_t ocolor = 0;
+ if (ts != NULL) {
+ ocolor = ts->prefcolor;
+ ts->prefcolor = color;
+ }
+ return ocolor;
+}
+
+rpmop rpmtsOp(rpmts ts, rpmtsOpX opx)
+{
+ rpmop op = NULL;
+
+ if (ts != NULL && opx >= 0 && opx < RPMTS_OP_MAX)
+ op = ts->ops + opx;
+ return op;
+}
+
+rpmPlugins rpmtsPlugins(rpmts ts)
+{
+ return (ts != NULL ? ts->plugins : NULL);
+}
+
+int rpmtsSetNotifyCallback(rpmts ts,
+ rpmCallbackFunction notify, rpmCallbackData notifyData)
+{
+ if (ts != NULL) {
+ ts->notify = notify;
+ ts->notifyData = notifyData;
+ }
+ return 0;
+}
+
+tsMembers rpmtsMembers(rpmts ts)
+{
+ return (ts != NULL) ? ts->members : NULL;
+}
+
+rpmts rpmtsCreate(void)
+{
+ rpmts ts;
+ tsMembers tsmem;
+
+ ts = xcalloc(1, sizeof(*ts));
+ memset(&ts->ops, 0, sizeof(ts->ops));
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_TOTAL), -1);
+ ts->dsi = NULL;
+
+ ts->solve = NULL;
+ ts->solveData = NULL;
+
+ ts->rdb = NULL;
+ ts->dbmode = O_RDONLY;
+
+ ts->scriptFd = NULL;
+ ts->tid = (rpm_tid_t) time(NULL);
+
+ ts->color = rpmExpandNumeric("%{?_transaction_color}");
+ ts->prefcolor = rpmExpandNumeric("%{?_prefer_color}")?:2;
+
+ ts->netsharedPaths = NULL;
+ ts->installLangs = NULL;
+ { char *tmp = rpmExpand("%{_netsharedpath}", NULL);
+ if (tmp && *tmp != '%') {
+ argvSplit(&ts->netsharedPaths, tmp, ":");
+ }
+ free(tmp);
+
+ tmp = rpmExpand("%{_install_langs}", NULL);
+ if (tmp && *tmp != '%') {
+ ARGV_t langs = NULL;
+ argvSplit(&langs, tmp, ":");
+ /* If we'll be installing all languages anyway, don't bother */
+ for (ARGV_t l = langs; *l; l++) {
+ if (rstreq(*l, "all")) {
+ langs = argvFree(langs);
+ break;
+ }
+ }
+ ts->installLangs = langs;
+ }
+ free(tmp);
+ }
+
+ tsmem = xcalloc(1, sizeof(*ts->members));
+ tsmem->delta = 5;
+ tsmem->addedPackages = NULL;
+ tsmem->removedPackages = intHashCreate(128, uintId, uintCmp, NULL);
+ tsmem->orderAlloced = 0;
+ tsmem->orderCount = 0;
+ tsmem->order = NULL;
+ ts->members = tsmem;
+
+ ts->rootDir = NULL;
+ ts->keyring = NULL;
+
+ ts->selabelHandle = NULL;
+
+ ts->nrefs = 0;
+
+ ts->plugins = rpmpluginsNew(ts);
+
+ return rpmtsLink(ts);
+}
+
+rpmtsi rpmtsiFree(rpmtsi tsi)
+{
+ /* XXX watchout: a funky recursion segfaults here iff nrefs is wrong. */
+ if (tsi) {
+ tsi->ts = rpmtsFree(tsi->ts);
+ _free(tsi);
+ }
+ return NULL;
+}
+
+rpmtsi rpmtsiInit(rpmts ts)
+{
+ rpmtsi tsi = NULL;
+
+ tsi = xcalloc(1, sizeof(*tsi));
+ tsi->ts = rpmtsLink(ts);
+ tsi->oc = 0;
+ return tsi;
+}
+
+/**
+ * Return next transaction element.
+ * @param tsi transaction element iterator
+ * @return transaction element, NULL on termination
+ */
+static
+rpmte rpmtsiNextElement(rpmtsi tsi)
+{
+ rpmte te = NULL;
+ int oc = -1;
+
+ if (tsi == NULL || tsi->ts == NULL || rpmtsNElements(tsi->ts) <= 0)
+ return te;
+
+ if (tsi->oc < rpmtsNElements(tsi->ts)) oc = tsi->oc++;
+ if (oc != -1)
+ te = rpmtsElement(tsi->ts, oc);
+ return te;
+}
+
+rpmte rpmtsiNext(rpmtsi tsi, rpmElementTypes types)
+{
+ rpmte te;
+
+ while ((te = rpmtsiNextElement(tsi)) != NULL) {
+ if (types == 0 || (rpmteType(te) & types) != 0)
+ break;
+ }
+ return te;
+}
+
+#define RPMLOCK_PATH LOCALSTATEDIR "/rpm/.rpm.lock"
+rpmlock rpmtsAcquireLock(rpmts ts)
+{
+ static const char * const rpmlock_path_default = "%{?_rpmlock_path}";
+
+ if (ts->lockPath == NULL) {
+ const char *rootDir = rpmtsRootDir(ts);
+ char *t;
+
+ if (!rootDir || rpmChrootDone())
+ rootDir = "/";
+
+ t = rpmGenPath(rootDir, rpmlock_path_default, NULL);
+ if (t == NULL || *t == '\0' || *t == '%') {
+ free(t);
+ t = xstrdup(RPMLOCK_PATH);
+ }
+ ts->lockPath = xstrdup(t);
+ (void) rpmioMkpath(dirname(t), 0755, getuid(), getgid());
+ free(t);
+ }
+ return rpmlockAcquire(ts->lockPath, _("transaction"));
+}
+
diff --git a/lib/rpmts.h b/lib/rpmts.h
new file mode 100644
index 0000000..0b8d970
--- /dev/null
+++ b/lib/rpmts.h
@@ -0,0 +1,582 @@
+#ifndef H_RPMTS
+#define H_RPMTS
+
+/** \ingroup rpmts
+ * \file lib/rpmts.h
+ * Structures and prototypes used for an "rpmts" transaction set.
+ */
+
+#include <sys/types.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmte.h>
+#include <rpm/rpmps.h>
+#include <rpm/rpmsw.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmcallback.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int _rpmts_stats;
+
+/** \ingroup rpmts
+ * Bit(s) to control rpmtsRun() operation.
+ */
+enum rpmtransFlags_e {
+ RPMTRANS_FLAG_NONE = 0,
+ RPMTRANS_FLAG_TEST = (1 << 0), /*!< from --test */
+ RPMTRANS_FLAG_BUILD_PROBS = (1 << 1), /*!< don't process payload */
+ RPMTRANS_FLAG_NOSCRIPTS = (1 << 2), /*!< from --noscripts */
+ RPMTRANS_FLAG_JUSTDB = (1 << 3), /*!< from --justdb */
+ RPMTRANS_FLAG_NOTRIGGERS = (1 << 4), /*!< from --notriggers */
+ RPMTRANS_FLAG_NODOCS = (1 << 5), /*!< from --excludedocs */
+ RPMTRANS_FLAG_ALLFILES = (1 << 6), /*!< from --allfiles */
+ /* bit 7 unused */
+ RPMTRANS_FLAG_NOCONTEXTS = (1 << 8), /*!< from --nocontexts */
+ /* bits 9-15 unused */
+ RPMTRANS_FLAG_NOTRIGGERPREIN= (1 << 16), /*!< from --notriggerprein */
+ RPMTRANS_FLAG_NOPRE = (1 << 17), /*!< from --nopre */
+ RPMTRANS_FLAG_NOPOST = (1 << 18), /*!< from --nopost */
+ RPMTRANS_FLAG_NOTRIGGERIN = (1 << 19), /*!< from --notriggerin */
+ RPMTRANS_FLAG_NOTRIGGERUN = (1 << 20), /*!< from --notriggerun */
+ RPMTRANS_FLAG_NOPREUN = (1 << 21), /*!< from --nopreun */
+ RPMTRANS_FLAG_NOPOSTUN = (1 << 22), /*!< from --nopostun */
+ RPMTRANS_FLAG_NOTRIGGERPOSTUN = (1 << 23), /*!< from --notriggerpostun */
+ /* bits 24-25 unused */
+ RPMTRANS_FLAG_NOCOLLECTIONS = (1 << 26), /*!< from --nocollections */
+ RPMTRANS_FLAG_NOMD5 = (1 << 27), /*!< from --nomd5 */
+ RPMTRANS_FLAG_NOFILEDIGEST = (1 << 27), /*!< from --nofiledigest (alias to --nomd5) */
+ /* bits 28-29 unused */
+ RPMTRANS_FLAG_NOCONFIGS = (1 << 30), /*!< from --noconfigs */
+ RPMTRANS_FLAG_DEPLOOPS = (1 << 31) /*!< from --deploops */
+};
+
+typedef rpmFlags rpmtransFlags;
+
+#define _noTransScripts \
+ ( RPMTRANS_FLAG_NOPRE | \
+ RPMTRANS_FLAG_NOPOST | \
+ RPMTRANS_FLAG_NOPREUN | \
+ RPMTRANS_FLAG_NOPOSTUN \
+ )
+
+#define _noTransTriggers \
+ ( RPMTRANS_FLAG_NOTRIGGERPREIN | \
+ RPMTRANS_FLAG_NOTRIGGERIN | \
+ RPMTRANS_FLAG_NOTRIGGERUN | \
+ RPMTRANS_FLAG_NOTRIGGERPOSTUN \
+ )
+
+/* Avoid unnecessary breakage for stuff referring to these unused flags */
+#define RPMTRANS_FLAG_NOPAYLOAD 0
+#define RPMTRANS_FLAG_APPLYONLY 0
+#define RPMTRANS_FLAG_KEEPOBSOLETE 0
+#define RPMTRANS_FLAG_DIRSTASH 0
+#define RPMTRANS_FLAG_REPACKAGE 0
+#define RPMTRANS_FLAG_PKGCOMMIT 0
+#define RPMTRANS_FLAG_PKGUNDO 0
+#define RPMTRANS_FLAG_COMMIT 0
+#define RPMTRANS_FLAG_UNDO 0
+#define RPMTRANS_FLAG_REVERSE 0
+#define RPMTRANS_FLAG_NOSUGGEST 0
+#define RPMTRANS_FLAG_ADDINDEPS 0
+
+/** \ingroup rpmts
+ * Bit(s) to control digest and signature verification.
+ */
+enum rpmVSFlags_e {
+ RPMVSF_DEFAULT = 0,
+ RPMVSF_NOHDRCHK = (1 << 0),
+ RPMVSF_NEEDPAYLOAD = (1 << 1),
+ /* bit(s) 2-7 unused */
+ RPMVSF_NOSHA1HEADER = (1 << 8),
+ RPMVSF_NOMD5HEADER = (1 << 9), /* unimplemented */
+ RPMVSF_NODSAHEADER = (1 << 10),
+ RPMVSF_NORSAHEADER = (1 << 11), /* unimplemented */
+ /* bit(s) 12-15 unused */
+ RPMVSF_NOSHA1 = (1 << 16), /* unimplemented */
+ RPMVSF_NOMD5 = (1 << 17),
+ RPMVSF_NODSA = (1 << 18),
+ RPMVSF_NORSA = (1 << 19)
+ /* bit(s) 16-31 unused */
+};
+
+typedef rpmFlags rpmVSFlags;
+
+#define _RPMVSF_NODIGESTS \
+ ( RPMVSF_NOSHA1HEADER | \
+ RPMVSF_NOMD5HEADER | \
+ RPMVSF_NOSHA1 | \
+ RPMVSF_NOMD5 )
+
+#define _RPMVSF_NOSIGNATURES \
+ ( RPMVSF_NODSAHEADER | \
+ RPMVSF_NORSAHEADER | \
+ RPMVSF_NODSA | \
+ RPMVSF_NORSA )
+
+#define _RPMVSF_NOHEADER \
+ ( RPMVSF_NOSHA1HEADER | \
+ RPMVSF_NOMD5HEADER | \
+ RPMVSF_NODSAHEADER | \
+ RPMVSF_NORSAHEADER )
+
+#define _RPMVSF_NOPAYLOAD \
+ ( RPMVSF_NOSHA1 | \
+ RPMVSF_NOMD5 | \
+ RPMVSF_NODSA | \
+ RPMVSF_NORSA )
+
+/** \ingroup rpmts
+ * Indices for timestamps.
+ */
+typedef enum rpmtsOpX_e {
+ RPMTS_OP_TOTAL = 0,
+ RPMTS_OP_CHECK = 1,
+ RPMTS_OP_ORDER = 2,
+ RPMTS_OP_FINGERPRINT = 3,
+ RPMTS_OP_INSTALL = 5,
+ RPMTS_OP_ERASE = 6,
+ RPMTS_OP_SCRIPTLETS = 7,
+ RPMTS_OP_COMPRESS = 8,
+ RPMTS_OP_UNCOMPRESS = 9,
+ RPMTS_OP_DIGEST = 10,
+ RPMTS_OP_SIGNATURE = 11,
+ RPMTS_OP_DBADD = 12,
+ RPMTS_OP_DBREMOVE = 13,
+ RPMTS_OP_DBGET = 14,
+ RPMTS_OP_DBPUT = 15,
+ RPMTS_OP_DBDEL = 16,
+ RPMTS_OP_MAX = 17
+} rpmtsOpX;
+
+/** \ingroup rpmts
+ * Perform dependency resolution on the transaction set.
+ *
+ * Any problems found by rpmtsCheck() can be examined by retrieving the
+ * problem set with rpmtsProblems(), success here only means that
+ * the resolution was successfully attempted for all packages in the set.
+ *
+ * @param ts transaction set
+ * @return 0 on success
+ */
+int rpmtsCheck(rpmts ts);
+
+/** \ingroup rpmts
+ * Determine package order in a transaction set according to dependencies.
+ *
+ * Order packages, returning error if circular dependencies cannot be
+ * eliminated by removing Requires's from the loop(s). Only dependencies from
+ * added or removed packages are used to determine ordering using a
+ * topological sort (Knuth vol. 1, p. 262). Use rpmtsCheck() to verify
+ * that all dependencies can be resolved.
+ *
+ * The final order ends up as installed packages followed by removed packages,
+ * with packages removed for upgrades immediately following the new package
+ * to be installed.
+ *
+ * @param ts transaction set
+ * @return no. of (added) packages that could not be ordered
+ */
+int rpmtsOrder(rpmts ts);
+
+/** \ingroup rpmts
+ * Process all package elements in a transaction set. Before calling
+ * rpmtsRun be sure to have:
+ *
+ * - setup the rpm root dir via rpmtsSetRoot().
+ * - setup the rpm notify callback via rpmtsSetNotifyCallback().
+ * - setup the rpm transaction flags via rpmtsSetFlags().
+ *
+ * Additionally, though not required you may want to:
+ *
+ * - setup the rpm verify signature flags via rpmtsSetVSFlags().
+ *
+ * @param ts transaction set
+ * @param okProbs unused
+ * @param ignoreSet bits to filter problem types
+ * @return 0 on success, -1 on error, >0 with newProbs set
+ */
+int rpmtsRun(rpmts ts, rpmps okProbs, rpmprobFilterFlags ignoreSet);
+
+/** \ingroup rpmts
+ * Reference a transaction set instance.
+ * @param ts transaction set
+ * @return new transaction set reference
+ */
+rpmts rpmtsLink (rpmts ts);
+
+/** \ingroup rpmts
+ * Close the database used by the transaction.
+ * @param ts transaction set
+ * @return 0 on success
+ */
+int rpmtsCloseDB(rpmts ts);
+
+/** \ingroup rpmts
+ * Open the database used by the transaction.
+ * @param ts transaction set
+ * @param dbmode O_RDONLY or O_RDWR
+ * @return 0 on success
+ */
+int rpmtsOpenDB(rpmts ts, int dbmode);
+
+/** \ingroup rpmts
+ * Initialize the database used by the transaction.
+ * @deprecated An explicit rpmdbInit() is almost never needed.
+ * @param ts transaction set
+ * @param dbmode O_RDONLY or O_RDWR
+ * @return 0 on success
+ */
+int rpmtsInitDB(rpmts ts, int dbmode);
+
+/** \ingroup rpmts
+ * Return the transaction database mode
+ * @param ts transaction set
+ * @return O_RDONLY, O_RDWR or -1 (lazy opens disabled)
+ */
+int rpmtsGetDBMode(rpmts ts);
+
+/** \ingroup rpmts
+ * Set the transaction database mode. Only permitted when when backing
+ * database hasn't been opened yet (ie rpmtsGetRdb(ts) == NULL)
+ * @param ts transaction set
+ * @param dbmode O_RDONLY, O_RDWR or -1 (disable lazy opens)
+ * @return 0 on success, 1 on error
+ */
+int rpmtsSetDBMode(rpmts ts, int dbmode);
+
+/** \ingroup rpmts
+ * Rebuild the database used by the transaction.
+ * @param ts transaction set
+ * @return 0 on success
+ */
+int rpmtsRebuildDB(rpmts ts);
+
+/** \ingroup rpmts
+ * Verify the database used by the transaction.
+ * @param ts transaction set
+ * @return 0 on success
+ */
+int rpmtsVerifyDB(rpmts ts);
+
+/** \ingroup rpmts
+ * Return transaction database iterator.
+ * @param ts transaction set
+ * @param rpmtag database index tag
+ * @param keyp key data (NULL for sequential access)
+ * @param keylen key data length (0 will use strlen(keyp))
+ * @return NULL on failure
+ */
+rpmdbMatchIterator rpmtsInitIterator(const rpmts ts, rpmDbiTagVal rpmtag,
+ const void * keyp, size_t keylen);
+
+/** \ingroup rpmts
+ * Import public key packet(s).
+ * @todo Implicit --update policy for gpg-pubkey headers.
+ * @param ts transaction set
+ * @param pkt pgp pubkey packet(s)
+ * @param pktlen pgp pubkey length
+ * @return RPMRC_OK/RPMRC_FAIL
+ */
+rpmRC rpmtsImportPubkey(rpmts ts, const unsigned char * pkt, size_t pktlen);
+
+/** \ingroup rpmts
+ * Retrieve handle for keyring used for this transaction set
+ * @param ts transaction set
+ * @param autoload load default keyring if keyring is not set
+ * @return keyring handle (or NULL)
+ */
+rpmKeyring rpmtsGetKeyring(rpmts ts, int autoload);
+
+/** \ingroup rpmts
+ * Set keyring to use for this transaction set.
+ * Keyring can be only changed while the underlying rpm database is not
+ * yet open.
+ * @param ts transaction set
+ * @param keyring keyring handle (NULL to free current keyring)
+ * @return 0 on success, -1 on error
+ */
+int rpmtsSetKeyring(rpmts ts, rpmKeyring keyring);
+
+/** \ingroup rpmts
+ * Set dependency solver callback.
+ * @param ts transaction set
+ * @param (*solve) dependency solver callback
+ * @param solveData dependency solver callback data (opaque)
+ * @return 0 on success
+ */
+int rpmtsSetSolveCallback(rpmts ts,
+ int (*solve) (rpmts ts, rpmds ds, const void * data),
+ const void * solveData);
+
+/** \ingroup rpmts
+ * Return current transaction set problems.
+ * @param ts transaction set
+ * @return current problem set (or NULL if no problems)
+ */
+rpmps rpmtsProblems(rpmts ts);
+
+/** \ingroup rpmts
+ * Clean current transaction problem set.
+ * @param ts transaction set
+ */
+void rpmtsCleanProblems(rpmts ts);
+
+/** \ingroup rpmts
+ * Free memory needed only for dependency checks and ordering.
+ * @param ts transaction set
+ */
+void rpmtsClean(rpmts ts);
+
+/** \ingroup rpmts
+ * Re-create an empty transaction set.
+ * @param ts transaction set
+ */
+void rpmtsEmpty(rpmts ts);
+
+/** \ingroup rpmts
+ * Destroy transaction set, closing the database as well.
+ * @param ts transaction set
+ * @return NULL always
+ */
+rpmts rpmtsFree(rpmts ts);
+
+/** \ingroup rpmts
+ * Get verify signatures flag(s).
+ * @param ts transaction set
+ * @return verify signatures flags
+ */
+rpmVSFlags rpmtsVSFlags(rpmts ts);
+
+/** \ingroup rpmts
+ * Set verify signatures flag(s).
+ * @param ts transaction set
+ * @param vsflags new verify signatures flags
+ * @return previous value
+ */
+rpmVSFlags rpmtsSetVSFlags(rpmts ts, rpmVSFlags vsflags);
+
+/** \ingroup rpmts
+ * Get transaction rootDir, i.e. path to chroot(2).
+ * @param ts transaction set
+ * @return transaction rootDir
+ */
+const char * rpmtsRootDir(rpmts ts);
+
+/** \ingroup rpmts
+ * Set transaction rootDir, i.e. path to chroot(2).
+ * @param ts transaction set
+ * @param rootDir new transaction rootDir (or NULL)
+ * @return 0 on success, -1 on error (invalid rootDir)
+ */
+int rpmtsSetRootDir(rpmts ts, const char * rootDir);
+
+/** \ingroup rpmts
+ * Get transaction script file handle, i.e. stdout/stderr on scriptlet execution
+ * @param ts transaction set
+ * @return transaction script file handle
+ */
+FD_t rpmtsScriptFd(rpmts ts);
+
+/** \ingroup rpmts
+ * Set transaction script file handle, i.e. stdout/stderr on scriptlet execution
+ * @param ts transaction set
+ * @param scriptFd new script file handle (or NULL)
+ */
+void rpmtsSetScriptFd(rpmts ts, FD_t scriptFd);
+
+/** \ingroup rpmts
+ * Get transaction id, i.e. transaction time stamp.
+ * @param ts transaction set
+ * @return transaction id
+ */
+rpm_tid_t rpmtsGetTid(rpmts ts);
+
+/** \ingroup rpmts
+ * Set transaction id, i.e. transaction time stamp.
+ * @param ts transaction set
+ * @param tid new transaction id
+ * @return previous transaction id
+ */
+rpm_tid_t rpmtsSetTid(rpmts ts, rpm_tid_t tid);
+
+/** \ingroup rpmts
+ * Get transaction set database handle.
+ * @param ts transaction set
+ * @return transaction database handle
+ */
+rpmdb rpmtsGetRdb(rpmts ts);
+
+/** \ingroup rpmts
+ * Perform transaction progress notify callback.
+ * @param ts transaction set
+ * @param te current transaction element
+ * @param what type of call back
+ * @param amount current value
+ * @param total final value
+ * @return callback dependent pointer
+ */
+void * rpmtsNotify(rpmts ts, rpmte te,
+ rpmCallbackType what, rpm_loff_t amount, rpm_loff_t total);
+
+/** \ingroup rpmts
+ * Return number of (ordered) transaction set elements.
+ * @param ts transaction set
+ * @return no. of transaction set elements
+ */
+int rpmtsNElements(rpmts ts);
+
+/** \ingroup rpmts
+ * Return (ordered) transaction set element.
+ * @param ts transaction set
+ * @param ix transaction element index
+ * @return transaction element (or NULL)
+ */
+rpmte rpmtsElement(rpmts ts, int ix);
+
+/** \ingroup rpmts
+ * Get problem ignore bit mask, i.e. bits to filter encountered problems.
+ * @param ts transaction set
+ * @return ignore bit mask
+ */
+rpmprobFilterFlags rpmtsFilterFlags(rpmts ts);
+
+/** \ingroup rpmts
+ * Get transaction flags, i.e. bits that control rpmtsRun().
+ * @param ts transaction set
+ * @return transaction flags
+ */
+rpmtransFlags rpmtsFlags(rpmts ts);
+
+/** \ingroup rpmts
+ * Set transaction flags, i.e. bits that control rpmtsRun().
+ * @param ts transaction set
+ * @param transFlags new transaction flags
+ * @return previous transaction flags
+ */
+rpmtransFlags rpmtsSetFlags(rpmts ts, rpmtransFlags transFlags);
+
+/** \ingroup rpmts
+ * Retrieve color bits of transaction set.
+ * @param ts transaction set
+ * @return color bits
+ */
+rpm_color_t rpmtsColor(rpmts ts);
+
+/** \ingroup rpmts
+ * Retrieve prefered file color
+ * @param ts transaction set
+ * @return color bits
+ */
+rpm_color_t rpmtsPrefColor(rpmts ts);
+
+/** \ingroup rpmts
+ * Set color bits of transaction set.
+ * @param ts transaction set
+ * @param color new color bits
+ * @return previous color bits
+ */
+rpm_color_t rpmtsSetColor(rpmts ts, rpm_color_t color);
+
+/** \ingroup rpmts
+ * Set prefered file color
+ * @param ts transaction set
+ * @param color new color bits
+ * @return previous color bits
+ */
+rpm_color_t rpmtsSetPrefColor(rpmts ts, rpm_color_t color);
+
+/** \ingroup rpmts
+ * Retrieve operation timestamp from a transaction set.
+ * @param ts transaction set
+ * @param opx operation timestamp index
+ * @return pointer to operation timestamp.
+ */
+rpmop rpmtsOp(rpmts ts, rpmtsOpX opx);
+
+/** \ingroup rpmts
+ * Get the plugins associated with a transaction set
+ * @param ts transaction set
+ * @return plugins
+ */
+rpmPlugins rpmtsPlugins(rpmts ts);
+
+/** \ingroup rpmts
+ * Set transaction notify callback function and argument.
+ *
+ * @warning This call must be made before rpmtsRun() for
+ * install/upgrade/freshen to function correctly.
+ *
+ * @param ts transaction set
+ * @param notify progress callback
+ * @param notifyData progress callback private data
+ * @return 0 on success
+ */
+int rpmtsSetNotifyCallback(rpmts ts,
+ rpmCallbackFunction notify,
+ rpmCallbackData notifyData);
+
+/** \ingroup rpmts
+ * Create an empty transaction set.
+ * @return new transaction set
+ */
+rpmts rpmtsCreate(void);
+
+/** \ingroup rpmts
+ * Add package to be installed to transaction set.
+ *
+ * The transaction set is checked for duplicate package names.
+ * If found, the package with the "newest" EVR will be replaced.
+ *
+ * @param ts transaction set
+ * @param h header
+ * @param key package retrieval key (e.g. file name)
+ * @param upgrade is package being upgraded?
+ * @param relocs package file relocations
+ * @return 0 on success, 1 on I/O error, 2 needs capabilities
+ */
+int rpmtsAddInstallElement(rpmts ts, Header h,
+ const fnpyKey key, int upgrade,
+ rpmRelocation * relocs);
+
+/** \ingroup rpmts
+ * Add package to be erased to transaction set.
+ * @param ts transaction set
+ * @param h header
+ * @param dboffset ununsed
+ * @return 0 on success, 1 on error (not installed)
+ */
+int rpmtsAddEraseElement(rpmts ts, Header h, int dboffset);
+
+/** \ingroup rpmte
+ * Destroy transaction element iterator.
+ * @param tsi transaction element iterator
+ * @return NULL always
+ */
+rpmtsi rpmtsiFree(rpmtsi tsi);
+
+/** \ingroup rpmte
+ * Create transaction element iterator.
+ * @param ts transaction set
+ * @return transaction element iterator
+ */
+rpmtsi rpmtsiInit(rpmts ts);
+
+/** \ingroup rpmte
+ * Return next transaction element of type.
+ * @param tsi transaction element iterator
+ * @param types transaction element type selector (0 for any)
+ * @return next transaction element of type, NULL on termination
+ */
+rpmte rpmtsiNext(rpmtsi tsi, rpmElementTypes types);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* H_RPMTS */
diff --git a/lib/rpmts_internal.h b/lib/rpmts_internal.h
new file mode 100644
index 0000000..438fd46
--- /dev/null
+++ b/lib/rpmts_internal.h
@@ -0,0 +1,108 @@
+#ifndef _RPMTS_INTERNAL_H
+#define _RPMTS_INTERNAL_H
+
+#include <rpm/rpmts.h>
+
+#include "lib/rpmal.h" /* XXX availablePackage */
+#include "lib/fprint.h"
+#include "lib/rpmlock.h"
+
+typedef struct diskspaceInfo_s * rpmDiskSpaceInfo;
+
+/* Transaction set elements information */
+typedef struct tsMembers_s {
+ intHash removedPackages; /*!< Set of packages being removed. */
+ rpmal addedPackages; /*!< Set of packages being installed. */
+
+ rpmte * order; /*!< Packages sorted by dependencies. */
+ int orderCount; /*!< No. of transaction elements. */
+ int orderAlloced; /*!< No. of allocated transaction elements. */
+ int delta; /*!< Delta for reallocation. */
+} * tsMembers;
+
+/** \ingroup rpmts
+ * The set of packages to be installed/removed atomically.
+ */
+struct rpmts_s {
+ rpmtransFlags transFlags; /*!< Bit(s) to control operation. */
+
+ int (*solve) (rpmts ts, rpmds key, const void * data);
+ /*!< Search for NEVRA key. */
+ const void * solveData; /*!< Solve callback data */
+
+ rpmCallbackFunction notify; /*!< Callback function. */
+ rpmCallbackData notifyData; /*!< Callback private data. */
+
+ rpmprobFilterFlags ignoreSet;
+ /*!< Bits to filter current problems. */
+
+ rpmDiskSpaceInfo dsi; /*!< Per filesystem disk/inode usage. */
+
+ rpmdb rdb; /*!< Install database handle. */
+ int dbmode; /*!< Install database open mode. */
+
+ tsMembers members; /*!< Transaction set member info (order etc) */
+
+ struct selabel_handle * selabelHandle; /*!< Handle to selabel */
+
+ char * rootDir; /*!< Path to top of install tree. */
+ char * lockPath; /*!< Transaction lock path */
+ FD_t scriptFd; /*!< Scriptlet stdout/stderr. */
+ rpm_tid_t tid; /*!< Transaction id. */
+
+ rpm_color_t color; /*!< Transaction color bits. */
+ rpm_color_t prefcolor; /*!< Preferred file color. */
+
+ rpmVSFlags vsflags; /*!< Signature/digest verification flags. */
+
+ rpmKeyring keyring; /*!< Keyring in use. */
+
+ ARGV_t netsharedPaths; /*!< From %{_netsharedpath} */
+ ARGV_t installLangs; /*!< From %{_install_langs} */
+
+ struct rpmop_s ops[RPMTS_OP_MAX];
+
+ rpmPlugins plugins; /*!< Transaction plugins */
+
+ int nrefs; /*!< Reference count. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+RPM_GNUC_INTERNAL
+tsMembers rpmtsMembers(rpmts ts);
+
+/* returns -1 for retry, 0 for ignore and 1 for not found */
+RPM_GNUC_INTERNAL
+int rpmtsSolve(rpmts ts, rpmds key);
+
+RPM_GNUC_INTERNAL
+rpmlock rpmtsAcquireLock(rpmts ts);
+
+/** \ingroup rpmts
+ * Get the selabel handle from the transaction set
+ * @param ts transaction set
+ * @return rpm selabel handle, or NULL if it hasn't been initialized yet
+ */
+struct selabel_handle * rpmtsSELabelHandle(rpmts ts);
+
+/** \ingroup rpmts
+ * Initialize selabel
+ * @param ts transaction set
+ * @param path path to contexts file
+ * @return RPMRC_OK on success, RPMRC_FAIL otherwise
+ */
+rpmRC rpmtsSELabelInit(rpmts ts, const char * path);
+
+/** \ingroup rpmts
+ * Clean up selabel
+ * @param ts transaction set
+ */
+void rpmtsSELabelFini(rpmts ts);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _RPMTS_INTERNAL_H */
diff --git a/lib/rpmtypes.h b/lib/rpmtypes.h
new file mode 100644
index 0000000..28ee5a9
--- /dev/null
+++ b/lib/rpmtypes.h
@@ -0,0 +1,112 @@
+#ifndef _RPMTYPES_H
+#define _RPMTYPES_H
+
+/** \ingroup rpmtypes
+ * \file lib/rpmtypes.h
+ *
+ * Typedefs for RPM abstract data types.
+ * @todo The grouping needs love to look sane...
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef const char * errmsg_t;
+
+/** \ingroup rpmtypes
+ *
+ * RPM header and data retrieval types.
+ * @{
+ */
+typedef struct headerToken_s * Header;
+typedef struct headerIterator_s * HeaderIterator;
+
+typedef int32_t rpm_tag_t;
+typedef uint32_t rpm_tagtype_t;
+typedef uint32_t rpm_count_t;
+typedef rpm_tag_t rpmTagVal;
+typedef rpm_tag_t rpmDbiTagVal;
+
+typedef void * rpm_data_t;
+typedef const void * rpm_constdata_t;
+
+typedef struct rpmtd_s * rpmtd;
+
+typedef uint32_t rpm_color_t;
+typedef uint32_t rpm_flag_t;
+typedef uint32_t rpm_tid_t;
+
+typedef uint32_t rpmFlags;
+/** @} */
+
+/** \ingroup rpmtypes
+ *
+ * In-header hardcoded sizes for various POSIXy types
+ * @{
+ */
+typedef uint32_t rpm_off_t;
+typedef uint64_t rpm_loff_t;
+typedef uint32_t rpm_time_t;
+typedef uint16_t rpm_mode_t;
+typedef uint16_t rpm_rdev_t;
+typedef uint32_t rpm_dev_t;
+typedef uint32_t rpm_ino_t;
+/** @} */
+
+/** \ingroup rpmtypes
+ * The main types involved in transaction manipulation
+ * @{
+ */
+typedef struct rpmts_s * rpmts;
+typedef struct rpmte_s * rpmte;
+typedef struct rpmds_s * rpmds;
+typedef struct rpmfi_s * rpmfi;
+typedef struct rpmdb_s * rpmdb;
+typedef struct rpmdbMatchIterator_s * rpmdbMatchIterator;
+typedef struct rpmtsi_s * rpmtsi;
+typedef struct rpmps_s * rpmps;
+
+typedef struct rpmdbIndexIterator_s * rpmdbIndexIterator;
+typedef const void * fnpyKey;
+typedef void * rpmCallbackData;
+/** @} */
+
+typedef struct rpmPubkey_s * rpmPubkey;
+typedef struct rpmKeyring_s * rpmKeyring;
+
+typedef struct rpmPlugins_s * rpmPlugins;
+
+typedef struct rpmgi_s * rpmgi;
+
+typedef struct rpmSpec_s * rpmSpec;
+
+typedef struct rpmRelocation_s rpmRelocation;
+
+
+/** \ingroup rpmtypes
+ * RPM IO file descriptor type
+ */
+typedef struct _FD_s * FD_t;
+
+/** \ingroup rpmtypes
+ * Package read return codes.
+ */
+typedef enum rpmRC_e {
+ RPMRC_OK = 0, /*!< Generic success code */
+ RPMRC_NOTFOUND = 1, /*!< Generic not found code. */
+ RPMRC_FAIL = 2, /*!< Generic failure code. */
+ RPMRC_NOTTRUSTED = 3, /*!< Signature is OK, but key is not trusted. */
+ RPMRC_NOKEY = 4 /*!< Public key is unavailable. */
+} rpmRC;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* XXX included late as rpmtag.h depends on our definitions here... */
+#include <rpm/rpmtag.h>
+
+#endif /* _RPMTYPES_H */
diff --git a/lib/rpmug.c b/lib/rpmug.c
new file mode 100644
index 0000000..b365e4a
--- /dev/null
+++ b/lib/rpmug.c
@@ -0,0 +1,202 @@
+#include "system.h"
+
+#include <pwd.h>
+#include <grp.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmstring.h>
+
+#include "lib/misc.h"
+#include "lib/rpmug.h"
+#include "debug.h"
+
+#define HASHTYPE strCache
+#define HTKEYTYPE const char *
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+#undef HASHTYPE
+#undef HTKEYTYPE
+
+static strCache strStash = NULL;
+
+const char * rpmugStashStr(const char *str)
+{
+ const char *ret = NULL;
+ if (str) {
+ if (strStash == NULL) {
+ strStash = strCacheCreate(64, hashFunctionString, strcmp,
+ (strCacheFreeKey)rfree);
+ }
+
+ if (!strCacheGetEntry(strStash, str, &ret)) {
+ strCacheAddEntry(strStash, xstrdup(str));
+ (void) strCacheGetEntry(strStash, str, &ret);
+ }
+ }
+ return ret;
+}
+
+/*
+ * These really ought to use hash tables. I just made the
+ * guess that most files would be owned by root or the same person/group
+ * who owned the last file. Those two values are cached, everything else
+ * is looked up via getpw() and getgr() functions. If this performs
+ * too poorly I'll have to implement it properly :-(
+ */
+
+int rpmugUid(const char * thisUname, uid_t * uid)
+{
+ static char * lastUname = NULL;
+ static size_t lastUnameLen = 0;
+ static size_t lastUnameAlloced;
+ static uid_t lastUid;
+ struct passwd * pwent;
+ size_t thisUnameLen;
+
+ if (!thisUname) {
+ lastUnameLen = 0;
+ return -1;
+ } else if (rstreq(thisUname, "root")) {
+ *uid = 0;
+ return 0;
+ }
+
+ thisUnameLen = strlen(thisUname);
+ if (lastUname == NULL || thisUnameLen != lastUnameLen ||
+ !rstreq(thisUname, lastUname))
+ {
+ if (lastUnameAlloced < thisUnameLen + 1) {
+ lastUnameAlloced = thisUnameLen + 10;
+ lastUname = xrealloc(lastUname, lastUnameAlloced); /* XXX memory leak */
+ }
+ strcpy(lastUname, thisUname);
+
+ pwent = getpwnam(thisUname);
+ if (pwent == NULL) {
+ /* FIX: shrug */
+ endpwent();
+ pwent = getpwnam(thisUname);
+ if (pwent == NULL) return -1;
+ }
+
+ lastUid = pwent->pw_uid;
+ }
+
+ *uid = lastUid;
+
+ return 0;
+}
+
+int rpmugGid(const char * thisGname, gid_t * gid)
+{
+ static char * lastGname = NULL;
+ static size_t lastGnameLen = 0;
+ static size_t lastGnameAlloced;
+ static gid_t lastGid;
+ size_t thisGnameLen;
+ struct group * grent;
+
+ if (thisGname == NULL) {
+ lastGnameLen = 0;
+ return -1;
+ } else if (rstreq(thisGname, "root")) {
+ *gid = 0;
+ return 0;
+ }
+
+ thisGnameLen = strlen(thisGname);
+ if (lastGname == NULL || thisGnameLen != lastGnameLen ||
+ !rstreq(thisGname, lastGname))
+ {
+ if (lastGnameAlloced < thisGnameLen + 1) {
+ lastGnameAlloced = thisGnameLen + 10;
+ lastGname = xrealloc(lastGname, lastGnameAlloced); /* XXX memory leak */
+ }
+ strcpy(lastGname, thisGname);
+
+ grent = getgrnam(thisGname);
+ if (grent == NULL) {
+ /* FIX: shrug */
+ endgrent();
+ grent = getgrnam(thisGname);
+ if (grent == NULL) {
+ return -1;
+ }
+ }
+ lastGid = grent->gr_gid;
+ }
+
+ *gid = lastGid;
+
+ return 0;
+}
+
+const char * rpmugUname(uid_t uid)
+{
+ static uid_t lastUid = (uid_t) -1;
+ static char * lastUname = NULL;
+ static size_t lastUnameLen = 0;
+
+ if (uid == (uid_t) -1) {
+ lastUid = (uid_t) -1;
+ return NULL;
+ } else if (uid == (uid_t) 0) {
+ return "root";
+ } else if (uid == lastUid) {
+ return lastUname;
+ } else {
+ struct passwd * pwent = getpwuid(uid);
+ size_t len;
+
+ if (pwent == NULL) return NULL;
+
+ lastUid = uid;
+ len = strlen(pwent->pw_name);
+ if (lastUnameLen < len + 1) {
+ lastUnameLen = len + 20;
+ lastUname = xrealloc(lastUname, lastUnameLen);
+ }
+ strcpy(lastUname, pwent->pw_name);
+
+ return lastUname;
+ }
+}
+
+const char * rpmugGname(gid_t gid)
+{
+ static gid_t lastGid = (gid_t) -1;
+ static char * lastGname = NULL;
+ static size_t lastGnameLen = 0;
+
+ if (gid == (gid_t) -1) {
+ lastGid = (gid_t) -1;
+ return NULL;
+ } else if (gid == (gid_t) 0) {
+ return "root";
+ } else if (gid == lastGid) {
+ return lastGname;
+ } else {
+ struct group * grent = getgrgid(gid);
+ size_t len;
+
+ if (grent == NULL) return NULL;
+
+ lastGid = gid;
+ len = strlen(grent->gr_name);
+ if (lastGnameLen < len + 1) {
+ lastGnameLen = len + 20;
+ lastGname = xrealloc(lastGname, lastGnameLen);
+ }
+ strcpy(lastGname, grent->gr_name);
+
+ return lastGname;
+ }
+}
+
+void rpmugFree(void)
+{
+ rpmugUid(NULL, NULL);
+ rpmugGid(NULL, NULL);
+ rpmugUname(-1);
+ rpmugGname(-1);
+ strStash = strCacheFree(strStash);
+}
diff --git a/lib/rpmug.h b/lib/rpmug.h
new file mode 100644
index 0000000..35e5d63
--- /dev/null
+++ b/lib/rpmug.h
@@ -0,0 +1,18 @@
+#ifndef _RPMUG_H
+#define _RPMUG_H
+
+#include <sys/types.h>
+
+const char * rpmugStashStr(const char *str);
+
+int rpmugUid(const char * name, uid_t * uid);
+
+int rpmugGid(const char * name, gid_t * gid);
+
+const char * rpmugUname(uid_t uid);
+
+const char * rpmugGname(gid_t gid);
+
+void rpmugFree(void);
+
+#endif /* _RPMUG_H */
diff --git a/lib/rpmvercmp.c b/lib/rpmvercmp.c
new file mode 100644
index 0000000..f5ae092
--- /dev/null
+++ b/lib/rpmvercmp.c
@@ -0,0 +1,131 @@
+/** \ingroup rpmts
+ * \file lib/rpmvercmp.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmlib.h> /* rpmvercmp proto */
+#include <rpm/rpmstring.h>
+
+#include "debug.h"
+
+/* compare alpha and numeric segments of two versions */
+/* return 1: a is newer than b */
+/* 0: a and b are the same version */
+/* -1: b is newer than a */
+int rpmvercmp(const char * a, const char * b)
+{
+ char oldch1, oldch2;
+ char abuf[strlen(a)+1], bbuf[strlen(b)+1];
+ char *str1 = abuf, *str2 = bbuf;
+ char * one, * two;
+ int rc;
+ int isnum;
+
+ /* easy comparison to see if versions are identical */
+ if (rstreq(a, b)) return 0;
+
+ strcpy(str1, a);
+ strcpy(str2, b);
+
+ one = str1;
+ two = str2;
+
+ /* loop through each version segment of str1 and str2 and compare them */
+ while (*one && *two) {
+ while (*one && !risalnum(*one)) one++;
+ while (*two && !risalnum(*two)) two++;
+
+ /* If we ran to the end of either, we are finished with the loop */
+ if (!(*one && *two)) break;
+
+ str1 = one;
+ str2 = two;
+
+ /* grab first completely alpha or completely numeric segment */
+ /* leave one and two pointing to the start of the alpha or numeric */
+ /* segment and walk str1 and str2 to end of segment */
+ if (risdigit(*str1)) {
+ while (*str1 && risdigit(*str1)) str1++;
+ while (*str2 && risdigit(*str2)) str2++;
+ isnum = 1;
+ } else {
+ while (*str1 && risalpha(*str1)) str1++;
+ while (*str2 && risalpha(*str2)) str2++;
+ isnum = 0;
+ }
+
+ /* save character at the end of the alpha or numeric segment */
+ /* so that they can be restored after the comparison */
+ oldch1 = *str1;
+ *str1 = '\0';
+ oldch2 = *str2;
+ *str2 = '\0';
+
+ /* this cannot happen, as we previously tested to make sure that */
+ /* the first string has a non-null segment */
+ if (one == str1) return -1; /* arbitrary */
+
+ /* take care of the case where the two version segments are */
+ /* different types: one numeric, the other alpha (i.e. empty) */
+ /* numeric segments are always newer than alpha segments */
+ /* XXX See patch #60884 (and details) from bugzilla #50977. */
+ if (two == str2) return (isnum ? 1 : -1);
+
+ if (isnum) {
+ /* this used to be done by converting the digit segments */
+ /* to ints using atoi() - it's changed because long */
+ /* digit segments can overflow an int - this should fix that. */
+
+ /* throw away any leading zeros - it's a number, right? */
+ while (*one == '0') one++;
+ while (*two == '0') two++;
+
+ /* whichever number has more digits wins */
+ if (strlen(one) > strlen(two)) return 1;
+ if (strlen(two) > strlen(one)) return -1;
+ }
+
+ /* strcmp will return which one is greater - even if the two */
+ /* segments are alpha or if they are numeric. don't return */
+ /* if they are equal because there might be more segments to */
+ /* compare */
+ rc = strcmp(one, two);
+ if (rc) return (rc < 1 ? -1 : 1);
+
+ /* restore character that was replaced by null above */
+ *str1 = oldch1;
+ one = str1;
+ *str2 = oldch2;
+ two = str2;
+ }
+
+ /* this catches the case where all numeric and alpha segments have */
+ /* compared identically but the segment sepparating characters were */
+ /* different */
+ if ((!*one) && (!*two)) return 0;
+
+ /* whichever version still has characters left over wins */
+ if (!*one) return -1; else return 1;
+}
+
+int rpmVersionCompare(Header first, Header second)
+{
+ /* Missing epoch becomes zero here, which is what we want */
+ uint32_t epochOne = headerGetNumber(first, RPMTAG_EPOCH);
+ uint32_t epochTwo = headerGetNumber(second, RPMTAG_EPOCH);
+ int rc;
+
+ if (epochOne < epochTwo)
+ return -1;
+ else if (epochOne > epochTwo)
+ return 1;
+
+ rc = rpmvercmp(headerGetString(first, RPMTAG_VERSION),
+ headerGetString(second, RPMTAG_VERSION));
+ if (rc)
+ return rc;
+
+ return rpmvercmp(headerGetString(first, RPMTAG_RELEASE),
+ headerGetString(second, RPMTAG_RELEASE));
+}
diff --git a/lib/rpmvf.h b/lib/rpmvf.h
new file mode 100644
index 0000000..51690b8
--- /dev/null
+++ b/lib/rpmvf.h
@@ -0,0 +1,103 @@
+#ifndef _RPMVF_H
+#define _RPMVF_H
+
+/** \ingroup rpmvf
+ * \file lib/rpmvf.h
+ * @todo Add a more complete API...
+ */
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmvf
+ * Bit(s) for rpmVerifyFile() attributes and result.
+ */
+enum rpmVerifyAttrs_e {
+ RPMVERIFY_NONE = 0, /*!< */
+ RPMVERIFY_MD5 = (1 << 0), /*!< from %verify(md5) - obsolete */
+ RPMVERIFY_FILEDIGEST= (1 << 0), /*!< from %verify(filedigest) */
+ RPMVERIFY_FILESIZE = (1 << 1), /*!< from %verify(size) */
+ RPMVERIFY_LINKTO = (1 << 2), /*!< from %verify(link) */
+ RPMVERIFY_USER = (1 << 3), /*!< from %verify(user) */
+ RPMVERIFY_GROUP = (1 << 4), /*!< from %verify(group) */
+ RPMVERIFY_MTIME = (1 << 5), /*!< from %verify(mtime) */
+ RPMVERIFY_MODE = (1 << 6), /*!< from %verify(mode) */
+ RPMVERIFY_RDEV = (1 << 7), /*!< from %verify(rdev) */
+ RPMVERIFY_CAPS = (1 << 8), /*!< from %verify(caps) */
+ /* bits 9-14 unused, reserved for rpmVerifyAttrs */
+ RPMVERIFY_CONTEXTS = (1 << 15), /*!< verify: from --nocontexts */
+ /* bits 16-22 used in rpmVerifyFlags */
+ /* bits 23-27 used in rpmQueryFlags */
+ RPMVERIFY_READLINKFAIL= (1 << 28), /*!< readlink failed */
+ RPMVERIFY_READFAIL = (1 << 29), /*!< file read failed */
+ RPMVERIFY_LSTATFAIL = (1 << 30), /*!< lstat failed */
+ RPMVERIFY_LGETFILECONFAIL = (1 << 31) /*!< lgetfilecon failed */
+};
+
+typedef rpmFlags rpmVerifyAttrs;
+
+#define RPMVERIFY_ALL ~(RPMVERIFY_NONE)
+#define RPMVERIFY_FAILURES \
+ (RPMVERIFY_LSTATFAIL|RPMVERIFY_READFAIL|RPMVERIFY_READLINKFAIL|RPMVERIFY_LGETFILECONFAIL)
+
+/** \ingroup rpmvf
+ * Bit(s) to control rpmVerify() operation
+ */
+enum rpmVerifyFlags_e {
+ VERIFY_DEFAULT = 0, /*!< */
+ VERIFY_MD5 = (1 << 0), /*!< from --nomd5 - obsolete */
+ VERIFY_FILEDIGEST = (1 << 0), /*!< from --nofiledigest */
+ VERIFY_SIZE = (1 << 1), /*!< from --nosize */
+ VERIFY_LINKTO = (1 << 2), /*!< from --nolinkto */
+ VERIFY_USER = (1 << 3), /*!< from --nouser */
+ VERIFY_GROUP = (1 << 4), /*!< from --nogroup */
+ VERIFY_MTIME = (1 << 5), /*!< from --nomtime */
+ VERIFY_MODE = (1 << 6), /*!< from --nomode */
+ VERIFY_RDEV = (1 << 7), /*!< from --nodev */
+ VERIFY_CAPS = (1 << 8), /*!< from --nocaps */
+ /* bits 9-14 unused, reserved for rpmVerifyAttrs */
+ VERIFY_CONTEXTS = (1 << 15), /*!< verify: from --nocontexts */
+ VERIFY_FILES = (1 << 16), /*!< verify: from --nofiles */
+ VERIFY_DEPS = (1 << 17), /*!< verify: from --nodeps */
+ VERIFY_SCRIPT = (1 << 18), /*!< verify: from --noscripts */
+ VERIFY_DIGEST = (1 << 19), /*!< verify: from --nodigest */
+ VERIFY_SIGNATURE = (1 << 20), /*!< verify: from --nosignature */
+ VERIFY_PATCHES = (1 << 21), /*!< verify: from --nopatches */
+ VERIFY_HDRCHK = (1 << 22), /*!< verify: from --nohdrchk */
+ VERIFY_FOR_LIST = (1 << 23), /*!< query: from --list */
+ VERIFY_FOR_STATE = (1 << 24), /*!< query: from --state */
+ VERIFY_FOR_DOCS = (1 << 25), /*!< query: from --docfiles */
+ VERIFY_FOR_CONFIG = (1 << 26), /*!< query: from --configfiles */
+ VERIFY_FOR_DUMPFILES= (1 << 27) /*!< query: from --dump */
+ /* bits 28-31 used in rpmVerifyAttrs */
+};
+
+typedef rpmFlags rpmVerifyFlags;
+
+#define VERIFY_ATTRS \
+ ( VERIFY_FILEDIGEST | VERIFY_SIZE | VERIFY_LINKTO | VERIFY_USER | VERIFY_GROUP | \
+ VERIFY_MTIME | VERIFY_MODE | VERIFY_RDEV | VERIFY_CONTEXTS | VERIFY_CAPS )
+#define VERIFY_ALL \
+ ( VERIFY_ATTRS | VERIFY_FILES | VERIFY_DEPS | VERIFY_SCRIPT | VERIFY_DIGEST |\
+ VERIFY_SIGNATURE | VERIFY_HDRCHK )
+
+/** \ingroup rpmvf
+ * Verify file attributes (including digest).
+ * @todo gnorpm and python bindings prevent this from being static.
+ * @param ts transaction set
+ * @param fi file info (with linked header and current file index)
+ * @retval *res bit(s) returned to indicate failure
+ * @param omitMask bit(s) to disable verify checks
+ * @return 0 on success (or not installed), 1 on error
+ */
+int rpmVerifyFile(const rpmts ts, rpmfi fi,
+ rpmVerifyAttrs * res, rpmVerifyAttrs omitMask);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RPMTYPES_H */
diff --git a/lib/signature.c b/lib/signature.c
new file mode 100644
index 0000000..74d74a4
--- /dev/null
+++ b/lib/signature.c
@@ -0,0 +1,534 @@
+/** \ingroup signature
+ * \file lib/signature.c
+ */
+
+#include "system.h"
+
+#include <inttypes.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmkeyring.h>
+
+#include "rpmio/digest.h"
+#include "lib/rpmlead.h"
+#include "lib/signature.h"
+#include "lib/header_internal.h"
+
+#include "debug.h"
+
+/* Dumb wrapper around headerPut() for signature header */
+static int sighdrPut(Header h, rpmTagVal tag, rpmTagType type,
+ rpm_data_t p, rpm_count_t c)
+{
+ struct rpmtd_s sigtd;
+ rpmtdReset(&sigtd);
+ sigtd.tag = tag;
+ sigtd.type = type;
+ sigtd.data = p;
+ sigtd.count = c;
+ return headerPut(h, &sigtd, HEADERPUT_DEFAULT);
+}
+
+/**
+ * Print package size.
+ * @todo rpmio: use fdSize rather than fstat(2) to get file size.
+ * @param fd package file handle
+ * @param siglen signature header size
+ * @param pad signature padding
+ * @param datalen length of header+payload
+ * @return rpmRC return code
+ */
+static inline rpmRC printSize(FD_t fd, size_t siglen, size_t pad, rpm_loff_t datalen)
+{
+ struct stat st;
+ int fdno = Fileno(fd);
+
+ if (fstat(fdno, &st) < 0)
+ return RPMRC_FAIL;
+
+ rpmlog(RPMLOG_DEBUG,
+ "Expected size: %12" PRIu64 \
+ " = lead(%d)+sigs(%zd)+pad(%zd)+data(%" PRIu64 ")\n",
+ RPMLEAD_SIZE+siglen+pad+datalen,
+ RPMLEAD_SIZE, siglen, pad, datalen);
+ rpmlog(RPMLOG_DEBUG,
+ " Actual size: %12" PRIu64 "\n", (rpm_loff_t) st.st_size);
+
+ return RPMRC_OK;
+}
+
+rpmRC rpmReadSignature(FD_t fd, Header * sighp, sigType sig_type, char ** msg)
+{
+ char *buf = NULL;
+ int32_t block[4];
+ int32_t il;
+ int32_t dl;
+ int32_t * ei = NULL;
+ entryInfo pe;
+ size_t nb;
+ int32_t ril = 0;
+ struct indexEntry_s entry;
+ struct entryInfo_s info;
+ unsigned char * dataStart;
+ unsigned char * dataEnd = NULL;
+ Header sigh = NULL;
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ int xx;
+ int i;
+
+ if (sighp)
+ *sighp = NULL;
+
+ if (sig_type != RPMSIGTYPE_HEADERSIG)
+ goto exit;
+
+ memset(block, 0, sizeof(block));
+ if ((xx = timedRead(fd, (void *)block, sizeof(block))) != sizeof(block)) {
+ rasprintf(&buf, _("sigh size(%d): BAD, read returned %d\n"),
+ (int)sizeof(block), xx);
+ goto exit;
+ }
+ if (memcmp(block, rpm_header_magic, sizeof(rpm_header_magic))) {
+ rasprintf(&buf, _("sigh magic: BAD\n"));
+ goto exit;
+ }
+ il = ntohl(block[2]);
+ if (il < 0 || il > 32) {
+ rasprintf(&buf,
+ _("sigh tags: BAD, no. of tags(%d) out of range\n"), il);
+ goto exit;
+ }
+ dl = ntohl(block[3]);
+ if (dl < 0 || dl > 8192) {
+ rasprintf(&buf,
+ _("sigh data: BAD, no. of bytes(%d) out of range\n"), dl);
+ goto exit;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ memset(&info, 0, sizeof(info));
+
+ nb = (il * sizeof(struct entryInfo_s)) + dl;
+ ei = xmalloc(sizeof(il) + sizeof(dl) + nb);
+ ei[0] = block[2];
+ ei[1] = block[3];
+ pe = (entryInfo) &ei[2];
+ dataStart = (unsigned char *) (pe + il);
+ if ((xx = timedRead(fd, (void *)pe, nb)) != nb) {
+ rasprintf(&buf,
+ _("sigh blob(%d): BAD, read returned %d\n"), (int)nb, xx);
+ goto exit;
+ }
+
+ /* Check (and convert) the 1st tag element. */
+ xx = headerVerifyInfo(1, dl, pe, &entry.info, 0);
+ if (xx != -1) {
+ rasprintf(&buf, _("tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
+ 0, entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+
+ /* Is there an immutable header region tag? */
+ if (entry.info.tag == RPMTAG_HEADERSIGNATURES
+ && entry.info.type == RPM_BIN_TYPE
+ && entry.info.count == REGION_TAG_COUNT)
+ {
+
+ if (entry.info.offset >= dl) {
+ rasprintf(&buf,
+ _("region offset: BAD, tag %d type %d offset %d count %d\n"),
+ entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+
+ /* Is there an immutable header region tag trailer? */
+ dataEnd = dataStart + entry.info.offset;
+ (void) memcpy(&info, dataEnd, REGION_TAG_COUNT);
+ /* XXX Really old packages have HEADER_IMAGE, not HEADER_SIGNATURES. */
+ if (info.tag == htonl(RPMTAG_HEADERIMAGE)) {
+ rpmTagVal stag = htonl(RPMTAG_HEADERSIGNATURES);
+ info.tag = stag;
+ memcpy(dataEnd, &stag, sizeof(stag));
+ }
+ dataEnd += REGION_TAG_COUNT;
+
+ xx = headerVerifyInfo(1, dl, &info, &entry.info, 1);
+ if (xx != -1 ||
+ !((entry.info.tag == RPMTAG_HEADERSIGNATURES || entry.info.tag == RPMTAG_HEADERIMAGE)
+ && entry.info.type == RPM_BIN_TYPE
+ && entry.info.count == REGION_TAG_COUNT))
+ {
+ rasprintf(&buf,
+ _("region trailer: BAD, tag %d type %d offset %d count %d\n"),
+ entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+ memset(&info, 0, sizeof(info));
+
+ /* Is the no. of tags in the region less than the total no. of tags? */
+ ril = entry.info.offset/sizeof(*pe);
+ if ((entry.info.offset % sizeof(*pe)) || ril > il) {
+ rasprintf(&buf, _("region size: BAD, ril(%d) > il(%d)\n"), ril, il);
+ goto exit;
+ }
+ }
+
+ /* Sanity check signature tags */
+ memset(&info, 0, sizeof(info));
+ for (i = 1; i < il; i++) {
+ xx = headerVerifyInfo(1, dl, pe+i, &entry.info, 0);
+ if (xx != -1) {
+ rasprintf(&buf,
+ _("sigh tag[%d]: BAD, tag %d type %d offset %d count %d\n"),
+ i, entry.info.tag, entry.info.type,
+ entry.info.offset, entry.info.count);
+ goto exit;
+ }
+ }
+
+ /* OK, blob looks sane, load the header. */
+ sigh = headerLoad(ei);
+ if (sigh == NULL) {
+ rasprintf(&buf, _("sigh load: BAD\n"));
+ goto exit;
+ }
+
+ { size_t sigSize = headerSizeof(sigh, HEADER_MAGIC_YES);
+ size_t pad = (8 - (sigSize % 8)) % 8; /* 8-byte pad */
+ ssize_t trc;
+ struct rpmtd_s sizetag;
+ rpm_loff_t archSize = 0;
+
+ /* Position at beginning of header. */
+ if (pad && (trc = timedRead(fd, (void *)block, pad)) != pad) {
+ rasprintf(&buf,
+ _("sigh pad(%zd): BAD, read %zd bytes\n"), pad, trc);
+ goto exit;
+ }
+
+ /* Print package component sizes. */
+ if (headerGet(sigh, RPMSIGTAG_LONGSIZE, &sizetag, HEADERGET_DEFAULT)) {
+ rpm_loff_t *tsize = rpmtdGetUint64(&sizetag);
+ archSize = (tsize) ? *tsize : 0;
+ } else if (headerGet(sigh, RPMSIGTAG_SIZE, &sizetag, HEADERGET_DEFAULT)) {
+ rpm_off_t *tsize = rpmtdGetUint32(&sizetag);
+ archSize = (tsize) ? *tsize : 0;
+ }
+ rpmtdFreeData(&sizetag);
+ rc = printSize(fd, sigSize, pad, archSize);
+ if (rc != RPMRC_OK) {
+ rasprintf(&buf,
+ _("sigh sigSize(%zd): BAD, fstat(2) failed\n"), sigSize);
+ goto exit;
+ }
+ }
+ ei = NULL; /* XXX will be freed with header */
+
+exit:
+ if (sighp && sigh && rc == RPMRC_OK)
+ *sighp = headerLink(sigh);
+ sigh = headerFree(sigh);
+ free(ei);
+
+ if (msg != NULL) {
+ *msg = buf;
+ } else {
+ free(buf);
+ }
+
+ return rc;
+}
+
+int rpmWriteSignature(FD_t fd, Header sigh)
+{
+ static uint8_t buf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ int sigSize, pad;
+ int rc;
+
+ rc = headerWrite(fd, sigh, HEADER_MAGIC_YES);
+ if (rc)
+ return rc;
+
+ sigSize = headerSizeof(sigh, HEADER_MAGIC_YES);
+ pad = (8 - (sigSize % 8)) % 8;
+ if (pad) {
+ if (Fwrite(buf, sizeof(buf[0]), pad, fd) != pad)
+ rc = 1;
+ }
+ rpmlog(RPMLOG_DEBUG, "Signature: size(%d)+pad(%d)\n", sigSize, pad);
+ return rc;
+}
+
+Header rpmNewSignature(void)
+{
+ Header sigh = headerNew();
+ return sigh;
+}
+
+Header rpmFreeSignature(Header sigh)
+{
+ return headerFree(sigh);
+}
+
+static int makeHDRDigest(Header sigh, const char * file, rpmTagVal sigTag)
+{
+ Header h = NULL;
+ FD_t fd = NULL;
+ char * SHA1 = NULL;
+ int ret = -1; /* assume failure. */
+
+ switch (sigTag) {
+ case RPMSIGTAG_SHA1:
+ fd = Fopen(file, "r.fdio");
+ if (fd == NULL || Ferror(fd))
+ goto exit;
+ h = headerRead(fd, HEADER_MAGIC_YES);
+ if (h == NULL)
+ goto exit;
+
+ if (headerIsEntry(h, RPMTAG_HEADERIMMUTABLE)) {
+ DIGEST_CTX ctx;
+ struct rpmtd_s utd;
+
+ if (!headerGet(h, RPMTAG_HEADERIMMUTABLE, &utd, HEADERGET_DEFAULT)
+ || utd.data == NULL)
+ {
+ rpmlog(RPMLOG_ERR,
+ _("Immutable header region could not be read. "
+ "Corrupted package?\n"));
+ goto exit;
+ }
+ ctx = rpmDigestInit(PGPHASHALGO_SHA1, RPMDIGEST_NONE);
+ (void) rpmDigestUpdate(ctx, rpm_header_magic, sizeof(rpm_header_magic));
+ (void) rpmDigestUpdate(ctx, utd.data, utd.count);
+ (void) rpmDigestFinal(ctx, (void **)&SHA1, NULL, 1);
+ rpmtdFreeData(&utd);
+ }
+
+ if (SHA1 == NULL)
+ goto exit;
+ if (!sighdrPut(sigh, RPMSIGTAG_SHA1, RPM_STRING_TYPE, SHA1, 1))
+ goto exit;
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+exit:
+ free(SHA1);
+ h = headerFree(h);
+ if (fd != NULL) (void) Fclose(fd);
+ return ret;
+}
+
+int rpmGenDigest(Header sigh, const char * file, rpmTagVal sigTag)
+{
+ struct stat st;
+ uint8_t * pkt = NULL;
+ size_t pktlen;
+ int ret = -1; /* assume failure. */
+
+ switch (sigTag) {
+ case RPMSIGTAG_SIZE: {
+ rpm_off_t size;
+ if (stat(file, &st) != 0)
+ break;
+ size = st.st_size;
+ if (!sighdrPut(sigh, sigTag, RPM_INT32_TYPE, &size, 1))
+ break;
+ ret = 0;
+ } break;
+ case RPMSIGTAG_LONGSIZE: {
+ rpm_loff_t size;
+ if (stat(file, &st) != 0)
+ break;
+ size = st.st_size;
+ if (!sighdrPut(sigh, sigTag, RPM_INT64_TYPE, &size, 1))
+ break;
+ ret = 0;
+ } break;
+ case RPMSIGTAG_MD5:
+ pktlen = 16;
+ pkt = xcalloc(pktlen, sizeof(*pkt));
+ if (rpmDoDigest(PGPHASHALGO_MD5, file, 0, pkt, NULL)
+ || !sighdrPut(sigh, sigTag, RPM_BIN_TYPE, pkt, pktlen))
+ break;
+ ret = 0;
+ break;
+ case RPMSIGTAG_SHA1:
+ ret = makeHDRDigest(sigh, file, sigTag);
+ break;
+ default:
+ break;
+ }
+ free(pkt);
+
+ return ret;
+}
+
+static const char * rpmSigString(rpmRC res)
+{
+ const char * str;
+ switch (res) {
+ case RPMRC_OK: str = "OK"; break;
+ case RPMRC_FAIL: str = "BAD"; break;
+ case RPMRC_NOKEY: str = "NOKEY"; break;
+ case RPMRC_NOTTRUSTED: str = "NOTRUSTED"; break;
+ default:
+ case RPMRC_NOTFOUND: str = "UNKNOWN"; break;
+ }
+ return str;
+}
+
+static rpmRC
+verifyMD5Digest(rpmtd sigtd, DIGEST_CTX md5ctx, char **msg)
+{
+ rpmRC res = RPMRC_FAIL; /* assume failure */
+ uint8_t * md5sum = NULL;
+ size_t md5len = 0;
+ char *md5;
+ const char *title = _("MD5 digest:");
+ *msg = NULL;
+ DIGEST_CTX ctx = rpmDigestDup(md5ctx);
+
+ if (ctx == NULL || sigtd->data == NULL) {
+ rasprintf(msg, "%s %s\n", title, rpmSigString(res));
+ goto exit;
+ }
+
+ (void) rpmDigestFinal(ctx, (void **)&md5sum, &md5len, 0);
+
+ md5 = pgpHexStr(md5sum, md5len);
+ if (md5len != sigtd->count || memcmp(md5sum, sigtd->data, md5len)) {
+ char *hex = rpmtdFormat(sigtd, RPMTD_FORMAT_STRING, NULL);
+ rasprintf(msg, "%s %s Expected(%s) != (%s)\n", title,
+ rpmSigString(res), hex, md5);
+ free(hex);
+ } else {
+ res = RPMRC_OK;
+ rasprintf(msg, "%s %s (%s)\n", title, rpmSigString(res), md5);
+ }
+ free(md5);
+
+exit:
+ md5sum = _free(md5sum);
+ return res;
+}
+
+/**
+ * Verify header immutable region SHA1 digest.
+ * @retval msg verbose success/failure text
+ * @param sha1ctx
+ * @return RPMRC_OK on success
+ */
+static rpmRC
+verifySHA1Digest(rpmtd sigtd, DIGEST_CTX sha1ctx, char **msg)
+{
+ rpmRC res = RPMRC_FAIL; /* assume failure */
+ char * SHA1 = NULL;
+ const char *title = _("Header SHA1 digest:");
+ const char *sig = sigtd->data;
+ *msg = NULL;
+ DIGEST_CTX ctx = rpmDigestDup(sha1ctx);
+
+ if (ctx == NULL || sigtd->data == NULL) {
+ rasprintf(msg, "%s %s\n", title, rpmSigString(res));
+ goto exit;
+ }
+
+ (void) rpmDigestFinal(ctx, (void **)&SHA1, NULL, 1);
+
+ if (SHA1 == NULL || strlen(SHA1) != strlen(sig) || !rstreq(SHA1, sig)) {
+ rasprintf(msg, "%s %s Expected(%s) != (%s)\n", title,
+ rpmSigString(res), sig, SHA1 ? SHA1 : "(nil)");
+ } else {
+ res = RPMRC_OK;
+ rasprintf(msg, "%s %s (%s)\n", title, rpmSigString(res), SHA1);
+ }
+
+exit:
+ SHA1 = _free(SHA1);
+ return res;
+}
+
+/**
+ * Verify DSA/RSA signature.
+ * @param keyring pubkey keyring
+ * @param dig OpenPGP container
+ * @param hashctx digest context
+ * @param isHdr header-only signature?
+ * @retval msg verbose success/failure text
+ * @return RPMRC_OK on success
+ */
+static rpmRC
+verifySignature(rpmKeyring keyring, pgpDig dig, DIGEST_CTX hashctx, int isHdr,
+ char **msg)
+{
+ pgpDigParams sigp = dig ? &dig->signature : NULL;
+ rpmRC res = RPMRC_FAIL; /* assume failure */
+ char *sigid = NULL;
+ *msg = NULL;
+
+ if (hashctx == NULL) {
+ goto exit;
+ }
+
+ /* Call verify even if we dont have a key for a basic sanity check */
+ (void) rpmKeyringLookup(keyring, dig);
+ res = pgpVerifySig(dig, hashctx);
+
+exit:
+ sigid = pgpIdentItem(sigp);
+ rasprintf(msg, "%s%s: %s\n", isHdr ? _("Header ") : "", sigid,
+ rpmSigString(res));
+ free(sigid);
+ return res;
+}
+
+rpmRC
+rpmVerifySignature(rpmKeyring keyring, rpmtd sigtd, pgpDig dig, DIGEST_CTX ctx, char ** result)
+{
+ rpmRC res = RPMRC_NOTFOUND;
+ char *msg = NULL;
+
+ if (sigtd->data == NULL || sigtd->count <= 0 || dig == NULL) {
+ rasprintf(&msg, _("Verify signature: BAD PARAMETERS\n"));
+ goto exit;
+ }
+
+ switch (sigtd->tag) {
+ case RPMSIGTAG_MD5:
+ res = verifyMD5Digest(sigtd, ctx, &msg);
+ break;
+ case RPMSIGTAG_SHA1:
+ res = verifySHA1Digest(sigtd, ctx, &msg);
+ break;
+ case RPMSIGTAG_RSA:
+ case RPMSIGTAG_DSA:
+ res = verifySignature(keyring, dig, ctx, 1, &msg);
+ break;
+ case RPMSIGTAG_PGP5: /* XXX legacy */
+ case RPMSIGTAG_PGP:
+ case RPMSIGTAG_GPG:
+ res = verifySignature(keyring, dig, ctx, 0, &msg);
+ break;
+ default:
+ rasprintf(&msg, _("Signature: UNKNOWN (%d)\n"), sigtd->tag);
+ break;
+ }
+
+exit:
+ if (result) {
+ *result = msg;
+ } else {
+ free(msg);
+ }
+ return res;
+}
diff --git a/lib/signature.h b/lib/signature.h
new file mode 100644
index 0000000..726f7f0
--- /dev/null
+++ b/lib/signature.h
@@ -0,0 +1,79 @@
+#ifndef H_SIGNATURE
+#define H_SIGNATURE
+
+/** \ingroup signature
+ * \file lib/signature.h
+ * Generate and verify signatures.
+ */
+
+#include <rpm/header.h>
+
+/** \ingroup signature
+ * Signature types stored in rpm lead.
+ */
+typedef enum sigType_e {
+ RPMSIGTYPE_HEADERSIG= 5 /*!< Header style signature */
+} sigType;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup signature
+ * Return new, empty (signature) header instance.
+ * @return signature header
+ */
+Header rpmNewSignature(void);
+
+/** \ingroup signature
+ * Read (and verify header+payload size) signature header.
+ * If an old-style signature is found, we emulate a new style one.
+ * @param fd file handle
+ * @retval sighp address of (signature) header (or NULL)
+ * @param sig_type type of signature header to read (from lead)
+ * @retval msg failure msg
+ * @return rpmRC return code
+ */
+rpmRC rpmReadSignature(FD_t fd, Header *sighp, sigType sig_type, char ** msg);
+
+/** \ingroup signature
+ * Write signature header.
+ * @param fd file handle
+ * @param h (signature) header
+ * @return 0 on success, 1 on error
+ */
+int rpmWriteSignature(FD_t fd, Header h);
+
+/** \ingroup signature
+ * Generate digest(s) from a header+payload file, save in signature header.
+ * @param sigh signature header
+ * @param file header+payload file name
+ * @param sigTag type of digest(s) to add
+ * @return 0 on success, -1 on failure
+ */
+int rpmGenDigest(Header sigh, const char * file, rpmTagVal sigTag);
+
+/** \ingroup signature
+ * Verify a signature from a package.
+ *
+ * @param keyring keyring handle
+ * @param sigtd signature tag data container
+ * @param dig signature/pubkey parameters
+ * @retval result detailed text result of signature verification
+ * (malloc'd)
+ * @return result of signature verification
+ */
+rpmRC rpmVerifySignature(rpmKeyring keyring, rpmtd sigtd, pgpDig dig, DIGEST_CTX ctx, char ** result);
+
+/** \ingroup signature
+ * Destroy signature header from package.
+ * @param h signature header
+ * @return NULL always
+ */
+Header rpmFreeSignature(Header h);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_SIGNATURE */
diff --git a/lib/tagexts.c b/lib/tagexts.c
new file mode 100644
index 0000000..aca3209
--- /dev/null
+++ b/lib/tagexts.c
@@ -0,0 +1,705 @@
+/** \ingroup header
+ * \file lib/formats.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmlib.h>
+#include <rpm/rpmmacro.h> /* XXX for %_i18ndomains */
+#include <rpm/rpmfi.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+#include "lib/misc.h" /* tag function proto */
+
+#include "debug.h"
+
+struct headerTagFunc_s {
+ rpmTag tag; /*!< Tag of extension. */
+ headerTagTagFunction func; /*!< Pointer to formatter function. */
+};
+
+/** \ingroup rpmfi
+ * Retrieve file names from header.
+ *
+ * The representation of file names in package headers changed in rpm-4.0.
+ * Originally, file names were stored as an array of absolute paths.
+ * In rpm-4.0, file names are stored as separate arrays of dirname's and
+ * basename's, * with a dirname index to associate the correct dirname
+ * with each basname.
+ *
+ * This function is used to retrieve file names independent of how the
+ * file names are represented in the package header.
+ *
+ * @param h header
+ * @param tagN RPMTAG_BASENAMES | PMTAG_ORIGBASENAMES
+ * @retval *fnp array of file names
+ * @retval *fcp number of files
+ */
+static void rpmfiBuildFNames(Header h, rpmTag tagN,
+ const char *** fnp, rpm_count_t * fcp)
+{
+ const char **baseNames, **dirNames, **fileNames;
+ uint32_t *dirIndexes;
+ rpm_count_t count;
+ size_t size;
+ rpmTag dirNameTag = RPMTAG_DIRNAMES;
+ rpmTag dirIndexesTag = RPMTAG_DIRINDEXES;
+ char * t;
+ int i;
+ struct rpmtd_s bnames, dnames, dixs;
+
+ if (tagN == RPMTAG_ORIGBASENAMES) {
+ dirNameTag = RPMTAG_ORIGDIRNAMES;
+ dirIndexesTag = RPMTAG_ORIGDIRINDEXES;
+ }
+
+ if (!headerGet(h, tagN, &bnames, HEADERGET_MINMEM)) {
+ *fnp = NULL;
+ *fcp = 0;
+ return; /* no file list */
+ }
+ (void) headerGet(h, dirNameTag, &dnames, HEADERGET_MINMEM);
+ (void) headerGet(h, dirIndexesTag, &dixs, HEADERGET_MINMEM);
+
+ count = rpmtdCount(&bnames);
+ baseNames = bnames.data;
+ dirNames = dnames.data;
+ dirIndexes = dixs.data;
+
+ /*
+ * fsm, psm and rpmfi assume the data is stored in a single allocation
+ * block, until those assumptions are removed we need to jump through
+ * a few hoops here and precalculate sizes etc
+ */
+ size = sizeof(*fileNames) * count;
+ for (i = 0; i < count; i++)
+ size += strlen(baseNames[i]) + strlen(dirNames[dirIndexes[i]]) + 1;
+
+ fileNames = xmalloc(size);
+ t = ((char *) fileNames) + (sizeof(*fileNames) * count);
+ for (i = 0; i < count; i++) {
+ fileNames[i] = t;
+ t = stpcpy( stpcpy(t, dirNames[dirIndexes[i]]), baseNames[i]);
+ *t++ = '\0';
+ }
+ rpmtdFreeData(&bnames);
+ rpmtdFreeData(&dnames);
+ rpmtdFreeData(&dixs);
+
+ *fnp = fileNames;
+ *fcp = count;
+}
+
+static int filedepTag(Header h, rpmTag tagN, rpmtd td, headerGetFlags hgflags)
+{
+ rpmfi fi = rpmfiNew(NULL, h, RPMTAG_BASENAMES, RPMFI_NOHEADER);
+ rpmds ds = NULL;
+ char **fdeps = NULL;
+ int numfiles;
+ char deptype = 'R';
+ int fileix;
+ int rc = 0;
+
+ numfiles = rpmfiFC(fi);
+ if (numfiles <= 0) {
+ goto exit;
+ }
+
+ if (tagN == RPMTAG_PROVIDENAME)
+ deptype = 'P';
+ else if (tagN == RPMTAG_REQUIRENAME)
+ deptype = 'R';
+
+ ds = rpmdsNew(h, tagN, 0);
+ fdeps = xmalloc(numfiles * sizeof(*fdeps));
+
+ while ((fileix = rpmfiNext(fi)) >= 0) {
+ ARGV_t deps = NULL;
+ const uint32_t * ddict = NULL;
+ int ndx = rpmfiFDepends(fi, &ddict);
+ if (ddict != NULL) {
+ while (ndx-- > 0) {
+ const char * DNEVR;
+ unsigned dix = *ddict++;
+ char mydt = ((dix >> 24) & 0xff);
+ if (mydt != deptype)
+ continue;
+ dix &= 0x00ffffff;
+ (void) rpmdsSetIx(ds, dix-1);
+ if (rpmdsNext(ds) < 0)
+ continue;
+ DNEVR = rpmdsDNEVR(ds);
+ if (DNEVR != NULL) {
+ argvAdd(&deps, DNEVR + 2);
+ }
+ }
+ }
+ fdeps[fileix] = deps ? argvJoin(deps, " ") : xstrdup("");
+ argvFree(deps);
+ }
+ td->data = fdeps;
+ td->count = numfiles;
+ td->flags = RPMTD_ALLOCED | RPMTD_PTR_ALLOCED;
+ td->type = RPM_STRING_ARRAY_TYPE;
+ rc = 1;
+
+exit:
+ fi = rpmfiFree(fi);
+ ds = rpmdsFree(ds);
+ return rc;
+}
+
+/**
+ * Retrieve trigger info.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int triggercondsTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ uint32_t * indices;
+ int i, j;
+ char ** conds;
+ struct rpmtd_s nametd, indextd, flagtd, versiontd, scripttd;
+ int hgeflags = HEADERGET_MINMEM;
+
+ if (!headerGet(h, RPMTAG_TRIGGERNAME, &nametd, hgeflags)) {
+ return 0;
+ }
+
+ headerGet(h, RPMTAG_TRIGGERINDEX, &indextd, hgeflags);
+ headerGet(h, RPMTAG_TRIGGERFLAGS, &flagtd, hgeflags);
+ headerGet(h, RPMTAG_TRIGGERVERSION, &versiontd, hgeflags);
+ headerGet(h, RPMTAG_TRIGGERSCRIPTS, &scripttd, hgeflags);
+
+ td->type = RPM_STRING_ARRAY_TYPE;
+ td->flags = RPMTD_ALLOCED | RPMTD_PTR_ALLOCED;
+ td->data = conds = xmalloc(sizeof(*conds) * rpmtdCount(&scripttd));
+ td->count = rpmtdCount(&scripttd);
+
+ indices = indextd.data;
+
+ while ((i = rpmtdNext(&scripttd)) >= 0) {
+ rpm_flag_t *flag;
+ char *flagStr, *item;
+ ARGV_t items = NULL;
+
+ rpmtdInit(&nametd); rpmtdInit(&flagtd); rpmtdInit(&versiontd);
+ while ((j = rpmtdNext(&nametd)) >= 0) {
+ /* flag and version arrays match name array size always */
+ rpmtdNext(&flagtd); rpmtdNext(&versiontd);
+
+ if (indices[j] != i)
+ continue;
+
+ flag = rpmtdGetUint32(&flagtd);
+ if (flag && *flag & RPMSENSE_SENSEMASK) {
+ flagStr = rpmtdFormat(&flagtd, RPMTD_FORMAT_DEPFLAGS, NULL);
+ rasprintf(&item, "%s %s %s", rpmtdGetString(&nametd),
+ flagStr,
+ rpmtdGetString(&versiontd));
+ free(flagStr);
+ } else {
+ item = xstrdup(rpmtdGetString(&nametd));
+ }
+
+ argvAdd(&items, item);
+ free(item);
+ }
+
+ conds[i] = argvJoin(items, ", ");
+ argvFree(items);
+ }
+
+ rpmtdFreeData(&nametd);
+ rpmtdFreeData(&versiontd);
+ rpmtdFreeData(&flagtd);
+ rpmtdFreeData(&indextd);
+ rpmtdFreeData(&scripttd);
+ return 1;
+}
+
+/**
+ * Retrieve trigger type info.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int triggertypeTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ int i;
+ char ** conds;
+ struct rpmtd_s indices, flags, scripts;
+
+ if (!headerGet(h, RPMTAG_TRIGGERINDEX, &indices, HEADERGET_MINMEM)) {
+ return 0;
+ }
+
+ headerGet(h, RPMTAG_TRIGGERFLAGS, &flags, HEADERGET_MINMEM);
+ headerGet(h, RPMTAG_TRIGGERSCRIPTS, &scripts, HEADERGET_MINMEM);
+
+ td->flags = RPMTD_ALLOCED | RPMTD_PTR_ALLOCED;
+ td->count = rpmtdCount(&scripts);
+ td->data = conds = xmalloc(sizeof(*conds) * td->count);
+ td->type = RPM_STRING_ARRAY_TYPE;
+
+ while ((i = rpmtdNext(&scripts)) >= 0) {
+ rpm_flag_t *flag;
+ rpmtdInit(&indices); rpmtdInit(&flags);
+
+ while (rpmtdNext(&indices) >= 0 && rpmtdNext(&flags) >= 0) {
+ if (*rpmtdGetUint32(&indices) != i)
+ continue;
+
+ flag = rpmtdGetUint32(&flags);
+ if (*flag & RPMSENSE_TRIGGERPREIN)
+ conds[i] = xstrdup("prein");
+ else if (*flag & RPMSENSE_TRIGGERIN)
+ conds[i] = xstrdup("in");
+ else if (*flag & RPMSENSE_TRIGGERUN)
+ conds[i] = xstrdup("un");
+ else if (*flag & RPMSENSE_TRIGGERPOSTUN)
+ conds[i] = xstrdup("postun");
+ else
+ conds[i] = xstrdup("");
+ break;
+ }
+ }
+ rpmtdFreeData(&indices);
+ rpmtdFreeData(&flags);
+ rpmtdFreeData(&scripts);
+
+ return 1;
+}
+
+/**
+ * Retrieve file paths.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int filenamesTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ rpmfiBuildFNames(h, RPMTAG_BASENAMES,
+ (const char ***) &(td->data), &(td->count));
+ if (td->data) {
+ td->type = RPM_STRING_ARRAY_TYPE;
+ td->flags = RPMTD_ALLOCED;
+ }
+ return (td->data != NULL);
+}
+
+/**
+ * Retrieve original file paths (wrt relocation).
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int origfilenamesTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ rpmfiBuildFNames(h, RPMTAG_ORIGBASENAMES,
+ (const char ***) &(td->data), &(td->count));
+ if (td->data) {
+ td->type = RPM_STRING_ARRAY_TYPE;
+ td->flags = RPMTD_ALLOCED;
+ }
+ return (td->data != NULL);
+}
+/**
+ * Retrieve file classes.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int fileclassTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ rpmfi fi = rpmfiNew(NULL, h, RPMTAG_BASENAMES, RPMFI_NOHEADER);
+ char **fclasses;
+ int ix, numfiles;
+ int rc = 0;
+
+ numfiles = rpmfiFC(fi);
+ if (numfiles <= 0) {
+ goto exit;
+ }
+
+ fclasses = xmalloc(numfiles * sizeof(*fclasses));
+ rpmfiInit(fi, 0);
+ while ((ix = rpmfiNext(fi)) >= 0) {
+ const char *fclass = rpmfiFClass(fi);
+ fclasses[ix] = xstrdup(fclass ? fclass : "");
+ }
+
+ td->data = fclasses;
+ td->count = numfiles;
+ td->flags = RPMTD_ALLOCED | RPMTD_PTR_ALLOCED;
+ td->type = RPM_STRING_ARRAY_TYPE;
+ rc = 1;
+
+exit:
+ fi = rpmfiFree(fi);
+ return rc;
+}
+
+/**
+ * Retrieve file provides.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int fileprovideTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return filedepTag(h, RPMTAG_PROVIDENAME, td, hgflags);
+}
+
+/**
+ * Retrieve file requires.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int filerequireTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return filedepTag(h, RPMTAG_REQUIRENAME, td, hgflags);
+}
+
+/* I18N look aside diversions */
+
+#if defined(ENABLE_NLS)
+extern int _nl_msg_cat_cntr; /* XXX GNU gettext voodoo */
+#endif
+static const char * const language = "LANGUAGE";
+
+static const char * const _macro_i18ndomains = "%{?_i18ndomains}";
+
+/**
+ * Retrieve i18n text.
+ * @param h header
+ * @param tag tag
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int i18nTag(Header h, rpmTag tag, rpmtd td, headerGetFlags hgflags)
+{
+ int rc;
+#if defined(ENABLE_NLS)
+ char * dstring = rpmExpand(_macro_i18ndomains, NULL);
+
+ td->type = RPM_STRING_TYPE;
+ td->data = NULL;
+ td->count = 0;
+
+ if (dstring && *dstring) {
+ char *domain, *de;
+ const char * langval;
+ char * msgkey;
+ const char * msgid;
+
+ rasprintf(&msgkey, "%s(%s)", headerGetString(h, RPMTAG_NAME),
+ rpmTagGetName(tag));
+
+ /* change to en_US for msgkey -> msgid resolution */
+ langval = getenv(language);
+ (void) setenv(language, "en_US", 1);
+ ++_nl_msg_cat_cntr;
+
+ msgid = NULL;
+ for (domain = dstring; domain != NULL; domain = de) {
+ de = strchr(domain, ':');
+ if (de) *de++ = '\0';
+ msgid = dgettext(domain, msgkey);
+ if (msgid != msgkey) break;
+ }
+
+ /* restore previous environment for msgid -> msgstr resolution */
+ if (langval)
+ (void) setenv(language, langval, 1);
+ else
+ unsetenv(language);
+ ++_nl_msg_cat_cntr;
+
+ if (domain && msgid) {
+ td->data = dgettext(domain, msgid);
+ td->data = xstrdup(td->data); /* XXX xstrdup has side effects. */
+ td->count = 1;
+ td->flags = RPMTD_ALLOCED;
+ }
+ dstring = _free(dstring);
+ free(msgkey);
+ if (td->data)
+ return 1;
+ }
+
+ dstring = _free(dstring);
+#endif
+
+ rc = headerGet(h, tag, td, HEADERGET_ALLOC);
+ return rc;
+}
+
+/**
+ * Retrieve summary text.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int summaryTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return i18nTag(h, RPMTAG_SUMMARY, td, hgflags);
+}
+
+/**
+ * Retrieve description text.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int descriptionTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return i18nTag(h, RPMTAG_DESCRIPTION, td, hgflags);
+}
+
+/**
+ * Retrieve group text.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int groupTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return i18nTag(h, RPMTAG_GROUP, td, hgflags);
+}
+
+/*
+ * Helper to convert 32bit tag to 64bit version.
+ * If header has new 64bit tag then just return the data,
+ * otherwise convert 32bit old tag data to 64bit values.
+ * For consistency, always return malloced data.
+ */
+static int get64(Header h, rpmtd td, rpmTag newtag, rpmTag oldtag)
+{
+ int rc;
+
+ if (headerIsEntry(h, newtag)) {
+ rc = headerGet(h, newtag, td, HEADERGET_ALLOC);
+ } else {
+ struct rpmtd_s olddata;
+ uint32_t *d32 = NULL;
+ uint64_t *d64 = NULL;
+
+ headerGet(h, oldtag, &olddata, HEADERGET_MINMEM);
+ if (rpmtdType(&olddata) == RPM_INT32_TYPE) {
+ td->type = RPM_INT64_TYPE;
+ td->count = olddata.count;
+ td->flags = RPMTD_ALLOCED;
+ td->data = xmalloc(sizeof(*d64) * td->count);
+ d64 = td->data;
+ while ((d32 = rpmtdNextUint32(&olddata))) {
+ *d64++ = *d32;
+ }
+ }
+ rpmtdFreeData(&olddata);
+ rc = d64 ? 1 : 0;
+ }
+
+ return rc;
+}
+
+/**
+ * Retrieve file sizes as 64bit regardless of how they're stored.
+ * @param h header
+ * @retval td tag data container
+ * @return 1 on success
+ */
+static int longfilesizesTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return get64(h, td, RPMTAG_LONGFILESIZES, RPMTAG_FILESIZES);
+}
+
+static int longarchivesizeTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return get64(h, td, RPMTAG_LONGARCHIVESIZE, RPMTAG_ARCHIVESIZE);
+}
+
+static int longsizeTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return get64(h, td, RPMTAG_LONGSIZE, RPMTAG_SIZE);
+}
+
+static int longsigsizeTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return get64(h, td, RPMTAG_LONGSIGSIZE, RPMTAG_SIGSIZE);
+}
+
+static int numberTag(rpmtd td, uint32_t val)
+{
+ uint32_t *tval = xmalloc(sizeof(*tval));
+
+ tval[0] = val;
+ td->type = RPM_INT32_TYPE;
+ td->count = 1;
+ td->data = tval;
+ td->flags = RPMTD_ALLOCED;
+ return 1; /* this cannot fail */
+}
+
+static int dbinstanceTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return numberTag(td, headerGetInstance(h));
+}
+
+static int headercolorTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ rpm_color_t *fcolor, hcolor = 0;
+ struct rpmtd_s fcolors;
+
+ headerGet(h, RPMTAG_FILECOLORS, &fcolors, HEADERGET_MINMEM);
+ while ((fcolor = rpmtdNextUint32(&fcolors)) != NULL) {
+ hcolor |= *fcolor;
+ }
+ rpmtdFreeData(&fcolors);
+ hcolor &= 0x0f;
+
+ return numberTag(td, hcolor);
+}
+
+enum nevraFlags_e {
+ NEVRA_NAME = (1 << 0),
+ NEVRA_EPOCH = (1 << 1),
+ NEVRA_VERSION = (1 << 2),
+ NEVRA_RELEASE = (1 << 3),
+ NEVRA_ARCH = (1 << 4)
+};
+typedef rpmFlags nevraFlags;
+
+static int getNEVRA(Header h, rpmtd td, nevraFlags flags)
+{
+ const char *val = NULL;
+ char *res = NULL;
+
+ if ((flags & NEVRA_NAME)) {
+ val = headerGetString(h, RPMTAG_NAME);
+ if (val) rstrscat(&res, val, "-", NULL);
+ }
+ if ((flags & NEVRA_EPOCH)) {
+ char *e = headerGetAsString(h, RPMTAG_EPOCH);
+ if (e) rstrscat(&res, e, ":", NULL);
+ free(e);
+ }
+ if ((flags & NEVRA_VERSION)) {
+ val = headerGetString(h, RPMTAG_VERSION);
+ if (val) rstrscat(&res, val, "-", NULL);
+ }
+ if ((flags & NEVRA_RELEASE)) {
+ val = headerGetString(h, RPMTAG_RELEASE);
+ if (val) rstrscat(&res, val, NULL);
+ }
+ if ((flags & NEVRA_ARCH)) {
+ val = headerGetString(h, RPMTAG_ARCH);
+ if (headerIsSource(h) && val == NULL) val = "src";
+ if (val) rstrscat(&res, ".", val, NULL);
+ }
+
+ td->type = RPM_STRING_TYPE;
+ td->data = res;
+ td->count = 1;
+ td->flags = RPMTD_ALLOCED;
+
+ return 1;
+}
+
+static int evrTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return getNEVRA(h, td, NEVRA_EPOCH|NEVRA_VERSION|NEVRA_RELEASE);
+}
+
+static int nvrTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return getNEVRA(h, td, NEVRA_NAME|NEVRA_VERSION|NEVRA_RELEASE);
+}
+
+static int nvraTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return getNEVRA(h, td, NEVRA_NAME|NEVRA_VERSION|NEVRA_RELEASE|NEVRA_ARCH);
+}
+
+static int nevrTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return getNEVRA(h, td, NEVRA_NAME|NEVRA_EPOCH|NEVRA_VERSION|NEVRA_RELEASE);
+}
+
+static int nevraTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ return getNEVRA(h, td, NEVRA_NAME|NEVRA_EPOCH|NEVRA_VERSION|NEVRA_RELEASE|NEVRA_ARCH);
+}
+
+static int verboseTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ if (rpmIsVerbose()) {
+ td->type = RPM_INT32_TYPE;
+ td->count = 1;
+ td->data = &(td->count);
+ td->flags = RPMTD_NONE;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static int epochnumTag(Header h, rpmtd td, headerGetFlags hgflags)
+{
+ /* For consistency, always return malloced data */
+ if (!headerGet(h, RPMTAG_EPOCH, td, HEADERGET_ALLOC)) {
+ uint32_t *e = malloc(sizeof(*e));
+ *e = 0;
+ td->data = e;
+ td->type = RPM_INT32_TYPE;
+ td->count = 1;
+ td->flags = RPMTD_ALLOCED;
+ }
+ td->tag = RPMTAG_EPOCHNUM;
+ return 1;
+}
+
+static const struct headerTagFunc_s rpmHeaderTagExtensions[] = {
+ { RPMTAG_GROUP, groupTag },
+ { RPMTAG_DESCRIPTION, descriptionTag },
+ { RPMTAG_SUMMARY, summaryTag },
+ { RPMTAG_FILECLASS, fileclassTag },
+ { RPMTAG_FILENAMES, filenamesTag },
+ { RPMTAG_ORIGFILENAMES, origfilenamesTag },
+ { RPMTAG_FILEPROVIDE, fileprovideTag },
+ { RPMTAG_FILEREQUIRE, filerequireTag },
+ { RPMTAG_TRIGGERCONDS, triggercondsTag },
+ { RPMTAG_TRIGGERTYPE, triggertypeTag },
+ { RPMTAG_LONGFILESIZES, longfilesizesTag },
+ { RPMTAG_LONGARCHIVESIZE, longarchivesizeTag },
+ { RPMTAG_LONGSIZE, longsizeTag },
+ { RPMTAG_LONGSIGSIZE, longsigsizeTag },
+ { RPMTAG_DBINSTANCE, dbinstanceTag },
+ { RPMTAG_EVR, evrTag },
+ { RPMTAG_NVR, nvrTag },
+ { RPMTAG_NEVR, nevrTag },
+ { RPMTAG_NVRA, nvraTag },
+ { RPMTAG_NEVRA, nevraTag },
+ { RPMTAG_HEADERCOLOR, headercolorTag },
+ { RPMTAG_VERBOSE, verboseTag },
+ { RPMTAG_EPOCHNUM, epochnumTag },
+ { 0, NULL }
+};
+
+headerTagTagFunction rpmHeaderTagFunc(rpmTagVal tag)
+{
+ const struct headerTagFunc_s * ext;
+ headerTagTagFunction func = NULL;
+
+ for (ext = rpmHeaderTagExtensions; ext->func != NULL; ext++) {
+ if (ext->tag == tag) {
+ func = ext->func;
+ break;
+ }
+ }
+ return func;
+}
+
diff --git a/lib/tagname.c b/lib/tagname.c
new file mode 100644
index 0000000..adc7178
--- /dev/null
+++ b/lib/tagname.c
@@ -0,0 +1,318 @@
+/**
+ * \file lib/tagname.c
+ */
+
+#include "system.h"
+
+#include <rpm/header.h>
+#include <rpm/rpmstring.h>
+#include "debug.h"
+
+/** \ingroup header
+ * Associate tag names with numeric values.
+ */
+typedef const struct headerTagTableEntry_s * headerTagTableEntry;
+struct headerTagTableEntry_s {
+ const char * name; /*!< Tag name. */
+ const char * shortname; /*!< "Human readable" short name. */
+ rpmTagVal val; /*!< Tag numeric value. */
+ rpmTagType type; /*!< Tag type. */
+ rpmTagReturnType retype; /*!< Tag return type. */
+ int extension; /*!< Extension or "real" tag */
+};
+
+#include "lib/tagtbl.C"
+
+static const int rpmTagTableSize = sizeof(rpmTagTable) / sizeof(rpmTagTable[0]) - 1;
+
+/**
+ */
+typedef struct headerTagIndices_s * headerTagIndices;
+
+struct headerTagIndices_s {
+ int (*loadIndex) (headerTagTableEntry ** ipp, int * np,
+ int (*cmp) (const void * avp, const void * bvp));
+ /*!< load sorted tag index. */
+ headerTagTableEntry * byName; /*!< header tags sorted by name. */
+ int byNameSize; /*!< no. of entries. */
+ int (*byNameCmp) (const void * avp, const void * bvp); /*!< compare entries by name. */
+ rpmTagVal (*tagValue) (const char * name); /* return value from name. */
+ headerTagTableEntry * byValue; /*!< header tags sorted by value. */
+ int byValueSize; /*!< no. of entries. */
+ int (*byValueCmp) (const void * avp, const void * bvp); /*!< compare entries by value. */
+ const char * (*tagName) (rpmTagVal value); /* Return name from value. */
+ rpmTagType (*tagType) (rpmTagVal value); /* Return type from value. */
+};
+
+/**
+ * Compare tag table entries by name.
+ * @param *avp tag table entry a
+ * @param *bvp tag table entry b
+ * @return comparison
+ */
+static int tagCmpName(const void * avp, const void * bvp)
+{
+ headerTagTableEntry a = *(const headerTagTableEntry *) avp;
+ headerTagTableEntry b = *(const headerTagTableEntry *) bvp;
+ return strcmp(a->name, b->name);
+}
+
+/**
+ * Compare tag table entries by value.
+ * @param *avp tag table entry a
+ * @param *bvp tag table entry b
+ * @return comparison
+ */
+static int tagCmpValue(const void * avp, const void * bvp)
+{
+ headerTagTableEntry a = *(const headerTagTableEntry *) avp;
+ headerTagTableEntry b = *(const headerTagTableEntry *) bvp;
+ int ret = (a->val - b->val);
+ /* Make sure that sort is stable, longest name first. */
+ if (ret == 0)
+ ret = (strlen(b->name) - strlen(a->name));
+ return ret;
+}
+
+/**
+ * Load/sort a tag index.
+ * @retval *ipp tag index
+ * @retval *np no. of tags
+ * @param cmp sort compare routine
+ * @return 0 always
+ */
+static int tagLoadIndex(headerTagTableEntry ** ipp, int * np,
+ int (*cmp) (const void * avp, const void * bvp))
+{
+ headerTagTableEntry tte, *ip;
+ int n = 0;
+
+ ip = xcalloc(rpmTagTableSize, sizeof(*ip));
+ n = 0;
+ for (tte = (headerTagTableEntry)rpmTagTable; tte->name != NULL; tte++) {
+ ip[n] = tte;
+ n++;
+ }
+assert(n == rpmTagTableSize);
+
+ if (n > 1)
+ qsort(ip, n, sizeof(*ip), cmp);
+ *ipp = ip;
+ *np = n;
+ return 0;
+}
+
+
+/* forward refs */
+static const char * _tagName(rpmTagVal tag);
+static rpmTagType _tagType(rpmTagVal tag);
+static rpmTagVal _tagValue(const char * tagstr);
+
+static struct headerTagIndices_s _rpmTags = {
+ tagLoadIndex,
+ NULL, 0, tagCmpName, _tagValue,
+ NULL, 0, tagCmpValue, _tagName, _tagType,
+};
+
+static headerTagIndices const rpmTags = &_rpmTags;
+
+static const char * _tagName(rpmTagVal tag)
+{
+ const char *name = "(unknown)";
+ const struct headerTagTableEntry_s *t;
+ int comparison, i, l, u;
+ int xx;
+
+ if (_rpmTags.byValue == NULL)
+ xx = tagLoadIndex(&_rpmTags.byValue, &_rpmTags.byValueSize, tagCmpValue);
+
+ switch (tag) {
+ case RPMDBI_PACKAGES:
+ name = "Packages";
+ break;
+ /* XXX make sure rpmdb indices are identically named. */
+ case RPMTAG_CONFLICTS:
+ name = "Conflictname";
+ break;
+ case RPMTAG_HDRID:
+ name = "Sha1header";
+ break;
+
+ default:
+ if (_rpmTags.byValue == NULL)
+ break;
+ l = 0;
+ u = _rpmTags.byValueSize;
+ while (l < u) {
+ i = (l + u) / 2;
+ t = _rpmTags.byValue[i];
+
+ comparison = (tag - t->val);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else {
+ /* Make sure that the bsearch retrieve is stable. */
+ while (i > 0 && tag == _rpmTags.byValue[i-1]->val) {
+ i--;
+ }
+ t = _rpmTags.byValue[i];
+ if (t->shortname != NULL)
+ name = t->shortname;
+ break;
+ }
+ }
+ break;
+ }
+ return name;
+}
+
+static rpmTagType _tagType(rpmTagVal tag)
+{
+ const struct headerTagTableEntry_s *t;
+ int comparison, i, l, u;
+ int xx;
+
+ if (_rpmTags.byValue == NULL)
+ xx = tagLoadIndex(&_rpmTags.byValue, &_rpmTags.byValueSize, tagCmpValue);
+ if (_rpmTags.byValue) {
+ l = 0;
+ u = _rpmTags.byValueSize;
+ while (l < u) {
+ i = (l + u) / 2;
+ t = _rpmTags.byValue[i];
+
+ comparison = (tag - t->val);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else {
+ /* Make sure that the bsearch retrieve is stable. */
+ while (i > 0 && t->val == _rpmTags.byValue[i-1]->val) {
+ i--;
+ }
+ t = _rpmTags.byValue[i];
+ /* XXX this is dumb */
+ return (rpmTagType)(t->type | t->retype);
+ }
+ }
+ }
+ return RPM_NULL_TYPE;
+}
+
+static rpmTagVal _tagValue(const char * tagstr)
+{
+ const struct headerTagTableEntry_s *t;
+ int comparison, i, l, u;
+ int xx;
+
+ if (!rstrcasecmp(tagstr, "Packages"))
+ return RPMDBI_PACKAGES;
+
+ if (_rpmTags.byName == NULL)
+ xx = tagLoadIndex(&_rpmTags.byName, &_rpmTags.byNameSize, tagCmpName);
+ if (_rpmTags.byName == NULL)
+ return RPMTAG_NOT_FOUND;
+
+ l = 0;
+ u = _rpmTags.byNameSize;
+ while (l < u) {
+ i = (l + u) / 2;
+ t = _rpmTags.byName[i];
+
+ comparison = rstrcasecmp(tagstr, t->shortname);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else
+ return t->val;
+ }
+ return RPMTAG_NOT_FOUND;
+}
+
+const char * rpmTagGetName(rpmTagVal tag)
+{
+ return ((*rpmTags->tagName)(tag));
+}
+
+rpmTagType rpmTagGetType(rpmTagVal tag)
+{
+ return ((*rpmTags->tagType)(tag));
+}
+
+rpmTagType rpmTagGetTagType(rpmTagVal tag)
+{
+ return (rpmTagType)((*rpmTags->tagType)(tag) & RPM_MASK_TYPE);
+}
+
+rpmTagReturnType rpmTagGetReturnType(rpmTagVal tag)
+{
+ return ((*rpmTags->tagType)(tag) & RPM_MASK_RETURN_TYPE);
+}
+
+rpmTagClass rpmTagTypeGetClass(rpmTagType type)
+{
+ rpmTagClass tclass;
+ switch (type & RPM_MASK_TYPE) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ case RPM_INT16_TYPE:
+ case RPM_INT32_TYPE:
+ case RPM_INT64_TYPE:
+ tclass = RPM_NUMERIC_CLASS;
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ tclass = RPM_STRING_CLASS;
+ break;
+ case RPM_BIN_TYPE:
+ tclass = RPM_BINARY_CLASS;
+ break;
+ case RPM_NULL_TYPE:
+ default:
+ tclass = RPM_NULL_CLASS;
+ break;
+ }
+ return tclass;
+}
+
+rpmTagClass rpmTagGetClass(rpmTagVal tag)
+{
+ return rpmTagTypeGetClass(rpmTagGetTagType(tag));
+}
+
+rpmTagVal rpmTagGetValue(const char * tagstr)
+{
+ return ((*rpmTags->tagValue)(tagstr));
+}
+
+int rpmTagGetNames(rpmtd tagnames, int fullname)
+{
+ const char **names;
+ const char *name;
+
+ if (_rpmTags.byName == NULL)
+ tagLoadIndex(&_rpmTags.byName, &_rpmTags.byNameSize, tagCmpName);
+ if (tagnames == NULL ||_rpmTags.byName == NULL)
+ return 0;
+
+ rpmtdReset(tagnames);
+ tagnames->count = _rpmTags.byNameSize;
+ tagnames->data = names = xmalloc(tagnames->count * sizeof(*names));
+ tagnames->type = RPM_STRING_ARRAY_TYPE;
+ tagnames->flags = RPMTD_ALLOCED | RPMTD_IMMUTABLE;
+
+ for (int i = 0; i < tagnames->count; i++) {
+ name = fullname ? _rpmTags.byName[i]->name :
+ _rpmTags.byName[i]->shortname;
+ names[i] = name;
+ }
+ return tagnames->count;
+}
diff --git a/lib/tagtbl.C b/lib/tagtbl.C
new file mode 100644
index 0000000..4daec25
--- /dev/null
+++ b/lib/tagtbl.C
@@ -0,0 +1,186 @@
+static const struct headerTagTableEntry_s rpmTagTable[] = {
+ { "RPMTAG_ARCH", "Arch", RPMTAG_ARCH, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_ARCHIVESIZE", "Archivesize", RPMTAG_ARCHIVESIZE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_BASENAMES", "Basenames", RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_BUGURL", "Bugurl", RPMTAG_BUGURL, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_BUILDARCHS", "Buildarchs", RPMTAG_BUILDARCHS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_BUILDHOST", "Buildhost", RPMTAG_BUILDHOST, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_BUILDTIME", "Buildtime", RPMTAG_BUILDTIME, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_C", "C", RPMTAG_CONFLICTNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CHANGELOGNAME", "Changelogname", RPMTAG_CHANGELOGNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CHANGELOGTEXT", "Changelogtext", RPMTAG_CHANGELOGTEXT, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CHANGELOGTIME", "Changelogtime", RPMTAG_CHANGELOGTIME, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CLASSDICT", "Classdict", RPMTAG_CLASSDICT, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_COLLECTIONS", "Collections", RPMTAG_COLLECTIONS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CONFLICTFLAGS", "Conflictflags", RPMTAG_CONFLICTFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CONFLICTNAME", "Conflictname", RPMTAG_CONFLICTNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CONFLICTS", "Conflicts", RPMTAG_CONFLICTNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_CONFLICTVERSION", "Conflictversion", RPMTAG_CONFLICTVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_COOKIE", "Cookie", RPMTAG_COOKIE, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_DBINSTANCE", "Dbinstance", RPMTAG_DBINSTANCE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_DEPENDSDICT", "Dependsdict", RPMTAG_DEPENDSDICT, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_DESCRIPTION", "Description", RPMTAG_DESCRIPTION, RPM_I18NSTRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_DIRINDEXES", "Dirindexes", RPMTAG_DIRINDEXES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_DIRNAMES", "Dirnames", RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_DISTRIBUTION", "Distribution", RPMTAG_DISTRIBUTION, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_DISTTAG", "Disttag", RPMTAG_DISTTAG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_DISTURL", "Disturl", RPMTAG_DISTURL, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_DSAHEADER", "Dsaheader", RPMTAG_DSAHEADER, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_E", "E", RPMTAG_EPOCH, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_EPOCH", "Epoch", RPMTAG_EPOCH, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_EPOCHNUM", "Epochnum", RPMTAG_EPOCHNUM, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_EVR", "Evr", RPMTAG_EVR, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_EXCLUDEARCH", "Excludearch", RPMTAG_EXCLUDEARCH, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_EXCLUDEOS", "Excludeos", RPMTAG_EXCLUDEOS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_EXCLUSIVEARCH", "Exclusivearch", RPMTAG_EXCLUSIVEARCH, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_EXCLUSIVEOS", "Exclusiveos", RPMTAG_EXCLUSIVEOS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILECAPS", "Filecaps", RPMTAG_FILECAPS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILECLASS", "Fileclass", RPMTAG_FILECLASS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILECOLORS", "Filecolors", RPMTAG_FILECOLORS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILECONTEXTS", "Filecontexts", RPMTAG_FILECONTEXTS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEDEPENDSN", "Filedependsn", RPMTAG_FILEDEPENDSN, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEDEPENDSX", "Filedependsx", RPMTAG_FILEDEPENDSX, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEDEVICES", "Filedevices", RPMTAG_FILEDEVICES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEDIGESTALGO", "Filedigestalgo", RPMTAG_FILEDIGESTALGO, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEDIGESTS", "Filedigests", RPMTAG_FILEDIGESTS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEFLAGS", "Fileflags", RPMTAG_FILEFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEGROUPNAME", "Filegroupname", RPMTAG_FILEGROUPNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEINODES", "Fileinodes", RPMTAG_FILEINODES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILELANGS", "Filelangs", RPMTAG_FILELANGS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILELINKTOS", "Filelinktos", RPMTAG_FILELINKTOS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEMD5S", "Filemd5s", RPMTAG_FILEDIGESTS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEMODES", "Filemodes", RPMTAG_FILEMODES, RPM_INT16_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEMTIMES", "Filemtimes", RPMTAG_FILEMTIMES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILENAMES", "Filenames", RPMTAG_FILENAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_FILEPROVIDE", "Fileprovide", RPMTAG_FILEPROVIDE, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_FILERDEVS", "Filerdevs", RPMTAG_FILERDEVS, RPM_INT16_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEREQUIRE", "Filerequire", RPMTAG_FILEREQUIRE, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_FILESIZES", "Filesizes", RPMTAG_FILESIZES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILESTATES", "Filestates", RPMTAG_FILESTATES, RPM_CHAR_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEUSERNAME", "Fileusername", RPMTAG_FILEUSERNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FILEVERIFYFLAGS", "Fileverifyflags", RPMTAG_FILEVERIFYFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_FSCONTEXTS", "Fscontexts", RPMTAG_FSCONTEXTS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_GIF", "Gif", RPMTAG_GIF, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_GROUP", "Group", RPMTAG_GROUP, RPM_I18NSTRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_HDRID", "Hdrid", RPMTAG_SHA1HEADER, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_HEADERCOLOR", "Headercolor", RPMTAG_HEADERCOLOR, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_HEADERI18NTABLE", "Headeri18ntable", RPMTAG_HEADERI18NTABLE, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_HEADERIMAGE", "Headerimage", RPMTAG_HEADERIMAGE, RPM_NULL_TYPE, RPM_ANY_RETURN_TYPE, 0 },
+ { "RPMTAG_HEADERIMMUTABLE", "Headerimmutable", RPMTAG_HEADERIMMUTABLE, RPM_NULL_TYPE, RPM_ANY_RETURN_TYPE, 0 },
+ { "RPMTAG_HEADERREGIONS", "Headerregions", RPMTAG_HEADERREGIONS, RPM_NULL_TYPE, RPM_ANY_RETURN_TYPE, 0 },
+ { "RPMTAG_HEADERSIGNATURES", "Headersignatures", RPMTAG_HEADERSIGNATURES, RPM_NULL_TYPE, RPM_ANY_RETURN_TYPE, 0 },
+ { "RPMTAG_ICON", "Icon", RPMTAG_ICON, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_INSTALLCOLOR", "Installcolor", RPMTAG_INSTALLCOLOR, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_INSTALLTID", "Installtid", RPMTAG_INSTALLTID, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_INSTALLTIME", "Installtime", RPMTAG_INSTALLTIME, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_INSTPREFIXES", "Instprefixes", RPMTAG_INSTPREFIXES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_LICENSE", "License", RPMTAG_LICENSE, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_LONGARCHIVESIZE", "Longarchivesize", RPMTAG_LONGARCHIVESIZE, RPM_INT64_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_LONGFILESIZES", "Longfilesizes", RPMTAG_LONGFILESIZES, RPM_INT64_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_LONGSIGSIZE", "Longsigsize", RPMTAG_LONGSIGSIZE, RPM_INT64_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_LONGSIZE", "Longsize", RPMTAG_LONGSIZE, RPM_INT64_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_NAME", "Name", RPMTAG_NAME, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_NEVRA", "Nevra", RPMTAG_NEVRA, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_NEVR", "Nevr", RPMTAG_NEVR, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_N", "N", RPMTAG_NAME, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_NOPATCH", "Nopatch", RPMTAG_NOPATCH, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_NOSOURCE", "Nosource", RPMTAG_NOSOURCE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_NVRA", "Nvra", RPMTAG_NVRA, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_NVR", "Nvr", RPMTAG_NVR, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_OBSOLETEFLAGS", "Obsoleteflags", RPMTAG_OBSOLETEFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_OBSOLETENAME", "Obsoletename", RPMTAG_OBSOLETENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_OBSOLETES", "Obsoletes", RPMTAG_OBSOLETENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_OBSOLETEVERSION", "Obsoleteversion", RPMTAG_OBSOLETEVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_OLDFILENAMES", "Oldfilenames", RPMTAG_OLDFILENAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_O", "O", RPMTAG_OBSOLETENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_OPTFLAGS", "Optflags", RPMTAG_OPTFLAGS, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_ORDERFLAGS", "Orderflags", RPMTAG_ORDERFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_ORDERNAME", "Ordername", RPMTAG_ORDERNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_ORDERVERSION", "Orderversion", RPMTAG_ORDERVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_ORIGBASENAMES", "Origbasenames", RPMTAG_ORIGBASENAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_ORIGDIRINDEXES", "Origdirindexes", RPMTAG_ORIGDIRINDEXES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_ORIGDIRNAMES", "Origdirnames", RPMTAG_ORIGDIRNAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_ORIGFILENAMES", "Origfilenames", RPMTAG_ORIGFILENAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_OS", "Os", RPMTAG_OS, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PACKAGER", "Packager", RPMTAG_PACKAGER, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PATCHESFLAGS", "Patchesflags", RPMTAG_PATCHESFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PATCHESNAME", "Patchesname", RPMTAG_PATCHESNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PATCHESVERSION", "Patchesversion", RPMTAG_PATCHESVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PATCH", "Patch", RPMTAG_PATCH, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PAYLOADCOMPRESSOR", "Payloadcompressor", RPMTAG_PAYLOADCOMPRESSOR, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PAYLOADFLAGS", "Payloadflags", RPMTAG_PAYLOADFLAGS, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PAYLOADFORMAT", "Payloadformat", RPMTAG_PAYLOADFORMAT, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PKGID", "Pkgid", RPMTAG_SIGMD5, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PLATFORM", "Platform", RPMTAG_PLATFORM, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POLICIES", "Policies", RPMTAG_POLICIES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_POLICYFLAGS", "Policyflags", RPMTAG_POLICYFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_POLICYNAMES", "Policynames", RPMTAG_POLICYNAMES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_POLICYTYPESINDEXES", "Policytypesindexes", RPMTAG_POLICYTYPESINDEXES, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_POLICYTYPES", "Policytypes", RPMTAG_POLICYTYPES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTINFLAGS", "Postinflags", RPMTAG_POSTINFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTIN", "Postin", RPMTAG_POSTIN, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTINPROG", "Postinprog", RPMTAG_POSTINPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTTRANSFLAGS", "Posttransflags", RPMTAG_POSTTRANSFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTTRANS", "Posttrans", RPMTAG_POSTTRANS, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTTRANSPROG", "Posttransprog", RPMTAG_POSTTRANSPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTUNFLAGS", "Postunflags", RPMTAG_POSTUNFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTUN", "Postun", RPMTAG_POSTUN, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_POSTUNPROG", "Postunprog", RPMTAG_POSTUNPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_P", "P", RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PREFIXES", "Prefixes", RPMTAG_PREFIXES, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PREINFLAGS", "Preinflags", RPMTAG_PREINFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PREIN", "Prein", RPMTAG_PREIN, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PREINPROG", "Preinprog", RPMTAG_PREINPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PRETRANSFLAGS", "Pretransflags", RPMTAG_PRETRANSFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PRETRANS", "Pretrans", RPMTAG_PRETRANS, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PRETRANSPROG", "Pretransprog", RPMTAG_PRETRANSPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PREUNFLAGS", "Preunflags", RPMTAG_PREUNFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PREUN", "Preun", RPMTAG_PREUN, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PREUNPROG", "Preunprog", RPMTAG_PREUNPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_PROVIDEFLAGS", "Provideflags", RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PROVIDENAME", "Providename", RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PROVIDES", "Provides", RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PROVIDEVERSION", "Provideversion", RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_PUBKEYS", "Pubkeys", RPMTAG_PUBKEYS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_RECONTEXTS", "Recontexts", RPMTAG_RECONTEXTS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_RELEASE", "Release", RPMTAG_RELEASE, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_REMOVETID", "Removetid", RPMTAG_REMOVETID, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_REQUIREFLAGS", "Requireflags", RPMTAG_REQUIREFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_REQUIRENAME", "Requirename", RPMTAG_REQUIRENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_REQUIRES", "Requires", RPMTAG_REQUIRENAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_REQUIREVERSION", "Requireversion", RPMTAG_REQUIREVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_RPMVERSION", "Rpmversion", RPMTAG_RPMVERSION, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_R", "R", RPMTAG_RELEASE, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_RSAHEADER", "Rsaheader", RPMTAG_RSAHEADER, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SHA1HEADER", "Sha1header", RPMTAG_SHA1HEADER, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SIGGPG", "Siggpg", RPMTAG_SIGGPG, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SIGMD5", "Sigmd5", RPMTAG_SIGMD5, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SIGPGP", "Sigpgp", RPMTAG_SIGPGP, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SIGSIZE", "Sigsize", RPMTAG_SIGSIZE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SIZE", "Size", RPMTAG_SIZE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SOURCEPACKAGE", "Sourcepackage", RPMTAG_SOURCEPACKAGE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SOURCEPKGID", "Sourcepkgid", RPMTAG_SOURCEPKGID, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SOURCERPM", "Sourcerpm", RPMTAG_SOURCERPM, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_SOURCE", "Source", RPMTAG_SOURCE, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_SUMMARY", "Summary", RPMTAG_SUMMARY, RPM_I18NSTRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERCONDS", "Triggerconds", RPMTAG_TRIGGERCONDS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_TRIGGERFLAGS", "Triggerflags", RPMTAG_TRIGGERFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERINDEX", "Triggerindex", RPMTAG_TRIGGERINDEX, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERNAME", "Triggername", RPMTAG_TRIGGERNAME, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERSCRIPTFLAGS", "Triggerscriptflags", RPMTAG_TRIGGERSCRIPTFLAGS, RPM_INT32_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERSCRIPTPROG", "Triggerscriptprog", RPMTAG_TRIGGERSCRIPTPROG, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERSCRIPTS", "Triggerscripts", RPMTAG_TRIGGERSCRIPTS, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_TRIGGERTYPE", "Triggertype", RPMTAG_TRIGGERTYPE, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 1 },
+ { "RPMTAG_TRIGGERVERSION", "Triggerversion", RPMTAG_TRIGGERVERSION, RPM_STRING_ARRAY_TYPE, RPM_ARRAY_RETURN_TYPE, 0 },
+ { "RPMTAG_URL", "Url", RPMTAG_URL, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_VCS", "Vcs", RPMTAG_VCS, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_VENDOR", "Vendor", RPMTAG_VENDOR, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_VERBOSE", "Verbose", RPMTAG_VERBOSE, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 1 },
+ { "RPMTAG_VERIFYSCRIPTFLAGS", "Verifyscriptflags", RPMTAG_VERIFYSCRIPTFLAGS, RPM_INT32_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_VERIFYSCRIPTPROG", "Verifyscriptprog", RPMTAG_VERIFYSCRIPTPROG, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_VERIFYSCRIPT", "Verifyscript", RPMTAG_VERIFYSCRIPT, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_VERSION", "Version", RPMTAG_VERSION, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_V", "V", RPMTAG_VERSION, RPM_STRING_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { "RPMTAG_XPM", "Xpm", RPMTAG_XPM, RPM_BIN_TYPE, RPM_SCALAR_RETURN_TYPE, 0 },
+ { NULL, NULL, RPMTAG_NOT_FOUND, RPM_NULL_TYPE, 0 }
+};
diff --git a/lib/transaction.c b/lib/transaction.c
new file mode 100644
index 0000000..7adf60b
--- /dev/null
+++ b/lib/transaction.c
@@ -0,0 +1,1471 @@
+/** \ingroup rpmts
+ * \file lib/transaction.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmlib.h> /* rpmMachineScore, rpmReadPackageFile */
+#include <rpm/rpmmacro.h> /* XXX for rpmExpand */
+#include <rpm/rpmlog.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmds.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmstring.h>
+
+#include "lib/fprint.h"
+#include "lib/misc.h"
+#include "lib/rpmchroot.h"
+#include "lib/rpmlock.h"
+#include "lib/rpmfi_internal.h" /* only internal apis */
+#include "lib/rpmte_internal.h" /* only internal apis */
+#include "lib/rpmts_internal.h"
+#include "rpmio/rpmhook.h"
+
+/* XXX FIXME: merge with existing (broken?) tests in system.h */
+/* portability fiddles */
+#if STATFS_IN_SYS_STATVFS
+#include <sys/statvfs.h>
+
+#else
+# if STATFS_IN_SYS_VFS
+# include <sys/vfs.h>
+# else
+# if STATFS_IN_SYS_MOUNT
+# include <sys/mount.h>
+# else
+# if STATFS_IN_SYS_STATFS
+# include <sys/statfs.h>
+# endif
+# endif
+# endif
+#endif
+
+#include "debug.h"
+
+struct diskspaceInfo_s {
+ char * mntPoint; /*!< File system mount point */
+ dev_t dev; /*!< File system device number. */
+ int64_t bneeded; /*!< No. of blocks needed. */
+ int64_t ineeded; /*!< No. of inodes needed. */
+ int64_t bsize; /*!< File system block size. */
+ int64_t bavail; /*!< No. of blocks available. */
+ int64_t iavail; /*!< No. of inodes available. */
+ int64_t obneeded; /*!< Bookkeeping to avoid duplicate reports */
+ int64_t oineeded; /*!< Bookkeeping to avoid duplicate reports */
+};
+
+/* Adjust for root only reserved space. On linux e2fs, this is 5%. */
+#define adj_fs_blocks(_nb) (((_nb) * 21) / 20)
+#define BLOCK_ROUND(size, block) (((size) + (block) - 1) / (block))
+
+static int rpmtsInitDSI(const rpmts ts)
+{
+ if (rpmtsFilterFlags(ts) & RPMPROB_FILTER_DISKSPACE)
+ return 0;
+ ts->dsi = _free(ts->dsi);
+ ts->dsi = xcalloc(1, sizeof(*ts->dsi));
+ return 0;
+}
+
+static rpmDiskSpaceInfo rpmtsCreateDSI(const rpmts ts, dev_t dev,
+ const char * dirName, int count)
+{
+ rpmDiskSpaceInfo dsi;
+ struct stat sb;
+ char * resolved_path;
+ char mntPoint[PATH_MAX];
+ int rc;
+
+#if STATFS_IN_SYS_STATVFS
+ struct statvfs sfb;
+ memset(&sfb, 0, sizeof(sfb));
+ rc = statvfs(dirName, &sfb);
+#else
+ struct statfs sfb;
+ memset(&sfb, 0, sizeof(sfb));
+# if STAT_STATFS4
+/* This platform has the 4-argument version of the statfs call. The last two
+ * should be the size of struct statfs and 0, respectively. The 0 is the
+ * filesystem type, and is always 0 when statfs is called on a mounted
+ * filesystem, as we're doing.
+ */
+ rc = statfs(dirName, &sfb, sizeof(sfb), 0);
+# else
+ rc = statfs(dirName, &sfb);
+# endif
+#endif
+ if (rc)
+ return NULL;
+
+ rc = stat(dirName, &sb);
+ if (rc)
+ return NULL;
+ if (sb.st_dev != dev)
+ return NULL;
+
+ ts->dsi = xrealloc(ts->dsi, (count + 2) * sizeof(*ts->dsi));
+ dsi = ts->dsi + count;
+ memset(dsi, 0, 2 * sizeof(*dsi));
+
+ dsi->dev = sb.st_dev;
+ dsi->bsize = sfb.f_bsize;
+ if (!dsi->bsize)
+ dsi->bsize = 512; /* we need a bsize */
+ dsi->bneeded = 0;
+ dsi->ineeded = 0;
+#ifdef STATFS_HAS_F_BAVAIL
+ dsi->bavail = (sfb.f_flag & ST_RDONLY) ? 0 : sfb.f_bavail;
+#else
+/* FIXME: the statfs struct doesn't have a member to tell how many blocks are
+ * available for non-superusers. f_blocks - f_bfree is probably too big, but
+ * it's about all we can do.
+ */
+ dsi->bavail = sfb.f_blocks - sfb.f_bfree;
+#endif
+ /* XXX Avoid FAT and other file systems that have not inodes. */
+ /* XXX assigning negative value to unsigned type */
+ dsi->iavail = !(sfb.f_ffree == 0 && sfb.f_files == 0)
+ ? sfb.f_ffree : -1;
+
+ /* Find mount point belonging to this device number */
+ resolved_path = realpath(dirName, mntPoint);
+ if (!resolved_path) {
+ strncpy(mntPoint, dirName, PATH_MAX);
+ mntPoint[PATH_MAX-1] = '\0';
+ }
+ char * end = NULL;
+ while (end != mntPoint) {
+ end = strrchr(mntPoint, '/');
+ if (end == mntPoint) { /* reached "/" */
+ stat("/", &sb);
+ if (dsi->dev != sb.st_dev) {
+ dsi->mntPoint = xstrdup(mntPoint);
+ } else {
+ dsi->mntPoint = xstrdup("/");
+ }
+ break;
+ } else if (end) {
+ *end = '\0';
+ } else { /* dirName doesn't start with / - should not happen */
+ dsi->mntPoint = xstrdup(dirName);
+ break;
+ }
+ stat(mntPoint, &sb);
+ if (dsi->dev != sb.st_dev) {
+ *end = '/';
+ dsi->mntPoint = xstrdup(mntPoint);
+ break;
+ }
+ }
+
+ rpmlog(RPMLOG_DEBUG,
+ "0x%08x %8" PRId64 " %12" PRId64 " %12" PRId64" %s\n",
+ (unsigned) dsi->dev, dsi->bsize,
+ dsi->bavail, dsi->iavail,
+ dsi->mntPoint);
+ return dsi;
+}
+
+static rpmDiskSpaceInfo rpmtsGetDSI(const rpmts ts, dev_t dev,
+ const char *dirName) {
+ rpmDiskSpaceInfo dsi;
+ dsi = ts->dsi;
+ if (dsi) {
+ while (dsi->bsize && dsi->dev != dev)
+ dsi++;
+ if (dsi->bsize == 0) {
+ /* create new entry */
+ dsi = rpmtsCreateDSI(ts, dev, dirName, dsi - ts->dsi);
+ }
+ }
+ return dsi;
+}
+
+static void rpmtsUpdateDSI(const rpmts ts, dev_t dev, const char *dirName,
+ rpm_loff_t fileSize, rpm_loff_t prevSize, rpm_loff_t fixupSize,
+ rpmFileAction action)
+{
+ int64_t bneeded;
+ rpmDiskSpaceInfo dsi = rpmtsGetDSI(ts, dev, dirName);
+ if (dsi == NULL)
+ return;
+
+ bneeded = BLOCK_ROUND(fileSize, dsi->bsize);
+
+ switch (action) {
+ case FA_BACKUP:
+ case FA_SAVE:
+ case FA_ALTNAME:
+ dsi->ineeded++;
+ dsi->bneeded += bneeded;
+ break;
+
+ /*
+ * FIXME: If two packages share a file (same md5sum), and
+ * that file is being replaced on disk, will dsi->bneeded get
+ * adjusted twice? Quite probably!
+ */
+ case FA_CREATE:
+ dsi->bneeded += bneeded;
+ dsi->bneeded -= BLOCK_ROUND(prevSize, dsi->bsize);
+ break;
+
+ case FA_ERASE:
+ dsi->ineeded--;
+ dsi->bneeded -= bneeded;
+ break;
+
+ default:
+ break;
+ }
+
+ if (fixupSize)
+ dsi->bneeded -= BLOCK_ROUND(fixupSize, dsi->bsize);
+
+ /* adjust bookkeeping when requirements shrink */
+ if (dsi->bneeded < dsi->obneeded) dsi->obneeded = dsi->bneeded;
+ if (dsi->ineeded < dsi->oineeded) dsi->oineeded = dsi->ineeded;
+}
+
+/* return DSI of the device the rpmdb lives on */
+static rpmDiskSpaceInfo rpmtsDbDSI(const rpmts ts) {
+ const char *dbhome = rpmdbHome(rpmtsGetRdb(ts));
+ struct stat sb;
+ int rc;
+
+ rc = stat(dbhome, &sb);
+ if (rc) {
+ return NULL;
+ }
+ return rpmtsGetDSI(ts, sb.st_dev, dbhome);
+}
+
+/* Update DSI for changing size of the rpmdb */
+static void rpmtsUpdateDSIrpmDBSize(const rpmte p,
+ rpmDiskSpaceInfo dsi) {
+ rpm_loff_t headerSize;
+ int64_t bneeded;
+
+ /* XXX somehow we can end up here with bsize 0 (RhBug:671056) */
+ if (dsi == NULL || dsi->bsize == 0) return;
+
+ headerSize = rpmteHeaderSize(p);
+ bneeded = BLOCK_ROUND(headerSize, dsi->bsize);
+ /* REMOVE doesn't neccessarily shrink the database */
+ if (rpmteType(p) == TR_ADDED) {
+ /* guessing that db grows 4 times more than the header size */
+ dsi->bneeded += (bneeded * 4);
+ }
+}
+
+
+static void rpmtsCheckDSIProblems(const rpmts ts, const rpmte te)
+{
+ rpmDiskSpaceInfo dsi = ts->dsi;
+
+ if (dsi == NULL || !dsi->bsize)
+ return;
+ if (rpmfiFC(rpmteFI(te)) <= 0)
+ return;
+
+ for (; dsi->bsize; dsi++) {
+
+ if (dsi->bavail >= 0 && adj_fs_blocks(dsi->bneeded) > dsi->bavail) {
+ if (dsi->bneeded > dsi->obneeded) {
+ rpmteAddProblem(te, RPMPROB_DISKSPACE, NULL, dsi->mntPoint,
+ (adj_fs_blocks(dsi->bneeded) - dsi->bavail) * dsi->bsize);
+ dsi->obneeded = dsi->bneeded;
+ }
+ }
+
+ if (dsi->iavail >= 0 && adj_fs_blocks(dsi->ineeded) > dsi->iavail) {
+ if (dsi->ineeded > dsi->oineeded) {
+ rpmteAddProblem(te, RPMPROB_DISKNODES, NULL, dsi->mntPoint,
+ (adj_fs_blocks(dsi->ineeded) - dsi->iavail));
+ dsi->oineeded = dsi->ineeded;
+ }
+ }
+ }
+}
+
+static void rpmtsFreeDSI(rpmts ts){
+ rpmDiskSpaceInfo dsi;
+ if (ts == NULL)
+ return;
+ dsi = ts->dsi;
+ while (dsi && dsi->bsize != 0) {
+ dsi->mntPoint = _free(dsi->mntPoint);
+ dsi++;
+ }
+
+ ts->dsi = _free(ts->dsi);
+}
+
+
+/* Calculate total number of files involved in transaction */
+static uint64_t countFiles(rpmts ts)
+{
+ uint64_t fc = 0;
+ rpmtsi pi = rpmtsiInit(ts);
+ rpmte p;
+ while ((p = rpmtsiNext(pi, 0)) != NULL)
+ fc += rpmfiFC(rpmteFI(p));
+ pi = rpmtsiFree(pi);
+ return fc;
+}
+
+/**
+ * handleInstInstalledFiles.
+ * @param ts transaction set
+ * @param p current transaction element
+ * @param fi file info set
+ * @param shared shared file info
+ * @param sharedCount no. of shared elements
+ * @param reportConflicts
+ */
+/* XXX only ts->{probs,rpmdb} modified */
+static int handleInstInstalledFile(const rpmts ts, rpmte p, rpmfi fi,
+ Header otherHeader, rpmfi otherFi,
+ int beingRemoved)
+{
+ unsigned int fx = rpmfiFX(fi);
+ rpmfs fs = rpmteGetFileStates(p);
+ int isCfgFile = ((rpmfiFFlags(otherFi) | rpmfiFFlags(fi)) & RPMFILE_CONFIG);
+
+ if (XFA_SKIPPING(rpmfsGetAction(fs, fx)))
+ return 0;
+
+ if (rpmfiCompare(otherFi, fi)) {
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpm_color_t prefcolor = rpmtsPrefColor(ts);
+ rpm_color_t FColor = rpmfiFColor(fi) & tscolor;
+ rpm_color_t oFColor = rpmfiFColor(otherFi) & tscolor;
+ int rConflicts;
+
+ rConflicts = !(beingRemoved || (rpmtsFilterFlags(ts) & RPMPROB_FILTER_REPLACEOLDFILES));
+ /* Resolve file conflicts to prefer Elf64 (if not forced). */
+ if (tscolor != 0 && FColor != 0 && oFColor != 0 && FColor != oFColor) {
+ if (oFColor & prefcolor) {
+ rpmfsSetAction(fs, fx, FA_SKIPCOLOR);
+ rConflicts = 0;
+ } else if (FColor & prefcolor) {
+ rpmfsSetAction(fs, fx, FA_CREATE);
+ rConflicts = 0;
+ }
+ }
+
+ if (rConflicts) {
+ char *altNEVR = headerGetAsString(otherHeader, RPMTAG_NEVRA);
+ rpmteAddProblem(p, RPMPROB_FILE_CONFLICT, altNEVR, rpmfiFN(fi),
+ headerGetInstance(otherHeader));
+ free(altNEVR);
+ }
+
+ /* Save file identifier to mark as state REPLACED. */
+ if ( !(isCfgFile || XFA_SKIPPING(rpmfsGetAction(fs, fx))) ) {
+ if (!beingRemoved)
+ rpmfsAddReplaced(rpmteGetFileStates(p), rpmfiFX(fi),
+ headerGetInstance(otherHeader),
+ rpmfiFX(otherFi));
+ }
+ }
+
+ /* Determine config file dispostion, skipping missing files (if any). */
+ if (isCfgFile) {
+ int skipMissing = ((rpmtsFlags(ts) & RPMTRANS_FLAG_ALLFILES) ? 0 : 1);
+ rpmFileAction action = rpmfiDecideFate(otherFi, fi, skipMissing);
+ rpmfsSetAction(fs, fx, action);
+ }
+ rpmfiSetFReplacedSize(fi, rpmfiFSize(otherFi));
+
+ return 0;
+}
+
+/**
+ * Update disk space needs on each partition for this package's files.
+ */
+/* XXX only ts->{probs,di} modified */
+static void handleOverlappedFiles(rpmts ts, rpmFpHash ht, rpmte p, rpmfi fi)
+{
+ rpm_loff_t fixupSize = 0;
+ const char * fn;
+ int i, j;
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpm_color_t prefcolor = rpmtsPrefColor(ts);
+ rpmfs fs = rpmteGetFileStates(p);
+ rpmfs otherFs;
+
+ fi = rpmfiInit(fi, 0);
+ while ((i = rpmfiNext(fi)) >= 0) {
+ rpm_color_t oFColor, FColor;
+ struct fingerPrint_s * fiFps;
+ int otherPkgNum, otherFileNum;
+ rpmfi otherFi;
+ rpmte otherTe;
+ rpmfileAttrs FFlags;
+ rpm_mode_t FMode;
+ struct rpmffi_s * recs;
+ int numRecs;
+
+ if (XFA_SKIPPING(rpmfsGetAction(fs, i)))
+ continue;
+
+ fn = rpmfiFN(fi);
+ fiFps = rpmfiFpsIndex(fi, i);
+ FFlags = rpmfiFFlags(fi);
+ FMode = rpmfiFMode(fi);
+ FColor = rpmfiFColor(fi);
+ FColor &= tscolor;
+
+ fixupSize = 0;
+
+ /*
+ * Retrieve all records that apply to this file. Note that the
+ * file info records were built in the same order as the packages
+ * will be installed and removed so the records for an overlapped
+ * files will be sorted in exactly the same order.
+ */
+ (void) rpmFpHashGetEntry(ht, fiFps, &recs, &numRecs, NULL);
+
+ /*
+ * If this package is being added, look only at other packages
+ * being added -- removed packages dance to a different tune.
+ *
+ * If both this and the other package are being added, overlapped
+ * files must be identical (or marked as a conflict). The
+ * disposition of already installed config files leads to
+ * a small amount of extra complexity.
+ *
+ * If this package is being removed, then there are two cases that
+ * need to be worried about:
+ * If the other package is being added, then skip any overlapped files
+ * so that this package removal doesn't nuke the overlapped files
+ * that were just installed.
+ * If both this and the other package are being removed, then each
+ * file removal from preceding packages needs to be skipped so that
+ * the file removal occurs only on the last occurence of an overlapped
+ * file in the transaction set.
+ *
+ */
+
+ /* Locate this overlapped file in the set of added/removed packages. */
+ for (j = 0; j < numRecs && recs[j].p != p; j++)
+ {};
+
+ /* Find what the previous disposition of this file was. */
+ otherFileNum = -1; /* keep gcc quiet */
+ otherFi = NULL;
+ otherTe = NULL;
+ otherFs = NULL;
+
+ for (otherPkgNum = j - 1; otherPkgNum >= 0; otherPkgNum--) {
+ otherTe = recs[otherPkgNum].p;
+ otherFi = rpmteFI(otherTe);
+ otherFileNum = recs[otherPkgNum].fileno;
+ otherFs = rpmteGetFileStates(otherTe);
+
+ /* Added packages need only look at other added packages. */
+ if (rpmteType(p) == TR_ADDED && rpmteType(otherTe) != TR_ADDED)
+ continue;
+
+ (void) rpmfiSetFX(otherFi, otherFileNum);
+
+ /* XXX Happens iff fingerprint for incomplete package install. */
+ if (rpmfsGetAction(otherFs, otherFileNum) != FA_UNKNOWN)
+ break;
+ }
+
+ oFColor = rpmfiFColor(otherFi);
+ oFColor &= tscolor;
+
+ switch (rpmteType(p)) {
+ case TR_ADDED:
+ {
+ int reportConflicts =
+ !(rpmtsFilterFlags(ts) & RPMPROB_FILTER_REPLACENEWFILES);
+ int done = 0;
+
+ if (otherPkgNum < 0) {
+ /* XXX is this test still necessary? */
+ rpmFileAction action;
+ if (rpmfsGetAction(fs, i) != FA_UNKNOWN)
+ break;
+ if (rpmfiConfigConflict(fi)) {
+ /* Here is a non-overlapped pre-existing config file. */
+ action = (FFlags & RPMFILE_NOREPLACE) ?
+ FA_ALTNAME : FA_BACKUP;
+ } else {
+ action = FA_CREATE;
+ }
+ rpmfsSetAction(fs, i, action);
+ break;
+ }
+
+assert(otherFi != NULL);
+ /* Mark added overlapped non-identical files as a conflict. */
+ if (rpmfiCompare(otherFi, fi)) {
+ int rConflicts;
+
+ rConflicts = reportConflicts;
+ /* Resolve file conflicts to prefer Elf64 (if not forced) ... */
+ if (tscolor != 0 && FColor != 0 && oFColor != 0 && FColor != oFColor) {
+ if (FColor & prefcolor) {
+ /* ... last file of preferred colour is installed ... */
+ if (!XFA_SKIPPING(rpmfsGetAction(fs, i)))
+ rpmfsSetAction(otherFs, otherFileNum, FA_SKIPCOLOR);
+ rpmfsSetAction(fs, i, FA_CREATE);
+ rConflicts = 0;
+ } else
+ if (oFColor & prefcolor) {
+ /* ... first file of preferred colour is installed ... */
+ if (XFA_SKIPPING(rpmfsGetAction(fs, i)))
+ rpmfsSetAction(otherFs, otherFileNum, FA_CREATE);
+ rpmfsSetAction(fs, i, FA_SKIPCOLOR);
+ rConflicts = 0;
+ }
+ done = 1;
+ }
+ if (rConflicts) {
+ rpmteAddProblem(p, RPMPROB_NEW_FILE_CONFLICT,
+ rpmteNEVRA(otherTe), fn, 0);
+ }
+ }
+
+ /* Try to get the disk accounting correct even if a conflict. */
+ fixupSize = rpmfiFSize(otherFi);
+
+ if (rpmfiConfigConflict(fi)) {
+ /* Here is an overlapped pre-existing config file. */
+ rpmFileAction action;
+ action = (FFlags & RPMFILE_NOREPLACE) ? FA_ALTNAME : FA_SKIP;
+ rpmfsSetAction(fs, i, action);
+ } else {
+ if (!done)
+ rpmfsSetAction(fs, i, FA_CREATE);
+ }
+ } break;
+
+ case TR_REMOVED:
+ if (otherPkgNum >= 0) {
+ assert(otherFi != NULL);
+ /* Here is an overlapped added file we don't want to nuke. */
+ if (rpmfsGetAction(otherFs, otherFileNum) != FA_ERASE) {
+ /* On updates, don't remove files. */
+ rpmfsSetAction(fs, i, FA_SKIP);
+ break;
+ }
+ /* Here is an overlapped removed file: skip in previous. */
+ rpmfsSetAction(otherFs, otherFileNum, FA_SKIP);
+ }
+ if (XFA_SKIPPING(rpmfsGetAction(fs, i)))
+ break;
+ if (rpmfiFState(fi) != RPMFILE_STATE_NORMAL)
+ break;
+ if (!(S_ISREG(FMode) && (FFlags & RPMFILE_CONFIG))) {
+ rpmfsSetAction(fs, i, FA_ERASE);
+ break;
+ }
+
+ /* Here is a pre-existing modified config file that needs saving. */
+ { int algo = 0;
+ size_t diglen = 0;
+ const unsigned char *digest;
+ if ((digest = rpmfiFDigest(fi, &algo, &diglen))) {
+ unsigned char fdigest[diglen];
+ if (!rpmDoDigest(algo, fn, 0, fdigest, NULL) &&
+ memcmp(digest, fdigest, diglen)) {
+ rpmfsSetAction(fs, i, FA_BACKUP);
+ break;
+ }
+ }
+ }
+ rpmfsSetAction(fs, i, FA_ERASE);
+ break;
+ }
+
+ /* Update disk space info for a file. */
+ rpmtsUpdateDSI(ts, fiFps->entry->dev, fiFps->entry->dirName,
+ rpmfiFSize(fi), rpmfiFReplacedSize(fi),
+ fixupSize, rpmfsGetAction(fs, i));
+
+ }
+}
+
+/**
+ * Ensure that current package is newer than installed package.
+ * @param p current transaction element
+ * @param h installed header
+ * @param ps problem set
+ */
+static void ensureOlder(const rpmte p, const Header h)
+{
+ rpmsenseFlags reqFlags = (RPMSENSE_LESS | RPMSENSE_EQUAL);
+ rpmds req;
+
+ req = rpmdsSingle(RPMTAG_REQUIRENAME, rpmteN(p), rpmteEVR(p), reqFlags);
+ if (rpmdsNVRMatchesDep(h, req, _rpmds_nopromote) == 0) {
+ char * altNEVR = headerGetAsString(h, RPMTAG_NEVRA);
+ rpmteAddProblem(p, RPMPROB_OLDPACKAGE, altNEVR, NULL,
+ headerGetInstance(h));
+ free(altNEVR);
+ }
+ rpmdsFree(req);
+}
+
+/**
+ * Check if the curent file in the file iterator is in the
+ * netshardpath and though should be excluded.
+ * @param ts transaction set
+ * @param fi file info set
+ * @returns pointer to matching path or NULL
+ */
+static char ** matchNetsharedpath(const rpmts ts, rpmfi fi)
+{
+ char ** nsp;
+ const char * dn, * bn;
+ size_t dnlen, bnlen;
+ char * s;
+ bn = rpmfiBN(fi);
+ bnlen = strlen(bn);
+ dn = rpmfiDN(fi);
+ dnlen = strlen(dn);
+ for (nsp = ts->netsharedPaths; nsp && *nsp; nsp++) {
+ size_t len;
+
+ len = strlen(*nsp);
+ if (dnlen >= len) {
+ if (!rstreqn(dn, *nsp, len))
+ continue;
+ /* Only directories or complete file paths can be net shared */
+ if (!(dn[len] == '/' || dn[len] == '\0'))
+ continue;
+ } else {
+ if (len < (dnlen + bnlen))
+ continue;
+ if (!rstreqn(dn, *nsp, dnlen))
+ continue;
+ /* Insure that only the netsharedpath basename is compared. */
+ if ((s = strchr((*nsp) + dnlen, '/')) != NULL && s[1] != '\0')
+ continue;
+ if (!rstreqn(bn, (*nsp) + dnlen, bnlen))
+ continue;
+ len = dnlen + bnlen;
+ /* Only directories or complete file paths can be net shared */
+ if (!((*nsp)[len] == '/' || (*nsp)[len] == '\0'))
+ continue;
+ }
+
+ break;
+ }
+ return nsp;
+}
+
+static void skipEraseFiles(const rpmts ts, rpmte p)
+{
+ rpmfi fi = rpmteFI(p);
+ rpmfs fs = rpmteGetFileStates(p);
+ int i;
+ char ** nsp;
+ /*
+ * Skip net shared paths.
+ * Net shared paths are not relative to the current root (though
+ * they do need to take package relocations into account).
+ */
+ fi = rpmfiInit(fi, 0);
+ while ((i = rpmfiNext(fi)) >= 0)
+ {
+ nsp = matchNetsharedpath(ts, fi);
+ if (nsp && *nsp) {
+ rpmfsSetAction(fs, i, FA_SKIPNETSHARED);
+ }
+ }
+}
+
+
+/**
+ * Skip any files that do not match install policies.
+ * @param ts transaction set
+ * @param fi file info set
+ */
+static void skipInstallFiles(const rpmts ts, rpmte p)
+{
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpm_color_t FColor;
+ int noConfigs = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCONFIGS);
+ int noDocs = (rpmtsFlags(ts) & RPMTRANS_FLAG_NODOCS);
+ int * drc;
+ char * dff;
+ int dc;
+ int i, j, ix;
+ rpmfi fi = rpmteFI(p);
+ rpmfs fs = rpmteGetFileStates(p);
+
+ if (!noDocs)
+ noDocs = rpmExpandNumeric("%{_excludedocs}");
+
+ /* Compute directory refcount, skip directory if now empty. */
+ dc = rpmfiDC(fi);
+ drc = xcalloc(dc, sizeof(*drc));
+ dff = xcalloc(dc, sizeof(*dff));
+
+ fi = rpmfiInit(fi, 0);
+ while ((i = rpmfiNext(fi)) >= 0) {
+ char ** nsp;
+ const char *flangs;
+
+ ix = rpmfiDX(fi);
+ drc[ix]++;
+
+ /* Don't bother with skipped files */
+ if (XFA_SKIPPING(rpmfsGetAction(fs, i))) {
+ drc[ix]--; dff[ix] = 1;
+ continue;
+ }
+
+ /* Ignore colored files not in our rainbow. */
+ FColor = rpmfiFColor(fi);
+ if (tscolor && FColor && !(tscolor & FColor)) {
+ drc[ix]--; dff[ix] = 1;
+ rpmfsSetAction(fs, i, FA_SKIPCOLOR);
+ continue;
+ }
+
+ /*
+ * Skip net shared paths.
+ * Net shared paths are not relative to the current root (though
+ * they do need to take package relocations into account).
+ */
+ nsp = matchNetsharedpath(ts, fi);
+ if (nsp && *nsp) {
+ drc[ix]--; dff[ix] = 1;
+ rpmfsSetAction(fs, i, FA_SKIPNETSHARED);
+ continue;
+ }
+
+ /*
+ * Skip i18n language specific files.
+ */
+ flangs = (ts->installLangs != NULL) ? rpmfiFLangs(fi) : NULL;
+ if (flangs != NULL && *flangs != '\0') {
+ const char *l, *le;
+ char **lang;
+ for (lang = ts->installLangs; *lang != NULL; lang++) {
+ for (l = flangs; *l != '\0'; l = le) {
+ for (le = l; *le != '\0' && *le != '|'; le++)
+ {};
+ if ((le-l) > 0 && rstreqn(*lang, l, (le-l)))
+ break;
+ if (*le == '|') le++; /* skip over | */
+ }
+ if (*l != '\0')
+ break;
+ }
+ if (*lang == NULL) {
+ drc[ix]--; dff[ix] = 1;
+ rpmfsSetAction(fs, i, FA_SKIPNSTATE);
+ continue;
+ }
+ }
+
+ /*
+ * Skip config files if requested.
+ */
+ if (noConfigs && (rpmfiFFlags(fi) & RPMFILE_CONFIG)) {
+ drc[ix]--; dff[ix] = 1;
+ rpmfsSetAction(fs, i, FA_SKIPNSTATE);
+ continue;
+ }
+
+ /*
+ * Skip documentation if requested.
+ */
+ if (noDocs && (rpmfiFFlags(fi) & RPMFILE_DOC)) {
+ drc[ix]--; dff[ix] = 1;
+ rpmfsSetAction(fs, i, FA_SKIPNSTATE);
+ continue;
+ }
+ }
+
+ /* Skip (now empty) directories that had skipped files. */
+ for (j = 0; j < dc; j++) {
+ const char * dn, * bn;
+ size_t dnlen, bnlen;
+
+ if (drc[j]) continue; /* dir still has files. */
+ if (!dff[j]) continue; /* dir was not emptied here. */
+
+ /* Find parent directory and basename. */
+ dn = rpmfiDNIndex(fi, j); dnlen = strlen(dn) - 1;
+ bn = dn + dnlen; bnlen = 0;
+ while (bn > dn && bn[-1] != '/') {
+ bnlen++;
+ dnlen--;
+ bn--;
+ }
+
+ /* If explicitly included in the package, skip the directory. */
+ fi = rpmfiInit(fi, 0);
+ while ((i = rpmfiNext(fi)) >= 0) {
+ const char * fdn, * fbn;
+ rpm_mode_t fFMode;
+
+ if (XFA_SKIPPING(rpmfsGetAction(fs, i)))
+ continue;
+
+ fFMode = rpmfiFMode(fi);
+
+ if (rpmfiWhatis(fFMode) != XDIR)
+ continue;
+ fdn = rpmfiDN(fi);
+ if (strlen(fdn) != dnlen)
+ continue;
+ if (!rstreqn(fdn, dn, dnlen))
+ continue;
+ fbn = rpmfiBN(fi);
+ if (strlen(fbn) != bnlen)
+ continue;
+ if (!rstreqn(fbn, bn, bnlen))
+ continue;
+ rpmlog(RPMLOG_DEBUG, "excluding directory %s\n", dn);
+ rpmfsSetAction(fs, i, FA_SKIPNSTATE);
+ break;
+ }
+ }
+
+ free(drc);
+ free(dff);
+}
+
+#undef HASHTYPE
+#undef HTKEYTYPE
+#undef HTDATATYPE
+
+#define HASHTYPE rpmStringSet
+#define HTKEYTYPE const char *
+#include "lib/rpmhash.H"
+#include "lib/rpmhash.C"
+
+/* Get a rpmdbMatchIterator containing all files in
+ * the rpmdb that share the basename with one from
+ * the transaction.
+ * @param ts transaction set
+ * @return rpmdbMatchIterator sorted
+ by (package, fileNum)
+ */
+static
+rpmdbMatchIterator rpmFindBaseNamesInDB(rpmts ts, uint64_t fileCount)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpmtsi pi; rpmte p;
+ rpmfi fi;
+ rpmdbMatchIterator mi;
+ int xx;
+ int oc = 0;
+ const char * baseName;
+
+ rpmStringSet baseNames = rpmStringSetCreate(fileCount,
+ hashFunctionString, strcmp, NULL);
+
+ mi = rpmdbNewIterator(rpmtsGetRdb(ts), RPMDBI_BASENAMES);
+
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ (void) rpmdbCheckSignals();
+
+ rpmtsNotify(ts, NULL, RPMCALLBACK_TRANS_PROGRESS, oc++, tsmem->orderCount);
+
+ /* Gather all installed headers with matching basename's. */
+ fi = rpmfiInit(rpmteFI(p), 0);
+ while (rpmfiNext(fi) >= 0) {
+ size_t keylen;
+ baseName = rpmfiBN(fi);
+ if (rpmStringSetHasEntry(baseNames, baseName))
+ continue;
+
+ keylen = strlen(baseName);
+ if (keylen == 0)
+ keylen++; /* XXX "/" fixup. */
+ xx = rpmdbExtendIterator(mi, baseName, keylen);
+ rpmStringSetAddEntry(baseNames, baseName);
+ }
+ }
+ pi = rpmtsiFree(pi);
+ rpmStringSetFree(baseNames);
+
+ rpmdbSortIterator(mi);
+ /* iterator is now sorted by (recnum, filenum) */
+ return mi;
+}
+
+/* Check files in the transactions against the rpmdb
+ * Lookup all files with the same basename in the rpmdb
+ * and then check for matching finger prints
+ * @param ts transaction set
+ * @param fpc global finger print cache
+ */
+static
+void checkInstalledFiles(rpmts ts, uint64_t fileCount, rpmFpHash ht, fingerPrintCache fpc)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpmte p;
+ rpmfi fi;
+ rpmfs fs;
+ rpmfi otherFi=NULL;
+ int j;
+ int xx;
+ unsigned int fileNum;
+ const char * oldDir;
+
+ rpmdbMatchIterator mi;
+ Header h, newheader;
+
+ int beingRemoved;
+
+ rpmlog(RPMLOG_DEBUG, "computing file dispositions\n");
+
+ mi = rpmFindBaseNamesInDB(ts, fileCount);
+
+ /* For all installed headers with matching basename's ... */
+ if (mi == NULL)
+ return;
+
+ if (rpmdbGetIteratorCount(mi) == 0) {
+ mi = rpmdbFreeIterator(mi);
+ return;
+ }
+
+ /* Loop over all packages from the rpmdb */
+ h = newheader = rpmdbNextIterator(mi);
+ while (h != NULL) {
+ headerGetFlags hgflags = HEADERGET_MINMEM;
+ struct rpmtd_s bnames, dnames, dindexes, ostates;
+ fingerPrint fp;
+ unsigned int installedPkg;
+
+ /* Is this package being removed? */
+ installedPkg = rpmdbGetIteratorOffset(mi);
+ beingRemoved = intHashHasEntry(tsmem->removedPackages, installedPkg);
+
+ h = headerLink(h);
+ headerGet(h, RPMTAG_BASENAMES, &bnames, hgflags);
+ headerGet(h, RPMTAG_DIRNAMES, &dnames, hgflags);
+ headerGet(h, RPMTAG_DIRINDEXES, &dindexes, hgflags);
+ headerGet(h, RPMTAG_FILESTATES, &ostates, hgflags);
+
+ oldDir = NULL;
+ /* loop over all interesting files in that package */
+ do {
+ int gotRecs;
+ struct rpmffi_s * recs;
+ int numRecs;
+ const char * dirName;
+ const char * baseName;
+
+ fileNum = rpmdbGetIteratorFileNum(mi);
+ rpmtdSetIndex(&bnames, fileNum);
+ rpmtdSetIndex(&dindexes, fileNum);
+ rpmtdSetIndex(&dnames, *rpmtdGetUint32(&dindexes));
+ rpmtdSetIndex(&ostates, fileNum);
+
+ dirName = rpmtdGetString(&dnames);
+ baseName = rpmtdGetString(&bnames);
+
+ /* lookup finger print for this file */
+ if ( dirName == oldDir) {
+ /* directory is the same as last round */
+ fp.baseName = baseName;
+ } else {
+ fp = fpLookup(fpc, dirName, baseName, 1);
+ oldDir = dirName;
+ }
+ /* search for files in the transaction with same finger print */
+ gotRecs = rpmFpHashGetEntry(ht, &fp, &recs, &numRecs, NULL);
+
+ for (j=0; (j<numRecs)&&gotRecs; j++) {
+ p = recs[j].p;
+ fi = rpmteFI(p);
+ fs = rpmteGetFileStates(p);
+
+ /* Determine the fate of each file. */
+ switch (rpmteType(p)) {
+ case TR_ADDED:
+ if (!otherFi) {
+ otherFi = rpmfiNew(ts, h, RPMTAG_BASENAMES, RPMFI_KEEPHEADER);
+ }
+ rpmfiSetFX(fi, recs[j].fileno);
+ rpmfiSetFX(otherFi, fileNum);
+ xx = handleInstInstalledFile(ts, p, fi, h, otherFi, beingRemoved);
+ break;
+ case TR_REMOVED:
+ if (!beingRemoved) {
+ rpmfiSetFX(fi, recs[j].fileno);
+ if (*rpmtdGetChar(&ostates) == RPMFILE_STATE_NORMAL)
+ rpmfsSetAction(fs, recs[j].fileno, FA_SKIP);
+ }
+ break;
+ }
+ }
+
+ newheader = rpmdbNextIterator(mi);
+
+ } while (newheader==h);
+
+ otherFi = rpmfiFree(otherFi);
+ rpmtdFreeData(&ostates);
+ rpmtdFreeData(&bnames);
+ rpmtdFreeData(&dnames);
+ rpmtdFreeData(&dindexes);
+ headerFree(h);
+ h = newheader;
+ }
+
+ mi = rpmdbFreeIterator(mi);
+}
+
+#define badArch(_a) (rpmMachineScore(RPM_MACHTABLE_INSTARCH, (_a)) == 0)
+#define badOs(_a) (rpmMachineScore(RPM_MACHTABLE_INSTOS, (_a)) == 0)
+
+/*
+ * For packages being installed:
+ * - verify package arch/os.
+ * - verify package epoch:version-release is newer.
+ */
+static rpmps checkProblems(rpmts ts)
+{
+ rpm_color_t tscolor = rpmtsColor(ts);
+ rpmprobFilterFlags probFilter = rpmtsFilterFlags(ts);
+ rpmtsi pi = rpmtsiInit(ts);
+ rpmte p;
+
+ /* The ordering doesn't matter here */
+ /* XXX Only added packages need be checked. */
+ rpmlog(RPMLOG_DEBUG, "sanity checking %d elements\n", rpmtsNElements(ts));
+ while ((p = rpmtsiNext(pi, TR_ADDED)) != NULL) {
+ rpmdbMatchIterator mi;
+
+ if (!(probFilter & RPMPROB_FILTER_IGNOREARCH) && badArch(rpmteA(p)))
+ rpmteAddProblem(p, RPMPROB_BADARCH, rpmteA(p), NULL, 0);
+
+ if (!(probFilter & RPMPROB_FILTER_IGNOREOS) && badOs(rpmteO(p)))
+ rpmteAddProblem(p, RPMPROB_BADOS, rpmteO(p), NULL, 0);
+
+ if (!(probFilter & RPMPROB_FILTER_OLDPACKAGE)) {
+ Header h;
+ mi = rpmtsInitIterator(ts, RPMDBI_NAME, rpmteN(p), 0);
+ while ((h = rpmdbNextIterator(mi)) != NULL)
+ ensureOlder(p, h);
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ if (!(probFilter & RPMPROB_FILTER_REPLACEPKG)) {
+ Header h;
+ mi = rpmtsInitIterator(ts, RPMDBI_NAME, rpmteN(p), 0);
+ rpmdbSetIteratorRE(mi, RPMTAG_EPOCH, RPMMIRE_STRCMP, rpmteE(p));
+ rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_STRCMP, rpmteV(p));
+ rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_STRCMP, rpmteR(p));
+ if (tscolor) {
+ rpmdbSetIteratorRE(mi, RPMTAG_ARCH, RPMMIRE_STRCMP, rpmteA(p));
+ rpmdbSetIteratorRE(mi, RPMTAG_OS, RPMMIRE_STRCMP, rpmteO(p));
+ }
+
+ if ((h = rpmdbNextIterator(mi)) != NULL) {
+ rpmteAddProblem(p, RPMPROB_PKG_INSTALLED, NULL, NULL,
+ headerGetInstance(h));
+ }
+ mi = rpmdbFreeIterator(mi);
+ }
+ }
+ pi = rpmtsiFree(pi);
+ return rpmtsProblems(ts);
+}
+
+/*
+ * Run pre/post transaction scripts for transaction set
+ * param ts Transaction set
+ * param goal PKG_PRETRANS/PKG_POSTTRANS
+ * return 0 on success
+ */
+static int runTransScripts(rpmts ts, pkgGoal goal)
+{
+ rpmte p;
+ rpmtsi pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, TR_ADDED)) != NULL) {
+ rpmteProcess(p, goal);
+ }
+ pi = rpmtsiFree(pi);
+ return 0; /* what to do about failures? */
+}
+
+static int rpmtsSetupCollections(rpmts ts)
+{
+ /* seenCollectionsPost and TEs are basically a key-value pair. each item in
+ * seenCollectionsPost is a collection that has been seen from any package,
+ * and the associated index in the TEs is the last transaction element
+ * where that collection was seen. */
+ ARGV_t seenCollectionsPost = NULL;
+ rpmte *TEs = NULL;
+ int numSeenPost = 0;
+
+ /* seenCollectionsPre is a list of collections that have been seen from
+ * only removed packages */
+ ARGV_t seenCollectionsPre = NULL;
+ int numSeenPre = 0;
+
+ ARGV_const_t collname;
+ int installing = 1;
+ int i;
+
+ rpmte p;
+ rpmtsi pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ /* detect when we switch from installing to removing packages, and
+ * update the lastInCollectionAdd lists */
+ if (installing && rpmteType(p) == TR_REMOVED) {
+ installing = 0;
+ for (i = 0; i < numSeenPost; i++) {
+ rpmteAddToLastInCollectionAdd(TEs[i], seenCollectionsPost[i]);
+ }
+ }
+
+ rpmteSetupCollectionPlugins(p);
+
+ for (collname = rpmteCollections(p); collname && *collname; collname++) {
+ /* figure out if we've seen this collection in post before */
+ for (i = 0; i < numSeenPost && strcmp(*collname, seenCollectionsPost[i]); i++) {
+ }
+ if (i < numSeenPost) {
+ /* we've seen the collection, update the index */
+ TEs[i] = p;
+ } else {
+ /* haven't seen the collection yet, add it */
+ argvAdd(&seenCollectionsPost, *collname);
+ TEs = xrealloc(TEs, sizeof(*TEs) * (numSeenPost + 1));
+ TEs[numSeenPost] = p;
+ numSeenPost++;
+ }
+
+ /* figure out if we've seen this collection in pre remove before */
+ if (installing == 0) {
+ for (i = 0; i < numSeenPre && strcmp(*collname, seenCollectionsPre[i]); i++) {
+ }
+ if (i >= numSeenPre) {
+ /* haven't seen this collection, add it */
+ rpmteAddToFirstInCollectionRemove(p, *collname);
+ argvAdd(&seenCollectionsPre, *collname);
+ numSeenPre++;
+ }
+ }
+ }
+ }
+ pi = rpmtsiFree(pi);
+
+ /* we've looked at all the rpmte's, update the lastInCollectionAny lists */
+ for (i = 0; i < numSeenPost; i++) {
+ rpmteAddToLastInCollectionAny(TEs[i], seenCollectionsPost[i]);
+ if (installing == 1) {
+ /* lastInCollectionAdd is only updated above if packages were
+ * removed. if nothing is removed in the transaction, we need to
+ * update that list here */
+ rpmteAddToLastInCollectionAdd(TEs[i], seenCollectionsPost[i]);
+ }
+ }
+
+ argvFree(seenCollectionsPost);
+ argvFree(seenCollectionsPre);
+ _free(TEs);
+
+ return 0;
+}
+
+/* Add fingerprint for each file not skipped. */
+static void addFingerprints(rpmts ts, uint64_t fileCount, rpmFpHash ht, fingerPrintCache fpc)
+{
+ rpmtsi pi;
+ rpmte p;
+ rpmfi fi;
+ int i;
+
+ rpmFpHash symlinks = rpmFpHashCreate(fileCount/16+16, fpHashFunction, fpEqual, NULL, NULL);
+
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ (void) rpmdbCheckSignals();
+
+ if ((fi = rpmteFI(p)) == NULL)
+ continue; /* XXX can't happen */
+
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
+ rpmfiFpLookup(fi, fpc);
+ /* collect symbolic links */
+ fi = rpmfiInit(fi, 0);
+ while ((i = rpmfiNext(fi)) >= 0) {
+ struct rpmffi_s ffi;
+ char const *linktarget;
+ linktarget = rpmfiFLink(fi);
+ if (!(linktarget && *linktarget != '\0'))
+ continue;
+ if (XFA_SKIPPING(rpmfsGetAction(rpmteGetFileStates(p), i)))
+ continue;
+ ffi.p = p;
+ ffi.fileno = i;
+ rpmFpHashAddEntry(symlinks, rpmfiFpsIndex(fi, i), ffi);
+ }
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), rpmfiFC(fi));
+
+ }
+ pi = rpmtsiFree(pi);
+
+ /* ===============================================
+ * Check fingerprints if they contain symlinks
+ * and add them to the hash table
+ */
+
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ (void) rpmdbCheckSignals();
+
+ fi = rpmfiInit(rpmteFI(p), 0);
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
+ while ((i = rpmfiNext(fi)) >= 0) {
+ if (XFA_SKIPPING(rpmfsGetAction(rpmteGetFileStates(p), i)))
+ continue;
+ fpLookupSubdir(symlinks, ht, fpc, p, i);
+ }
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
+ }
+ pi = rpmtsiFree(pi);
+
+ rpmFpHashFree(symlinks);
+}
+
+static int rpmtsSetup(rpmts ts, rpmprobFilterFlags ignoreSet)
+{
+ rpm_tid_t tid = (rpm_tid_t) time(NULL);
+ int dbmode = (rpmtsFlags(ts) & RPMTRANS_FLAG_TEST) ? O_RDONLY : (O_RDWR|O_CREAT);
+
+ if (rpmtsFlags(ts) & RPMTRANS_FLAG_NOSCRIPTS)
+ (void) rpmtsSetFlags(ts, (rpmtsFlags(ts) | _noTransScripts | _noTransTriggers));
+ if (rpmtsFlags(ts) & RPMTRANS_FLAG_NOTRIGGERS)
+ (void) rpmtsSetFlags(ts, (rpmtsFlags(ts) | _noTransTriggers));
+
+ if (rpmtsFlags(ts) & (RPMTRANS_FLAG_JUSTDB | RPMTRANS_FLAG_TEST))
+ (void) rpmtsSetFlags(ts, (rpmtsFlags(ts) | _noTransScripts | _noTransTriggers | RPMTRANS_FLAG_NOCOLLECTIONS));
+
+ /* if SELinux isn't enabled, init fails or test run, don't bother... */
+ if (!is_selinux_enabled() || (rpmtsFlags(ts) & RPMTRANS_FLAG_TEST)) {
+ rpmtsSetFlags(ts, (rpmtsFlags(ts) | RPMTRANS_FLAG_NOCONTEXTS));
+ }
+
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOCONTEXTS)) {
+ rpmtsSELabelInit(ts, selinux_file_context_path());
+ }
+
+ /*
+ * Make sure the database is open RDWR for package install/erase.
+ * Note that we initialize chroot state here even if it's just "/" as
+ * this ensures we can successfully perform open(".") which is
+ * required to reliably restore cwd after Lua scripts.
+ */
+ if (rpmtsOpenDB(ts, dbmode) || rpmChrootSet(rpmtsRootDir(ts)))
+ return -1;
+
+ ts->ignoreSet = ignoreSet;
+ (void) rpmtsSetTid(ts, tid);
+
+ /* Get available space on mounted file systems. */
+ (void) rpmtsInitDSI(ts);
+
+ return 0;
+}
+
+static int rpmtsFinish(rpmts ts)
+{
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_NOCONTEXTS)) {
+ rpmtsSELabelFini(ts);
+ }
+ return rpmChrootSet(NULL);
+}
+
+static int rpmtsPrepare(rpmts ts)
+{
+ tsMembers tsmem = rpmtsMembers(ts);
+ rpmtsi pi;
+ rpmte p;
+ rpmfi fi;
+ int rc = 0;
+ uint64_t fileCount = countFiles(ts);
+
+ fingerPrintCache fpc = fpCacheCreate(fileCount/2 + 10001);
+ rpmFpHash ht = rpmFpHashCreate(fileCount/2+1, fpHashFunction, fpEqual,
+ NULL, NULL);
+ rpmDiskSpaceInfo dsi;
+
+ rpmlog(RPMLOG_DEBUG, "computing %" PRIu64 " file fingerprints\n", fileCount);
+
+ /* Skip netshared paths, not our i18n files, and excluded docs */
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ if (rpmfiFC(rpmteFI(p)) == 0)
+ continue;
+ if (rpmteType(p) == TR_ADDED) {
+ skipInstallFiles(ts, p);
+ } else {
+ skipEraseFiles(ts, p);
+ }
+ }
+ pi = rpmtsiFree(pi);
+
+ /* Open rpmdb & enter chroot for fingerprinting if necessary */
+ if (rpmdbOpenAll(ts->rdb) || rpmChrootIn()) {
+ rc = -1;
+ goto exit;
+ }
+
+ rpmtsNotify(ts, NULL, RPMCALLBACK_TRANS_START, 6, tsmem->orderCount);
+ addFingerprints(ts, fileCount, ht, fpc);
+ /* check against files in the rpmdb */
+ checkInstalledFiles(ts, fileCount, ht, fpc);
+
+ dsi = rpmtsDbDSI(ts);
+
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ if ((fi = rpmteFI(p)) == NULL)
+ continue; /* XXX can't happen */
+
+ (void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
+ /* check files in ts against each other and update disk space
+ needs on each partition for this package. */
+ handleOverlappedFiles(ts, ht, p, fi);
+
+ rpmtsUpdateDSIrpmDBSize(p, dsi);
+
+ /* Check added package has sufficient space on each partition used. */
+ if (rpmteType(p) == TR_ADDED) {
+ rpmtsCheckDSIProblems(ts, p);
+ }
+ (void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
+ }
+ pi = rpmtsiFree(pi);
+ rpmtsNotify(ts, NULL, RPMCALLBACK_TRANS_STOP, 6, tsmem->orderCount);
+
+ /* return from chroot if done earlier */
+ if (rpmChrootOut())
+ rc = -1;
+
+ /* File info sets, fp caches etc not needed beyond here, free 'em up. */
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ rpmteSetFI(p, NULL);
+ }
+ pi = rpmtsiFree(pi);
+
+exit:
+ ht = rpmFpHashFree(ht);
+ fpc = fpCacheFree(fpc);
+ rpmtsFreeDSI(ts);
+ return rc;
+}
+
+/*
+ * Transaction main loop: install and remove packages
+ */
+static int rpmtsProcess(rpmts ts)
+{
+ rpmtsi pi; rpmte p;
+ int rc = 0;
+
+ pi = rpmtsiInit(ts);
+ while ((p = rpmtsiNext(pi, 0)) != NULL) {
+ int failed;
+
+ rpmlog(RPMLOG_DEBUG, "========== +++ %s %s-%s 0x%x\n",
+ rpmteNEVR(p), rpmteA(p), rpmteO(p), rpmteColor(p));
+
+ failed = rpmteProcess(p, rpmteType(p));
+ if (failed) {
+ rpmlog(RPMLOG_ERR, "%s: %s %s\n", rpmteNEVRA(p),
+ rpmteTypeString(p), failed > 1 ? _("skipped") : _("failed"));
+ rc++;
+ }
+ (void) rpmdbSync(rpmtsGetRdb(ts));
+ }
+ pi = rpmtsiFree(pi);
+ return rc;
+}
+
+int rpmtsRun(rpmts ts, rpmps okProbs, rpmprobFilterFlags ignoreSet)
+{
+ int rc = -1; /* assume failure */
+ rpmlock lock = NULL;
+ rpmps tsprobs = NULL;
+ /* Force default 022 umask during transaction for consistent results */
+ mode_t oldmask = umask(022);
+
+ /* Empty transaction, nothing to do */
+ if (rpmtsNElements(ts) <= 0) {
+ rc = 0;
+ goto exit;
+ }
+
+ /* If we are in test mode, then there's no need for transaction lock. */
+ if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_TEST)) {
+ if (!(lock = rpmtsAcquireLock(ts))) {
+ goto exit;
+ }
+ }
+
+ /* Setup flags and such, open the DB */
+ if (rpmtsSetup(ts, ignoreSet)) {
+ goto exit;
+ }
+
+ rpmtsSetupCollections(ts);
+
+ /* Check package set for problems */
+ tsprobs = checkProblems(ts);
+
+ /* Run pre-transaction scripts, but only if there are no known
+ * problems up to this point and not disabled otherwise. */
+ if (!((rpmtsFlags(ts) & (RPMTRANS_FLAG_BUILD_PROBS|RPMTRANS_FLAG_NOPRE))
+ || (rpmpsNumProblems(tsprobs)))) {
+ rpmlog(RPMLOG_DEBUG, "running pre-transaction scripts\n");
+ runTransScripts(ts, PKG_PRETRANS);
+ }
+ tsprobs = rpmpsFree(tsprobs);
+
+ /* Compute file disposition for each package in transaction set. */
+ if (rpmtsPrepare(ts)) {
+ goto exit;
+ }
+ /* Check again for problems (now including file conflicts, duh */
+ tsprobs = rpmtsProblems(ts);
+
+ /* If unfiltered problems exist, free memory and return. */
+ if ((rpmtsFlags(ts) & RPMTRANS_FLAG_BUILD_PROBS) || (rpmpsNumProblems(tsprobs))) {
+ tsMembers tsmem = rpmtsMembers(ts);
+ rc = tsmem->orderCount;
+ goto exit;
+ }
+
+ /* Free up memory taken by problem sets */
+ tsprobs = rpmpsFree(tsprobs);
+ rpmtsCleanProblems(ts);
+
+ /* Actually install and remove packages, get final exit code */
+ rc = rpmtsProcess(ts) ? -1 : 0;
+
+ /* Run post-transaction scripts unless disabled */
+ if (!(rpmtsFlags(ts) & (RPMTRANS_FLAG_NOPOST))) {
+ rpmlog(RPMLOG_DEBUG, "running post-transaction scripts\n");
+ runTransScripts(ts, PKG_POSTTRANS);
+ }
+
+exit:
+ /* Finish up... */
+ (void) umask(oldmask);
+ (void) rpmtsFinish(ts);
+ tsprobs = rpmpsFree(tsprobs);
+ rpmlockFree(lock);
+ return rc;
+}
diff --git a/lib/verify.c b/lib/verify.c
new file mode 100644
index 0000000..32e837c
--- /dev/null
+++ b/lib/verify.c
@@ -0,0 +1,516 @@
+/** \ingroup rpmcli
+ * \file lib/verify.c
+ * Verify installed payload files from package metadata.
+ */
+
+#include "system.h"
+
+#include <errno.h>
+#if WITH_CAP
+#include <sys/capability.h>
+#endif
+#if WITH_ACL
+#include <acl/libacl.h>
+#endif
+
+#include <rpm/rpmcli.h>
+#include <rpm/header.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmfi.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmfileutil.h>
+
+#include "lib/misc.h"
+#include "lib/rpmchroot.h"
+#include "lib/rpmte_internal.h" /* rpmteProcess() */
+#include "lib/rpmug.h"
+
+#include "debug.h"
+
+#define S_ISDEV(m) (S_ISBLK((m)) || S_ISCHR((m)))
+
+/* If cap_compare() (Linux extension) not available, do it the hard way */
+#if WITH_CAP && !defined(HAVE_CAP_COMPARE)
+static int cap_compare(cap_t acap, cap_t bcap)
+{
+ int rc = 0;
+ size_t asize = cap_size(acap);
+ size_t bsize = cap_size(bcap);
+
+ if (asize != bsize) {
+ rc = 1;
+ } else {
+ char *abuf = xcalloc(asize, sizeof(*abuf));
+ char *bbuf = xcalloc(bsize, sizeof(*bbuf));
+ cap_copy_ext(abuf, acap, asize);
+ cap_copy_ext(bbuf, bcap, bsize);
+ rc = memcmp(abuf, bbuf, asize);
+ free(abuf);
+ free(bbuf);
+ }
+ return rc;
+}
+#endif
+
+int rpmVerifyFile(const rpmts ts, const rpmfi fi,
+ rpmVerifyAttrs * res, rpmVerifyAttrs omitMask)
+{
+ rpm_mode_t fmode = rpmfiFMode(fi);
+ rpmfileAttrs fileAttrs = rpmfiFFlags(fi);
+ rpmVerifyAttrs flags = rpmfiVFlags(fi);
+ const char * fn = rpmfiFN(fi);
+ struct stat sb;
+ int rc;
+
+ *res = RPMVERIFY_NONE;
+
+ /*
+ * Check to see if the file was installed - if not pretend all is OK.
+ */
+ switch (rpmfiFState(fi)) {
+ case RPMFILE_STATE_NETSHARED:
+ case RPMFILE_STATE_NOTINSTALLED:
+ case RPMFILE_STATE_MISSING:
+ return 0;
+ break;
+ case RPMFILE_STATE_REPLACED:
+ /* For replaced files we can only verify if it exists at all */
+ flags = RPMVERIFY_LSTATFAIL;
+ break;
+ case RPMFILE_STATE_WRONGCOLOR:
+ /*
+ * Files with wrong color are supposed to share some attributes
+ * with the actually installed file - verify what we can.
+ */
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE |
+ RPMVERIFY_MTIME | RPMVERIFY_RDEV);
+ break;
+ case RPMFILE_STATE_NORMAL:
+ break;
+ }
+
+ if (fn == NULL || lstat(fn, &sb) != 0) {
+ *res |= RPMVERIFY_LSTATFAIL;
+ return 1;
+ }
+
+ /*
+ * Not all attributes of non-regular files can be verified.
+ */
+ if (S_ISDIR(sb.st_mode))
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME |
+ RPMVERIFY_LINKTO | RPMVERIFY_CAPS);
+ else if (S_ISLNK(sb.st_mode)) {
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME |
+ RPMVERIFY_MODE | RPMVERIFY_CAPS);
+ }
+ else if (S_ISFIFO(sb.st_mode))
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME |
+ RPMVERIFY_LINKTO | RPMVERIFY_CAPS);
+ else if (S_ISCHR(sb.st_mode))
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME |
+ RPMVERIFY_LINKTO | RPMVERIFY_CAPS);
+ else if (S_ISBLK(sb.st_mode))
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME |
+ RPMVERIFY_LINKTO | RPMVERIFY_CAPS);
+ else
+ flags &= ~(RPMVERIFY_LINKTO);
+
+ /*
+ * Content checks of %ghost files are meaningless.
+ */
+ if (fileAttrs & RPMFILE_GHOST)
+ flags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME |
+ RPMVERIFY_LINKTO);
+
+ /*
+ * Don't verify any features in omitMask.
+ */
+ flags &= ~(omitMask | RPMVERIFY_FAILURES);
+
+
+ if (flags & RPMVERIFY_FILEDIGEST) {
+ const unsigned char *digest;
+ int algo;
+ size_t diglen;
+
+ /* XXX If --nomd5, then prelinked library sizes are not corrected. */
+ if ((digest = rpmfiFDigest(fi, &algo, &diglen))) {
+ unsigned char fdigest[diglen];
+ rpm_loff_t fsize;
+
+ rc = rpmDoDigest(algo, fn, 0, fdigest, &fsize);
+ sb.st_size = fsize;
+ if (rc) {
+ *res |= (RPMVERIFY_READFAIL|RPMVERIFY_FILEDIGEST);
+ } else if (memcmp(fdigest, digest, diglen)) {
+ *res |= RPMVERIFY_FILEDIGEST;
+ }
+ } else {
+ *res |= RPMVERIFY_FILEDIGEST;
+ }
+ }
+
+ if (flags & RPMVERIFY_LINKTO) {
+ char linkto[1024+1];
+ int size = 0;
+
+ if ((size = readlink(fn, linkto, sizeof(linkto)-1)) == -1)
+ *res |= (RPMVERIFY_READLINKFAIL|RPMVERIFY_LINKTO);
+ else {
+ const char * flink = rpmfiFLink(fi);
+ linkto[size] = '\0';
+ if (flink == NULL || !rstreq(linkto, flink))
+ *res |= RPMVERIFY_LINKTO;
+ }
+ }
+
+ if (flags & RPMVERIFY_FILESIZE) {
+ if (sb.st_size != rpmfiFSize(fi))
+ *res |= RPMVERIFY_FILESIZE;
+ }
+
+ if (flags & RPMVERIFY_MODE) {
+ rpm_mode_t metamode = fmode;
+ rpm_mode_t filemode;
+
+ /*
+ * Platforms (like AIX) where sizeof(rpm_mode_t) != sizeof(mode_t)
+ * need the (rpm_mode_t) cast here.
+ */
+ filemode = (rpm_mode_t)sb.st_mode;
+
+ /*
+ * Comparing the type of %ghost files is meaningless, but perms are OK.
+ */
+ if (fileAttrs & RPMFILE_GHOST) {
+ metamode &= ~0xf000;
+ filemode &= ~0xf000;
+ }
+
+ if (metamode != filemode)
+ *res |= RPMVERIFY_MODE;
+
+#if WITH_ACL
+ /*
+ * For now, any non-default acl's on a file is a difference as rpm
+ * cannot have set them.
+ */
+ acl_t facl = acl_get_file(fn, ACL_TYPE_ACCESS);
+ if (facl) {
+ if (acl_equiv_mode(facl, NULL) == 1) {
+ *res |= RPMVERIFY_MODE;
+ }
+ acl_free(facl);
+ }
+#endif
+ }
+
+ if (flags & RPMVERIFY_RDEV) {
+ if (S_ISCHR(fmode) != S_ISCHR(sb.st_mode)
+ || S_ISBLK(fmode) != S_ISBLK(sb.st_mode))
+ {
+ *res |= RPMVERIFY_RDEV;
+ } else if (S_ISDEV(fmode) && S_ISDEV(sb.st_mode)) {
+ rpm_rdev_t st_rdev = (sb.st_rdev & 0xffff);
+ rpm_rdev_t frdev = (rpmfiFRdev(fi) & 0xffff);
+ if (st_rdev != frdev)
+ *res |= RPMVERIFY_RDEV;
+ }
+ }
+
+#if WITH_CAP
+ if (flags & RPMVERIFY_CAPS) {
+ /*
+ * Empty capability set ("=") is not exactly the same as no
+ * capabilities at all but suffices for now...
+ */
+ cap_t cap, fcap;
+ cap = cap_from_text(rpmfiFCaps(fi));
+ if (!cap) {
+ cap = cap_from_text("=");
+ }
+ fcap = cap_get_file(fn);
+ if (!fcap) {
+ fcap = cap_from_text("=");
+ }
+
+ if (cap_compare(cap, fcap) != 0)
+ *res |= RPMVERIFY_CAPS;
+
+ cap_free(fcap);
+ cap_free(cap);
+ }
+#endif
+
+ if ((flags & RPMVERIFY_MTIME) && (sb.st_mtime != rpmfiFMtime(fi))) {
+ *res |= RPMVERIFY_MTIME;
+ }
+
+ if (flags & RPMVERIFY_USER) {
+ const char * name = rpmugUname(sb.st_uid);
+ const char * fuser = rpmfiFUser(fi);
+ if (name == NULL || fuser == NULL || !rstreq(name, fuser))
+ *res |= RPMVERIFY_USER;
+ }
+
+ if (flags & RPMVERIFY_GROUP) {
+ const char * name = rpmugGname(sb.st_gid);
+ const char * fgroup = rpmfiFGroup(fi);
+ if (name == NULL || fgroup == NULL || !rstreq(name, fgroup))
+ *res |= RPMVERIFY_GROUP;
+ }
+
+ return 0;
+}
+
+/**
+ * Return exit code from running verify script from header.
+ * @param ts transaction set
+ * @param h header
+ * @return 0 on success
+ */
+static int rpmVerifyScript(rpmts ts, Header h)
+{
+ int rc = 0;
+
+ if (headerIsEntry(h, RPMTAG_VERIFYSCRIPT)) {
+ /* fake up a erasure transaction element */
+ rpmte p = rpmteNew(ts, h, TR_REMOVED, NULL, NULL);
+ rpmteSetHeader(p, h);
+
+ rc = (rpmpsmRun(ts, p, PKG_VERIFY) != RPMRC_OK);
+
+ /* clean up our fake transaction bits */
+ rpmteFree(p);
+ }
+
+ return rc;
+}
+
+#define unknown "?"
+#define _verify(_RPMVERIFY_F, _C, _pad) \
+ ((verifyResult & _RPMVERIFY_F) ? _C : _pad)
+#define _verifylink(_RPMVERIFY_F, _C, _pad) \
+ ((verifyResult & RPMVERIFY_READLINKFAIL) ? unknown : \
+ (verifyResult & _RPMVERIFY_F) ? _C : _pad)
+#define _verifyfile(_RPMVERIFY_F, _C, _pad) \
+ ((verifyResult & RPMVERIFY_READFAIL) ? unknown : \
+ (verifyResult & _RPMVERIFY_F) ? _C : _pad)
+char * rpmVerifyString(uint32_t verifyResult, const char *pad)
+{
+ char *fmt = NULL;
+ rasprintf(&fmt, "%s%s%s%s%s%s%s%s%s",
+ _verifyfile(RPMVERIFY_FILEDIGEST, "5", pad),
+ _verify(RPMVERIFY_FILESIZE, "S", pad),
+ _verifylink(RPMVERIFY_LINKTO, "L", pad),
+ _verify(RPMVERIFY_MTIME, "T", pad),
+ _verify(RPMVERIFY_RDEV, "D", pad),
+ _verify(RPMVERIFY_USER, "U", pad),
+ _verify(RPMVERIFY_GROUP, "G", pad),
+ _verify(RPMVERIFY_MODE, "M", pad),
+ _verify(RPMVERIFY_CAPS, "P", pad));
+
+ return fmt;
+}
+#undef _verifyfile
+#undef _verifylink
+#undef _verify
+#undef aok
+#undef unknown
+
+char * rpmFFlagsString(uint32_t fflags, const char *pad)
+{
+ char *fmt = NULL;
+ rasprintf(&fmt, "%s%s%s%s%s%s%s%s",
+ (fflags & RPMFILE_DOC) ? "d" : pad,
+ (fflags & RPMFILE_CONFIG) ? "c" : pad,
+ (fflags & RPMFILE_SPECFILE) ? "s" : pad,
+ (fflags & RPMFILE_MISSINGOK) ? "m" : pad,
+ (fflags & RPMFILE_NOREPLACE) ? "n" : pad,
+ (fflags & RPMFILE_GHOST) ? "g" : pad,
+ (fflags & RPMFILE_LICENSE) ? "l" : pad,
+ (fflags & RPMFILE_README) ? "r" : pad);
+ return fmt;
+}
+
+/**
+ * Check file info from header against what's actually installed.
+ * @param ts transaction set
+ * @param h header to verify
+ * @param omitMask bits to disable verify checks
+ * @param ghosts should ghosts be verified?
+ * @return 0 no problems, 1 problems found
+ */
+static int verifyHeader(rpmts ts, Header h, rpmVerifyAttrs omitMask, int ghosts)
+{
+ rpmVerifyAttrs verifyResult = 0;
+ int ec = 0; /* assume no problems */
+ rpmfi fi = rpmfiNew(ts, h, RPMTAG_BASENAMES, RPMFI_FLAGS_VERIFY);
+
+ rpmfiInit(fi, 0);
+ while (rpmfiNext(fi) >= 0) {
+ rpmfileAttrs fileAttrs = rpmfiFFlags(fi);
+ char *buf = NULL, *attrFormat;
+ char ac;
+ int rc;
+
+ /* If not verifying %ghost, skip ghost files. */
+ if ((fileAttrs & RPMFILE_GHOST) && !ghosts)
+ continue;
+
+ rc = rpmVerifyFile(ts, fi, &verifyResult, omitMask);
+
+ /* Filter out timestamp differences of shared files */
+ if (rc == 0 && (verifyResult & RPMVERIFY_MTIME)) {
+ rpmdbMatchIterator mi;
+ mi = rpmtsInitIterator(ts, RPMDBI_BASENAMES, rpmfiFN(fi), 0);
+ if (rpmdbGetIteratorCount(mi) > 1)
+ verifyResult &= ~RPMVERIFY_MTIME;
+ rpmdbFreeIterator(mi);
+ }
+
+ attrFormat = rpmFFlagsString(fileAttrs, "");
+ ac = rstreq(attrFormat, "") ? ' ' : attrFormat[0];
+ if (rc) {
+ if (!(fileAttrs & (RPMFILE_MISSINGOK|RPMFILE_GHOST)) || rpmIsVerbose()) {
+ rasprintf(&buf, _("missing %c %s"), ac, rpmfiFN(fi));
+ if ((verifyResult & RPMVERIFY_LSTATFAIL) != 0 &&
+ errno != ENOENT) {
+ char *app;
+ rasprintf(&app, " (%s)", strerror(errno));
+ rstrcat(&buf, app);
+ free(app);
+ }
+ ec = rc;
+ }
+ } else if (verifyResult || rpmIsVerbose()) {
+ char *verifyFormat = rpmVerifyString(verifyResult, ".");
+ rasprintf(&buf, "%s %c %s", verifyFormat, ac, rpmfiFN(fi));
+ free(verifyFormat);
+
+ if (verifyResult) ec = 1;
+ }
+ free(attrFormat);
+
+ if (buf) {
+ rpmlog(RPMLOG_NOTICE, "%s\n", buf);
+ buf = _free(buf);
+ }
+ }
+ rpmfiFree(fi);
+
+ return ec;
+}
+
+/**
+ * Check installed package dependencies for problems.
+ * @param ts transaction set
+ * @param h header
+ * @return number of problems found (0 for no problems)
+ */
+static int verifyDependencies(rpmts ts, Header h)
+{
+ rpmps ps;
+ rpmte te;
+ int rc;
+
+ rpmtsEmpty(ts);
+ (void) rpmtsAddInstallElement(ts, h, NULL, 0, NULL);
+
+ (void) rpmtsCheck(ts);
+ te = rpmtsElement(ts, 0);
+ ps = rpmteProblems(te);
+ rc = rpmpsNumProblems(ps);
+
+ if (rc > 0) {
+ rpmlog(RPMLOG_NOTICE, _("Unsatisfied dependencies for %s:\n"),
+ rpmteNEVRA(te));
+ rpmpsi psi = rpmpsInitIterator(ps);
+ rpmProblem p;
+
+ while ((p = rpmpsiNext(psi)) != NULL) {
+ char * ps = rpmProblemString(p);
+ rpmlog(RPMLOG_NOTICE, "\t%s\n", ps);
+ free(ps);
+ }
+ psi = rpmpsFreeIterator(psi);
+ }
+ ps = rpmpsFree(ps);
+ rpmtsEmpty(ts);
+
+ return rc;
+}
+
+int showVerifyPackage(QVA_t qva, rpmts ts, Header h)
+{
+ rpmVerifyAttrs omitMask = ((qva->qva_flags & VERIFY_ATTRS) ^ VERIFY_ATTRS);
+ int ghosts = (qva->qva_fflags & RPMFILE_GHOST);
+ int ec = 0;
+ int rc;
+
+ if (qva->qva_flags & VERIFY_DEPS) {
+ if ((rc = verifyDependencies(ts, h)) != 0)
+ ec = rc;
+ }
+ if (qva->qva_flags & VERIFY_FILES) {
+ if ((rc = verifyHeader(ts, h, omitMask, ghosts)) != 0)
+ ec = rc;
+ }
+ if (qva->qva_flags & VERIFY_SCRIPT) {
+ if ((rc = rpmVerifyScript(ts, h)) != 0)
+ ec = rc;
+ }
+
+ return ec;
+}
+
+int rpmcliVerify(rpmts ts, QVA_t qva, char * const * argv)
+{
+ rpmVSFlags vsflags, ovsflags;
+ int ec = 0;
+ FD_t scriptFd = fdDup(STDOUT_FILENO);
+
+ /*
+ * Open the DB + indices explicitly before possible chroot,
+ * otherwises BDB is going to be unhappy...
+ */
+ rpmtsOpenDB(ts, O_RDONLY);
+ rpmdbOpenAll(rpmtsGetRdb(ts));
+ if (rpmChrootSet(rpmtsRootDir(ts)) || rpmChrootIn()) {
+ ec = 1;
+ goto exit;
+ }
+
+ if (qva->qva_showPackage == NULL)
+ qva->qva_showPackage = showVerifyPackage;
+
+ vsflags = rpmExpandNumeric("%{?_vsflags_verify}");
+ if (rpmcliQueryFlags & VERIFY_DIGEST)
+ vsflags |= _RPMVSF_NODIGESTS;
+ if (rpmcliQueryFlags & VERIFY_SIGNATURE)
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ if (rpmcliQueryFlags & VERIFY_HDRCHK)
+ vsflags |= RPMVSF_NOHDRCHK;
+ vsflags &= ~RPMVSF_NEEDPAYLOAD;
+
+ rpmtsSetScriptFd(ts, scriptFd);
+ ovsflags = rpmtsSetVSFlags(ts, vsflags);
+ ec = rpmcliArgIter(ts, qva, argv);
+ vsflags = rpmtsSetVSFlags(ts, ovsflags);
+ rpmtsSetScriptFd(ts, NULL);
+
+ if (qva->qva_showPackage == showVerifyPackage)
+ qva->qva_showPackage = NULL;
+
+ rpmtsEmpty(ts);
+
+ if (rpmChrootOut() || rpmChrootSet(NULL))
+ ec = 1;
+
+exit:
+ Fclose(scriptFd);
+
+ return ec;
+}