diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 173 | ||||
-rw-r--r-- | lib/Makefile.in | 1052 | ||||
-rw-r--r-- | lib/backend/db3.c | 617 | ||||
-rw-r--r-- | lib/backend/dbconfig.c | 268 | ||||
-rw-r--r-- | lib/backend/dbi.h | 270 | ||||
-rw-r--r-- | lib/cpio.c | 245 | ||||
-rw-r--r-- | lib/cpio.h | 133 | ||||
-rw-r--r-- | lib/depends.c | 596 | ||||
-rw-r--r-- | lib/formats.c | 705 | ||||
-rw-r--r-- | lib/fprint.c | 365 | ||||
-rw-r--r-- | lib/fprint.h | 170 | ||||
-rw-r--r-- | lib/fsm.c | 2332 | ||||
-rw-r--r-- | lib/fsm.h | 227 | ||||
-rwxr-xr-x | lib/gentagtbl.sh | 80 | ||||
-rw-r--r-- | lib/header.c | 1761 | ||||
-rw-r--r-- | lib/header.h | 446 | ||||
-rw-r--r-- | lib/header_internal.h | 78 | ||||
-rw-r--r-- | lib/headerfmt.c | 868 | ||||
-rw-r--r-- | lib/headerutil.c | 245 | ||||
-rw-r--r-- | lib/legacy.c | 379 | ||||
-rw-r--r-- | lib/manifest.c | 175 | ||||
-rw-r--r-- | lib/manifest.h | 34 | ||||
-rw-r--r-- | lib/merge.c | 347 | ||||
-rw-r--r-- | lib/misc.c | 24 | ||||
-rw-r--r-- | lib/misc.h | 46 | ||||
-rw-r--r-- | lib/order.c | 655 | ||||
-rw-r--r-- | lib/package.c | 807 | ||||
-rw-r--r-- | lib/poptALL.c | 316 | ||||
-rw-r--r-- | lib/poptI.c | 252 | ||||
-rw-r--r-- | lib/poptQV.c | 246 | ||||
-rw-r--r-- | lib/psm.c | 1095 | ||||
-rw-r--r-- | lib/query.c | 575 | ||||
-rw-r--r-- | lib/rpmal.c | 406 | ||||
-rw-r--r-- | lib/rpmal.h | 75 | ||||
-rw-r--r-- | lib/rpmcallback.h | 47 | ||||
-rw-r--r-- | lib/rpmchecksig.c | 469 | ||||
-rw-r--r-- | lib/rpmchroot.c | 105 | ||||
-rw-r--r-- | lib/rpmchroot.h | 45 | ||||
-rw-r--r-- | lib/rpmcli.h | 401 | ||||
-rw-r--r-- | lib/rpmdb.c | 2956 | ||||
-rw-r--r-- | lib/rpmdb.h | 215 | ||||
-rw-r--r-- | lib/rpmdb_internal.h | 155 | ||||
-rw-r--r-- | lib/rpmds.c | 933 | ||||
-rw-r--r-- | lib/rpmds.h | 333 | ||||
-rw-r--r-- | lib/rpmfi.c | 1349 | ||||
-rw-r--r-- | lib/rpmfi.h | 459 | ||||
-rw-r--r-- | lib/rpmfi_internal.h | 172 | ||||
-rw-r--r-- | lib/rpmfs.c | 120 | ||||
-rw-r--r-- | lib/rpmfs.h | 63 | ||||
-rw-r--r-- | lib/rpmgi.c | 241 | ||||
-rw-r--r-- | lib/rpmgi.h | 65 | ||||
-rw-r--r-- | lib/rpmhash.C | 272 | ||||
-rw-r--r-- | lib/rpmhash.H | 145 | ||||
-rw-r--r-- | lib/rpminstall.c | 735 | ||||
-rw-r--r-- | lib/rpmlead.c | 141 | ||||
-rw-r--r-- | lib/rpmlead.h | 82 | ||||
-rw-r--r-- | lib/rpmlegacy.h | 241 | ||||
-rw-r--r-- | lib/rpmlib.h | 210 | ||||
-rw-r--r-- | lib/rpmliblua.c | 45 | ||||
-rw-r--r-- | lib/rpmliblua.h | 18 | ||||
-rw-r--r-- | lib/rpmlock.c | 125 | ||||
-rw-r--r-- | lib/rpmlock.h | 22 | ||||
-rw-r--r-- | lib/rpmplugins.c | 198 | ||||
-rw-r--r-- | lib/rpmplugins.h | 125 | ||||
-rw-r--r-- | lib/rpmpol.h | 28 | ||||
-rw-r--r-- | lib/rpmprob.c | 213 | ||||
-rw-r--r-- | lib/rpmprob.h | 148 | ||||
-rw-r--r-- | lib/rpmps.c | 172 | ||||
-rw-r--r-- | lib/rpmps.h | 110 | ||||
-rw-r--r-- | lib/rpmrc.c | 1710 | ||||
-rw-r--r-- | lib/rpmscript.c | 415 | ||||
-rw-r--r-- | lib/rpmscript.h | 42 | ||||
-rw-r--r-- | lib/rpmtag.h | 472 | ||||
-rw-r--r-- | lib/rpmtd.c | 443 | ||||
-rw-r--r-- | lib/rpmtd.h | 355 | ||||
-rw-r--r-- | lib/rpmte.c | 927 | ||||
-rw-r--r-- | lib/rpmte.h | 262 | ||||
-rw-r--r-- | lib/rpmte_internal.h | 154 | ||||
-rw-r--r-- | lib/rpmts.c | 1036 | ||||
-rw-r--r-- | lib/rpmts.h | 582 | ||||
-rw-r--r-- | lib/rpmts_internal.h | 108 | ||||
-rw-r--r-- | lib/rpmtypes.h | 112 | ||||
-rw-r--r-- | lib/rpmug.c | 202 | ||||
-rw-r--r-- | lib/rpmug.h | 18 | ||||
-rw-r--r-- | lib/rpmvercmp.c | 131 | ||||
-rw-r--r-- | lib/rpmvf.h | 103 | ||||
-rw-r--r-- | lib/signature.c | 534 | ||||
-rw-r--r-- | lib/signature.h | 79 | ||||
-rw-r--r-- | lib/tagexts.c | 705 | ||||
-rw-r--r-- | lib/tagname.c | 318 | ||||
-rw-r--r-- | lib/tagtbl.C | 186 | ||||
-rw-r--r-- | lib/transaction.c | 1471 | ||||
-rw-r--r-- | lib/verify.c | 516 |
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, "<"); break; + case '>': rstrcat(&new_s, ">"); break; + case '&': rstrcat(&new_s, "&"); 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, ¤t_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, ¯ofiles, 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; +} |