diff --git a/0001-Add-optional-callback-on-directory-changes-during-rp.patch b/0001-Add-optional-callback-on-directory-changes-during-rp.patch
new file mode 100644
index 0000000..bda7110
--- /dev/null
+++ b/0001-Add-optional-callback-on-directory-changes-during-rp.patch
@@ -0,0 +1,107 @@
+From 186e0ab025b9ad92d900697f611633a6f6162f3b Mon Sep 17 00:00:00 2001
+From: Panu Matilainen <pmatilai@redhat.com>
+Date: Wed, 9 Feb 2022 14:47:14 +0200
+Subject: [PATCH] Add optional callback on directory changes during rpmfi
+ iteration
+
+Internal only for now in case we need to fiddle with the API some more,
+but no reason this couldn't be made public later.
+---
+ lib/rpmfi.c          | 24 ++++++++++++++++++++----
+ lib/rpmfi_internal.h | 17 +++++++++++++++++
+ 2 files changed, 37 insertions(+), 4 deletions(-)
+
+diff --git a/lib/rpmfi.c b/lib/rpmfi.c
+index aec8220a3..6c631fdb5 100644
+--- a/lib/rpmfi.c
++++ b/lib/rpmfi.c
+@@ -53,6 +53,9 @@ struct rpmfi_s {
+     int intervalStart;		/*!< Start of iterating interval. */
+     int intervalEnd;		/*!< End of iterating interval. */
+ 
++    rpmfiChdirCb onChdir;	/*!< Callback for directory changes */
++    void *onChdirData;		/*!< Caller private callback data */
++
+     rpmfiles files;		/*!< File info set */
+     rpmcpio_t archive;		/*!< Archive with payload */
+     unsigned char * found;	/*!< Bit field of files found in the archive */
+@@ -298,11 +301,16 @@ rpm_count_t rpmfiDC(rpmfi fi)
+     return (fi != NULL ? rpmfilesDC(fi->files) : 0);
+ }
+ 
+-#ifdef	NOTYET
+-int rpmfiDI(rpmfi fi)
++int rpmfiSetOnChdir(rpmfi fi, rpmfiChdirCb cb, void *data)
+ {
++    int rc = -1;
++    if (fi != NULL) {
++	fi->onChdir = cb;
++	fi->onChdirData = data;
++	rc = 0;
++    }
++    return rc;
+ }
+-#endif
+ 
+ int rpmfiFX(rpmfi fi)
+ {
+@@ -314,9 +322,17 @@ int rpmfiSetFX(rpmfi fi, int fx)
+     int i = -1;
+ 
+     if (fi != NULL && fx >= 0 && fx < rpmfilesFC(fi->files)) {
++	int dx = fi->j;
+ 	i = fi->i;
+ 	fi->i = fx;
+ 	fi->j = rpmfilesDI(fi->files, fi->i);
++	i = fi->i;
++
++	if (fi->j != dx && fi->onChdir) {
++	    int chrc = fi->onChdir(fi, fi->onChdirData);
++	    if (chrc < 0)
++		i = chrc;
++	}
+     }
+     return i;
+ }
+@@ -1682,9 +1698,9 @@ static rpmfi initIter(rpmfiles files, int itype, int link)
+     if (files && itype>=0 && itype<=RPMFILEITERMAX) {
+ 	fi = xcalloc(1, sizeof(*fi)); 
+ 	fi->i = -1;
++	fi->j = -1;
+ 	fi->files = link ? rpmfilesLink(files) : files;
+ 	fi->next = nextfuncs[itype];
+-	fi->i = -1;
+ 	if (itype == RPMFI_ITER_BACK) {
+ 	    fi->i = rpmfilesFC(fi->files);
+ 	} else if (itype >=RPMFI_ITER_READ_ARCHIVE
+diff --git a/lib/rpmfi_internal.h b/lib/rpmfi_internal.h
+index dccc6ccbe..37f1d45f5 100644
+--- a/lib/rpmfi_internal.h
++++ b/lib/rpmfi_internal.h
+@@ -13,6 +13,23 @@
+ extern "C" {
+ #endif
+ 
++/** \ingroup rpmfi
++ * Callback on file iterator directory changes
++ * @param fi		file info
++ * @param data		caller private callback data
++ * @return		0 on success, < 0 on error (to stop iteration)
++ */
++typedef int (*rpmfiChdirCb)(rpmfi fi, void *data);
++
++/** \ingroup rpmfi
++ * Set a callback for directory changes during iteration.
++ * @param fi		file info
++ * @param cb		callback function
++ * @param data		caller private callback data
++ * @return		string pool handle (weak reference)
++ */
++int rpmfiSetOnChdir(rpmfi fi, rpmfiChdirCb cb, void *data);
++
+ /** \ingroup rpmfi
+  * Return file info set string pool handle
+  * @param fi		file info
+-- 
+2.41.0
+
diff --git a/0001-Eliminate-code-duplication-from-rpmfiNext.patch b/0001-Eliminate-code-duplication-from-rpmfiNext.patch
new file mode 100644
index 0000000..a5e0463
--- /dev/null
+++ b/0001-Eliminate-code-duplication-from-rpmfiNext.patch
@@ -0,0 +1,35 @@
+From 0bc13d75b5883ccf4d6579f7a60fb1badd104649 Mon Sep 17 00:00:00 2001
+From: Panu Matilainen <pmatilai@redhat.com>
+Date: Thu, 10 Feb 2022 10:23:22 +0200
+Subject: [PATCH] Eliminate code duplication from rpmfiNext()
+
+Now that we can, let rpmfiSetFX() take care of the details.
+---
+ lib/rpmfi.c | 11 ++---------
+ 1 file changed, 2 insertions(+), 9 deletions(-)
+
+diff --git a/lib/rpmfi.c b/lib/rpmfi.c
+index 689ead2c5..aec8220a3 100644
+--- a/lib/rpmfi.c
++++ b/lib/rpmfi.c
+@@ -856,15 +856,8 @@ int rpmfiNext(rpmfi fi)
+ 	    next = fi->next(fi);
+ 	} while (next == RPMERR_ITER_SKIP);
+ 
+-	if (next >= 0 && next < rpmfilesFC(fi->files)) {
+-	    fi->i = next;
+-	    fi->j = rpmfilesDI(fi->files, fi->i);
+-	} else {
+-	    fi->i = -1;
+-	    if (next >= 0) {
+-		next = -1;
+-	    }
+-	}
++	if (next >= 0)
++	    next = rpmfiSetFX(fi, next);
+     }
+     return next;
+ }
+-- 
+2.41.0
+
diff --git a/0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch b/0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch
new file mode 100644
index 0000000..a1fbca4
--- /dev/null
+++ b/0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch
@@ -0,0 +1,158 @@
+From ac7b0dbd5a18d2c57a942ca14ac856b8047425ff Mon Sep 17 00:00:00 2001
+From: Panu Matilainen <pmatilai@redhat.com>
+Date: Tue, 15 Feb 2022 10:43:13 +0200
+Subject: [PATCH] Pass file descriptor to file prepare plugin hook, use when
+ possible
+
+Sadly the thing that allegedly makes things better mostly just makes
+things more complicated as symlinks can't be opened, so we'll now have
+to deal with both cases in plugins too. To make matters worse, most
+APIs out there support either an fd or a path, but very few support
+the *at() style dirfd + basename approach so plugins are stuck with
+absolute paths for now.
+
+This is of course a plugin API/ABI change too.
+---
+ lib/rpmplugin.h   |  2 +-
+ lib/rpmplugins.c  |  4 ++--
+ lib/rpmplugins.h  |  3 ++-
+ plugins/ima.c     |  9 +++++++--
+ plugins/selinux.c | 13 ++++++++-----
+ 5 files changed, 20 insertions(+), 11 deletions(-)
+
+diff --git a/lib/rpmplugin.h b/lib/rpmplugin.h
+index fd81aec8d..fab4b3e83 100644
+--- a/lib/rpmplugin.h
++++ b/lib/rpmplugin.h
+@@ -57,7 +57,7 @@ typedef rpmRC (*plugin_fsm_file_post_func)(rpmPlugin plugin, rpmfi fi,
+ 					   const char* path, mode_t file_mode,
+ 					   rpmFsmOp op, int res);
+ typedef rpmRC (*plugin_fsm_file_prepare_func)(rpmPlugin plugin, rpmfi fi,
+-					      const char* path,
++					      int fd, const char* path,
+ 					      const char *dest,
+ 					      mode_t file_mode, rpmFsmOp op);
+ 
+diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
+index 65e684e84..923084b78 100644
+--- a/lib/rpmplugins.c
++++ b/lib/rpmplugins.c
+@@ -384,7 +384,7 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char *path,
+ }
+ 
+ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
+-				   const char *path, const char *dest,
++				   int fd, const char *path, const char *dest,
+ 				   mode_t file_mode, rpmFsmOp op)
+ {
+     plugin_fsm_file_prepare_func hookFunc;
+@@ -394,7 +394,7 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
+     for (i = 0; i < plugins->count; i++) {
+ 	rpmPlugin plugin = plugins->plugins[i];
+ 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_prepare);
+-	if (hookFunc && hookFunc(plugin, fi, path, dest, file_mode, op) == RPMRC_FAIL) {
++	if (hookFunc && hookFunc(plugin, fi, fd, path, dest, file_mode, op) == RPMRC_FAIL) {
+ 	    rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_prepare failed\n", plugin->name);
+ 	    rc = RPMRC_FAIL;
+ 	}
+diff --git a/lib/rpmplugins.h b/lib/rpmplugins.h
+index 39762c376..ddf5d7048 100644
+--- a/lib/rpmplugins.h
++++ b/lib/rpmplugins.h
+@@ -156,6 +156,7 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char* path,
+  * permissions etc, but before committing file to destination path.
+  * @param plugins	plugins structure
+  * @param fi		file info iterator (or NULL)
++ * @param fd		file descriptor (or -1 if not available)
+  * @param path		file object current path
+  * @param dest		file object destination path
+  * @param mode		file object mode
+@@ -164,7 +165,7 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char* path,
+  */
+ RPM_GNUC_INTERNAL
+ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
+-                                   const char *path, const char *dest,
++                                   int fd, const char *path, const char *dest,
+                                    mode_t mode, rpmFsmOp op);
+ 
+ #ifdef __cplusplus
+diff --git a/plugins/ima.c b/plugins/ima.c
+index fe6d3ad7f..9c28a41a3 100644
+--- a/plugins/ima.c
++++ b/plugins/ima.c
+@@ -39,7 +39,7 @@ static int check_zero_hdr(const unsigned char *fsig, size_t siglen)
+ 	return (memcmp(fsig, &zero_hdr, sizeof(zero_hdr)) == 0);
+ }
+ 
+-static rpmRC ima_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
++static rpmRC ima_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, int fd,
+                                   const char *path,
+                                   const char *dest,
+                                   mode_t file_mode, rpmFsmOp op)
+@@ -68,8 +68,13 @@
+ 
+ 	fsig = rpmfiFSignature(fi, &len);
+ 	if (fsig && (check_zero_hdr(fsig, len) == 0)) {
+-	    if (lsetxattr(path, XATTR_NAME_IMA, fsig, len, 0) < 0) {
+-		int is_err = errno != EOPNOTSUPP; 
++	    int xx;
++	    if (fd >= 0)
++		xx = fsetxattr(fd, XATTR_NAME_IMA, fsig, len, 0);
++	    else
++		xx = lsetxattr(path, XATTR_NAME_IMA, fsig, len, 0);
++	    if (xx < 0) {
++		int is_err = xx != EOPNOTSUPP;
+ 	        rpmlog(is_err?RPMLOG_ERR:RPMLOG_DEBUG,
+ 			"ima: could not apply signature on '%s': %s\n",
+ 			path, strerror(errno));
+diff --git a/plugins/fapolicyd.c b/plugins/fapolicyd.c
+index 7ac44f0d0..1ff50c30f 100644
+--- a/plugins/fapolicyd.c
++++ b/plugins/fapolicyd.c
+@@ -145,7 +145,8 @@ static rpmRC fapolicyd_scriptlet_pre(rpmPlugin plugin, const char *s_name,
+ }
+ 
+ static rpmRC fapolicyd_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
+-                                        const char *path, const char *dest,
++                                        int fd, const char *path,
++					const char *dest,
+                                         mode_t file_mode, rpmFsmOp op)
+ {
+     /* not ready  */
+diff --git a/plugins/selinux.c b/plugins/selinux.c
+index 32c3b7529..a7f20aeca 100644
+--- a/plugins/selinux.c
++++ b/plugins/selinux.c
+@@ -149,7 +149,7 @@ static rpmRC selinux_scriptlet_fork_post(rpmPlugin plugin,
+     return rc;
+ }
+ 
+-static rpmRC selinux_fsm_file_prepare(rpmPlugin plugin, rpmfi fi,
++static rpmRC selinux_fsm_file_prepare(rpmPlugin plugin, rpmfi fi, int fd,
+ 					const char *path, const char *dest,
+ 				        mode_t file_mode, rpmFsmOp op)
+ {
+@@ -194,13 +194,17 @@
+     if (sehandle && !XFA_SKIPPING(action)) {
+ 	char *scon = NULL;
+ 	if (selabel_lookup_raw(sehandle, &scon, dest, file_mode) == 0) {
+-	    int conrc = lsetfilecon(path, scon);
++	    int conrc;
++	    if (fd >= 0)
++		conrc = fsetfilecon(fd, scon);
++	    else
++		conrc = lsetfilecon(path, scon);
+ 
+ 	    if (conrc == 0 || (conrc < 0 && errno == EOPNOTSUPP))
+ 		rc = RPMRC_OK;
+ 
+-	    rpmlog(loglvl(rc != RPMRC_OK), "lsetfilecon: (%s, %s) %s\n",
+-		       path, scon, (conrc < 0 ? strerror(errno) : ""));
++	    rpmlog(loglvl(rc != RPMRC_OK), "lsetfilecon: (%d %s, %s) %s\n",
++		   fd, path, scon, (conrc < 0 ? strerror(errno) : ""));
+ 
+ 	    freecon(scon);
+ 	} else {
+-- 
+2.41.0
+
diff --git a/0001-Swap-over-to-dirfd-basename-based-operation-within-t.patch b/0001-Swap-over-to-dirfd-basename-based-operation-within-t.patch
new file mode 100644
index 0000000..3210c07
--- /dev/null
+++ b/0001-Swap-over-to-dirfd-basename-based-operation-within-t.patch
@@ -0,0 +1,90 @@
+From 6dd62720fe84f7e2ad902c915b952fc0b29e3dcd Mon Sep 17 00:00:00 2001
+From: Panu Matilainen <pmatilai@redhat.com>
+Date: Tue, 15 Feb 2022 11:34:37 +0200
+Subject: [PATCH] Swap over to dirfd+basename based operation within the fsm
+
+Within fsm this is just a matter of adjusting error messages to include
+the directory... if it only wasn't for the plugins requiring absolute
+paths for outside users. For the plugins, we need to assemble absolute
+paths as needed, both in ensureDir() and plugin file slots.
+---
+ lib/rpmplugins.c | 20 +++++++++++++++++---
+ 2 files changed, 36 insertions(+), 14 deletions(-)
+
+diff --git a/lib/rpmplugins.c b/lib/rpmplugins.c
+index 703368c0d..f06fd7895 100644
+--- a/lib/rpmplugins.c
++++ b/lib/rpmplugins.c
+@@ -350,21 +350,31 @@ rpmRC rpmpluginsCallScriptletPost(rpmPlugins plugins, const char *s_name, int ty
+     return rc;
+ }
+ 
++static char *abspath(rpmfi fi, const char *path)
++{
++    if (*path == '/')
++	return xstrdup(path);
++    else
++	return rstrscat(NULL, rpmfiDN(fi), path, NULL);
++}
++
+ rpmRC rpmpluginsCallFsmFilePre(rpmPlugins plugins, rpmfi fi, const char *path,
+ 			       mode_t file_mode, rpmFsmOp op)
+ {
+     plugin_fsm_file_pre_func hookFunc;
+     int i;
+     rpmRC rc = RPMRC_OK;
++    char *apath = abspath(fi, path);
+ 
+     for (i = 0; i < plugins->count; i++) {
+ 	rpmPlugin plugin = plugins->plugins[i];
+ 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_pre);
+-	if (hookFunc && hookFunc(plugin, fi, path, file_mode, op) == RPMRC_FAIL) {
++	if (hookFunc && hookFunc(plugin, fi, apath, file_mode, op) == RPMRC_FAIL) {
+ 	    rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_pre failed\n", plugin->name);
+ 	    rc = RPMRC_FAIL;
+ 	}
+     }
++    free(apath);
+ 
+     return rc;
+ }
+@@ -375,14 +385,16 @@ rpmRC rpmpluginsCallFsmFilePost(rpmPlugins plugins, rpmfi fi, const char *path,
+     plugin_fsm_file_post_func hookFunc;
+     int i;
+     rpmRC rc = RPMRC_OK;
++    char *apath = abspath(fi, path);
+ 
+     for (i = 0; i < plugins->count; i++) {
+ 	rpmPlugin plugin = plugins->plugins[i];
+ 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_post);
+-	if (hookFunc && hookFunc(plugin, fi, path, file_mode, op, res) == RPMRC_FAIL) {
++	if (hookFunc && hookFunc(plugin, fi, apath, file_mode, op, res) == RPMRC_FAIL) {
+ 	    rpmlog(RPMLOG_WARNING, "Plugin %s: hook fsm_file_post failed\n", plugin->name);
+ 	}
+     }
++    free(apath);
+ 
+     return rc;
+ }
+@@ -394,15 +406,17 @@ rpmRC rpmpluginsCallFsmFilePrepare(rpmPlugins plugins, rpmfi fi,
+     plugin_fsm_file_prepare_func hookFunc;
+     int i;
+     rpmRC rc = RPMRC_OK;
++    char *apath = abspath(fi, path);
+ 
+     for (i = 0; i < plugins->count; i++) {
+ 	rpmPlugin plugin = plugins->plugins[i];
+ 	RPMPLUGINS_SET_HOOK_FUNC(fsm_file_prepare);
+-	if (hookFunc && hookFunc(plugin, fi, fd, path, dest, file_mode, op) == RPMRC_FAIL) {
++	if (hookFunc && hookFunc(plugin, fi, fd, apath, dest, file_mode, op) == RPMRC_FAIL) {
+ 	    rpmlog(RPMLOG_ERR, "Plugin %s: hook fsm_file_prepare failed\n", plugin->name);
+ 	    rc = RPMRC_FAIL;
+ 	}
+     }
++    free(apath);
+ 
+     return rc;
+ }
+-- 
+2.41.0
+
diff --git a/0001-Use-file-state-machine-from-rpm-4.19.patch b/0001-Use-file-state-machine-from-rpm-4.19.patch
new file mode 100644
index 0000000..2bb5dd9
--- /dev/null
+++ b/0001-Use-file-state-machine-from-rpm-4.19.patch
@@ -0,0 +1,1631 @@
+From 36ee14a07630668629a0d461fba8b5b2248d7d71 Mon Sep 17 00:00:00 2001
+From: Florian Festi <ffesti@redhat.com>
+Date: Tue, 10 Oct 2023 16:46:17 +0200
+Subject: [PATCH] Use file state machine from rpm-4.19
+
+This new implementation fixes several race conditions when placing down
+files on disc
+---
+ lib/fsm.c        | 1164 +++++++++++++++++++++++++---------------------
+ lib/rpmarchive.h |    3 +
+ lib/rpmfiles.h   |    3 +
+
+diff --git a/lib/rpmarchive.h b/lib/rpmarchive.h
+index c864e5b56..e5cda4f97 100644
+--- a/lib/rpmarchive.h
++++ b/lib/rpmarchive.h
+@@ -26,6 +26,8 @@ enum rpmfilesErrorCodes {
+ 	RPMERR_FILE_SIZE	= -12,
+ 	RPMERR_ITER_SKIP	= -13,
+ 	RPMERR_EXIST_AS_DIR	= -14,
++	RPMERR_INVALID_SYMLINK  = -15,
++	RPMERR_ENOTDIR          = -16,
+ 
+ 	RPMERR_OPEN_FAILED	= -32768,
+ 	RPMERR_CHMOD_FAILED	= -32769,
+@@ -47,6 +49,7 @@ enum rpmfilesErrorCodes {
+ 	RPMERR_COPY_FAILED	= -32785,
+ 	RPMERR_LSETFCON_FAILED	= -32786,
+ 	RPMERR_SETCAP_FAILED	= -32787,
++	RPMERR_CLOSE_FAILED     = -32788,
+ };
+ 
+ #ifdef __cplusplus
+diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h
+index daf572cf4..e74bb2201 100644
+--- a/lib/rpmfiles.h
++++ b/lib/rpmfiles.h
+@@ -90,6 +90,9 @@ typedef enum rpmFileAction_e {
+ #define XFA_SKIPPING(_a)	\
+     ((_a) == FA_SKIP || (_a) == FA_SKIPNSTATE || (_a) == FA_SKIPNETSHARED || (_a) == FA_SKIPCOLOR)
+ 
++#define XFA_CREATING(_a)	\
++    ((_a) == FA_CREATE || (_a) == FA_BACKUP || (_a) == FA_SAVE || (_a) == FA_ALTNAME)
++
+ /**
+  * We pass these around as an array with a sentinel.
+  */
+--- rpm-4.16.1.3/lib/fsm.c.orig	2023-11-11 10:05:19.208206675 +0100
++++ rpm-4.16.1.3/lib/fsm.c	2023-11-11 10:05:43.559432708 +0100
+@@ -5,9 +5,11 @@
+ 
+ #include "system.h"
+ 
++#include <inttypes.h>
+ #include <utime.h>
+ #include <errno.h>
+-#if WITH_CAP
++#include <fcntl.h>
++#ifdef WITH_CAP
+ #include <sys/capability.h>
+ #endif
+ 
+@@ -17,10 +19,11 @@
+ #include <rpm/rpmmacro.h>
+ 
+ #include "rpmio/rpmio_internal.h"	/* fdInit/FiniDigest */
+-#include "lib/fsm.h"
+-#include "lib/rpmte_internal.h"	/* XXX rpmfs */
+-#include "lib/rpmplugins.h"	/* rpm plugins hooks */
+-#include "lib/rpmug.h"
++#include "fsm.h"
++#include "rpmte_internal.h"	/* XXX rpmfs */
++#include "rpmfi_internal.h" /* rpmfiSetOnChdir */
++#include "rpmplugins.h"	/* rpm plugins hooks */
++#include "rpmug.h"
+ 
+ #include "debug.h"
+ 
+@@ -38,172 +41,92 @@
+ #define _dirPerms 0755
+ #define _filePerms 0644
+ 
++enum filestage_e {
++    FILE_COMMIT = -1,
++    FILE_NONE   = 0,
++    FILE_PRE    = 1,
++    FILE_UNPACK = 2,
++    FILE_PREP   = 3,
++    FILE_POST   = 4,
++};
++
++struct filedata_s {
++    int stage;
++    int setmeta;
++    int skip;
++    rpmFileAction action;
++    const char *suffix;
++    char *fpath;
++    struct stat sb;
++};
++
+ /* 
+  * XXX Forward declarations for previously exported functions to avoid moving 
+  * things around needlessly 
+  */ 
+ static const char * fileActionString(rpmFileAction a);
++static int fsmOpenat(int dirfd, const char *path, int flags, int dir);
++static int fsmClose(int *wfdp);
+ 
+ /** \ingroup payload
+  * Build path to file from file info, optionally ornamented with suffix.
++ * "/" needs special handling to avoid appearing as empty path.
+  * @param fi		file info iterator
+  * @param suffix	suffix to use (NULL disables)
+- * @retval		path to file (malloced)
++ * @param[out]		path to file (malloced)
+  */
+ static char * fsmFsPath(rpmfi fi, const char * suffix)
+ {
+-    return rstrscat(NULL, rpmfiDN(fi), rpmfiBN(fi), suffix ? suffix : "", NULL);
+-}
+-
+-/** \ingroup payload
+- * Directory name iterator.
+- */
+-typedef struct dnli_s {
+-    rpmfiles fi;
+-    char * active;
+-    int reverse;
+-    int isave;
+-    int i;
+-} * DNLI_t;
+-
+-/** \ingroup payload
+- * Destroy directory name iterator.
+- * @param dnli		directory name iterator
+- * @retval		NULL always
+- */
+-static DNLI_t dnlFreeIterator(DNLI_t dnli)
+-{
+-    if (dnli) {
+-	if (dnli->active) free(dnli->active);
+-	free(dnli);
+-    }
+-    return NULL;
++    const char *bn = rpmfiBN(fi);
++    return rstrscat(NULL, *bn ? bn : "/", suffix ? suffix : "", NULL);
+ }
+ 
+-/** \ingroup payload
+- * Create directory name iterator.
+- * @param fi		file info set
+- * @param fs		file state set
+- * @param reverse	traverse directory names in reverse order?
+- * @return		directory name iterator
+- */
+-static DNLI_t dnlInitIterator(rpmfiles fi, rpmfs fs, int reverse)
++static int fsmLink(int odirfd, const char *opath, int dirfd, const char *path)
+ {
+-    DNLI_t dnli;
+-    int i, j;
+-    int dc;
+-
+-    if (fi == NULL)
+-	return NULL;
+-    dc = rpmfilesDC(fi);
+-    dnli = xcalloc(1, sizeof(*dnli));
+-    dnli->fi = fi;
+-    dnli->reverse = reverse;
+-    dnli->i = (reverse ? dc : 0);
+-
+-    if (dc) {
+-	dnli->active = xcalloc(dc, sizeof(*dnli->active));
+-	int fc = rpmfilesFC(fi);
+-
+-	/* Identify parent directories not skipped. */
+-	for (i = 0; i < fc; i++)
+-            if (!XFA_SKIPPING(rpmfsGetAction(fs, i)))
+-		dnli->active[rpmfilesDI(fi, i)] = 1;
+-
+-	/* Exclude parent directories that are explicitly included. */
+-	for (i = 0; i < fc; i++) {
+-	    int dil;
+-	    size_t dnlen, bnlen;
++    int rc = linkat(odirfd, opath, dirfd, path, 0);
+ 
+-	    if (!S_ISDIR(rpmfilesFMode(fi, i)))
+-		continue;
+-
+-	    dil = rpmfilesDI(fi, i);
+-	    dnlen = strlen(rpmfilesDN(fi, dil));
+-	    bnlen = strlen(rpmfilesBN(fi, i));
+-
+-	    for (j = 0; j < dc; j++) {
+-		const char * dnl;
+-		size_t jlen;
+-
+-		if (!dnli->active[j] || j == dil)
+-		    continue;
+-		dnl = rpmfilesDN(fi, j);
+-		jlen = strlen(dnl);
+-		if (jlen != (dnlen+bnlen+1))
+-		    continue;
+-		if (!rstreqn(dnl, rpmfilesDN(fi, dil), dnlen))
+-		    continue;
+-		if (!rstreqn(dnl+dnlen, rpmfilesBN(fi, i), bnlen))
+-		    continue;
+-		if (dnl[dnlen+bnlen] != '/' || dnl[dnlen+bnlen+1] != '\0')
+-		    continue;
+-		/* This directory is included in the package. */
+-		dnli->active[j] = 0;
+-		break;
+-	    }
+-	}
+-
+-	/* Print only once per package. */
+-	if (!reverse) {
+-	    j = 0;
+-	    for (i = 0; i < dc; i++) {
+-		if (!dnli->active[i]) continue;
+-		if (j == 0) {
+-		    j = 1;
+-		    rpmlog(RPMLOG_DEBUG,
+-	"========== Directories not explicitly included in package:\n");
+-		}
+-		rpmlog(RPMLOG_DEBUG, "%10d %s\n", i, rpmfilesDN(fi, i));
+-	    }
+-	    if (j)
+-		rpmlog(RPMLOG_DEBUG, "==========\n");
+-	}
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__,
++	       odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : ""));
+     }
+-    return dnli;
++
++    if (rc < 0)
++	rc = RPMERR_LINK_FAILED;
++    return rc;
+ }
+ 
+-/** \ingroup payload
+- * Return next directory name (from file info).
+- * @param dnli		directory name iterator
+- * @return		next directory name
+- */
+-static
+-const char * dnlNextIterator(DNLI_t dnli)
++#ifdef WITH_CAP
++static int cap_set_fileat(int dirfd, const char *path, cap_t fcaps)
+ {
+-    const char * dn = NULL;
+-
+-    if (dnli) {
+-	rpmfiles fi = dnli->fi;
+-	int dc = rpmfilesDC(fi);
+-	int i = -1;
+-
+-	if (dnli->active)
+-	do {
+-	    i = (!dnli->reverse ? dnli->i++ : --dnli->i);
+-	} while (i >= 0 && i < dc && !dnli->active[i]);
+-
+-	if (i >= 0 && i < dc)
+-	    dn = rpmfilesDN(fi, i);
+-	else
+-	    i = -1;
+-	dnli->isave = i;
++    int rc = -1;
++    int fd = fsmOpenat(dirfd, path, O_RDONLY|O_NOFOLLOW, 0);
++    if (fd >= 0) {
++	rc = cap_set_fd(fd, fcaps);
++	fsmClose(&fd);
+     }
+-    return dn;
++    return rc;
+ }
++#endif
+ 
+-static int fsmSetFCaps(const char *path, const char *captxt)
++static int fsmSetFCaps(int fd, int dirfd, const char *path, const char *captxt)
+ {
+     int rc = 0;
+-#if WITH_CAP
++
++#ifdef WITH_CAP
+     if (captxt && *captxt != '\0') {
+ 	cap_t fcaps = cap_from_text(captxt);
+-	if (fcaps == NULL || cap_set_file(path, fcaps) != 0) {
+-	    rc = RPMERR_SETCAP_FAILED;
++
++	if (fd >= 0) {
++	    if (fcaps == NULL || cap_set_fd(fd, fcaps))
++		rc = RPMERR_SETCAP_FAILED;
++	} else {
++	    if (fcaps == NULL || cap_set_fileat(dirfd, path, fcaps))
++		rc = RPMERR_SETCAP_FAILED;
+ 	}
++
+ 	if (_fsm_debug) {
+-	    rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
+-		   path, captxt, (rc < 0 ? strerror(errno) : ""));
++	    rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %s) %s\n", __func__,
++		   fd, dirfd, path, captxt, (rc < 0 ? strerror(errno) : ""));
+ 	}
+ 	cap_free(fcaps);
+     } 
+@@ -211,101 +134,104 @@
+     return rc;
+ }
+ 
+-static void wfd_close(FD_t *wfdp)
++static int fsmClose(int *wfdp)
+ {
+-    if (wfdp && *wfdp) {
++    int rc = 0;
++    if (wfdp && *wfdp >= 0) {
+ 	int myerrno = errno;
+ 	static int oneshot = 0;
+ 	static int flush_io = 0;
++	int fdno = *wfdp;
++
+ 	if (!oneshot) {
+ 	    flush_io = (rpmExpandNumeric("%{?_flush_io}") > 0);
+ 	    oneshot = 1;
+ 	}
+ 	if (flush_io) {
+-	    int fdno = Fileno(*wfdp);
+ 	    fsync(fdno);
+ 	}
+-	Fclose(*wfdp);
+-	*wfdp = NULL;
++	if (close(fdno))
++	    rc = RPMERR_CLOSE_FAILED;
++
++	if (_fsm_debug) {
++	    rpmlog(RPMLOG_DEBUG, " %8s ([%d]) %s\n", __func__,
++		   fdno, (rc < 0 ? strerror(errno) : ""));
++	}
++	*wfdp = -1;
+ 	errno = myerrno;
+     }
++    return rc;
+ }
+ 
+-static int wfd_open(FD_t *wfdp, const char *dest)
++static int fsmOpen(int *wfdp, int dirfd, const char *dest)
+ {
+     int rc = 0;
+     /* Create the file with 0200 permissions (write by owner). */
+-    {
+-	mode_t old_umask = umask(0577);
+-	*wfdp = Fopen(dest, "wx.ufdio");
+-	umask(old_umask);
+-    }
+-    if (Ferror(*wfdp)) {
++    int fd = openat(dirfd, dest, O_WRONLY|O_EXCL|O_CREAT, 0200);
++
++    if (fd < 0)
+ 	rc = RPMERR_OPEN_FAILED;
+-	goto exit;
+-    }
+ 
+-    return 0;
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%s [%d]) %s\n", __func__,
++	       dest, fd, (rc < 0 ? strerror(errno) : ""));
++    }
++    *wfdp = fd;
+ 
+-exit:
+-    wfd_close(wfdp);
+     return rc;
+ }
+ 
+-/** \ingroup payload
+- * Create file from payload stream.
+- * @return		0 on success
+- */
+-static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest)
++static int fsmUnpack(rpmfi fi, int fdno, rpmpsm psm, int nodigest)
+ {
+-    FD_t wfd = NULL;
+-    int rc;
+-
+-    rc = wfd_open(&wfd, dest);
+-    if (rc != 0)
+-        goto exit;
+-
+-    rc = rpmfiArchiveReadToFilePsm(fi, wfd, nodigest, psm);
+-    wfd_close(&wfd);
+-exit:
++    FD_t fd = fdDup(fdno);
++    int rc = rpmfiArchiveReadToFilePsm(fi, fd, nodigest, psm);
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%s %" PRIu64 " bytes [%d]) %s\n", __func__,
++	       rpmfiFN(fi), rpmfiFSize(fi), Fileno(fd),
++	       (rc < 0 ? strerror(errno) : ""));
++    }
++    Fclose(fd);
+     return rc;
+ }
+ 
+-static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
+-		     rpmpsm psm, int nodigest, int *setmeta,
+-		     int * firsthardlink, FD_t *firstlinkfile)
++static int fsmMkfile(int dirfd, rpmfi fi, struct filedata_s *fp, rpmfiles files,
++		     rpmpsm psm, int nodigest,
++		     struct filedata_s ** firstlink, int *firstlinkfile,
++		     int *firstdir, int *fdp)
+ {
+     int rc = 0;
+-    int numHardlinks = rpmfiFNlink(fi);
++    int fd = -1;
+ 
+-    if (numHardlinks > 1) {
+-	/* Create first hardlinked file empty */
+-	if (*firsthardlink < 0) {
+-	    *firsthardlink = rpmfiFX(fi);
+-	    rc = wfd_open(firstlinkfile, dest);
+-	} else {
+-	    /* Create hard links for others */
+-	    char *fn = rpmfilesFN(files, *firsthardlink);
+-	    rc = link(fn, dest);
+-	    if (rc < 0) {
+-		rc = RPMERR_LINK_FAILED;
+-	    }
+-	    free(fn);
++    if (*firstlink == NULL) {
++	/* First encounter, open file for writing */
++	rc = fsmOpen(&fd, dirfd, fp->fpath);
++	/* If it's a part of a hardlinked set, the content may come later */
++	if (fp->sb.st_nlink > 1) {
++	    *firstlink = fp;
++	    *firstlinkfile = fd;
++	    *firstdir = dup(dirfd);
++	}
++    } else {
++	/* Create hard links for others and avoid redundant metadata setting */
++	if (*firstlink != fp) {
++	    rc = fsmLink(*firstdir, (*firstlink)->fpath, dirfd, fp->fpath);
+ 	}
++	fd = *firstlinkfile;
+     }
+-    /* Write normal files or fill the last hardlinked (already
+-       existing) file with content */
+-    if (numHardlinks<=1) {
+-	if (!rc)
+-	    rc = expandRegular(fi, dest, psm, nodigest);
+-    } else if (rpmfiArchiveHasContent(fi)) {
++
++    /* If the file has content, unpack it */
++    if (rpmfiArchiveHasContent(fi)) {
+ 	if (!rc)
+-	    rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
+-	wfd_close(firstlinkfile);
+-	*firsthardlink = -1;
+-    } else {
+-	*setmeta = 0;
++	    rc = fsmUnpack(fi, fd, psm, nodigest);
++	/* Last file of hardlink set, ensure metadata gets set */
++	if (*firstlink) {
++	    fp->setmeta = 1;
++	    *firstlink = NULL;
++	    *firstlinkfile = -1;
++	    fsmClose(firstdir);
++	}
+     }
++    *fdp = fd;
+ 
+     return rc;
+ }
+@@ -330,18 +256,15 @@
+     return rc;
+ }
+ 
+-static int fsmStat(const char *path, int dolstat, struct stat *sb)
++static int fsmStat(int dirfd, const char *path, int dolstat, struct stat *sb)
+ {
+-    int rc;
+-    if (dolstat){
+-	rc = lstat(path, sb);
+-    } else {
+-        rc = stat(path, sb);
+-    }
++    int flags = dolstat ? AT_SYMLINK_NOFOLLOW : 0;
++    int rc = fstatat(dirfd, path, sb, flags);
++
+     if (_fsm_debug && rc && errno != ENOENT)
+-        rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n",
++        rpmlog(RPMLOG_DEBUG, " %8s (%d %s, ost) %s\n",
+                __func__,
+-               path, (rc < 0 ? strerror(errno) : ""));
++               dirfd, path, (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0) {
+         rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_LSTAT_FAILED);
+ 	/* Ensure consistent struct content on failure */
+@@ -350,12 +273,12 @@
+     return rc;
+ }
+ 
+-static int fsmRmdir(const char *path)
++static int fsmRmdir(int dirfd, const char *path)
+ {
+-    int rc = rmdir(path);
++    int rc = unlinkat(dirfd, path, AT_REMOVEDIR);
+     if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
+-	       path, (rc < 0 ? strerror(errno) : ""));
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s) %s\n", __func__,
++	       dirfd, path, (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0)
+ 	switch (errno) {
+ 	case ENOENT:        rc = RPMERR_ENOENT;    break;
+@@ -365,148 +288,194 @@
+     return rc;
+ }
+ 
+-static int fsmMkdir(const char *path, mode_t mode)
++static int fsmMkdir(int dirfd, const char *path, mode_t mode)
+ {
+-    int rc = mkdir(path, (mode & 07777));
++    int rc = mkdirat(dirfd, path, (mode & 07777));
+     if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
+-	       path, (unsigned)(mode & 07777),
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n", __func__,
++	       dirfd, path, (unsigned)(mode & 07777),
+ 	       (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0)	rc = RPMERR_MKDIR_FAILED;
+     return rc;
+ }
+ 
+-static int fsmMkfifo(const char *path, mode_t mode)
++static int fsmOpenat(int dirfd, const char *path, int flags, int dir)
+ {
+-    int rc = mkfifo(path, (mode & 07777));
+-
+-    if (_fsm_debug) {
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n",
+-	       __func__, path, (unsigned)(mode & 07777),
+-	       (rc < 0 ? strerror(errno) : ""));
++    struct stat lsb, sb;
++    int sflags = flags | O_NOFOLLOW;
++    int fd = openat(dirfd, path, sflags);
++
++    /*
++     * Only ever follow symlinks by root or target owner. Since we can't
++     * open the symlink itself, the order matters: we stat the link *after*
++     * opening the target, and if the link ownership changed between the calls
++     * it could've only been the link owner or root.
++     */
++    if (fd < 0 && errno == ELOOP && flags != sflags) {
++	int ffd = openat(dirfd, path, flags);
++	if (ffd >= 0) {
++	    if (fstatat(dirfd, path, &lsb, AT_SYMLINK_NOFOLLOW) == 0) {
++		if (fstat(ffd, &sb) == 0) {
++		    if (lsb.st_uid == 0 || lsb.st_uid == sb.st_uid) {
++			fd = ffd;
++		    }
++		}
++	    }
++	    if (ffd != fd)
++		close(ffd);
++	}
+     }
+ 
+-    if (rc < 0)
+-	rc = RPMERR_MKFIFO_FAILED;
+-
+-    return rc;
++    /* O_DIRECTORY equivalent */
++    if (dir && fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode)) {
++	errno = ENOTDIR;
++	fsmClose(&fd);
++    }
++    return fd;
+ }
+ 
+-static int fsmMknod(const char *path, mode_t mode, dev_t dev)
++static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn,
++			const char *apath,
++			int owned, mode_t mode, int *fdp)
+ {
+-    /* FIX: check S_IFIFO or dev != 0 */
+-    int rc = mknod(path, (mode & ~07777), dev);
++    int rc;
++    rpmFsmOp op = (FA_CREATE);
++    if (!owned)
++	op |= FAF_UNOWNED;
+ 
+-    if (_fsm_debug) {
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%o, 0x%x) %s\n",
+-	       __func__, path, (unsigned)(mode & ~07777),
+-	       (unsigned)dev, (rc < 0 ? strerror(errno) : ""));
++    /* Run fsm file pre hook for all plugins */
++    rc = rpmpluginsCallFsmFilePre(plugins, NULL, apath, mode, op);
++
++    if (!rc)
++	rc = fsmMkdir(dirfd, dn, mode);
++
++    if (!rc) {
++	*fdp = fsmOpenat(dirfd, dn, O_RDONLY|O_NOFOLLOW, 1);
++	if (*fdp == -1)
++	    rc = RPMERR_ENOTDIR;
+     }
+ 
+-    if (rc < 0)
+-	rc = RPMERR_MKNOD_FAILED;
++    if (!rc) {
++	rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, *fdp, apath, apath, mode, op);
++    }
++
++    /* Run fsm file post hook for all plugins */
++    rpmpluginsCallFsmFilePost(plugins, NULL, apath, mode, op, rc);
++
++    if (!rc) {
++	rpmlog(RPMLOG_DEBUG,
++		"%s directory created with perms %04o\n",
++		apath, (unsigned)(mode & 07777));
++    }
+ 
+     return rc;
+ }
+ 
+-/**
+- * Create (if necessary) directories not explicitly included in package.
+- * @param files		file data
+- * @param fs		file states
+- * @param plugins	rpm plugins handle
+- * @return		0 on success
+- */
+-static int fsmMkdirs(rpmfiles files, rpmfs fs, rpmPlugins plugins)
++static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create,
++		    int quiet, int *dirfdp)
+ {
+-    DNLI_t dnli = dnlInitIterator(files, fs, 0);
+-    struct stat sb;
+-    const char *dpath;
++    char *sp = NULL, *bn;
++    char *apath = NULL;
++    int oflags = O_RDONLY;
+     int rc = 0;
+-    int i;
+-    size_t ldnlen = 0;
+-    const char * ldn = NULL;
+-
+-    while ((dpath = dnlNextIterator(dnli)) != NULL) {
+-	size_t dnlen = strlen(dpath);
+-	char * te, dn[dnlen+1];
+ 
+-	if (dnlen <= 1)
+-	    continue;
++    if (*dirfdp >= 0)
++	return rc;
+ 
+-	if (dnlen == ldnlen && rstreq(dpath, ldn))
+-	    continue;
++    int dirfd = fsmOpenat(-1, "/", oflags, 1);
++    int fd = dirfd; /* special case of "/" */
+ 
+-	/* Copy as we need to modify the string */
+-	(void) stpcpy(dn, dpath);
++    char *path = xstrdup(p);
++    char *dp = path;
+ 
+-	/* Assume '/' directory exists, "mkdir -p" for others if non-existent */
+-	for (i = 1, te = dn + 1; *te != '\0'; te++, i++) {
+-	    if (*te != '/')
+-		continue;
++    while ((bn = strtok_r(dp, "/", &sp)) != NULL) {
++	fd = fsmOpenat(dirfd, bn, oflags, 1);
++	/* assemble absolute path for plugins benefit, sigh */
++	apath = rstrscat(&apath, "/", bn, NULL);
++
++	if (fd < 0 && errno == ENOENT && create) {
++	    mode_t mode = S_IFDIR | (_dirPerms & 07777);
++	    rc = fsmDoMkDir(plugins, dirfd, bn, apath, owned, mode, &fd);
++	}
+ 
+-	    /* Already validated? */
+-	    if (i < ldnlen &&
+-		(ldn[i] == '/' || ldn[i] == '\0') && rstreqn(dn, ldn, i))
+-		continue;
++	fsmClose(&dirfd);
++	if (fd >= 0) {
++	    dirfd = fd;
++	} else {
++	    if (!quiet) {
++		rpmlog(RPMLOG_ERR, _("failed to open dir %s of %s: %s\n"),
++			bn, p, strerror(errno));
++	    }
++	    rc = RPMERR_OPEN_FAILED;
++	    break;
++	}
+ 
+-	    /* Validate next component of path. */
+-	    *te = '\0';
+-	    rc = fsmStat(dn, 1, &sb); /* lstat */
+-	    *te = '/';
++	dp = NULL;
++    }
+ 
+-	    /* Directory already exists? */
+-	    if (rc == 0 && S_ISDIR(sb.st_mode)) {
+-		continue;
+-	    } else if (rc == RPMERR_ENOENT) {
+-		*te = '\0';
+-		mode_t mode = S_IFDIR | (_dirPerms & 07777);
+-		rpmFsmOp op = (FA_CREATE|FAF_UNOWNED);
+-
+-		/* Run fsm file pre hook for all plugins */
+-		rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op);
+-
+-		if (!rc)
+-		    rc = fsmMkdir(dn, mode);
+-
+-		if (!rc) {
+-		    rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn,
+-						      mode, op);
+-		}
++    if (rc) {
++	fsmClose(&fd);
++	fsmClose(&dirfd);
++    } else {
++	rc = 0;
++    }
++    *dirfdp = dirfd;
+ 
+-		/* Run fsm file post hook for all plugins */
+-		rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc);
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%s: %d) %s\n", __func__,
++		p, dirfd, (rc < 0 ? strerror(errno) : ""));
++    }
+ 
+-		if (!rc) {
+-		    rpmlog(RPMLOG_DEBUG,
+-			    "%s directory created with perms %04o\n",
+-			    dn, (unsigned)(mode & 07777));
+-		}
+-		*te = '/';
+-	    }
+-	    if (rc)
+-		break;
+-	}
+-	if (rc) break;
++    free(path);
++    free(apath);
++    return rc;
++}
+ 
+-	/* Save last validated path. */
+-	ldn = dpath;
+-	ldnlen = dnlen;
++static int fsmMkfifo(int dirfd, const char *path, mode_t mode)
++{
++    int rc = mkfifoat(dirfd, path, (mode & 07777));
++
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n",
++	       __func__, dirfd, path, (unsigned)(mode & 07777),
++	       (rc < 0 ? strerror(errno) : ""));
+     }
+-    dnlFreeIterator(dnli);
++
++    if (rc < 0)
++	rc = RPMERR_MKFIFO_FAILED;
+ 
+     return rc;
+ }
+ 
+-static void removeSBITS(const char *path)
++static int fsmMknod(int dirfd, const char *path, mode_t mode, dev_t dev)
++{
++    /* FIX: check S_IFIFO or dev != 0 */
++    int rc = mknodat(dirfd, path, (mode & ~07777), dev);
++
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%o, 0x%x) %s\n",
++	       __func__, dirfd, path, (unsigned)(mode & ~07777),
++	       (unsigned)dev, (rc < 0 ? strerror(errno) : ""));
++    }
++
++    if (rc < 0)
++	rc = RPMERR_MKNOD_FAILED;
++
++    return rc;
++}
++
++static void removeSBITS(int dirfd, const char *path)
+ {
+     struct stat stb;
+-    if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) {
++    int flags = AT_SYMLINK_NOFOLLOW;
++    if (fstatat(dirfd, path, &stb, flags) == 0 && S_ISREG(stb.st_mode)) {
++    	/* We now know it's not a link so no need to worry about following */
+ 	if ((stb.st_mode & 06000) != 0) {
+-	    (void) chmod(path, stb.st_mode & 0777);
++	    (void) fchmodat(dirfd, path, stb.st_mode & 0777, 0);
+ 	}
+-#if WITH_CAP
++#ifdef WITH_CAP
+ 	if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
+-	    (void) cap_set_file(path, NULL);
++	    (void) cap_set_fileat(dirfd, path, NULL);
+ 	}
+ #endif
+     }
+@@ -522,13 +491,13 @@
+ 	    (fpath ? fpath : ""));
+ }
+ 
+-static int fsmSymlink(const char *opath, const char *path)
++static int fsmSymlink(const char *opath, int dirfd, const char *path)
+ {
+-    int rc = symlink(opath, path);
++    int rc = symlinkat(opath, dirfd, path);
+ 
+     if (_fsm_debug) {
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
+-	       opath, path, (rc < 0 ? strerror(errno) : ""));
++	rpmlog(RPMLOG_DEBUG, " %8s (%s, %d %s) %s\n", __func__,
++	       opath, dirfd, path, (rc < 0 ? strerror(errno) : ""));
+     }
+ 
+     if (rc < 0)
+@@ -536,96 +505,125 @@
+     return rc;
+ }
+ 
+-static int fsmUnlink(const char *path)
++static int fsmUnlink(int dirfd, const char *path)
+ {
+     int rc = 0;
+-    removeSBITS(path);
+-    rc = unlink(path);
++    removeSBITS(dirfd, path);
++    rc = unlinkat(dirfd, path, 0);
+     if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
+-	       path, (rc < 0 ? strerror(errno) : ""));
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s) %s\n", __func__,
++	       dirfd, path, (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0)
+ 	rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_UNLINK_FAILED);
+     return rc;
+ }
+ 
+-static int fsmRename(const char *opath, const char *path)
++static int fsmRename(int odirfd, const char *opath, int dirfd, const char *path)
+ {
+-    removeSBITS(path);
+-    int rc = rename(opath, path);
++    removeSBITS(dirfd, path);
++    int rc = renameat(odirfd, opath, dirfd, path);
+ #if defined(ETXTBSY) && defined(__HPUX__)
+     /* XXX HP-UX (and other os'es) don't permit rename to busy files. */
+     if (rc && errno == ETXTBSY) {
+ 	char *rmpath = NULL;
+ 	rstrscat(&rmpath, path, "-RPMDELETE", NULL);
+-	rc = rename(path, rmpath);
+-	if (!rc) rc = rename(opath, path);
++	/* Rename within the original directory */
++	rc = renameat(odirfd, path, odirfd, rmpath);
++	if (!rc) rc = renameat(odirfd, opath, dirfd, path);
+ 	free(rmpath);
+     }
+ #endif
+     if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
+-	       opath, path, (rc < 0 ? strerror(errno) : ""));
++	rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__,
++	       odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0)
+ 	rc = (errno == EISDIR ? RPMERR_EXIST_AS_DIR : RPMERR_RENAME_FAILED);
+     return rc;
+ }
+ 
+-static int fsmRemove(const char *path, mode_t mode)
++static int fsmRemove(int dirfd, const char *path, mode_t mode)
+ {
+-    return S_ISDIR(mode) ? fsmRmdir(path) : fsmUnlink(path);
++    return S_ISDIR(mode) ? fsmRmdir(dirfd, path) : fsmUnlink(dirfd, path);
+ }
+ 
+-static int fsmChown(const char *path, mode_t mode, uid_t uid, gid_t gid)
++static int fsmChown(int fd, int dirfd, const char *path, mode_t mode, uid_t uid, gid_t gid)
+ {
+-    int rc = S_ISLNK(mode) ? lchown(path, uid, gid) : chown(path, uid, gid);
+-    if (rc < 0) {
+-	struct stat st;
+-	if (lstat(path, &st) == 0 && st.st_uid == uid && st.st_gid == gid)
+-	    rc = 0;
++    int rc;
++    struct stat st;
++
++    if (fd >= 0) {
++	rc = fchown(fd, uid, gid);
++	if (rc < 0) {
++	    if (fstat(fd, &st) == 0 && (st.st_uid == uid && st.st_gid == gid)) {
++		rc = 0;
++	    }
++	}
++    } else {
++	int flags = AT_SYMLINK_NOFOLLOW;
++	rc = fchownat(dirfd, path, uid, gid, flags);
++	if (rc < 0) {
++	    struct stat st;
++	    if (fstatat(dirfd, path, &st, flags) == 0 &&
++		    (st.st_uid == uid && st.st_gid == gid)) {
++		rc = 0;
++	    }
++	}
+     }
+-    if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", __func__,
+-	       path, (int)uid, (int)gid,
++    if (_fsm_debug) {
++	rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %d, %d) %s\n", __func__,
++	       fd, dirfd, path, (int)uid, (int)gid,
+ 	       (rc < 0 ? strerror(errno) : ""));
++    }
+     if (rc < 0)	rc = RPMERR_CHOWN_FAILED;
+     return rc;
+ }
+ 
+-static int fsmChmod(const char *path, mode_t mode)
++static int fsmChmod(int fd, int dirfd, const char *path, mode_t mode)
+ {
+-    int rc = chmod(path, (mode & 07777));
+-    if (rc < 0) {
+-	struct stat st;
+-	if (lstat(path, &st) == 0 && (st.st_mode & 07777) == (mode & 07777))
+-	    rc = 0;
++    mode_t fmode = (mode & 07777);
++    int rc;
++    if (fd >= 0) {
++	rc = fchmod(fd, fmode);
++	if (rc < 0) {
++	    struct stat st;
++	    if (fstat(fd, &st) == 0 && (st.st_mode & 07777) == fmode) {
++		rc = 0;
++	    }
++	}
++    } else {
++	rc = fchmodat(dirfd, path, fmode, 0);
++	if (rc < 0) {
++	    struct stat st;
++	    if (fstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW) == 0 &&
++		    (st.st_mode & 07777) == fmode) {
++		rc = 0;
++	    }
++	}
+     }
+     if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
+-	       path, (unsigned)(mode & 07777),
++	rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, 0%04o) %s\n", __func__,
++	       fd, dirfd, path, (unsigned)(mode & 07777),
+ 	       (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0)	rc = RPMERR_CHMOD_FAILED;
+     return rc;
+ }
+ 
+-static int fsmUtime(const char *path, mode_t mode, time_t mtime)
++static int fsmUtime(int fd, int dirfd, const char *path, mode_t mode, time_t mtime)
+ {
+     int rc = 0;
+-    struct timeval stamps[2] = {
+-	{ .tv_sec = mtime, .tv_usec = 0 },
+-	{ .tv_sec = mtime, .tv_usec = 0 },
++    struct timespec stamps[2] = {
++	{ .tv_sec = mtime, .tv_nsec = 0 },
++	{ .tv_sec = mtime, .tv_nsec = 0 },
+     };
+ 
+-#if HAVE_LUTIMES
+-    rc = lutimes(path, stamps);
+-#else
+-    if (!S_ISLNK(mode))
+-	rc = utimes(path, stamps);
+-#endif
++    if (fd >= 0)
++	rc = futimens(fd, stamps);
++    else
++	rc = utimensat(dirfd, path, stamps, AT_SYMLINK_NOFOLLOW);
+     
+     if (_fsm_debug)
+-	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", __func__,
+-	       path, (unsigned)mtime, (rc < 0 ? strerror(errno) : ""));
++	rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, 0x%x) %s\n", __func__,
++	       fd, dirfd, path, (unsigned)mtime, (rc < 0 ? strerror(errno) : ""));
+     if (rc < 0)	rc = RPMERR_UTIME_FAILED;
+     /* ...but utime error is not critical for directories */
+     if (rc && S_ISDIR(mode))
+@@ -633,24 +631,24 @@
+     return rc;
+ }
+ 
+-static int fsmVerify(const char *path, rpmfi fi)
++static int fsmVerify(int dirfd, const char *path, rpmfi fi)
+ {
+     int rc;
+     int saveerrno = errno;
+     struct stat dsb;
+     mode_t mode = rpmfiFMode(fi);
+ 
+-    rc = fsmStat(path, 1, &dsb);
++    rc = fsmStat(dirfd, path, 1, &dsb);
+     if (rc)
+ 	return rc;
+ 
+     if (S_ISREG(mode)) {
+ 	/* HP-UX (and other os'es) don't permit unlink on busy files. */
+ 	char *rmpath = rstrscat(NULL, path, "-RPMDELETE", NULL);
+-	rc = fsmRename(path, rmpath);
++	rc = fsmRename(dirfd, path, dirfd, rmpath);
+ 	/* XXX shouldn't we take unlink return code here? */
+ 	if (!rc)
+-	    (void) fsmUnlink(rmpath);
++	    (void) fsmUnlink(dirfd, rmpath);
+ 	else
+ 	    rc = RPMERR_UNLINK_FAILED;
+ 	free(rmpath);
+@@ -659,7 +657,7 @@
+         if (S_ISDIR(dsb.st_mode)) return 0;
+         if (S_ISLNK(dsb.st_mode)) {
+ 	    uid_t luid = dsb.st_uid;
+-            rc = fsmStat(path, 0, &dsb);
++            rc = fsmStat(dirfd, path, 0, &dsb);
+             if (rc == RPMERR_ENOENT) rc = 0;
+             if (rc) return rc;
+             errno = saveerrno;
+@@ -685,7 +683,7 @@
+         if (S_ISSOCK(dsb.st_mode)) return 0;
+     }
+     /* XXX shouldn't do this with commit/undo. */
+-    rc = fsmUnlink(path);
++    rc = fsmUnlink(dirfd, path);
+     if (rc == 0)	rc = RPMERR_ENOENT;
+     return (rc ? rc : RPMERR_ENOENT);	/* XXX HACK */
+ }
+@@ -699,7 +697,7 @@
+ 
+ 
+ /* Rename pre-existing modified or unmanaged file. */
+-static int fsmBackup(rpmfi fi, rpmFileAction action)
++static int fsmBackup(int dirfd, rpmfi fi, rpmFileAction action)
+ {
+     int rc = 0;
+     const char *suffix = NULL;
+@@ -720,9 +718,10 @@
+     if (suffix) {
+ 	char * opath = fsmFsPath(fi, NULL);
+ 	char * path = fsmFsPath(fi, suffix);
+-	rc = fsmRename(opath, path);
++	rc = fsmRename(dirfd, opath, dirfd, path);
+ 	if (!rc) {
+-	    rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"), opath, path);
++	    rpmlog(RPMLOG_WARNING, _("%s%s saved as %s%s\n"),
++		   rpmfiDN(fi), opath, rpmfiDN(fi), path);
+ 	}
+ 	free(path);
+ 	free(opath);
+@@ -730,7 +729,8 @@
+     return rc;
+ }
+ 
+-static int fsmSetmeta(const char *path, rpmfi fi, rpmPlugins plugins,
++static int fsmSetmeta(int fd, int dirfd, const char *path,
++		      rpmfi fi, rpmPlugins plugins,
+ 		      rpmFileAction action, const struct stat * st,
+ 		      int nofcaps)
+ {
+@@ -738,27 +738,28 @@
+     const char *dest = rpmfiFN(fi);
+ 
+     if (!rc && !getuid()) {
+-	rc = fsmChown(path, st->st_mode, st->st_uid, st->st_gid);
++	rc = fsmChown(fd, dirfd, path, st->st_mode, st->st_uid, st->st_gid);
+     }
+     if (!rc && !S_ISLNK(st->st_mode)) {
+-	rc = fsmChmod(path, st->st_mode);
++	rc = fsmChmod(fd, dirfd, path, st->st_mode);
+     }
+     /* Set file capabilities (if enabled) */
+     if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) {
+-	rc = fsmSetFCaps(path, rpmfiFCaps(fi));
++	rc = fsmSetFCaps(fd, dirfd, path, rpmfiFCaps(fi));
+     }
+     if (!rc) {
+-	rc = fsmUtime(path, st->st_mode, rpmfiFMtime(fi));
++	rc = fsmUtime(fd, dirfd, path, st->st_mode, rpmfiFMtime(fi));
+     }
+     if (!rc) {
+ 	rc = rpmpluginsCallFsmFilePrepare(plugins, fi,
+-					  path, dest, st->st_mode, action);
++					  fd, path, dest,
++					  st->st_mode, action);
+     }
+ 
+     return rc;
+ }
+ 
+-static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *suffix)
++static int fsmCommit(int dirfd, char **path, rpmfi fi, rpmFileAction action, const char *suffix)
+ {
+     int rc = 0;
+ 
+@@ -772,15 +773,18 @@
+ 
+ 	/* Rename temporary to final file name if needed. */
+ 	if (dest != *path) {
+-	    rc = fsmRename(*path, dest);
+-	    if (!rc && nsuffix) {
+-		char * opath = fsmFsPath(fi, NULL);
+-		rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
+-		       opath, dest);
+-		free(opath);
+-	    }
+-	    free(*path);
+-	    *path = dest;
++	    rc = fsmRename(dirfd, *path, dirfd, dest);
++	    if (!rc) {
++		if (nsuffix) {
++		    char * opath = fsmFsPath(fi, NULL);
++		    rpmlog(RPMLOG_WARNING, _("%s%s created as %s%s\n"),
++			   rpmfiDN(fi), opath, rpmfiDN(fi), dest);
++		    free(opath);
++		}
++		free(*path);
++		*path = dest;
++	    } else
++		free(dest);
+ 	}
+     }
+ 
+@@ -831,191 +835,277 @@
+     }
+ }
+ 
++struct diriter_s {
++    int dirfd;
++    int firstdir;
++};
++
++static int onChdir(rpmfi fi, void *data)
++{
++    struct diriter_s *di = data;
++
++    fsmClose(&(di->dirfd));
++    return 0;
++}
++
++static rpmfi fsmIter(FD_t payload, rpmfiles files, rpmFileIter iter, void *data)
++{
++    rpmfi fi;
++    if (payload)
++	fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
++    else
++	fi = rpmfilesIter(files, iter);
++    if (fi && data)
++	rpmfiSetOnChdir(fi, onChdir, data);
++    return fi;
++}
++
++static rpmfi fsmIterFini(rpmfi fi, struct diriter_s *di)
++{
++    fsmClose(&(di->dirfd));
++    fsmClose(&(di->firstdir));
++    return rpmfiFree(fi);
++}
++
+ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
+               rpmpsm psm, char ** failedFile)
+ {
+     FD_t payload = rpmtePayload(te);
+-    rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
++    rpmfi fi = NULL;
+     rpmfs fs = rpmteGetFileStates(te);
+     rpmPlugins plugins = rpmtsPlugins(ts);
+-    struct stat sb;
+-    int saveerrno = errno;
+     int rc = 0;
++    int fx = -1;
++    int fc = rpmfilesFC(files);
+     int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
+     int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
+-    int firsthardlink = -1;
+-    FD_t firstlinkfile = NULL;
+-    int skip;
+-    rpmFileAction action;
++    int firstlinkfile = -1;
+     char *tid = NULL;
+-    const char *suffix;
+-    char *fpath = NULL;
+-
+-    if (fi == NULL) {
+-	rc = RPMERR_BAD_MAGIC;
+-	goto exit;
+-    }
++    struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
++    struct filedata_s *firstlink = NULL;
++    struct diriter_s di = { -1, -1 };
+ 
+     /* transaction id used for temporary path suffix while installing */
+     rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
+ 
+-    /* Detect and create directories not explicitly in package. */
+-    rc = fsmMkdirs(files, fs, plugins);
++    /* Collect state data for the whole operation */
++    fi = rpmfilesIter(files, RPMFI_ITER_FWD);
++    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
++	struct filedata_s *fp = &fdata[fx];
++	if (rpmfiFFlags(fi) & RPMFILE_GHOST)
++            fp->action = FA_SKIP;
++	else
++	    fp->action = rpmfsGetAction(fs, fx);
++	fp->skip = XFA_SKIPPING(fp->action);
++	if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi)))
++	    fp->suffix = tid;
++	fp->fpath = fsmFsPath(fi, fp->suffix);
+ 
+-    while (!rc) {
+-	/* Read next payload header. */
+-	rc = rpmfiNext(fi);
++	/* Remap file perms, owner, and group. */
++	rc = rpmfiStat(fi, 1, &fp->sb);
+ 
+-	if (rc < 0) {
+-	    if (rc == RPMERR_ITER_END)
+-		rc = 0;
+-	    break;
+-	}
++	/* Hardlinks are tricky and handled elsewhere for install */
++	fp->setmeta = (fp->skip == 0) &&
++		      (fp->sb.st_nlink == 1 || fp->action == FA_TOUCH);
+ 
+-	action = rpmfsGetAction(fs, rpmfiFX(fi));
+-	skip = XFA_SKIPPING(action);
+-	if (action != FA_TOUCH) {
+-	    suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
+-	} else {
+-	    suffix = NULL;
+-	}
+-	fpath = fsmFsPath(fi, suffix);
++	setFileState(fs, fx);
++	fsmDebug(fp->fpath, fp->action, &fp->sb);
+ 
+-	/* Remap file perms, owner, and group. */
+-	rc = rpmfiStat(fi, 1, &sb);
++	fp->stage = FILE_PRE;
++    }
++    fi = rpmfiFree(fi);
+ 
+-	fsmDebug(fpath, action, &sb);
++    if (rc)
++	goto exit;
+ 
+-        /* Exit on error. */
+-        if (rc)
+-            break;
++    fi = fsmIter(payload, files,
++		 payload ? RPMFI_ITER_READ_ARCHIVE : RPMFI_ITER_FWD, &di);
+ 
+-	/* Run fsm file pre hook for all plugins */
+-	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
+-				      sb.st_mode, action);
+-	if (rc) {
+-	    skip = 1;
+-	} else {
+-	    setFileState(fs, rpmfiFX(fi));
+-	}
++    if (fi == NULL) {
++        rc = RPMERR_BAD_MAGIC;
++        goto exit;
++    }
+ 
+-        if (!skip) {
+-	    int setmeta = 1;
++    /* Process the payload */
++    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
++	struct filedata_s *fp = &fdata[fx];
++
++	/*
++	 * Tricksy case: this file is a being skipped, but it's part of
++	 * a hardlinked set and has the actual content linked with it.
++	 * Write the content to the first non-skipped file of the set
++	 * instead.
++	 */
++	if (fp->skip && firstlink && rpmfiArchiveHasContent(fi))
++	    fp = firstlink;
++
++        if (!fp->skip) {
++	    int mayopen = 0;
++	    int fd = -1;
++	    rc = ensureDir(plugins, rpmfiDN(fi), 0,
++			    (fp->action == FA_CREATE), 0, &di.dirfd);
+ 
+ 	    /* Directories replacing something need early backup */
+-	    if (!suffix) {
+-		rc = fsmBackup(fi, action);
++	    if (!rc && !fp->suffix && fp != firstlink) {
++		rc = fsmBackup(di.dirfd, fi, fp->action);
+ 	    }
++
++	    /* Run fsm file pre hook for all plugins */
++	    if (!rc)
++		rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
++					      fp->sb.st_mode, fp->action);
++	    if (rc)
++		goto setmeta; /* for error notification */
++
+ 	    /* Assume file does't exist when tmp suffix is in use */
+-	    if (!suffix) {
+-		rc = fsmVerify(fpath, fi);
++	    if (!fp->suffix) {
++		if (fp->action == FA_TOUCH) {
++		    struct stat sb;
++		    rc = fsmStat(di.dirfd, fp->fpath, 1, &sb);
++		} else {
++		    rc = fsmVerify(di.dirfd, fp->fpath, fi);
++		}
+ 	    } else {
+ 		rc = RPMERR_ENOENT;
+ 	    }
+ 
+ 	    /* See if the file was removed while our attention was elsewhere */
+-	    if (rc == RPMERR_ENOENT && action == FA_TOUCH) {
+-		rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", fpath);
+-		action = FA_CREATE;
+-		fsmDebug(fpath, action, &sb);
++	    if (rc == RPMERR_ENOENT && fp->action == FA_TOUCH) {
++		rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n",
++			fp->fpath);
++		fp->action = FA_CREATE;
++		fsmDebug(fp->fpath, fp->action, &fp->sb);
+ 	    }
+ 
+ 	    /* When touching we don't need any of this... */
+-	    if (action == FA_TOUCH)
+-		goto touch;
++	    if (fp->action == FA_TOUCH)
++		goto setmeta;
+ 
+-            if (S_ISREG(sb.st_mode)) {
++            if (S_ISREG(fp->sb.st_mode)) {
+ 		if (rc == RPMERR_ENOENT) {
+-		    rc = fsmMkfile(fi, fpath, files, psm, nodigest,
+-				   &setmeta, &firsthardlink, &firstlinkfile);
++		    rc = fsmMkfile(di.dirfd, fi, fp, files, psm, nodigest,
++				   &firstlink, &firstlinkfile, &di.firstdir,
++				   &fd);
+ 		}
+-            } else if (S_ISDIR(sb.st_mode)) {
++            } else if (S_ISDIR(fp->sb.st_mode)) {
+                 if (rc == RPMERR_ENOENT) {
+-                    mode_t mode = sb.st_mode;
++                    mode_t mode = fp->sb.st_mode;
+                     mode &= ~07777;
+                     mode |=  00700;
+-                    rc = fsmMkdir(fpath, mode);
++                    rc = fsmMkdir(di.dirfd, fp->fpath, mode);
+                 }
+-            } else if (S_ISLNK(sb.st_mode)) {
++            } else if (S_ISLNK(fp->sb.st_mode)) {
+ 		if (rc == RPMERR_ENOENT) {
+-		    rc = fsmSymlink(rpmfiFLink(fi), fpath);
++		    rc = fsmSymlink(rpmfiFLink(fi), di.dirfd, fp->fpath);
+ 		}
+-            } else if (S_ISFIFO(sb.st_mode)) {
++            } else if (S_ISFIFO(fp->sb.st_mode)) {
+                 /* This mimics cpio S_ISSOCK() behavior but probably isn't right */
+                 if (rc == RPMERR_ENOENT) {
+-                    rc = fsmMkfifo(fpath, 0000);
++                    rc = fsmMkfifo(di.dirfd, fp->fpath, 0000);
+                 }
+-            } else if (S_ISCHR(sb.st_mode) ||
+-                       S_ISBLK(sb.st_mode) ||
+-                       S_ISSOCK(sb.st_mode))
++            } else if (S_ISCHR(fp->sb.st_mode) ||
++                       S_ISBLK(fp->sb.st_mode) ||
++                       S_ISSOCK(fp->sb.st_mode))
+             {
+                 if (rc == RPMERR_ENOENT) {
+-                    rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
++                    rc = fsmMknod(di.dirfd, fp->fpath, fp->sb.st_mode, fp->sb.st_rdev);
+                 }
+             } else {
+                 /* XXX Special case /dev/log, which shouldn't be packaged anyways */
+-                if (!IS_DEV_LOG(fpath))
++                if (!IS_DEV_LOG(fp->fpath))
+                     rc = RPMERR_UNKNOWN_FILETYPE;
+             }
+ 
+-touch:
+-	    /* Set permissions, timestamps etc for non-hardlink entries */
+-	    if (!rc && setmeta) {
+-		rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps);
++setmeta:
++	    /* Special files require path-based ops */
++	    mayopen = S_ISREG(fp->sb.st_mode) || S_ISDIR(fp->sb.st_mode);
++	    if (!rc && fd == -1 && mayopen) {
++		int flags = O_RDONLY;
++		/* Only follow safe symlinks, and never on temporary files */
++		if (fp->suffix)
++		    flags |= AT_SYMLINK_NOFOLLOW;
++		fd = fsmOpenat(di.dirfd, fp->fpath, flags,
++				S_ISDIR(fp->sb.st_mode));
++		if (fd < 0)
++		    rc = RPMERR_OPEN_FAILED;
+ 	    }
+-        } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
+-	    /* On FA_TOUCH no hardlinks are created thus this is skipped. */
+-	    /* we skip the hard linked file containing the content */
+-	    /* write the content to the first used instead */
+-	    char *fn = rpmfilesFN(files, firsthardlink);
+-	    rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm);
+-	    wfd_close(&firstlinkfile);
+-	    firsthardlink = -1;
+-	    free(fn);
+-	}
+-
+-        if (rc) {
+-            if (!skip) {
+-                /* XXX only erase if temp fn w suffix is in use */
+-                if (suffix) {
+-		    (void) fsmRemove(fpath, sb.st_mode);
+-                }
+-                errno = saveerrno;
+-            }
+-        } else {
+-	    /* Notify on success. */
+-	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
+ 
+-	    if (!skip) {
+-		/* Backup file if needed. Directories are handled earlier */
+-		if (suffix)
+-		    rc = fsmBackup(fi, action);
+-
+-		if (!rc)
+-		    rc = fsmCommit(&fpath, fi, action, suffix);
++	    if (!rc && fp->setmeta) {
++		rc = fsmSetmeta(fd, di.dirfd, fp->fpath,
++				fi, plugins, fp->action,
++				&fp->sb, nofcaps);
+ 	    }
++
++	    if (fd != firstlinkfile)
++		fsmClose(&fd);
+ 	}
+ 
++	/* Notify on success. */
+ 	if (rc)
+-	    *failedFile = xstrdup(fpath);
++	    *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL);
++	else
++	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
++	fp->stage = FILE_UNPACK;
++    }
++    fi = fsmIterFini(fi, &di);
+ 
+-	/* Run fsm file post hook for all plugins */
+-	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
+-				  sb.st_mode, action, rc);
+-	fpath = _free(fpath);
++    if (!rc && fx < 0 && fx != RPMERR_ITER_END)
++	rc = fx;
++
++    /* If all went well, commit files to final destination */
++    fi = fsmIter(NULL, files, RPMFI_ITER_FWD, &di);
++    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
++	struct filedata_s *fp = &fdata[fx];
++
++	if (!fp->skip) {
++	    if (!rc)
++		rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd);
++
++	    /* Backup file if needed. Directories are handled earlier */
++	    if (!rc && fp->suffix)
++		rc = fsmBackup(di.dirfd, fi, fp->action);
++
++	    if (!rc)
++		rc = fsmCommit(di.dirfd, &fp->fpath, fi, fp->action, fp->suffix);
++
++	    if (!rc)
++		fp->stage = FILE_COMMIT;
++	    else
++		*failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL);
++
++	    /* Run fsm file post hook for all plugins for all processed files */
++	    rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
++				      fp->sb.st_mode, fp->action, rc);
++	}
++    }
++    fi = fsmIterFini(fi, &di);
++
++    /* On failure, walk backwards and erase non-committed files */
++    if (rc) {
++	fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di);
++	while ((fx = rpmfiNext(fi)) >= 0) {
++	    struct filedata_s *fp = &fdata[fx];
++
++	    /* If the directory doesn't exist there's nothing to clean up */
++	    if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd))
++		continue;
++
++	    if (fp->stage > FILE_NONE && !fp->skip) {
++		(void) fsmRemove(di.dirfd, fp->fpath, fp->sb.st_mode);
++	    }
++	}
+     }
+ 
+     rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
+     rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
+ 
+ exit:
+-
+-    /* No need to bother with close errors on read */
+-    rpmfiArchiveClose(fi);
+-    rpmfiFree(fi);
++    fi = fsmIterFini(fi, &di);
+     Fclose(payload);
+     free(tid);
+-    free(fpath);
++    for (int i = 0; i < fc; i++)
++	free(fdata[i].fpath);
++    free(fdata);
+ 
+     return rc;
+ }
+@@ -1024,32 +1114,42 @@
+ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
+               rpmpsm psm, char ** failedFile)
+ {
+-    rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK);
++    struct diriter_s di = { -1, -1 };
++    rpmfi fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di);
+     rpmfs fs = rpmteGetFileStates(te);
+     rpmPlugins plugins = rpmtsPlugins(ts);
+-    struct stat sb;
++    int fc = rpmfilesFC(files);
++    int fx = -1;
++    struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
+     int rc = 0;
+-    char *fpath = NULL;
+ 
+-    while (!rc && rpmfiNext(fi) >= 0) {
+-	rpmFileAction action = rpmfsGetAction(fs, rpmfiFX(fi));
+-	fpath = fsmFsPath(fi, NULL);
+-	rc = fsmStat(fpath, 1, &sb);
++    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
++	struct filedata_s *fp = &fdata[fx];
++	fp->action = rpmfsGetAction(fs, rpmfiFX(fi));
++
++	if (XFA_SKIPPING(fp->action))
++	    continue;
++
++	fp->fpath = fsmFsPath(fi, NULL);
++	/* If the directory doesn't exist there's nothing to clean up */
++	if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd))
++	    continue;
++
++	rc = fsmStat(di.dirfd, fp->fpath, 1, &fp->sb);
+ 
+-	fsmDebug(fpath, action, &sb);
++	fsmDebug(fp->fpath, fp->action, &fp->sb);
+ 
+ 	/* Run fsm file pre hook for all plugins */
+-	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
+-				      sb.st_mode, action);
++	rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
++				      fp->sb.st_mode, fp->action);
+ 
+-	if (!XFA_SKIPPING(action))
+-	    rc = fsmBackup(fi, action);
++	rc = fsmBackup(di.dirfd, fi, fp->action);
+ 
+         /* Remove erased files. */
+-        if (action == FA_ERASE) {
++        if (fp->action == FA_ERASE) {
+ 	    int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
+ 
+-	    rc = fsmRemove(fpath, sb.st_mode);
++	    rc = fsmRemove(di.dirfd, fp->fpath, fp->sb.st_mode);
+ 
+ 	    /*
+ 	     * Missing %ghost or %missingok entries are not errors.
+@@ -1074,20 +1174,20 @@
+ 	    if (rc) {
+ 		int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING;
+ 		rpmlog(lvl, _("%s %s: remove failed: %s\n"),
+-			S_ISDIR(sb.st_mode) ? _("directory") : _("file"),
+-			fpath, strerror(errno));
++			S_ISDIR(fp->sb.st_mode) ? _("directory") : _("file"),
++			fp->fpath, strerror(errno));
+             }
+         }
+ 
+ 	/* Run fsm file post hook for all plugins */
+-	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
+-				  sb.st_mode, action, rc);
++	rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
++				  fp->sb.st_mode, fp->action, rc);
+ 
+         /* XXX Failure to remove is not (yet) cause for failure. */
+         if (!strict_erasures) rc = 0;
+ 
+ 	if (rc)
+-	    *failedFile = xstrdup(fpath);
++	    *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL);
+ 
+ 	if (rc == 0) {
+ 	    /* Notify on success. */
+@@ -1095,11 +1195,12 @@
+ 	    rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi);
+ 	    rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount);
+ 	}
+-	fpath = _free(fpath);
+     }
+ 
+-    free(fpath);
+-    rpmfiFree(fi);
++    for (int i = 0; i < fc; i++)
++	free(fdata[i].fpath);
++    free(fdata);
++    fsmIterFini(fi, &di);
+ 
+     return rc;
+ }
diff --git a/rpm.spec b/rpm.spec
index e14a0e8..59207c1 100644
--- a/rpm.spec
+++ b/rpm.spec
@@ -32,7 +32,7 @@
 
 %global rpmver 4.16.1.3
 #global snapver rc1
-%global rel 25
+%global rel 26
 %global sover 9
 
 %global srcver %{rpmver}%{?snapver:-%{snapver}}
@@ -94,6 +94,12 @@ Patch122: rpm-4.16.1.3-Support-long-languages-names-for-QT.patch
 Patch123: rpm-4.14.3-rpm2archive-parse-popt-options.patch
 Patch124: rpm-4.14.3-rpm2archive-Don-t-print-usage.patch
 Patch125: rpm-4.16.1.3-IMA-without-xattr.patch
+# Backport fsm to fix CVEs
+Patch126: 0001-Eliminate-code-duplication-from-rpmfiNext.patch
+Patch127: 0001-Add-optional-callback-on-directory-changes-during-rp.patch
+Patch128: 0001-Pass-file-descriptor-to-file-prepare-plugin-hook-use.patch
+Patch129: 0001-Swap-over-to-dirfd-basename-based-operation-within-t.patch
+Patch130: 0001-Use-file-state-machine-from-rpm-4.19.patch
 
 # These are not yet upstream
 Patch906: rpm-4.7.1-geode-i686.patch
@@ -643,6 +649,9 @@ fi
 %doc doc/librpm/html/*
 
 %changelog
+* Fri Nov 10 2023 Florian Festi <ffesti@redhat.com> - 4.16.1.3-26
+- Backport file handling code from rpm-4.19 to fix CVE-2021-35937,
+  CVE-2021-35938 and CVE-2021-35939
 
 * Fri Jun 30 2023 Florian Festi <ffesti@redhat.com> - 4.16.1.3-25
 - Followup on #2166383