af2e7f
From e38a26f5d6050a5c72f7503c8664ca11623ed319 Mon Sep 17 00:00:00 2001
af2e7f
From: Matteo Croce <teknoraver@meta.com>
af2e7f
Date: Fri, 6 Dec 2024 07:45:17 +0100
af2e7f
Subject: [PATCH] RPM with Copy on Write
af2e7f
af2e7f
This is part of https://fedoraproject.org/wiki/Changes/RPMCoW
af2e7f
af2e7f
The majority of changes are in two new programs:
af2e7f
af2e7f
= rpm2extents
af2e7f
af2e7f
Modeled as a 'stream processor'. It reads a regular .rpm file on stdin,
af2e7f
and produces a modified .rpm file on stdout. The lead, signature and
af2e7f
headers are preserved 1:1 to allow all the normal metadata inspection,
af2e7f
signature verification to work as expected. Only the 'payload' is
af2e7f
modified.
af2e7f
af2e7f
The primary motivation for this tool is to re-organize the payload as a
af2e7f
sequence of raw file extents (hence the name). The files are organized
af2e7f
by their digest identity instead of path/filename. If any digest is
af2e7f
repeated, then the file is skipped/de-duped. Only regular files are
af2e7f
represented. All other entries like directories, symlinks, devices are
af2e7f
fully described in the headers and are omitted.
af2e7f
af2e7f
The files are padded so they start on `sysconf(_SC_PAGESIZE)` boundries
af2e7f
to permit 'reflink' syscalls to work in the `reflink` plugin.
af2e7f
af2e7f
At the end of the file is a footer with 3 sections:
af2e7f
af2e7f
1. List of calculated digests of the input stream. This is used in
af2e7f
   `librepo` because the file *written* is a derivative, and not the
af2e7f
   same as the repo metadata describes. `rpm2extents` takes one or more
af2e7f
   positional arguments that described which digest algorithms are
af2e7f
   desired. This is often just `SHA256`. This program is only measuring
af2e7f
   and recording the digest - it does not express an opinion on whether
af2e7f
   the file is correct. Due to the API on most compression libraries
af2e7f
   directly reading the source file, the whole file digest is measured
af2e7f
   using a subprocess and pipes. I don't love it, but it works.
af2e7f
2. Sorted List of file content digests + offset pairs. This is used in
af2e7f
   the plugin with a trivial binary search to locate the start of file
af2e7f
   content. The size is not needed because it's part of normal headers.
af2e7f
3. (offset of 1., offset of 2., 8 byte MAGIC value) triple
af2e7f
af2e7f
= reflink plugin
af2e7f
af2e7f
Looks for the 8 byte magic value at the end of the rpm file. If present
af2e7f
it alters the `RPMTAG_PAYLOADFORMAT` in memory to `clon`, and reads in
af2e7f
the digest-> offset table.
af2e7f
af2e7f
`rpmPackageFilesInstall()` in `fsm.c` is
af2e7f
modified to alter the enumeration strategy from
af2e7f
`rpmfiNewArchiveReader()` to `rpmfilesIter()` if not `cpio`. This is
af2e7f
needed because there is no cpio to enumerate. In the same function, if
af2e7f
`rpmpluginsCallFsmFilePre()` returns `RPMRC_PLUGIN_CONTENTS` then
af2e7f
`fsmMkfile()` is skipped as it is assumed the plugin did the work.
af2e7f
af2e7f
The majority of the work is in `reflink_fsm_file_pre()` - the per file
af2e7f
hook for RPM plugins. If the file enumerated in
af2e7f
`rpmPackageFilesInstall()` is a regular file, this function will look up
af2e7f
the offset in the digest->offset table and will try to reflink it, then
af2e7f
fall back to a regular copy. If reflinking does work: we will have
af2e7f
reflinked a whole number of pages, so we truncate the file to the
af2e7f
expected size. Therefore installing most files does involve two writes:
af2e7f
the reflink of the full size, then a fork/copy on write for the last
af2e7f
page worth.
af2e7f
af2e7f
If the file passed to `reflink_fsm_file_pre()` is anything other than a
af2e7f
regular file, it return `RPMRC_OK` so the normal mechanics of
af2e7f
`rpmPackageFilesInstall()` are used. That handles directories, symlinks
af2e7f
and other non file types.
af2e7f
af2e7f
= New API for internal use
af2e7f
af2e7f
1. `rpmReadPackageRaw()` is used within `rpm2extents` to read all the
af2e7f
   headers without trying to validate signatures. This eliminates the
af2e7f
   runtime dependency on rpmdb.
af2e7f
2. `rpmteFd()` exposes the Fd behind the rpmte, so plugins can interact
af2e7f
   with the rpm itself.
af2e7f
3. `RPMRC_PLUGIN_CONTENTS` in `rpmRC_e` for use in
af2e7f
   `rpmpluginsCallFsmFilePre()` specifically.
af2e7f
4. `pgpStringVal()` is used to help parse the command line in
af2e7f
   `rpm2extents` - the positional arguments are strings, and this
af2e7f
   converts the values back to the values in the table.
af2e7f
af2e7f
Nothing has been removed, and none of the changes are intended to be
af2e7f
used externally, so I don't think a soname bump is warranted here.
af2e7f
af2e7f
Co-authored-by: Matthew Almond <malmond@meta.com>
af2e7f
---
af2e7f
 build/pack.c                      |   2 +-
af2e7f
 include/rpm/rpmcli.h              |  10 +
af2e7f
 include/rpm/rpmextents_internal.h |  58 +++
af2e7f
 include/rpm/rpmlib.h              |   9 +
af2e7f
 include/rpm/rpmpgp.h              |   9 +
af2e7f
 include/rpm/rpmte.h               |   2 +
af2e7f
 include/rpm/rpmtypes.h            |   3 +-
af2e7f
 lib/CMakeLists.txt                |   1 +
af2e7f
 lib/fsm.c                         |  45 +-
af2e7f
 lib/package.c                     |  36 ++
af2e7f
 lib/rpmchecksig.c                 | 116 ++++-
af2e7f
 lib/rpmextents.c                  | 109 +++++
af2e7f
 lib/rpmlead.c                     |  43 +-
af2e7f
 lib/rpmlead.h                     |  37 +-
af2e7f
 lib/rpmplugin.h                   |   9 +
af2e7f
 lib/rpmplugins.c                  |  94 +++-
af2e7f
 lib/rpmplugins.h                  |  17 +
af2e7f
 lib/rpmte.c                       |   5 +
af2e7f
 lib/transaction.c                 |  29 +-
af2e7f
 macros.in                         |   1 +
af2e7f
 plugins/CMakeLists.txt            |   4 +-
af2e7f
 plugins/reflink.c                 | 401 +++++++++++++++++
af2e7f
 rpmio/rpmpgp.c                    |  28 ++
af2e7f
 rpmio/rpmpgp_internal.c           |  18 -
af2e7f
 scripts/CMakeLists.txt            |   2 +-
af2e7f
 scripts/rpm2extents_dump          |  94 ++++
af2e7f
 tests/CMakeLists.txt              |   1 +
af2e7f
 tests/atlocal.in                  |  21 +
af2e7f
 tests/rpm2extents.at              | 151 +++++++
af2e7f
 tests/rpmtests.at                 |   1 +
af2e7f
 tools/CMakeLists.txt              |   5 +-
af2e7f
 tools/rpm2extents.c               | 707 ++++++++++++++++++++++++++++++
af2e7f
 32 files changed, 1978 insertions(+), 90 deletions(-)
af2e7f
 create mode 100644 include/rpm/rpmextents_internal.h
af2e7f
 create mode 100644 lib/rpmextents.c
af2e7f
 create mode 100644 plugins/reflink.c
af2e7f
 create mode 100755 scripts/rpm2extents_dump
af2e7f
 create mode 100644 tests/rpm2extents.at
af2e7f
 create mode 100644 tools/rpm2extents.c
af2e7f
af2e7f
diff --git a/build/pack.c b/build/pack.c
af2e7f
index f7dac6d..c38b1a9 100644
af2e7f
--- a/build/pack.c
af2e7f
+++ b/build/pack.c
af2e7f
@@ -495,7 +495,7 @@ static rpmRC writeRPM(Package pkg, unsigned char ** pkgidp,
af2e7f
     }
af2e7f
 
af2e7f
     /* Write the lead section into the package. */
af2e7f
-    if (rpmLeadWrite(fd, pkg->header)) {
af2e7f
+    if (rpmLeadWriteFromHeader(fd, pkg->header)) {
af2e7f
 	rpmlog(RPMLOG_ERR, _("Unable to write package: %s\n"), Fstrerror(fd));
af2e7f
 	goto exit;
af2e7f
     }
af2e7f
diff --git a/include/rpm/rpmcli.h b/include/rpm/rpmcli.h
af2e7f
index 3e5900d..09274e3 100644
af2e7f
--- a/include/rpm/rpmcli.h
af2e7f
+++ b/include/rpm/rpmcli.h
af2e7f
@@ -421,6 +421,16 @@ int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv);
af2e7f
  */
af2e7f
 int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv);
af2e7f
 
af2e7f
+
af2e7f
+/** \ingroup rpmcli
af2e7f
+ * Verify package signatures.
af2e7f
+ * @param ts		transaction set
af2e7f
+ * @param fd		a file descriptor to verify
af2e7f
+ * @param msg		a string containing textual information about the verification, similar to rpmcliVerifySignatures output.
af2e7f
+ * @return		0 on success
af2e7f
+ */
af2e7f
+int rpmcliVerifySignaturesFD(rpmts ts, FD_t fd, char **msg);
af2e7f
+
af2e7f
 #ifdef __cplusplus
af2e7f
 }
af2e7f
 #endif
af2e7f
diff --git a/include/rpm/rpmextents_internal.h b/include/rpm/rpmextents_internal.h
af2e7f
new file mode 100644
af2e7f
index 0000000..0a3318c
af2e7f
--- /dev/null
af2e7f
+++ b/include/rpm/rpmextents_internal.h
af2e7f
@@ -0,0 +1,58 @@
af2e7f
+#ifndef _RPMEXTENTS_INTERNAL_H
af2e7f
+#define _RPMEXTENTS_INTERNAL_H
af2e7f
+
af2e7f
+#ifdef __cplusplus
af2e7f
+extern "C" {
af2e7f
+#endif
af2e7f
+
af2e7f
+#include <stdint.h>
af2e7f
+
af2e7f
+/** \ingroup rpmextents
af2e7f
+ * RPM extents library
af2e7f
+ */
af2e7f
+
af2e7f
+/* magic value at end of file (64 bits) that indicates this is a transcoded
af2e7f
+ * rpm.
af2e7f
+ */
af2e7f
+#define EXTENTS_MAGIC 3472329499408095051
af2e7f
+
af2e7f
+typedef uint64_t extents_magic_t;
af2e7f
+
af2e7f
+struct __attribute__ ((__packed__)) extents_footer_offsets_t {
af2e7f
+    off64_t checksig_offset;
af2e7f
+    off64_t table_offset;
af2e7f
+    off64_t csum_offset;
af2e7f
+};
af2e7f
+
af2e7f
+struct __attribute__ ((__packed__)) extents_footer_t {
af2e7f
+    struct extents_footer_offsets_t offsets;
af2e7f
+    extents_magic_t magic;
af2e7f
+};
af2e7f
+
af2e7f
+/** \ingroup rpmextents
af2e7f
+ * Checks the results of the signature verification ran during transcoding.
af2e7f
+ * @param fd	The FD_t of the transcoded RPM
af2e7f
+ * @param print_content Whether or not to print the result from rpmsig
af2e7f
+ * @return	The number of checks that `rpmvsVerify` failed during transcoding.
af2e7f
+ */
af2e7f
+int extentsVerifySigs(FD_t fd, int print_content);
af2e7f
+
af2e7f
+/** \ingroup rpmextents
af2e7f
+ * Read the RPM Extents footer from a file descriptor.
af2e7f
+ * @param fd		The FD_t of the transcoded RPM
af2e7f
+ * @param[out] footer	A pointer to an allocated extents_footer_t with a copy of the footer.
af2e7f
+ * @return		RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure.
af2e7f
+ */
af2e7f
+rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer);
af2e7f
+
af2e7f
+/** \ingroup rpmextents
af2e7f
+ * Check if a RPM is a transcoded RPM
af2e7f
+ * @param fd	The FD_t of the transcoded RPM
af2e7f
+ * return	RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure.
af2e7f
+ */
af2e7f
+rpmRC isTranscodedRpm(FD_t fd);
af2e7f
+
af2e7f
+#ifdef __cplusplus
af2e7f
+}
af2e7f
+#endif
af2e7f
+#endif /* _RPMEXTENTS_INTERNAL_H */
af2e7f
diff --git a/include/rpm/rpmlib.h b/include/rpm/rpmlib.h
af2e7f
index db38397..b11f87f 100644
af2e7f
--- a/include/rpm/rpmlib.h
af2e7f
+++ b/include/rpm/rpmlib.h
af2e7f
@@ -155,6 +155,15 @@ rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg);
af2e7f
 rpmRC rpmReadPackageFile(rpmts ts, FD_t fd,
af2e7f
 		const char * fn, Header * hdrp);
af2e7f
 
af2e7f
+/** \ingroup header
af2e7f
+ * Return package signature, header from file handle, no verification.
af2e7f
+ * @param fd		file handle
af2e7f
+ * @param[out] sigp		address of header (or NULL)
af2e7f
+ * @param[out] hdrp		address of header (or NULL)
af2e7f
+ * @return		RPMRC_OK on success
af2e7f
+ */
af2e7f
+rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp);
af2e7f
+
af2e7f
 /** \ingroup rpmtrans
af2e7f
  * Install source package.
af2e7f
  * @param ts		transaction set
af2e7f
diff --git a/include/rpm/rpmpgp.h b/include/rpm/rpmpgp.h
af2e7f
index a7eecbe..24e9617 100644
af2e7f
--- a/include/rpm/rpmpgp.h
af2e7f
+++ b/include/rpm/rpmpgp.h
af2e7f
@@ -962,6 +962,15 @@ typedef enum pgpValType_e {
af2e7f
  */
af2e7f
 const char * pgpValString(pgpValType type, uint8_t val);
af2e7f
 
af2e7f
+/** \ingroup rpmpgp
af2e7f
+ * Return  OpenPGP value for a string.
af2e7f
+ * @param type		type of value
af2e7f
+ * @param str		string to lookup
af2e7f
+ * @param[out] val  byte value associated with string
af2e7f
+ * @return		0 on success else -1
af2e7f
+ */
af2e7f
+int pgpStringVal(pgpValType type, const char *str, uint8_t *val);
af2e7f
+
af2e7f
 /** \ingroup rpmpgp
af2e7f
  * Return (native-endian) integer from big-endian representation.
af2e7f
  * @param s		pointer to big-endian integer
af2e7f
diff --git a/include/rpm/rpmte.h b/include/rpm/rpmte.h
af2e7f
index effcdab..499fd4f 100644
af2e7f
--- a/include/rpm/rpmte.h
af2e7f
+++ b/include/rpm/rpmte.h
af2e7f
@@ -209,6 +209,8 @@ const char * rpmteNEVR(rpmte te);
af2e7f
  */
af2e7f
 const char * rpmteNEVRA(rpmte te);
af2e7f
 
af2e7f
+FD_t rpmteFd(rpmte te);
af2e7f
+
af2e7f
 /** \ingroup rpmte
af2e7f
  * Retrieve key from transaction element.
af2e7f
  * @param te		transaction element
af2e7f
diff --git a/include/rpm/rpmtypes.h b/include/rpm/rpmtypes.h
af2e7f
index 7c14553..552ec52 100644
af2e7f
--- a/include/rpm/rpmtypes.h
af2e7f
+++ b/include/rpm/rpmtypes.h
af2e7f
@@ -106,7 +106,8 @@ typedef	enum rpmRC_e {
af2e7f
     RPMRC_NOTFOUND	= 1,	/*!< Generic not found code. */
af2e7f
     RPMRC_FAIL		= 2,	/*!< Generic failure code. */
af2e7f
     RPMRC_NOTTRUSTED	= 3,	/*!< Signature is OK, but key is not trusted. */
af2e7f
-    RPMRC_NOKEY		= 4	/*!< Public key is unavailable. */
af2e7f
+    RPMRC_NOKEY		= 4,	/*!< Public key is unavailable. */
af2e7f
+    RPMRC_PLUGIN_CONTENTS = 5     /*!< fsm_file_pre plugin is handling content */
af2e7f
 } rpmRC;
af2e7f
 
af2e7f
 #ifdef __cplusplus
af2e7f
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
af2e7f
index 2528a64..754a198 100644
af2e7f
--- a/lib/CMakeLists.txt
af2e7f
+++ b/lib/CMakeLists.txt
af2e7f
@@ -41,6 +41,7 @@ target_sources(librpm PRIVATE
af2e7f
 	rpmchroot.c rpmchroot.h
af2e7f
 	rpmplugins.c rpmplugins.h rpmplugin.h rpmug.c rpmug.h
af2e7f
 	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h
af2e7f
+	rpmextents.c
af2e7f
 )
af2e7f
 
af2e7f
 if(ENABLE_SQLITE)
af2e7f
diff --git a/lib/fsm.c b/lib/fsm.c
af2e7f
index 36708ac..2df7db7 100644
af2e7f
--- a/lib/fsm.c
af2e7f
+++ b/lib/fsm.c
af2e7f
@@ -869,6 +869,24 @@ static rpmfi fsmIterFini(rpmfi fi, struct diriter_s *di)
af2e7f
     return rpmfiFree(fi);
af2e7f
 }
af2e7f
 
af2e7f
+static int fiIterator(rpmPlugins plugins, FD_t payload, rpmfiles files, rpmfi *fi)
af2e7f
+{
af2e7f
+    rpmRC plugin_rc = rpmpluginsCallFsmFileArchiveReader(plugins, payload, files, fi);
af2e7f
+    switch (plugin_rc) {
af2e7f
+	case RPMRC_PLUGIN_CONTENTS:
af2e7f
+	    if (*fi == NULL)
af2e7f
+                return RPMERR_BAD_MAGIC;
af2e7f
+            return RPMRC_OK;
af2e7f
+	case RPMRC_OK:
af2e7f
+	    *fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
af2e7f
+	    if (*fi == NULL)
af2e7f
+                return RPMERR_BAD_MAGIC;
af2e7f
+            return RPMRC_OK;
af2e7f
+	default:
af2e7f
+            return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+}
af2e7f
+
af2e7f
 int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
af2e7f
               rpmpsm psm, char ** failedFile)
af2e7f
 {
af2e7f
@@ -920,8 +938,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
af2e7f
     if (rc)
af2e7f
 	goto exit;
af2e7f
 
af2e7f
-    fi = fsmIter(payload, files,
af2e7f
-		 payload ? RPMFI_ITER_READ_ARCHIVE : RPMFI_ITER_FWD, &di);
af2e7f
+    rc = fiIterator(plugins, payload, files, &fi);
af2e7f
+    if (rc)
af2e7f
+        goto exit;
af2e7f
 
af2e7f
     if (fi == NULL) {
af2e7f
         rc = RPMERR_BAD_MAGIC;
af2e7f
@@ -944,6 +963,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
af2e7f
         if (!fp->skip) {
af2e7f
 	    int mayopen = 0;
af2e7f
 	    int fd = -1;
af2e7f
+
af2e7f
+	    if (di.dirfd >= 0)
af2e7f
+		fsmClose(&di.dirfd);
af2e7f
 	    rc = ensureDir(plugins, rpmfiDN(fi), 0,
af2e7f
 			    (fp->action == FA_CREATE), 0, &di.dirfd);
af2e7f
 
af2e7f
@@ -953,9 +975,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
af2e7f
 	    }
af2e7f
 
af2e7f
 	    /* Run fsm file pre hook for all plugins */
af2e7f
-	    if (!rc)
af2e7f
+	    if (!rc) {
af2e7f
 		rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
af2e7f
 					      fp->sb.st_mode, fp->action);
af2e7f
+	    }
af2e7f
 	    if (rc)
af2e7f
 		goto setmeta; /* for error notification */
af2e7f
 
af2e7f
@@ -983,11 +1006,18 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
af2e7f
 	    if (fp->action == FA_TOUCH)
af2e7f
 		goto setmeta;
af2e7f
 
af2e7f
-            if (S_ISREG(fp->sb.st_mode)) {
af2e7f
+	    rpmRC plugin_rc = rpmpluginsCallFsmFileInstall(plugins, fi, fp->fpath, fp->sb.st_mode, fp->action);
af2e7f
+	    if (!(plugin_rc == RPMRC_PLUGIN_CONTENTS || plugin_rc == RPMRC_OK)){
af2e7f
+		rc = plugin_rc;
af2e7f
+	    } else if(plugin_rc == RPMRC_PLUGIN_CONTENTS){
af2e7f
+		rc = RPMRC_OK;
af2e7f
+		/* The reflink plugins handles hardlink differently, metadata has to be set. */
af2e7f
+		fp->setmeta = 1;
af2e7f
+	    } else if (S_ISREG(fp->sb.st_mode)) {
af2e7f
 		if (rc == RPMERR_ENOENT) {
af2e7f
 		    rc = fsmMkfile(di.dirfd, fi, fp, files, psm, nodigest,
af2e7f
-				   &firstlink, &firstlinkfile, &di.firstdir,
af2e7f
-				   &fd;;
af2e7f
+				   &firstlink, &firstlinkfile,
af2e7f
+				   &di.firstdir, &fd;;
af2e7f
 		}
af2e7f
             } else if (S_ISDIR(fp->sb.st_mode)) {
af2e7f
                 if (rc == RPMERR_ENOENT) {
af2e7f
@@ -1056,10 +1086,13 @@ setmeta:
af2e7f
 
af2e7f
     /* If all went well, commit files to final destination */
af2e7f
     fi = fsmIter(NULL, files, RPMFI_ITER_FWD, &di);
af2e7f
+
af2e7f
     while (!rc && (fx = rpmfiNext(fi)) >= 0) {
af2e7f
 	struct filedata_s *fp = &fdata[fx];
af2e7f
 
af2e7f
 	if (!fp->skip) {
af2e7f
+	    if (di.dirfd >= 0)
af2e7f
+		fsmClose(&di.dirfd);
af2e7f
 	    if (!rc)
af2e7f
 		rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd);
af2e7f
 
af2e7f
diff --git a/lib/package.c b/lib/package.c
af2e7f
index 8eee368..d4256a7 100644
af2e7f
--- a/lib/package.c
af2e7f
+++ b/lib/package.c
af2e7f
@@ -394,5 +394,41 @@ exit:
af2e7f
     return rc;
af2e7f
 }
af2e7f
 
af2e7f
+rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp)
af2e7f
+{
af2e7f
+    char *msg = NULL;
af2e7f
+    hdrblob sigblob = hdrblobCreate();
af2e7f
+    hdrblob blob = hdrblobCreate();
af2e7f
+    Header h = NULL;
af2e7f
+    Header sigh = NULL;
af2e7f
+
af2e7f
+    rpmRC rc = hdrblobRead(fd, 1, 0, RPMTAG_HEADERSIGNATURES, sigblob, &msg;;
af2e7f
+    if (rc != RPMRC_OK)
af2e7f
+	goto exit;
af2e7f
+
af2e7f
+    rc = hdrblobRead(fd, 1, 1, RPMTAG_HEADERIMMUTABLE, blob, &msg;;
af2e7f
+    if (rc != RPMRC_OK)
af2e7f
+	goto exit;
af2e7f
+
af2e7f
+    rc = hdrblobImport(sigblob, 0, &sigh, &msg;;
af2e7f
+    if (rc)
af2e7f
+	goto exit;
af2e7f
+
af2e7f
+    rc = hdrblobImport(blob, 0, &h, &msg;;
af2e7f
+    if (rc)
af2e7f
+	goto exit;
af2e7f
+
af2e7f
+    *sigp = headerLink(sigh);
af2e7f
+    *hdrp = headerLink(h);
af2e7f
 
af2e7f
+exit:
af2e7f
+    if (rc != RPMRC_OK && msg)
af2e7f
+	rpmlog(RPMLOG_ERR, "%s: %s\n", Fdescr(fd), msg);
af2e7f
+    hdrblobFree(sigblob);
af2e7f
+    hdrblobFree(blob);
af2e7f
+    headerFree(sigh);
af2e7f
+    headerFree(h);
af2e7f
+    free(msg);
af2e7f
 
af2e7f
+    return rc;
af2e7f
+}
af2e7f
diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c
af2e7f
index 3a3a4bd..8257dd9 100644
af2e7f
--- a/lib/rpmchecksig.c
af2e7f
+++ b/lib/rpmchecksig.c
af2e7f
@@ -16,6 +16,7 @@
af2e7f
 #include <rpm/rpmlog.h>
af2e7f
 #include <rpm/rpmstring.h>
af2e7f
 #include <rpm/rpmkeyring.h>
af2e7f
+#include <rpm/rpmextents_internal.h>
af2e7f
 
af2e7f
 #include "rpmio_internal.h" 	/* fdSetBundle() */
af2e7f
 #include "rpmlead.h"
af2e7f
@@ -208,36 +209,24 @@ exit:
af2e7f
 }
af2e7f
 
af2e7f
 static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags,
af2e7f
-			   FD_t fd, const char *fn)
af2e7f
+			   FD_t fd, rpmsinfoCb cb, void *cbdata)
af2e7f
 {
af2e7f
     char *msg = NULL;
af2e7f
-    struct vfydata_s vd = { .seen = 0,
af2e7f
-			    .bad = 0,
af2e7f
-			    .verbose = rpmIsVerbose(),
af2e7f
-    };
af2e7f
     int rc;
af2e7f
-    struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring);
af2e7f
 
af2e7f
-    rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd.verbose ? "\n" : "");
af2e7f
+
af2e7f
+    if(isTranscodedRpm(fd) == RPMRC_OK){
af2e7f
+	return extentsVerifySigs(fd, 1);
af2e7f
+    }
af2e7f
+
af2e7f
+    struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring);
af2e7f
 
af2e7f
     rc = rpmpkgRead(vs, fd, NULL, NULL, &msg;;
af2e7f
 
af2e7f
     if (rc)
af2e7f
 	goto exit;
af2e7f
 
af2e7f
-    rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
af2e7f
-
af2e7f
-    if (!vd.verbose) {
af2e7f
-	if (vd.seen & RPMSIG_DIGEST_TYPE) {
af2e7f
-	    rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_DIGEST_TYPE) ?
af2e7f
-					_("DIGESTS") : _("digests"));
af2e7f
-	}
af2e7f
-	if (vd.seen & RPMSIG_SIGNATURE_TYPE) {
af2e7f
-	    rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_SIGNATURE_TYPE) ?
af2e7f
-					_("SIGNATURES") : _("signatures"));
af2e7f
-	}
af2e7f
-	rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK"));
af2e7f
-    }
af2e7f
+    rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, cb, cbdata);
af2e7f
 
af2e7f
 exit:
af2e7f
     if (rc && msg)
af2e7f
@@ -247,15 +236,39 @@ exit:
af2e7f
     return rc;
af2e7f
 }
af2e7f
 
af2e7f
+static void rpmkgVerifySigsPreLogging(struct vfydata_s *vd, const char *fn){
af2e7f
+    rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd->verbose ? "\n" : "");
af2e7f
+}
af2e7f
+
af2e7f
+static void rpmkgVerifySigsPostLogging(struct vfydata_s *vd, int rc){
af2e7f
+    if (!vd->verbose) {
af2e7f
+	if (vd->seen & RPMSIG_DIGEST_TYPE) {
af2e7f
+	    rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_DIGEST_TYPE) ?
af2e7f
+					_("DIGESTS") : _("digests"));
af2e7f
+	}
af2e7f
+	if (vd->seen & RPMSIG_SIGNATURE_TYPE) {
af2e7f
+	    rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_SIGNATURE_TYPE) ?
af2e7f
+					_("SIGNATURES") : _("signatures"));
af2e7f
+	}
af2e7f
+	rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK"));
af2e7f
+    }
af2e7f
+}
af2e7f
+
af2e7f
 /* Wrapper around rpmkVerifySigs to preserve API */
af2e7f
 int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn)
af2e7f
 {
af2e7f
     int rc = 1; /* assume failure */
af2e7f
+    struct vfydata_s vd = { .seen = 0,
af2e7f
+			    .bad = 0,
af2e7f
+			    .verbose = rpmIsVerbose(),
af2e7f
+    };
af2e7f
     if (ts && qva && fd && fn) {
af2e7f
 	rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
af2e7f
 	rpmVSFlags vsflags = rpmtsVfyFlags(ts);
af2e7f
 	int vfylevel = rpmtsVfyLevel(ts);
af2e7f
-	rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, fn);
af2e7f
+	rpmkgVerifySigsPreLogging(&vd, fn);
af2e7f
+	rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, vfyCb, &vd);
af2e7f
+	rpmkgVerifySigsPostLogging(&vd, rc);
af2e7f
     	rpmKeyringFree(keyring);
af2e7f
     }
af2e7f
     return rc;
af2e7f
@@ -277,12 +290,22 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
af2e7f
 
af2e7f
     while ((arg = *argv++) != NULL) {
af2e7f
 	FD_t fd = Fopen(arg, "r.ufdio");
af2e7f
+	struct vfydata_s vd = { .seen = 0,
af2e7f
+				.bad = 0,
af2e7f
+				.verbose = rpmIsVerbose(),
af2e7f
+	};
af2e7f
 	if (fd == NULL || Ferror(fd)) {
af2e7f
 	    rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"), 
af2e7f
 		     arg, Fstrerror(fd));
af2e7f
 	    res++;
af2e7f
-	} else if (rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, arg)) {
af2e7f
+	} else {
af2e7f
+	    rpmkgVerifySigsPreLogging(&vd, arg);
af2e7f
+	    int rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd,
af2e7f
+				      vfyCb, &vd);
af2e7f
+	    rpmkgVerifySigsPostLogging(&vd, rc);
af2e7f
+	    if (rc) {
af2e7f
 	    res++;
af2e7f
+	    }
af2e7f
 	}
af2e7f
 
af2e7f
 	Fclose(fd);
af2e7f
@@ -290,3 +313,52 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
af2e7f
     rpmKeyringFree(keyring);
af2e7f
     return res;
af2e7f
 }
af2e7f
+
af2e7f
+struct vfydatafd_s {
af2e7f
+    size_t len;
af2e7f
+    char msg[BUFSIZ];
af2e7f
+};
af2e7f
+
af2e7f
+
af2e7f
+static int vfyFDCb(struct rpmsinfo_s *sinfo, void *cbdata)
af2e7f
+{
af2e7f
+    struct vfydatafd_s *vd = cbdata;
af2e7f
+    char *vmsg, *msg;
af2e7f
+    size_t n;
af2e7f
+    size_t remainder = BUFSIZ - vd->len >= 0 ? BUFSIZ - vd->len : 0;
af2e7f
+
af2e7f
+    vmsg = rpmsinfoMsg(sinfo);
af2e7f
+    rasprintf(&msg, "    %s\n", vmsg);
af2e7f
+    n = rstrlcpy(vd->msg + vd->len, msg, remainder);
af2e7f
+    free(vmsg);
af2e7f
+    free(msg);
af2e7f
+    if(n <= remainder){
af2e7f
+	vd->len += n;
af2e7f
+    }
af2e7f
+    return 1;
af2e7f
+}
af2e7f
+
af2e7f
+
af2e7f
+int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi, char **msg)
af2e7f
+{
af2e7f
+    rpmRC rc = RPMRC_FAIL;
af2e7f
+    rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
af2e7f
+    rpmVSFlags vsflags = rpmtsVfyFlags(ts);
af2e7f
+    int vfylevel = rpmtsVfyLevel(ts);
af2e7f
+    struct vfydatafd_s vd = {.len = 0};
af2e7f
+
af2e7f
+    vsflags |= rpmcliVSFlags;
af2e7f
+    if (rpmcliVfyLevelMask) {
af2e7f
+	vfylevel &= ~rpmcliVfyLevelMask;
af2e7f
+	rpmtsSetVfyLevel(ts, vfylevel);
af2e7f
+    }
af2e7f
+
af2e7f
+    if (!rpmpkgVerifySigs(keyring, vfylevel, vsflags, fdi, vfyFDCb, &vd)) {
af2e7f
+	rc = RPMRC_OK;
af2e7f
+    }
af2e7f
+    *msg = strdup(vd.msg);
af2e7f
+
af2e7f
+    rpmKeyringFree(keyring);
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
diff --git a/lib/rpmextents.c b/lib/rpmextents.c
af2e7f
new file mode 100644
af2e7f
index 0000000..ef687d6
af2e7f
--- /dev/null
af2e7f
+++ b/lib/rpmextents.c
af2e7f
@@ -0,0 +1,109 @@
af2e7f
+
af2e7f
+#include "system.h"
af2e7f
+
af2e7f
+#include <rpm/rpmlog.h>
af2e7f
+#include <rpm/rpmio.h>
af2e7f
+#include <rpm/rpmextents_internal.h>
af2e7f
+#include <string.h>
af2e7f
+#include <errno.h>
af2e7f
+
af2e7f
+
af2e7f
+
af2e7f
+int extentsVerifySigs(FD_t fd, int print_content){
af2e7f
+    rpm_loff_t current;
af2e7f
+    int32_t rc;
af2e7f
+    size_t len;
af2e7f
+    uint64_t content_len;
af2e7f
+    char *content = NULL;
af2e7f
+    struct extents_footer_t footer;
af2e7f
+
af2e7f
+    current = Ftell(fd);
af2e7f
+
af2e7f
+    if(extentsFooterFromFD(fd, &footer) != RPMRC_OK) {
af2e7f
+	rc = -1;
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    if(Fseek(fd, footer.offsets.checksig_offset, SEEK_SET) < 0) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to seek signature verification offset\n"));
af2e7f
+	rc = -1;
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    len = sizeof(rc);
af2e7f
+    if (Fread(&rc, len, 1, fd) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read Signature Verification RC\n"));
af2e7f
+	rc = -1;
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+
af2e7f
+    if(print_content) {
af2e7f
+	len = sizeof(content_len);
af2e7f
+	if (Fread(&content_len, len, 1, fd) != len) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content length\n"));
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+
af2e7f
+	content = rmalloc(content_len + 1);
af2e7f
+	if(content == NULL) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to allocate memory to read signature content\n"));
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+	content[content_len] = 0;
af2e7f
+	if (Fread(content, content_len, 1, fd) != content_len) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content\n"));
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+
af2e7f
+	rpmlog(RPMLOG_NOTICE, "%s", content);
af2e7f
+    }
af2e7f
+exit:
af2e7f
+    if(content){
af2e7f
+	rfree(content);
af2e7f
+    }
af2e7f
+    if (Fseek(fd, current, SEEK_SET) < 0) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: unable to seek back to original location\n"));
af2e7f
+    }
af2e7f
+    return rc;
af2e7f
+
af2e7f
+}
af2e7f
+
af2e7f
+rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) {
af2e7f
+
af2e7f
+    rpmRC rc = RPMRC_NOTFOUND;
af2e7f
+    rpm_loff_t current;
af2e7f
+    size_t len;
af2e7f
+
af2e7f
+    // If the file is not seekable, we cannot detect whether or not it is transcoded.
af2e7f
+    if(Fseek(fd, 0, SEEK_CUR) < 0) {
af2e7f
+        return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    current = Ftell(fd);
af2e7f
+
af2e7f
+    len = sizeof(struct extents_footer_t);
af2e7f
+    if(Fseek(fd, -len, SEEK_END) < 0) {
af2e7f
+	rc = RPMRC_FAIL;
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    if (Fread(footer, len, 1, fd) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read footer\n"));
af2e7f
+	rc = RPMRC_FAIL;
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    if (footer->magic != EXTENTS_MAGIC) {
af2e7f
+	rc = RPMRC_NOTFOUND;
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    rc = RPMRC_OK;
af2e7f
+exit:
af2e7f
+    if (Fseek(fd, current, SEEK_SET) < 0) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to seek back to original location\n"));
af2e7f
+	rc = RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+rpmRC isTranscodedRpm(FD_t fd) {
af2e7f
+    struct extents_footer_t footer;
af2e7f
+    return extentsFooterFromFD(fd, &footer);
af2e7f
+}
af2e7f
+
af2e7f
+
af2e7f
diff --git a/lib/rpmlead.c b/lib/rpmlead.c
af2e7f
index d0db60a..e4504d2 100644
af2e7f
--- a/lib/rpmlead.c
af2e7f
+++ b/lib/rpmlead.c
af2e7f
@@ -24,24 +24,6 @@ static unsigned char const lead_magic[] = {
af2e7f
     RPMLEAD_MAGIC0, RPMLEAD_MAGIC1, RPMLEAD_MAGIC2, RPMLEAD_MAGIC3
af2e7f
 };
af2e7f
 
af2e7f
-/** \ingroup lead
af2e7f
- * The lead data structure.
af2e7f
- * The lead needs to be 8 byte aligned.
af2e7f
- * @deprecated The lead (except for signature_type) is legacy.
af2e7f
- * @todo Don't use any information from lead.
af2e7f
- */
af2e7f
-struct rpmlead_s {
af2e7f
-    unsigned char magic[4];
af2e7f
-    unsigned char major;
af2e7f
-    unsigned char minor;
af2e7f
-    short type;
af2e7f
-    short archnum;
af2e7f
-    char name[66];
af2e7f
-    short osnum;
af2e7f
-    short signature_type;       /*!< Signature header type (RPMSIG_HEADERSIG) */
af2e7f
-    char reserved[16];      /*!< Pad to 96 bytes -- 8 byte aligned! */
af2e7f
-};
af2e7f
-
af2e7f
 static int rpmLeadFromHeader(Header h, struct rpmlead_s *l)
af2e7f
 {
af2e7f
     if (h != NULL) {
af2e7f
@@ -70,13 +52,23 @@ static int rpmLeadFromHeader(Header h, struct rpmlead_s *l)
af2e7f
 }
af2e7f
 
af2e7f
 /* The lead needs to be 8 byte aligned */
af2e7f
-rpmRC rpmLeadWrite(FD_t fd, Header h)
af2e7f
+rpmRC rpmLeadWriteFromHeader(FD_t fd, Header h)
af2e7f
 {
af2e7f
     rpmRC rc = RPMRC_FAIL;
af2e7f
     struct rpmlead_s l;
af2e7f
 
af2e7f
-    if (rpmLeadFromHeader(h, &l)) {
af2e7f
-	
af2e7f
+    if (rpmLeadFromHeader(h, &l)) {	
af2e7f
+	rc = rpmLeadWrite(fd, l);
af2e7f
+    }
af2e7f
+
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+/* The lead needs to be 8 byte aligned */
af2e7f
+rpmRC rpmLeadWrite(FD_t fd, struct rpmlead_s l)
af2e7f
+{
af2e7f
+    rpmRC rc = RPMRC_FAIL;
af2e7f
+
af2e7f
 	l.type = htons(l.type);
af2e7f
 	l.archnum = htons(l.archnum);
af2e7f
 	l.osnum = htons(l.osnum);
af2e7f
@@ -84,7 +76,6 @@ rpmRC rpmLeadWrite(FD_t fd, Header h)
af2e7f
 	    
af2e7f
 	if (Fwrite(&l, 1, sizeof(l), fd) == sizeof(l))
af2e7f
 	    rc = RPMRC_OK;
af2e7f
-    }
af2e7f
 
af2e7f
     return rc;
af2e7f
 }
af2e7f
@@ -99,6 +90,11 @@ static rpmRC rpmLeadCheck(struct rpmlead_s *lead, char **msg)
af2e7f
 }
af2e7f
 
af2e7f
 rpmRC rpmLeadRead(FD_t fd, char **emsg)
af2e7f
+{
af2e7f
+    return rpmLeadReadAndReturn(fd, emsg, NULL);
af2e7f
+}
af2e7f
+
af2e7f
+rpmRC rpmLeadReadAndReturn(FD_t fd, char **emsg, struct rpmlead_s * ret)
af2e7f
 {
af2e7f
     rpmRC rc = RPMRC_OK;
af2e7f
     struct rpmlead_s l;
af2e7f
@@ -128,5 +124,8 @@ rpmRC rpmLeadRead(FD_t fd, char **emsg)
af2e7f
 	    free(err);
af2e7f
     }
af2e7f
 
af2e7f
+	if (ret)
af2e7f
+		*ret = l;
af2e7f
+
af2e7f
     return rc;
af2e7f
 }
af2e7f
diff --git a/lib/rpmlead.h b/lib/rpmlead.h
af2e7f
index 80db44e..4ba2ba9 100644
af2e7f
--- a/lib/rpmlead.h
af2e7f
+++ b/lib/rpmlead.h
af2e7f
@@ -19,13 +19,39 @@ extern "C" {
af2e7f
 
af2e7f
 #define RPMLEAD_SIZE 96         /*!< Don't rely on sizeof(struct) */
af2e7f
 
af2e7f
+/** \ingroup lead
af2e7f
+ * The lead data structure.
af2e7f
+ * The lead needs to be 8 byte aligned.
af2e7f
+ * @deprecated The lead (except for signature_type) is legacy.
af2e7f
+ * @todo Don't use any information from lead.
af2e7f
+ */
af2e7f
+struct rpmlead_s {
af2e7f
+    unsigned char magic[4];
af2e7f
+    unsigned char major;
af2e7f
+    unsigned char minor;
af2e7f
+    short type;
af2e7f
+    short archnum;
af2e7f
+    char name[66];
af2e7f
+    short osnum;
af2e7f
+    short signature_type;       /*!< Signature header type (RPMSIG_HEADERSIG) */
af2e7f
+    char reserved[16];      /*!< Pad to 96 bytes -- 8 byte aligned! */
af2e7f
+};
af2e7f
+
af2e7f
 /** \ingroup lead
af2e7f
  * Write lead to file handle.
af2e7f
  * @param fd		file handle
af2e7f
  * @param h		package header
af2e7f
  * @return		RPMRC_OK on success, RPMRC_FAIL on error
af2e7f
  */
af2e7f
-rpmRC rpmLeadWrite(FD_t fd, Header h);
af2e7f
+rpmRC rpmLeadWriteFromHeader(FD_t fd, Header h);
af2e7f
+
af2e7f
+/** \ingroup lead
af2e7f
+ * Write lead to file handle.
af2e7f
+ * @param fd		file handle
af2e7f
+ * @param l		lead
af2e7f
+ * @return		RPMRC_OK on success, RPMRC_FAIL on error
af2e7f
+ */
af2e7f
+rpmRC rpmLeadWrite(FD_t fd, struct rpmlead_s l);
af2e7f
 
af2e7f
 /** \ingroup lead
af2e7f
  * Read lead from file handle.
af2e7f
@@ -35,6 +61,15 @@ rpmRC rpmLeadWrite(FD_t fd, Header h);
af2e7f
  */
af2e7f
 rpmRC rpmLeadRead(FD_t fd, char **emsg);
af2e7f
 
af2e7f
+/** \ingroup lead
af2e7f
+ * Read lead from file handle and return it.
af2e7f
+ * @param fd		file handle
af2e7f
+ * @param[out] emsg		failure message on error (malloced)
af2e7f
+ * @param[out] ret		address of lead
af2e7f
+ * @return		RPMRC_OK on success, RPMRC_FAIL/RPMRC_NOTFOUND on error
af2e7f
+ */
af2e7f
+rpmRC rpmLeadReadAndReturn(FD_t fd, char **emsg, struct rpmlead_s * ret);
af2e7f
+
af2e7f
 #ifdef __cplusplus
af2e7f
 }
af2e7f
 #endif
af2e7f
diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h
af2e7f
index fab4b3e..c82d6be 100644
af2e7f
--- a/lib/rpmplugin.h
af2e7f
+++ b/lib/rpmplugin.h
af2e7f
@@ -60,6 +60,13 @@ typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi,
af2e7f
 					      int fd, const char* path,
af2e7f
 					      const char *dest,
af2e7f
 					      mode_t file_mode, rpmFsmOp op);
af2e7f
+typedef rpmRC (*plugin_fsm_file_install_func)(rpmPlugin plugin, rpmfi fi,
af2e7f
+					      const char* path,
af2e7f
+					      mode_t file_mode, rpmFsmOp op);
af2e7f
+typedef rpmRC (*plugin_fsm_file_archive_reader_func)(rpmPlugin plugin,
af2e7f
+						     FD_t payload,
af2e7f
+						     rpmfiles files, rpmfi *fi);
af2e7f
+
af2e7f
 
af2e7f
 typedef struct rpmPluginHooks_s * rpmPluginHooks;
af2e7f
 struct rpmPluginHooks_s {
af2e7f
@@ -80,6 +87,8 @@ struct rpmPluginHooks_s {
af2e7f
     plugin_fsm_file_pre_func		fsm_file_pre;
af2e7f
     plugin_fsm_file_post_func		fsm_file_post;
af2e7f
     plugin_fsm_file_prepare_func	fsm_file_prepare;
af2e7f
+    plugin_fsm_file_install_func	fsm_file_install;
af2e7f
+    plugin_fsm_file_archive_reader_func	fsm_file_archive_reader;
af2e7f
 };
af2e7f
 
af2e7f
 #ifdef __cplusplus
af2e7f
diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
af2e7f
index 62e806b..24abe96 100644
af2e7f
--- a/lib/rpmplugins.c
af2e7f
+++ b/lib/rpmplugins.c
af2e7f
@@ -364,14 +364,29 @@ rpmRC rpmpluginsCallFsmFilePre(rpmPlugins plugins, rpmfi fi, const char *path,
af2e7f
     plugin_fsm_file_pre_func hookFunc;
af2e7f
     int i;
af2e7f
     rpmRC rc = RPMRC_OK;
af2e7f
+    rpmRC hook_rc;
af2e7f
     char *apath = abspath(fi, path);
af2e7f
 
af2e7f
     for (i = 0; i < plugins->count; i++) {
af2e7f
 	rpmPlugin plugin = plugins->plugins[i];
af2e7f
 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_pre);
af2e7f
-	if (hookFunc && hookFunc(plugin, fi, apath, file_mode, op) == RPMRC_FAIL) {
af2e7f
-	    rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
af2e7f
-	    rc = RPMRC_FAIL;
af2e7f
+	if (hookFunc) {
af2e7f
+	    hook_rc = hookFunc(plugin, fi, apath, file_mode, op);
af2e7f
+	    if (hook_rc == RPMRC_FAIL) {
af2e7f
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
af2e7f
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
af2e7f
+		    /* Another plugin already said it'd handle contents. It's
af2e7f
+		     * undefined how these would combine, so treat this as a
af2e7f
+		     * failure condition.
af2e7f
+		    */
af2e7f
+		    rc = RPMRC_FAIL;
af2e7f
+		} else {
af2e7f
+		    /* Plugin will handle content */
af2e7f
+		    rc = RPMRC_PLUGIN_CONTENTS;
af2e7f
+		}
af2e7f
+	    }
af2e7f
 	}
af2e7f
     }
af2e7f
     free(apath);
af2e7f
@@ -420,3 +435,76 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
af2e7f
 
af2e7f
     return rc;
af2e7f
 }
af2e7f
+
af2e7f
+rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi,
af2e7f
+				   const char *path, mode_t file_mode,
af2e7f
+				   rpmFsmOp op)
af2e7f
+{
af2e7f
+    plugin_fsm_file_install_func hookFunc;
af2e7f
+    int i;
af2e7f
+    rpmRC rc = RPMRC_OK;
af2e7f
+    rpmRC hook_rc;
af2e7f
+    char *apath = abspath(fi, path);
af2e7f
+
af2e7f
+    for (i = 0; i < plugins->count; i++) {
af2e7f
+	rpmPlugin plugin = plugins->plugins[i];
af2e7f
+	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_install);
af2e7f
+	if (hookFunc) {
af2e7f
+	    hook_rc = hookFunc(plugin, fi, apath, file_mode, op);
af2e7f
+	    if (hook_rc == RPMRC_FAIL) {
af2e7f
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_install failed\n", plugin->name);
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
af2e7f
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
af2e7f
+		    /* Another plugin already said it'd handle contents. It's
af2e7f
+		     * undefined how these would combine, so treat this as a
af2e7f
+		     * failure condition.
af2e7f
+		    */
af2e7f
+		    rc = RPMRC_FAIL;
af2e7f
+		} else {
af2e7f
+		    /* Plugin will handle content */
af2e7f
+		    rc = RPMRC_PLUGIN_CONTENTS;
af2e7f
+		}
af2e7f
+	    }
af2e7f
+	}
af2e7f
+    }
af2e7f
+    free(apath);
af2e7f
+
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload,
af2e7f
+				   rpmfiles files, rpmfi *fi)
af2e7f
+{
af2e7f
+    plugin_fsm_file_archive_reader_func hookFunc;
af2e7f
+    int i;
af2e7f
+    rpmRC rc = RPMRC_OK;
af2e7f
+    rpmRC hook_rc;
af2e7f
+
af2e7f
+    for (i = 0; i < plugins->count; i++) {
af2e7f
+	rpmPlugin plugin = plugins->plugins[i];
af2e7f
+	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_archive_reader);
af2e7f
+	if (hookFunc) {
af2e7f
+	    hook_rc = hookFunc(plugin, payload, files, fi);
af2e7f
+	    if (hook_rc == RPMRC_FAIL) {
af2e7f
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_archive_reader failed\n", plugin->name);
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
af2e7f
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
af2e7f
+		    /* Another plugin already said it'd handle contents. It's
af2e7f
+		     * undefined how these would combine, so treat this as a
af2e7f
+		     * failure condition.
af2e7f
+		    */
af2e7f
+		    rc = RPMRC_FAIL;
af2e7f
+		} else {
af2e7f
+		    /* Plugin will handle content */
af2e7f
+		    rc = RPMRC_PLUGIN_CONTENTS;
af2e7f
+		}
af2e7f
+	    }
af2e7f
+	}
af2e7f
+    }
af2e7f
+
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+
af2e7f
diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h
af2e7f
index 287a302..a00f156 100644
af2e7f
--- a/lib/rpmplugins.h
af2e7f
+++ b/lib/rpmplugins.h
af2e7f
@@ -168,6 +168,23 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
af2e7f
                                    int fd, const char *path, const char *dest,
af2e7f
                                    mode_t mode, rpmFsmOp op);
af2e7f
 
af2e7f
+/** \ingroup rpmplugins
af2e7f
+ * Call the fsm file install plugin hook
af2e7f
+ * @param plugins	plugins structure
af2e7f
+ * @param fi		file info iterator (or NULL)
af2e7f
+ * @param path		file object path
af2e7f
+ * @param file_mode	file object mode
af2e7f
+ * @param op		file operation + associated flags
af2e7f
+ * @return		RPMRC_OK on success, RPMRC_FAIL otherwise
af2e7f
+ */
af2e7f
+RPM_GNUC_INTERNAL
af2e7f
+rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi,
af2e7f
+				   const char* path, mode_t file_mode,
af2e7f
+				   rpmFsmOp op);
af2e7f
+
af2e7f
+RPM_GNUC_INTERNAL
af2e7f
+rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload,
af2e7f
+					 rpmfiles files, rpmfi *fi);
af2e7f
 #ifdef __cplusplus
af2e7f
 }
af2e7f
 #endif
af2e7f
diff --git a/lib/rpmte.c b/lib/rpmte.c
af2e7f
index d31152a..642e809 100644
af2e7f
--- a/lib/rpmte.c
af2e7f
+++ b/lib/rpmte.c
af2e7f
@@ -437,6 +437,11 @@ FD_t rpmteSetFd(rpmte te, FD_t fd)
af2e7f
     return NULL;
af2e7f
 }
af2e7f
 
af2e7f
+FD_t rpmteFd(rpmte te)
af2e7f
+{
af2e7f
+    return (te != NULL ? te->fd : NULL);
af2e7f
+}
af2e7f
+
af2e7f
 fnpyKey rpmteKey(rpmte te)
af2e7f
 {
af2e7f
     return (te != NULL ? te->key : NULL);
af2e7f
diff --git a/lib/transaction.c b/lib/transaction.c
af2e7f
index 70d2587..1e98f11 100644
af2e7f
--- a/lib/transaction.c
af2e7f
+++ b/lib/transaction.c
af2e7f
@@ -28,6 +28,7 @@
af2e7f
 #include <rpm/rpmstring.h>
af2e7f
 #include <rpm/rpmsq.h>
af2e7f
 #include <rpm/rpmkeyring.h>
af2e7f
+#include <rpm/rpmextents_internal.h>
af2e7f
 
af2e7f
 #include "fprint.h"
af2e7f
 #include "misc.h"
af2e7f
@@ -1292,19 +1293,25 @@ static int verifyPackageFiles(rpmts ts, rpm_loff_t total)
af2e7f
 
af2e7f
 	rpmtsNotify(ts, p, RPMCALLBACK_VERIFY_PROGRESS, oc++, total);
af2e7f
 	FD_t fd = rpmtsNotify(ts, p, RPMCALLBACK_INST_OPEN_FILE, 0, 0);
af2e7f
-	if (fd != NULL) {
af2e7f
-	    prc = rpmpkgRead(vs, fd, NULL, NULL, &vd.msg);
af2e7f
-	    rpmtsNotify(ts, p, RPMCALLBACK_INST_CLOSE_FILE, 0, 0);
af2e7f
+	if(fd != NULL && isTranscodedRpm(fd) == RPMRC_OK) {
af2e7f
+	    /* Transcoded RPMs are validated at transcoding time */
af2e7f
+	    prc = RPMRC_OK;
af2e7f
+	    verified = 1;
af2e7f
+	} else {
af2e7f
+	    if (fd != NULL) {
af2e7f
+		prc = rpmpkgRead(vs, fd, NULL, NULL, &vd.msg);
af2e7f
+		rpmtsNotify(ts, p, RPMCALLBACK_INST_CLOSE_FILE, 0, 0);
af2e7f
+	    }
af2e7f
+	    if (prc == RPMRC_OK)
af2e7f
+		prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
af2e7f
+
af2e7f
+	    /* Record verify result */
af2e7f
+	    if (vd.type[RPMSIG_SIGNATURE_TYPE] == RPMRC_OK)
af2e7f
+		verified |= RPMSIG_SIGNATURE_TYPE;
af2e7f
+	    if (vd.type[RPMSIG_DIGEST_TYPE] == RPMRC_OK)
af2e7f
+		verified |= RPMSIG_DIGEST_TYPE;
af2e7f
 	}
af2e7f
 
af2e7f
-	if (prc == RPMRC_OK)
af2e7f
-	    prc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
af2e7f
-
af2e7f
-	/* Record verify result */
af2e7f
-	if (vd.type[RPMSIG_SIGNATURE_TYPE] == RPMRC_OK)
af2e7f
-	    verified |= RPMSIG_SIGNATURE_TYPE;
af2e7f
-	if (vd.type[RPMSIG_DIGEST_TYPE] == RPMRC_OK)
af2e7f
-	    verified |= RPMSIG_DIGEST_TYPE;
af2e7f
 	rpmteSetVerified(p, verified);
af2e7f
 
af2e7f
 	if (prc)
af2e7f
diff --git a/macros.in b/macros.in
af2e7f
index 7eb3d2b..df7defe 100644
af2e7f
--- a/macros.in
af2e7f
+++ b/macros.in
af2e7f
@@ -1188,6 +1188,7 @@ Supplements:   (%{name} = %{version}-%{release} and langpacks-%{1})\
af2e7f
 
af2e7f
 # Transaction plugin macros
af2e7f
 %__plugindir		%{_libdir}/rpm-plugins
af2e7f
+%__transaction_reflink		%{__plugindir}/reflink.so
af2e7f
 %__transaction_systemd_inhibit	%{__plugindir}/systemd_inhibit.so
af2e7f
 %__transaction_selinux		%{__plugindir}/selinux.so
af2e7f
 %__transaction_syslog		%{__plugindir}/syslog.so
af2e7f
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
af2e7f
index c2c112b..ed63a43 100644
af2e7f
--- a/plugins/CMakeLists.txt
af2e7f
+++ b/plugins/CMakeLists.txt
af2e7f
@@ -1,5 +1,6 @@
af2e7f
 add_library(prioreset MODULE prioreset.c)
af2e7f
 add_library(syslog MODULE syslog.c)
af2e7f
+add_library(reflink MODULE reflink.c)
af2e7f
 
af2e7f
 if(WITH_SELINUX)
af2e7f
 	add_library(selinux MODULE selinux.c)
af2e7f
@@ -44,7 +45,6 @@ get_property(plugins DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
af2e7f
 foreach(plugin ${plugins})
af2e7f
 	target_link_libraries(${plugin} PRIVATE librpmio librpm ${Intl_LIBRARIES})
af2e7f
 	# XX this is wrong, but required while rpmplugin.h is there
af2e7f
-	target_include_directories(${plugin} PRIVATE ${CMAKE_SOURCE_DIR}/lib ${Intl_INCLUDE_DIRS})
af2e7f
+	target_include_directories(${plugin} PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/rpmio ${Intl_INCLUDE_DIRS})
af2e7f
 	install(TARGETS ${plugin} DESTINATION ${plugindir})
af2e7f
 endforeach()
af2e7f
-
af2e7f
diff --git a/plugins/reflink.c b/plugins/reflink.c
af2e7f
new file mode 100644
af2e7f
index 0000000..7ce3a9f
af2e7f
--- /dev/null
af2e7f
+++ b/plugins/reflink.c
af2e7f
@@ -0,0 +1,401 @@
af2e7f
+#include "system.h"
af2e7f
+
af2e7f
+#include <errno.h>
af2e7f
+#include <sys/resource.h>
af2e7f
+#include <unistd.h>
af2e7f
+#include <sys/types.h>
af2e7f
+#include <sys/stat.h>
af2e7f
+#include <fcntl.h>
af2e7f
+#if defined(__linux__)
af2e7f
+#include <linux/fs.h>        /* For FICLONE */
af2e7f
+#endif
af2e7f
+
af2e7f
+#include <rpm/rpmlog.h>
af2e7f
+#include <rpm/rpmlib.h>
af2e7f
+#include <rpm/rpmextents_internal.h>
af2e7f
+#include <rpm/rpmfileutil.h>
af2e7f
+#include "rpmplugin.h"
af2e7f
+#include "rpmte_internal.h"
af2e7f
+#include "rpmio_internal.h"
af2e7f
+
af2e7f
+
af2e7f
+#include "debug.h"
af2e7f
+
af2e7f
+#include <sys/ioctl.h>
af2e7f
+
af2e7f
+/* use hash table to remember inode -> ix (for rpmfilesFN(ix)) lookups */
af2e7f
+#undef HASHTYPE
af2e7f
+#undef HTKEYTYPE
af2e7f
+#undef HTDATATYPE
af2e7f
+#define HASHTYPE inodeIndexHash
af2e7f
+#define HTKEYTYPE rpm_ino_t
af2e7f
+#define HTDATATYPE const char *
af2e7f
+#include "rpmhash.H"
af2e7f
+#include "rpmhash.C"
af2e7f
+
af2e7f
+/* We use this in find to indicate a key wasn't found. This is an
af2e7f
+ * unrecoverable error, but we can at least show a decent error. 0 is never a
af2e7f
+ * valid offset because it's the offset of the start of the file.
af2e7f
+ */
af2e7f
+#define NOT_FOUND 0
af2e7f
+
af2e7f
+#define BUFFER_SIZE (1024 * 128)
af2e7f
+
af2e7f
+struct reflink_state_s {
af2e7f
+    /* Stuff that's used across rpms */
af2e7f
+    long fundamental_block_size;
af2e7f
+    char *buffer;
af2e7f
+
af2e7f
+    /* stuff that's used/updated per psm */
af2e7f
+    uint32_t keys, keysize;
af2e7f
+
af2e7f
+    /* table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) */
af2e7f
+    unsigned char *table;
af2e7f
+    FD_t fd;
af2e7f
+    rpmfiles files;
af2e7f
+    inodeIndexHash inodeIndexes;
af2e7f
+    int transcoded;
af2e7f
+};
af2e7f
+
af2e7f
+typedef struct reflink_state_s * reflink_state;
af2e7f
+
af2e7f
+/*
af2e7f
+ * bsearch_r: implements a re-entrant version of stdlib's bsearch.
af2e7f
+ * code taken and adapted from /usr/include/bits/stdlib-bsearch.h
af2e7f
+ */
af2e7f
+static inline void *
af2e7f
+bsearch_r (const void *__key, const void *__base, size_t __nmemb, size_t __size,
af2e7f
+	 __compar_d_fn_t __compar, void *__arg)
af2e7f
+{
af2e7f
+  size_t __l, __u, __idx;
af2e7f
+  const void *__p;
af2e7f
+  int __comparison;
af2e7f
+
af2e7f
+  __l = 0;
af2e7f
+  __u = __nmemb;
af2e7f
+  while (__l < __u)
af2e7f
+    {
af2e7f
+      __idx = (__l + __u) / 2;
af2e7f
+      __p = (const void *) (((const char *) __base) + (__idx * __size));
af2e7f
+      __comparison = (*__compar) (__key, __p, __arg);
af2e7f
+      if (__comparison < 0)
af2e7f
+	__u = __idx;
af2e7f
+      else if (__comparison > 0)
af2e7f
+	__l = __idx + 1;
af2e7f
+      else
af2e7f
+	{
af2e7f
+#if __GNUC_PREREQ(4, 6)
af2e7f
+# pragma GCC diagnostic push
af2e7f
+# pragma GCC diagnostic ignored "-Wcast-qual"
af2e7f
+#endif
af2e7f
+	  return (void *) __p;
af2e7f
+#if __GNUC_PREREQ(4, 6)
af2e7f
+# pragma GCC diagnostic pop
af2e7f
+#endif
af2e7f
+	}
af2e7f
+    }
af2e7f
+
af2e7f
+  return NULL;
af2e7f
+}
af2e7f
+
af2e7f
+static int cmpdigest(const void *k1, const void *k2, void *data) {
af2e7f
+    rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2);
af2e7f
+    return memcmp(k1, k2, *(int *)data);
af2e7f
+}
af2e7f
+
af2e7f
+static int inodeCmp(rpm_ino_t a, rpm_ino_t b)
af2e7f
+{
af2e7f
+    return (a != b);
af2e7f
+}
af2e7f
+
af2e7f
+static unsigned int inodeId(rpm_ino_t a)
af2e7f
+{
af2e7f
+    /* rpm_ino_t is uint32_t so maps safely to unsigned int */
af2e7f
+    return (unsigned int)a;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC reflink_init(rpmPlugin plugin, rpmts ts) {
af2e7f
+    reflink_state state = rcalloc(1, sizeof(struct reflink_state_s));
af2e7f
+
af2e7f
+    /* IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset
af2e7f
+     * and length arguments to be aligned to the fundamental block size.
af2e7f
+     *
af2e7f
+     * The value of "fundamental block size" is directly related to the
af2e7f
+     * system's page size, so we should use that.
af2e7f
+     */
af2e7f
+    state->fundamental_block_size = sysconf(_SC_PAGESIZE);
af2e7f
+    state->buffer = rcalloc(1, BUFFER_SIZE);
af2e7f
+    rpmPluginSetData(plugin, state);
af2e7f
+
af2e7f
+    return RPMRC_OK;
af2e7f
+}
af2e7f
+
af2e7f
+static void reflink_cleanup(rpmPlugin plugin) {
af2e7f
+    reflink_state state = rpmPluginGetData(plugin);
af2e7f
+    free(state->buffer);
af2e7f
+    free(state);
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) {
af2e7f
+    rpmRC rc;
af2e7f
+    size_t len;
af2e7f
+
af2e7f
+    reflink_state state = rpmPluginGetData(plugin);
af2e7f
+    state->fd = rpmteFd(te);
af2e7f
+    if (state->fd == 0) {
af2e7f
+	rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n"));
af2e7f
+	return RPMRC_OK;
af2e7f
+    }
af2e7f
+
af2e7f
+    rpm_loff_t current = Ftell(state->fd);
af2e7f
+    rc = isTranscodedRpm(state->fd);
af2e7f
+
af2e7f
+    switch(rc){
af2e7f
+	// Fail to parse the file, fail the plugin.
af2e7f
+	case RPMRC_FAIL:
af2e7f
+	    return RPMRC_FAIL;
af2e7f
+	// This is not a transcoded file, do nothing.
af2e7f
+	case RPMRC_NOTFOUND:
af2e7f
+	    return RPMRC_OK;
af2e7f
+	default:
af2e7f
+	    break;
af2e7f
+    }
af2e7f
+    rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n"));
af2e7f
+    state->transcoded = 1;
af2e7f
+
af2e7f
+    state->files = rpmteFiles(te);
af2e7f
+    /* tail of file contains offset_table, offset_checksums then magic */
af2e7f
+    if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(extents_magic_t)), SEEK_END) < 0) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"),
af2e7f
+	       state->fd);
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    rpm_loff_t table_start;
af2e7f
+    len = sizeof(table_start);
af2e7f
+    if (Fread(&table_start, len, 1, state->fd) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    if (Fseek(state->fd, table_start, SEEK_SET) < 0) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    len = sizeof(state->keys);
af2e7f
+    if (Fread(&state->keys, len, 1, state->fd) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    len = sizeof(state->keysize);
af2e7f
+    if (Fread(&state->keysize, len, 1, state->fd) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    rpmlog(
af2e7f
+	RPMLOG_DEBUG,
af2e7f
+	_("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"),
af2e7f
+	table_start, state->keys, state->keysize
af2e7f
+    );
af2e7f
+    /* now get digest table if there is a reason to have one. */
af2e7f
+    if (state->keys == 0 || state->keysize == 0) {
af2e7f
+	/* no files (or no digests(!)) */
af2e7f
+	state->table = NULL;
af2e7f
+    } else {
af2e7f
+	int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t));
af2e7f
+	state->table = rcalloc(1, table_size);
af2e7f
+	if (Fread(state->table, table_size, 1, state->fd) != table_size) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n"));
af2e7f
+	    return RPMRC_FAIL;
af2e7f
+	}
af2e7f
+	state->inodeIndexes = inodeIndexHashCreate(
af2e7f
+	    state->keys, inodeId, inodeCmp, NULL, (inodeIndexHashFreeData)free
af2e7f
+	);
af2e7f
+    }
af2e7f
+
af2e7f
+    /* Seek back to original location.
af2e7f
+     * Might not be needed if we seek to offset immediately
af2e7f
+     */
af2e7f
+    if (Fseek(state->fd, current, SEEK_SET) < 0) {
af2e7f
+	rpmlog(RPMLOG_ERR,
af2e7f
+	       _("reflink: unable to seek back to original location\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+    return RPMRC_OK;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res)
af2e7f
+{
af2e7f
+    reflink_state state = rpmPluginGetData(plugin);
af2e7f
+    state->files = rpmfilesFree(state->files);
af2e7f
+    if (state->table) {
af2e7f
+	free(state->table);
af2e7f
+	state->table = NULL;
af2e7f
+    }
af2e7f
+    if (state->inodeIndexes) {
af2e7f
+	inodeIndexHashFree(state->inodeIndexes);
af2e7f
+	state->inodeIndexes = NULL;
af2e7f
+    }
af2e7f
+    state->transcoded = 0;
af2e7f
+    return RPMRC_OK;
af2e7f
+}
af2e7f
+
af2e7f
+
af2e7f
+/* have a prototype, warnings system */
af2e7f
+rpm_loff_t find(const unsigned char *digest, reflink_state state);
af2e7f
+
af2e7f
+rpm_loff_t find(const unsigned char *digest, reflink_state state) {
af2e7f
+    rpmlog(RPMLOG_DEBUG,
af2e7f
+	   _("reflink: bsearch_r(key=%p, base=%p, nmemb=%d, size=%lu)\n"),
af2e7f
+	   digest, state->table, state->keys,
af2e7f
+	   state->keysize + sizeof(rpm_loff_t));
af2e7f
+    char *entry = bsearch_r(digest, state->table, state->keys,
af2e7f
+			    state->keysize + sizeof(rpm_loff_t), cmpdigest,
af2e7f
+			    &state->keysize);
af2e7f
+    if (entry == NULL) {
af2e7f
+	return NOT_FOUND;
af2e7f
+    }
af2e7f
+    rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize);
af2e7f
+    return offset;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* path,
af2e7f
+                                  mode_t file_mode, rpmFsmOp op)
af2e7f
+{
af2e7f
+    struct file_clone_range fcr;
af2e7f
+    rpm_loff_t size;
af2e7f
+    int dst, rc;
af2e7f
+    const char **hl_target = NULL;
af2e7f
+
af2e7f
+    reflink_state state = rpmPluginGetData(plugin);
af2e7f
+    if (state->table == NULL) {
af2e7f
+	/* no table means rpm is not in reflink format, so leave. Now. */
af2e7f
+	return RPMRC_OK;
af2e7f
+    }
af2e7f
+    if (op == FA_TOUCH) {
af2e7f
+	/* we're not overwriting an existing file. */
af2e7f
+	return RPMRC_OK;
af2e7f
+    }
af2e7f
+    fcr.dest_offset = 0;
af2e7f
+    if (S_ISREG(file_mode) && !(rpmfiFFlags(fi) & RPMFILE_GHOST)) {
af2e7f
+	rpm_ino_t inode = rpmfiFInode(fi);
af2e7f
+	/* check for hard link entry in table. GetEntry overwrites hlix with
af2e7f
+	 * the address of the first match.
af2e7f
+	 */
af2e7f
+	if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hl_target,
af2e7f
+				   NULL, NULL)) {
af2e7f
+	    /* entry is in table, use hard link */
af2e7f
+	    if (link(hl_target[0], path) != 0) {
af2e7f
+		rpmlog(RPMLOG_ERR,
af2e7f
+		       _("reflink: Unable to hard link %s -> %s due to %s\n"),
af2e7f
+		       hl_target[0], path, strerror(errno));
af2e7f
+		return RPMRC_FAIL;
af2e7f
+	    }
af2e7f
+	    return RPMRC_PLUGIN_CONTENTS;
af2e7f
+	}
af2e7f
+	/* if we didn't hard link, then we'll track this inode as being
af2e7f
+	 * created soon
af2e7f
+	 */
af2e7f
+	if (rpmfiFNlink(fi) > 1) {
af2e7f
+	    /* minor optimization: only store files with more than one link */
af2e7f
+	    inodeIndexHashAddEntry(state->inodeIndexes, inode, rstrdup(path));
af2e7f
+	}
af2e7f
+	/* derived from wfd_open in fsm.c */
af2e7f
+	mode_t old_umask = umask(0577);
af2e7f
+	dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR);
af2e7f
+	umask(old_umask);
af2e7f
+	if (dst == -1) {
af2e7f
+	    rpmlog(RPMLOG_ERR,
af2e7f
+		   _("reflink: Unable to open %s for writing due to %s, flags = %x\n"),
af2e7f
+		   path, strerror(errno), rpmfiFFlags(fi));
af2e7f
+	    return RPMRC_FAIL;
af2e7f
+	}
af2e7f
+	size = rpmfiFSize(fi);
af2e7f
+	if (size > 0) {
af2e7f
+	    /* round src_length down to fundamental_block_size multiple */
af2e7f
+	    fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size;
af2e7f
+	    if ((size % state->fundamental_block_size) > 0) {
af2e7f
+		/* round up to next fundamental_block_size. We expect the data
af2e7f
+		 * in the rpm to be similarly padded.
af2e7f
+		 */
af2e7f
+		fcr.src_length += state->fundamental_block_size;
af2e7f
+	    }
af2e7f
+	    fcr.src_fd = Fileno(state->fd);
af2e7f
+	    if (fcr.src_fd == -1) {
af2e7f
+		close(dst);
af2e7f
+		rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n"));
af2e7f
+		return RPMRC_FAIL;
af2e7f
+	    }
af2e7f
+	    fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state);
af2e7f
+	    if (fcr.src_offset == NOT_FOUND) {
af2e7f
+		close(dst);
af2e7f
+		rpmlog(RPMLOG_ERR, _("reflink: digest not found\n"));
af2e7f
+		return RPMRC_FAIL;
af2e7f
+	    }
af2e7f
+	    rpmlog(RPMLOG_DEBUG,
af2e7f
+	           _("reflink: Reflinking %llu bytes at %llu to %s orig size=%ld, file=%lld\n"),
af2e7f
+		   fcr.src_length, fcr.src_offset, path, size, fcr.src_fd);
af2e7f
+	    rc = ioctl(dst, FICLONERANGE, &fcr;;
af2e7f
+	    if (rc) {
af2e7f
+		rpmlog(RPMLOG_WARNING,
af2e7f
+		       _("reflink: falling back to copying bits for %s due to %d, %d = %s\n"),
af2e7f
+		       path, rc, errno, strerror(errno));
af2e7f
+		if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) {
af2e7f
+		    close(dst);
af2e7f
+		    rpmlog(RPMLOG_ERR,
af2e7f
+			   _("reflink: unable to seek on copying bits\n"));
af2e7f
+		    return RPMRC_FAIL;
af2e7f
+		}
af2e7f
+		rpm_loff_t left = size;
af2e7f
+		size_t len, read, written;
af2e7f
+		while (left) {
af2e7f
+		    len = (left > BUFFER_SIZE ? BUFFER_SIZE : left);
af2e7f
+		    read = Fread(state->buffer, len, 1, state->fd);
af2e7f
+		    if (read != len) {
af2e7f
+			close(dst);
af2e7f
+			rpmlog(RPMLOG_ERR,
af2e7f
+			       _("reflink: short read on copying bits\n"));
af2e7f
+			return RPMRC_FAIL;
af2e7f
+		    }
af2e7f
+		    written = write(dst, state->buffer, len);
af2e7f
+		    if (read != written) {
af2e7f
+			close(dst);
af2e7f
+			rpmlog(RPMLOG_ERR,
af2e7f
+			       _("reflink: short write on copying bits\n"));
af2e7f
+			return RPMRC_FAIL;
af2e7f
+		    }
af2e7f
+		    left -= len;
af2e7f
+		}
af2e7f
+	    } else {
af2e7f
+		/* reflink worked, so truncate */
af2e7f
+		rc = ftruncate(dst, size);
af2e7f
+		if (rc) {
af2e7f
+		    rpmlog(RPMLOG_ERR,
af2e7f
+			   _("reflink: Unable to truncate %s to %ld due to %s\n"),
af2e7f
+			   path, size, strerror(errno));
af2e7f
+		     return RPMRC_FAIL;
af2e7f
+		}
af2e7f
+	    }
af2e7f
+	}
af2e7f
+	close(dst);
af2e7f
+	return RPMRC_PLUGIN_CONTENTS;
af2e7f
+    }
af2e7f
+    return RPMRC_OK;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC reflink_fsm_file_archive_reader(rpmPlugin plugin, FD_t payload,
af2e7f
+					     rpmfiles files, rpmfi *fi) {
af2e7f
+    reflink_state state = rpmPluginGetData(plugin);
af2e7f
+    if(state->transcoded) {
af2e7f
+	*fi = rpmfilesIter(files, RPMFI_ITER_FWD);
af2e7f
+	return RPMRC_PLUGIN_CONTENTS;
af2e7f
+    }
af2e7f
+    return RPMRC_OK;
af2e7f
+}
af2e7f
+
af2e7f
+struct rpmPluginHooks_s reflink_hooks = {
af2e7f
+    .init = reflink_init,
af2e7f
+    .cleanup = reflink_cleanup,
af2e7f
+    .psm_pre = reflink_psm_pre,
af2e7f
+    .psm_post = reflink_psm_post,
af2e7f
+    .fsm_file_install = reflink_fsm_file_install,
af2e7f
+    .fsm_file_archive_reader = reflink_fsm_file_archive_reader,
af2e7f
+};
af2e7f
diff --git a/rpmio/rpmpgp.c b/rpmio/rpmpgp.c
af2e7f
index f545213..5b2ad78 100644
af2e7f
--- a/rpmio/rpmpgp.c
af2e7f
+++ b/rpmio/rpmpgp.c
af2e7f
@@ -81,3 +81,31 @@ pgpArmor pgpReadPkts(const char * fn, uint8_t ** pkt, size_t * pktlen)
af2e7f
     free(b);
af2e7f
     return ec;
af2e7f
 }
af2e7f
+
af2e7f
+/** \ingroup rpmpgp
af2e7f
+ * Return value of an OpenPGP string.
af2e7f
+ * @param vs		table of (string,value) pairs
af2e7f
+ * @param s		string token to lookup
af2e7f
+ * @param se		end-of-string address
af2e7f
+ * @return		byte value
af2e7f
+ */
af2e7f
+static inline
af2e7f
+int pgpValTok(pgpValTbl vs, const char * s, const char * se)
af2e7f
+{
af2e7f
+    do {
af2e7f
+	size_t vlen = strlen(vs->str);
af2e7f
+	if (vlen <= (se-s) && rstreqn(s, vs->str, vlen))
af2e7f
+	    break;
af2e7f
+    } while ((++vs)->val != -1);
af2e7f
+    return vs->val;
af2e7f
+}
af2e7f
+
af2e7f
+int pgpStringVal(pgpValType type, const char *str, uint8_t *val)
af2e7f
+{
af2e7f
+    pgpValTbl tbl = pgpValTable(type);
af2e7f
+    if (tbl == NULL) return -1;
af2e7f
+    int v = pgpValTok(tbl, str, str + strlen(str));
af2e7f
+    if (v == -1) return -1;
af2e7f
+    *val = (uint8_t)v;
af2e7f
+    return 0;
af2e7f
+}
af2e7f
diff --git a/rpmio/rpmpgp_internal.c b/rpmio/rpmpgp_internal.c
af2e7f
index 0e38946..0cfe9b9 100644
af2e7f
--- a/rpmio/rpmpgp_internal.c
af2e7f
+++ b/rpmio/rpmpgp_internal.c
af2e7f
@@ -88,24 +88,6 @@ static void pgpPrtTime(const char * pre, const uint8_t *p, size_t plen)
af2e7f
     }
af2e7f
 }
af2e7f
 
af2e7f
-/** \ingroup rpmpgp
af2e7f
- * Return value of an OpenPGP string.
af2e7f
- * @param vs		table of (string,value) pairs
af2e7f
- * @param s		string token to lookup
af2e7f
- * @param se		end-of-string address
af2e7f
- * @return		byte value
af2e7f
- */
af2e7f
-static inline
af2e7f
-int pgpValTok(pgpValTbl vs, const char * s, const char * se)
af2e7f
-{
af2e7f
-    do {
af2e7f
-	size_t vlen = strlen(vs->str);
af2e7f
-	if (vlen <= (se-s) && rstreqn(s, vs->str, vlen))
af2e7f
-	    break;
af2e7f
-    } while ((++vs)->val != -1);
af2e7f
-    return vs->val;
af2e7f
-}
af2e7f
-
af2e7f
 /** \ingroup rpmpgp
af2e7f
  * Decode length from 1, 2, or 5 octet body length encoding, used in
af2e7f
  * new format packet headers and V4 signature subpackets.
af2e7f
diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt
af2e7f
index 07749bc..8f8ae64 100644
af2e7f
--- a/scripts/CMakeLists.txt
af2e7f
+++ b/scripts/CMakeLists.txt
af2e7f
@@ -12,7 +12,7 @@ install(PROGRAMS
af2e7f
 	rpm_macros_provides.sh
af2e7f
 	rpmdb_dump rpmdb_load
af2e7f
 	rpm2cpio.sh tgpg
af2e7f
-	sysusers.sh
af2e7f
+	sysusers.sh rpm2extents_dump
af2e7f
 	DESTINATION ${RPM_CONFIGDIR}
af2e7f
 )
af2e7f
 install(FILES
af2e7f
diff --git a/scripts/rpm2extents_dump b/scripts/rpm2extents_dump
af2e7f
new file mode 100755
af2e7f
index 0000000..596a59a
af2e7f
--- /dev/null
af2e7f
+++ b/scripts/rpm2extents_dump
af2e7f
@@ -0,0 +1,94 @@
af2e7f
+#!/usr/bin/env python3
af2e7f
+
af2e7f
+import argparse
af2e7f
+import binascii
af2e7f
+import os
af2e7f
+import struct
af2e7f
+import sys
af2e7f
+
af2e7f
+MAGIC_SIZE = 8
af2e7f
+MAGIC_STR = b'KWTSH100'
af2e7f
+
af2e7f
+POS_SIZE = 8
af2e7f
+
af2e7f
+def keep_position(func):
af2e7f
+    def wrapper(*args, **kwargs):
af2e7f
+        curr = args[0].tell()
af2e7f
+        res = func(*args, **kwargs)
af2e7f
+        f.seek(curr, os.SEEK_SET)
af2e7f
+        return res
af2e7f
+    return wrapper
af2e7f
+
af2e7f
+def read_validation_digest(f, validation_offset):
af2e7f
+	digests = []
af2e7f
+    # validation
af2e7f
+	f.seek(validation_offset, os.SEEK_SET)
af2e7f
+	val_content_len, val_digests_num = struct.unpack('=QI', f.read(8+4))
af2e7f
+	for i in range(val_digests_num):
af2e7f
+		algo_name_len, digest_len = struct.unpack('=II', f.read(8))
af2e7f
+		algo_name, digest = struct.unpack(f'{algo_name_len}s{digest_len}s', f.read(algo_name_len+digest_len))
af2e7f
+		digests.append((algo_name, binascii.hexlify(digest)))
af2e7f
+	return digests
af2e7f
+
af2e7f
+
af2e7f
+def read_digests_table(f, digest_offset):
af2e7f
+	digests = []
af2e7f
+    # validation
af2e7f
+	f.seek(digest_offset, os.SEEK_SET)
af2e7f
+	table_len, digest_len = struct.unpack('=II', f.read(8))
af2e7f
+
af2e7f
+	for i in range(table_len):
af2e7f
+		digest, pos = struct.unpack(f'{digest_len}sQ', f.read(digest_len + 8))
af2e7f
+		digests.append((pos, binascii.hexlify(digest)))
af2e7f
+	return digests
af2e7f
+
af2e7f
+def read_signature_output(f, signature_offset):
af2e7f
+    f.seek(signature_offset, os.SEEK_SET)
af2e7f
+    signature_rc, signature_output_len = struct.unpack('=IQ', f.read(12))
af2e7f
+    return signature_rc, f.read(signature_output_len)
af2e7f
+
af2e7f
+@keep_position
af2e7f
+def parse_file(f):
af2e7f
+	digests = []
af2e7f
+	pos_table_offset = f.seek(-8 - 3*POS_SIZE, os.SEEK_END)
af2e7f
+	signature_offset, digest_offset, validation_offset = struct.unpack('=QQQ', f.read(3*POS_SIZE))
af2e7f
+
af2e7f
+	validation_digests = read_validation_digest(f, validation_offset)
af2e7f
+	digests_table = read_digests_table(f, digest_offset)
af2e7f
+	signature_ouput = read_signature_output(f, signature_offset)
af2e7f
+
af2e7f
+	return validation_digests, digests_table, signature_ouput
af2e7f
+
af2e7f
+@keep_position
af2e7f
+def is_transcoded(f):
af2e7f
+    f.seek(-MAGIC_SIZE, os.SEEK_END)
af2e7f
+    magic = f.read(MAGIC_SIZE)
af2e7f
+    return magic == MAGIC_STR
af2e7f
+
af2e7f
+def arg_parse():
af2e7f
+    parser = argparse.ArgumentParser()
af2e7f
+    parser.add_argument('--dump-signature', action='store_true')
af2e7f
+    parser.add_argument('--dump-file-digest-table', action='store_true')
af2e7f
+    parser.add_argument('--dump-digests', action='store_true')
af2e7f
+    parser.add_argument('file')
af2e7f
+
af2e7f
+    return parser.parse_args()
af2e7f
+
af2e7f
+if __name__ == '__main__':
af2e7f
+    args = arg_parse()
af2e7f
+    f = open(args.file, 'rb')
af2e7f
+    if not is_transcoded(f):
af2e7f
+        sys.exit(1)
af2e7f
+
af2e7f
+    validation_digests, digests_table, signature_output = parse_file(f)
af2e7f
+    if(args.dump_file_digest_table):
af2e7f
+        for digest in digests_table:
af2e7f
+            print(f"FileDigest {hex(digest[0])}: {digest[1]}")
af2e7f
+
af2e7f
+    if(args.dump_digests):
af2e7f
+        for validation_digest in validation_digests:
af2e7f
+            print(f"HeaderDigest {validation_digest[0]} {validation_digest[1]}")
af2e7f
+
af2e7f
+    if(args.dump_signature):
af2e7f
+        print(f"RPMSignOutput RC {signature_output[0]}\nRPMSignOutput Content {signature_output[1].decode()}")
af2e7f
+
af2e7f
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
af2e7f
index a6afbc0..bee3b64 100644
af2e7f
--- a/tests/CMakeLists.txt
af2e7f
+++ b/tests/CMakeLists.txt
af2e7f
@@ -51,6 +51,7 @@ set(TESTSUITE_AT
af2e7f
 	rpmvfylevel.at
af2e7f
 	rpmpgp.at
af2e7f
 	rpmdevel.at
af2e7f
+	rpm2extents.at
af2e7f
 )
af2e7f
 
af2e7f
 set(TESTPROGS rpmpgpcheck rpmpgppubkeyfingerprint)
af2e7f
diff --git a/tests/atlocal.in b/tests/atlocal.in
af2e7f
index a5faeed..9930cb4 100644
af2e7f
--- a/tests/atlocal.in
af2e7f
+++ b/tests/atlocal.in
af2e7f
@@ -100,6 +100,19 @@ snapshot()
af2e7f
     esac
af2e7f
 }
af2e7f
 
af2e7f
+FSTYPE=$(stat -f -c %T /)
af2e7f
+REFLINKABLE_FS=("xfs" "brtfs")
af2e7f
+
af2e7f
+REFLINK_DISABLED=true;
af2e7f
+for item in "${REFLINKABLE_FS[@]}"
af2e7f
+do
af2e7f
+    if test "${FSTYPE}" = "${item}"
af2e7f
+    then
af2e7f
+	REFLINK_DISABLED=false;
af2e7f
+	break
af2e7f
+    fi
af2e7f
+done
af2e7f
+
af2e7f
 setup_env()
af2e7f
 {
af2e7f
     if [ -d tree ]; then
af2e7f
@@ -138,6 +151,14 @@ runroot()
af2e7f
              --nouserns
af2e7f
 }
af2e7f
 
af2e7f
+function runroot_plugins()
af2e7f
+{
af2e7f
+    setup_env
af2e7f
+    (unset RPM_CONFIGDIR RPM_POPTEXEC_PATH; cd ${RPMTEST} && \
af2e7f
+     MAGIC="/magic/magic" FAKECHROOT_BASE="${RPMTEST}" fakechroot "$@" --define "_buildhost testhost" --define "_topdir /build" --nouserns
af2e7f
+    )
af2e7f
+}
af2e7f
+
af2e7f
 runroot_other()
af2e7f
 {
af2e7f
     setup_env
af2e7f
diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at
af2e7f
new file mode 100644
af2e7f
index 0000000..c9c79c5
af2e7f
--- /dev/null
af2e7f
+++ b/tests/rpm2extents.at
af2e7f
@@ -0,0 +1,151 @@
af2e7f
+#    rpm2extents.at: Some very basic checks
af2e7f
+#
af2e7f
+#    Copyright (C) 2022  Manu Bretelle <chantr4@gmail.com>
af2e7f
+#
af2e7f
+#    This program is free software; you can redistribute it and/or modify
af2e7f
+#    it under the terms of the GNU General Public License as published by
af2e7f
+#    the Free Software Foundation; either version 2 of the License, or
af2e7f
+#    (at your option) any later version.
af2e7f
+#
af2e7f
+#    This program is distributed in the hope that it will be useful,
af2e7f
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
af2e7f
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
af2e7f
+#    GNU General Public License for more details.
af2e7f
+#
af2e7f
+#    You should have received a copy of the GNU General Public License
af2e7f
+#    along with this program; if not, write to the Free Software
af2e7f
+#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
af2e7f
+
af2e7f
+AT_BANNER([rpm2extents tests])
af2e7f
+
af2e7f
+# ------------------------------
af2e7f
+
af2e7f
+# check that transcoder write magic at the end
af2e7f
+AT_SETUP([rpm2extents magic])
af2e7f
+AT_KEYWORDS([rpm2extents])
af2e7f
+AT_CHECK([runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 | tail -c8],
af2e7f
+[0],
af2e7f
+[KWTSH100],
af2e7f
+[ignore])
af2e7f
+AT_CLEANUP
af2e7f
+
af2e7f
+# Check that transcoder writes checksig return code and content.
af2e7f
+#
af2e7f
+AT_SETUP([rpm2extents signature])
af2e7f
+AT_KEYWORDS([rpm2extents])
af2e7f
+AT_CHECK([
af2e7f
+RPMDB_INIT
af2e7f
+
af2e7f
+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
af2e7f
+rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm
af2e7f
+runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
af2e7f
+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
af2e7f
+rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm
af2e7f
+],
af2e7f
+[0],
af2e7f
+[RPMSignOutput RC 2
af2e7f
+RPMSignOutput Content     Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
af2e7f
+    Header SHA256 digest: OK
af2e7f
+    Header SHA1 digest: OK
af2e7f
+    Payload SHA256 digest: OK
af2e7f
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
af2e7f
+    MD5 digest: OK
af2e7f
+
af2e7f
+RPMSignOutput RC 0
af2e7f
+RPMSignOutput Content     Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
af2e7f
+    Header SHA256 digest: OK
af2e7f
+    Header SHA1 digest: OK
af2e7f
+    Payload SHA256 digest: OK
af2e7f
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
af2e7f
+    MD5 digest: OK
af2e7f
+
af2e7f
+],
af2e7f
+[])
af2e7f
+AT_CLEANUP
af2e7f
+
af2e7f
+AT_SETUP([rpm2extents signature verification])
af2e7f
+AT_KEYWORDS([rpm2extents])
af2e7f
+AT_CHECK([
af2e7f
+RPMDB_INIT
af2e7f
+
af2e7f
+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
af2e7f
+runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $?
af2e7f
+runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
af2e7f
+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
af2e7f
+runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $?
af2e7f
+],
af2e7f
+[0],
af2e7f
+[/tmp/hello-2.0-1.x86_64-signed.rpm:
af2e7f
+    Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
af2e7f
+    Header SHA256 digest: OK
af2e7f
+    Header SHA1 digest: OK
af2e7f
+    Payload SHA256 digest: OK
af2e7f
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
af2e7f
+    MD5 digest: OK
af2e7f
+1
af2e7f
+/tmp/hello-2.0-1.x86_64-signed.rpm:
af2e7f
+    Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
af2e7f
+    Header SHA256 digest: OK
af2e7f
+    Header SHA1 digest: OK
af2e7f
+    Payload SHA256 digest: OK
af2e7f
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
af2e7f
+    MD5 digest: OK
af2e7f
+0
af2e7f
+],
af2e7f
+[])
af2e7f
+AT_CLEANUP
af2e7f
+
af2e7f
+# check that package in denylist is not transcoded
af2e7f
+AT_SETUP([rpm2extents denylist])
af2e7f
+AT_KEYWORDS([rpm2extents])
af2e7f
+AT_CHECK([
af2e7f
+export LIBREPO_TRANSCODE_RPMS_DENYLIST="vim,hello,cowsay"
af2e7f
+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 -],
af2e7f
+[0],
af2e7f
+[],
af2e7f
+[ignore])
af2e7f
+AT_CLEANUP
af2e7f
+
af2e7f
+AT_SETUP([rpm2extents install package])
af2e7f
+AT_KEYWORDS([rpm2extents reflink])
af2e7f
+AT_SKIP_IF([$REFLINK_DISABLED])
af2e7f
+AT_CHECK([
af2e7f
+RPMDB_INIT
af2e7f
+
af2e7f
+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
af2e7f
+runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm
af2e7f
+test -f ${RPMTEST}/usr/bin/hello
af2e7f
+],
af2e7f
+[0],
af2e7f
+[],
af2e7f
+[])
af2e7f
+AT_CLEANUP
af2e7f
+
af2e7f
+AT_SETUP([reflink ignores non-transcoded package])
af2e7f
+AT_KEYWORDS([reflink])
af2e7f
+AT_CHECK([
af2e7f
+RPMDB_INIT
af2e7f
+
af2e7f
+runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /data/RPMS/hello-2.0-1.x86_64.rpm && exit $?
af2e7f
+# Check that the file is properly installed in chroot
af2e7f
+test -f ${RPMTEST}/usr/bin/hello
af2e7f
+],
af2e7f
+[0],
af2e7f
+[],
af2e7f
+[])
af2e7f
+AT_CLEANUP
af2e7f
+
af2e7f
+AT_SETUP([reflink hardlink package])
af2e7f
+AT_KEYWORDS([reflink hardlink])
af2e7f
+AT_SKIP_IF([$REFLINK_DISABLED])
af2e7f
+AT_CHECK([
af2e7f
+RPMDB_INIT
af2e7f
+
af2e7f
+PKG=hlinktest-1.0-1.noarch.rpm
af2e7f
+runroot_other cat /data/RPMS/${PKG} | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/${PKG} 2> /dev/null
af2e7f
+runroot_plugins rpm -i --nodeps --undefine=%__transaction_dbus_announce /tmp/${PKG}
af2e7f
+],
af2e7f
+[0],
af2e7f
+[],
af2e7f
+[])
af2e7f
+AT_CLEANUP
af2e7f
diff --git a/tests/rpmtests.at b/tests/rpmtests.at
af2e7f
index d675452..3e6d528 100644
af2e7f
--- a/tests/rpmtests.at
af2e7f
+++ b/tests/rpmtests.at
af2e7f
@@ -24,3 +24,4 @@ m4_include([rpmreplace.at])
af2e7f
 m4_include([rpmconfig.at])
af2e7f
 m4_include([rpmconfig2.at])
af2e7f
 m4_include([rpmconfig3.at])
af2e7f
+m4_include([rpm2extents.at])
af2e7f
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
af2e7f
index f4fbefe..d369d20 100644
af2e7f
--- a/tools/CMakeLists.txt
af2e7f
+++ b/tools/CMakeLists.txt
af2e7f
@@ -12,6 +12,7 @@ add_executable(rpmdeps rpmdeps.c)
af2e7f
 add_executable(rpmgraph rpmgraph.c)
af2e7f
 add_executable(rpmlua rpmlua.c)
af2e7f
 add_executable(rpmuncompress rpmuncompress.c)
af2e7f
+add_executable(rpm2extents rpm2extents.c)
af2e7f
 
af2e7f
 target_link_libraries(rpmsign PRIVATE librpmsign)
af2e7f
 target_link_libraries(rpmlua PRIVATE LUA::LUA)
af2e7f
@@ -32,6 +33,7 @@ endif()
af2e7f
 
af2e7f
 target_include_directories(rpmlua PRIVATE ${CMAKE_SOURCE_DIR}/rpmio)
af2e7f
 target_include_directories(rpmgraph PRIVATE ${CMAKE_SOURCE_DIR}/lib)
af2e7f
+target_include_directories(rpm2extents PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/rpmio)
af2e7f
 
af2e7f
 if (READLINE_FOUND)
af2e7f
 	target_link_libraries(rpmspec PRIVATE PkgConfig::READLINE)
af2e7f
@@ -62,5 +64,4 @@ install(TARGETS
af2e7f
 	rpm rpmdb rpmkeys rpm2cpio rpmsign rpmbuild rpmspec
af2e7f
 	rpmlua rpmgraph
af2e7f
 )
af2e7f
-install(TARGETS rpmdeps rpmuncompress DESTINATION ${RPM_CONFIGDIR})
af2e7f
-
af2e7f
+install(TARGETS rpmdeps rpmuncompress rpm2extents DESTINATION ${RPM_CONFIGDIR})
af2e7f
diff --git a/tools/rpm2extents.c b/tools/rpm2extents.c
af2e7f
new file mode 100644
af2e7f
index 0000000..98124c2
af2e7f
--- /dev/null
af2e7f
+++ b/tools/rpm2extents.c
af2e7f
@@ -0,0 +1,707 @@
af2e7f
+/* rpm2extents: convert payload to inline extents */
af2e7f
+
af2e7f
+#include "system.h"
af2e7f
+
af2e7f
+#include <rpm/rpmcli.h>
af2e7f
+#include <rpm/rpmlib.h>		/* rpmReadPackageFile .. */
af2e7f
+#include <rpm/rpmlog.h>
af2e7f
+#include <rpm/rpmfi.h>
af2e7f
+#include <rpm/rpmtag.h>
af2e7f
+#include <rpm/rpmio.h>
af2e7f
+#include <rpm/rpmpgp.h>
af2e7f
+#include <rpm/rpmts.h>
af2e7f
+#include <rpm/rpmextents_internal.h>
af2e7f
+
af2e7f
+#include "rpmlead.h"
af2e7f
+#include "signature.h"
af2e7f
+#include "header_internal.h"
af2e7f
+#include "rpmio_internal.h"
af2e7f
+
af2e7f
+#include <unistd.h>
af2e7f
+#include <sys/types.h>
af2e7f
+#include <sys/wait.h>
af2e7f
+#include <signal.h>
af2e7f
+#include <errno.h>
af2e7f
+#include <string.h>
af2e7f
+
af2e7f
+#include "debug.h"
af2e7f
+
af2e7f
+/* hash of void * (pointers) to file digests to offsets within output.
af2e7f
+ * The length of the key depends on what the FILEDIGESTALGO is.
af2e7f
+ */
af2e7f
+#undef HASHTYPE
af2e7f
+#undef HTKEYTYPE
af2e7f
+#undef HTDATATYPE
af2e7f
+#define HASHTYPE digestSet
af2e7f
+#define HTKEYTYPE const unsigned char *
af2e7f
+#include "rpmhash.H"
af2e7f
+#include "rpmhash.C"
af2e7f
+
af2e7f
+struct digestoffset {
af2e7f
+    const unsigned char * digest;
af2e7f
+    rpm_loff_t pos;
af2e7f
+};
af2e7f
+
af2e7f
+rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit);
af2e7f
+
af2e7f
+rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit)
af2e7f
+{
af2e7f
+    return (unit - (pos % unit)) % unit;
af2e7f
+}
af2e7f
+
af2e7f
+static struct poptOption optionsTable[] = {
af2e7f
+    { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0,
af2e7f
+    N_("Common options for all rpm modes and executables:"), NULL },
af2e7f
+
af2e7f
+    POPT_AUTOALIAS
af2e7f
+    POPT_AUTOHELP
af2e7f
+    POPT_TABLEEND
af2e7f
+};
af2e7f
+
af2e7f
+
af2e7f
+static void FDDigestInit(FD_t fdi, uint8_t algos[], uint32_t algos_len){
af2e7f
+    int algo;
af2e7f
+
af2e7f
+    for (algo = 0; algo < algos_len; algo++) {
af2e7f
+	fdInitDigest(fdi, algos[algo], 0);
af2e7f
+    }
af2e7f
+}
af2e7f
+
af2e7f
+static int FDWriteDigests(
af2e7f
+    FD_t fdi,
af2e7f
+    FD_t fdo,
af2e7f
+    uint8_t algos[],
af2e7f
+    uint32_t algos_len)
af2e7f
+{
af2e7f
+    const char *filedigest, *algo_name;
af2e7f
+    size_t filedigest_len, len;
af2e7f
+    uint32_t algo_name_len, algo_digest_len;
af2e7f
+    int algo;
af2e7f
+    rpmRC rc = RPMRC_FAIL;
af2e7f
+
af2e7f
+    ssize_t fdilength = fdOp(fdi, FDSTAT_READ)->bytes;
af2e7f
+
af2e7f
+    len = sizeof(fdilength);
af2e7f
+    if (Fwrite(&fdilength, len, 1, fdo) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("Unable to write input length %zd: %d, %s\n"),
af2e7f
+	       fdilength, errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    len = sizeof(algos_len);
af2e7f
+    if (Fwrite(&algos_len, len, 1, fdo) != len) {
af2e7f
+	algo_digest_len = (uint32_t)filedigest_len;
af2e7f
+	rpmlog(RPMLOG_ERR, _("Unable to write number of digests: %d, %s\n"),
af2e7f
+	       errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    for (algo = 0; algo < algos_len; algo++) {
af2e7f
+	fdFiniDigest(fdi, algos[algo], (void **)&filedigest, &filedigest_len, 0);
af2e7f
+
af2e7f
+	algo_name = pgpValString(PGPVAL_HASHALGO, algos[algo]);
af2e7f
+	algo_name_len = (uint32_t)strlen(algo_name);
af2e7f
+	algo_digest_len = (uint32_t)filedigest_len;
af2e7f
+
af2e7f
+	len = sizeof(algo_name_len);
af2e7f
+	if (Fwrite(&algo_name_len, len, 1, fdo) != len) {
af2e7f
+	    rpmlog(RPMLOG_ERR,
af2e7f
+		   _("Unable to write digest algo name length: %d, %s\n"),
af2e7f
+		   errno, strerror(errno));
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+	len = sizeof(algo_digest_len);
af2e7f
+	if (Fwrite(&algo_digest_len, len, 1, fdo) != len) {
af2e7f
+	    rpmlog(RPMLOG_ERR,
af2e7f
+		   _("Unable to write number of bytes for digest: %d, %s\n"),
af2e7f
+		   errno, strerror(errno));
af2e7f
+	     goto exit;
af2e7f
+	}
af2e7f
+	if (Fwrite(algo_name, algo_name_len, 1, fdo) != algo_name_len) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write digest algo name: %d, %s\n"),
af2e7f
+		   errno, strerror(errno));
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+	if (Fwrite(filedigest, algo_digest_len, 1, fdo ) != algo_digest_len) {
af2e7f
+	    rpmlog(RPMLOG_ERR,
af2e7f
+		   _("Unable to write digest value %u, %zu: %d, %s\n"),
af2e7f
+		   algo_digest_len, filedigest_len,
af2e7f
+		   errno, strerror(errno));
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+    }
af2e7f
+    rc = RPMRC_OK;
af2e7f
+exit:
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+/**
af2e7f
+ * Check if package is in deny list.
af2e7f
+ * @param package_name	package name
af2e7f
+ * @return 		true if package is in deny list
af2e7f
+ */
af2e7f
+static inline int isInDenyList(char *package_name)
af2e7f
+{
af2e7f
+    int is_in_deny_list = 0;
af2e7f
+    if (package_name) {
af2e7f
+	char *e_denylist = getenv("LIBREPO_TRANSCODE_RPMS_DENYLIST");
af2e7f
+	char *denytlist_item = strtok(e_denylist, ",");
af2e7f
+	while (denytlist_item) {
af2e7f
+	    if (strstr(package_name, denytlist_item)) {
af2e7f
+		is_in_deny_list = 1;
af2e7f
+		break;
af2e7f
+	    }
af2e7f
+	    denytlist_item = strtok(NULL, ",");
af2e7f
+	}
af2e7f
+    }
af2e7f
+    return is_in_deny_list;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC FDWriteSignaturesValidation(FD_t fdo, int rpmvsrc, char *msg) {
af2e7f
+    size_t len;
af2e7f
+    rpmRC rc = RPMRC_FAIL;
af2e7f
+
af2e7f
+    if(rpmvsrc){
af2e7f
+	rpmlog(RPMLOG_WARNING,
af2e7f
+	       _("Error verifying package signatures:\n%s\n"), msg);
af2e7f
+    }
af2e7f
+
af2e7f
+    len = sizeof(rpmvsrc);
af2e7f
+    if (Fwrite(&rpmvsrc, len, 1, fdo) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR,
af2e7f
+	       _("Unable to write signature verification RC code %d: %d, %s\n"),
af2e7f
+	       rpmvsrc, errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    size_t content_len = msg ? strlen(msg) : 0;
af2e7f
+    len = sizeof(content_len);
af2e7f
+    if (Fwrite(&content_len, len, 1, fdo) != len) {
af2e7f
+	rpmlog(RPMLOG_ERR,
af2e7f
+	       _("Unable to write signature verification output length %zd: %d, %s\n"),
af2e7f
+	       content_len, errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    if (Fwrite(msg, content_len, 1, fdo) != content_len) {
af2e7f
+	rpmlog(RPMLOG_ERR,
af2e7f
+	       _("Unable to write signature verification output %s: %d, %s\n"),
af2e7f
+	       msg, errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+
af2e7f
+    rc = RPMRC_OK;
af2e7f
+exit:
af2e7f
+
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC validator(FD_t fdi, FD_t digesto, FD_t sigo,
af2e7f
+	uint8_t algos[],
af2e7f
+	uint32_t algos_len){
af2e7f
+    int rpmvsrc;
af2e7f
+    rpmRC rc = RPMRC_FAIL;
af2e7f
+    char *msg = NULL;
af2e7f
+    rpmts ts = rpmtsCreate();
af2e7f
+
af2e7f
+    rpmtsSetRootDir(ts, rpmcliRootDir);
af2e7f
+
af2e7f
+    FDDigestInit(fdi, algos, algos_len);
af2e7f
+
af2e7f
+    rpmvsrc = rpmcliVerifySignaturesFD(ts, fdi, &msg;;
af2e7f
+
af2e7f
+    // Write result of digest computation
af2e7f
+    if(FDWriteDigests(fdi, digesto, algos, algos_len) != RPMRC_OK) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("Failed to write digests: %d, %s\n"),
af2e7f
+	       errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+
af2e7f
+    // Write result of signature validation.
af2e7f
+    if(FDWriteSignaturesValidation(sigo, rpmvsrc, msg)) {
af2e7f
+	rpmlog(RPMLOG_ERR,
af2e7f
+	       _("Failed to write signature verification result: %d, %s\n"),
af2e7f
+	       errno, strerror(errno));
af2e7f
+	goto exit;
af2e7f
+    }
af2e7f
+    rc = RPMRC_OK;
af2e7f
+exit:
af2e7f
+    if(msg) {
af2e7f
+	free(msg);
af2e7f
+    }
af2e7f
+    rpmtsFree(ts);
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+static void sanitizeSignatureHeader(Header * sigh)
af2e7f
+{
af2e7f
+    struct rpmtd_s td;
af2e7f
+
af2e7f
+    /* This is inspired by the code in unloadImmutableRegion. See https://github.com/rpm-software-management/rpm/pull/1330 */
af2e7f
+    if (!headerGet(*sigh, RPMTAG_HEADERSIGNATURES, &td, HEADERGET_DEFAULT)) {
af2e7f
+	/* Signature header corrupt/missing */
af2e7f
+	rpmlog(RPMLOG_WARNING, _("Error verifying signature header\n"));
af2e7f
+	rpmtdFreeData(&td);
af2e7f
+	Header nh = headerCopy(*sigh);
af2e7f
+	headerFree(*sigh);
af2e7f
+	*sigh = headerLink(nh);
af2e7f
+	headerFree(nh);
af2e7f
+    }
af2e7f
+    rpmtdFreeData(&td);
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi)
af2e7f
+{
af2e7f
+    uint32_t diglen;
af2e7f
+    /* GNU C extension: can use diglen from outer context */
af2e7f
+    int digestSetCmp(const unsigned char * a, const unsigned char * b) {
af2e7f
+	return memcmp(a, b, diglen);
af2e7f
+    }
af2e7f
+
af2e7f
+    unsigned int digestSetHash(const unsigned char * digest) {
af2e7f
+        /* assumes sizeof(unsigned int) < diglen */
af2e7f
+        return *(unsigned int *)digest;
af2e7f
+    }
af2e7f
+
af2e7f
+    int digestoffsetCmp(const void * a, const void * b) {
af2e7f
+	return digestSetCmp(
af2e7f
+	    ((struct digestoffset *)a)->digest,
af2e7f
+	    ((struct digestoffset *)b)->digest
af2e7f
+	);
af2e7f
+    }
af2e7f
+
af2e7f
+    FD_t fdo;
af2e7f
+    FD_t gzdi;
af2e7f
+    Header h, sigh;
af2e7f
+    long fundamental_block_size = sysconf(_SC_PAGESIZE);
af2e7f
+    rpmRC rc = RPMRC_OK;
af2e7f
+    rpm_mode_t mode;
af2e7f
+    char *rpmio_flags = NULL, *zeros;
af2e7f
+    const unsigned char *digest;
af2e7f
+    rpm_loff_t pos, size, pad, digest_pos, validation_pos, digest_table_pos;
af2e7f
+    uint32_t offset_ix = 0;
af2e7f
+    size_t len;
af2e7f
+    int next = 0;
af2e7f
+    struct rpmlead_s l;
af2e7f
+    rpmfiles files = NULL;
af2e7f
+    rpmfi fi = NULL;
af2e7f
+    char *msg = NULL;
af2e7f
+    struct digestoffset *offsets = NULL;
af2e7f
+    digestSet ds = NULL;
af2e7f
+
af2e7f
+    fdo = fdDup(STDOUT_FILENO);
af2e7f
+
af2e7f
+    rc = rpmLeadReadAndReturn(fdi, &msg, &l);
af2e7f
+    if (rc != RPMRC_OK)
af2e7f
+	goto exit;
af2e7f
+
af2e7f
+    /* Skip conversion if package is in deny list */
af2e7f
+    if (isInDenyList(l.name)) {
af2e7f
+	rpmlog(RPMLOG_WARNING, _("package %s is in deny list: conversion skipped\n"), l.name);
af2e7f
+	if (rpmLeadWrite(fdo, l)) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write package lead: %s\n"),
af2e7f
+		    Fstrerror(fdo));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+
af2e7f
+	ssize_t fdilength = ufdCopy(fdi, fdo);
af2e7f
+	if (fdilength == -1) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("process_package cat failed\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+
af2e7f
+	goto exit;
af2e7f
+    } else {
af2e7f
+	if (rpmReadPackageRaw(fdi, &sigh, &h)) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Error reading package\n"));
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+
af2e7f
+	sanitizeSignatureHeader(&sigh;;
af2e7f
+
af2e7f
+	if (rpmLeadWriteFromHeader(fdo, h)) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write package lead: %s\n"),
af2e7f
+		   Fstrerror(fdo));
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+
af2e7f
+	if (rpmWriteSignature(fdo, sigh)) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write signature: %s\n"),
af2e7f
+		   Fstrerror(fdo));
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+
af2e7f
+	if (headerWrite(fdo, h, HEADER_MAGIC_YES)) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write headers: %s\n"),
af2e7f
+		   Fstrerror(fdo));
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+
af2e7f
+	/* Retrieve payload size and compression type. */
af2e7f
+	{
af2e7f
+	    const char *compr =
af2e7f
+		headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
af2e7f
+	    rpmio_flags =
af2e7f
+		rstrscat(NULL, "r.", compr ? compr : "gzip", NULL);
af2e7f
+	}
af2e7f
+
af2e7f
+	gzdi = Fdopen(fdi, rpmio_flags);	/* XXX gzdi == fdi */
af2e7f
+	free(rpmio_flags);
af2e7f
+
af2e7f
+	if (gzdi == NULL) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("cannot re-open payload: %s\n"),
af2e7f
+		   Fstrerror(gzdi));
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+
af2e7f
+	files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER);
af2e7f
+	fi = rpmfiNewArchiveReader(gzdi, files,
af2e7f
+				   RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST);
af2e7f
+
af2e7f
+	/* this is encoded in the file format, so needs to be fixed size (for
af2e7f
+	 * now?)
af2e7f
+	 */
af2e7f
+	diglen = (uint32_t) rpmDigestLength(rpmfiDigestAlgo(fi));
af2e7f
+	ds = digestSetCreate(rpmfiFC(fi), digestSetHash, digestSetCmp, NULL);
af2e7f
+	offsets = xcalloc(rpmfiFC(fi), sizeof(*offsets));
af2e7f
+	pos = RPMLEAD_SIZE + headerSizeof(sigh, HEADER_MAGIC_YES);
af2e7f
+
af2e7f
+	/* main headers are aligned to 8 byte boundry */
af2e7f
+	pos += pad_to(pos, 8);
af2e7f
+	pos += headerSizeof(h, HEADER_MAGIC_YES);
af2e7f
+
af2e7f
+	zeros = xcalloc(fundamental_block_size, 1);
af2e7f
+
af2e7f
+	while (next >= 0) {
af2e7f
+	    next = rpmfiNext(fi);
af2e7f
+	    if (next == RPMERR_ITER_END) {
af2e7f
+		rc = RPMRC_OK;
af2e7f
+		break;
af2e7f
+	    }
af2e7f
+	    mode = rpmfiFMode(fi);
af2e7f
+	    if (!S_ISREG(mode) || !rpmfiArchiveHasContent(fi)) {
af2e7f
+		/* not a regular file, or the archive doesn't contain any content
af2e7f
+		 * for this entry.
af2e7f
+		 */
af2e7f
+		continue;
af2e7f
+	    }
af2e7f
+	    digest = rpmfiFDigest(fi, NULL, NULL);
af2e7f
+	    if (digestSetGetEntry(ds, digest, NULL)) {
af2e7f
+		/* This specific digest has already been included, so skip it. */
af2e7f
+		continue;
af2e7f
+	    }
af2e7f
+	    pad = pad_to(pos, fundamental_block_size);
af2e7f
+	    if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
af2e7f
+		rpmlog(RPMLOG_ERR, _("Unable to write padding\n"));
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+		goto exit;
af2e7f
+	    }
af2e7f
+	    /* round up to next fundamental_block_size */
af2e7f
+	    pos += pad;
af2e7f
+	    digestSetAddEntry(ds, digest);
af2e7f
+	    offsets[offset_ix].digest = digest;
af2e7f
+	    offsets[offset_ix].pos = pos;
af2e7f
+	    offset_ix++;
af2e7f
+	    size = rpmfiFSize(fi);
af2e7f
+	    rc = rpmfiArchiveReadToFile(fi, fdo, 0);
af2e7f
+	    if (rc != RPMRC_OK) {
af2e7f
+		char *errstr = rpmfileStrerror(rc);
af2e7f
+		rpmlog(RPMLOG_ERR,
af2e7f
+		       _("rpmfiArchiveReadToFile failed while extracting "
af2e7f
+			 "\"%s\" with RC %d: %s\n"),
af2e7f
+		       rpmfiFN(fi), rc, errstr);
af2e7f
+		free(errstr);
af2e7f
+		goto exit;
af2e7f
+	    }
af2e7f
+	    pos += size;
af2e7f
+	}
af2e7f
+	Fclose(gzdi);		/* XXX gzdi == fdi */
af2e7f
+
af2e7f
+	qsort(offsets, (size_t) offset_ix, sizeof(struct digestoffset),
af2e7f
+	      digestoffsetCmp);
af2e7f
+
af2e7f
+	validation_pos = pos;
af2e7f
+	ssize_t validation_len = ufdCopy(validationi, fdo);
af2e7f
+	if (validation_len == -1) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("validation output ufdCopy failed\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+
af2e7f
+	digest_table_pos = validation_pos + validation_len;
af2e7f
+
af2e7f
+	len = sizeof(offset_ix);
af2e7f
+	if (Fwrite(&offset_ix, len, 1, fdo) != len) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write length of table\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+	len = sizeof(diglen);
af2e7f
+	if (Fwrite(&diglen, len, 1, fdo) != len) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write length of digest\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+	len = sizeof(rpm_loff_t);
af2e7f
+	for (int x = 0; x < offset_ix; x++) {
af2e7f
+	    if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) {
af2e7f
+		rpmlog(RPMLOG_ERR, _("Unable to write digest\n"));
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+		goto exit;
af2e7f
+	    }
af2e7f
+	    if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) {
af2e7f
+		rpmlog(RPMLOG_ERR, _("Unable to write offset\n"));
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+		goto exit;
af2e7f
+	    }
af2e7f
+	}
af2e7f
+	digest_pos =
af2e7f
+	    (digest_table_pos + sizeof(offset_ix) + sizeof(diglen) +
af2e7f
+	     offset_ix * (diglen + sizeof(rpm_loff_t))
af2e7f
+	    );
af2e7f
+
af2e7f
+	ssize_t digest_len = ufdCopy(digestori, fdo);
af2e7f
+	if (digest_len == -1) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("digest table ufdCopy failed\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+
af2e7f
+	/* add more padding so the last file can be cloned. It doesn't matter that
af2e7f
+	 * the table and validation etc are in this space. In fact, it's pretty
af2e7f
+	 * efficient if it is.
af2e7f
+	 */
af2e7f
+
af2e7f
+	pad =
af2e7f
+	    pad_to((validation_pos + validation_len +
af2e7f
+		    2 * sizeof(rpm_loff_t) + sizeof(uint64_t)),
af2e7f
+		   fundamental_block_size);
af2e7f
+	if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write final padding\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+	zeros = _free(zeros);
af2e7f
+	struct extents_footer_t footer = {.offsets =
af2e7f
+		{ validation_pos, digest_table_pos, digest_pos },.magic =
af2e7f
+	    EXTENTS_MAGIC };
af2e7f
+	len = sizeof(footer);
af2e7f
+	if (Fwrite(&footer, len, 1, fdo) != len) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Unable to write footer\n"));
af2e7f
+	    rc = RPMRC_FAIL;
af2e7f
+	    goto exit;
af2e7f
+	}
af2e7f
+    }
af2e7f
+
af2e7f
+  exit:
af2e7f
+    rpmfilesFree(files);
af2e7f
+    rpmfiFree(fi);
af2e7f
+    headerFree(h);
af2e7f
+    headerFree(sigh);
af2e7f
+    free(offsets);
af2e7f
+    Fclose(fdo);
af2e7f
+    digestSetFree(ds);
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+static off_t ufdTee(FD_t sfd, FD_t *fds, int len)
af2e7f
+{
af2e7f
+    char buf[BUFSIZ];
af2e7f
+    ssize_t rdbytes, wrbytes;
af2e7f
+    off_t total = 0;
af2e7f
+
af2e7f
+    while (1) {
af2e7f
+	rdbytes = Fread(buf, sizeof(buf[0]), sizeof(buf), sfd);
af2e7f
+
af2e7f
+	if (rdbytes > 0) {
af2e7f
+	    for(int i=0; i < len; i++) {
af2e7f
+		wrbytes = Fwrite(buf, sizeof(buf[0]), rdbytes, fds[i]);
af2e7f
+		if (wrbytes != rdbytes) {
af2e7f
+		    rpmlog(RPMLOG_ERR,
af2e7f
+			   _("Error wriing to FD %d: %s\n"),
af2e7f
+			   i, Fstrerror(fds[i]));
af2e7f
+		    total = -1;
af2e7f
+		    break;
af2e7f
+		}
af2e7f
+	    }
af2e7f
+	    if(total == -1){
af2e7f
+		break;
af2e7f
+	    }
af2e7f
+	    total += wrbytes;
af2e7f
+	} else {
af2e7f
+	    if (rdbytes < 0)
af2e7f
+		total = -1;
af2e7f
+	    break;
af2e7f
+	}
af2e7f
+    }
af2e7f
+
af2e7f
+    return total;
af2e7f
+}
af2e7f
+
af2e7f
+static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) {
af2e7f
+    rpmRC rc = RPMRC_FAIL;
af2e7f
+    off_t offt = -1;
af2e7f
+    // tee-ed stdin
af2e7f
+    int processorpipefd[2];
af2e7f
+    int validatorpipefd[2];
af2e7f
+    // metadata
af2e7f
+    int meta_digestpipefd[2];
af2e7f
+    int meta_rpmsignpipefd[2];
af2e7f
+
af2e7f
+    pid_t cpids[2], w;
af2e7f
+    int wstatus;
af2e7f
+    FD_t fds[2];
af2e7f
+
af2e7f
+     if (pipe(processorpipefd) == -1) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("Processor pipe failure\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+
af2e7f
+    if (pipe(validatorpipefd) == -1) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("Validator pipe failure\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+
af2e7f
+    if (pipe(meta_digestpipefd) == -1) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("Meta digest pipe failure\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+
af2e7f
+    if (pipe(meta_rpmsignpipefd) == -1) {
af2e7f
+	rpmlog(RPMLOG_ERR, _("Meta rpm signature pipe failure\n"));
af2e7f
+	return RPMRC_FAIL;
af2e7f
+    }
af2e7f
+
af2e7f
+    cpids[0] = fork();
af2e7f
+    if (cpids[0] == 0) {
af2e7f
+	/* child: validator */
af2e7f
+	close(processorpipefd[0]);
af2e7f
+	close(processorpipefd[1]);
af2e7f
+	close(validatorpipefd[1]);
af2e7f
+	close(meta_digestpipefd[0]);
af2e7f
+	close(meta_rpmsignpipefd[0]);
af2e7f
+	FD_t fdi = fdDup(validatorpipefd[0]);
af2e7f
+	FD_t digesto = fdDup(meta_digestpipefd[1]);
af2e7f
+	FD_t sigo = fdDup(meta_rpmsignpipefd[1]);
af2e7f
+	close(meta_digestpipefd[1]);
af2e7f
+	close(meta_rpmsignpipefd[1]);
af2e7f
+	rc = validator(fdi, digesto, sigo, algos, algos_len);
af2e7f
+	if(rc != RPMRC_OK) {
af2e7f
+	    rpmlog(RPMLOG_ERR, _("Validator failed with RC %d\n"), rc);
af2e7f
+	}
af2e7f
+	Fclose(fdi);
af2e7f
+	Fclose(digesto);
af2e7f
+	Fclose(sigo);
af2e7f
+	if (rc != RPMRC_OK) {
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+	exit(EXIT_SUCCESS);
af2e7f
+    } else {
af2e7f
+	/* parent: main program */
af2e7f
+	cpids[1] = fork();
af2e7f
+	if (cpids[1] == 0) {
af2e7f
+	    /* child: process_package */
af2e7f
+	    close(validatorpipefd[0]);
af2e7f
+	    close(validatorpipefd[1]);
af2e7f
+	    close(processorpipefd[1]);
af2e7f
+	    close(meta_digestpipefd[1]);
af2e7f
+	    close(meta_rpmsignpipefd[1]);
af2e7f
+	    FD_t fdi = fdDup(processorpipefd[0]);
af2e7f
+	    close(processorpipefd[0]);
af2e7f
+	    FD_t sigi = fdDup(meta_rpmsignpipefd[0]);
af2e7f
+	    close(meta_rpmsignpipefd[0]);
af2e7f
+	    FD_t digestori = fdDup(meta_digestpipefd[0]);
af2e7f
+	    close(meta_digestpipefd[0]);
af2e7f
+
af2e7f
+	    rc = process_package(fdi, digestori, sigi);
af2e7f
+	    if(rc != RPMRC_OK) {
af2e7f
+		rpmlog(RPMLOG_ERR, _("Package processor failed: %d\n"), rc);
af2e7f
+	    }
af2e7f
+	    Fclose(digestori);
af2e7f
+	    Fclose(sigi);
af2e7f
+	    /* fdi is normally closed through the stacked file gzdi in the
af2e7f
+	     * function
af2e7f
+	     */
af2e7f
+
af2e7f
+	    if (rc != RPMRC_OK) {
af2e7f
+		exit(EXIT_FAILURE);
af2e7f
+	    }
af2e7f
+	    exit(EXIT_SUCCESS);
af2e7f
+
af2e7f
+
af2e7f
+	} else {
af2e7f
+	    /* Actual parent. Read from fdi and write to both processes */
af2e7f
+	    close(processorpipefd[0]);
af2e7f
+	    close(validatorpipefd[0]);
af2e7f
+	    fds[0] = fdDup(processorpipefd[1]);
af2e7f
+	    fds[1] = fdDup(validatorpipefd[1]);
af2e7f
+	    close(validatorpipefd[1]);
af2e7f
+	    close(processorpipefd[1]);
af2e7f
+	    close(meta_digestpipefd[0]);
af2e7f
+	    close(meta_digestpipefd[1]);
af2e7f
+	    close(meta_rpmsignpipefd[0]);
af2e7f
+	    close(meta_rpmsignpipefd[1]);
af2e7f
+
af2e7f
+	    rc = RPMRC_OK;
af2e7f
+	    offt = ufdTee(fdi, fds, 2);
af2e7f
+	    if(offt == -1){
af2e7f
+		rpmlog(RPMLOG_ERR, _("Failed to tee RPM\n"));
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+	    }
af2e7f
+	    Fclose(fds[0]);
af2e7f
+	    Fclose(fds[1]);
af2e7f
+	    w = waitpid(cpids[0], &wstatus, 0);
af2e7f
+	    if (w == -1) {
af2e7f
+		rpmlog(RPMLOG_ERR, _("waitpid cpids[0] failed\n"));
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+	    }
af2e7f
+	    w = waitpid(cpids[1], &wstatus, 0);
af2e7f
+	    if (w == -1) {
af2e7f
+		rpmlog(RPMLOG_ERR, _("waitpid cpids[1] failed\n"));
af2e7f
+		rc = RPMRC_FAIL;
af2e7f
+	    }
af2e7f
+	}
af2e7f
+    }
af2e7f
+
af2e7f
+    return rc;
af2e7f
+}
af2e7f
+
af2e7f
+int main(int argc, char *argv[]) {
af2e7f
+    rpmRC rc;
af2e7f
+    poptContext optCon = NULL;
af2e7f
+    const char **args = NULL;
af2e7f
+    int nb_algos = 0;
af2e7f
+
af2e7f
+    xsetprogname(argv[0]);	/* Portability call -- see system.h */
af2e7f
+    rpmReadConfigFiles(NULL, NULL);
af2e7f
+    optCon = rpmcliInit(argc, argv, optionsTable);
af2e7f
+    poptSetOtherOptionHelp(optCon, "[OPTIONS]* <DIGESTALGO>");
af2e7f
+
af2e7f
+    if (poptPeekArg(optCon) == NULL) {
af2e7f
+	rpmlog(RPMLOG_ERR,
af2e7f
+	       _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n"));
af2e7f
+	poptPrintUsage(optCon, stderr, 0);
af2e7f
+	exit(EXIT_FAILURE);
af2e7f
+    }
af2e7f
+
af2e7f
+    args = poptGetArgs(optCon);
af2e7f
+
af2e7f
+    for (nb_algos=0; args[nb_algos]; nb_algos++);
af2e7f
+    uint8_t algos[nb_algos];
af2e7f
+    for (int x = 0; x < nb_algos; x++) {
af2e7f
+	if (pgpStringVal(PGPVAL_HASHALGO, args[x], &algos[x]) != 0)
af2e7f
+	{
af2e7f
+	    rpmlog(RPMLOG_ERR,
af2e7f
+		   _("Unable to resolve '%s' as a digest algorithm, exiting\n"),
af2e7f
+		   args[x]);
af2e7f
+	    exit(EXIT_FAILURE);
af2e7f
+	}
af2e7f
+    }
af2e7f
+
af2e7f
+    FD_t fdi = fdDup(STDIN_FILENO);
af2e7f
+    rc = teeRpm(fdi, algos, nb_algos);
af2e7f
+    Fclose(fdi);
af2e7f
+    if (rc != RPMRC_OK) {
af2e7f
+	/* translate rpmRC into generic failure return code. */
af2e7f
+	return EXIT_FAILURE;
af2e7f
+    }
af2e7f
+    return EXIT_SUCCESS;
af2e7f
+}
af2e7f
-- 
af2e7f
2.47.1
af2e7f