teknoraver / rpms / rpm

Forked from rpms/rpm 4 months ago
Clone

Blame 0001-RPM-with-Copy-on-Write.patch

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