alexk / rpms / rpm

Forked from rpms/rpm 2 years ago
Clone
c89eec
diff --git a/Makefile.am b/Makefile.am
c89eec
index 288668819..96542c8c8 100644
c89eec
--- a/Makefile.am
c89eec
+++ b/Makefile.am
c89eec
@@ -185,7 +185,7 @@ bin_PROGRAMS +=		rpmgraph
c89eec
 rpmgraph_SOURCES =	tools/rpmgraph.c
c89eec
 rpmgraph_LDADD =	lib/librpm.la rpmio/librpmio.la @WITH_POPT_LIB@
c89eec
 
c89eec
-dist_bin_SCRIPTS =	scripts/gendiff
c89eec
+dist_bin_SCRIPTS =	scripts/gendiff scripts/rpm2extents_dump
c89eec
 
c89eec
 rpmconfig_DATA = rpmrc
c89eec
 rpmrc: $(top_srcdir)/rpmrc.in
c89eec
diff --git a/lib/Makefile.am b/lib/Makefile.am
c89eec
index 5a1b6ca9b..2f1b3597f 100644
c89eec
--- a/lib/Makefile.am
c89eec
+++ b/lib/Makefile.am
c89eec
@@ -40,7 +40,8 @@ librpm_la_SOURCES = \
c89eec
 	rpmscript.h rpmscript.c \
c89eec
 	rpmchroot.c rpmchroot.h \
c89eec
 	rpmplugins.c rpmplugins.h rpmplugin.h rpmug.c rpmug.h \
c89eec
-	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h
c89eec
+	rpmtriggers.h rpmtriggers.c rpmvs.c rpmvs.h \
c89eec
+	rpmextents.c rpmextents_internal.h
c89eec
 
c89eec
 librpm_la_LDFLAGS = -version-info $(rpm_version_info)
c89eec
 
c89eec
diff --git a/lib/depends.c b/lib/depends.c
c89eec
index 8998afcd3..30234df3d 100644
c89eec
--- a/lib/depends.c
c89eec
+++ b/lib/depends.c
c89eec
@@ -81,8 +81,6 @@ static rpmRC headerCheckPayloadFormat(Header h) {
c89eec
      */
c89eec
     if (!payloadfmt) return rc;
c89eec
 
c89eec
-    if (rstreq(payloadfmt, "clon")) return rc;
c89eec
-
c89eec
     if (!rstreq(payloadfmt, "cpio")) {
c89eec
         char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
c89eec
         if (payloadfmt && rstreq(payloadfmt, "drpm")) {
c89eec
diff --git a/lib/fsm.c b/lib/fsm.c
c89eec
index feda3750c..6972602e0 100644
c89eec
--- a/lib/fsm.c
c89eec
+++ b/lib/fsm.c
c89eec
@@ -19,7 +19,6 @@
c89eec
 
c89eec
 #include "rpmio/rpmio_internal.h"	/* fdInit/FiniDigest */
c89eec
 #include "lib/fsm.h"
c89eec
-#include "lib/rpmlib.h"
c89eec
 #include "lib/rpmte_internal.h"	/* XXX rpmfs */
c89eec
 #include "lib/rpmplugins.h"	/* rpm plugins hooks */
c89eec
 #include "lib/rpmug.h"
c89eec
@@ -53,7 +52,6 @@ struct filedata_s {
c89eec
     int stage;
c89eec
     int setmeta;
c89eec
     int skip;
c89eec
-    int plugin_contents;
c89eec
     rpmFileAction action;
c89eec
     const char *suffix;
c89eec
     char *fpath;
c89eec
@@ -893,14 +891,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
c89eec
     struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
c89eec
     struct filedata_s *firstlink = NULL;
c89eec
 
c89eec
-    Header h = rpmteHeader(te);
c89eec
-    const char *payloadfmt = headerGetString(h, RPMTAG_PAYLOADFORMAT);
c89eec
-    int cpio = 1;
c89eec
-
c89eec
-    if (payloadfmt && rstreq(payloadfmt, "clon")) {
c89eec
-	cpio = 0;
c89eec
-    }
c89eec
-
c89eec
     /* transaction id used for temporary path suffix while installing */
c89eec
     rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
c89eec
 
c89eec
@@ -921,23 +911,12 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
c89eec
 	/* Remap file perms, owner, and group. */
c89eec
 	rc = rpmfiStat(fi, 1, &fp->sb);
c89eec
 
c89eec
+	setFileState(fs, fx);
c89eec
 	fsmDebug(fp->fpath, fp->action, &fp->sb);
c89eec
 
c89eec
 	/* Run fsm file pre hook for all plugins */
c89eec
 	rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
c89eec
 				      fp->sb.st_mode, fp->action);
c89eec
-	fp->plugin_contents = 0;
c89eec
-	switch (rc) {
c89eec
-	case RPMRC_OK:
c89eec
-	    setFileState(fs, fx);
c89eec
-	    break;
c89eec
-	case RPMRC_PLUGIN_CONTENTS:
c89eec
-	    fp->plugin_contents = 1;
c89eec
-	    // reduce reads on cpio to this value. Could be zero if
c89eec
-	    // this is from a hard link.
c89eec
-	    rc = RPMRC_OK;
c89eec
-	    break;
c89eec
-	}
c89eec
 	fp->stage = FILE_PRE;
c89eec
     }
c89eec
     fi = rpmfiFree(fi);
c89eec
@@ -945,14 +924,24 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
c89eec
     if (rc)
c89eec
 	goto exit;
c89eec
 
c89eec
-    if (cpio) {
c89eec
-	fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
c89eec
-	if (fi == NULL) {
c89eec
-	    rc = RPMERR_BAD_MAGIC;
c89eec
-	    goto exit;
c89eec
-	}
c89eec
-    } else {
c89eec
-	fi = rpmfilesIter(files, RPMFI_ITER_FWD);
c89eec
+    rpmRC plugin_rc = rpmpluginsCallFsmFileArchiveReader(plugins, payload, files, &fi);
c89eec
+    switch(plugin_rc) {
c89eec
+	case RPMRC_PLUGIN_CONTENTS:
c89eec
+	    if(fi == NULL) {
c89eec
+		rc = RPMERR_BAD_MAGIC;
c89eec
+		goto exit;
c89eec
+	    }
c89eec
+	    rc = RPMRC_OK;
c89eec
+	    break;
c89eec
+	case RPMRC_OK:
c89eec
+	    fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
c89eec
+	    if (fi == NULL) {
c89eec
+		rc = RPMERR_BAD_MAGIC;
c89eec
+		goto exit;
c89eec
+	    }
c89eec
+	    break;
c89eec
+	default:
c89eec
+	    rc = RPMRC_FAIL;
c89eec
     }
c89eec
 
c89eec
     /* Detect and create directories not explicitly in package. */
c89eec
@@ -992,14 +981,15 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
c89eec
 	    if (fp->action == FA_TOUCH)
c89eec
 		continue;
c89eec
 
c89eec
-            if (S_ISREG(fp->sb.st_mode)) {
c89eec
+	    rpmRC plugin_rc = rpmpluginsCallFsmFileInstall(plugins, fi, fp->fpath, fp->sb.st_mode, fp->action);
c89eec
+	    if(!(plugin_rc == RPMRC_PLUGIN_CONTENTS || plugin_rc == RPMRC_OK)){
c89eec
+		rc = plugin_rc;
c89eec
+	    } else if(plugin_rc == RPMRC_PLUGIN_CONTENTS){
c89eec
+		rc = RPMRC_OK;
c89eec
+	    } else if (S_ISREG(fp->sb.st_mode)) {
c89eec
 		if (rc == RPMERR_ENOENT) {
c89eec
-		    if(fp->plugin_contents) {
c89eec
-			rc = RPMRC_OK;
c89eec
-		    }else {
c89eec
-			rc = fsmMkfile(fi, fp, files, psm, nodigest,
c89eec
-			    &firstlink, &firstlinkfile);
c89eec
-		    }
c89eec
+		    rc = fsmMkfile(fi, fp, files, psm, nodigest,
c89eec
+				   &firstlink, &firstlinkfile);
c89eec
 		}
c89eec
             } else if (S_ISDIR(fp->sb.st_mode)) {
c89eec
                 if (rc == RPMERR_ENOENT) {
c89eec
@@ -1107,7 +1097,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
c89eec
     rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
c89eec
 
c89eec
 exit:
c89eec
-    h = headerFree(h);
c89eec
     fi = rpmfiFree(fi);
c89eec
     Fclose(payload);
c89eec
     free(tid);
c89eec
diff --git a/lib/rpmchecksig.c b/lib/rpmchecksig.c
c89eec
index 40a3ab83f..c9fc3bbc9 100644
c89eec
--- a/lib/rpmchecksig.c
c89eec
+++ b/lib/rpmchecksig.c
c89eec
@@ -20,6 +20,7 @@
c89eec
 #include "rpmio/rpmio_internal.h" 	/* fdSetBundle() */
c89eec
 #include "lib/rpmlead.h"
c89eec
 #include "lib/header_internal.h"
c89eec
+#include "lib/rpmextents_internal.h"
c89eec
 #include "lib/rpmvs.h"
c89eec
 
c89eec
 #include "debug.h"
c89eec
@@ -221,36 +222,24 @@ rpmRC rpmpkgRead(struct rpmvs_s *vs, FD_t fd,
c89eec
 }
c89eec
 
c89eec
 static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags,
c89eec
-			   FD_t fd, const char *fn)
c89eec
+			   FD_t fd, rpmsinfoCb cb, void *cbdata)
c89eec
 {
c89eec
     char *msg = NULL;
c89eec
-    struct vfydata_s vd = { .seen = 0,
c89eec
-			    .bad = 0,
c89eec
-			    .verbose = rpmIsVerbose(),
c89eec
-    };
c89eec
     int rc;
c89eec
-    struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring);
c89eec
 
c89eec
-    rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd.verbose ? "\n" : "");
c89eec
+
c89eec
+    if(isTranscodedRpm(fd) == RPMRC_OK){
c89eec
+	return extentsVerifySigs(fd);
c89eec
+    }
c89eec
+
c89eec
+    struct rpmvs_s *vs = rpmvsCreate(vfylevel, flags, keyring);
c89eec
 
c89eec
     rc = rpmpkgRead(vs, fd, NULL, NULL, &msg;;
c89eec
 
c89eec
     if (rc)
c89eec
 	goto exit;
c89eec
 
c89eec
-    rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, vfyCb, &vd);
c89eec
-
c89eec
-    if (!vd.verbose) {
c89eec
-	if (vd.seen & RPMSIG_DIGEST_TYPE) {
c89eec
-	    rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_DIGEST_TYPE) ?
c89eec
-					_("DIGESTS") : _("digests"));
c89eec
-	}
c89eec
-	if (vd.seen & RPMSIG_SIGNATURE_TYPE) {
c89eec
-	    rpmlog(RPMLOG_NOTICE, " %s", (vd.bad & RPMSIG_SIGNATURE_TYPE) ?
c89eec
-					_("SIGNATURES") : _("signatures"));
c89eec
-	}
c89eec
-	rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK"));
c89eec
-    }
c89eec
+    rc = rpmvsVerify(vs, RPMSIG_VERIFIABLE_TYPE, cb, cbdata);
c89eec
 
c89eec
 exit:
c89eec
     if (rc && msg)
c89eec
@@ -260,15 +249,39 @@ static int rpmpkgVerifySigs(rpmKeyring keyring, int vfylevel, rpmVSFlags flags,
c89eec
     return rc;
c89eec
 }
c89eec
 
c89eec
+static void rpmkgVerifySigsPreLogging(struct vfydata_s *vd, const char *fn){
c89eec
+    rpmlog(RPMLOG_NOTICE, "%s:%s", fn, vd->verbose ? "\n" : "");
c89eec
+}
c89eec
+
c89eec
+static void rpmkgVerifySigsPostLogging(struct vfydata_s *vd, int rc){
c89eec
+    if (!vd->verbose) {
c89eec
+	if (vd->seen & RPMSIG_DIGEST_TYPE) {
c89eec
+	    rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_DIGEST_TYPE) ?
c89eec
+					_("DIGESTS") : _("digests"));
c89eec
+	}
c89eec
+	if (vd->seen & RPMSIG_SIGNATURE_TYPE) {
c89eec
+	    rpmlog(RPMLOG_NOTICE, " %s", (vd->bad & RPMSIG_SIGNATURE_TYPE) ?
c89eec
+					_("SIGNATURES") : _("signatures"));
c89eec
+	}
c89eec
+	rpmlog(RPMLOG_NOTICE, " %s\n", rc ? _("NOT OK") : _("OK"));
c89eec
+    }
c89eec
+}
c89eec
+
c89eec
 /* Wrapper around rpmkVerifySigs to preserve API */
c89eec
 int rpmVerifySignatures(QVA_t qva, rpmts ts, FD_t fd, const char * fn)
c89eec
 {
c89eec
     int rc = 1; /* assume failure */
c89eec
+    struct vfydata_s vd = { .seen = 0,
c89eec
+			    .bad = 0,
c89eec
+			    .verbose = rpmIsVerbose(),
c89eec
+    };
c89eec
     if (ts && qva && fd && fn) {
c89eec
 	rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
c89eec
 	rpmVSFlags vsflags = rpmtsVfyFlags(ts);
c89eec
 	int vfylevel = rpmtsVfyLevel(ts);
c89eec
-	rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, fn);
c89eec
+	rpmkgVerifySigsPreLogging(&vd, fn);
c89eec
+	rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, vfyCb, &vd);
c89eec
+	rpmkgVerifySigsPostLogging(&vd, rc);
c89eec
     	rpmKeyringFree(keyring);
c89eec
     }
c89eec
     return rc;
c89eec
@@ -290,12 +303,22 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
c89eec
 
c89eec
     while ((arg = *argv++) != NULL) {
c89eec
 	FD_t fd = Fopen(arg, "r.ufdio");
c89eec
+	struct vfydata_s vd = { .seen = 0,
c89eec
+				.bad = 0,
c89eec
+				.verbose = rpmIsVerbose(),
c89eec
+	};
c89eec
 	if (fd == NULL || Ferror(fd)) {
c89eec
 	    rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"), 
c89eec
 		     arg, Fstrerror(fd));
c89eec
 	    res++;
c89eec
-	} else if (rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd, arg)) {
c89eec
+	} else {
c89eec
+	    rpmkgVerifySigsPreLogging(&vd, arg);
c89eec
+	    int rc = rpmpkgVerifySigs(keyring, vfylevel, vsflags, fd,
c89eec
+				      vfyCb, &vd);
c89eec
+	    rpmkgVerifySigsPostLogging(&vd, rc);
c89eec
+	    if (rc) {
c89eec
 	    res++;
c89eec
+	    }
c89eec
 	}
c89eec
 
c89eec
 	Fclose(fd);
c89eec
@@ -304,3 +327,53 @@ int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv)
c89eec
     rpmKeyringFree(keyring);
c89eec
     return res;
c89eec
 }
c89eec
+
c89eec
+struct vfydatafd_s {
c89eec
+    size_t len;
c89eec
+    char msg[BUFSIZ];
c89eec
+};
c89eec
+
c89eec
+
c89eec
+static int vfyFDCb(struct rpmsinfo_s *sinfo, void *cbdata)
c89eec
+{
c89eec
+    struct vfydatafd_s *vd = cbdata;
c89eec
+    char *vmsg, *msg;
c89eec
+    size_t n;
c89eec
+    size_t remainder = BUFSIZ - vd->len >= 0 ? BUFSIZ - vd->len : 0;
c89eec
+
c89eec
+    vmsg = rpmsinfoMsg(sinfo);
c89eec
+    rasprintf(&msg, "    %s\n", vmsg);
c89eec
+    n = rstrlcpy(vd->msg + vd->len, msg, remainder);
c89eec
+    free(vmsg);
c89eec
+    free(msg);
c89eec
+    if(n <= remainder){
c89eec
+	vd->len += n;
c89eec
+    }
c89eec
+    return 1;
c89eec
+}
c89eec
+
c89eec
+
c89eec
+int rpmcliVerifySignaturesFD(rpmts ts, FD_t fdi, char **msg)
c89eec
+{
c89eec
+    rpmRC rc = RPMRC_FAIL;
c89eec
+    rpmKeyring keyring = rpmtsGetKeyring(ts, 1);
c89eec
+    rpmVSFlags vsflags = rpmtsVfyFlags(ts);
c89eec
+    int vfylevel = rpmtsVfyLevel(ts);
c89eec
+    struct vfydatafd_s vd = {.len = 0};
c89eec
+
c89eec
+    vsflags |= rpmcliVSFlags;
c89eec
+    if (rpmcliVfyLevelMask) {
c89eec
+	vfylevel &= ~rpmcliVfyLevelMask;
c89eec
+	rpmtsSetVfyLevel(ts, vfylevel);
c89eec
+    }
c89eec
+
c89eec
+    if (!rpmpkgVerifySigs(keyring, vfylevel, vsflags, fdi, vfyFDCb, &vd)) {
c89eec
+	rc = RPMRC_OK;
c89eec
+    }
c89eec
+    *msg = strdup(vd.msg);
c89eec
+    rpmsqPoll();
c89eec
+
c89eec
+    rpmKeyringFree(keyring);
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
diff --git a/lib/rpmcli.h b/lib/rpmcli.h
c89eec
index 906fe9951..7ff48b37a 100644
c89eec
--- a/lib/rpmcli.h
c89eec
+++ b/lib/rpmcli.h
c89eec
@@ -411,6 +411,16 @@ int rpmcliImportPubkeys(rpmts ts, ARGV_const_t argv);
c89eec
  */
c89eec
 int rpmcliVerifySignatures(rpmts ts, ARGV_const_t argv);
c89eec
 
c89eec
+
c89eec
+/** \ingroup rpmcli
c89eec
+ * Verify package signatures.
c89eec
+ * @param ts		transaction set
c89eec
+ * @param fd		a file descriptor to verify
c89eec
+ * @param msg		a string containing textual information about the verification, similar to rpmcliVerifySignatures output.
c89eec
+ * @return		0 on success
c89eec
+ */
c89eec
+int rpmcliVerifySignaturesFD(rpmts ts, FD_t fd, char **msg);
c89eec
+
c89eec
 #ifdef __cplusplus
c89eec
 }
c89eec
 #endif
c89eec
diff --git a/lib/rpmextents.c b/lib/rpmextents.c
c89eec
new file mode 100644
c89eec
index 000000000..59ba427a4
c89eec
--- /dev/null
c89eec
+++ b/lib/rpmextents.c
c89eec
@@ -0,0 +1,109 @@
c89eec
+
c89eec
+#include "system.h"
c89eec
+
c89eec
+#include <rpm/rpmlog.h>
c89eec
+#include <rpm/rpmio.h>
c89eec
+#include <string.h>
c89eec
+#include <errno.h>
c89eec
+
c89eec
+
c89eec
+#include "lib/rpmextents_internal.h"
c89eec
+
c89eec
+
c89eec
+int extentsVerifySigs(FD_t fd){
c89eec
+    rpm_loff_t current;
c89eec
+    int32_t rc;
c89eec
+    size_t len;
c89eec
+    uint64_t content_len;
c89eec
+    char *content = NULL;
c89eec
+    struct extents_footer_t footer;
c89eec
+
c89eec
+    current = Ftell(fd);
c89eec
+
c89eec
+    if(extentsFooterFromFD(fd, &footer) != RPMRC_OK) {
c89eec
+	rc = -1;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    if(Fseek(fd, footer.offsets.checksig_offset, SEEK_SET) < 0) {
c89eec
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to seek signature verification offset\n"));
c89eec
+	rc = -1;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    len = sizeof(rc);
c89eec
+    if (Fread(&rc, len, 1, fd) != len) {
c89eec
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read Signature Verification RC\n"));
c89eec
+	rc = -1;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+
c89eec
+    len = sizeof(content_len);
c89eec
+    if (Fread(&content_len, len, 1, fd) != len) {
c89eec
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content length\n"));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+
c89eec
+    content = rmalloc(content_len + 1);
c89eec
+    if(content == NULL) {
c89eec
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to allocate memory to read signature content\n"));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    content[content_len] = 0;
c89eec
+    if (Fread(content, content_len, 1, fd) != content_len) {
c89eec
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: Failed to read signature content\n"));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+
c89eec
+    rpmlog(RPMLOG_NOTICE, "%s", content);
c89eec
+exit:
c89eec
+    if(content){
c89eec
+	rfree(content);
c89eec
+    }
c89eec
+    if (Fseek(fd, current, SEEK_SET) < 0) {
c89eec
+	rpmlog(RPMLOG_ERR, _("extentsVerifySigs: unable to seek back to original location\n"));
c89eec
+    }
c89eec
+    return rc;
c89eec
+
c89eec
+}
c89eec
+
c89eec
+rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer) {
c89eec
+
c89eec
+    rpmRC rc = RPMRC_NOTFOUND;
c89eec
+    rpm_loff_t current;
c89eec
+    size_t len;
c89eec
+
c89eec
+    // If the file is not seekable, we cannot detect whether or not it is transcoded.
c89eec
+    if(Fseek(fd, 0, SEEK_CUR) < 0) {
c89eec
+        return RPMRC_FAIL;
c89eec
+    }
c89eec
+    current = Ftell(fd);
c89eec
+
c89eec
+    len = sizeof(struct extents_footer_t);
c89eec
+    if(Fseek(fd, -len, SEEK_END) < 0) {
c89eec
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: failed to seek for footer: %s\n"), strerror(errno));
c89eec
+	rc = RPMRC_FAIL;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    if (Fread(footer, len, 1, fd) != len) {
c89eec
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to read footer\n"));
c89eec
+	rc = RPMRC_FAIL;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    if (footer->magic != EXTENTS_MAGIC) {
c89eec
+	rc = RPMRC_NOTFOUND;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    rc = RPMRC_OK;
c89eec
+exit:
c89eec
+    if (Fseek(fd, current, SEEK_SET) < 0) {
c89eec
+	rpmlog(RPMLOG_ERR, _("isTranscodedRpm: unable to seek back to original location\n"));
c89eec
+	rc = RPMRC_FAIL;
c89eec
+    }
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
+rpmRC isTranscodedRpm(FD_t fd) {
c89eec
+    struct extents_footer_t footer;
c89eec
+    return extentsFooterFromFD(fd, &footer);
c89eec
+}
c89eec
+
c89eec
+
c89eec
diff --git a/lib/rpmextents_internal.h b/lib/rpmextents_internal.h
c89eec
new file mode 100644
c89eec
index 000000000..380c08425
c89eec
--- /dev/null
c89eec
+++ b/lib/rpmextents_internal.h
c89eec
@@ -0,0 +1,57 @@
c89eec
+#ifndef _RPMEXTENTS_INTERNAL_H
c89eec
+#define _RPMEXTENTS_INTERNAL_H
c89eec
+
c89eec
+#ifdef __cplusplus
c89eec
+extern "C" {
c89eec
+#endif
c89eec
+
c89eec
+#include <stdint.h>
c89eec
+
c89eec
+/** \ingroup rpmextents
c89eec
+ * RPM extents library
c89eec
+ */
c89eec
+
c89eec
+/* magic value at end of file (64 bits) that indicates this is a transcoded
c89eec
+ * rpm.
c89eec
+ */
c89eec
+#define EXTENTS_MAGIC 3472329499408095051
c89eec
+
c89eec
+typedef uint64_t extents_magic_t;
c89eec
+
c89eec
+struct __attribute__ ((__packed__)) extents_footer_offsets_t {
c89eec
+    off64_t checksig_offset;
c89eec
+    off64_t table_offset;
c89eec
+    off64_t csum_offset;
c89eec
+};
c89eec
+
c89eec
+struct __attribute__ ((__packed__)) extents_footer_t {
c89eec
+    struct extents_footer_offsets_t offsets;
c89eec
+    extents_magic_t magic;
c89eec
+};
c89eec
+
c89eec
+/** \ingroup rpmextents
c89eec
+ * Checks the results of the signature verification ran during transcoding.
c89eec
+ * @param fd	The FD_t of the transcoded RPM
c89eec
+ * @return	The number of checks that `rpmvsVerify` failed during transcoding.
c89eec
+ */
c89eec
+int extentsVerifySigs(FD_t fd);
c89eec
+
c89eec
+/** \ingroup rpmextents
c89eec
+ * Read the RPM Extents footer from a file descriptor.
c89eec
+ * @param fd		The FD_t of the transcoded RPM
c89eec
+ * @param[out] footer	A pointer to an allocated extents_footer_t with a copy of the footer.
c89eec
+ * @return		RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure.
c89eec
+ */
c89eec
+rpmRC extentsFooterFromFD(FD_t fd, struct extents_footer_t *footer);
c89eec
+
c89eec
+/** \ingroup rpmextents
c89eec
+ * Check if a RPM is a transcoded RPM
c89eec
+ * @param fd	The FD_t of the transcoded RPM
c89eec
+ * return	RPMRC_OK on success, RPMRC_NOTFOUND if not a transcoded file, RPMRC_FAIL on any failure.
c89eec
+ */
c89eec
+rpmRC isTranscodedRpm(FD_t fd);
c89eec
+
c89eec
+#ifdef __cplusplus
c89eec
+}
c89eec
+#endif
c89eec
+#endif /* _RPMEXTENTS_INTERNAL_H */
c89eec
diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h
c89eec
index fd81aec8d..6dbbcff35 100644
c89eec
--- a/lib/rpmplugin.h
c89eec
+++ b/lib/rpmplugin.h
c89eec
@@ -60,6 +60,13 @@ typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi,
c89eec
 					      const char* path,
c89eec
 					      const char *dest,
c89eec
 					      mode_t file_mode, rpmFsmOp op);
c89eec
+typedef rpmRC (*plugin_fsm_file_install_func)(rpmPlugin plugin, rpmfi fi,
c89eec
+					      const char* path,
c89eec
+					      mode_t file_mode, rpmFsmOp op);
c89eec
+typedef rpmRC (*plugin_fsm_file_archive_reader_func)(rpmPlugin plugin,
c89eec
+						     FD_t payload,
c89eec
+						     rpmfiles files, rpmfi *fi);
c89eec
+
c89eec
 
c89eec
 typedef struct rpmPluginHooks_s * rpmPluginHooks;
c89eec
 struct rpmPluginHooks_s {
c89eec
@@ -80,6 +87,8 @@ struct rpmPluginHooks_s {
c89eec
     plugin_fsm_file_pre_func		fsm_file_pre;
c89eec
     plugin_fsm_file_post_func		fsm_file_post;
c89eec
     plugin_fsm_file_prepare_func	fsm_file_prepare;
c89eec
+    plugin_fsm_file_install_func	fsm_file_install;
c89eec
+    plugin_fsm_file_archive_reader_func	fsm_file_archive_reader;
c89eec
 };
c89eec
 
c89eec
 #ifdef __cplusplus
c89eec
diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
c89eec
index 3da3097af..901af1ac5 100644
c89eec
--- a/lib/rpmplugins.c
c89eec
+++ b/lib/rpmplugins.c
c89eec
@@ -421,3 +421,74 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
c89eec
 
c89eec
     return rc;
c89eec
 }
c89eec
+
c89eec
+rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi,
c89eec
+				   const char *path, mode_t file_mode,
c89eec
+				   rpmFsmOp op)
c89eec
+{
c89eec
+    plugin_fsm_file_install_func hookFunc;
c89eec
+    int i;
c89eec
+    rpmRC rc = RPMRC_OK;
c89eec
+    rpmRC hook_rc;
c89eec
+
c89eec
+    for (i = 0; i < plugins->count; i++) {
c89eec
+	rpmPlugin plugin = plugins->plugins[i];
c89eec
+	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_install);
c89eec
+	if (hookFunc) {
c89eec
+	    hook_rc = hookFunc(plugin, fi, path, file_mode, op);
c89eec
+	    if (hook_rc == RPMRC_FAIL) {
c89eec
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_install failed\n", plugin->name);
c89eec
+		rc = RPMRC_FAIL;
c89eec
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
c89eec
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
c89eec
+		    /* Another plugin already said it'd handle contents. It's
c89eec
+		     * undefined how these would combine, so treat this as a
c89eec
+		     * failure condition.
c89eec
+		    */
c89eec
+		    rc = RPMRC_FAIL;
c89eec
+		} else {
c89eec
+		    /* Plugin will handle content */
c89eec
+		    rc = RPMRC_PLUGIN_CONTENTS;
c89eec
+		}
c89eec
+	    }
c89eec
+	}
c89eec
+    }
c89eec
+
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
+rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload,
c89eec
+				   rpmfiles files, rpmfi *fi)
c89eec
+{
c89eec
+    plugin_fsm_file_archive_reader_func hookFunc;
c89eec
+    int i;
c89eec
+    rpmRC rc = RPMRC_OK;
c89eec
+    rpmRC hook_rc;
c89eec
+
c89eec
+    for (i = 0; i < plugins->count; i++) {
c89eec
+	rpmPlugin plugin = plugins->plugins[i];
c89eec
+	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_archive_reader);
c89eec
+	if (hookFunc) {
c89eec
+	    hook_rc = hookFunc(plugin, payload, files, fi);
c89eec
+	    if (hook_rc == RPMRC_FAIL) {
c89eec
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_archive_reader failed\n", plugin->name);
c89eec
+		rc = RPMRC_FAIL;
c89eec
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
c89eec
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
c89eec
+		    /* Another plugin already said it'd handle contents. It's
c89eec
+		     * undefined how these would combine, so treat this as a
c89eec
+		     * failure condition.
c89eec
+		    */
c89eec
+		    rc = RPMRC_FAIL;
c89eec
+		} else {
c89eec
+		    /* Plugin will handle content */
c89eec
+		    rc = RPMRC_PLUGIN_CONTENTS;
c89eec
+		}
c89eec
+	    }
c89eec
+	}
c89eec
+    }
c89eec
+
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
+
c89eec
diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h
c89eec
index 39762c376..88807c53c 100644
c89eec
--- a/lib/rpmplugins.h
c89eec
+++ b/lib/rpmplugins.h
c89eec
@@ -167,6 +167,23 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
c89eec
                                    const char *path, const char *dest,
c89eec
                                    mode_t mode, rpmFsmOp op);
c89eec
 
c89eec
+/** \ingroup rpmplugins
c89eec
+ * Call the fsm file install plugin hook
c89eec
+ * @param plugins	plugins structure
c89eec
+ * @param fi		file info iterator (or NULL)
c89eec
+ * @param path		file object path
c89eec
+ * @param file_mode	file object mode
c89eec
+ * @param op		file operation + associated flags
c89eec
+ * @return		RPMRC_OK on success, RPMRC_FAIL otherwise
c89eec
+ */
c89eec
+RPM_GNUC_INTERNAL
c89eec
+rpmRC rpmpluginsCallFsmFileInstall(rpmPlugins plugins, rpmfi fi,
c89eec
+				   const char* path, mode_t file_mode,
c89eec
+				   rpmFsmOp op);
c89eec
+
c89eec
+RPM_GNUC_INTERNAL
c89eec
+rpmRC rpmpluginsCallFsmFileArchiveReader(rpmPlugins plugins, FD_t payload,
c89eec
+					 rpmfiles files, rpmfi *fi);
c89eec
 #ifdef __cplusplus
c89eec
 }
c89eec
 #endif
c89eec
diff --git a/plugins/reflink.c b/plugins/reflink.c
c89eec
index 513887604..4fc1d74d1 100644
c89eec
--- a/plugins/reflink.c
c89eec
+++ b/plugins/reflink.c
c89eec
@@ -13,6 +13,7 @@
c89eec
 #include <rpm/rpmlog.h>
c89eec
 #include "lib/rpmlib.h"
c89eec
 #include "lib/rpmplugin.h"
c89eec
+#include "lib/rpmextents_internal.h"
c89eec
 #include "lib/rpmte_internal.h"
c89eec
 #include <rpm/rpmfileutil.h>
c89eec
 #include "rpmio/rpmio_internal.h"
c89eec
@@ -28,7 +29,7 @@
c89eec
 #undef HTDATATYPE
c89eec
 #define HASHTYPE inodeIndexHash
c89eec
 #define HTKEYTYPE rpm_ino_t
c89eec
-#define HTDATATYPE int
c89eec
+#define HTDATATYPE const char *
c89eec
 #include "lib/rpmhash.H"
c89eec
 #include "lib/rpmhash.C"
c89eec
 
c89eec
@@ -40,11 +41,6 @@
c89eec
 
c89eec
 #define BUFFER_SIZE (1024 * 128)
c89eec
 
c89eec
-/* magic value at end of file (64 bits) that indicates this is a transcoded
c89eec
- * rpm.
c89eec
- */
c89eec
-#define MAGIC 3472329499408095051
c89eec
-
c89eec
 struct reflink_state_s {
c89eec
     /* Stuff that's used across rpms */
c89eec
     long fundamental_block_size;
c89eec
@@ -58,6 +54,7 @@ struct reflink_state_s {
c89eec
     FD_t fd;
c89eec
     rpmfiles files;
c89eec
     inodeIndexHash inodeIndexes;
c89eec
+    int transcoded;
c89eec
 };
c89eec
 
c89eec
 typedef struct reflink_state_s * reflink_state;
c89eec
@@ -96,51 +93,35 @@ static void reflink_cleanup(rpmPlugin plugin) {
c89eec
 }
c89eec
 
c89eec
 static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) {
c89eec
+    rpmRC rc;
c89eec
+    size_t len;
c89eec
+
c89eec
     reflink_state state = rpmPluginGetData(plugin);
c89eec
     state->fd = rpmteFd(te);
c89eec
     if (state->fd == 0) {
c89eec
 	rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n"));
c89eec
 	return RPMRC_OK;
c89eec
     }
c89eec
+
c89eec
     rpm_loff_t current = Ftell(state->fd);
c89eec
-    uint64_t magic;
c89eec
-    if (Fseek(state->fd, -(sizeof(magic)), SEEK_END) < 0) {
c89eec
-	rpmlog(RPMLOG_ERR, _("reflink: failed to seek for magic\n"));
c89eec
-	if (Fseek(state->fd, current, SEEK_SET) < 0) {
c89eec
-	    /* yes this gets a bit repetitive */
c89eec
-	    rpmlog(RPMLOG_ERR,
c89eec
-		 _("reflink: unable to seek back to original location\n"));
c89eec
-	}
c89eec
-	return RPMRC_FAIL;
c89eec
-    }
c89eec
-    size_t len = sizeof(magic);
c89eec
-    if (Fread(&magic, len, 1, state->fd) != len) {
c89eec
-	rpmlog(RPMLOG_ERR, _("reflink: unable to read magic\n"));
c89eec
-	if (Fseek(state->fd, current, SEEK_SET) < 0) {
c89eec
-	    rpmlog(RPMLOG_ERR,
c89eec
-		   _("reflink: unable to seek back to original location\n"));
c89eec
-	}
c89eec
-	return RPMRC_FAIL;
c89eec
-    }
c89eec
-    if (magic != MAGIC) {
c89eec
-	rpmlog(RPMLOG_DEBUG, _("reflink: not transcoded\n"));
c89eec
-	if (Fseek(state->fd, current, SEEK_SET) < 0) {
c89eec
-	    rpmlog(RPMLOG_ERR,
c89eec
-		   _("reflink: unable to seek back to original location\n"));
c89eec
+    rc = isTranscodedRpm(state->fd);
c89eec
+
c89eec
+    switch(rc){
c89eec
+	// Fail to parse the file, fail the plugin.
c89eec
+	case RPMRC_FAIL:
c89eec
 	    return RPMRC_FAIL;
c89eec
-	}
c89eec
-	return RPMRC_OK;
c89eec
+	// This is not a transcoded file, do nothing.
c89eec
+	case RPMRC_NOTFOUND:
c89eec
+	    return RPMRC_OK;
c89eec
+	default:
c89eec
+	    break;
c89eec
     }
c89eec
     rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n"));
c89eec
-    Header h = rpmteHeader(te);
c89eec
+    state->transcoded = 1;
c89eec
 
c89eec
-    /* replace/add header that main fsm.c can read */
c89eec
-    headerDel(h, RPMTAG_PAYLOADFORMAT);
c89eec
-    headerPutString(h, RPMTAG_PAYLOADFORMAT, "clon");
c89eec
-    headerFree(h);
c89eec
     state->files = rpmteFiles(te);
c89eec
     /* tail of file contains offset_table, offset_checksums then magic */
c89eec
-    if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(magic)), SEEK_END) < 0) {
c89eec
+    if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(extents_magic_t)), SEEK_END) < 0) {
c89eec
 	rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"),
c89eec
 	       state->fd);
c89eec
 	return RPMRC_FAIL;
c89eec
@@ -182,7 +163,7 @@ static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) {
c89eec
 	    return RPMRC_FAIL;
c89eec
 	}
c89eec
 	state->inodeIndexes = inodeIndexHashCreate(
c89eec
-	    state->keys, inodeId, inodeCmp, NULL, NULL
c89eec
+	    state->keys, inodeId, inodeCmp, NULL, (inodeIndexHashFreeData)rfree
c89eec
 	);
c89eec
     }
c89eec
 
c89eec
@@ -239,13 +220,13 @@ rpm_loff_t find(const unsigned char *digest, reflink_state state) {
c89eec
     return offset;
c89eec
 }
c89eec
 
c89eec
-static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path,
c89eec
+static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* path,
c89eec
                                   mode_t file_mode, rpmFsmOp op)
c89eec
 {
c89eec
     struct file_clone_range fcr;
c89eec
     rpm_loff_t size;
c89eec
     int dst, rc;
c89eec
-    int *hlix;
c89eec
+    const char **hl_target = NULL;
c89eec
 
c89eec
     reflink_state state = rpmPluginGetData(plugin);
c89eec
     if (state->table == NULL) {
c89eec
@@ -262,18 +243,15 @@ static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path,
c89eec
 	/* check for hard link entry in table. GetEntry overwrites hlix with
c89eec
 	 * the address of the first match.
c89eec
 	 */
c89eec
-	if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hlix, NULL,
c89eec
-	                           NULL)) {
c89eec
+	if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hl_target,
c89eec
+				   NULL, NULL)) {
c89eec
 	    /* entry is in table, use hard link */
c89eec
-	    char *fn = rpmfilesFN(state->files, hlix[0]);
c89eec
-	    if (link(fn, path) != 0) {
c89eec
+	    if (link(hl_target[0], path) != 0) {
c89eec
 		rpmlog(RPMLOG_ERR,
c89eec
 		       _("reflink: Unable to hard link %s -> %s due to %s\n"),
c89eec
-		       fn, path, strerror(errno));
c89eec
-		free(fn);
c89eec
+		       hl_target[0], path, strerror(errno));
c89eec
 		return RPMRC_FAIL;
c89eec
 	    }
c89eec
-	    free(fn);
c89eec
 	    return RPMRC_PLUGIN_CONTENTS;
c89eec
 	}
c89eec
 	/* if we didn't hard link, then we'll track this inode as being
c89eec
@@ -281,7 +259,7 @@ static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path,
c89eec
 	 */
c89eec
 	if (rpmfiFNlink(fi) > 1) {
c89eec
 	    /* minor optimization: only store files with more than one link */
c89eec
-	    inodeIndexHashAddEntry(state->inodeIndexes, inode, rpmfiFX(fi));
c89eec
+	    inodeIndexHashAddEntry(state->inodeIndexes, inode, rstrdup(path));
c89eec
 	}
c89eec
 	/* derived from wfd_open in fsm.c */
c89eec
 	mode_t old_umask = umask(0577);
c89eec
@@ -366,10 +344,21 @@ static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path,
c89eec
     return RPMRC_OK;
c89eec
 }
c89eec
 
c89eec
+static rpmRC reflink_fsm_file_archive_reader(rpmPlugin plugin, FD_t payload,
c89eec
+					     rpmfiles files, rpmfi *fi) {
c89eec
+    reflink_state state = rpmPluginGetData(plugin);
c89eec
+    if(state->transcoded) {
c89eec
+	*fi = rpmfilesIter(files, RPMFI_ITER_FWD);
c89eec
+	return RPMRC_PLUGIN_CONTENTS;
c89eec
+    }
c89eec
+    return RPMRC_OK;
c89eec
+}
c89eec
+
c89eec
 struct rpmPluginHooks_s reflink_hooks = {
c89eec
     .init = reflink_init,
c89eec
     .cleanup = reflink_cleanup,
c89eec
     .psm_pre = reflink_psm_pre,
c89eec
     .psm_post = reflink_psm_post,
c89eec
-    .fsm_file_pre = reflink_fsm_file_pre,
c89eec
+    .fsm_file_install = reflink_fsm_file_install,
c89eec
+    .fsm_file_archive_reader = reflink_fsm_file_archive_reader,
c89eec
 };
c89eec
diff --git a/rpm2extents.c b/rpm2extents.c
c89eec
index c111be0a2..7dd5128de 100644
c89eec
--- a/rpm2extents.c
c89eec
+++ b/rpm2extents.c
c89eec
@@ -2,7 +2,9 @@
c89eec
 
c89eec
 #include "system.h"
c89eec
 
c89eec
+#include <rpm/rpmcli.h>
c89eec
 #include <rpm/rpmlib.h>		/* rpmReadPackageFile .. */
c89eec
+#include <rpm/rpmlog.h>
c89eec
 #include <rpm/rpmfi.h>
c89eec
 #include <rpm/rpmtag.h>
c89eec
 #include <rpm/rpmio.h>
c89eec
@@ -10,8 +12,10 @@
c89eec
 
c89eec
 #include <rpm/rpmts.h>
c89eec
 #include "lib/rpmlead.h"
c89eec
+#include "lib/rpmts.h"
c89eec
 #include "lib/signature.h"
c89eec
 #include "lib/header_internal.h"
c89eec
+#include "lib/rpmextents_internal.h"
c89eec
 #include "rpmio/rpmio_internal.h"
c89eec
 
c89eec
 #include <unistd.h>
c89eec
@@ -34,11 +38,6 @@
c89eec
 #include "lib/rpmhash.H"
c89eec
 #include "lib/rpmhash.C"
c89eec
 
c89eec
-/* magic value at end of file (64 bits) that indicates this is a transcoded
c89eec
- * rpm.
c89eec
- */
c89eec
-#define MAGIC 3472329499408095051
c89eec
-
c89eec
 struct digestoffset {
c89eec
     const unsigned char * digest;
c89eec
     rpm_loff_t pos;
c89eec
@@ -51,38 +50,49 @@ rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit)
c89eec
     return (unit - (pos % unit)) % unit;
c89eec
 }
c89eec
 
c89eec
-static int digestor(
c89eec
+static struct poptOption optionsTable[] = {
c89eec
+    { NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0,
c89eec
+    N_("Common options for all rpm modes and executables:"), NULL },
c89eec
+
c89eec
+    POPT_AUTOALIAS
c89eec
+    POPT_AUTOHELP
c89eec
+    POPT_TABLEEND
c89eec
+};
c89eec
+
c89eec
+
c89eec
+static void FDDigestInit(FD_t fdi, uint8_t algos[], uint32_t algos_len){
c89eec
+    int algo;
c89eec
+
c89eec
+    for (algo = 0; algo < algos_len; algo++) {
c89eec
+	fdInitDigest(fdi, algos[algo], 0);
c89eec
+    }
c89eec
+}
c89eec
+
c89eec
+static int FDWriteDigests(
c89eec
     FD_t fdi,
c89eec
     FD_t fdo,
c89eec
-    FD_t validationo,
c89eec
     uint8_t algos[],
c89eec
-    uint32_t algos_len
c89eec
-)
c89eec
+    uint32_t algos_len)
c89eec
 {
c89eec
-    ssize_t fdilength;
c89eec
     const char *filedigest, *algo_name;
c89eec
     size_t filedigest_len, len;
c89eec
     uint32_t algo_name_len, algo_digest_len;
c89eec
     int algo;
c89eec
     rpmRC rc = RPMRC_FAIL;
c89eec
 
c89eec
-    for (algo = 0; algo < algos_len; algo++) {
c89eec
-	fdInitDigest(fdi, algos[algo], 0);
c89eec
-    }
c89eec
-    fdilength = ufdCopy(fdi, fdo);
c89eec
-    if (fdilength == -1) {
c89eec
-	fprintf(stderr, _("digest cat failed\n"));
c89eec
-	goto exit;
c89eec
-    }
c89eec
+    ssize_t fdilength = fdOp(fdi, FDSTAT_READ)->bytes;
c89eec
 
c89eec
     len = sizeof(fdilength);
c89eec
-    if (Fwrite(&fdilength, len, 1, validationo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write input length %zd\n"), fdilength);
c89eec
+    if (Fwrite(&fdilength, len, 1, fdo) != len) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write input length %zd: %d, %s\n"),
c89eec
+	       fdilength, errno, strerror(errno));
c89eec
 	goto exit;
c89eec
     }
c89eec
     len = sizeof(algos_len);
c89eec
-    if (Fwrite(&algos_len, len, 1, validationo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write number of validation digests\n"));
c89eec
+    if (Fwrite(&algos_len, len, 1, fdo) != len) {
c89eec
+	algo_digest_len = (uint32_t)filedigest_len;
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write number of digests: %d, %s\n"),
c89eec
+	       errno, strerror(errno));
c89eec
 	goto exit;
c89eec
     }
c89eec
     for (algo = 0; algo < algos_len; algo++) {
c89eec
@@ -93,25 +103,29 @@ static int digestor(
c89eec
 	algo_digest_len = (uint32_t)filedigest_len;
c89eec
 
c89eec
 	len = sizeof(algo_name_len);
c89eec
-	if (Fwrite(&algo_name_len, len, 1, validationo) != len) {
c89eec
-	    fprintf(stderr,
c89eec
-		    _("Unable to write validation algo name length\n"));
c89eec
+	if (Fwrite(&algo_name_len, len, 1, fdo) != len) {
c89eec
+	    rpmlog(RPMLOG_ERR,
c89eec
+		   _("Unable to write digest algo name length: %d, %s\n"),
c89eec
+		   errno, strerror(errno));
c89eec
 	    goto exit;
c89eec
 	}
c89eec
 	len = sizeof(algo_digest_len);
c89eec
-	if (Fwrite(&algo_digest_len, len, 1, validationo) != len) {
c89eec
-	    fprintf(stderr,
c89eec
-		    _("Unable to write number of bytes for validation digest\n"));
c89eec
+	if (Fwrite(&algo_digest_len, len, 1, fdo) != len) {
c89eec
+	    rpmlog(RPMLOG_ERR,
c89eec
+		   _("Unable to write number of bytes for digest: %d, %s\n"),
c89eec
+		   errno, strerror(errno));
c89eec
 	     goto exit;
c89eec
 	}
c89eec
-	if (Fwrite(algo_name, algo_name_len, 1, validationo) != algo_name_len) {
c89eec
-	    fprintf(stderr, _("Unable to write validation algo name\n"));
c89eec
+	if (Fwrite(algo_name, algo_name_len, 1, fdo) != algo_name_len) {
c89eec
+	    rpmlog(RPMLOG_ERR, _("Unable to write digest algo name: %d, %s\n"),
c89eec
+		   errno, strerror(errno));
c89eec
 	    goto exit;
c89eec
 	}
c89eec
-	if (Fwrite(filedigest, algo_digest_len, 1, validationo ) != algo_digest_len) {
c89eec
-	    fprintf(stderr,
c89eec
-		    _("Unable to write validation digest value %u, %zu\n"),
c89eec
-		    algo_digest_len, filedigest_len);
c89eec
+	if (Fwrite(filedigest, algo_digest_len, 1, fdo ) != algo_digest_len) {
c89eec
+	    rpmlog(RPMLOG_ERR,
c89eec
+		   _("Unable to write digest value %u, %zu: %d, %s\n"),
c89eec
+		   algo_digest_len, filedigest_len,
c89eec
+		   errno, strerror(errno));
c89eec
 	    goto exit;
c89eec
 	}
c89eec
     }
c89eec
@@ -120,7 +134,80 @@ static int digestor(
c89eec
     return rc;
c89eec
 }
c89eec
 
c89eec
-static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
+static rpmRC FDWriteSignaturesValidation(FD_t fdo, int rpmvsrc, char *msg) {
c89eec
+    size_t len;
c89eec
+    rpmRC rc = RPMRC_FAIL;
c89eec
+
c89eec
+    if(rpmvsrc){
c89eec
+	rpmlog(RPMLOG_WARNING,
c89eec
+	       _("Error verifying package signatures:\n%s\n"), msg);
c89eec
+    }
c89eec
+
c89eec
+    len = sizeof(rpmvsrc);
c89eec
+    if (Fwrite(&rpmvsrc, len, 1, fdo) != len) {
c89eec
+	rpmlog(RPMLOG_ERR,
c89eec
+	       _("Unable to write signature verification RC code %d: %d, %s\n"),
c89eec
+	       rpmvsrc, errno, strerror(errno));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    size_t content_len = msg ? strlen(msg) : 0;
c89eec
+    len = sizeof(content_len);
c89eec
+    if (Fwrite(&content_len, len, 1, fdo) != len) {
c89eec
+	rpmlog(RPMLOG_ERR,
c89eec
+	       _("Unable to write signature verification output length %zd: %d, %s\n"),
c89eec
+	       content_len, errno, strerror(errno));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    if (Fwrite(msg, content_len, 1, fdo) != content_len) {
c89eec
+	rpmlog(RPMLOG_ERR,
c89eec
+	       _("Unable to write signature verification output %s: %d, %s\n"),
c89eec
+	       msg, errno, strerror(errno));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+
c89eec
+    rc = RPMRC_OK;
c89eec
+exit:
c89eec
+
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
+static rpmRC validator(FD_t fdi, FD_t digesto, FD_t sigo,
c89eec
+	uint8_t algos[],
c89eec
+	uint32_t algos_len){
c89eec
+    int rpmvsrc;
c89eec
+    rpmRC rc = RPMRC_FAIL;
c89eec
+    char *msg = NULL;
c89eec
+    rpmts ts = rpmtsCreate();
c89eec
+
c89eec
+    rpmtsSetRootDir(ts, rpmcliRootDir);
c89eec
+
c89eec
+    FDDigestInit(fdi, algos, algos_len);
c89eec
+
c89eec
+    rpmvsrc = rpmcliVerifySignaturesFD(ts, fdi, &msg;;
c89eec
+
c89eec
+    // Write result of digest computation
c89eec
+    if(FDWriteDigests(fdi, digesto, algos, algos_len) != RPMRC_OK) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Failed to write digests: %d, %s\n"),
c89eec
+	       errno, strerror(errno));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+
c89eec
+    // Write result of signature validation.
c89eec
+    if(FDWriteSignaturesValidation(sigo, rpmvsrc, msg)) {
c89eec
+	rpmlog(RPMLOG_ERR,
c89eec
+	       _("Failed to write signature verification result: %d, %s\n"),
c89eec
+	       errno, strerror(errno));
c89eec
+	goto exit;
c89eec
+    }
c89eec
+    rc = RPMRC_OK;
c89eec
+exit:
c89eec
+    if(msg) {
c89eec
+	free(msg);
c89eec
+    }
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
+static rpmRC process_package(FD_t fdi, FD_t digestori, FD_t validationi)
c89eec
 {
c89eec
     uint32_t diglen;
c89eec
     /* GNU C extension: can use diglen from outer context */
c89eec
@@ -148,7 +235,7 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
     rpm_mode_t mode;
c89eec
     char *rpmio_flags = NULL, *zeros;
c89eec
     const unsigned char *digest;
c89eec
-    rpm_loff_t pos, size, pad, validation_pos;
c89eec
+    rpm_loff_t pos, size, pad, digest_pos, validation_pos, digest_table_pos;
c89eec
     uint32_t offset_ix = 0;
c89eec
     size_t len;
c89eec
     int next = 0;
c89eec
@@ -156,24 +243,24 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
     fdo = fdDup(STDOUT_FILENO);
c89eec
 
c89eec
     if (rpmReadPackageRaw(fdi, &sigh, &h)) {
c89eec
-	fprintf(stderr, _("Error reading package\n"));
c89eec
+	rpmlog(RPMLOG_ERR, _("Error reading package\n"));
c89eec
 	exit(EXIT_FAILURE);
c89eec
     }
c89eec
 
c89eec
     if (rpmLeadWrite(fdo, h))
c89eec
     {
c89eec
-	fprintf(stderr, _("Unable to write package lead: %s\n"),
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write package lead: %s\n"),
c89eec
 		Fstrerror(fdo));
c89eec
 	exit(EXIT_FAILURE);
c89eec
     }
c89eec
 
c89eec
     if (rpmWriteSignature(fdo, sigh)) {
c89eec
-	fprintf(stderr, _("Unable to write signature: %s\n"), Fstrerror(fdo));
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write signature: %s\n"), Fstrerror(fdo));
c89eec
 	exit(EXIT_FAILURE);
c89eec
     }
c89eec
 
c89eec
     if (headerWrite(fdo, h, HEADER_MAGIC_YES)) {
c89eec
-	fprintf(stderr, _("Unable to write headers: %s\n"), Fstrerror(fdo));
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write headers: %s\n"), Fstrerror(fdo));
c89eec
 	exit(EXIT_FAILURE);
c89eec
     }
c89eec
 
c89eec
@@ -187,7 +274,7 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
     free(rpmio_flags);
c89eec
 
c89eec
     if (gzdi == NULL) {
c89eec
-	fprintf(stderr, _("cannot re-open payload: %s\n"), Fstrerror(gzdi));
c89eec
+	rpmlog(RPMLOG_ERR, _("cannot re-open payload: %s\n"), Fstrerror(gzdi));
c89eec
 	exit(EXIT_FAILURE);
c89eec
     }
c89eec
 
c89eec
@@ -230,7 +317,7 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
 	}
c89eec
 	pad = pad_to(pos, fundamental_block_size);
c89eec
 	if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
c89eec
-	    fprintf(stderr, _("Unable to write padding\n"));
c89eec
+	    rpmlog(RPMLOG_ERR, _("Unable to write padding\n"));
c89eec
 	    rc = RPMRC_FAIL;
c89eec
 	    goto exit;
c89eec
 	}
c89eec
@@ -243,7 +330,12 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
 	size = rpmfiFSize(fi);
c89eec
 	rc = rpmfiArchiveReadToFile(fi, fdo, 0);
c89eec
 	if (rc != RPMRC_OK) {
c89eec
-	    fprintf(stderr, _("rpmfiArchiveReadToFile failed with %d\n"), rc);
c89eec
+	    char *errstr = rpmfileStrerror(rc);
c89eec
+	    rpmlog(RPMLOG_ERR,
c89eec
+		   _("rpmfiArchiveReadToFile failed while extracting "\
c89eec
+		   "\"%s\" with RC %d: %s\n"),
c89eec
+		   rpmfiFN(fi), rc, errstr);
c89eec
+	    free(errstr);
c89eec
 	    goto exit;
c89eec
 	}
c89eec
 	pos += size;
c89eec
@@ -253,42 +345,53 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
     qsort(offsets, (size_t)offset_ix, sizeof(struct digestoffset),
c89eec
 	  digestoffsetCmp);
c89eec
 
c89eec
+    validation_pos = pos;
c89eec
+    ssize_t validation_len = ufdCopy(validationi, fdo);
c89eec
+    if (validation_len == -1) {
c89eec
+	rpmlog(RPMLOG_ERR, _("validation output ufdCopy failed\n"));
c89eec
+	rc = RPMRC_FAIL;
c89eec
+	goto exit;
c89eec
+    }
c89eec
+
c89eec
+    digest_table_pos = validation_pos + validation_len;
c89eec
+
c89eec
     len = sizeof(offset_ix);
c89eec
     if (Fwrite(&offset_ix, len, 1, fdo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write length of table\n"));
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write length of table\n"));
c89eec
 	rc = RPMRC_FAIL;
c89eec
 	goto exit;
c89eec
     }
c89eec
     len = sizeof(diglen);
c89eec
     if (Fwrite(&diglen, len, 1, fdo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write length of digest\n"));
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write length of digest\n"));
c89eec
 	rc = RPMRC_FAIL;
c89eec
 	goto exit;
c89eec
     }
c89eec
     len = sizeof(rpm_loff_t);
c89eec
     for (int x = 0; x < offset_ix; x++) {
c89eec
 	if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) {
c89eec
-	    fprintf(stderr, _("Unable to write digest\n"));
c89eec
+	    rpmlog(RPMLOG_ERR, _("Unable to write digest\n"));
c89eec
 	    rc = RPMRC_FAIL;
c89eec
 	    goto exit;
c89eec
 	}
c89eec
 	if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) {
c89eec
-	    fprintf(stderr, _("Unable to write offset\n"));
c89eec
+	    rpmlog(RPMLOG_ERR, _("Unable to write offset\n"));
c89eec
 	    rc = RPMRC_FAIL;
c89eec
 	    goto exit;
c89eec
 	}
c89eec
     }
c89eec
-    validation_pos = (
c89eec
-	pos + sizeof(offset_ix) + sizeof(diglen) +
c89eec
+    digest_pos = (
c89eec
+	digest_table_pos + sizeof(offset_ix) + sizeof(diglen) +
c89eec
 	offset_ix * (diglen + sizeof(rpm_loff_t))
c89eec
     );
c89eec
 
c89eec
-    ssize_t validation_len = ufdCopy(validationi, fdo);
c89eec
-    if (validation_len == -1) {
c89eec
-	fprintf(stderr, _("digest table ufdCopy failed\n"));
c89eec
+    ssize_t digest_len = ufdCopy(digestori, fdo);
c89eec
+    if (digest_len == -1) {
c89eec
+	rpmlog(RPMLOG_ERR, _("digest table ufdCopy failed\n"));
c89eec
 	rc = RPMRC_FAIL;
c89eec
 	goto exit;
c89eec
     }
c89eec
+
c89eec
     /* add more padding so the last file can be cloned. It doesn't matter that
c89eec
      * the table and validation etc are in this space. In fact, it's pretty
c89eec
      * efficient if it is.
c89eec
@@ -297,25 +400,15 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
     pad = pad_to((validation_pos + validation_len + 2 * sizeof(rpm_loff_t) +
c89eec
 		 sizeof(uint64_t)), fundamental_block_size);
c89eec
     if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
c89eec
-	fprintf(stderr, _("Unable to write final padding\n"));
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write final padding\n"));
c89eec
 	rc = RPMRC_FAIL;
c89eec
 	goto exit;
c89eec
     }
c89eec
     zeros = _free(zeros);
c89eec
-    if (Fwrite(&pos, len, 1, fdo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write offset of digest table\n"));
c89eec
-	rc = RPMRC_FAIL;
c89eec
-	goto exit;
c89eec
-    }
c89eec
-    if (Fwrite(&validation_pos, len, 1, fdo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write offset of validation table\n"));
c89eec
-	rc = RPMRC_FAIL;
c89eec
-	goto exit;
c89eec
-    }
c89eec
-    uint64_t magic = MAGIC;
c89eec
-    len = sizeof(magic);
c89eec
-    if (Fwrite(&magic, len, 1, fdo) != len) {
c89eec
-	fprintf(stderr, _("Unable to write magic\n"));
c89eec
+    struct extents_footer_t footer = {.offsets = {validation_pos, digest_table_pos, digest_pos}, .magic = EXTENTS_MAGIC};
c89eec
+    len = sizeof(footer);
c89eec
+    if (Fwrite(&footer, len, 1, fdo) != len) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Unable to write footer\n"));
c89eec
 	rc = RPMRC_FAIL;
c89eec
 	goto exit;
c89eec
     }
c89eec
@@ -327,104 +420,202 @@ static rpmRC process_package(FD_t fdi, FD_t validationi)
c89eec
     return rc;
c89eec
 }
c89eec
 
c89eec
-int main(int argc, char *argv[]) {
c89eec
-    rpmRC rc;
c89eec
-    int cprc = 0;
c89eec
-    uint8_t algos[argc - 1];
c89eec
-    int mainpipefd[2];
c89eec
-    int metapipefd[2];
c89eec
-    pid_t cpid, w;
c89eec
-    int wstatus;
c89eec
+static off_t ufdTee(FD_t sfd, FD_t *fds, int len)
c89eec
+{
c89eec
+    char buf[BUFSIZ];
c89eec
+    ssize_t rdbytes, wrbytes;
c89eec
+    off_t total = 0;
c89eec
+
c89eec
+    while (1) {
c89eec
+	rdbytes = Fread(buf, sizeof(buf[0]), sizeof(buf), sfd);
c89eec
+
c89eec
+	if (rdbytes > 0) {
c89eec
+	    for(int i=0; i < len; i++) {
c89eec
+		wrbytes = Fwrite(buf, sizeof(buf[0]), rdbytes, fds[i]);
c89eec
+		if (wrbytes != rdbytes) {
c89eec
+		    rpmlog(RPMLOG_ERR,
c89eec
+			   _("Error wriing to FD %d: %s\n"),
c89eec
+			   i, Fstrerror(fds[i]));
c89eec
+		    total = -1;
c89eec
+		    break;
c89eec
+		}
c89eec
+	    }
c89eec
+	    if(total == -1){
c89eec
+		break;
c89eec
+	    }
c89eec
+	    total += wrbytes;
c89eec
+	} else {
c89eec
+	    if (rdbytes < 0)
c89eec
+		total = -1;
c89eec
+	    break;
c89eec
+	}
c89eec
+    }
c89eec
 
c89eec
-    xsetprogname(argv[0]);	/* Portability call -- see system.h */
c89eec
-    rpmReadConfigFiles(NULL, NULL);
c89eec
+    return total;
c89eec
+}
c89eec
 
c89eec
-    if (argc > 1 && (rstreq(argv[1], "-h") || rstreq(argv[1], "--help"))) {
c89eec
-	fprintf(stderr, _("Usage: %s [DIGESTALGO]...\n"), argv[0]);
c89eec
-	exit(EXIT_FAILURE);
c89eec
-    }
c89eec
+static rpmRC teeRpm(FD_t fdi, uint8_t algos[], uint32_t algos_len) {
c89eec
+    rpmRC rc = RPMRC_FAIL;
c89eec
+    off_t offt = -1;
c89eec
+    // tee-ed stdin
c89eec
+    int processorpipefd[2];
c89eec
+    int validatorpipefd[2];
c89eec
+    // metadata
c89eec
+    int meta_digestpipefd[2];
c89eec
+    int meta_rpmsignpipefd[2];
c89eec
+
c89eec
+    pid_t cpids[2], w;
c89eec
+    int wstatus;
c89eec
+    FD_t fds[2];
c89eec
 
c89eec
-    if (argc == 1) {
c89eec
-	fprintf(stderr,
c89eec
-		_("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n"));
c89eec
-	exit(EXIT_FAILURE);
c89eec
+     if (pipe(processorpipefd) == -1) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Processor pipe failure\n"));
c89eec
+	return RPMRC_FAIL;
c89eec
     }
c89eec
 
c89eec
-    for (int x = 0; x < (argc - 1); x++) {
c89eec
-	if (pgpStringVal(PGPVAL_HASHALGO, argv[x + 1], &algos[x]) != 0)
c89eec
-	{
c89eec
-	    fprintf(stderr,
c89eec
-		    _("Unable to resolve '%s' as a digest algorithm, exiting\n"),
c89eec
-		    argv[x + 1]);
c89eec
-	    exit(EXIT_FAILURE);
c89eec
-	}
c89eec
+    if (pipe(validatorpipefd) == -1) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Validator pipe failure\n"));
c89eec
+	return RPMRC_FAIL;
c89eec
     }
c89eec
 
c89eec
-
c89eec
-    if (pipe(mainpipefd) == -1) {
c89eec
-	fprintf(stderr, _("Main pipe failure\n"));
c89eec
-	exit(EXIT_FAILURE);
c89eec
+    if (pipe(meta_digestpipefd) == -1) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Meta digest pipe failure\n"));
c89eec
+	return RPMRC_FAIL;
c89eec
     }
c89eec
-    if (pipe(metapipefd) == -1) {
c89eec
-	fprintf(stderr, _("Meta pipe failure\n"));
c89eec
-	exit(EXIT_FAILURE);
c89eec
+
c89eec
+    if (pipe(meta_rpmsignpipefd) == -1) {
c89eec
+	rpmlog(RPMLOG_ERR, _("Meta rpm signature pipe failure\n"));
c89eec
+	return RPMRC_FAIL;
c89eec
     }
c89eec
-    cpid = fork();
c89eec
-    if (cpid == 0) {
c89eec
-	/* child: digestor */
c89eec
-	close(mainpipefd[0]);
c89eec
-	close(metapipefd[0]);
c89eec
-	FD_t fdi = fdDup(STDIN_FILENO);
c89eec
-	FD_t fdo = fdDup(mainpipefd[1]);
c89eec
-	FD_t validationo = fdDup(metapipefd[1]);
c89eec
-	rc = digestor(fdi, fdo, validationo, algos, argc - 1);
c89eec
-	Fclose(validationo);
c89eec
-	Fclose(fdo);
c89eec
+
c89eec
+    cpids[0] = fork();
c89eec
+    if (cpids[0] == 0) {
c89eec
+	/* child: validator */
c89eec
+	close(processorpipefd[0]);
c89eec
+	close(processorpipefd[1]);
c89eec
+	close(validatorpipefd[1]);
c89eec
+	close(meta_digestpipefd[0]);
c89eec
+	close(meta_rpmsignpipefd[0]);
c89eec
+	FD_t fdi = fdDup(validatorpipefd[0]);
c89eec
+	FD_t digesto = fdDup(meta_digestpipefd[1]);
c89eec
+	FD_t sigo = fdDup(meta_rpmsignpipefd[1]);
c89eec
+	close(meta_digestpipefd[1]);
c89eec
+	close(meta_rpmsignpipefd[1]);
c89eec
+	rc = validator(fdi, digesto, sigo, algos, algos_len);
c89eec
+	if(rc != RPMRC_OK) {
c89eec
+	    rpmlog(RPMLOG_ERR, _("Validator failed with RC %d\n"), rc);
c89eec
+	}
c89eec
 	Fclose(fdi);
c89eec
+	Fclose(digesto);
c89eec
+	Fclose(sigo);
c89eec
+	if (rc != RPMRC_OK) {
c89eec
+	    exit(EXIT_FAILURE);
c89eec
+	}
c89eec
+	exit(EXIT_SUCCESS);
c89eec
     } else {
c89eec
 	/* parent: main program */
c89eec
-	close(mainpipefd[1]);
c89eec
-	close(metapipefd[1]);
c89eec
-	FD_t fdi = fdDup(mainpipefd[0]);
c89eec
-	FD_t validationi = fdDup(metapipefd[0]);
c89eec
-	rc = process_package(fdi, validationi);
c89eec
-	Fclose(validationi);
c89eec
-	/* fdi is normally closed through the stacked file gzdi in the
c89eec
-	 * function.
c89eec
-	 * Wait for child process (digestor for stdin) to complete.
c89eec
-	 */
c89eec
-	if (rc != RPMRC_OK) {
c89eec
-	    if (kill(cpid, SIGTERM) != 0) {
c89eec
-		fprintf(stderr,
c89eec
-		        _("Failed to kill digest process when main process failed: %s\n"),
c89eec
-			strerror(errno));
c89eec
+	cpids[1] = fork();
c89eec
+	if (cpids[1] == 0) {
c89eec
+	    /* child: process_package */
c89eec
+	    close(validatorpipefd[0]);
c89eec
+	    close(validatorpipefd[1]);
c89eec
+	    close(processorpipefd[1]);
c89eec
+	    close(meta_digestpipefd[1]);
c89eec
+	    close(meta_rpmsignpipefd[1]);
c89eec
+	    FD_t fdi = fdDup(processorpipefd[0]);
c89eec
+	    close(processorpipefd[0]);
c89eec
+	    FD_t sigi = fdDup(meta_rpmsignpipefd[0]);
c89eec
+	    close(meta_rpmsignpipefd[0]);
c89eec
+	    FD_t digestori = fdDup(meta_digestpipefd[0]);
c89eec
+	    close(meta_digestpipefd[0]);
c89eec
+
c89eec
+	    rc = process_package(fdi, digestori, sigi);
c89eec
+	    if(rc != RPMRC_OK) {
c89eec
+		rpmlog(RPMLOG_ERR, _("Package processor failed: %d\n"), rc);
c89eec
 	    }
c89eec
-	}
c89eec
-	w = waitpid(cpid, &wstatus, 0);
c89eec
-	if (w == -1) {
c89eec
-	    fprintf(stderr, _("waitpid failed\n"));
c89eec
-	    cprc = EXIT_FAILURE;
c89eec
-	} else if (WIFEXITED(wstatus)) {
c89eec
-	    cprc = WEXITSTATUS(wstatus);
c89eec
-	    if (cprc != 0) {
c89eec
-		fprintf(stderr,
c89eec
-			_("Digest process non-zero exit code %d\n"),
c89eec
-			cprc);
c89eec
+	    Fclose(digestori);
c89eec
+	    Fclose(sigi);
c89eec
+	    /* fdi is normally closed through the stacked file gzdi in the
c89eec
+	     * function
c89eec
+	     */
c89eec
+
c89eec
+	    if (rc != RPMRC_OK) {
c89eec
+		exit(EXIT_FAILURE);
c89eec
 	    }
c89eec
-	} else if (WIFSIGNALED(wstatus)) {
c89eec
-	    fprintf(stderr,
c89eec
-		    _("Digest process was terminated with a signal: %d\n"),
c89eec
-		    WTERMSIG(wstatus));
c89eec
-	    cprc = EXIT_FAILURE;
c89eec
+	    exit(EXIT_SUCCESS);
c89eec
+
c89eec
+
c89eec
 	} else {
c89eec
-	    /* Don't think this can happen, but covering all bases */
c89eec
-	    fprintf(stderr, _("Unhandled circumstance in waitpid\n"));
c89eec
-	    cprc = EXIT_FAILURE;
c89eec
+	    /* Actual parent. Read from fdi and write to both processes */
c89eec
+	    close(processorpipefd[0]);
c89eec
+	    close(validatorpipefd[0]);
c89eec
+	    fds[0] = fdDup(processorpipefd[1]);
c89eec
+	    fds[1] = fdDup(validatorpipefd[1]);
c89eec
+	    close(validatorpipefd[1]);
c89eec
+	    close(processorpipefd[1]);
c89eec
+	    close(meta_digestpipefd[0]);
c89eec
+	    close(meta_digestpipefd[1]);
c89eec
+	    close(meta_rpmsignpipefd[0]);
c89eec
+	    close(meta_rpmsignpipefd[1]);
c89eec
+
c89eec
+	    rc = RPMRC_OK;
c89eec
+	    offt = ufdTee(fdi, fds, 2);
c89eec
+	    if(offt == -1){
c89eec
+		rpmlog(RPMLOG_ERR, _("Failed to tee RPM\n"));
c89eec
+		rc = RPMRC_FAIL;
c89eec
+	    }
c89eec
+	    Fclose(fds[0]);
c89eec
+	    Fclose(fds[1]);
c89eec
+	    w = waitpid(cpids[0], &wstatus, 0);
c89eec
+	    if (w == -1) {
c89eec
+		rpmlog(RPMLOG_ERR, _("waitpid cpids[0] failed\n"));
c89eec
+		rc = RPMRC_FAIL;
c89eec
+	    }
c89eec
+	    w = waitpid(cpids[1], &wstatus, 0);
c89eec
+	    if (w == -1) {
c89eec
+		rpmlog(RPMLOG_ERR, _("waitpid cpids[1] failed\n"));
c89eec
+		rc = RPMRC_FAIL;
c89eec
+	    }
c89eec
 	}
c89eec
-	if (cprc != EXIT_SUCCESS) {
c89eec
-	    rc = RPMRC_FAIL;
c89eec
+    }
c89eec
+
c89eec
+    return rc;
c89eec
+}
c89eec
+
c89eec
+int main(int argc, char *argv[]) {
c89eec
+    rpmRC rc;
c89eec
+    poptContext optCon = NULL;
c89eec
+    const char **args = NULL;
c89eec
+    int nb_algos = 0;
c89eec
+
c89eec
+    xsetprogname(argv[0]);	/* Portability call -- see system.h */
c89eec
+    rpmReadConfigFiles(NULL, NULL);
c89eec
+    optCon = rpmcliInit(argc, argv, optionsTable);
c89eec
+    poptSetOtherOptionHelp(optCon, "[OPTIONS]* <DIGESTALGO>");
c89eec
+
c89eec
+    if (poptPeekArg(optCon) == NULL) {
c89eec
+	rpmlog(RPMLOG_ERR,
c89eec
+	       _("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n"));
c89eec
+	poptPrintUsage(optCon, stderr, 0);
c89eec
+	exit(EXIT_FAILURE);
c89eec
+    }
c89eec
+
c89eec
+    args = poptGetArgs(optCon);
c89eec
+
c89eec
+    for (nb_algos=0; args[nb_algos]; nb_algos++);
c89eec
+    uint8_t algos[nb_algos];
c89eec
+    for (int x = 0; x < nb_algos; x++) {
c89eec
+	if (pgpStringVal(PGPVAL_HASHALGO, args[x], &algos[x]) != 0)
c89eec
+	{
c89eec
+	    rpmlog(RPMLOG_ERR,
c89eec
+		   _("Unable to resolve '%s' as a digest algorithm, exiting\n"),
c89eec
+		   args[x]);
c89eec
+	    exit(EXIT_FAILURE);
c89eec
 	}
c89eec
     }
c89eec
+
c89eec
+    FD_t fdi = fdDup(STDIN_FILENO);
c89eec
+    rc = teeRpm(fdi, algos, nb_algos);
c89eec
     if (rc != RPMRC_OK) {
c89eec
 	/* translate rpmRC into generic failure return code. */
c89eec
 	return EXIT_FAILURE;
c89eec
diff --git a/scripts/rpm2extents_dump b/scripts/rpm2extents_dump
c89eec
new file mode 100755
c89eec
index 000000000..596a59a49
c89eec
--- /dev/null
c89eec
+++ b/scripts/rpm2extents_dump
c89eec
@@ -0,0 +1,94 @@
c89eec
+#!/usr/bin/env python3
c89eec
+
c89eec
+import argparse
c89eec
+import binascii
c89eec
+import os
c89eec
+import struct
c89eec
+import sys
c89eec
+
c89eec
+MAGIC_SIZE = 8
c89eec
+MAGIC_STR = b'KWTSH100'
c89eec
+
c89eec
+POS_SIZE = 8
c89eec
+
c89eec
+def keep_position(func):
c89eec
+    def wrapper(*args, **kwargs):
c89eec
+        curr = args[0].tell()
c89eec
+        res = func(*args, **kwargs)
c89eec
+        f.seek(curr, os.SEEK_SET)
c89eec
+        return res
c89eec
+    return wrapper
c89eec
+
c89eec
+def read_validation_digest(f, validation_offset):
c89eec
+	digests = []
c89eec
+    # validation
c89eec
+	f.seek(validation_offset, os.SEEK_SET)
c89eec
+	val_content_len, val_digests_num = struct.unpack('=QI', f.read(8+4))
c89eec
+	for i in range(val_digests_num):
c89eec
+		algo_name_len, digest_len = struct.unpack('=II', f.read(8))
c89eec
+		algo_name, digest = struct.unpack(f'{algo_name_len}s{digest_len}s', f.read(algo_name_len+digest_len))
c89eec
+		digests.append((algo_name, binascii.hexlify(digest)))
c89eec
+	return digests
c89eec
+
c89eec
+
c89eec
+def read_digests_table(f, digest_offset):
c89eec
+	digests = []
c89eec
+    # validation
c89eec
+	f.seek(digest_offset, os.SEEK_SET)
c89eec
+	table_len, digest_len = struct.unpack('=II', f.read(8))
c89eec
+
c89eec
+	for i in range(table_len):
c89eec
+		digest, pos = struct.unpack(f'{digest_len}sQ', f.read(digest_len + 8))
c89eec
+		digests.append((pos, binascii.hexlify(digest)))
c89eec
+	return digests
c89eec
+
c89eec
+def read_signature_output(f, signature_offset):
c89eec
+    f.seek(signature_offset, os.SEEK_SET)
c89eec
+    signature_rc, signature_output_len = struct.unpack('=IQ', f.read(12))
c89eec
+    return signature_rc, f.read(signature_output_len)
c89eec
+
c89eec
+@keep_position
c89eec
+def parse_file(f):
c89eec
+	digests = []
c89eec
+	pos_table_offset = f.seek(-8 - 3*POS_SIZE, os.SEEK_END)
c89eec
+	signature_offset, digest_offset, validation_offset = struct.unpack('=QQQ', f.read(3*POS_SIZE))
c89eec
+
c89eec
+	validation_digests = read_validation_digest(f, validation_offset)
c89eec
+	digests_table = read_digests_table(f, digest_offset)
c89eec
+	signature_ouput = read_signature_output(f, signature_offset)
c89eec
+
c89eec
+	return validation_digests, digests_table, signature_ouput
c89eec
+
c89eec
+@keep_position
c89eec
+def is_transcoded(f):
c89eec
+    f.seek(-MAGIC_SIZE, os.SEEK_END)
c89eec
+    magic = f.read(MAGIC_SIZE)
c89eec
+    return magic == MAGIC_STR
c89eec
+
c89eec
+def arg_parse():
c89eec
+    parser = argparse.ArgumentParser()
c89eec
+    parser.add_argument('--dump-signature', action='store_true')
c89eec
+    parser.add_argument('--dump-file-digest-table', action='store_true')
c89eec
+    parser.add_argument('--dump-digests', action='store_true')
c89eec
+    parser.add_argument('file')
c89eec
+
c89eec
+    return parser.parse_args()
c89eec
+
c89eec
+if __name__ == '__main__':
c89eec
+    args = arg_parse()
c89eec
+    f = open(args.file, 'rb')
c89eec
+    if not is_transcoded(f):
c89eec
+        sys.exit(1)
c89eec
+
c89eec
+    validation_digests, digests_table, signature_output = parse_file(f)
c89eec
+    if(args.dump_file_digest_table):
c89eec
+        for digest in digests_table:
c89eec
+            print(f"FileDigest {hex(digest[0])}: {digest[1]}")
c89eec
+
c89eec
+    if(args.dump_digests):
c89eec
+        for validation_digest in validation_digests:
c89eec
+            print(f"HeaderDigest {validation_digest[0]} {validation_digest[1]}")
c89eec
+
c89eec
+    if(args.dump_signature):
c89eec
+        print(f"RPMSignOutput RC {signature_output[0]}\nRPMSignOutput Content {signature_output[1].decode()}")
c89eec
+
c89eec
diff --git a/tests/Makefile.am b/tests/Makefile.am
c89eec
index f78e17c3e..fc8a24a5e 100644
c89eec
--- a/tests/Makefile.am
c89eec
+++ b/tests/Makefile.am
c89eec
@@ -36,6 +36,7 @@ TESTSUITE_AT += rpmsigdig.at
c89eec
 TESTSUITE_AT += rpmio.at
c89eec
 TESTSUITE_AT += rpmorder.at
c89eec
 TESTSUITE_AT += rpmvfylevel.at
c89eec
+TESTSUITE_AT += rpm2extents.at
c89eec
 EXTRA_DIST += $(TESTSUITE_AT)
c89eec
 
c89eec
 ## testsuite data
c89eec
diff --git a/tests/atlocal.in b/tests/atlocal.in
c89eec
index c3189d327..a110564e2 100644
c89eec
--- a/tests/atlocal.in
c89eec
+++ b/tests/atlocal.in
c89eec
@@ -50,6 +50,19 @@ else
c89eec
     CAP_DISABLED=true;
c89eec
 fi
c89eec
 
c89eec
+FSTYPE=$(stat -f -c %T /)
c89eec
+REFLINKABLE_FS=("xfs" "brtfs")
c89eec
+
c89eec
+REFLINK_DISABLED=true;
c89eec
+for item in "${REFLINKABLE_FS[@]}"
c89eec
+do
c89eec
+    if test "${FSTYPE}" = "${item}"
c89eec
+    then
c89eec
+	REFLINK_DISABLED=false;
c89eec
+	break
c89eec
+    fi
c89eec
+done
c89eec
+
c89eec
 function setup_env()
c89eec
 {
c89eec
     if [ -d testing ]; then
c89eec
@@ -82,6 +95,15 @@ function runroot()
c89eec
     )
c89eec
 }
c89eec
 
c89eec
+function runroot_plugins()
c89eec
+{
c89eec
+    setup_env
c89eec
+    (unset RPM_CONFIGDIR RPM_POPTEXEC_PATH; cd ${RPMTEST} && \
c89eec
+     MAGIC="/magic/magic" FAKECHROOT_BASE="${RPMTEST}" fakechroot "$@" --define "_buildhost testhost" --define "_topdir /build" --nouserns
c89eec
+    )
c89eec
+}
c89eec
+
c89eec
+
c89eec
 function runroot_other()
c89eec
 {
c89eec
     setup_env
c89eec
diff --git a/tests/rpm2extents.at b/tests/rpm2extents.at
c89eec
new file mode 100644
c89eec
index 000000000..29165c1b9
c89eec
--- /dev/null
c89eec
+++ b/tests/rpm2extents.at
c89eec
@@ -0,0 +1,141 @@
c89eec
+#    rpm2extents.at: Some very basic checks
c89eec
+#
c89eec
+#    Copyright (C) 2022  Manu Bretelle <chantr4@gmail.com>
c89eec
+#
c89eec
+#    This program is free software; you can redistribute it and/or modify
c89eec
+#    it under the terms of the GNU General Public License as published by
c89eec
+#    the Free Software Foundation; either version 2 of the License, or
c89eec
+#    (at your option) any later version.
c89eec
+#
c89eec
+#    This program is distributed in the hope that it will be useful,
c89eec
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
c89eec
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
c89eec
+#    GNU General Public License for more details.
c89eec
+#
c89eec
+#    You should have received a copy of the GNU General Public License
c89eec
+#    along with this program; if not, write to the Free Software
c89eec
+#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
c89eec
+
c89eec
+AT_BANNER([rpm2extents tests])
c89eec
+
c89eec
+# ------------------------------
c89eec
+
c89eec
+# check that transcoder write magic at the end
c89eec
+AT_SETUP([rpm2extents magic])
c89eec
+AT_KEYWORDS([rpm2extents])
c89eec
+AT_CHECK([runroot_other cat /data/RPMS/hello-2.0-1.x86_64.rpm | runroot_other rpm2extents SHA256 | tail -c8],
c89eec
+[0],
c89eec
+[KWTSH100],
c89eec
+[ignore])
c89eec
+AT_CLEANUP
c89eec
+
c89eec
+# Check that transcoder writes checksig return code and content.
c89eec
+#
c89eec
+AT_SETUP([rpm2extents signature])
c89eec
+AT_KEYWORDS([rpm2extents])
c89eec
+AT_CHECK([
c89eec
+RPMDB_INIT
c89eec
+
c89eec
+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
c89eec
+rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm
c89eec
+runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
c89eec
+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
c89eec
+rpm2extents_dump --dump-signature /tmp/hello-2.0-1.x86_64-signed.rpm
c89eec
+],
c89eec
+[0],
c89eec
+[RPMSignOutput RC 2
c89eec
+RPMSignOutput Content     Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
c89eec
+    Header SHA256 digest: OK
c89eec
+    Header SHA1 digest: OK
c89eec
+    Payload SHA256 digest: OK
c89eec
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
c89eec
+    MD5 digest: OK
c89eec
+
c89eec
+RPMSignOutput RC 0
c89eec
+RPMSignOutput Content     Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
c89eec
+    Header SHA256 digest: OK
c89eec
+    Header SHA1 digest: OK
c89eec
+    Payload SHA256 digest: OK
c89eec
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
c89eec
+    MD5 digest: OK
c89eec
+
c89eec
+],
c89eec
+[])
c89eec
+AT_CLEANUP
c89eec
+
c89eec
+AT_SETUP([rpm2extents signature verification])
c89eec
+AT_KEYWORDS([rpm2extents])
c89eec
+AT_CHECK([
c89eec
+RPMDB_INIT
c89eec
+
c89eec
+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
c89eec
+runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $?
c89eec
+runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
c89eec
+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
c89eec
+runroot rpmkeys -Kv /tmp/hello-2.0-1.x86_64-signed.rpm; echo $?
c89eec
+],
c89eec
+[0],
c89eec
+[/tmp/hello-2.0-1.x86_64-signed.rpm:
c89eec
+    Header V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
c89eec
+    Header SHA256 digest: OK
c89eec
+    Header SHA1 digest: OK
c89eec
+    Payload SHA256 digest: OK
c89eec
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: NOKEY
c89eec
+    MD5 digest: OK
c89eec
+1
c89eec
+/tmp/hello-2.0-1.x86_64-signed.rpm:
c89eec
+    Header V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
c89eec
+    Header SHA256 digest: OK
c89eec
+    Header SHA1 digest: OK
c89eec
+    Payload SHA256 digest: OK
c89eec
+    V4 RSA/SHA256 Signature, key ID 1964c5fc: OK
c89eec
+    MD5 digest: OK
c89eec
+0
c89eec
+],
c89eec
+[])
c89eec
+AT_CLEANUP
c89eec
+
c89eec
+AT_SETUP([rpm2extents install package])
c89eec
+AT_KEYWORDS([rpm2extents reflink])
c89eec
+AT_SKIP_IF([$REFLINK_DISABLED])
c89eec
+AT_CHECK([
c89eec
+RPMDB_INIT
c89eec
+
c89eec
+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
c89eec
+runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm
c89eec
+],
c89eec
+[0],
c89eec
+[],
c89eec
+[])
c89eec
+AT_CLEANUP
c89eec
+
c89eec
+AT_SETUP([reflink hardlink package])
c89eec
+AT_KEYWORDS([reflink hardlink])
c89eec
+AT_SKIP_IF([$REFLINK_DISABLED])
c89eec
+AT_CHECK([
c89eec
+RPMDB_INIT
c89eec
+
c89eec
+PKG=hlinktest-1.0-1.noarch.rpm
c89eec
+runroot_other cat /data/RPMS/${PKG} | runroot_other rpm2extents SHA256 > ${RPMTEST}/tmp/${PKG} 2> /dev/null
c89eec
+runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/${PKG}
c89eec
+],
c89eec
+[0],
c89eec
+[],
c89eec
+[])
c89eec
+AT_CLEANUP
c89eec
+
c89eec
+AT_SETUP([rpm2extents install package])
c89eec
+AT_KEYWORDS([rpm2extents reflink])
c89eec
+AT_SKIP_IF([$REFLINK_DISABLED])
c89eec
+AT_CHECK([
c89eec
+RPMDB_INIT
c89eec
+
c89eec
+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
c89eec
+runroot_plugins rpm -i --nodigest --nodeps --undefine=%__transaction_dbus_announce /tmp/hello-2.0-1.x86_64.rpm
c89eec
+],
c89eec
+[0],
c89eec
+[],
c89eec
+[])
c89eec
+AT_CLEANUP
c89eec
+
c89eec
+
c89eec
diff --git a/tests/rpmtests.at b/tests/rpmtests.at
c89eec
index a1adab8e0..205fed6a3 100644
c89eec
--- a/tests/rpmtests.at
c89eec
+++ b/tests/rpmtests.at
c89eec
@@ -21,3 +21,4 @@ m4_include([rpmreplace.at])
c89eec
 m4_include([rpmconfig.at])
c89eec
 m4_include([rpmconfig2.at])
c89eec
 m4_include([rpmconfig3.at])
c89eec
+m4_include([rpm2extents.at])