michal-grzedzicki / rpms / rpm

Forked from rpms/rpm 4 months ago
Clone
8b6ae9
diff --git a/Makefile.am b/Makefile.am
8b6ae9
index e5c75d7b4..288668819 100644
8b6ae9
--- a/Makefile.am
8b6ae9
+++ b/Makefile.am
8b6ae9
@@ -99,7 +99,7 @@ pkginclude_HEADERS += build/rpmfc.h
8b6ae9
 pkginclude_HEADERS += build/rpmspec.h
8b6ae9
 
8b6ae9
 
8b6ae9
-bin_PROGRAMS =		rpm rpm2cpio rpmbuild rpmdb rpmkeys rpmsign rpmspec
8b6ae9
+bin_PROGRAMS =		rpm rpm2cpio rpmbuild rpmdb rpmkeys rpmsign rpmspec rpm2extents
8b6ae9
 if WITH_ARCHIVE
8b6ae9
 bin_PROGRAMS += 	rpm2archive 
8b6ae9
 endif
8b6ae9
@@ -154,6 +154,10 @@ rpm2cpio_SOURCES =	rpm2cpio.c debug.h system.h
8b6ae9
 rpm2cpio_LDADD =	lib/librpm.la rpmio/librpmio.la
8b6ae9
 rpm2cpio_LDADD +=	@WITH_POPT_LIB@
8b6ae9
 
8b6ae9
+rpm2extents_SOURCES =	rpm2extents.c debug.h system.h
8b6ae9
+rpm2extents_LDADD =	lib/librpm.la rpmio/librpmio.la
8b6ae9
+rpm2extents_LDADD +=	@WITH_POPT_LIB@
8b6ae9
+
8b6ae9
 rpm2archive_SOURCES =	rpm2archive.c debug.h system.h
8b6ae9
 rpm2archive_LDADD =	lib/librpm.la rpmio/librpmio.la
8b6ae9
 rpm2archive_LDADD +=	@WITH_POPT_LIB@ @WITH_ARCHIVE_LIB@
8b6ae9
diff --git a/lib/depends.c b/lib/depends.c
8b6ae9
index 30234df3d..8998afcd3 100644
8b6ae9
--- a/lib/depends.c
8b6ae9
+++ b/lib/depends.c
8b6ae9
@@ -81,6 +81,8 @@ static rpmRC headerCheckPayloadFormat(Header h) {
8b6ae9
      */
8b6ae9
     if (!payloadfmt) return rc;
8b6ae9
 
8b6ae9
+    if (rstreq(payloadfmt, "clon")) return rc;
8b6ae9
+
8b6ae9
     if (!rstreq(payloadfmt, "cpio")) {
8b6ae9
         char *nevra = headerGetAsString(h, RPMTAG_NEVRA);
8b6ae9
         if (payloadfmt && rstreq(payloadfmt, "drpm")) {
8b6ae9
diff --git a/lib/fsm.c b/lib/fsm.c
8b6ae9
index 935a0a5c6..feda3750c 100644
8b6ae9
--- a/lib/fsm.c
8b6ae9
+++ b/lib/fsm.c
8b6ae9
@@ -19,6 +19,7 @@
8b6ae9
 
8b6ae9
 #include "rpmio/rpmio_internal.h"	/* fdInit/FiniDigest */
8b6ae9
 #include "lib/fsm.h"
8b6ae9
+#include "lib/rpmlib.h"
8b6ae9
 #include "lib/rpmte_internal.h"	/* XXX rpmfs */
8b6ae9
 #include "lib/rpmplugins.h"	/* rpm plugins hooks */
8b6ae9
 #include "lib/rpmug.h"
8b6ae9
@@ -52,6 +53,7 @@ struct filedata_s {
8b6ae9
     int stage;
8b6ae9
     int setmeta;
8b6ae9
     int skip;
8b6ae9
+    int plugin_contents;
8b6ae9
     rpmFileAction action;
8b6ae9
     const char *suffix;
8b6ae9
     char *fpath;
8b6ae9
@@ -891,6 +893,14 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
8b6ae9
     struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
8b6ae9
     struct filedata_s *firstlink = NULL;
8b6ae9
 
8b6ae9
+    Header h = rpmteHeader(te);
8b6ae9
+    const char *payloadfmt = headerGetString(h, RPMTAG_PAYLOADFORMAT);
8b6ae9
+    int cpio = 1;
8b6ae9
+
8b6ae9
+    if (payloadfmt && rstreq(payloadfmt, "clon")) {
8b6ae9
+	cpio = 0;
8b6ae9
+    }
8b6ae9
+
8b6ae9
     /* transaction id used for temporary path suffix while installing */
8b6ae9
     rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
8b6ae9
 
8b6ae9
@@ -911,12 +921,23 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
8b6ae9
 	/* Remap file perms, owner, and group. */
8b6ae9
 	rc = rpmfiStat(fi, 1, &fp->sb);
8b6ae9
 
8b6ae9
-	setFileState(fs, fx);
8b6ae9
 	fsmDebug(fp->fpath, fp->action, &fp->sb);
8b6ae9
 
8b6ae9
 	/* Run fsm file pre hook for all plugins */
8b6ae9
 	rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
8b6ae9
 				      fp->sb.st_mode, fp->action);
8b6ae9
+	fp->plugin_contents = 0;
8b6ae9
+	switch (rc) {
8b6ae9
+	case RPMRC_OK:
8b6ae9
+	    setFileState(fs, fx);
8b6ae9
+	    break;
8b6ae9
+	case RPMRC_PLUGIN_CONTENTS:
8b6ae9
+	    fp->plugin_contents = 1;
8b6ae9
+	    // reduce reads on cpio to this value. Could be zero if
8b6ae9
+	    // this is from a hard link.
8b6ae9
+	    rc = RPMRC_OK;
8b6ae9
+	    break;
8b6ae9
+	}
8b6ae9
 	fp->stage = FILE_PRE;
8b6ae9
     }
8b6ae9
     fi = rpmfiFree(fi);
8b6ae9
@@ -924,10 +945,14 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
8b6ae9
     if (rc)
8b6ae9
 	goto exit;
8b6ae9
 
8b6ae9
-    fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
8b6ae9
-    if (fi == NULL) {
8b6ae9
-        rc = RPMERR_BAD_MAGIC;
8b6ae9
-        goto exit;
8b6ae9
+    if (cpio) {
8b6ae9
+	fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
8b6ae9
+	if (fi == NULL) {
8b6ae9
+	    rc = RPMERR_BAD_MAGIC;
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+    } else {
8b6ae9
+	fi = rpmfilesIter(files, RPMFI_ITER_FWD);
8b6ae9
     }
8b6ae9
 
8b6ae9
     /* Detect and create directories not explicitly in package. */
8b6ae9
@@ -969,8 +994,12 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
8b6ae9
 
8b6ae9
             if (S_ISREG(fp->sb.st_mode)) {
8b6ae9
 		if (rc == RPMERR_ENOENT) {
8b6ae9
-		    rc = fsmMkfile(fi, fp, files, psm, nodigest,
8b6ae9
-				   &firstlink, &firstlinkfile);
8b6ae9
+		    if(fp->plugin_contents) {
8b6ae9
+			rc = RPMRC_OK;
8b6ae9
+		    }else {
8b6ae9
+			rc = fsmMkfile(fi, fp, files, psm, nodigest,
8b6ae9
+			    &firstlink, &firstlinkfile);
8b6ae9
+		    }
8b6ae9
 		}
8b6ae9
             } else if (S_ISDIR(fp->sb.st_mode)) {
8b6ae9
                 if (rc == RPMERR_ENOENT) {
8b6ae9
@@ -1078,6 +1107,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
8b6ae9
     rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
8b6ae9
 
8b6ae9
 exit:
8b6ae9
+    h = headerFree(h);
8b6ae9
     fi = rpmfiFree(fi);
8b6ae9
     Fclose(payload);
8b6ae9
     free(tid);
8b6ae9
diff --git a/lib/package.c b/lib/package.c
8b6ae9
index 281275029..90bd0d8a7 100644
8b6ae9
--- a/lib/package.c
8b6ae9
+++ b/lib/package.c
8b6ae9
@@ -404,5 +404,45 @@ rpmRC rpmReadPackageFile(rpmts ts, FD_t fd, const char * fn, Header * hdrp)
8b6ae9
     return rc;
8b6ae9
 }
8b6ae9
 
8b6ae9
+rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp)
8b6ae9
+{
8b6ae9
+    char *msg = NULL;
8b6ae9
+    hdrblob sigblob = hdrblobCreate();
8b6ae9
+    hdrblob blob = hdrblobCreate();
8b6ae9
+    Header h = NULL;
8b6ae9
+    Header sigh = NULL;
8b6ae9
+
8b6ae9
+    rpmRC rc = rpmLeadRead(fd, &msg;;
8b6ae9
+    if (rc != RPMRC_OK)
8b6ae9
+	goto exit;
8b6ae9
+
8b6ae9
+    rc = hdrblobRead(fd, 1, 0, RPMTAG_HEADERSIGNATURES, sigblob, &msg;;
8b6ae9
+    if (rc != RPMRC_OK)
8b6ae9
+	goto exit;
8b6ae9
+
8b6ae9
+    rc = hdrblobRead(fd, 1, 1, RPMTAG_HEADERIMMUTABLE, blob, &msg;;
8b6ae9
+    if (rc != RPMRC_OK)
8b6ae9
+	goto exit;
8b6ae9
+
8b6ae9
+    rc = hdrblobImport(sigblob, 0, &sigh, &msg;;
8b6ae9
+    if (rc)
8b6ae9
+	goto exit;
8b6ae9
 
8b6ae9
+    rc = hdrblobImport(blob, 0, &h, &msg;;
8b6ae9
+    if (rc)
8b6ae9
+	goto exit;
8b6ae9
 
8b6ae9
+    *sigp = headerLink(sigh);
8b6ae9
+    *hdrp = headerLink(h);
8b6ae9
+
8b6ae9
+exit:
8b6ae9
+    if (rc != RPMRC_OK && msg)
8b6ae9
+	rpmlog(RPMLOG_ERR, "%s: %s\n", Fdescr(fd), msg);
8b6ae9
+    hdrblobFree(sigblob);
8b6ae9
+    hdrblobFree(blob);
8b6ae9
+    headerFree(sigh);
8b6ae9
+    headerFree(h);
8b6ae9
+    free(msg);
8b6ae9
+
8b6ae9
+    return rc;
8b6ae9
+}
8b6ae9
diff --git a/lib/rpmlib.h b/lib/rpmlib.h
8b6ae9
index 0879d04e5..a09ba0daf 100644
8b6ae9
--- a/lib/rpmlib.h
8b6ae9
+++ b/lib/rpmlib.h
8b6ae9
@@ -155,6 +155,15 @@ rpmRC rpmReadHeader(rpmts ts, FD_t fd, Header *hdrp, char ** msg);
8b6ae9
 rpmRC rpmReadPackageFile(rpmts ts, FD_t fd,
8b6ae9
 		const char * fn, Header * hdrp);
8b6ae9
 
8b6ae9
+/** \ingroup header
8b6ae9
+ * Return package signature, header from file handle, no verification.
8b6ae9
+ * @param fd		file handle
8b6ae9
+ * @param[out] sigp		address of header (or NULL)
8b6ae9
+ * @param[out] hdrp		address of header (or NULL)
8b6ae9
+ * @return		RPMRC_OK on success
8b6ae9
+ */
8b6ae9
+rpmRC rpmReadPackageRaw(FD_t fd, Header * sigp, Header * hdrp);
8b6ae9
+
8b6ae9
 /** \ingroup rpmtrans
8b6ae9
  * Install source package.
8b6ae9
  * @param ts		transaction set
8b6ae9
diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
8b6ae9
index 62d75c4cf..3da3097af 100644
8b6ae9
--- a/lib/rpmplugins.c
8b6ae9
+++ b/lib/rpmplugins.c
8b6ae9
@@ -356,13 +356,28 @@ rpmRC rpmpluginsCallFsmFilePre(rpmPlugins plugins, rpmfi fi, const char *path,
8b6ae9
     plugin_fsm_file_pre_func hookFunc;
8b6ae9
     int i;
8b6ae9
     rpmRC rc = RPMRC_OK;
8b6ae9
+    rpmRC hook_rc;
8b6ae9
 
8b6ae9
     for (i = 0; i < plugins->count; i++) {
8b6ae9
 	rpmPlugin plugin = plugins->plugins[i];
8b6ae9
 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_pre);
8b6ae9
-	if (hookFunc && hookFunc(plugin, fi, path, file_mode, op) == RPMRC_FAIL) {
8b6ae9
-	    rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
8b6ae9
-	    rc = RPMRC_FAIL;
8b6ae9
+	if (hookFunc) {
8b6ae9
+	    hook_rc = hookFunc(plugin, fi, path, file_mode, op);
8b6ae9
+	    if (hook_rc == RPMRC_FAIL) {
8b6ae9
+		rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
8b6ae9
+		rc = RPMRC_FAIL;
8b6ae9
+	    } else if (hook_rc == RPMRC_PLUGIN_CONTENTS && rc != RPMRC_FAIL) {
8b6ae9
+		if (rc == RPMRC_PLUGIN_CONTENTS) {
8b6ae9
+		    /* Another plugin already said it'd handle contents. It's
8b6ae9
+		     * undefined how these would combine, so treat this as a
8b6ae9
+		     * failure condition.
8b6ae9
+		    */
8b6ae9
+		    rc = RPMRC_FAIL;
8b6ae9
+		} else {
8b6ae9
+		    /* Plugin will handle content */
8b6ae9
+		    rc = RPMRC_PLUGIN_CONTENTS;
8b6ae9
+		}
8b6ae9
+	    }
8b6ae9
 	}
8b6ae9
     }
8b6ae9
 
8b6ae9
diff --git a/lib/rpmte.c b/lib/rpmte.c
8b6ae9
index 3663604e7..d43dc41ad 100644
8b6ae9
--- a/lib/rpmte.c
8b6ae9
+++ b/lib/rpmte.c
8b6ae9
@@ -423,6 +423,11 @@ FD_t rpmteSetFd(rpmte te, FD_t fd)
8b6ae9
     return NULL;
8b6ae9
 }
8b6ae9
 
8b6ae9
+FD_t rpmteFd(rpmte te)
8b6ae9
+{
8b6ae9
+    return (te != NULL ? te->fd : NULL);
8b6ae9
+}
8b6ae9
+
8b6ae9
 fnpyKey rpmteKey(rpmte te)
8b6ae9
 {
8b6ae9
     return (te != NULL ? te->key : NULL);
8b6ae9
diff --git a/lib/rpmte.h b/lib/rpmte.h
8b6ae9
index 81acf7a19..6fc0a9f91 100644
8b6ae9
--- a/lib/rpmte.h
8b6ae9
+++ b/lib/rpmte.h
8b6ae9
@@ -209,6 +209,8 @@ const char * rpmteNEVR(rpmte te);
8b6ae9
  */
8b6ae9
 const char * rpmteNEVRA(rpmte te);
8b6ae9
 
8b6ae9
+FD_t rpmteFd(rpmte te);
8b6ae9
+
8b6ae9
 /** \ingroup rpmte
8b6ae9
  * Retrieve key from transaction element.
8b6ae9
  * @param te		transaction element
8b6ae9
diff --git a/lib/rpmtypes.h b/lib/rpmtypes.h
8b6ae9
index e8e69b506..af2611e9e 100644
8b6ae9
--- a/lib/rpmtypes.h
8b6ae9
+++ b/lib/rpmtypes.h
8b6ae9
@@ -106,7 +106,8 @@ typedef	enum rpmRC_e {
8b6ae9
     RPMRC_NOTFOUND	= 1,	/*!< Generic not found code. */
8b6ae9
     RPMRC_FAIL		= 2,	/*!< Generic failure code. */
8b6ae9
     RPMRC_NOTTRUSTED	= 3,	/*!< Signature is OK, but key is not trusted. */
8b6ae9
-    RPMRC_NOKEY		= 4	/*!< Public key is unavailable. */
8b6ae9
+    RPMRC_NOKEY		= 4,	/*!< Public key is unavailable. */
8b6ae9
+    RPMRC_PLUGIN_CONTENTS = 5     /*!< fsm_file_pre plugin is handling content */
8b6ae9
 } rpmRC;
8b6ae9
 
8b6ae9
 #ifdef __cplusplus
8b6ae9
diff --git a/macros.in b/macros.in
8b6ae9
index e90cefa9a..363252b0f 100644
8b6ae9
--- a/macros.in
8b6ae9
+++ b/macros.in
8b6ae9
@@ -1143,6 +1143,7 @@ package or when debugging this package.\
8b6ae9
 
8b6ae9
 # Transaction plugin macros
8b6ae9
 %__plugindir		%{_libdir}/rpm-plugins
8b6ae9
+%__transaction_reflink		%{__plugindir}/reflink.so
8b6ae9
 %__transaction_systemd_inhibit	%{__plugindir}/systemd_inhibit.so
8b6ae9
 %__transaction_selinux		%{__plugindir}/selinux.so
8b6ae9
 %__transaction_syslog		%{__plugindir}/syslog.so
8b6ae9
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
8b6ae9
index 3a929d0ce..ad0d3bce7 100644
8b6ae9
--- a/plugins/Makefile.am
8b6ae9
+++ b/plugins/Makefile.am
8b6ae9
@@ -42,6 +42,10 @@ prioreset_la_SOURCES = prioreset.c
8b6ae9
 prioreset_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
8b6ae9
 plugins_LTLIBRARIES += prioreset.la
8b6ae9
 
8b6ae9
+reflink_la_SOURCES = reflink.c
8b6ae9
+reflink_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
8b6ae9
+plugins_LTLIBRARIES += reflink.la
8b6ae9
+
8b6ae9
 syslog_la_SOURCES = syslog.c
8b6ae9
 syslog_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
8b6ae9
 plugins_LTLIBRARIES += syslog.la
8b6ae9
diff --git a/plugins/reflink.c b/plugins/reflink.c
8b6ae9
new file mode 100644
8b6ae9
index 000000000..513887604
8b6ae9
--- /dev/null
8b6ae9
+++ b/plugins/reflink.c
8b6ae9
@@ -0,0 +1,375 @@
8b6ae9
+#include "system.h"
8b6ae9
+
8b6ae9
+#include <errno.h>
8b6ae9
+#include <sys/resource.h>
8b6ae9
+#include <unistd.h>
8b6ae9
+#include <sys/types.h>
8b6ae9
+#include <sys/stat.h>
8b6ae9
+#include <fcntl.h>
8b6ae9
+#if defined(__linux__)
8b6ae9
+#include <linux/fs.h>        /* For FICLONE */
8b6ae9
+#endif
8b6ae9
+
8b6ae9
+#include <rpm/rpmlog.h>
8b6ae9
+#include "lib/rpmlib.h"
8b6ae9
+#include "lib/rpmplugin.h"
8b6ae9
+#include "lib/rpmte_internal.h"
8b6ae9
+#include <rpm/rpmfileutil.h>
8b6ae9
+#include "rpmio/rpmio_internal.h"
8b6ae9
+
8b6ae9
+
8b6ae9
+#include "debug.h"
8b6ae9
+
8b6ae9
+#include <sys/ioctl.h>
8b6ae9
+
8b6ae9
+/* use hash table to remember inode -> ix (for rpmfilesFN(ix)) lookups */
8b6ae9
+#undef HASHTYPE
8b6ae9
+#undef HTKEYTYPE
8b6ae9
+#undef HTDATATYPE
8b6ae9
+#define HASHTYPE inodeIndexHash
8b6ae9
+#define HTKEYTYPE rpm_ino_t
8b6ae9
+#define HTDATATYPE int
8b6ae9
+#include "lib/rpmhash.H"
8b6ae9
+#include "lib/rpmhash.C"
8b6ae9
+
8b6ae9
+/* We use this in find to indicate a key wasn't found. This is an
8b6ae9
+ * unrecoverable error, but we can at least show a decent error. 0 is never a
8b6ae9
+ * valid offset because it's the offset of the start of the file.
8b6ae9
+ */
8b6ae9
+#define NOT_FOUND 0
8b6ae9
+
8b6ae9
+#define BUFFER_SIZE (1024 * 128)
8b6ae9
+
8b6ae9
+/* magic value at end of file (64 bits) that indicates this is a transcoded
8b6ae9
+ * rpm.
8b6ae9
+ */
8b6ae9
+#define MAGIC 3472329499408095051
8b6ae9
+
8b6ae9
+struct reflink_state_s {
8b6ae9
+    /* Stuff that's used across rpms */
8b6ae9
+    long fundamental_block_size;
8b6ae9
+    char *buffer;
8b6ae9
+
8b6ae9
+    /* stuff that's used/updated per psm */
8b6ae9
+    uint32_t keys, keysize;
8b6ae9
+
8b6ae9
+    /* table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) */
8b6ae9
+    unsigned char *table;
8b6ae9
+    FD_t fd;
8b6ae9
+    rpmfiles files;
8b6ae9
+    inodeIndexHash inodeIndexes;
8b6ae9
+};
8b6ae9
+
8b6ae9
+typedef struct reflink_state_s * reflink_state;
8b6ae9
+
8b6ae9
+static int inodeCmp(rpm_ino_t a, rpm_ino_t b)
8b6ae9
+{
8b6ae9
+    return (a != b);
8b6ae9
+}
8b6ae9
+
8b6ae9
+static unsigned int inodeId(rpm_ino_t a)
8b6ae9
+{
8b6ae9
+    /* rpm_ino_t is uint32_t so maps safely to unsigned int */
8b6ae9
+    return (unsigned int)a;
8b6ae9
+}
8b6ae9
+
8b6ae9
+static rpmRC reflink_init(rpmPlugin plugin, rpmts ts) {
8b6ae9
+    reflink_state state = rcalloc(1, sizeof(struct reflink_state_s));
8b6ae9
+
8b6ae9
+    /* IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset
8b6ae9
+     * and length arguments to be aligned to the fundamental block size.
8b6ae9
+     *
8b6ae9
+     * The value of "fundamental block size" is directly related to the
8b6ae9
+     * system's page size, so we should use that.
8b6ae9
+     */
8b6ae9
+    state->fundamental_block_size = sysconf(_SC_PAGESIZE);
8b6ae9
+    state->buffer = rcalloc(1, BUFFER_SIZE);
8b6ae9
+    rpmPluginSetData(plugin, state);
8b6ae9
+
8b6ae9
+    return RPMRC_OK;
8b6ae9
+}
8b6ae9
+
8b6ae9
+static void reflink_cleanup(rpmPlugin plugin) {
8b6ae9
+    reflink_state state = rpmPluginGetData(plugin);
8b6ae9
+    free(state->buffer);
8b6ae9
+    free(state);
8b6ae9
+}
8b6ae9
+
8b6ae9
+static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) {
8b6ae9
+    reflink_state state = rpmPluginGetData(plugin);
8b6ae9
+    state->fd = rpmteFd(te);
8b6ae9
+    if (state->fd == 0) {
8b6ae9
+	rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n"));
8b6ae9
+	return RPMRC_OK;
8b6ae9
+    }
8b6ae9
+    rpm_loff_t current = Ftell(state->fd);
8b6ae9
+    uint64_t magic;
8b6ae9
+    if (Fseek(state->fd, -(sizeof(magic)), SEEK_END) < 0) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: failed to seek for magic\n"));
8b6ae9
+	if (Fseek(state->fd, current, SEEK_SET) < 0) {
8b6ae9
+	    /* yes this gets a bit repetitive */
8b6ae9
+	    rpmlog(RPMLOG_ERR,
8b6ae9
+		 _("reflink: unable to seek back to original location\n"));
8b6ae9
+	}
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    size_t len = sizeof(magic);
8b6ae9
+    if (Fread(&magic, len, 1, state->fd) != len) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read magic\n"));
8b6ae9
+	if (Fseek(state->fd, current, SEEK_SET) < 0) {
8b6ae9
+	    rpmlog(RPMLOG_ERR,
8b6ae9
+		   _("reflink: unable to seek back to original location\n"));
8b6ae9
+	}
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    if (magic != MAGIC) {
8b6ae9
+	rpmlog(RPMLOG_DEBUG, _("reflink: not transcoded\n"));
8b6ae9
+	if (Fseek(state->fd, current, SEEK_SET) < 0) {
8b6ae9
+	    rpmlog(RPMLOG_ERR,
8b6ae9
+		   _("reflink: unable to seek back to original location\n"));
8b6ae9
+	    return RPMRC_FAIL;
8b6ae9
+	}
8b6ae9
+	return RPMRC_OK;
8b6ae9
+    }
8b6ae9
+    rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n"));
8b6ae9
+    Header h = rpmteHeader(te);
8b6ae9
+
8b6ae9
+    /* replace/add header that main fsm.c can read */
8b6ae9
+    headerDel(h, RPMTAG_PAYLOADFORMAT);
8b6ae9
+    headerPutString(h, RPMTAG_PAYLOADFORMAT, "clon");
8b6ae9
+    headerFree(h);
8b6ae9
+    state->files = rpmteFiles(te);
8b6ae9
+    /* tail of file contains offset_table, offset_checksums then magic */
8b6ae9
+    if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(magic)), SEEK_END) < 0) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"),
8b6ae9
+	       state->fd);
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    rpm_loff_t table_start;
8b6ae9
+    len = sizeof(table_start);
8b6ae9
+    if (Fread(&table_start, len, 1, state->fd) != len) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n"));
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    if (Fseek(state->fd, table_start, SEEK_SET) < 0) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n"));
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    len = sizeof(state->keys);
8b6ae9
+    if (Fread(&state->keys, len, 1, state->fd) != len) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n"));
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    len = sizeof(state->keysize);
8b6ae9
+    if (Fread(&state->keysize, len, 1, state->fd) != len) {
8b6ae9
+	rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n"));
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    rpmlog(
8b6ae9
+	RPMLOG_DEBUG,
8b6ae9
+	_("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"),
8b6ae9
+	table_start, state->keys, state->keysize
8b6ae9
+    );
8b6ae9
+    /* now get digest table if there is a reason to have one. */
8b6ae9
+    if (state->keys == 0 || state->keysize == 0) {
8b6ae9
+	/* no files (or no digests(!)) */
8b6ae9
+	state->table = NULL;
8b6ae9
+    } else {
8b6ae9
+	int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t));
8b6ae9
+	state->table = rcalloc(1, table_size);
8b6ae9
+	if (Fread(state->table, table_size, 1, state->fd) != table_size) {
8b6ae9
+	    rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n"));
8b6ae9
+	    return RPMRC_FAIL;
8b6ae9
+	}
8b6ae9
+	state->inodeIndexes = inodeIndexHashCreate(
8b6ae9
+	    state->keys, inodeId, inodeCmp, NULL, NULL
8b6ae9
+	);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    /* Seek back to original location.
8b6ae9
+     * Might not be needed if we seek to offset immediately
8b6ae9
+     */
8b6ae9
+    if (Fseek(state->fd, current, SEEK_SET) < 0) {
8b6ae9
+	rpmlog(RPMLOG_ERR,
8b6ae9
+	       _("reflink: unable to seek back to original location\n"));
8b6ae9
+	return RPMRC_FAIL;
8b6ae9
+    }
8b6ae9
+    return RPMRC_OK;
8b6ae9
+}
8b6ae9
+
8b6ae9
+static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res)
8b6ae9
+{
8b6ae9
+    reflink_state state = rpmPluginGetData(plugin);
8b6ae9
+    state->files = rpmfilesFree(state->files);
8b6ae9
+    if (state->table) {
8b6ae9
+	free(state->table);
8b6ae9
+	state->table = NULL;
8b6ae9
+    }
8b6ae9
+    if (state->inodeIndexes) {
8b6ae9
+	inodeIndexHashFree(state->inodeIndexes);
8b6ae9
+	state->inodeIndexes = NULL;
8b6ae9
+    }
8b6ae9
+    return RPMRC_OK;
8b6ae9
+}
8b6ae9
+
8b6ae9
+
8b6ae9
+/* have a prototype, warnings system */
8b6ae9
+rpm_loff_t find(const unsigned char *digest, reflink_state state);
8b6ae9
+
8b6ae9
+rpm_loff_t find(const unsigned char *digest, reflink_state state) {
8b6ae9
+# if defined(__GNUC__)
8b6ae9
+    /* GCC nested function because bsearch's comparison function can't access
8b6ae9
+     * state-keysize otherwise
8b6ae9
+     */
8b6ae9
+    int cmpdigest(const void *k1, const void *k2) {
8b6ae9
+	rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2);
8b6ae9
+	return memcmp(k1, k2, state->keysize);
8b6ae9
+    }
8b6ae9
+# endif
8b6ae9
+    rpmlog(RPMLOG_DEBUG,
8b6ae9
+	   _("reflink: bsearch(key=%p, base=%p, nmemb=%d, size=%lu)\n"),
8b6ae9
+	   digest, state->table, state->keys,
8b6ae9
+	   state->keysize + sizeof(rpm_loff_t));
8b6ae9
+    char *entry = bsearch(digest, state->table, state->keys,
8b6ae9
+			  state->keysize + sizeof(rpm_loff_t), cmpdigest);
8b6ae9
+    if (entry == NULL) {
8b6ae9
+	return NOT_FOUND;
8b6ae9
+    }
8b6ae9
+    rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize);
8b6ae9
+    return offset;
8b6ae9
+}
8b6ae9
+
8b6ae9
+static rpmRC reflink_fsm_file_pre(rpmPlugin plugin, rpmfi fi, const char* path,
8b6ae9
+                                  mode_t file_mode, rpmFsmOp op)
8b6ae9
+{
8b6ae9
+    struct file_clone_range fcr;
8b6ae9
+    rpm_loff_t size;
8b6ae9
+    int dst, rc;
8b6ae9
+    int *hlix;
8b6ae9
+
8b6ae9
+    reflink_state state = rpmPluginGetData(plugin);
8b6ae9
+    if (state->table == NULL) {
8b6ae9
+	/* no table means rpm is not in reflink format, so leave. Now. */
8b6ae9
+	return RPMRC_OK;
8b6ae9
+    }
8b6ae9
+    if (op == FA_TOUCH) {
8b6ae9
+	/* we're not overwriting an existing file. */
8b6ae9
+	return RPMRC_OK;
8b6ae9
+    }
8b6ae9
+    fcr.dest_offset = 0;
8b6ae9
+    if (S_ISREG(file_mode) && !(rpmfiFFlags(fi) & RPMFILE_GHOST)) {
8b6ae9
+	rpm_ino_t inode = rpmfiFInode(fi);
8b6ae9
+	/* check for hard link entry in table. GetEntry overwrites hlix with
8b6ae9
+	 * the address of the first match.
8b6ae9
+	 */
8b6ae9
+	if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hlix, NULL,
8b6ae9
+	                           NULL)) {
8b6ae9
+	    /* entry is in table, use hard link */
8b6ae9
+	    char *fn = rpmfilesFN(state->files, hlix[0]);
8b6ae9
+	    if (link(fn, path) != 0) {
8b6ae9
+		rpmlog(RPMLOG_ERR,
8b6ae9
+		       _("reflink: Unable to hard link %s -> %s due to %s\n"),
8b6ae9
+		       fn, path, strerror(errno));
8b6ae9
+		free(fn);
8b6ae9
+		return RPMRC_FAIL;
8b6ae9
+	    }
8b6ae9
+	    free(fn);
8b6ae9
+	    return RPMRC_PLUGIN_CONTENTS;
8b6ae9
+	}
8b6ae9
+	/* if we didn't hard link, then we'll track this inode as being
8b6ae9
+	 * created soon
8b6ae9
+	 */
8b6ae9
+	if (rpmfiFNlink(fi) > 1) {
8b6ae9
+	    /* minor optimization: only store files with more than one link */
8b6ae9
+	    inodeIndexHashAddEntry(state->inodeIndexes, inode, rpmfiFX(fi));
8b6ae9
+	}
8b6ae9
+	/* derived from wfd_open in fsm.c */
8b6ae9
+	mode_t old_umask = umask(0577);
8b6ae9
+	dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR);
8b6ae9
+	umask(old_umask);
8b6ae9
+	if (dst == -1) {
8b6ae9
+	    rpmlog(RPMLOG_ERR,
8b6ae9
+		   _("reflink: Unable to open %s for writing due to %s, flags = %x\n"),
8b6ae9
+		   path, strerror(errno), rpmfiFFlags(fi));
8b6ae9
+	    return RPMRC_FAIL;
8b6ae9
+	}
8b6ae9
+	size = rpmfiFSize(fi);
8b6ae9
+	if (size > 0) {
8b6ae9
+	    /* round src_length down to fundamental_block_size multiple */
8b6ae9
+	    fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size;
8b6ae9
+	    if ((size % state->fundamental_block_size) > 0) {
8b6ae9
+		/* round up to next fundamental_block_size. We expect the data
8b6ae9
+		 * in the rpm to be similarly padded.
8b6ae9
+		 */
8b6ae9
+		fcr.src_length += state->fundamental_block_size;
8b6ae9
+	    }
8b6ae9
+	    fcr.src_fd = Fileno(state->fd);
8b6ae9
+	    if (fcr.src_fd == -1) {
8b6ae9
+		close(dst);
8b6ae9
+		rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n"));
8b6ae9
+		return RPMRC_FAIL;
8b6ae9
+	    }
8b6ae9
+	    fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state);
8b6ae9
+	    if (fcr.src_offset == NOT_FOUND) {
8b6ae9
+		close(dst);
8b6ae9
+		rpmlog(RPMLOG_ERR, _("reflink: digest not found\n"));
8b6ae9
+		return RPMRC_FAIL;
8b6ae9
+	    }
8b6ae9
+	    rpmlog(RPMLOG_DEBUG,
8b6ae9
+	           _("reflink: Reflinking %llu bytes at %llu to %s orig size=%ld, file=%lld\n"),
8b6ae9
+		   fcr.src_length, fcr.src_offset, path, size, fcr.src_fd);
8b6ae9
+	    rc = ioctl(dst, FICLONERANGE, &fcr;;
8b6ae9
+	    if (rc) {
8b6ae9
+		rpmlog(RPMLOG_WARNING,
8b6ae9
+		       _("reflink: falling back to copying bits for %s due to %d, %d = %s\n"),
8b6ae9
+		       path, rc, errno, strerror(errno));
8b6ae9
+		if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) {
8b6ae9
+		    close(dst);
8b6ae9
+		    rpmlog(RPMLOG_ERR,
8b6ae9
+			   _("reflink: unable to seek on copying bits\n"));
8b6ae9
+		    return RPMRC_FAIL;
8b6ae9
+		}
8b6ae9
+		rpm_loff_t left = size;
8b6ae9
+		size_t len, read, written;
8b6ae9
+		while (left) {
8b6ae9
+		    len = (left > BUFFER_SIZE ? BUFFER_SIZE : left);
8b6ae9
+		    read = Fread(state->buffer, len, 1, state->fd);
8b6ae9
+		    if (read != len) {
8b6ae9
+			close(dst);
8b6ae9
+			rpmlog(RPMLOG_ERR,
8b6ae9
+			       _("reflink: short read on copying bits\n"));
8b6ae9
+			return RPMRC_FAIL;
8b6ae9
+		    }
8b6ae9
+		    written = write(dst, state->buffer, len);
8b6ae9
+		    if (read != written) {
8b6ae9
+			close(dst);
8b6ae9
+			rpmlog(RPMLOG_ERR,
8b6ae9
+			       _("reflink: short write on copying bits\n"));
8b6ae9
+			return RPMRC_FAIL;
8b6ae9
+		    }
8b6ae9
+		    left -= len;
8b6ae9
+		}
8b6ae9
+	    } else {
8b6ae9
+		/* reflink worked, so truncate */
8b6ae9
+		rc = ftruncate(dst, size);
8b6ae9
+		if (rc) {
8b6ae9
+		    rpmlog(RPMLOG_ERR,
8b6ae9
+			   _("reflink: Unable to truncate %s to %ld due to %s\n"),
8b6ae9
+			   path, size, strerror(errno));
8b6ae9
+		     return RPMRC_FAIL;
8b6ae9
+		}
8b6ae9
+	    }
8b6ae9
+	}
8b6ae9
+	close(dst);
8b6ae9
+	return RPMRC_PLUGIN_CONTENTS;
8b6ae9
+    }
8b6ae9
+    return RPMRC_OK;
8b6ae9
+}
8b6ae9
+
8b6ae9
+struct rpmPluginHooks_s reflink_hooks = {
8b6ae9
+    .init = reflink_init,
8b6ae9
+    .cleanup = reflink_cleanup,
8b6ae9
+    .psm_pre = reflink_psm_pre,
8b6ae9
+    .psm_post = reflink_psm_post,
8b6ae9
+    .fsm_file_pre = reflink_fsm_file_pre,
8b6ae9
+};
8b6ae9
diff --git a/rpm2extents.c b/rpm2extents.c
8b6ae9
new file mode 100644
8b6ae9
index 000000000..c111be0a2
8b6ae9
--- /dev/null
8b6ae9
+++ b/rpm2extents.c
8b6ae9
@@ -0,0 +1,433 @@
8b6ae9
+/* rpm2extents: convert payload to inline extents */
8b6ae9
+
8b6ae9
+#include "system.h"
8b6ae9
+
8b6ae9
+#include <rpm/rpmlib.h>		/* rpmReadPackageFile .. */
8b6ae9
+#include <rpm/rpmfi.h>
8b6ae9
+#include <rpm/rpmtag.h>
8b6ae9
+#include <rpm/rpmio.h>
8b6ae9
+#include <rpm/rpmpgp.h>
8b6ae9
+
8b6ae9
+#include <rpm/rpmts.h>
8b6ae9
+#include "lib/rpmlead.h"
8b6ae9
+#include "lib/signature.h"
8b6ae9
+#include "lib/header_internal.h"
8b6ae9
+#include "rpmio/rpmio_internal.h"
8b6ae9
+
8b6ae9
+#include <unistd.h>
8b6ae9
+#include <sys/types.h>
8b6ae9
+#include <sys/wait.h>
8b6ae9
+#include <signal.h>
8b6ae9
+#include <errno.h>
8b6ae9
+#include <string.h>
8b6ae9
+
8b6ae9
+#include "debug.h"
8b6ae9
+
8b6ae9
+/* hash of void * (pointers) to file digests to offsets within output.
8b6ae9
+ * The length of the key depends on what the FILEDIGESTALGO is.
8b6ae9
+ */
8b6ae9
+#undef HASHTYPE
8b6ae9
+#undef HTKEYTYPE
8b6ae9
+#undef HTDATATYPE
8b6ae9
+#define HASHTYPE digestSet
8b6ae9
+#define HTKEYTYPE const unsigned char *
8b6ae9
+#include "lib/rpmhash.H"
8b6ae9
+#include "lib/rpmhash.C"
8b6ae9
+
8b6ae9
+/* magic value at end of file (64 bits) that indicates this is a transcoded
8b6ae9
+ * rpm.
8b6ae9
+ */
8b6ae9
+#define MAGIC 3472329499408095051
8b6ae9
+
8b6ae9
+struct digestoffset {
8b6ae9
+    const unsigned char * digest;
8b6ae9
+    rpm_loff_t pos;
8b6ae9
+};
8b6ae9
+
8b6ae9
+rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit);
8b6ae9
+
8b6ae9
+rpm_loff_t pad_to(rpm_loff_t pos, rpm_loff_t unit)
8b6ae9
+{
8b6ae9
+    return (unit - (pos % unit)) % unit;
8b6ae9
+}
8b6ae9
+
8b6ae9
+static int digestor(
8b6ae9
+    FD_t fdi,
8b6ae9
+    FD_t fdo,
8b6ae9
+    FD_t validationo,
8b6ae9
+    uint8_t algos[],
8b6ae9
+    uint32_t algos_len
8b6ae9
+)
8b6ae9
+{
8b6ae9
+    ssize_t fdilength;
8b6ae9
+    const char *filedigest, *algo_name;
8b6ae9
+    size_t filedigest_len, len;
8b6ae9
+    uint32_t algo_name_len, algo_digest_len;
8b6ae9
+    int algo;
8b6ae9
+    rpmRC rc = RPMRC_FAIL;
8b6ae9
+
8b6ae9
+    for (algo = 0; algo < algos_len; algo++) {
8b6ae9
+	fdInitDigest(fdi, algos[algo], 0);
8b6ae9
+    }
8b6ae9
+    fdilength = ufdCopy(fdi, fdo);
8b6ae9
+    if (fdilength == -1) {
8b6ae9
+	fprintf(stderr, _("digest cat failed\n"));
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    len = sizeof(fdilength);
8b6ae9
+    if (Fwrite(&fdilength, len, 1, validationo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write input length %zd\n"), fdilength);
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    len = sizeof(algos_len);
8b6ae9
+    if (Fwrite(&algos_len, len, 1, validationo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write number of validation digests\n"));
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    for (algo = 0; algo < algos_len; algo++) {
8b6ae9
+	fdFiniDigest(fdi, algos[algo], (void **)&filedigest, &filedigest_len, 0);
8b6ae9
+
8b6ae9
+	algo_name = pgpValString(PGPVAL_HASHALGO, algos[algo]);
8b6ae9
+	algo_name_len = (uint32_t)strlen(algo_name);
8b6ae9
+	algo_digest_len = (uint32_t)filedigest_len;
8b6ae9
+
8b6ae9
+	len = sizeof(algo_name_len);
8b6ae9
+	if (Fwrite(&algo_name_len, len, 1, validationo) != len) {
8b6ae9
+	    fprintf(stderr,
8b6ae9
+		    _("Unable to write validation algo name length\n"));
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+	len = sizeof(algo_digest_len);
8b6ae9
+	if (Fwrite(&algo_digest_len, len, 1, validationo) != len) {
8b6ae9
+	    fprintf(stderr,
8b6ae9
+		    _("Unable to write number of bytes for validation digest\n"));
8b6ae9
+	     goto exit;
8b6ae9
+	}
8b6ae9
+	if (Fwrite(algo_name, algo_name_len, 1, validationo) != algo_name_len) {
8b6ae9
+	    fprintf(stderr, _("Unable to write validation algo name\n"));
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+	if (Fwrite(filedigest, algo_digest_len, 1, validationo ) != algo_digest_len) {
8b6ae9
+	    fprintf(stderr,
8b6ae9
+		    _("Unable to write validation digest value %u, %zu\n"),
8b6ae9
+		    algo_digest_len, filedigest_len);
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+    }
8b6ae9
+    rc = RPMRC_OK;
8b6ae9
+exit:
8b6ae9
+    return rc;
8b6ae9
+}
8b6ae9
+
8b6ae9
+static rpmRC process_package(FD_t fdi, FD_t validationi)
8b6ae9
+{
8b6ae9
+    uint32_t diglen;
8b6ae9
+    /* GNU C extension: can use diglen from outer context */
8b6ae9
+    int digestSetCmp(const unsigned char * a, const unsigned char * b) {
8b6ae9
+	return memcmp(a, b, diglen);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    unsigned int digestSetHash(const unsigned char * digest) {
8b6ae9
+        /* assumes sizeof(unsigned int) < diglen */
8b6ae9
+        return *(unsigned int *)digest;
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    int digestoffsetCmp(const void * a, const void * b) {
8b6ae9
+	return digestSetCmp(
8b6ae9
+	    ((struct digestoffset *)a)->digest,
8b6ae9
+	    ((struct digestoffset *)b)->digest
8b6ae9
+	);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    FD_t fdo;
8b6ae9
+    FD_t gzdi;
8b6ae9
+    Header h, sigh;
8b6ae9
+    long fundamental_block_size = sysconf(_SC_PAGESIZE);
8b6ae9
+    rpmRC rc = RPMRC_OK;
8b6ae9
+    rpm_mode_t mode;
8b6ae9
+    char *rpmio_flags = NULL, *zeros;
8b6ae9
+    const unsigned char *digest;
8b6ae9
+    rpm_loff_t pos, size, pad, validation_pos;
8b6ae9
+    uint32_t offset_ix = 0;
8b6ae9
+    size_t len;
8b6ae9
+    int next = 0;
8b6ae9
+
8b6ae9
+    fdo = fdDup(STDOUT_FILENO);
8b6ae9
+
8b6ae9
+    if (rpmReadPackageRaw(fdi, &sigh, &h)) {
8b6ae9
+	fprintf(stderr, _("Error reading package\n"));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    if (rpmLeadWrite(fdo, h))
8b6ae9
+    {
8b6ae9
+	fprintf(stderr, _("Unable to write package lead: %s\n"),
8b6ae9
+		Fstrerror(fdo));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    if (rpmWriteSignature(fdo, sigh)) {
8b6ae9
+	fprintf(stderr, _("Unable to write signature: %s\n"), Fstrerror(fdo));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    if (headerWrite(fdo, h, HEADER_MAGIC_YES)) {
8b6ae9
+	fprintf(stderr, _("Unable to write headers: %s\n"), Fstrerror(fdo));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    /* Retrieve payload size and compression type. */
8b6ae9
+    {
8b6ae9
+	const char *compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
8b6ae9
+	rpmio_flags = rstrscat(NULL, "r.", compr ? compr : "gzip", NULL);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    gzdi = Fdopen(fdi, rpmio_flags);	/* XXX gzdi == fdi */
8b6ae9
+    free(rpmio_flags);
8b6ae9
+
8b6ae9
+    if (gzdi == NULL) {
8b6ae9
+	fprintf(stderr, _("cannot re-open payload: %s\n"), Fstrerror(gzdi));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    rpmfiles files = rpmfilesNew(NULL, h, 0, RPMFI_KEEPHEADER);
8b6ae9
+    rpmfi fi = rpmfiNewArchiveReader(gzdi, files,
8b6ae9
+				     RPMFI_ITER_READ_ARCHIVE_CONTENT_FIRST);
8b6ae9
+
8b6ae9
+    /* this is encoded in the file format, so needs to be fixed size (for
8b6ae9
+     * now?)
8b6ae9
+     */
8b6ae9
+    diglen = (uint32_t)rpmDigestLength(rpmfiDigestAlgo(fi));
8b6ae9
+    digestSet ds = digestSetCreate(rpmfiFC(fi), digestSetHash, digestSetCmp,
8b6ae9
+				   NULL);
8b6ae9
+    struct digestoffset offsets[rpmfiFC(fi)];
8b6ae9
+    pos = RPMLEAD_SIZE + headerSizeof(sigh, HEADER_MAGIC_YES);
8b6ae9
+
8b6ae9
+    /* main headers are aligned to 8 byte boundry */
8b6ae9
+    pos += pad_to(pos, 8);
8b6ae9
+    pos += headerSizeof(h, HEADER_MAGIC_YES);
8b6ae9
+
8b6ae9
+    zeros = xcalloc(fundamental_block_size, 1);
8b6ae9
+
8b6ae9
+    while (next >= 0) {
8b6ae9
+	next = rpmfiNext(fi);
8b6ae9
+	if (next == RPMERR_ITER_END) {
8b6ae9
+	    rc = RPMRC_OK;
8b6ae9
+	    break;
8b6ae9
+	}
8b6ae9
+	mode = rpmfiFMode(fi);
8b6ae9
+	if (!S_ISREG(mode) || !rpmfiArchiveHasContent(fi)) {
8b6ae9
+	    /* not a regular file, or the archive doesn't contain any content
8b6ae9
+	     * for this entry.
8b6ae9
+	    */
8b6ae9
+	    continue;
8b6ae9
+	}
8b6ae9
+	digest = rpmfiFDigest(fi, NULL, NULL);
8b6ae9
+	if (digestSetGetEntry(ds, digest, NULL)) {
8b6ae9
+	    /* This specific digest has already been included, so skip it. */
8b6ae9
+	    continue;
8b6ae9
+	}
8b6ae9
+	pad = pad_to(pos, fundamental_block_size);
8b6ae9
+	if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
8b6ae9
+	    fprintf(stderr, _("Unable to write padding\n"));
8b6ae9
+	    rc = RPMRC_FAIL;
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+	/* round up to next fundamental_block_size */
8b6ae9
+	pos += pad;
8b6ae9
+	digestSetAddEntry(ds, digest);
8b6ae9
+	offsets[offset_ix].digest = digest;
8b6ae9
+	offsets[offset_ix].pos = pos;
8b6ae9
+	offset_ix++;
8b6ae9
+	size = rpmfiFSize(fi);
8b6ae9
+	rc = rpmfiArchiveReadToFile(fi, fdo, 0);
8b6ae9
+	if (rc != RPMRC_OK) {
8b6ae9
+	    fprintf(stderr, _("rpmfiArchiveReadToFile failed with %d\n"), rc);
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+	pos += size;
8b6ae9
+    }
8b6ae9
+    Fclose(gzdi);	/* XXX gzdi == fdi */
8b6ae9
+
8b6ae9
+    qsort(offsets, (size_t)offset_ix, sizeof(struct digestoffset),
8b6ae9
+	  digestoffsetCmp);
8b6ae9
+
8b6ae9
+    len = sizeof(offset_ix);
8b6ae9
+    if (Fwrite(&offset_ix, len, 1, fdo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write length of table\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    len = sizeof(diglen);
8b6ae9
+    if (Fwrite(&diglen, len, 1, fdo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write length of digest\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    len = sizeof(rpm_loff_t);
8b6ae9
+    for (int x = 0; x < offset_ix; x++) {
8b6ae9
+	if (Fwrite(offsets[x].digest, diglen, 1, fdo) != diglen) {
8b6ae9
+	    fprintf(stderr, _("Unable to write digest\n"));
8b6ae9
+	    rc = RPMRC_FAIL;
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+	if (Fwrite(&offsets[x].pos, len, 1, fdo) != len) {
8b6ae9
+	    fprintf(stderr, _("Unable to write offset\n"));
8b6ae9
+	    rc = RPMRC_FAIL;
8b6ae9
+	    goto exit;
8b6ae9
+	}
8b6ae9
+    }
8b6ae9
+    validation_pos = (
8b6ae9
+	pos + sizeof(offset_ix) + sizeof(diglen) +
8b6ae9
+	offset_ix * (diglen + sizeof(rpm_loff_t))
8b6ae9
+    );
8b6ae9
+
8b6ae9
+    ssize_t validation_len = ufdCopy(validationi, fdo);
8b6ae9
+    if (validation_len == -1) {
8b6ae9
+	fprintf(stderr, _("digest table ufdCopy failed\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    /* add more padding so the last file can be cloned. It doesn't matter that
8b6ae9
+     * the table and validation etc are in this space. In fact, it's pretty
8b6ae9
+     * efficient if it is.
8b6ae9
+    */
8b6ae9
+
8b6ae9
+    pad = pad_to((validation_pos + validation_len + 2 * sizeof(rpm_loff_t) +
8b6ae9
+		 sizeof(uint64_t)), fundamental_block_size);
8b6ae9
+    if (Fwrite(zeros, sizeof(char), pad, fdo) != pad) {
8b6ae9
+	fprintf(stderr, _("Unable to write final padding\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    zeros = _free(zeros);
8b6ae9
+    if (Fwrite(&pos, len, 1, fdo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write offset of digest table\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    if (Fwrite(&validation_pos, len, 1, fdo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write offset of validation table\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+    uint64_t magic = MAGIC;
8b6ae9
+    len = sizeof(magic);
8b6ae9
+    if (Fwrite(&magic, len, 1, fdo) != len) {
8b6ae9
+	fprintf(stderr, _("Unable to write magic\n"));
8b6ae9
+	rc = RPMRC_FAIL;
8b6ae9
+	goto exit;
8b6ae9
+    }
8b6ae9
+
8b6ae9
+exit:
8b6ae9
+    rpmfilesFree(files);
8b6ae9
+    rpmfiFree(fi);
8b6ae9
+    headerFree(h);
8b6ae9
+    return rc;
8b6ae9
+}
8b6ae9
+
8b6ae9
+int main(int argc, char *argv[]) {
8b6ae9
+    rpmRC rc;
8b6ae9
+    int cprc = 0;
8b6ae9
+    uint8_t algos[argc - 1];
8b6ae9
+    int mainpipefd[2];
8b6ae9
+    int metapipefd[2];
8b6ae9
+    pid_t cpid, w;
8b6ae9
+    int wstatus;
8b6ae9
+
8b6ae9
+    xsetprogname(argv[0]);	/* Portability call -- see system.h */
8b6ae9
+    rpmReadConfigFiles(NULL, NULL);
8b6ae9
+
8b6ae9
+    if (argc > 1 && (rstreq(argv[1], "-h") || rstreq(argv[1], "--help"))) {
8b6ae9
+	fprintf(stderr, _("Usage: %s [DIGESTALGO]...\n"), argv[0]);
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    if (argc == 1) {
8b6ae9
+	fprintf(stderr,
8b6ae9
+		_("Need at least one DIGESTALGO parameter, e.g. 'SHA256'\n"));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+
8b6ae9
+    for (int x = 0; x < (argc - 1); x++) {
8b6ae9
+	if (pgpStringVal(PGPVAL_HASHALGO, argv[x + 1], &algos[x]) != 0)
8b6ae9
+	{
8b6ae9
+	    fprintf(stderr,
8b6ae9
+		    _("Unable to resolve '%s' as a digest algorithm, exiting\n"),
8b6ae9
+		    argv[x + 1]);
8b6ae9
+	    exit(EXIT_FAILURE);
8b6ae9
+	}
8b6ae9
+    }
8b6ae9
+
8b6ae9
+
8b6ae9
+    if (pipe(mainpipefd) == -1) {
8b6ae9
+	fprintf(stderr, _("Main pipe failure\n"));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+    if (pipe(metapipefd) == -1) {
8b6ae9
+	fprintf(stderr, _("Meta pipe failure\n"));
8b6ae9
+	exit(EXIT_FAILURE);
8b6ae9
+    }
8b6ae9
+    cpid = fork();
8b6ae9
+    if (cpid == 0) {
8b6ae9
+	/* child: digestor */
8b6ae9
+	close(mainpipefd[0]);
8b6ae9
+	close(metapipefd[0]);
8b6ae9
+	FD_t fdi = fdDup(STDIN_FILENO);
8b6ae9
+	FD_t fdo = fdDup(mainpipefd[1]);
8b6ae9
+	FD_t validationo = fdDup(metapipefd[1]);
8b6ae9
+	rc = digestor(fdi, fdo, validationo, algos, argc - 1);
8b6ae9
+	Fclose(validationo);
8b6ae9
+	Fclose(fdo);
8b6ae9
+	Fclose(fdi);
8b6ae9
+    } else {
8b6ae9
+	/* parent: main program */
8b6ae9
+	close(mainpipefd[1]);
8b6ae9
+	close(metapipefd[1]);
8b6ae9
+	FD_t fdi = fdDup(mainpipefd[0]);
8b6ae9
+	FD_t validationi = fdDup(metapipefd[0]);
8b6ae9
+	rc = process_package(fdi, validationi);
8b6ae9
+	Fclose(validationi);
8b6ae9
+	/* fdi is normally closed through the stacked file gzdi in the
8b6ae9
+	 * function.
8b6ae9
+	 * Wait for child process (digestor for stdin) to complete.
8b6ae9
+	 */
8b6ae9
+	if (rc != RPMRC_OK) {
8b6ae9
+	    if (kill(cpid, SIGTERM) != 0) {
8b6ae9
+		fprintf(stderr,
8b6ae9
+		        _("Failed to kill digest process when main process failed: %s\n"),
8b6ae9
+			strerror(errno));
8b6ae9
+	    }
8b6ae9
+	}
8b6ae9
+	w = waitpid(cpid, &wstatus, 0);
8b6ae9
+	if (w == -1) {
8b6ae9
+	    fprintf(stderr, _("waitpid failed\n"));
8b6ae9
+	    cprc = EXIT_FAILURE;
8b6ae9
+	} else if (WIFEXITED(wstatus)) {
8b6ae9
+	    cprc = WEXITSTATUS(wstatus);
8b6ae9
+	    if (cprc != 0) {
8b6ae9
+		fprintf(stderr,
8b6ae9
+			_("Digest process non-zero exit code %d\n"),
8b6ae9
+			cprc);
8b6ae9
+	    }
8b6ae9
+	} else if (WIFSIGNALED(wstatus)) {
8b6ae9
+	    fprintf(stderr,
8b6ae9
+		    _("Digest process was terminated with a signal: %d\n"),
8b6ae9
+		    WTERMSIG(wstatus));
8b6ae9
+	    cprc = EXIT_FAILURE;
8b6ae9
+	} else {
8b6ae9
+	    /* Don't think this can happen, but covering all bases */
8b6ae9
+	    fprintf(stderr, _("Unhandled circumstance in waitpid\n"));
8b6ae9
+	    cprc = EXIT_FAILURE;
8b6ae9
+	}
8b6ae9
+	if (cprc != EXIT_SUCCESS) {
8b6ae9
+	    rc = RPMRC_FAIL;
8b6ae9
+	}
8b6ae9
+    }
8b6ae9
+    if (rc != RPMRC_OK) {
8b6ae9
+	/* translate rpmRC into generic failure return code. */
8b6ae9
+	return EXIT_FAILURE;
8b6ae9
+    }
8b6ae9
+    return EXIT_SUCCESS;
8b6ae9
+}
8b6ae9
diff --git a/rpmio/rpmpgp.c b/rpmio/rpmpgp.c
8b6ae9
index 015c15a5c..7b972b4a6 100644
8b6ae9
--- a/rpmio/rpmpgp.c
8b6ae9
+++ b/rpmio/rpmpgp.c
8b6ae9
@@ -283,6 +283,16 @@ int pgpValTok(pgpValTbl vs, const char * s, const char * se)
8b6ae9
     return vs->val;
8b6ae9
 }
8b6ae9
 
8b6ae9
+int pgpStringVal(pgpValType type, const char *str, uint8_t *val)
8b6ae9
+{
8b6ae9
+    pgpValTbl tbl = pgpValTable(type);
8b6ae9
+    if (tbl == NULL) return -1;
8b6ae9
+    int v = pgpValTok(tbl, str, str + strlen(str));
8b6ae9
+    if (v == -1) return -1;
8b6ae9
+    *val = (uint8_t)v;
8b6ae9
+    return 0;
8b6ae9
+}
8b6ae9
+
8b6ae9
 /** \ingroup rpmpgp
8b6ae9
  * Decode length from 1, 2, or 5 octet body length encoding, used in
8b6ae9
  * new format packet headers and V4 signature subpackets.
8b6ae9
diff --git a/rpmio/rpmpgp.h b/rpmio/rpmpgp.h
8b6ae9
index c53e29b01..2b57318ba 100644
8b6ae9
--- a/rpmio/rpmpgp.h
8b6ae9
+++ b/rpmio/rpmpgp.h
8b6ae9
@@ -973,6 +973,15 @@ typedef rpmFlags rpmDigestFlags;
8b6ae9
  */
8b6ae9
 const char * pgpValString(pgpValType type, uint8_t val);
8b6ae9
 
8b6ae9
+/** \ingroup rpmpgp
8b6ae9
+ * Return  OpenPGP value for a string.
8b6ae9
+ * @param type		type of value
8b6ae9
+ * @param str		string to lookup
8b6ae9
+ * @param[out] val  byte value associated with string
8b6ae9
+ * @return		0 on success else -1
8b6ae9
+ */
8b6ae9
+int pgpStringVal(pgpValType type, const char *str, uint8_t *val);
8b6ae9
+
8b6ae9
 /** \ingroup rpmpgp
8b6ae9
  * Return (native-endian) integer from big-endian representation.
8b6ae9
  * @param s		pointer to big-endian integer