Blob Blame History Raw
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;
 }