a521f4
From 55de6f0be10d96b56de214b677ed185bf12d15ee Mon Sep 17 00:00:00 2001
2f13d7
From: Matthew Almond <malmond@fb.com>
2f13d7
Date: Fri, 8 Nov 2019 09:29:43 -0800
a521f4
Subject: [PATCH] RPM with Copy on Write
2f13d7
2f13d7
This is part of https://fedoraproject.org/wiki/Changes/RPMCoW
2f13d7
2f13d7
The majority of changes are in two new programs:
2f13d7
2f13d7
= rpm2extents
2f13d7
2f13d7
Modeled as a 'stream processor'. It reads a regular .rpm file on stdin,
2f13d7
and produces a modified .rpm file on stdout. The lead, signature and
2f13d7
headers are preserved 1:1 to allow all the normal metadata inspection,
2f13d7
signature verification to work as expected. Only the 'payload' is
2f13d7
modified.
2f13d7
2f13d7
The primary motivation for this tool is to re-organize the payload as a
2f13d7
sequence of raw file extents (hence the name). The files are organized
2f13d7
by their digest identity instead of path/filename. If any digest is
2f13d7
repeated, then the file is skipped/de-duped. Only regular files are
2f13d7
represented. All other entries like directories, symlinks, devices are
2f13d7
fully described in the headers and are omitted.
2f13d7
2f13d7
The files are padded so they start on `sysconf(_SC_PAGESIZE)` boundries
2f13d7
to permit 'reflink' syscalls to work in the `reflink` plugin.
2f13d7
2f13d7
At the end of the file is a footer with 3 sections:
2f13d7
2f13d7
1. List of calculated digests of the input stream. This is used in
2f13d7
   `librepo` because the file *written* is a derivative, and not the
2f13d7
   same as the repo metadata describes. `rpm2extents` takes one or more
2f13d7
   positional arguments that described which digest algorithms are
2f13d7
   desired. This is often just `SHA256`. This program is only measuring
2f13d7
   and recording the digest - it does not express an opinion on whether
2f13d7
   the file is correct. Due to the API on most compression libraries
2f13d7
   directly reading the source file, the whole file digest is measured
2f13d7
   using a subprocess and pipes. I don't love it, but it works.
2f13d7
2. Sorted List of file content digests + offset pairs. This is used in
2f13d7
   the plugin with a trivial binary search to locate the start of file
2f13d7
   content. The size is not needed because it's part of normal headers.
2f13d7
3. (offset of 1., offset of 2., 8 byte MAGIC value) triple
2f13d7
2f13d7
= reflink plugin
2f13d7
2f13d7
Looks for the 8 byte magic value at the end of the rpm file. If present
2f13d7
it alters the `RPMTAG_PAYLOADFORMAT` in memory to `clon`, and reads in
2f13d7
the digest-> offset table.
2f13d7
2f13d7
`rpmPackageFilesInstall()` in `fsm.c` is
2f13d7
modified to alter the enumeration strategy from
2f13d7
`rpmfiNewArchiveReader()` to `rpmfilesIter()` if not `cpio`. This is
2f13d7
needed because there is no cpio to enumerate. In the same function, if
2f13d7
`rpmpluginsCallFsmFilePre()` returns `RPMRC_PLUGIN_CONTENTS` then
2f13d7
`fsmMkfile()` is skipped as it is assumed the plugin did the work.
2f13d7
2f13d7
The majority of the work is in `reflink_fsm_file_pre()` - the per file
2f13d7
hook for RPM plugins. If the file enumerated in
2f13d7
`rpmPackageFilesInstall()` is a regular file, this function will look up
2f13d7
the offset in the digest->offset table and will try to reflink it, then
2f13d7
fall back to a regular copy. If reflinking does work: we will have
2f13d7
reflinked a whole number of pages, so we truncate the file to the
2f13d7
expected size. Therefore installing most files does involve two writes:
2f13d7
the reflink of the full size, then a fork/copy on write for the last
2f13d7
page worth.
2f13d7
2f13d7
If the file passed to `reflink_fsm_file_pre()` is anything other than a
2f13d7
regular file, it return `RPMRC_OK` so the normal mechanics of
2f13d7
`rpmPackageFilesInstall()` are used. That handles directories, symlinks
2f13d7
and other non file types.
2f13d7
2f13d7
= New API for internal use
2f13d7
2f13d7
1. `rpmReadPackageRaw()` is used within `rpm2extents` to read all the
2f13d7
   headers without trying to validate signatures. This eliminates the
2f13d7
   runtime dependency on rpmdb.
2f13d7
2. `rpmteFd()` exposes the Fd behind the rpmte, so plugins can interact
2f13d7
   with the rpm itself.
2f13d7
3. `RPMRC_PLUGIN_CONTENTS` in `rpmRC_e` for use in
2f13d7
   `rpmpluginsCallFsmFilePre()` specifically.
2f13d7
4. `pgpStringVal()` is used to help parse the command line in
2f13d7
   `rpm2extents` - the positional arguments are strings, and this
2f13d7
   converts the values back to the values in the table.
2f13d7
2f13d7
Nothing has been removed, and none of the changes are intended to be
2f13d7
used externally, so I don't think a soname bump is warranted here.
2f13d7
---
a521f4
 Makefile.am               |   8 +-
a521f4
 build/pack.c              |   2 +-
a521f4
 lib/Makefile.am           |   3 +-
a521f4
 lib/Makefile.in           |  51 +--
a521f4
 lib/fsm.c                 |  45 ++-
a521f4
 lib/package.c             |  36 ++
a521f4
 lib/rpmchecksig.c         | 117 +++++--
a521f4
 lib/rpmcli.h              |  10 +
a521f4
 lib/rpmextents.c          | 110 ++++++
a521f4
 lib/rpmextents_internal.h |  58 ++++
a521f4
 lib/rpmlead.c             |  43 ++-
a521f4
 lib/rpmlead.h             |  37 +-
a521f4
 lib/rpmlib.h              |   9 +
a521f4
 lib/rpmplugin.h           |   9 +
a521f4
 lib/rpmplugins.c          |  92 ++++-
a521f4
 lib/rpmplugins.h          |  17 +
a521f4
 lib/rpmte.c               |   5 +
a521f4
 lib/rpmte.h               |   2 +
a521f4
 lib/rpmtypes.h            |   3 +-
a521f4
 lib/transaction.c         |  29 +-
a521f4
 macros.in                 |   1 +
a521f4
 plugins/Makefile.am       |   4 +
a521f4
 plugins/reflink.c         | 401 +++++++++++++++++++++
a521f4
 rpm2extents.c             | 708 ++++++++++++++++++++++++++++++++++++++
a521f4
 rpmio/rpmpgp.c            |  10 +
a521f4
 rpmio/rpmpgp.h            |   9 +
a521f4
 scripts/rpm2extents_dump  |  94 +++++
a521f4
 sign/rpmgensig.c          |   2 +-
a521f4
 tests/Makefile.am         |   1 +
a521f4
 tests/atlocal.in          |  22 ++
a521f4
 tests/rpm2extents.at      | 151 ++++++++
a521f4
 tests/rpmtests.at         |   1 +
a521f4
 32 files changed, 1996 insertions(+), 94 deletions(-)
a521f4
 create mode 100644 lib/rpmextents.c
a521f4
 create mode 100644 lib/rpmextents_internal.h
2f13d7
 create mode 100644 plugins/reflink.c
2f13d7
 create mode 100644 rpm2extents.c
a521f4
 create mode 100755 scripts/rpm2extents_dump
a521f4
 create mode 100644 tests/rpm2extents.at
2f13d7
2f13d7
diff --git a/Makefile.am b/Makefile.am
a521f4
index 78bc131..43fbd94 100644
2f13d7
--- a/Makefile.am
2f13d7
+++ b/Makefile.am
bd9c00
@@ -106,7 +106,7 @@ pkginclude_HEADERS += build/rpmfc.h
2f13d7
 pkginclude_HEADERS += build/rpmspec.h
2f13d7
 
2f13d7
 
2f13d7
-bin_PROGRAMS =		rpm rpm2cpio rpmbuild rpmdb rpmkeys rpmsign rpmspec
2f13d7
+bin_PROGRAMS =		rpm rpm2cpio rpmbuild rpmdb rpmkeys rpmsign rpmspec rpm2extents
2f13d7
 if WITH_ARCHIVE
2f13d7
 bin_PROGRAMS += 	rpm2archive 
2f13d7
 endif
bd9c00
@@ -160,6 +160,10 @@ rpm2cpio_SOURCES =	rpm2cpio.c debug.h system.h
2f13d7
 rpm2cpio_LDADD =	lib/librpm.la rpmio/librpmio.la
2f13d7
 rpm2cpio_LDADD +=	@WITH_POPT_LIB@
2f13d7
 
2f13d7
+rpm2extents_SOURCES =	rpm2extents.c debug.h system.h
2f13d7
+rpm2extents_LDADD =	lib/librpm.la rpmio/librpmio.la
2f13d7
+rpm2extents_LDADD +=	@WITH_POPT_LIB@
2f13d7
+
2f13d7
 rpm2archive_SOURCES =	rpm2archive.c debug.h system.h
2f13d7
 rpm2archive_LDADD =	lib/librpm.la rpmio/librpmio.la
2f13d7
 rpm2archive_LDADD +=	@WITH_POPT_LIB@ @WITH_ARCHIVE_LIB@
a521f4
@@ -199,7 +203,7 @@ bin_PROGRAMS +=		rpmgraph
a521f4
 rpmgraph_SOURCES =	tools/rpmgraph.c
a521f4
 rpmgraph_LDADD =	lib/librpm.la rpmio/librpmio.la @WITH_POPT_LIB@
2f13d7
 
a521f4
-dist_bin_SCRIPTS =	scripts/gendiff
a521f4
+dist_bin_SCRIPTS =	scripts/gendiff scripts/rpm2extents_dump
a521f4
 
a521f4
 rpmconfig_DATA = rpmrc
a521f4
 rpmrc: $(top_srcdir)/rpmrc.in
a521f4
diff --git a/build/pack.c b/build/pack.c
a521f4
index 8d6f749..e2f05b6 100644
a521f4
--- a/build/pack.c
a521f4
+++ b/build/pack.c
a521f4
@@ -493,7 +493,7 @@ static rpmRC writeRPM(Package pkg, unsigned char ** pkgidp,
a521f4
     }
a521f4
 
a521f4
     /* Write the lead section into the package. */
a521f4
-    if (rpmLeadWrite(fd, pkg->header)) {
a521f4
+    if (rpmLeadWriteFromHeader(fd, pkg->header)) {
a521f4
 	rpmlog(RPMLOG_ERR, _("Unable to write package: %s\n"), Fstrerror(fd));
a521f4
 	goto exit;
a521f4
     }
a521f4
diff --git a/lib/Makefile.am b/lib/Makefile.am
a521f4
index c561ad5..4a83e9a 100644
a521f4
--- a/lib/Makefile.am
a521f4
+++ b/lib/Makefile.am
a521f4
@@ -41,7 +41,8 @@ librpm_la_SOURCES = \
a521f4
 	rpmscript.h rpmscript.c \
a521f4
 	rpmchroot.c rpmchroot.h \
a521f4
 	rpmplugins.c rpmplugins.h rpmplugin.h rpmug.c rpmug.h \
a521f4
-	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h
a521f4
+	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h \
a521f4
+	rpmextents.c rpmextents_internal.h
a521f4
 
a521f4
 librpm_la_LDFLAGS = -version-info $(rpm_version_info)
a521f4
 
a521f4
diff --git a/lib/Makefile.in b/lib/Makefile.in
a521f4
index 1aafaac..bf911fa 100644
a521f4
--- a/lib/Makefile.in
a521f4
+++ b/lib/Makefile.in
a521f4
@@ -173,10 +173,11 @@ am__librpm_la_SOURCES_DIST = backend/dbi.c backend/dbi.h \
a521f4
 	rpmlock.c rpmlock.h misc.h relocation.c rpmscript.h \
a521f4
 	rpmscript.c rpmchroot.c rpmchroot.h rpmplugins.c rpmplugins.h \
a521f4
 	rpmplugin.h rpmug.c rpmug.h rpmtriggers.h rpmtriggers.c \
a521f4
-	rpmvs.c rpmvs.h backend/db3.c backend/bdb_ro.c \
a521f4
-	backend/ndb/glue.c backend/ndb/rpmpkg.c backend/ndb/rpmpkg.h \
a521f4
-	backend/ndb/rpmidx.c backend/ndb/rpmidx.h backend/ndb/rpmxdb.c \
a521f4
-	backend/ndb/rpmxdb.h backend/sqlite.c
a521f4
+	rpmvs.c rpmvs.h rpmextents.c rpmextents_internal.h \
a521f4
+	backend/db3.c backend/bdb_ro.c backend/ndb/glue.c \
a521f4
+	backend/ndb/rpmpkg.c backend/ndb/rpmpkg.h backend/ndb/rpmidx.c \
a521f4
+	backend/ndb/rpmidx.h backend/ndb/rpmxdb.c backend/ndb/rpmxdb.h \
a521f4
+	backend/sqlite.c
a521f4
 am__dirstamp = $(am__leading_dot)dirstamp
a521f4
 @BDB_TRUE@am__objects_1 = backend/db3.lo
a521f4
 @BDB_RO_TRUE@am__objects_2 = backend/bdb_ro.lo
a521f4
@@ -192,8 +193,8 @@ am_librpm_la_OBJECTS = backend/dbi.lo backend/dummydb.lo \
a521f4
 	rpmlead.lo rpmps.lo rpmprob.lo rpmrc.lo rpmte.lo rpmts.lo \
a521f4
 	rpmfs.lo signature.lo transaction.lo verify.lo rpmlock.lo \
a521f4
 	relocation.lo rpmscript.lo rpmchroot.lo rpmplugins.lo rpmug.lo \
a521f4
-	rpmtriggers.lo rpmvs.lo $(am__objects_1) $(am__objects_2) \
a521f4
-	$(am__objects_3) $(am__objects_4)
a521f4
+	rpmtriggers.lo rpmvs.lo rpmextents.lo $(am__objects_1) \
a521f4
+	$(am__objects_2) $(am__objects_3) $(am__objects_4)
a521f4
 librpm_la_OBJECTS = $(am_librpm_la_OBJECTS)
a521f4
 AM_V_lt = $(am__v_lt_@AM_V@)
a521f4
 am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
a521f4
@@ -228,21 +229,21 @@ am__depfiles_remade = ./$(DEPDIR)/cpio.Plo ./$(DEPDIR)/depends.Plo \
a521f4
 	./$(DEPDIR)/relocation.Plo ./$(DEPDIR)/rpmal.Plo \
a521f4
 	./$(DEPDIR)/rpmchecksig.Plo ./$(DEPDIR)/rpmchroot.Plo \
a521f4
 	./$(DEPDIR)/rpmdb.Plo ./$(DEPDIR)/rpmds.Plo \
a521f4
-	./$(DEPDIR)/rpmfi.Plo ./$(DEPDIR)/rpmfs.Plo \
a521f4
-	./$(DEPDIR)/rpmgi.Plo ./$(DEPDIR)/rpminstall.Plo \
a521f4
-	./$(DEPDIR)/rpmlead.Plo ./$(DEPDIR)/rpmlock.Plo \
a521f4
-	./$(DEPDIR)/rpmplugins.Plo ./$(DEPDIR)/rpmprob.Plo \
a521f4
-	./$(DEPDIR)/rpmps.Plo ./$(DEPDIR)/rpmrc.Plo \
a521f4
-	./$(DEPDIR)/rpmscript.Plo ./$(DEPDIR)/rpmtd.Plo \
a521f4
-	./$(DEPDIR)/rpmte.Plo ./$(DEPDIR)/rpmtriggers.Plo \
a521f4
-	./$(DEPDIR)/rpmts.Plo ./$(DEPDIR)/rpmug.Plo \
a521f4
-	./$(DEPDIR)/rpmvs.Plo ./$(DEPDIR)/signature.Plo \
a521f4
-	./$(DEPDIR)/tagexts.Plo ./$(DEPDIR)/tagname.Plo \
a521f4
-	./$(DEPDIR)/transaction.Plo ./$(DEPDIR)/verify.Plo \
a521f4
-	backend/$(DEPDIR)/bdb_ro.Plo backend/$(DEPDIR)/db3.Plo \
a521f4
-	backend/$(DEPDIR)/dbi.Plo backend/$(DEPDIR)/dbiset.Plo \
a521f4
-	backend/$(DEPDIR)/dummydb.Plo backend/$(DEPDIR)/sqlite.Plo \
a521f4
-	backend/ndb/$(DEPDIR)/glue.Plo \
a521f4
+	./$(DEPDIR)/rpmextents.Plo ./$(DEPDIR)/rpmfi.Plo \
a521f4
+	./$(DEPDIR)/rpmfs.Plo ./$(DEPDIR)/rpmgi.Plo \
a521f4
+	./$(DEPDIR)/rpminstall.Plo ./$(DEPDIR)/rpmlead.Plo \
a521f4
+	./$(DEPDIR)/rpmlock.Plo ./$(DEPDIR)/rpmplugins.Plo \
a521f4
+	./$(DEPDIR)/rpmprob.Plo ./$(DEPDIR)/rpmps.Plo \
a521f4
+	./$(DEPDIR)/rpmrc.Plo ./$(DEPDIR)/rpmscript.Plo \
a521f4
+	./$(DEPDIR)/rpmtd.Plo ./$(DEPDIR)/rpmte.Plo \
a521f4
+	./$(DEPDIR)/rpmtriggers.Plo ./$(DEPDIR)/rpmts.Plo \
a521f4
+	./$(DEPDIR)/rpmug.Plo ./$(DEPDIR)/rpmvs.Plo \
a521f4
+	./$(DEPDIR)/signature.Plo ./$(DEPDIR)/tagexts.Plo \
a521f4
+	./$(DEPDIR)/tagname.Plo ./$(DEPDIR)/transaction.Plo \
a521f4
+	./$(DEPDIR)/verify.Plo backend/$(DEPDIR)/bdb_ro.Plo \
a521f4
+	backend/$(DEPDIR)/db3.Plo backend/$(DEPDIR)/dbi.Plo \
a521f4
+	backend/$(DEPDIR)/dbiset.Plo backend/$(DEPDIR)/dummydb.Plo \
a521f4
+	backend/$(DEPDIR)/sqlite.Plo backend/ndb/$(DEPDIR)/glue.Plo \
a521f4
 	backend/ndb/$(DEPDIR)/rpmidx.Plo \
a521f4
 	backend/ndb/$(DEPDIR)/rpmpkg.Plo \
a521f4
 	backend/ndb/$(DEPDIR)/rpmxdb.Plo
a521f4
@@ -586,8 +587,9 @@ librpm_la_SOURCES = backend/dbi.c backend/dbi.h backend/dummydb.c \
a521f4
 	signature.h transaction.c verify.c rpmlock.c rpmlock.h misc.h \
a521f4
 	relocation.c rpmscript.h rpmscript.c rpmchroot.c rpmchroot.h \
a521f4
 	rpmplugins.c rpmplugins.h rpmplugin.h rpmug.c rpmug.h \
a521f4
-	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h $(am__append_1) \
a521f4
-	$(am__append_4) $(am__append_5) $(am__append_8)
a521f4
+	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h rpmextents.c \
a521f4
+	rpmextents_internal.h $(am__append_1) $(am__append_4) \
a521f4
+	$(am__append_5) $(am__append_8)
a521f4
 librpm_la_LDFLAGS = -version-info $(rpm_version_info)
a521f4
 librpm_la_LIBADD = $(top_builddir)/rpmio/librpmio.la @WITH_POPT_LIB@ \
a521f4
 	@WITH_CAP_LIB@ @WITH_ACL_LIB@ @LIBINTL@ $(am__append_2) \
a521f4
@@ -748,6 +750,7 @@ distclean-compile:
a521f4
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmchroot.Plo@am__quote@ # am--include-marker
a521f4
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmdb.Plo@am__quote@ # am--include-marker
a521f4
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmds.Plo@am__quote@ # am--include-marker
a521f4
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmextents.Plo@am__quote@ # am--include-marker
a521f4
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmfi.Plo@am__quote@ # am--include-marker
a521f4
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmfs.Plo@am__quote@ # am--include-marker
a521f4
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpmgi.Plo@am__quote@ # am--include-marker
a521f4
@@ -978,6 +981,7 @@ distclean: distclean-am
a521f4
 	-rm -f ./$(DEPDIR)/rpmchroot.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmdb.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmds.Plo
a521f4
+	-rm -f ./$(DEPDIR)/rpmextents.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmfi.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmfs.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmgi.Plo
a521f4
@@ -1077,6 +1081,7 @@ maintainer-clean: maintainer-clean-am
a521f4
 	-rm -f ./$(DEPDIR)/rpmchroot.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmdb.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmds.Plo
a521f4
+	-rm -f ./$(DEPDIR)/rpmextents.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmfi.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmfs.Plo
a521f4
 	-rm -f ./$(DEPDIR)/rpmgi.Plo
2f13d7
diff --git a/lib/fsm.c b/lib/fsm.c
a521f4
index 9dd50b7..e562220 100644
2f13d7
--- a/lib/fsm.c
2f13d7
+++ b/lib/fsm.c
a521f4
@@ -868,6 +868,24 @@ static rpmfi fsmIterFini(rpmfi fi, struct diriter_s *di)
a521f4
     return rpmfiFree(fi);
a521f4
 }
2f13d7
 
a521f4
+static int fiIterator(rpmPlugins plugins, FD_t payload, rpmfiles files, rpmfi *fi)
a521f4
+{
a521f4
+    rpmRC plugin_rc = rpmpluginsCallFsmFileArchiveReader(plugins, payload, files, fi);
a521f4
+    switch (plugin_rc) {
2f13d7
+	case RPMRC_PLUGIN_CONTENTS:
a521f4
+	    if (*fi == NULL)
a521f4
+                return RPMERR_BAD_MAGIC;
a521f4
+            return RPMRC_OK;
a521f4
+	case RPMRC_OK:
a521f4
+	    *fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
a521f4
+	    if (*fi == NULL)
a521f4
+                return RPMERR_BAD_MAGIC;
a521f4
+            return RPMRC_OK;
bd9c00
+	default:
a521f4
+            return RPMRC_FAIL;
a521f4
+    }
a521f4
+}
a521f4
+
a521f4
 int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
a521f4
               rpmpsm psm, char ** failedFile)
a521f4
 {
a521f4
@@ -919,8 +937,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
2f13d7
     if (rc)
2f13d7
 	goto exit;
2f13d7
 
bd9c00
-    fi = fsmIter(payload, files,
bd9c00
-		 payload ? RPMFI_ITER_READ_ARCHIVE : RPMFI_ITER_FWD, &di);
a521f4
+    rc = fiIterator(plugins, payload, files, &fi);
a521f4
+    if (rc)
a521f4
+        goto exit;
bd9c00
 
bd9c00
     if (fi == NULL) {
bd9c00
         rc = RPMERR_BAD_MAGIC;
a521f4
@@ -943,6 +962,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
bd9c00
         if (!fp->skip) {
bd9c00
 	    int mayopen = 0;
bd9c00
 	    int fd = -1;
bd9c00
+
a521f4
+	    if (di.dirfd >= 0)
bd9c00
+		fsmClose(&di.dirfd);
bd9c00
 	    rc = ensureDir(plugins, rpmfiDN(fi), 0,
bd9c00
 			    (fp->action == FA_CREATE), 0, &di.dirfd);
2f13d7
 
a521f4
@@ -952,9 +974,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
bd9c00
 	    }
bd9c00
 
bd9c00
 	    /* Run fsm file pre hook for all plugins */
bd9c00
-	    if (!rc)
bd9c00
+	    if (!rc) {
bd9c00
 		rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
bd9c00
 					      fp->sb.st_mode, fp->action);
bd9c00
+	    }
bd9c00
 	    if (rc)
bd9c00
 		goto setmeta; /* for error notification */
bd9c00
 
a521f4
@@ -982,11 +1005,18 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
a521f4
 	    if (fp->action == FA_TOUCH)
a521f4
 		goto setmeta;
2f13d7
 
a521f4
-            if (S_ISREG(fp->sb.st_mode)) {
a521f4
+	    rpmRC plugin_rc = rpmpluginsCallFsmFileInstall(plugins, fi, fp->fpath, fp->sb.st_mode, fp->action);
a521f4
+	    if (!(plugin_rc == RPMRC_PLUGIN_CONTENTS || plugin_rc == RPMRC_OK)){
a521f4
+		rc = plugin_rc;
a521f4
+	    } else if(plugin_rc == RPMRC_PLUGIN_CONTENTS){
a521f4
+		rc = RPMRC_OK;
a521f4
+		/* The reflink plugins handles hardlink differently, metadata has to be set. */
a521f4
+		fp->setmeta = 1;
a521f4
+	    } else if (S_ISREG(fp->sb.st_mode)) {
2f13d7
 		if (rc == RPMERR_ENOENT) {
a521f4
 		    rc = fsmMkfile(di.dirfd, fi, fp, files, psm, nodigest,
bd9c00
-				   &firstlink, &firstlinkfile, &di.firstdir,
bd9c00
-				   &fd;;
a521f4
+				   &firstlink, &firstlinkfile,
a521f4
+				   &di.firstdir, &fd;;
2f13d7
 		}
2f13d7
             } else if (S_ISDIR(fp->sb.st_mode)) {
2f13d7
                 if (rc == RPMERR_ENOENT) {
a521f4
@@ -1055,10 +1085,13 @@ setmeta:
bd9c00
 
bd9c00
     /* If all went well, commit files to final destination */
a521f4
     fi = fsmIter(NULL, files, RPMFI_ITER_FWD, &di);
a521f4
+
bd9c00
     while (!rc && (fx = rpmfiNext(fi)) >= 0) {
bd9c00
 	struct filedata_s *fp = &fdata[fx];
bd9c00
 
bd9c00
 	if (!fp->skip) {
a521f4
+	    if (di.dirfd >= 0)
bd9c00
+		fsmClose(&di.dirfd);
bd9c00
 	    if (!rc)
bd9c00
 		rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd);
2f13d7
 
2f13d7
diff --git a/lib/package.c b/lib/package.c
a521f4
index 8c2b66b..fb7ec7b 100644
2f13d7
--- a/lib/package.c
2f13d7
+++ b/lib/package.c
a521f4
@@ -402,5 +402,41 @@ exit:
2f13d7
     return rc;
2f13d7
 }
2f13d7
 
2f13d7
+rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp)
2f13d7
+{
2f13d7
+    char *msg = NULL;
2f13d7
+    hdrblob sigblob = hdrblobCreate();
2f13d7
+    hdrblob blob = hdrblobCreate();
2f13d7
+    Header h = NULL;
2f13d7
+    Header sigh = NULL;
2f13d7
+
a521f4
+    rpmRC rc = hdrblobRead(fd, 1, 0, RPMTAG_HEADERSIGNATURES, sigblob, &msg;;
2f13d7
+    if (rc != RPMRC_OK)
2f13d7
+	goto exit;
2f13d7
+
2f13d7
+    rc = hdrblobRead(fd, 1, 1, RPMTAG_HEADERIMMUTABLE, blob, &msg;;
2f13d7
+    if (rc != RPMRC_OK)
2f13d7
+	goto exit;
2f13d7
+
2f13d7
+    rc = hdrblobImport(sigblob, 0, &sigh, &msg;;
2f13d7
+    if (rc)
2f13d7
+	goto exit;
a521f4
+
2f13d7
+    rc = hdrblobImport(blob, 0, &h, &msg;;
2f13d7
+    if (rc)
2f13d7
+	goto exit;
a521f4
+
2f13d7
+    *sigp = headerLink(sigh);
2f13d7
+    *hdrp = headerLink(h);
a521f4
 
2f13d7
+exit:
2f13d7
+    if (rc != RPMRC_OK && msg)
2f13d7
+	rpmlog(RPMLOG_ERR, "%s: %s\n", Fdescr(fd), msg);
2f13d7
+    hdrblobFree(sigblob);
2f13d7
+    hdrblobFree(blob);
2f13d7
+    headerFree(sigh);
2f13d7
+    headerFree(h);
2f13d7
+    free(msg);
a521f4
 
a521f4
+    return rc;
a521f4
+}
a521f4
diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c
a521f4
index 40a3ab8..7f85615 100644
a521f4
--- a/lib/rpmchecksig.c
a521f4
+++ b/lib/rpmchecksig.c
a521f4
@@ -20,6 +20,7 @@
a521f4
 #include "rpmio/rpmio_internal.h" 	/* fdSetBundle() */
a521f4
 #include "lib/rpmlead.h"
a521f4
 #include "lib/header_internal.h"
a521f4
+#include "lib/rpmextents_internal.h"
a521f4
 #include "lib/rpmvs.h"
a521f4
 
a521f4
 #include "debug.h"
a521f4
@@ -221,36 +222,24 @@ exit:
a521f4
 }
a521f4
 
a521f4
 static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags,
a521f4
-			   FD_t fd, const char *fn)
a521f4
+			   FD_t fd, rpmsinfoCb cb, void *cbdata)
a521f4
 {
a521f4
     char *msg = NULL;
a521f4
-    struct vfydata_s vd = { .seen = 0,
a521f4
-			    .bad = 0,
a521f4
-			    .verbose = rpmIsVerbose(),
a521f4
-    };
a521f4
     int rc;
a521f4
-    struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring);
a521f4
 
a521f4
-    rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd.verbose ? "\n" : "");
a521f4
+
a521f4
+    if(isTranscodedRpm(fd) == RPMRC_OK){
a521f4
+	return extentsVerifySigs(fd, 1);
a521f4
+    }
a521f4
+
a521f4
+    struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring);
a521f4
 
a521f4
     rc = rpmpkgRead(vs, fd, NULL, NULL, &msg;;
a521f4
 
a521f4
     if (rc)
a521f4
 	goto exit;
a521f4
 
a521f4
-    rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
a521f4
-
a521f4
-    if (!vd.verbose) {
a521f4
-	if (vd.seen & RPMSIG_DIGEST_TYPE) {
a521f4
-	    rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_DIGEST_TYPE) ?
a521f4
-					_("DIGESTS") : _("digests"));
a521f4
-	}
a521f4
-	if (vd.seen & RPMSIG_SIGNATURE_TYPE) {
a521f4
-	    rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_SIGNATURE_TYPE) ?
a521f4
-					_("SIGNATURES") : _("signatures"));
a521f4
-	}
a521f4
-	rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK"));
a521f4
-    }
a521f4
+    rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, cb, cbdata);
a521f4
 
a521f4
 exit:
a521f4
     if (rc && msg)
a521f4
@@ -260,15 +249,39 @@ exit:
a521f4
     return rc;
a521f4
 }
a521f4
 
a521f4
+static void rpmkgVerifySigsPreLogging(struct vfydata_s *vd, const char *fn){
a521f4
+    rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd->verbose ? "\n" : "");
a521f4
+}
a521f4
+
a521f4
+static void rpmkgVerifySigsPostLogging(struct vfydata_s *vd, int rc){
a521f4
+    if (!vd->verbose) {
a521f4
+	if (vd->seen & RPMSIG_DIGEST_TYPE) {
a521f4
+	    rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_DIGEST_TYPE) ?
a521f4
+					_("DIGESTS") : _("digests"));
a521f4
+	}
a521f4
+	if (vd->seen & RPMSIG_SIGNATURE_TYPE) {
a521f4
+	    rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_SIGNATURE_TYPE) ?
a521f4
+					_("SIGNATURES") : _("signatures"));
a521f4
+	}
a521f4
+	rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK"));
a521f4
+    }
a521f4
+}
a521f4
+
a521f4
 /* Wrapper around rpmkVerifySigs to preserve API */
a521f4
 int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn)
a521f4
 {
a521f4
     int rc = 1; /* assume failure */
a521f4
+    struct vfydata_s vd = { .seen = 0,
a521f4
+			    .bad = 0,
a521f4
+			    .verbose = rpmIsVerbose(),
a521f4
+    };
a521f4
     if (ts && qva && fd && fn) {
a521f4
 	rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
a521f4
 	rpmVSFlags vsflags = rpmtsVfyFlags(ts);
a521f4
 	int vfylevel = rpmtsVfyLevel(ts);
a521f4
-	rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, fn);
a521f4
+	rpmkgVerifySigsPreLogging(&vd, fn);
a521f4
+	rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, vfyCb, &vd);
a521f4
+	rpmkgVerifySigsPostLogging(&vd, rc);
a521f4
     	rpmKeyringFree(keyring);
a521f4
     }
a521f4
     return rc;
a521f4
@@ -290,12 +303,22 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
a521f4
 
a521f4
     while ((arg = *argv++) != NULL) {
a521f4
 	FD_t fd = Fopen(arg, "r.ufdio");
a521f4
+	struct vfydata_s vd = { .seen = 0,
a521f4
+				.bad = 0,
a521f4
+				.verbose = rpmIsVerbose(),
a521f4
+	};
a521f4
 	if (fd == NULL || Ferror(fd)) {
a521f4
 	    rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"), 
a521f4
 		     arg, Fstrerror(fd));
a521f4
 	    res++;
a521f4
-	} else if (rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, arg)) {
a521f4
+	} else {
a521f4
+	    rpmkgVerifySigsPreLogging(&vd, arg);
a521f4
+	    int rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd,
a521f4
+				      vfyCb, &vd);
a521f4
+	    rpmkgVerifySigsPostLogging(&vd, rc);
a521f4
+	    if (rc) {
a521f4
 	    res++;
a521f4
+	    }
a521f4
 	}
a521f4
 
a521f4
 	Fclose(fd);
a521f4
@@ -304,3 +327,53 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
a521f4
     rpmKeyringFree(keyring);
a521f4
     return res;
a521f4
 }
a521f4
+
a521f4
+struct vfydatafd_s {
a521f4
+    size_t len;
a521f4
+    char msg[BUFSIZ];
a521f4
+};
a521f4
+
a521f4
+
a521f4
+static int vfyFDCb(struct rpmsinfo_s *sinfo, void *cbdata)
a521f4
+{
a521f4
+    struct vfydatafd_s *vd = cbdata;
a521f4
+    char *vmsg, *msg;
a521f4
+    size_t n;
a521f4
+    size_t remainder = BUFSIZ - vd->len >= 0 ? BUFSIZ - vd->len : 0;
a521f4
+
a521f4
+    vmsg = rpmsinfoMsg(sinfo);
a521f4
+    rasprintf(&msg, "    %s\n", vmsg);
a521f4
+    n = rstrlcpy(vd->msg + vd->len, msg, remainder);
a521f4
+    free(vmsg);
a521f4
+    free(msg);
a521f4
+    if(n <= remainder){
a521f4
+	vd->len += n;
a521f4
+    }
a521f4
+    return 1;
a521f4
+}
a521f4
+
2f13d7
+
a521f4
+int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi, char **msg)
a521f4
+{
a521f4
+    rpmRC rc = RPMRC_FAIL;
a521f4
+    rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
a521f4
+    rpmVSFlags vsflags = rpmtsVfyFlags(ts);
a521f4
+    int vfylevel = rpmtsVfyLevel(ts);
a521f4
+    struct vfydatafd_s vd = {.len = 0};
a521f4
+
a521f4
+    vsflags |= rpmcliVSFlags;
a521f4
+    if (rpmcliVfyLevelMask) {
a521f4
+	vfylevel &= ~rpmcliVfyLevelMask;
a521f4
+	rpmtsSetVfyLevel(ts, vfylevel);
a521f4
+    }
a521f4
+
a521f4
+    if (!rpmpkgVerifySigs(keyring, vfylevel, vsflags, fdi, vfyFDCb, &vd)) {
a521f4
+	rc = RPMRC_OK;
a521f4
+    }
a521f4
+    *msg = strdup(vd.msg);
a521f4
+    rpmsqPoll();
a521f4
+
a521f4
+    rpmKeyringFree(keyring);
2f13d7
+    return rc;
2f13d7
+}
a521f4
+
a521f4
diff --git a/lib/rpmcli.h b/lib/rpmcli.h
a521f4
index 3961418..450f7be 100644
a521f4
--- a/lib/rpmcli.h
a521f4
+++ b/lib/rpmcli.h
a521f4
@@ -411,6 +411,16 @@ int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv);
a521f4
  */
a521f4
 int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv);
a521f4
 
a521f4
+
a521f4
+/** \ingroup rpmcli
a521f4
+ * Verify package signatures.
a521f4
+ * @param ts		transaction set
a521f4
+ * @param fd		a file descriptor to verify
a521f4
+ * @param msg		a string containing textual information about the verification, similar to rpmcliVerifySignatures output.
a521f4
+ * @return		0 on success
a521f4
+ */
a521f4
+int rpmcliVerifySignaturesFD(rpmts ts, FD_t fd, char **msg);
a521f4
+
a521f4
 #ifdef __cplusplus
a521f4
 }
a521f4
 #endif
a521f4
diff --git a/lib/rpmextents.c b/lib/rpmextents.c
a521f4
new file mode 100644
a521f4
index 0000000..ac43264
a521f4
--- /dev/null
a521f4
+++ b/lib/rpmextents.c
a521f4
@@ -0,0 +1,110 @@
a521f4
+
a521f4
+#include "system.h"
a521f4
+
a521f4
+#include <rpm/rpmlog.h>
a521f4
+#include <rpm/rpmio.h>
a521f4
+#include <string.h>
a521f4
+#include <errno.h>
a521f4
+
a521f4
+
a521f4
+#include "lib/rpmextents_internal.h"
a521f4
+
a521f4
+
a521f4
+int extentsVerifySigs(FD_t fd, int print_content){
a521f4
+    rpm_loff_t current;
a521f4
+    int32_t rc;
a521f4
+    size_t len;
a521f4
+    uint64_t content_len;
a521f4
+    char *content = NULL;
a521f4
+    struct extents_footer_t footer;
a521f4
+
a521f4
+    current = Ftell(fd);
a521f4
+
a521f4
+    if(extentsFooterFromFD(fd, &footer) != RPMRC_OK) {
a521f4
+	rc = -1;
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    if(Fseek(fd, footer.offsets.checksig_offset, SEEK_SET) < 0) {
a521f4
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to seek signature verification offset\n"));
a521f4
+	rc = -1;
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    len = sizeof(rc);
a521f4
+    if (Fread(&rc, len, 1, fd) != len) {
a521f4
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read Signature Verification RC\n"));
a521f4
+	rc = -1;
a521f4
+	goto exit;
a521f4
+    }
a521f4
+
a521f4
+    if(print_content) {
a521f4
+	len = sizeof(content_len);
a521f4
+	if (Fread(&content_len, len, 1, fd) != len) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content length\n"));
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+
a521f4
+	content = rmalloc(content_len + 1);
a521f4
+	if(content == NULL) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to allocate memory to read signature content\n"));
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+	content[content_len] = 0;
a521f4
+	if (Fread(content, content_len, 1, fd) != content_len) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content\n"));
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+
a521f4
+	rpmlog(RPMLOG_NOTICE, "%s", content);
a521f4
+    }
a521f4
+exit:
a521f4
+    if(content){
a521f4
+	rfree(content);
a521f4
+    }
a521f4
+    if (Fseek(fd, current, SEEK_SET) < 0) {
a521f4
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: unable to seek back to original location\n"));
a521f4
+    }
a521f4
+    return rc;
a521f4
+
a521f4
+}
a521f4
+
a521f4
+rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) {
a521f4
+
a521f4
+    rpmRC rc = RPMRC_NOTFOUND;
a521f4
+    rpm_loff_t current;
a521f4
+    size_t len;
a521f4
+
a521f4
+    // If the file is not seekable, we cannot detect whether or not it is transcoded.
a521f4
+    if(Fseek(fd, 0, SEEK_CUR) < 0) {
a521f4
+        return RPMRC_FAIL;
a521f4
+    }
a521f4
+    current = Ftell(fd);
a521f4
+
a521f4
+    len = sizeof(struct extents_footer_t);
a521f4
+    if(Fseek(fd, -len, SEEK_END) < 0) {
a521f4
+	rc = RPMRC_FAIL;
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    if (Fread(footer, len, 1, fd) != len) {
a521f4
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read footer\n"));
a521f4
+	rc = RPMRC_FAIL;
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    if (footer->magic != EXTENTS_MAGIC) {
a521f4
+	rc = RPMRC_NOTFOUND;
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    rc = RPMRC_OK;
a521f4
+exit:
a521f4
+    if (Fseek(fd, current, SEEK_SET) < 0) {
a521f4
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to seek back to original location\n"));
a521f4
+	rc = RPMRC_FAIL;
a521f4
+    }
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+rpmRC isTranscodedRpm(FD_t fd) {
a521f4
+    struct extents_footer_t footer;
a521f4
+    return extentsFooterFromFD(fd, &footer);
a521f4
+}
a521f4
+
a521f4
+
a521f4
diff --git a/lib/rpmextents_internal.h b/lib/rpmextents_internal.h
a521f4
new file mode 100644
a521f4
index 0000000..0a3318c
a521f4
--- /dev/null
a521f4
+++ b/lib/rpmextents_internal.h
a521f4
@@ -0,0 +1,58 @@
a521f4
+#ifndef _RPMEXTENTS_INTERNAL_H
a521f4
+#define _RPMEXTENTS_INTERNAL_H
a521f4
+
a521f4
+#ifdef __cplusplus
a521f4
+extern "C" {
a521f4
+#endif
a521f4
+
a521f4
+#include <stdint.h>
a521f4
+
a521f4
+/** \ingroup rpmextents
a521f4
+ * RPM extents library
a521f4
+ */
a521f4
+
a521f4
+/* magic value at end of file (64 bits) that indicates this is a transcoded
a521f4
+ * rpm.
a521f4
+ */
a521f4
+#define EXTENTS_MAGIC 3472329499408095051
a521f4
+
a521f4
+typedef uint64_t extents_magic_t;
a521f4
+
a521f4
+struct __attribute__ ((__packed__)) extents_footer_offsets_t {
a521f4
+    off64_t checksig_offset;
a521f4
+    off64_t table_offset;
a521f4
+    off64_t csum_offset;
a521f4
+};
a521f4
+
a521f4
+struct __attribute__ ((__packed__)) extents_footer_t {
a521f4
+    struct extents_footer_offsets_t offsets;
a521f4
+    extents_magic_t magic;
a521f4
+};
a521f4
+
a521f4
+/** \ingroup rpmextents
a521f4
+ * Checks the results of the signature verification ran during transcoding.
a521f4
+ * @param fd	The FD_t of the transcoded RPM
a521f4
+ * @param print_content Whether or not to print the result from rpmsig
a521f4
+ * @return	The number of checks that `rpmvsVerify` failed during transcoding.
a521f4
+ */
a521f4
+int extentsVerifySigs(FD_t fd, int print_content);
a521f4
+
a521f4
+/** \ingroup rpmextents
a521f4
+ * Read the RPM Extents footer from a file descriptor.
a521f4
+ * @param fd		The FD_t of the transcoded RPM
a521f4
+ * @param[out] footer	A pointer to an allocated extents_footer_t with a copy of the footer.
a521f4
+ * @return		RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure.
a521f4
+ */
a521f4
+rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer);
a521f4
+
a521f4
+/** \ingroup rpmextents
a521f4
+ * Check if a RPM is a transcoded RPM
a521f4
+ * @param fd	The FD_t of the transcoded RPM
a521f4
+ * return	RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure.
a521f4
+ */
a521f4
+rpmRC isTranscodedRpm(FD_t fd);
a521f4
+
a521f4
+#ifdef __cplusplus
a521f4
+}
a521f4
+#endif
a521f4
+#endif /* _RPMEXTENTS_INTERNAL_H */
a521f4
diff --git a/lib/rpmlead.c b/lib/rpmlead.c
a521f4
index 45b1c6f..8210512 100644
a521f4
--- a/lib/rpmlead.c
a521f4
+++ b/lib/rpmlead.c
a521f4
@@ -24,24 +24,6 @@ static unsigned char const lead_magic[] = {
a521f4
     RPMLEAD_MAGIC0, RPMLEAD_MAGIC1, RPMLEAD_MAGIC2, RPMLEAD_MAGIC3
a521f4
 };
a521f4
 
a521f4
-/** \ingroup lead
a521f4
- * The lead data structure.
a521f4
- * The lead needs to be 8 byte aligned.
a521f4
- * @deprecated The lead (except for signature_type) is legacy.
a521f4
- * @todo Don't use any information from lead.
a521f4
- */
a521f4
-struct rpmlead_s {
a521f4
-    unsigned char magic[4];
a521f4
-    unsigned char major;
a521f4
-    unsigned char minor;
a521f4
-    short type;
a521f4
-    short archnum;
a521f4
-    char name[66];
a521f4
-    short osnum;
a521f4
-    short signature_type;       /*!< Signature header type (RPMSIG_HEADERSIG) */
a521f4
-    char reserved[16];      /*!< Pad to 96 bytes -- 8 byte aligned! */
a521f4
-};
a521f4
-
a521f4
 static int rpmLeadFromHeader(Header h, struct rpmlead_s *l)
a521f4
 {
a521f4
     if (h != NULL) {
a521f4
@@ -70,13 +52,23 @@ static int rpmLeadFromHeader(Header h, struct rpmlead_s *l)
a521f4
 }
a521f4
 
a521f4
 /* The lead needs to be 8 byte aligned */
a521f4
-rpmRC rpmLeadWrite(FD_t fd, Header h)
a521f4
+rpmRC rpmLeadWriteFromHeader(FD_t fd, Header h)
a521f4
 {
a521f4
     rpmRC rc = RPMRC_FAIL;
a521f4
     struct rpmlead_s l;
a521f4
 
a521f4
-    if (rpmLeadFromHeader(h, &l)) {
a521f4
-	
a521f4
+    if (rpmLeadFromHeader(h, &l)) {	
a521f4
+	rc = rpmLeadWrite(fd, l);
a521f4
+    }
a521f4
+
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+/* The lead needs to be 8 byte aligned */
a521f4
+rpmRC rpmLeadWrite(FD_t fd, struct rpmlead_s l)
a521f4
+{
a521f4
+    rpmRC rc = RPMRC_FAIL;
a521f4
+
a521f4
 	l.type = htons(l.type);
a521f4
 	l.archnum = htons(l.archnum);
a521f4
 	l.osnum = htons(l.osnum);
a521f4
@@ -84,7 +76,6 @@ rpmRC rpmLeadWrite(FD_t fd, Header h)
a521f4
 	    
a521f4
 	if (Fwrite(&l, 1, sizeof(l), fd) == sizeof(l))
a521f4
 	    rc = RPMRC_OK;
a521f4
-    }
a521f4
 
a521f4
     return rc;
a521f4
 }
a521f4
@@ -107,6 +98,11 @@ static rpmRC rpmLeadCheck(struct rpmlead_s *lead, char **msg)
a521f4
 }
a521f4
 
a521f4
 rpmRC rpmLeadRead(FD_t fd, char **emsg)
a521f4
+{
a521f4
+    return rpmLeadReadAndReturn(fd, emsg, NULL);
a521f4
+}
a521f4
+
a521f4
+rpmRC rpmLeadReadAndReturn(FD_t fd, char **emsg, struct rpmlead_s * ret)
a521f4
 {
a521f4
     rpmRC rc = RPMRC_OK;
a521f4
     struct rpmlead_s l;
a521f4
@@ -136,5 +132,8 @@ rpmRC rpmLeadRead(FD_t fd, char **emsg)
a521f4
 	    free(err);
a521f4
     }
a521f4
 
a521f4
+	if (ret)
a521f4
+		*ret = l;
a521f4
+
a521f4
     return rc;
a521f4
 }
a521f4
diff --git a/lib/rpmlead.h b/lib/rpmlead.h
a521f4
index b344ed4..cc63512 100644
a521f4
--- a/lib/rpmlead.h
a521f4
+++ b/lib/rpmlead.h
a521f4
@@ -19,13 +19,39 @@ extern "C" {
a521f4
 
a521f4
 #define RPMLEAD_SIZE 96         /*!< Don't rely on sizeof(struct) */
a521f4
 
a521f4
+/** \ingroup lead
a521f4
+ * The lead data structure.
a521f4
+ * The lead needs to be 8 byte aligned.
a521f4
+ * @deprecated The lead (except for signature_type) is legacy.
a521f4
+ * @todo Don't use any information from lead.
a521f4
+ */
a521f4
+struct rpmlead_s {
a521f4
+    unsigned char magic[4];
a521f4
+    unsigned char major;
a521f4
+    unsigned char minor;
a521f4
+    short type;
a521f4
+    short archnum;
a521f4
+    char name[66];
a521f4
+    short osnum;
a521f4
+    short signature_type;       /*!< Signature header type (RPMSIG_HEADERSIG) */
a521f4
+    char reserved[16];      /*!< Pad to 96 bytes -- 8 byte aligned! */
a521f4
+};
a521f4
+
a521f4
 /** \ingroup lead
a521f4
  * Write lead to file handle.
a521f4
  * @param fd		file handle
a521f4
  * @param h		package header
a521f4
  * @return		RPMRC_OK on success, RPMRC_FAIL on error
a521f4
  */
a521f4
-rpmRC rpmLeadWrite(FD_t fd, Header h);
a521f4
+rpmRC rpmLeadWriteFromHeader(FD_t fd, Header h);
a521f4
+
a521f4
+/** \ingroup lead
a521f4
+ * Write lead to file handle.
a521f4
+ * @param fd		file handle
a521f4
+ * @param l		lead
a521f4
+ * @return		RPMRC_OK on success, RPMRC_FAIL on error
a521f4
+ */
a521f4
+rpmRC rpmLeadWrite(FD_t fd, struct rpmlead_s l);
a521f4
 
a521f4
 /** \ingroup lead
a521f4
  * Read lead from file handle.
a521f4
@@ -35,6 +61,15 @@ rpmRC rpmLeadWrite(FD_t fd, Header h);
a521f4
  */
a521f4
 rpmRC rpmLeadRead(FD_t fd, char **emsg);
a521f4
 
a521f4
+/** \ingroup lead
a521f4
+ * Read lead from file handle and return it.
a521f4
+ * @param fd		file handle
a521f4
+ * @param[out] emsg		failure message on error (malloced)
a521f4
+ * @param[out] ret		address of lead
a521f4
+ * @return		RPMRC_OK on success, RPMRC_FAIL/RPMRC_NOTFOUND on error
a521f4
+ */
a521f4
+rpmRC rpmLeadReadAndReturn(FD_t fd, char **emsg, struct rpmlead_s * ret);
a521f4
+
a521f4
 #ifdef __cplusplus
a521f4
 }
a521f4
 #endif
2f13d7
diff --git a/lib/rpmlib.h b/lib/rpmlib.h
a521f4
index cee47df..af61379 100644
2f13d7
--- a/lib/rpmlib.h
2f13d7
+++ b/lib/rpmlib.h
bd9c00
@@ -156,6 +156,15 @@ rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg);
2f13d7
 rpmRC rpmReadPackageFile(rpmts ts, FD_t fd,
2f13d7
 		const char * fn, Header * hdrp);
2f13d7
 
2f13d7
+/** \ingroup header
2f13d7
+ * Return package signature, header from file handle, no verification.
2f13d7
+ * @param fd		file handle
2f13d7
+ * @param[out] sigp		address of header (or NULL)
2f13d7
+ * @param[out] hdrp		address of header (or NULL)
2f13d7
+ * @return		RPMRC_OK on success
2f13d7
+ */
2f13d7
+rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp);
2f13d7
+
2f13d7
 /** \ingroup rpmtrans
2f13d7
  * Install source package.
2f13d7
  * @param ts		transaction set
a521f4
diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h
a521f4
index fab4b3e..c82d6be 100644
a521f4
--- a/lib/rpmplugin.h
a521f4
+++ b/lib/rpmplugin.h
a521f4
@@ -60,6 +60,13 @@ typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi,
a521f4
 					      int fd, const char* path,
a521f4
 					      const char *dest,
a521f4
 					      mode_t file_mode, rpmFsmOp op);
a521f4
+typedef rpmRC (*plugin_fsm_file_install_func)(rpmPlugin plugin, rpmfi fi,
a521f4
+					      const char* path,
a521f4
+					      mode_t file_mode, rpmFsmOp op);
a521f4
+typedef rpmRC (*plugin_fsm_file_archive_reader_func)(rpmPlugin plugin,
a521f4
+						     FD_t payload,
a521f4
+						     rpmfiles files, rpmfi *fi);
a521f4
+
a521f4
 
a521f4
 typedef struct rpmPluginHooks_s * rpmPluginHooks;
a521f4
 struct rpmPluginHooks_s {
a521f4
@@ -80,6 +87,8 @@ struct rpmPluginHooks_s {
a521f4
     plugin_fsm_file_pre_func		fsm_file_pre;
a521f4
     plugin_fsm_file_post_func		fsm_file_post;
a521f4
     plugin_fsm_file_prepare_func	fsm_file_prepare;
a521f4
+    plugin_fsm_file_install_func	fsm_file_install;
a521f4
+    plugin_fsm_file_archive_reader_func	fsm_file_archive_reader;
a521f4
 };
a521f4
 
a521f4
 #ifdef __cplusplus
2f13d7
diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
a521f4
index f06fd78..1e0c345 100644
2f13d7
--- a/lib/rpmplugins.c
2f13d7
+++ b/lib/rpmplugins.c
bd9c00
@@ -364,14 +364,29 @@ rpmRC rpmpluginsCallFsmFilePre(rpmPlugins plugins, rpmfi fi, const char *path,
2f13d7
     plugin_fsm_file_pre_func hookFunc;
2f13d7
     int i;
2f13d7
     rpmRC rc = RPMRC_OK;
2f13d7
+    rpmRC hook_rc;
bd9c00
     char *apath = abspath(fi, path);
2f13d7
 
2f13d7
     for (i = 0; i < plugins->count; i++) {
2f13d7
 	rpmPlugin plugin = plugins->plugins[i];
2f13d7
 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_pre);
bd9c00
-	if (hookFunc && hookFunc(plugin, fi, apath, file_mode, op) == RPMRC_FAIL) {
2f13d7
-	    rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
2f13d7
-	    rc = RPMRC_FAIL;
2f13d7
+	if (hookFunc) {
2f13d7
+	    hook_rc = hookFunc(plugin, fi, path, file_mode, op);
2f13d7
+	    if (hook_rc == RPMRC_FAIL) {
2f13d7
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
2f13d7
+		rc = RPMRC_FAIL;
2f13d7
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
2f13d7
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
bd9c00
+		    /* Another plugin already said it'd handle contents. It's
bd9c00
+		     * undefined how these would combine, so treat this as a
bd9c00
+		     * failure condition.
2f13d7
+		    */
2f13d7
+		    rc = RPMRC_FAIL;
2f13d7
+		} else {
2f13d7
+		    /* Plugin will handle content */
2f13d7
+		    rc = RPMRC_PLUGIN_CONTENTS;
2f13d7
+		}
2f13d7
+	    }
2f13d7
 	}
2f13d7
     }
bd9c00
     free(apath);
a521f4
@@ -420,3 +435,74 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
a521f4
 
a521f4
     return rc;
a521f4
 }
a521f4
+
a521f4
+rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi,
a521f4
+				   const char *path, mode_t file_mode,
a521f4
+				   rpmFsmOp op)
a521f4
+{
a521f4
+    plugin_fsm_file_install_func hookFunc;
a521f4
+    int i;
a521f4
+    rpmRC rc = RPMRC_OK;
a521f4
+    rpmRC hook_rc;
a521f4
+
a521f4
+    for (i = 0; i < plugins->count; i++) {
a521f4
+	rpmPlugin plugin = plugins->plugins[i];
a521f4
+	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_install);
a521f4
+	if (hookFunc) {
a521f4
+	    hook_rc = hookFunc(plugin, fi, path, file_mode, op);
a521f4
+	    if (hook_rc == RPMRC_FAIL) {
a521f4
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_install failed\n", plugin->name);
a521f4
+		rc = RPMRC_FAIL;
a521f4
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
a521f4
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
a521f4
+		    /* Another plugin already said it'd handle contents. It's
a521f4
+		     * undefined how these would combine, so treat this as a
a521f4
+		     * failure condition.
a521f4
+		    */
a521f4
+		    rc = RPMRC_FAIL;
a521f4
+		} else {
a521f4
+		    /* Plugin will handle content */
a521f4
+		    rc = RPMRC_PLUGIN_CONTENTS;
a521f4
+		}
a521f4
+	    }
a521f4
+	}
a521f4
+    }
a521f4
+
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload,
a521f4
+				   rpmfiles files, rpmfi *fi)
a521f4
+{
a521f4
+    plugin_fsm_file_archive_reader_func hookFunc;
a521f4
+    int i;
a521f4
+    rpmRC rc = RPMRC_OK;
a521f4
+    rpmRC hook_rc;
a521f4
+
a521f4
+    for (i = 0; i < plugins->count; i++) {
a521f4
+	rpmPlugin plugin = plugins->plugins[i];
a521f4
+	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_archive_reader);
a521f4
+	if (hookFunc) {
a521f4
+	    hook_rc = hookFunc(plugin, payload, files, fi);
a521f4
+	    if (hook_rc == RPMRC_FAIL) {
a521f4
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_archive_reader failed\n", plugin->name);
a521f4
+		rc = RPMRC_FAIL;
a521f4
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
a521f4
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
a521f4
+		    /* Another plugin already said it'd handle contents. It's
a521f4
+		     * undefined how these would combine, so treat this as a
a521f4
+		     * failure condition.
a521f4
+		    */
a521f4
+		    rc = RPMRC_FAIL;
a521f4
+		} else {
a521f4
+		    /* Plugin will handle content */
a521f4
+		    rc = RPMRC_PLUGIN_CONTENTS;
a521f4
+		}
a521f4
+	    }
a521f4
+	}
a521f4
+    }
a521f4
+
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+
a521f4
diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h
a521f4
index ddf5d70..db01bff 100644
a521f4
--- a/lib/rpmplugins.h
a521f4
+++ b/lib/rpmplugins.h
a521f4
@@ -168,6 +168,23 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
a521f4
                                    int fd, const char *path, const char *dest,
a521f4
                                    mode_t mode, rpmFsmOp op);
a521f4
 
a521f4
+/** \ingroup rpmplugins
a521f4
+ * Call the fsm file install plugin hook
a521f4
+ * @param plugins	plugins structure
a521f4
+ * @param fi		file info iterator (or NULL)
a521f4
+ * @param path		file object path
a521f4
+ * @param file_mode	file object mode
a521f4
+ * @param op		file operation + associated flags
a521f4
+ * @return		RPMRC_OK on success, RPMRC_FAIL otherwise
a521f4
+ */
a521f4
+RPM_GNUC_INTERNAL
a521f4
+rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi,
a521f4
+				   const char* path, mode_t file_mode,
a521f4
+				   rpmFsmOp op);
a521f4
+
a521f4
+RPM_GNUC_INTERNAL
a521f4
+rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload,
a521f4
+					 rpmfiles files, rpmfi *fi);
a521f4
 #ifdef __cplusplus
a521f4
 }
a521f4
 #endif
2f13d7
diff --git a/lib/rpmte.c b/lib/rpmte.c
a521f4
index 0551a0f..61b3905 100644
2f13d7
--- a/lib/rpmte.c
2f13d7
+++ b/lib/rpmte.c
bd9c00
@@ -421,6 +421,11 @@ FD_t rpmteSetFd(rpmte te, FD_t fd)
2f13d7
     return NULL;
2f13d7
 }
2f13d7
 
2f13d7
+FD_t rpmteFd(rpmte te)
2f13d7
+{
2f13d7
+    return (te != NULL ? te->fd : NULL);
2f13d7
+}
2f13d7
+
2f13d7
 fnpyKey rpmteKey(rpmte te)
2f13d7
 {
2f13d7
     return (te != NULL ? te->key : NULL);
2f13d7
diff --git a/lib/rpmte.h b/lib/rpmte.h
a521f4
index 188c3c1..47dec4b 100644
2f13d7
--- a/lib/rpmte.h
2f13d7
+++ b/lib/rpmte.h
2f13d7
@@ -209,6 +209,8 @@ const char * rpmteNEVR(rpmte te);
2f13d7
  */
2f13d7
 const char * rpmteNEVRA(rpmte te);
2f13d7
 
2f13d7
+FD_t rpmteFd(rpmte te);
2f13d7
+
2f13d7
 /** \ingroup rpmte
2f13d7
  * Retrieve key from transaction element.
2f13d7
  * @param te		transaction element
2f13d7
diff --git a/lib/rpmtypes.h b/lib/rpmtypes.h
a521f4
index e8e69b5..af2611e 100644
2f13d7
--- a/lib/rpmtypes.h
2f13d7
+++ b/lib/rpmtypes.h
2f13d7
@@ -106,7 +106,8 @@ typedef	enum rpmRC_e {
2f13d7
     RPMRC_NOTFOUND	= 1,	/*!< Generic not found code. */
2f13d7
     RPMRC_FAIL		= 2,	/*!< Generic failure code. */
2f13d7
     RPMRC_NOTTRUSTED	= 3,	/*!< Signature is OK, but key is not trusted. */
2f13d7
-    RPMRC_NOKEY		= 4	/*!< Public key is unavailable. */
2f13d7
+    RPMRC_NOKEY		= 4,	/*!< Public key is unavailable. */
2f13d7
+    RPMRC_PLUGIN_CONTENTS = 5     /*!< fsm_file_pre plugin is handling content */
2f13d7
 } rpmRC;
2f13d7
 
2f13d7
 #ifdef __cplusplus
a521f4
diff --git a/lib/transaction.c b/lib/transaction.c
a521f4
index 55bc2d9..9603d5e 100644
a521f4
--- a/lib/transaction.c
a521f4
+++ b/lib/transaction.c
a521f4
@@ -37,6 +37,7 @@
a521f4
 #include "lib/rpmfi_internal.h"	/* only internal apis */
a521f4
 #include "lib/rpmte_internal.h"	/* only internal apis */
a521f4
 #include "lib/rpmts_internal.h"
a521f4
+#include "lib/rpmextents_internal.h"
a521f4
 #include "lib/rpmvs.h"
a521f4
 #include "rpmio/rpmhook.h"
a521f4
 #include "lib/rpmtriggers.h"
a521f4
@@ -1286,19 +1287,25 @@ static int verifyPackageFiles(rpmts ts, rpm_loff_t total)
a521f4
 
a521f4
 	rpmtsNotify(ts, p, RPMCALLBACK_VERIFY_PROGRESS, oc++, total);
a521f4
 	FD_t fd = rpmtsNotify(ts, p, RPMCALLBACK_INST_OPEN_FILE, 0, 0);
a521f4
-	if (fd != NULL) {
a521f4
-	    prc = rpmpkgRead(vs, fd, NULL, NULL, &vd.msg);
a521f4
-	    rpmtsNotify(ts, p, RPMCALLBACK_INST_CLOSE_FILE, 0, 0);
a521f4
+	if(fd != NULL && isTranscodedRpm(fd) == RPMRC_OK) {
a521f4
+	    /* Transcoded RPMs are validated at transcoding time */
a521f4
+	    prc = RPMRC_OK;
a521f4
+	    verified = 1;
a521f4
+	} else {
a521f4
+	    if (fd != NULL) {
a521f4
+		prc = rpmpkgRead(vs, fd, NULL, NULL, &vd.msg);
a521f4
+		rpmtsNotify(ts, p, RPMCALLBACK_INST_CLOSE_FILE, 0, 0);
a521f4
+	    }
a521f4
+	    if (prc == RPMRC_OK)
a521f4
+		prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
a521f4
+
a521f4
+	    /* Record verify result */
a521f4
+	    if (vd.type[RPMSIG_SIGNATURE_TYPE] == RPMRC_OK)
a521f4
+		verified |= RPMSIG_SIGNATURE_TYPE;
a521f4
+	    if (vd.type[RPMSIG_DIGEST_TYPE] == RPMRC_OK)
a521f4
+		verified |= RPMSIG_DIGEST_TYPE;
a521f4
 	}
a521f4
 
a521f4
-	if (prc == RPMRC_OK)
a521f4
-	    prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
a521f4
-
a521f4
-	/* Record verify result */
a521f4
-	if (vd.type[RPMSIG_SIGNATURE_TYPE] == RPMRC_OK)
a521f4
-	    verified |= RPMSIG_SIGNATURE_TYPE;
a521f4
-	if (vd.type[RPMSIG_DIGEST_TYPE] == RPMRC_OK)
a521f4
-	    verified |= RPMSIG_DIGEST_TYPE;
a521f4
 	rpmteSetVerified(p, verified);
a521f4
 
a521f4
 	if (prc)
2f13d7
diff --git a/macros.in b/macros.in
a521f4
index 877f3ed..a9cc673 100644
2f13d7
--- a/macros.in
2f13d7
+++ b/macros.in
bd9c00
@@ -1189,6 +1189,7 @@ package or when debugging this package.\
2f13d7
 
2f13d7
 # Transaction plugin macros
2f13d7
 %__plugindir		%{_libdir}/rpm-plugins
2f13d7
+%__transaction_reflink		%{__plugindir}/reflink.so
2f13d7
 %__transaction_systemd_inhibit	%{__plugindir}/systemd_inhibit.so
2f13d7
 %__transaction_selinux		%{__plugindir}/selinux.so
2f13d7
 %__transaction_syslog		%{__plugindir}/syslog.so
2f13d7
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
a521f4
index f7b95a4..154acb0 100644
2f13d7
--- a/plugins/Makefile.am
2f13d7
+++ b/plugins/Makefile.am
bd9c00
@@ -33,6 +33,10 @@ prioreset_la_SOURCES = prioreset.c
2f13d7
 prioreset_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
2f13d7
 plugins_LTLIBRARIES += prioreset.la
2f13d7
 
2f13d7
+reflink_la_SOURCES = reflink.c
2f13d7
+reflink_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
2f13d7
+plugins_LTLIBRARIES += reflink.la
2f13d7
+
2f13d7
 syslog_la_SOURCES = syslog.c
2f13d7
 syslog_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
2f13d7
 plugins_LTLIBRARIES += syslog.la
2f13d7
diff --git a/plugins/reflink.c b/plugins/reflink.c
2f13d7
new file mode 100644
a521f4
index 0000000..127888e
2f13d7
--- /dev/null
2f13d7
+++ b/plugins/reflink.c
a521f4
@@ -0,0 +1,401 @@
2f13d7
+#include "system.h"
2f13d7
+
2f13d7
+#include <errno.h>
2f13d7
+#include <sys/resource.h>
2f13d7
+#include <unistd.h>
2f13d7
+#include <sys/types.h>
2f13d7
+#include <sys/stat.h>
2f13d7
+#include <fcntl.h>
2f13d7
+#if defined(__linux__)
2f13d7
+#include <linux/fs.h>        /* For FICLONE */
2f13d7
+#endif
2f13d7
+
2f13d7
+#include <rpm/rpmlog.h>
2f13d7
+#include "lib/rpmlib.h"
2f13d7
+#include "lib/rpmplugin.h"
a521f4
+#include "lib/rpmextents_internal.h"
2f13d7
+#include "lib/rpmte_internal.h"
2f13d7
+#include <rpm/rpmfileutil.h>
2f13d7
+#include "rpmio/rpmio_internal.h"
2f13d7
+
2f13d7
+
2f13d7
+#include "debug.h"
2f13d7
+
2f13d7
+#include <sys/ioctl.h>
2f13d7
+
2f13d7
+/* use hash table to remember inode -> ix (for rpmfilesFN(ix)) lookups */
2f13d7
+#undef HASHTYPE
2f13d7
+#undef HTKEYTYPE
2f13d7
+#undef HTDATATYPE
2f13d7
+#define HASHTYPE inodeIndexHash
2f13d7
+#define HTKEYTYPE rpm_ino_t
a521f4
+#define HTDATATYPE const char *
2f13d7
+#include "lib/rpmhash.H"
2f13d7
+#include "lib/rpmhash.C"
2f13d7
+
a521f4
+/* We use this in find to indicate a key wasn't found. This is an
a521f4
+ * unrecoverable error, but we can at least show a decent error. 0 is never a
a521f4
+ * valid offset because it's the offset of the start of the file.
a521f4
+ */
2f13d7
+#define NOT_FOUND 0
2f13d7
+
2f13d7
+#define BUFFER_SIZE (1024 * 128)
2f13d7
+
2f13d7
+struct reflink_state_s {
a521f4
+    /* Stuff that's used across rpms */
a521f4
+    long fundamental_block_size;
a521f4
+    char *buffer;
a521f4
+
a521f4
+    /* stuff that's used/updated per psm */
a521f4
+    uint32_t keys, keysize;
a521f4
+
a521f4
+    /* table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) */
a521f4
+    unsigned char *table;
a521f4
+    FD_t fd;
a521f4
+    rpmfiles files;
a521f4
+    inodeIndexHash inodeIndexes;
a521f4
+    int transcoded;
2f13d7
+};
2f13d7
+
a521f4
+typedef struct reflink_state_s * reflink_state;
a521f4
+
a521f4
+/*
a521f4
+ * bsearch_r: implements a re-entrant version of stdlib's bsearch.
a521f4
+ * code taken and adapted from /usr/include/bits/stdlib-bsearch.h
a521f4
+ */
a521f4
+inline void *
a521f4
+bsearch_r (const void *__key, const void *__base, size_t __nmemb, size_t __size,
a521f4
+	 __compar_d_fn_t __compar, void *__arg)
a521f4
+{
a521f4
+  size_t __l, __u, __idx;
a521f4
+  const void *__p;
a521f4
+  int __comparison;
a521f4
+
a521f4
+  __l = 0;
a521f4
+  __u = __nmemb;
a521f4
+  while (__l < __u)
a521f4
+    {
a521f4
+      __idx = (__l + __u) / 2;
a521f4
+      __p = (const void *) (((const char *) __base) + (__idx * __size));
a521f4
+      __comparison = (*__compar) (__key, __p, __arg);
a521f4
+      if (__comparison < 0)
a521f4
+	__u = __idx;
a521f4
+      else if (__comparison > 0)
a521f4
+	__l = __idx + 1;
a521f4
+      else
a521f4
+	{
a521f4
+#if __GNUC_PREREQ(4, 6)
a521f4
+# pragma GCC diagnostic push
a521f4
+# pragma GCC diagnostic ignored "-Wcast-qual"
a521f4
+#endif
a521f4
+	  return (void *) __p;
a521f4
+#if __GNUC_PREREQ(4, 6)
a521f4
+# pragma GCC diagnostic pop
a521f4
+#endif
a521f4
+	}
a521f4
+    }
a521f4
+
a521f4
+  return NULL;
a521f4
+}
a521f4
+
a521f4
+static int cmpdigest(const void *k1, const void *k2, void *data) {
a521f4
+    rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2);
a521f4
+    return memcmp(k1, k2, *(int *)data);
a521f4
+}
2f13d7
+
2f13d7
+static int inodeCmp(rpm_ino_t a, rpm_ino_t b)
2f13d7
+{
2f13d7
+    return (a != b);
2f13d7
+}
2f13d7
+
2f13d7
+static unsigned int inodeId(rpm_ino_t a)
2f13d7
+{
2f13d7
+    /* rpm_ino_t is uint32_t so maps safely to unsigned int */
2f13d7
+    return (unsigned int)a;
2f13d7
+}
2f13d7
+
2f13d7
+static rpmRC reflink_init(rpmPlugin plugin, rpmts ts) {
a521f4
+    reflink_state state = rcalloc(1, sizeof(struct reflink_state_s));
a521f4
+
a521f4
+    /* IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset
a521f4
+     * and length arguments to be aligned to the fundamental block size.
a521f4
+     *
a521f4
+     * The value of "fundamental block size" is directly related to the
a521f4
+     * system's page size, so we should use that.
a521f4
+     */
a521f4
+    state->fundamental_block_size = sysconf(_SC_PAGESIZE);
a521f4
+    state->buffer = rcalloc(1, BUFFER_SIZE);
a521f4
+    rpmPluginSetData(plugin, state);
2f13d7
+
a521f4
+    return RPMRC_OK;
2f13d7
+}
2f13d7
+
2f13d7
+static void reflink_cleanup(rpmPlugin plugin) {
a521f4
+    reflink_state state = rpmPluginGetData(plugin);
a521f4
+    free(state->buffer);
a521f4
+    free(state);
2f13d7
+}
2f13d7
+
2f13d7
+static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) {
a521f4
+    rpmRC rc;
a521f4
+    size_t len;
a521f4
+
2f13d7
+    reflink_state state = rpmPluginGetData(plugin);
2f13d7
+    state->fd = rpmteFd(te);
2f13d7
+    if (state->fd == 0) {
a521f4
+	rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n"));
a521f4
+	return RPMRC_OK;
2f13d7
+    }
a521f4
+
2f13d7
+    rpm_loff_t current = Ftell(state->fd);
a521f4
+    rc = isTranscodedRpm(state->fd);
a521f4
+
a521f4
+    switch(rc){
a521f4
+	// Fail to parse the file, fail the plugin.
a521f4
+	case RPMRC_FAIL:
a521f4
+	    return RPMRC_FAIL;
a521f4
+	// This is not a transcoded file, do nothing.
a521f4
+	case RPMRC_NOTFOUND:
a521f4
+	    return RPMRC_OK;
a521f4
+	default:
a521f4
+	    break;
2f13d7
+    }
2f13d7
+    rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n"));
a521f4
+    state->transcoded = 1;
2f13d7
+
2f13d7
+    state->files = rpmteFiles(te);
a521f4
+    /* tail of file contains offset_table, offset_checksums then magic */
a521f4
+    if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(extents_magic_t)), SEEK_END) < 0) {
a521f4
+	rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"),
a521f4
+	       state->fd);
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+    rpm_loff_t table_start;
2f13d7
+    len = sizeof(table_start);
2f13d7
+    if (Fread(&table_start, len, 1, state->fd) != len) {
a521f4
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+    if (Fseek(state->fd, table_start, SEEK_SET) < 0) {
a521f4
+	rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+    len = sizeof(state->keys);
2f13d7
+    if (Fread(&state->keys, len, 1, state->fd) != len) {
a521f4
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+    len = sizeof(state->keysize);
2f13d7
+    if (Fread(&state->keysize, len, 1, state->fd) != len) {
a521f4
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
a521f4
+    rpmlog(
a521f4
+	RPMLOG_DEBUG,
a521f4
+	_("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"),
a521f4
+	table_start, state->keys, state->keysize
a521f4
+    );
a521f4
+    /* now get digest table if there is a reason to have one. */
2f13d7
+    if (state->keys == 0 || state->keysize == 0) {
a521f4
+	/* no files (or no digests(!)) */
a521f4
+	state->table = NULL;
2f13d7
+    } else {
a521f4
+	int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t));
a521f4
+	state->table = rcalloc(1, table_size);
a521f4
+	if (Fread(state->table, table_size, 1, state->fd) != table_size) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n"));
a521f4
+	    return RPMRC_FAIL;
a521f4
+	}
a521f4
+	state->inodeIndexes = inodeIndexHashCreate(
a521f4
+	    state->keys, inodeId, inodeCmp, NULL, (inodeIndexHashFreeData)rfree
a521f4
+	);
2f13d7
+    }
2f13d7
+
a521f4
+    /* Seek back to original location.
a521f4
+     * Might not be needed if we seek to offset immediately
a521f4
+     */
2f13d7
+    if (Fseek(state->fd, current, SEEK_SET) < 0) {
a521f4
+	rpmlog(RPMLOG_ERR,
a521f4
+	       _("reflink: unable to seek back to original location\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+    return RPMRC_OK;
2f13d7
+}
2f13d7
+
2f13d7
+static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res)
2f13d7
+{
2f13d7
+    reflink_state state = rpmPluginGetData(plugin);
2f13d7
+    state->files = rpmfilesFree(state->files);
2f13d7
+    if (state->table) {
a521f4
+	free(state->table);
a521f4
+	state->table = NULL;
2f13d7
+    }
2f13d7
+    if (state->inodeIndexes) {
a521f4
+	inodeIndexHashFree(state->inodeIndexes);
a521f4
+	state->inodeIndexes = NULL;
2f13d7
+    }
a521f4
+    state->transcoded = 0;
2f13d7
+    return RPMRC_OK;
2f13d7
+}
2f13d7
+
2f13d7
+
a521f4
+/* have a prototype, warnings system */
2f13d7
+rpm_loff_t find(const unsigned char *digest, reflink_state state);
2f13d7
+
2f13d7
+rpm_loff_t find(const unsigned char *digest, reflink_state state) {
a521f4
+    rpmlog(RPMLOG_DEBUG,
a521f4
+	   _("reflink: bsearch_r(key=%p, base=%p, nmemb=%d, size=%lu)\n"),
a521f4
+	   digest, state->table, state->keys,
a521f4
+	   state->keysize + sizeof(rpm_loff_t));
a521f4
+    char *entry = bsearch_r(digest, state->table, state->keys,
a521f4
+			    state->keysize + sizeof(rpm_loff_t), cmpdigest,
a521f4
+			    &state->keysize);
a521f4
+    if (entry == NULL) {
a521f4
+	return NOT_FOUND;
a521f4
+    }
a521f4
+    rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize);
a521f4
+    return offset;
2f13d7
+}
2f13d7
+
a521f4
+static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* path,
a521f4
+                                  mode_t file_mode, rpmFsmOp op)
2f13d7
+{
2f13d7
+    struct file_clone_range fcr;
2f13d7
+    rpm_loff_t size;
2f13d7
+    int dst, rc;
a521f4
+    const char **hl_target = NULL;
2f13d7
+
2f13d7
+    reflink_state state = rpmPluginGetData(plugin);
2f13d7
+    if (state->table == NULL) {
a521f4
+	/* no table means rpm is not in reflink format, so leave. Now. */
a521f4
+	return RPMRC_OK;
2f13d7
+    }
2f13d7
+    if (op == FA_TOUCH) {
a521f4
+	/* we're not overwriting an existing file. */
a521f4
+	return RPMRC_OK;
2f13d7
+    }
2f13d7
+    fcr.dest_offset = 0;
2f13d7
+    if (S_ISREG(file_mode) && !(rpmfiFFlags(fi) & RPMFILE_GHOST)) {
a521f4
+	rpm_ino_t inode = rpmfiFInode(fi);
a521f4
+	/* check for hard link entry in table. GetEntry overwrites hlix with
a521f4
+	 * the address of the first match.
a521f4
+	 */
a521f4
+	if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hl_target,
a521f4
+				   NULL, NULL)) {
a521f4
+	    /* entry is in table, use hard link */
a521f4
+	    if (link(hl_target[0], path) != 0) {
a521f4
+		rpmlog(RPMLOG_ERR,
a521f4
+		       _("reflink: Unable to hard link %s -> %s due to %s\n"),
a521f4
+		       hl_target[0], path, strerror(errno));
a521f4
+		return RPMRC_FAIL;
a521f4
+	    }
a521f4
+	    return RPMRC_PLUGIN_CONTENTS;
a521f4
+	}
a521f4
+	/* if we didn't hard link, then we'll track this inode as being
a521f4
+	 * created soon
a521f4
+	 */
a521f4
+	if (rpmfiFNlink(fi) > 1) {
a521f4
+	    /* minor optimization: only store files with more than one link */
a521f4
+	    inodeIndexHashAddEntry(state->inodeIndexes, inode, rstrdup(path));
a521f4
+	}
a521f4
+	/* derived from wfd_open in fsm.c */
a521f4
+	mode_t old_umask = umask(0577);
a521f4
+	dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR);
a521f4
+	umask(old_umask);
a521f4
+	if (dst == -1) {
a521f4
+	    rpmlog(RPMLOG_ERR,
a521f4
+		   _("reflink: Unable to open %s for writing due to %s, flags = %x\n"),
a521f4
+		   path, strerror(errno), rpmfiFFlags(fi));
a521f4
+	    return RPMRC_FAIL;
a521f4
+	}
a521f4
+	size = rpmfiFSize(fi);
a521f4
+	if (size > 0) {
a521f4
+	    /* round src_length down to fundamental_block_size multiple */
a521f4
+	    fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size;
a521f4
+	    if ((size % state->fundamental_block_size) > 0) {
a521f4
+		/* round up to next fundamental_block_size. We expect the data
a521f4
+		 * in the rpm to be similarly padded.
a521f4
+		 */
a521f4
+		fcr.src_length += state->fundamental_block_size;
a521f4
+	    }
a521f4
+	    fcr.src_fd = Fileno(state->fd);
a521f4
+	    if (fcr.src_fd == -1) {
a521f4
+		close(dst);
a521f4
+		rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n"));
a521f4
+		return RPMRC_FAIL;
a521f4
+	    }
a521f4
+	    fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state);
a521f4
+	    if (fcr.src_offset == NOT_FOUND) {
a521f4
+		close(dst);
a521f4
+		rpmlog(RPMLOG_ERR, _("reflink: digest not found\n"));
a521f4
+		return RPMRC_FAIL;
a521f4
+	    }
a521f4
+	    rpmlog(RPMLOG_DEBUG,
a521f4
+	           _("reflink: Reflinking %llu bytes at %llu to %s orig size=%ld, file=%lld\n"),
a521f4
+		   fcr.src_length, fcr.src_offset, path, size, fcr.src_fd);
a521f4
+	    rc = ioctl(dst, FICLONERANGE, &fcr;;
a521f4
+	    if (rc) {
a521f4
+		rpmlog(RPMLOG_WARNING,
a521f4
+		       _("reflink: falling back to copying bits for %s due to %d, %d = %s\n"),
a521f4
+		       path, rc, errno, strerror(errno));
a521f4
+		if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) {
a521f4
+		    close(dst);
a521f4
+		    rpmlog(RPMLOG_ERR,
a521f4
+			   _("reflink: unable to seek on copying bits\n"));
a521f4
+		    return RPMRC_FAIL;
a521f4
+		}
a521f4
+		rpm_loff_t left = size;
a521f4
+		size_t len, read, written;
a521f4
+		while (left) {
a521f4
+		    len = (left > BUFFER_SIZE ? BUFFER_SIZE : left);
a521f4
+		    read = Fread(state->buffer, len, 1, state->fd);
a521f4
+		    if (read != len) {
a521f4
+			close(dst);
a521f4
+			rpmlog(RPMLOG_ERR,
a521f4
+			       _("reflink: short read on copying bits\n"));
a521f4
+			return RPMRC_FAIL;
a521f4
+		    }
a521f4
+		    written = write(dst, state->buffer, len);
a521f4
+		    if (read != written) {
a521f4
+			close(dst);
a521f4
+			rpmlog(RPMLOG_ERR,
a521f4
+			       _("reflink: short write on copying bits\n"));
a521f4
+			return RPMRC_FAIL;
a521f4
+		    }
a521f4
+		    left -= len;
a521f4
+		}
a521f4
+	    } else {
a521f4
+		/* reflink worked, so truncate */
a521f4
+		rc = ftruncate(dst, size);
a521f4
+		if (rc) {
a521f4
+		    rpmlog(RPMLOG_ERR,
a521f4
+			   _("reflink: Unable to truncate %s to %ld due to %s\n"),
a521f4
+			   path, size, strerror(errno));
a521f4
+		     return RPMRC_FAIL;
a521f4
+		}
a521f4
+	    }
a521f4
+	}
a521f4
+	close(dst);
a521f4
+	return RPMRC_PLUGIN_CONTENTS;
a521f4
+    }
a521f4
+    return RPMRC_OK;
a521f4
+}
a521f4
+
a521f4
+static rpmRC reflink_fsm_file_archive_reader(rpmPlugin plugin, FD_t payload,
a521f4
+					     rpmfiles files, rpmfi *fi) {
a521f4
+    reflink_state state = rpmPluginGetData(plugin);
a521f4
+    if(state->transcoded) {
a521f4
+	*fi = rpmfilesIter(files, RPMFI_ITER_FWD);
a521f4
+	return RPMRC_PLUGIN_CONTENTS;
2f13d7
+    }
2f13d7
+    return RPMRC_OK;
2f13d7
+}
2f13d7
+
2f13d7
+struct rpmPluginHooks_s reflink_hooks = {
2f13d7
+    .init = reflink_init,
2f13d7
+    .cleanup = reflink_cleanup,
2f13d7
+    .psm_pre = reflink_psm_pre,
2f13d7
+    .psm_post = reflink_psm_post,
a521f4
+    .fsm_file_install = reflink_fsm_file_install,
a521f4
+    .fsm_file_archive_reader = reflink_fsm_file_archive_reader,
2f13d7
+};
2f13d7
diff --git a/rpm2extents.c b/rpm2extents.c
2f13d7
new file mode 100644
a521f4
index 0000000..c29831d
2f13d7
--- /dev/null
2f13d7
+++ b/rpm2extents.c
a521f4
@@ -0,0 +1,708 @@
2f13d7
+/* rpm2extents: convert payload to inline extents */
2f13d7
+
2f13d7
+#include "system.h"
2f13d7
+
a521f4
+#include <rpm/rpmcli.h>
2f13d7
+#include <rpm/rpmlib.h>		/* rpmReadPackageFile .. */
a521f4
+#include <rpm/rpmlog.h>
2f13d7
+#include <rpm/rpmfi.h>
2f13d7
+#include <rpm/rpmtag.h>
2f13d7
+#include <rpm/rpmio.h>
2f13d7
+#include <rpm/rpmpgp.h>
2f13d7
+
2f13d7
+#include <rpm/rpmts.h>
2f13d7
+#include "lib/rpmlead.h"
a521f4
+#include "lib/rpmts.h"
2f13d7
+#include "lib/signature.h"
2f13d7
+#include "lib/header_internal.h"
a521f4
+#include "lib/rpmextents_internal.h"
2f13d7
+#include "rpmio/rpmio_internal.h"
2f13d7
+
2f13d7
+#include <unistd.h>
2f13d7
+#include <sys/types.h>
2f13d7
+#include <sys/wait.h>
2f13d7
+#include <signal.h>
2f13d7
+#include <errno.h>
2f13d7
+#include <string.h>
2f13d7
+
2f13d7
+#include "debug.h"
2f13d7
+
2f13d7
+/* hash of void * (pointers) to file digests to offsets within output.
a521f4
+ * The length of the key depends on what the FILEDIGESTALGO is.
2f13d7
+ */
2f13d7
+#undef HASHTYPE
2f13d7
+#undef HTKEYTYPE
2f13d7
+#undef HTDATATYPE
2f13d7
+#define HASHTYPE digestSet
2f13d7
+#define HTKEYTYPE const unsigned char *
2f13d7
+#include "lib/rpmhash.H"
2f13d7
+#include "lib/rpmhash.C"
2f13d7
+
2f13d7
+struct digestoffset {
2f13d7
+    const unsigned char * digest;
2f13d7
+    rpm_loff_t pos;
2f13d7
+};
2f13d7
+
2f13d7
+rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit);
2f13d7
+
2f13d7
+rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit)
2f13d7
+{
2f13d7
+    return (unit - (pos % unit)) % unit;
2f13d7
+}
2f13d7
+
a521f4
+static struct poptOption optionsTable[] = {
a521f4
+    { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0,
a521f4
+    N_("Common options for all rpm modes and executables:"), NULL },
a521f4
+
a521f4
+    POPT_AUTOALIAS
a521f4
+    POPT_AUTOHELP
a521f4
+    POPT_TABLEEND
a521f4
+};
a521f4
+
a521f4
+
a521f4
+static void FDDigestInit(FD_t fdi, uint8_t algos[], uint32_t algos_len){
a521f4
+    int algo;
a521f4
+
a521f4
+    for (algo = 0; algo < algos_len; algo++) {
a521f4
+	fdInitDigest(fdi, algos[algo], 0);
a521f4
+    }
a521f4
+}
a521f4
+
a521f4
+static int FDWriteDigests(
2f13d7
+    FD_t fdi,
2f13d7
+    FD_t fdo,
2f13d7
+    uint8_t algos[],
a521f4
+    uint32_t algos_len)
2f13d7
+{
2f13d7
+    const char *filedigest, *algo_name;
2f13d7
+    size_t filedigest_len, len;
2f13d7
+    uint32_t algo_name_len, algo_digest_len;
2f13d7
+    int algo;
2f13d7
+    rpmRC rc = RPMRC_FAIL;
2f13d7
+
a521f4
+    ssize_t fdilength = fdOp(fdi, FDSTAT_READ)->bytes;
2f13d7
+
2f13d7
+    len = sizeof(fdilength);
a521f4
+    if (Fwrite(&fdilength, len, 1, fdo) != len) {
a521f4
+	rpmlog(RPMLOG_ERR, _("Unable to write input length %zd: %d, %s\n"),
a521f4
+	       fdilength, errno, strerror(errno));
a521f4
+	goto exit;
2f13d7
+    }
2f13d7
+    len = sizeof(algos_len);
a521f4
+    if (Fwrite(&algos_len, len, 1, fdo) != len) {
a521f4
+	algo_digest_len = (uint32_t)filedigest_len;
a521f4
+	rpmlog(RPMLOG_ERR, _("Unable to write number of digests: %d, %s\n"),
a521f4
+	       errno, strerror(errno));
a521f4
+	goto exit;
2f13d7
+    }
a521f4
+    for (algo = 0; algo < algos_len; algo++) {
a521f4
+	fdFiniDigest(fdi, algos[algo], (void **)&filedigest, &filedigest_len, 0);
a521f4
+
a521f4
+	algo_name = pgpValString(PGPVAL_HASHALGO, algos[algo]);
a521f4
+	algo_name_len = (uint32_t)strlen(algo_name);
a521f4
+	algo_digest_len = (uint32_t)filedigest_len;
a521f4
+
a521f4
+	len = sizeof(algo_name_len);
a521f4
+	if (Fwrite(&algo_name_len, len, 1, fdo) != len) {
a521f4
+	    rpmlog(RPMLOG_ERR,
a521f4
+		   _("Unable to write digest algo name length: %d, %s\n"),
a521f4
+		   errno, strerror(errno));
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+	len = sizeof(algo_digest_len);
a521f4
+	if (Fwrite(&algo_digest_len, len, 1, fdo) != len) {
a521f4
+	    rpmlog(RPMLOG_ERR,
a521f4
+		   _("Unable to write number of bytes for digest: %d, %s\n"),
a521f4
+		   errno, strerror(errno));
a521f4
+	     goto exit;
a521f4
+	}
a521f4
+	if (Fwrite(algo_name, algo_name_len, 1, fdo) != algo_name_len) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write digest algo name: %d, %s\n"),
a521f4
+		   errno, strerror(errno));
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+	if (Fwrite(filedigest, algo_digest_len, 1, fdo ) != algo_digest_len) {
a521f4
+	    rpmlog(RPMLOG_ERR,
a521f4
+		   _("Unable to write digest value %u, %zu: %d, %s\n"),
a521f4
+		   algo_digest_len, filedigest_len,
a521f4
+		   errno, strerror(errno));
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+    }
a521f4
+    rc = RPMRC_OK;
a521f4
+exit:
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+/**
a521f4
+ * Check if package is in deny list.
a521f4
+ * @param package_name	package name
a521f4
+ * @return 		true if package is in deny list
a521f4
+ */
a521f4
+static inline int isInDenyList(char *package_name)
a521f4
+{
a521f4
+    int is_in_deny_list = 0;
a521f4
+    if (package_name) {
a521f4
+	char *e_denylist = getenv("LIBREPO_TRANSCODE_RPMS_DENYLIST");
a521f4
+	char *denytlist_item = strtok(e_denylist, ",");
a521f4
+	while (denytlist_item) {
a521f4
+	    if (strstr(package_name, denytlist_item)) {
a521f4
+		is_in_deny_list = 1;
a521f4
+		break;
a521f4
+	    }
a521f4
+	    denytlist_item = strtok(NULL, ",");
a521f4
+	}
a521f4
+    }
a521f4
+    return is_in_deny_list;
a521f4
+}
a521f4
+
a521f4
+static rpmRC FDWriteSignaturesValidation(FD_t fdo, int rpmvsrc, char *msg) {
a521f4
+    size_t len;
a521f4
+    rpmRC rc = RPMRC_FAIL;
a521f4
+
a521f4
+    if(rpmvsrc){
a521f4
+	rpmlog(RPMLOG_WARNING,
a521f4
+	       _("Error verifying package signatures:\n%s\n"), msg);
a521f4
+    }
a521f4
+
a521f4
+    len = sizeof(rpmvsrc);
a521f4
+    if (Fwrite(&rpmvsrc, len, 1, fdo) != len) {
a521f4
+	rpmlog(RPMLOG_ERR,
a521f4
+	       _("Unable to write signature verification RC code %d: %d, %s\n"),
a521f4
+	       rpmvsrc, errno, strerror(errno));
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    size_t content_len = msg ? strlen(msg) : 0;
a521f4
+    len = sizeof(content_len);
a521f4
+    if (Fwrite(&content_len, len, 1, fdo) != len) {
a521f4
+	rpmlog(RPMLOG_ERR,
a521f4
+	       _("Unable to write signature verification output length %zd: %d, %s\n"),
a521f4
+	       content_len, errno, strerror(errno));
a521f4
+	goto exit;
a521f4
+    }
a521f4
+    if (Fwrite(msg, content_len, 1, fdo) != content_len) {
a521f4
+	rpmlog(RPMLOG_ERR,
a521f4
+	       _("Unable to write signature verification output %s: %d, %s\n"),
a521f4
+	       msg, errno, strerror(errno));
a521f4
+	goto exit;
a521f4
+    }
a521f4
+
a521f4
+    rc = RPMRC_OK;
a521f4
+exit:
a521f4
+
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+static rpmRC validator(FD_t fdi, FD_t digesto, FD_t sigo,
a521f4
+	uint8_t algos[],
a521f4
+	uint32_t algos_len){
a521f4
+    int rpmvsrc;
a521f4
+    rpmRC rc = RPMRC_FAIL;
a521f4
+    char *msg = NULL;
a521f4
+    rpmts ts = rpmtsCreate();
a521f4
+
a521f4
+    rpmtsSetRootDir(ts, rpmcliRootDir);
a521f4
+
a521f4
+    FDDigestInit(fdi, algos, algos_len);
a521f4
+
a521f4
+    rpmvsrc = rpmcliVerifySignaturesFD(ts, fdi, &msg;;
a521f4
+
a521f4
+    // Write result of digest computation
a521f4
+    if(FDWriteDigests(fdi, digesto, algos, algos_len) != RPMRC_OK) {
a521f4
+	rpmlog(RPMLOG_ERR, _("Failed to write digests: %d, %s\n"),
a521f4
+	       errno, strerror(errno));
a521f4
+	goto exit;
a521f4
+    }
a521f4
+
a521f4
+    // Write result of signature validation.
a521f4
+    if(FDWriteSignaturesValidation(sigo, rpmvsrc, msg)) {
a521f4
+	rpmlog(RPMLOG_ERR,
a521f4
+	       _("Failed to write signature verification result: %d, %s\n"),
a521f4
+	       errno, strerror(errno));
a521f4
+	goto exit;
2f13d7
+    }
2f13d7
+    rc = RPMRC_OK;
2f13d7
+exit:
a521f4
+    if(msg) {
a521f4
+	free(msg);
a521f4
+    }
a521f4
+    rpmtsFree(ts);
2f13d7
+    return rc;
2f13d7
+}
2f13d7
+
a521f4
+static void sanitizeSignatureHeader(Header * sigh)
a521f4
+{
a521f4
+    struct rpmtd_s td;
a521f4
+
a521f4
+    /* This is inspired by the code in unloadImmutableRegion. See https://github.com/rpm-software-management/rpm/pull/1330 */
a521f4
+    if (!headerGet(*sigh, RPMTAG_HEADERSIGNATURES, &td, HEADERGET_DEFAULT)) {
a521f4
+	/* Signature header corrupt/missing */
a521f4
+	rpmlog(RPMLOG_WARNING, _("Error verifying signature header\n"));
a521f4
+	rpmtdFreeData(&td);
a521f4
+	Header nh = headerCopy(*sigh);
a521f4
+	headerFree(*sigh);
a521f4
+	*sigh = headerLink(nh);
a521f4
+	headerFree(nh);
a521f4
+    }
a521f4
+    rpmtdFreeData(&td);
a521f4
+}
a521f4
+
a521f4
+static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi)
2f13d7
+{
2f13d7
+    uint32_t diglen;
2f13d7
+    /* GNU C extension: can use diglen from outer context */
a521f4
+    int digestSetCmp(const unsigned char * a, const unsigned char * b) {
a521f4
+	return memcmp(a, b, diglen);
2f13d7
+    }
2f13d7
+
a521f4
+    unsigned int digestSetHash(const unsigned char * digest) {
2f13d7
+        /* assumes sizeof(unsigned int) < diglen */
2f13d7
+        return *(unsigned int *)digest;
2f13d7
+    }
2f13d7
+
a521f4
+    int digestoffsetCmp(const void * a, const void * b) {
a521f4
+	return digestSetCmp(
a521f4
+	    ((struct digestoffset *)a)->digest,
a521f4
+	    ((struct digestoffset *)b)->digest
a521f4
+	);
2f13d7
+    }
2f13d7
+
2f13d7
+    FD_t fdo;
2f13d7
+    FD_t gzdi;
2f13d7
+    Header h, sigh;
2f13d7
+    long fundamental_block_size = sysconf(_SC_PAGESIZE);
2f13d7
+    rpmRC rc = RPMRC_OK;
2f13d7
+    rpm_mode_t mode;
2f13d7
+    char *rpmio_flags = NULL, *zeros;
2f13d7
+    const unsigned char *digest;
a521f4
+    rpm_loff_t pos, size, pad, digest_pos, validation_pos, digest_table_pos;
2f13d7
+    uint32_t offset_ix = 0;
2f13d7
+    size_t len;
2f13d7
+    int next = 0;
a521f4
+    struct rpmlead_s l;
a521f4
+    rpmfiles files = NULL;
a521f4
+    rpmfi fi = NULL;
a521f4
+    char *msg = NULL;
a521f4
+    struct digestoffset *offsets = NULL;
a521f4
+    digestSet ds = NULL;
2f13d7
+
2f13d7
+    fdo = fdDup(STDOUT_FILENO);
2f13d7
+
a521f4
+    rc = rpmLeadReadAndReturn(fdi, &msg, &l);
a521f4
+    if (rc != RPMRC_OK)
a521f4
+	goto exit;
2f13d7
+
a521f4
+    /* Skip conversion if package is in deny list */
a521f4
+    if (isInDenyList(l.name)) {
a521f4
+	rpmlog(RPMLOG_WARNING, _("package %s is in deny list: conversion skipped\n"), l.name);
a521f4
+	if (rpmLeadWrite(fdo, l)) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write package lead: %s\n"),
a521f4
+		    Fstrerror(fdo));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
2f13d7
+
a521f4
+	ssize_t fdilength = ufdCopy(fdi, fdo);
a521f4
+	if (fdilength == -1) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("process_package cat failed\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
2f13d7
+
a521f4
+	goto exit;
a521f4
+    } else {
a521f4
+	if (rpmReadPackageRaw(fdi, &sigh, &h)) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Error reading package\n"));
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
2f13d7
+
a521f4
+	sanitizeSignatureHeader(&sigh;;
2f13d7
+
a521f4
+	if (rpmLeadWriteFromHeader(fdo, h)) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write package lead: %s\n"),
a521f4
+		   Fstrerror(fdo));
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
2f13d7
+
a521f4
+	if (rpmWriteSignature(fdo, sigh)) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write signature: %s\n"),
a521f4
+		   Fstrerror(fdo));
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
2f13d7
+
a521f4
+	if (headerWrite(fdo, h, HEADER_MAGIC_YES)) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write headers: %s\n"),
a521f4
+		   Fstrerror(fdo));
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
2f13d7
+
a521f4
+	/* Retrieve payload size and compression type. */
a521f4
+	{
a521f4
+	    const char *compr =
a521f4
+		headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
a521f4
+	    rpmio_flags =
a521f4
+		rstrscat(NULL, "r.", compr ? compr : "gzip", NULL);
a521f4
+	}
2f13d7
+
a521f4
+	gzdi = Fdopen(fdi, rpmio_flags);	/* XXX gzdi == fdi */
a521f4
+	free(rpmio_flags);
2f13d7
+
a521f4
+	if (gzdi == NULL) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("cannot re-open payload: %s\n"),
a521f4
+		   Fstrerror(gzdi));
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
2f13d7
+
a521f4
+	files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER);
a521f4
+	fi = rpmfiNewArchiveReader(gzdi, files,
a521f4
+				   RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST);
a521f4
+
a521f4
+	/* this is encoded in the file format, so needs to be fixed size (for
a521f4
+	 * now?)
a521f4
+	 */
a521f4
+	diglen = (uint32_t) rpmDigestLength(rpmfiDigestAlgo(fi));
a521f4
+	ds = digestSetCreate(rpmfiFC(fi), digestSetHash, digestSetCmp, NULL);
a521f4
+	offsets = xcalloc(rpmfiFC(fi), sizeof(*offsets));
a521f4
+	pos = RPMLEAD_SIZE + headerSizeof(sigh, HEADER_MAGIC_YES);
a521f4
+
a521f4
+	/* main headers are aligned to 8 byte boundry */
a521f4
+	pos += pad_to(pos, 8);
a521f4
+	pos += headerSizeof(h, HEADER_MAGIC_YES);
a521f4
+
a521f4
+	zeros = xcalloc(fundamental_block_size, 1);
a521f4
+
a521f4
+	while (next >= 0) {
a521f4
+	    next = rpmfiNext(fi);
a521f4
+	    if (next == RPMERR_ITER_END) {
a521f4
+		rc = RPMRC_OK;
a521f4
+		break;
a521f4
+	    }
a521f4
+	    mode = rpmfiFMode(fi);
a521f4
+	    if (!S_ISREG(mode) || !rpmfiArchiveHasContent(fi)) {
a521f4
+		/* not a regular file, or the archive doesn't contain any content
a521f4
+		 * for this entry.
a521f4
+		 */
a521f4
+		continue;
a521f4
+	    }
a521f4
+	    digest = rpmfiFDigest(fi, NULL, NULL);
a521f4
+	    if (digestSetGetEntry(ds, digest, NULL)) {
a521f4
+		/* This specific digest has already been included, so skip it. */
a521f4
+		continue;
a521f4
+	    }
a521f4
+	    pad = pad_to(pos, fundamental_block_size);
a521f4
+	    if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
a521f4
+		rpmlog(RPMLOG_ERR, _("Unable to write padding\n"));
a521f4
+		rc = RPMRC_FAIL;
a521f4
+		goto exit;
a521f4
+	    }
a521f4
+	    /* round up to next fundamental_block_size */
a521f4
+	    pos += pad;
a521f4
+	    digestSetAddEntry(ds, digest);
a521f4
+	    offsets[offset_ix].digest = digest;
a521f4
+	    offsets[offset_ix].pos = pos;
a521f4
+	    offset_ix++;
a521f4
+	    size = rpmfiFSize(fi);
a521f4
+	    rc = rpmfiArchiveReadToFile(fi, fdo, 0);
a521f4
+	    if (rc != RPMRC_OK) {
a521f4
+		char *errstr = rpmfileStrerror(rc);
a521f4
+		rpmlog(RPMLOG_ERR,
a521f4
+		       _("rpmfiArchiveReadToFile failed while extracting "
a521f4
+			 "\"%s\" with RC %d: %s\n"),
a521f4
+		       rpmfiFN(fi), rc, errstr);
a521f4
+		free(errstr);
a521f4
+		goto exit;
a521f4
+	    }
a521f4
+	    pos += size;
a521f4
+	}
a521f4
+	Fclose(gzdi);		/* XXX gzdi == fdi */
2f13d7
+
a521f4
+	qsort(offsets, (size_t) offset_ix, sizeof(struct digestoffset),
a521f4
+	      digestoffsetCmp);
2f13d7
+
a521f4
+	validation_pos = pos;
a521f4
+	ssize_t validation_len = ufdCopy(validationi, fdo);
a521f4
+	if (validation_len == -1) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("validation output ufdCopy failed\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
2f13d7
+
a521f4
+	digest_table_pos = validation_pos + validation_len;
a521f4
+
a521f4
+	len = sizeof(offset_ix);
a521f4
+	if (Fwrite(&offset_ix, len, 1, fdo) != len) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write length of table\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+	len = sizeof(diglen);
a521f4
+	if (Fwrite(&diglen, len, 1, fdo) != len) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write length of digest\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+	len = sizeof(rpm_loff_t);
a521f4
+	for (int x = 0; x < offset_ix; x++) {
a521f4
+	    if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) {
a521f4
+		rpmlog(RPMLOG_ERR, _("Unable to write digest\n"));
a521f4
+		rc = RPMRC_FAIL;
a521f4
+		goto exit;
a521f4
+	    }
a521f4
+	    if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) {
a521f4
+		rpmlog(RPMLOG_ERR, _("Unable to write offset\n"));
a521f4
+		rc = RPMRC_FAIL;
a521f4
+		goto exit;
a521f4
+	    }
a521f4
+	}
a521f4
+	digest_pos =
a521f4
+	    (digest_table_pos + sizeof(offset_ix) + sizeof(diglen) +
a521f4
+	     offset_ix * (diglen + sizeof(rpm_loff_t))
a521f4
+	    );
a521f4
+
a521f4
+	ssize_t digest_len = ufdCopy(digestori, fdo);
a521f4
+	if (digest_len == -1) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("digest table ufdCopy failed\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+
a521f4
+	/* add more padding so the last file can be cloned. It doesn't matter that
a521f4
+	 * the table and validation etc are in this space. In fact, it's pretty
a521f4
+	 * efficient if it is.
a521f4
+	 */
a521f4
+
a521f4
+	pad =
a521f4
+	    pad_to((validation_pos + validation_len +
a521f4
+		    2 * sizeof(rpm_loff_t) + sizeof(uint64_t)),
a521f4
+		   fundamental_block_size);
a521f4
+	if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write final padding\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
a521f4
+	zeros = _free(zeros);
a521f4
+	struct extents_footer_t footer = {.offsets =
a521f4
+		{ validation_pos, digest_table_pos, digest_pos },.magic =
a521f4
+	    EXTENTS_MAGIC };
a521f4
+	len = sizeof(footer);
a521f4
+	if (Fwrite(&footer, len, 1, fdo) != len) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Unable to write footer\n"));
a521f4
+	    rc = RPMRC_FAIL;
a521f4
+	    goto exit;
a521f4
+	}
2f13d7
+    }
2f13d7
+
a521f4
+  exit:
2f13d7
+    rpmfilesFree(files);
2f13d7
+    rpmfiFree(fi);
2f13d7
+    headerFree(h);
a521f4
+    headerFree(sigh);
a521f4
+    free(offsets);
a521f4
+    Fclose(fdo);
a521f4
+    digestSetFree(ds);
2f13d7
+    return rc;
2f13d7
+}
2f13d7
+
a521f4
+static off_t ufdTee(FD_t sfd, FD_t *fds, int len)
2f13d7
+{
a521f4
+    char buf[BUFSIZ];
a521f4
+    ssize_t rdbytes, wrbytes;
a521f4
+    off_t total = 0;
a521f4
+
a521f4
+    while (1) {
a521f4
+	rdbytes = Fread(buf, sizeof(buf[0]), sizeof(buf), sfd);
a521f4
+
a521f4
+	if (rdbytes > 0) {
a521f4
+	    for(int i=0; i < len; i++) {
a521f4
+		wrbytes = Fwrite(buf, sizeof(buf[0]), rdbytes, fds[i]);
a521f4
+		if (wrbytes != rdbytes) {
a521f4
+		    rpmlog(RPMLOG_ERR,
a521f4
+			   _("Error wriing to FD %d: %s\n"),
a521f4
+			   i, Fstrerror(fds[i]));
a521f4
+		    total = -1;
a521f4
+		    break;
a521f4
+		}
a521f4
+	    }
a521f4
+	    if(total == -1){
a521f4
+		break;
a521f4
+	    }
a521f4
+	    total += wrbytes;
a521f4
+	} else {
a521f4
+	    if (rdbytes < 0)
a521f4
+		total = -1;
a521f4
+	    break;
a521f4
+	}
a521f4
+    }
a521f4
+
a521f4
+    return total;
a521f4
+}
a521f4
+
a521f4
+static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) {
a521f4
+    rpmRC rc = RPMRC_FAIL;
a521f4
+    off_t offt = -1;
a521f4
+    // tee-ed stdin
a521f4
+    int processorpipefd[2];
a521f4
+    int validatorpipefd[2];
a521f4
+    // metadata
a521f4
+    int meta_digestpipefd[2];
a521f4
+    int meta_rpmsignpipefd[2];
a521f4
+
a521f4
+    pid_t cpids[2], w;
2f13d7
+    int wstatus;
a521f4
+    FD_t fds[2];
2f13d7
+
a521f4
+     if (pipe(processorpipefd) == -1) {
a521f4
+	rpmlog(RPMLOG_ERR, _("Processor pipe failure\n"));
a521f4
+	return RPMRC_FAIL;
a521f4
+    }
2f13d7
+
a521f4
+    if (pipe(validatorpipefd) == -1) {
a521f4
+	rpmlog(RPMLOG_ERR, _("Validator pipe failure\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+
a521f4
+    if (pipe(meta_digestpipefd) == -1) {
a521f4
+	rpmlog(RPMLOG_ERR, _("Meta digest pipe failure\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+
a521f4
+    if (pipe(meta_rpmsignpipefd) == -1) {
a521f4
+	rpmlog(RPMLOG_ERR, _("Meta rpm signature pipe failure\n"));
a521f4
+	return RPMRC_FAIL;
2f13d7
+    }
2f13d7
+
a521f4
+    cpids[0] = fork();
a521f4
+    if (cpids[0] == 0) {
a521f4
+	/* child: validator */
a521f4
+	close(processorpipefd[0]);
a521f4
+	close(processorpipefd[1]);
a521f4
+	close(validatorpipefd[1]);
a521f4
+	close(meta_digestpipefd[0]);
a521f4
+	close(meta_rpmsignpipefd[0]);
a521f4
+	FD_t fdi = fdDup(validatorpipefd[0]);
a521f4
+	FD_t digesto = fdDup(meta_digestpipefd[1]);
a521f4
+	FD_t sigo = fdDup(meta_rpmsignpipefd[1]);
a521f4
+	close(meta_digestpipefd[1]);
a521f4
+	close(meta_rpmsignpipefd[1]);
a521f4
+	rc = validator(fdi, digesto, sigo, algos, algos_len);
a521f4
+	if(rc != RPMRC_OK) {
a521f4
+	    rpmlog(RPMLOG_ERR, _("Validator failed with RC %d\n"), rc);
a521f4
+	}
a521f4
+	Fclose(fdi);
a521f4
+	Fclose(digesto);
a521f4
+	Fclose(sigo);
a521f4
+	if (rc != RPMRC_OK) {
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
a521f4
+	exit(EXIT_SUCCESS);
a521f4
+    } else {
a521f4
+	/* parent: main program */
a521f4
+	cpids[1] = fork();
a521f4
+	if (cpids[1] == 0) {
a521f4
+	    /* child: process_package */
a521f4
+	    close(validatorpipefd[0]);
a521f4
+	    close(validatorpipefd[1]);
a521f4
+	    close(processorpipefd[1]);
a521f4
+	    close(meta_digestpipefd[1]);
a521f4
+	    close(meta_rpmsignpipefd[1]);
a521f4
+	    FD_t fdi = fdDup(processorpipefd[0]);
a521f4
+	    close(processorpipefd[0]);
a521f4
+	    FD_t sigi = fdDup(meta_rpmsignpipefd[0]);
a521f4
+	    close(meta_rpmsignpipefd[0]);
a521f4
+	    FD_t digestori = fdDup(meta_digestpipefd[0]);
a521f4
+	    close(meta_digestpipefd[0]);
a521f4
+
a521f4
+	    rc = process_package(fdi, digestori, sigi);
a521f4
+	    if(rc != RPMRC_OK) {
a521f4
+		rpmlog(RPMLOG_ERR, _("Package processor failed: %d\n"), rc);
a521f4
+	    }
a521f4
+	    Fclose(digestori);
a521f4
+	    Fclose(sigi);
a521f4
+	    /* fdi is normally closed through the stacked file gzdi in the
a521f4
+	     * function
a521f4
+	     */
a521f4
+
a521f4
+	    if (rc != RPMRC_OK) {
a521f4
+		exit(EXIT_FAILURE);
a521f4
+	    }
a521f4
+	    exit(EXIT_SUCCESS);
a521f4
+
a521f4
+
a521f4
+	} else {
a521f4
+	    /* Actual parent. Read from fdi and write to both processes */
a521f4
+	    close(processorpipefd[0]);
a521f4
+	    close(validatorpipefd[0]);
a521f4
+	    fds[0] = fdDup(processorpipefd[1]);
a521f4
+	    fds[1] = fdDup(validatorpipefd[1]);
a521f4
+	    close(validatorpipefd[1]);
a521f4
+	    close(processorpipefd[1]);
a521f4
+	    close(meta_digestpipefd[0]);
a521f4
+	    close(meta_digestpipefd[1]);
a521f4
+	    close(meta_rpmsignpipefd[0]);
a521f4
+	    close(meta_rpmsignpipefd[1]);
2f13d7
+
a521f4
+	    rc = RPMRC_OK;
a521f4
+	    offt = ufdTee(fdi, fds, 2);
a521f4
+	    if(offt == -1){
a521f4
+		rpmlog(RPMLOG_ERR, _("Failed to tee RPM\n"));
a521f4
+		rc = RPMRC_FAIL;
a521f4
+	    }
a521f4
+	    Fclose(fds[0]);
a521f4
+	    Fclose(fds[1]);
a521f4
+	    w = waitpid(cpids[0], &wstatus, 0);
a521f4
+	    if (w == -1) {
a521f4
+		rpmlog(RPMLOG_ERR, _("waitpid cpids[0] failed\n"));
a521f4
+		rc = RPMRC_FAIL;
a521f4
+	    }
a521f4
+	    w = waitpid(cpids[1], &wstatus, 0);
a521f4
+	    if (w == -1) {
a521f4
+		rpmlog(RPMLOG_ERR, _("waitpid cpids[1] failed\n"));
a521f4
+		rc = RPMRC_FAIL;
a521f4
+	    }
a521f4
+	}
2f13d7
+    }
a521f4
+
a521f4
+    return rc;
a521f4
+}
a521f4
+
a521f4
+int main(int argc, char *argv[]) {
a521f4
+    rpmRC rc;
a521f4
+    poptContext optCon = NULL;
a521f4
+    const char **args = NULL;
a521f4
+    int nb_algos = 0;
a521f4
+
a521f4
+    xsetprogname(argv[0]);	/* Portability call -- see system.h */
a521f4
+    rpmReadConfigFiles(NULL, NULL);
a521f4
+    optCon = rpmcliInit(argc, argv, optionsTable);
a521f4
+    poptSetOtherOptionHelp(optCon, "[OPTIONS]* <DIGESTALGO>");
a521f4
+
a521f4
+    if (poptPeekArg(optCon) == NULL) {
a521f4
+	rpmlog(RPMLOG_ERR,
a521f4
+	       _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n"));
a521f4
+	poptPrintUsage(optCon, stderr, 0);
a521f4
+	exit(EXIT_FAILURE);
2f13d7
+    }
a521f4
+
a521f4
+    args = poptGetArgs(optCon);
a521f4
+
a521f4
+    for (nb_algos=0; args[nb_algos]; nb_algos++);
a521f4
+    uint8_t algos[nb_algos];
a521f4
+    for (int x = 0; x < nb_algos; x++) {
a521f4
+	if (pgpStringVal(PGPVAL_HASHALGO, args[x], &algos[x]) != 0)
a521f4
+	{
a521f4
+	    rpmlog(RPMLOG_ERR,
a521f4
+		   _("Unable to resolve '%s' as a digest algorithm, exiting\n"),
a521f4
+		   args[x]);
a521f4
+	    exit(EXIT_FAILURE);
a521f4
+	}
2f13d7
+    }
a521f4
+
a521f4
+    FD_t fdi = fdDup(STDIN_FILENO);
a521f4
+    rc = teeRpm(fdi, algos, nb_algos);
a521f4
+    Fclose(fdi);
a521f4
+    if (rc != RPMRC_OK) {
a521f4
+	/* translate rpmRC into generic failure return code. */
a521f4
+	return EXIT_FAILURE;
2f13d7
+    }
2f13d7
+    return EXIT_SUCCESS;
2f13d7
+}
2f13d7
diff --git a/rpmio/rpmpgp.c b/rpmio/rpmpgp.c
a521f4
index 43a2a99..f8d5562 100644
2f13d7
--- a/rpmio/rpmpgp.c
2f13d7
+++ b/rpmio/rpmpgp.c
bd9c00
@@ -298,6 +298,16 @@ int pgpValTok(pgpValTbl vs, const char * s, const char * se)
2f13d7
     return vs->val;
2f13d7
 }
2f13d7
 
2f13d7
+int pgpStringVal(pgpValType type, const char *str, uint8_t *val)
2f13d7
+{
2f13d7
+    pgpValTbl tbl = pgpValTable(type);
2f13d7
+    if (tbl == NULL) return -1;
2f13d7
+    int v = pgpValTok(tbl, str, str + strlen(str));
2f13d7
+    if (v == -1) return -1;
2f13d7
+    *val = (uint8_t)v;
2f13d7
+    return 0;
2f13d7
+}
2f13d7
+
2f13d7
 /** \ingroup rpmpgp
2f13d7
  * Decode length from 1, 2, or 5 octet body length encoding, used in
2f13d7
  * new format packet headers and V4 signature subpackets.
2f13d7
diff --git a/rpmio/rpmpgp.h b/rpmio/rpmpgp.h
a521f4
index 469b5b3..7a1fefd 100644
2f13d7
--- a/rpmio/rpmpgp.h
2f13d7
+++ b/rpmio/rpmpgp.h
2f13d7
@@ -973,6 +973,15 @@ typedef rpmFlags rpmDigestFlags;
2f13d7
  */
2f13d7
 const char * pgpValString(pgpValType type, uint8_t val);
2f13d7
 
2f13d7
+/** \ingroup rpmpgp
2f13d7
+ * Return  OpenPGP value for a string.
2f13d7
+ * @param type		type of value
2f13d7
+ * @param str		string to lookup
2f13d7
+ * @param[out] val  byte value associated with string
2f13d7
+ * @return		0 on success else -1
2f13d7
+ */
2f13d7
+int pgpStringVal(pgpValType type, const char *str, uint8_t *val);
2f13d7
+
2f13d7
 /** \ingroup rpmpgp
2f13d7
  * Return (native-endian) integer from big-endian representation.
2f13d7
  * @param s		pointer to big-endian integer
a521f4
diff --git a/scripts/rpm2extents_dump b/scripts/rpm2extents_dump
a521f4
new file mode 100755
a521f4
index 0000000..596a59a
a521f4
--- /dev/null
a521f4
+++ b/scripts/rpm2extents_dump
a521f4
@@ -0,0 +1,94 @@
a521f4
+#!/usr/bin/env python3
a521f4
+
a521f4
+import argparse
a521f4
+import binascii
a521f4
+import os
a521f4
+import struct
a521f4
+import sys
a521f4
+
a521f4
+MAGIC_SIZE = 8
a521f4
+MAGIC_STR = b'KWTSH100'
a521f4
+
a521f4
+POS_SIZE = 8
a521f4
+
a521f4
+def keep_position(func):
a521f4
+    def wrapper(*args, **kwargs):
a521f4
+        curr = args[0].tell()
a521f4
+        res = func(*args, **kwargs)
a521f4
+        f.seek(curr, os.SEEK_SET)
a521f4
+        return res
a521f4
+    return wrapper
a521f4
+
a521f4
+def read_validation_digest(f, validation_offset):
a521f4
+	digests = []
a521f4
+    # validation
a521f4
+	f.seek(validation_offset, os.SEEK_SET)
a521f4
+	val_content_len, val_digests_num = struct.unpack('=QI', f.read(8+4))
a521f4
+	for i in range(val_digests_num):
a521f4
+		algo_name_len, digest_len = struct.unpack('=II', f.read(8))
a521f4
+		algo_name, digest = struct.unpack(f'{algo_name_len}s{digest_len}s', f.read(algo_name_len+digest_len))
a521f4
+		digests.append((algo_name, binascii.hexlify(digest)))
a521f4
+	return digests
a521f4
+
a521f4
+
a521f4
+def read_digests_table(f, digest_offset):
a521f4
+	digests = []
a521f4
+    # validation
a521f4
+	f.seek(digest_offset, os.SEEK_SET)
a521f4
+	table_len, digest_len = struct.unpack('=II', f.read(8))
a521f4
+
a521f4
+	for i in range(table_len):
a521f4
+		digest, pos = struct.unpack(f'{digest_len}sQ', f.read(digest_len + 8))
a521f4
+		digests.append((pos, binascii.hexlify(digest)))
a521f4
+	return digests
a521f4
+
a521f4
+def read_signature_output(f, signature_offset):
a521f4
+    f.seek(signature_offset, os.SEEK_SET)
a521f4
+    signature_rc, signature_output_len = struct.unpack('=IQ', f.read(12))
a521f4
+    return signature_rc, f.read(signature_output_len)
a521f4
+
a521f4
+@keep_position
a521f4
+def parse_file(f):
a521f4
+	digests = []
a521f4
+	pos_table_offset = f.seek(-8 - 3*POS_SIZE, os.SEEK_END)
a521f4
+	signature_offset, digest_offset, validation_offset = struct.unpack('=QQQ', f.read(3*POS_SIZE))
a521f4
+
a521f4
+	validation_digests = read_validation_digest(f, validation_offset)
a521f4
+	digests_table = read_digests_table(f, digest_offset)
a521f4
+	signature_ouput = read_signature_output(f, signature_offset)
a521f4
+
a521f4
+	return validation_digests, digests_table, signature_ouput
a521f4
+
a521f4
+@keep_position
a521f4
+def is_transcoded(f):
a521f4
+    f.seek(-MAGIC_SIZE, os.SEEK_END)
a521f4
+    magic = f.read(MAGIC_SIZE)
a521f4
+    return magic == MAGIC_STR
a521f4
+
a521f4
+def arg_parse():
a521f4
+    parser = argparse.ArgumentParser()
a521f4
+    parser.add_argument('--dump-signature', action='store_true')
a521f4
+    parser.add_argument('--dump-file-digest-table', action='store_true')
a521f4
+    parser.add_argument('--dump-digests', action='store_true')
a521f4
+    parser.add_argument('file')
a521f4
+
a521f4
+    return parser.parse_args()
a521f4
+
a521f4
+if __name__ == '__main__':
a521f4
+    args = arg_parse()
a521f4
+    f = open(args.file, 'rb')
a521f4
+    if not is_transcoded(f):
a521f4
+        sys.exit(1)
a521f4
+
a521f4
+    validation_digests, digests_table, signature_output = parse_file(f)
a521f4
+    if(args.dump_file_digest_table):
a521f4
+        for digest in digests_table:
a521f4
+            print(f"FileDigest {hex(digest[0])}: {digest[1]}")
a521f4
+
a521f4
+    if(args.dump_digests):
a521f4
+        for validation_digest in validation_digests:
a521f4
+            print(f"HeaderDigest {validation_digest[0]} {validation_digest[1]}")
a521f4
+
a521f4
+    if(args.dump_signature):
a521f4
+        print(f"RPMSignOutput RC {signature_output[0]}\nRPMSignOutput Content {signature_output[1].decode()}")
a521f4
+
a521f4
diff --git a/sign/rpmgensig.c b/sign/rpmgensig.c
a521f4
index 4608e1a..6159088 100644
a521f4
--- a/sign/rpmgensig.c
a521f4
+++ b/sign/rpmgensig.c
a521f4
@@ -610,7 +610,7 @@ static int rpmSign(const char *rpm, int deleting, int flags)
a521f4
 	}
a521f4
 
a521f4
 	/* Write the lead/signature of the output rpm */
a521f4
-	rc = rpmLeadWrite(ofd, h);
a521f4
+	rc = rpmLeadWriteFromHeader(ofd, h);
a521f4
 	if (rc != RPMRC_OK) {
a521f4
 	    rpmlog(RPMLOG_ERR, _("%s: writeLead failed: %s\n"), trpm,
a521f4
 		Fstrerror(ofd));
a521f4
diff --git a/tests/Makefile.am b/tests/Makefile.am
a521f4
index 6d9f1f8..4a588a6 100644
a521f4
--- a/tests/Makefile.am
a521f4
+++ b/tests/Makefile.am
a521f4
@@ -36,6 +36,7 @@ TESTSUITE_AT += rpmspec.at
a521f4
 TESTSUITE_AT += rpmio.at
a521f4
 TESTSUITE_AT += rpmorder.at
a521f4
 TESTSUITE_AT += rpmvfylevel.at
a521f4
+TESTSUITE_AT += rpm2extents.at
a521f4
 EXTRA_DIST += $(TESTSUITE_AT)
a521f4
 
a521f4
 ## testsuite data
a521f4
diff --git a/tests/atlocal.in b/tests/atlocal.in
a521f4
index 7d1deb8..ff0703a 100644
a521f4
--- a/tests/atlocal.in
a521f4
+++ b/tests/atlocal.in
a521f4
@@ -50,6 +50,19 @@ else
a521f4
     CAP_DISABLED=true;
a521f4
 fi
a521f4
 
a521f4
+FSTYPE=$(stat -f -c %T /)
a521f4
+REFLINKABLE_FS=("xfs" "brtfs")
a521f4
+
a521f4
+REFLINK_DISABLED=true;
a521f4
+for item in "${REFLINKABLE_FS[@]}"
a521f4
+do
a521f4
+    if test "${FSTYPE}" = "${item}"
a521f4
+    then
a521f4
+	REFLINK_DISABLED=false;
a521f4
+	break
a521f4
+    fi
a521f4
+done
a521f4
+
a521f4
 function setup_env()
a521f4
 {
a521f4
     if [ -d testing ]; then
a521f4
@@ -82,6 +95,15 @@ function runroot()
a521f4
     )
a521f4
 }
a521f4
 
a521f4
+function runroot_plugins()
a521f4
+{
a521f4
+    setup_env
a521f4
+    (unset RPM_CONFIGDIR RPM_POPTEXEC_PATH; cd ${RPMTEST} && \
a521f4
+     MAGIC="/magic/magic" FAKECHROOT_BASE="${RPMTEST}" fakechroot "$@" --define "_buildhost testhost" --define "_topdir /build" --nouserns
a521f4
+    )
a521f4
+}
a521f4
+
a521f4
+
a521f4
 function runroot_other()
a521f4
 {
a521f4
     setup_env
a521f4
diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at
a521f4
new file mode 100644
a521f4
index 0000000..c9c79c5
a521f4
--- /dev/null
a521f4
+++ b/tests/rpm2extents.at
a521f4
@@ -0,0 +1,151 @@
a521f4
+#    rpm2extents.at: Some very basic checks
a521f4
+#
a521f4
+#    Copyright (C) 2022  Manu Bretelle <chantr4@gmail.com>
a521f4
+#
a521f4
+#    This program is free software; you can redistribute it and/or modify
a521f4
+#    it under the terms of the GNU General Public License as published by
a521f4
+#    the Free Software Foundation; either version 2 of the License, or
a521f4
+#    (at your option) any later version.
a521f4
+#
a521f4
+#    This program is distributed in the hope that it will be useful,
a521f4
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
a521f4
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
a521f4
+#    GNU General Public License for more details.
a521f4
+#
a521f4
+#    You should have received a copy of the GNU General Public License
a521f4
+#    along with this program; if not, write to the Free Software
a521f4
+#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
a521f4
+
a521f4
+AT_BANNER([rpm2extents tests])
a521f4
+
a521f4
+# ------------------------------
a521f4
+
a521f4
+# check that transcoder write magic at the end
a521f4
+AT_SETUP([rpm2extents magic])
a521f4
+AT_KEYWORDS([rpm2extents])
a521f4
+AT_CHECK([runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 | tail -c8],
a521f4
+[0],
a521f4
+[KWTSH100],
a521f4
+[ignore])
a521f4
+AT_CLEANUP
a521f4
+
a521f4
+# Check that transcoder writes checksig return code and content.
a521f4
+#
a521f4
+AT_SETUP([rpm2extents signature])
a521f4
+AT_KEYWORDS([rpm2extents])
a521f4
+AT_CHECK([
a521f4
+RPMDB_INIT
a521f4
+
a521f4
+runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > /tmp/hello-2.0-1.x86_64-signed.rpm 2> /dev/null
a521f4
+rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm
a521f4
+runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
a521f4
+runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > /tmp/hello-2.0-1.x86_64-signed.rpm
a521f4
+rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm
a521f4
+],
a521f4
+[0],
a521f4
+[RPMSignOutput RC 2
a521f4
+RPMSignOutput Content     Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
a521f4
+    Header SHA256 digest: OK
a521f4
+    Header SHA1 digest: OK
a521f4
+    Payload SHA256 digest: OK
a521f4
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
a521f4
+    MD5 digest: OK
a521f4
+
a521f4
+RPMSignOutput RC 0
a521f4
+RPMSignOutput Content     Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
a521f4
+    Header SHA256 digest: OK
a521f4
+    Header SHA1 digest: OK
a521f4
+    Payload SHA256 digest: OK
a521f4
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
a521f4
+    MD5 digest: OK
a521f4
+
a521f4
+],
a521f4
+[])
a521f4
+AT_CLEANUP
a521f4
+
a521f4
+AT_SETUP([rpm2extents signature verification])
a521f4
+AT_KEYWORDS([rpm2extents])
a521f4
+AT_CHECK([
a521f4
+RPMDB_INIT
a521f4
+
a521f4
+runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64-signed.rpm 2> /dev/null
a521f4
+runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $?
a521f4
+runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
a521f4
+runroot_other cat /data/RPMS/hello-2.0-1.x86_64-signed.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64-signed.rpm
a521f4
+runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $?
a521f4
+],
a521f4
+[0],
a521f4
+[/tmp/hello-2.0-1.x86_64-signed.rpm:
a521f4
+    Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
a521f4
+    Header SHA256 digest: OK
a521f4
+    Header SHA1 digest: OK
a521f4
+    Payload SHA256 digest: OK
a521f4
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
a521f4
+    MD5 digest: OK
a521f4
+1
a521f4
+/tmp/hello-2.0-1.x86_64-signed.rpm:
a521f4
+    Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
a521f4
+    Header SHA256 digest: OK
a521f4
+    Header SHA1 digest: OK
a521f4
+    Payload SHA256 digest: OK
a521f4
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
a521f4
+    MD5 digest: OK
a521f4
+0
a521f4
+],
a521f4
+[])
a521f4
+AT_CLEANUP
a521f4
+
a521f4
+# check that package in denylist is not transcoded
a521f4
+AT_SETUP([rpm2extents denylist])
a521f4
+AT_KEYWORDS([rpm2extents])
a521f4
+AT_CHECK([
a521f4
+export LIBREPO_TRANSCODE_RPMS_DENYLIST="vim,hello,cowsay"
a521f4
+runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 | runroot_other cmp /data/RPMS/hello-2.0-1.x86_64.rpm -],
a521f4
+[0],
a521f4
+[],
a521f4
+[ignore])
a521f4
+AT_CLEANUP
a521f4
+
a521f4
+AT_SETUP([rpm2extents install package])
a521f4
+AT_KEYWORDS([rpm2extents reflink])
a521f4
+AT_SKIP_IF([$REFLINK_DISABLED])
a521f4
+AT_CHECK([
a521f4
+RPMDB_INIT
a521f4
+
a521f4
+runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/hello-2.0-1.x86_64.rpm 2> /dev/null
a521f4
+runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm
a521f4
+test -f ${RPMTEST}/usr/bin/hello
a521f4
+],
a521f4
+[0],
a521f4
+[],
a521f4
+[])
a521f4
+AT_CLEANUP
a521f4
+
a521f4
+AT_SETUP([reflink ignores non-transcoded package])
a521f4
+AT_KEYWORDS([reflink])
a521f4
+AT_CHECK([
a521f4
+RPMDB_INIT
a521f4
+
a521f4
+runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /data/RPMS/hello-2.0-1.x86_64.rpm && exit $?
a521f4
+# Check that the file is properly installed in chroot
a521f4
+test -f ${RPMTEST}/usr/bin/hello
a521f4
+],
a521f4
+[0],
a521f4
+[],
a521f4
+[])
a521f4
+AT_CLEANUP
a521f4
+
a521f4
+AT_SETUP([reflink hardlink package])
a521f4
+AT_KEYWORDS([reflink hardlink])
a521f4
+AT_SKIP_IF([$REFLINK_DISABLED])
a521f4
+AT_CHECK([
a521f4
+RPMDB_INIT
a521f4
+
a521f4
+PKG=hlinktest-1.0-1.noarch.rpm
a521f4
+runroot_other cat /data/RPMS/${PKG} | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/${PKG} 2> /dev/null
a521f4
+runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /tmp/${PKG}
a521f4
+],
a521f4
+[0],
a521f4
+[],
a521f4
+[])
a521f4
+AT_CLEANUP
a521f4
diff --git a/tests/rpmtests.at b/tests/rpmtests.at
a521f4
index 48b86bd..e219a16 100644
a521f4
--- a/tests/rpmtests.at
a521f4
+++ b/tests/rpmtests.at
a521f4
@@ -22,3 +22,4 @@ m4_include([rpmreplace.at])
a521f4
 m4_include([rpmconfig.at])
a521f4
 m4_include([rpmconfig2.at])
a521f4
 m4_include([rpmconfig3.at])
a521f4
+m4_include([rpm2extents.at])
2f13d7
-- 
a521f4
2.47.0
2f13d7