9ceacf
From 3a5d2ee0f56d049927c4dcdd03e8e275ac03e20d Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Mon, 8 Feb 2021 10:45:59 +0200
9ceacf
Subject: [PATCH 01/10] Clean up file unpack iteration logic a bit
9ceacf
9ceacf
Handle rpmfiNext() in the while-condition directly to make it more like
9ceacf
similar other constructs elsewhere, adjust for the end of iteration
9ceacf
code after the loop. Also take the file index from rpmfiNext() so
9ceacf
we don't need multiple calls to rpmfiFX() later.
9ceacf
---
9ceacf
 lib/fsm.c | 19 +++++++------------
9ceacf
 1 file changed, 7 insertions(+), 12 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index 432bcbd90..f71b9bc1e 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -865,6 +865,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     struct stat sb;
9ceacf
     int saveerrno = errno;
9ceacf
     int rc = 0;
9ceacf
+    int fx = -1;
9ceacf
     int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
9ceacf
     int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
9ceacf
     int firsthardlink = -1;
9ceacf
@@ -886,17 +887,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     /* Detect and create directories not explicitly in package. */
9ceacf
     rc = fsmMkdirs(files, fs, plugins);
9ceacf
 
9ceacf
-    while (!rc) {
9ceacf
-	/* Read next payload header. */
9ceacf
-	rc = rpmfiNext(fi);
9ceacf
-
9ceacf
-	if (rc < 0) {
9ceacf
-	    if (rc == RPMERR_ITER_END)
9ceacf
-		rc = 0;
9ceacf
-	    break;
9ceacf
-	}
9ceacf
-
9ceacf
-	action = rpmfsGetAction(fs, rpmfiFX(fi));
9ceacf
+    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
+	action = rpmfsGetAction(fs, fx);
9ceacf
 	skip = XFA_SKIPPING(action);
9ceacf
 	if (action != FA_TOUCH) {
9ceacf
 	    suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
9ceacf
@@ -920,7 +912,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	if (rc) {
9ceacf
 	    skip = 1;
9ceacf
 	} else {
9ceacf
-	    setFileState(fs, rpmfiFX(fi));
9ceacf
+	    setFileState(fs, fx);
9ceacf
 	}
9ceacf
 
9ceacf
         if (!skip) {
9ceacf
@@ -1022,6 +1014,9 @@ touch:
9ceacf
 	fpath = _free(fpath);
9ceacf
     }
9ceacf
 
9ceacf
+    if (!rc && fx != RPMERR_ITER_END)
9ceacf
+	rc = fx;
9ceacf
+
9ceacf
     rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
9ceacf
     rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
9ceacf
 
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 3096acbddc57eb2b65c96aad1ed3524ea9d0cead Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 08:25:28 +0200
9ceacf
Subject: [PATCH 02/10] Drop unused filename variable
9ceacf
9ceacf
---
9ceacf
 lib/fsm.c | 2 --
9ceacf
 1 file changed, 2 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index f71b9bc1e..8e6a6c08b 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -976,11 +976,9 @@ touch:
9ceacf
 	    /* On FA_TOUCH no hardlinks are created thus this is skipped. */
9ceacf
 	    /* we skip the hard linked file containing the content */
9ceacf
 	    /* write the content to the first used instead */
9ceacf
-	    char *fn = rpmfilesFN(files, firsthardlink);
9ceacf
 	    rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm);
9ceacf
 	    wfd_close(&firstlinkfile);
9ceacf
 	    firsthardlink = -1;
9ceacf
-	    free(fn);
9ceacf
 	}
9ceacf
 
9ceacf
         if (rc) {
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 9561315538f1fc4a5b207a43ac39805134e7153f Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 09:57:17 +0200
9ceacf
Subject: [PATCH 03/10] Don't update path info if rename failed on file commit
9ceacf
9ceacf
---
9ceacf
 lib/fsm.c | 16 +++++++++-------
9ceacf
 1 file changed, 9 insertions(+), 7 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index 8e6a6c08b..138b55297 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -797,14 +797,16 @@ static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *su
9ceacf
 	/* Rename temporary to final file name if needed. */
9ceacf
 	if (dest != *path) {
9ceacf
 	    rc = fsmRename(*path, dest);
9ceacf
-	    if (!rc && nsuffix) {
9ceacf
-		char * opath = fsmFsPath(fi, NULL);
9ceacf
-		rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
9ceacf
-		       opath, dest);
9ceacf
-		free(opath);
9ceacf
+	    if (!rc) {
9ceacf
+		if (nsuffix) {
9ceacf
+		    char * opath = fsmFsPath(fi, NULL);
9ceacf
+		    rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
9ceacf
+			   opath, dest);
9ceacf
+		    free(opath);
9ceacf
+		}
9ceacf
+		free(*path);
9ceacf
+		*path = dest;
9ceacf
 	    }
9ceacf
-	    free(*path);
9ceacf
-	    *path = dest;
9ceacf
 	}
9ceacf
     }
9ceacf
 
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 73d090166a60c57484e4be40e46fcb07f026cbf8 Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Thu, 27 Aug 2020 10:31:07 +0300
9ceacf
Subject: [PATCH 04/10] Upgrade FA_TOUCH to FA_CREATE if the file went away
9ceacf
 (RhBug:1872141)
9ceacf
9ceacf
When %_minimize_writes is enabled, we determine unchanged files during
9ceacf
fingerprinting and only update their metadata (FA_TOUCH) instead of
9ceacf
always recreating from scratch (FA_CREATE) during install. However
9ceacf
package scriptlets (and administrators) can and will do arbitrary stuff
9ceacf
in the meanwhile, such as rm -f their own files in %pre, hoping to
9ceacf
get a fresh copy of contents no matter what. Or something.
9ceacf
Now, if the file was determined to not need changing by rpm, this will
9ceacf
just fail with chown & friends trying to touch non-existent file.
9ceacf
One can consider this a case of package shooting itself in the foot, but
9ceacf
when a package update fails or succeeds depending on %_minimize_writes this
9ceacf
to me suggests the feature is at fault as much as the package.
9ceacf
9ceacf
Do fsmVerify() on all files to be FA_TOUCH'ed to detect files whose
9ceacf
type changed or were removed since fingerprinting. This still doesn't
9ceacf
ensure correctness if something tampers with the contents in the meanwhile,
9ceacf
(for that we'd need to run the file through the whole machinery again,
9ceacf
checksumming and all) but covers the most glaring cases.
9ceacf
---
9ceacf
 lib/fsm.c                      | 15 ++++++---
9ceacf
 tests/Makefile.am              |  1 +
9ceacf
 tests/data/SPECS/suicidal.spec | 19 ++++++++++++
9ceacf
 tests/rpmi.at                  | 56 ++++++++++++++++++++++++++++++++++
9ceacf
 4 files changed, 87 insertions(+), 4 deletions(-)
9ceacf
 create mode 100644 tests/data/SPECS/suicidal.spec
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index 138b55297..8a4ecaf05 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -920,10 +920,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
         if (!skip) {
9ceacf
 	    int setmeta = 1;
9ceacf
 
9ceacf
-	    /* When touching we don't need any of this... */
9ceacf
-	    if (action == FA_TOUCH)
9ceacf
-		goto touch;
9ceacf
-
9ceacf
 	    /* Directories replacing something need early backup */
9ceacf
 	    if (!suffix) {
9ceacf
 		rc = fsmBackup(fi, action);
9ceacf
@@ -935,6 +931,17 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 		rc = RPMERR_ENOENT;
9ceacf
 	    }
9ceacf
 
9ceacf
+	    /* See if the file was removed while our attention was elsewhere */
9ceacf
+	    if (rc == RPMERR_ENOENT && action == FA_TOUCH) {
9ceacf
+		rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", fpath);
9ceacf
+		action = FA_CREATE;
9ceacf
+		fsmDebug(fpath, action, &sb);
9ceacf
+	    }
9ceacf
+
9ceacf
+	    /* When touching we don't need any of this... */
9ceacf
+	    if (action == FA_TOUCH)
9ceacf
+		goto touch;
9ceacf
+
9ceacf
             if (S_ISREG(sb.st_mode)) {
9ceacf
 		if (rc == RPMERR_ENOENT) {
9ceacf
 		    rc = fsmMkfile(fi, fpath, files, psm, nodigest,
9ceacf
diff --git a/tests/Makefile.am b/tests/Makefile.am
9ceacf
index 7a6641d4f..d540e2a7b 100644
9ceacf
--- a/tests/Makefile.am
9ceacf
+++ b/tests/Makefile.am
9ceacf
@@ -59,6 +59,7 @@ EXTRA_DIST += data/SPECS/verifyscript.spec
9ceacf
 EXTRA_DIST += data/SPECS/fakeshell.spec
9ceacf
 EXTRA_DIST += data/SPECS/scripts.spec
9ceacf
 EXTRA_DIST += data/SPECS/selfconflict.spec
9ceacf
+EXTRA_DIST += data/SPECS/suicidal.spec
9ceacf
 EXTRA_DIST += data/SPECS/replacetest.spec
9ceacf
 EXTRA_DIST += data/SPECS/triggers.spec
9ceacf
 EXTRA_DIST += data/SPECS/filetriggers.spec
9ceacf
diff --git a/tests/data/SPECS/suicidal.spec b/tests/data/SPECS/suicidal.spec
9ceacf
new file mode 100644
9ceacf
index 000000000..77d17d8c9
9ceacf
--- /dev/null
9ceacf
+++ b/tests/data/SPECS/suicidal.spec
9ceacf
@@ -0,0 +1,19 @@
9ceacf
+Name: suicidal
9ceacf
+Version: 1
9ceacf
+Release: %{rel}
9ceacf
+License: GPL
9ceacf
+Group: Testing
9ceacf
+Summary: Testing suicidal package behavior
9ceacf
+BuildArch: noarch
9ceacf
+
9ceacf
+%description
9ceacf
+
9ceacf
+%build
9ceacf
+mkdir -p %{buildroot}/opt
9ceacf
+echo shoot > %{buildroot}/opt/foot
9ceacf
+
9ceacf
+%pre -p <lua>
9ceacf
+os.remove('/opt/foot')
9ceacf
+
9ceacf
+%files
9ceacf
+/opt/foot
9ceacf
diff --git a/tests/rpmi.at b/tests/rpmi.at
9ceacf
index e8d6e9b7a..71e17821b 100644
9ceacf
--- a/tests/rpmi.at
9ceacf
+++ b/tests/rpmi.at
9ceacf
@@ -711,3 +711,59 @@ runroot rpm -e testdoc
9ceacf
 [])
9ceacf
 AT_CLEANUP
9ceacf
 
9ceacf
+AT_SETUP([rpm -i --excludeartifacts])
9ceacf
+AT_KEYWORDS([install])
9ceacf
+RPMDB_INIT
9ceacf
+runroot rpmbuild --quiet -bb /data/SPECS/vattrtest.spec
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+runroot rpm -i --excludeartifacts /build/RPMS/noarch/vattrtest-1.0-1.noarch.rpm
9ceacf
+test -e ${RPMTEST}/opt/vattrtest/a && exit 1
9ceacf
+runroot rpm -e vattrtest
9ceacf
+runroot rpm -i /build/RPMS/noarch/vattrtest-1.0-1.noarch.rpm
9ceacf
+test -e ${RPMTEST}/opt/vattrtest/a || exit 1
9ceacf
+],
9ceacf
+[0],
9ceacf
+[],
9ceacf
+[])
9ceacf
+AT_CLEANUP
9ceacf
+
9ceacf
+AT_SETUP([rpm -U <suicidal>])
9ceacf
+AT_KEYWORDS([install])
9ceacf
+RPMDB_INIT
9ceacf
+
9ceacf
+for r in 1 2; do
9ceacf
+    runroot rpmbuild -bb --quiet \
9ceacf
+		--define "rel ${r}" \
9ceacf
+		/data/SPECS/suicidal.spec
9ceacf
+done
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+
9ceacf
+for r in 1 2; do
9ceacf
+    runroot rpm -U \
9ceacf
+	--define "_minimize_writes 0" \
9ceacf
+	/build/RPMS/noarch/suicidal-1-${r}.noarch.rpm
9ceacf
+done
9ceacf
+runroot rpm -V --nouser --nogroup suicidal
9ceacf
+],
9ceacf
+[0],
9ceacf
+[],
9ceacf
+[])
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+
9ceacf
+for r in 1 2; do
9ceacf
+    runroot rpm -U \
9ceacf
+	--define "_minimize_writes 1" \
9ceacf
+	/build/RPMS/noarch/suicidal-1-${r}.noarch.rpm
9ceacf
+done
9ceacf
+runroot rpm -V --nouser --nogroup suicidal
9ceacf
+],
9ceacf
+[0],
9ceacf
+[],
9ceacf
+[])
9ceacf
+AT_CLEANUP
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From e78ea489eeed84cdee9f5cedcbbf35cfcf1a70b6 Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 09:47:19 +0200
9ceacf
Subject: [PATCH 05/10] Refactor file install and remove around a common struct
9ceacf
9ceacf
Collect the common state info into a struct shared by both file install
9ceacf
and remove, update code accordingly. The change looks much more drastic
9ceacf
than it is - it's just adding fp-> prefix to a lot of places.
9ceacf
While we're at it, remember the state data throughout the operation.
9ceacf
9ceacf
No functional changes here, just paving way for the next steps which
9ceacf
will look clearer with these pre-requisites in place.
9ceacf
---
9ceacf
 lib/fsm.c | 158 +++++++++++++++++++++++++++++-------------------------
9ceacf
 1 file changed, 85 insertions(+), 73 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index 8a4ecaf05..ab15c2bf3 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -38,6 +38,14 @@ static int strict_erasures = 0;
9ceacf
 #define _dirPerms 0755
9ceacf
 #define _filePerms 0644
9ceacf
 
9ceacf
+struct filedata_s {
9ceacf
+    int skip;
9ceacf
+    rpmFileAction action;
9ceacf
+    const char *suffix;
9ceacf
+    char *fpath;
9ceacf
+    struct stat sb;
9ceacf
+};
9ceacf
+
9ceacf
 /* 
9ceacf
  * XXX Forward declarations for previously exported functions to avoid moving 
9ceacf
  * things around needlessly 
9ceacf
@@ -864,19 +872,16 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
9ceacf
     rpmfs fs = rpmteGetFileStates(te);
9ceacf
     rpmPlugins plugins = rpmtsPlugins(ts);
9ceacf
-    struct stat sb;
9ceacf
     int saveerrno = errno;
9ceacf
     int rc = 0;
9ceacf
     int fx = -1;
9ceacf
+    int fc = rpmfilesFC(files);
9ceacf
     int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
9ceacf
     int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
9ceacf
     int firsthardlink = -1;
9ceacf
     FD_t firstlinkfile = NULL;
9ceacf
-    int skip;
9ceacf
-    rpmFileAction action;
9ceacf
     char *tid = NULL;
9ceacf
-    const char *suffix;
9ceacf
-    char *fpath = NULL;
9ceacf
+    struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
9ceacf
 
9ceacf
     if (fi == NULL) {
9ceacf
 	rc = RPMERR_BAD_MAGIC;
9ceacf
@@ -890,96 +895,99 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     rc = fsmMkdirs(files, fs, plugins);
9ceacf
 
9ceacf
     while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
-	action = rpmfsGetAction(fs, fx);
9ceacf
-	skip = XFA_SKIPPING(action);
9ceacf
-	if (action != FA_TOUCH) {
9ceacf
-	    suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
9ceacf
+	struct filedata_s *fp = &fdata[fx];
9ceacf
+	fp->action = rpmfsGetAction(fs, fx);
9ceacf
+	fp->skip = XFA_SKIPPING(fp->action);
9ceacf
+	if (fp->action != FA_TOUCH) {
9ceacf
+	    fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
9ceacf
 	} else {
9ceacf
-	    suffix = NULL;
9ceacf
+	    fp->suffix = NULL;
9ceacf
 	}
9ceacf
-	fpath = fsmFsPath(fi, suffix);
9ceacf
+	fp->fpath = fsmFsPath(fi, fp->suffix);
9ceacf
 
9ceacf
 	/* Remap file perms, owner, and group. */
9ceacf
-	rc = rpmfiStat(fi, 1, &sb);
9ceacf
+	rc = rpmfiStat(fi, 1, &fp->sb);
9ceacf
 
9ceacf
-	fsmDebug(fpath, action, &sb);
9ceacf
+	fsmDebug(fp->fpath, fp->action, &fp->sb);
9ceacf
 
9ceacf
         /* Exit on error. */
9ceacf
         if (rc)
9ceacf
             break;
9ceacf
 
9ceacf
 	/* Run fsm file pre hook for all plugins */
9ceacf
-	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
9ceacf
-				      sb.st_mode, action);
9ceacf
+	rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
9ceacf
+				      fp->sb.st_mode, fp->action);
9ceacf
 	if (rc) {
9ceacf
-	    skip = 1;
9ceacf
+	    fp->skip = 1;
9ceacf
 	} else {
9ceacf
 	    setFileState(fs, fx);
9ceacf
 	}
9ceacf
 
9ceacf
-        if (!skip) {
9ceacf
+        if (!fp->skip) {
9ceacf
 	    int setmeta = 1;
9ceacf
 
9ceacf
 	    /* Directories replacing something need early backup */
9ceacf
-	    if (!suffix) {
9ceacf
-		rc = fsmBackup(fi, action);
9ceacf
+	    if (!fp->suffix) {
9ceacf
+		rc = fsmBackup(fi, fp->action);
9ceacf
 	    }
9ceacf
 	    /* Assume file does't exist when tmp suffix is in use */
9ceacf
-	    if (!suffix) {
9ceacf
-		rc = fsmVerify(fpath, fi);
9ceacf
+	    if (!fp->suffix) {
9ceacf
+		rc = fsmVerify(fp->fpath, fi);
9ceacf
 	    } else {
9ceacf
 		rc = RPMERR_ENOENT;
9ceacf
 	    }
9ceacf
 
9ceacf
 	    /* See if the file was removed while our attention was elsewhere */
9ceacf
-	    if (rc == RPMERR_ENOENT && action == FA_TOUCH) {
9ceacf
-		rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", fpath);
9ceacf
-		action = FA_CREATE;
9ceacf
-		fsmDebug(fpath, action, &sb);
9ceacf
+	    if (rc == RPMERR_ENOENT && fp->action == FA_TOUCH) {
9ceacf
+		rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n",
9ceacf
+			fp->fpath);
9ceacf
+		fp->action = FA_CREATE;
9ceacf
+		fsmDebug(fp->fpath, fp->action, &fp->sb);
9ceacf
 	    }
9ceacf
 
9ceacf
 	    /* When touching we don't need any of this... */
9ceacf
-	    if (action == FA_TOUCH)
9ceacf
+	    if (fp->action == FA_TOUCH)
9ceacf
 		goto touch;
9ceacf
 
9ceacf
-            if (S_ISREG(sb.st_mode)) {
9ceacf
+            if (S_ISREG(fp->sb.st_mode)) {
9ceacf
 		if (rc == RPMERR_ENOENT) {
9ceacf
-		    rc = fsmMkfile(fi, fpath, files, psm, nodigest,
9ceacf
+		    rc = fsmMkfile(fi, fp->fpath, files, psm, nodigest,
9ceacf
 				   &setmeta, &firsthardlink, &firstlinkfile);
9ceacf
 		}
9ceacf
-            } else if (S_ISDIR(sb.st_mode)) {
9ceacf
+            } else if (S_ISDIR(fp->sb.st_mode)) {
9ceacf
                 if (rc == RPMERR_ENOENT) {
9ceacf
-                    mode_t mode = sb.st_mode;
9ceacf
+                    mode_t mode = fp->sb.st_mode;
9ceacf
                     mode &= ~07777;
9ceacf
                     mode |=  00700;
9ceacf
-                    rc = fsmMkdir(fpath, mode);
9ceacf
+                    rc = fsmMkdir(fp->fpath, mode);
9ceacf
                 }
9ceacf
-            } else if (S_ISLNK(sb.st_mode)) {
9ceacf
+            } else if (S_ISLNK(fp->sb.st_mode)) {
9ceacf
 		if (rc == RPMERR_ENOENT) {
9ceacf
-		    rc = fsmSymlink(rpmfiFLink(fi), fpath);
9ceacf
+		    rc = fsmSymlink(rpmfiFLink(fi), fp->fpath);
9ceacf
 		}
9ceacf
-            } else if (S_ISFIFO(sb.st_mode)) {
9ceacf
+            } else if (S_ISFIFO(fp->sb.st_mode)) {
9ceacf
                 /* This mimics cpio S_ISSOCK() behavior but probably isn't right */
9ceacf
                 if (rc == RPMERR_ENOENT) {
9ceacf
-                    rc = fsmMkfifo(fpath, 0000);
9ceacf
+                    rc = fsmMkfifo(fp->fpath, 0000);
9ceacf
                 }
9ceacf
-            } else if (S_ISCHR(sb.st_mode) ||
9ceacf
-                       S_ISBLK(sb.st_mode) ||
9ceacf
-                       S_ISSOCK(sb.st_mode))
9ceacf
+            } else if (S_ISCHR(fp->sb.st_mode) ||
9ceacf
+                       S_ISBLK(fp->sb.st_mode) ||
9ceacf
+                       S_ISSOCK(fp->sb.st_mode))
9ceacf
             {
9ceacf
                 if (rc == RPMERR_ENOENT) {
9ceacf
-                    rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
9ceacf
+                    rc = fsmMknod(fp->fpath, fp->sb.st_mode, fp->sb.st_rdev);
9ceacf
                 }
9ceacf
             } else {
9ceacf
                 /* XXX Special case /dev/log, which shouldn't be packaged anyways */
9ceacf
-                if (!IS_DEV_LOG(fpath))
9ceacf
+                if (!IS_DEV_LOG(fp->fpath))
9ceacf
                     rc = RPMERR_UNKNOWN_FILETYPE;
9ceacf
             }
9ceacf
 
9ceacf
 touch:
9ceacf
 	    /* Set permissions, timestamps etc for non-hardlink entries */
9ceacf
 	    if (!rc && setmeta) {
9ceacf
-		rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps);
9ceacf
+		rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action,
9ceacf
+				&fp->sb, nofcaps);
9ceacf
 	    }
9ceacf
         } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
9ceacf
 	    /* On FA_TOUCH no hardlinks are created thus this is skipped. */
9ceacf
@@ -991,10 +999,10 @@ touch:
9ceacf
 	}
9ceacf
 
9ceacf
         if (rc) {
9ceacf
-            if (!skip) {
9ceacf
+            if (!fp->skip) {
9ceacf
                 /* XXX only erase if temp fn w suffix is in use */
9ceacf
-                if (suffix) {
9ceacf
-		    (void) fsmRemove(fpath, sb.st_mode);
9ceacf
+                if (fp->suffix) {
9ceacf
+		    (void) fsmRemove(fp->fpath, fp->sb.st_mode);
9ceacf
                 }
9ceacf
                 errno = saveerrno;
9ceacf
             }
9ceacf
@@ -1002,23 +1010,22 @@ touch:
9ceacf
 	    /* Notify on success. */
9ceacf
 	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
9ceacf
 
9ceacf
-	    if (!skip) {
9ceacf
+	    if (!fp->skip) {
9ceacf
 		/* Backup file if needed. Directories are handled earlier */
9ceacf
-		if (suffix)
9ceacf
-		    rc = fsmBackup(fi, action);
9ceacf
+		if (fp->suffix)
9ceacf
+		    rc = fsmBackup(fi, fp->action);
9ceacf
 
9ceacf
 		if (!rc)
9ceacf
-		    rc = fsmCommit(&fpath, fi, action, suffix);
9ceacf
+		    rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix);
9ceacf
 	    }
9ceacf
 	}
9ceacf
 
9ceacf
 	if (rc)
9ceacf
-	    *failedFile = xstrdup(fpath);
9ceacf
+	    *failedFile = xstrdup(fp->fpath);
9ceacf
 
9ceacf
 	/* Run fsm file post hook for all plugins */
9ceacf
-	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
9ceacf
-				  sb.st_mode, action, rc);
9ceacf
-	fpath = _free(fpath);
9ceacf
+	rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
9ceacf
+				  fp->sb.st_mode, fp->action, rc);
9ceacf
     }
9ceacf
 
9ceacf
     if (!rc && fx != RPMERR_ITER_END)
9ceacf
@@ -1034,7 +1041,9 @@ exit:
9ceacf
     rpmfiFree(fi);
9ceacf
     Fclose(payload);
9ceacf
     free(tid);
9ceacf
-    free(fpath);
9ceacf
+    for (int i = 0; i < fc; i++)
9ceacf
+	free(fdata[i].fpath);
9ceacf
+    free(fdata);
9ceacf
 
9ceacf
     return rc;
9ceacf
 }
9ceacf
@@ -1046,29 +1055,31 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK);
9ceacf
     rpmfs fs = rpmteGetFileStates(te);
9ceacf
     rpmPlugins plugins = rpmtsPlugins(ts);
9ceacf
-    struct stat sb;
9ceacf
+    int fc = rpmfilesFC(files);
9ceacf
+    int fx = -1;
9ceacf
+    struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
9ceacf
     int rc = 0;
9ceacf
-    char *fpath = NULL;
9ceacf
 
9ceacf
-    while (!rc && rpmfiNext(fi) >= 0) {
9ceacf
-	rpmFileAction action = rpmfsGetAction(fs, rpmfiFX(fi));
9ceacf
-	fpath = fsmFsPath(fi, NULL);
9ceacf
-	rc = fsmStat(fpath, 1, &sb);
9ceacf
+    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
+	struct filedata_s *fp = &fdata[fx];
9ceacf
+	fp->action = rpmfsGetAction(fs, rpmfiFX(fi));
9ceacf
+	fp->fpath = fsmFsPath(fi, NULL);
9ceacf
+	rc = fsmStat(fp->fpath, 1, &fp->sb);
9ceacf
 
9ceacf
-	fsmDebug(fpath, action, &sb);
9ceacf
+	fsmDebug(fp->fpath, fp->action, &fp->sb);
9ceacf
 
9ceacf
 	/* Run fsm file pre hook for all plugins */
9ceacf
-	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
9ceacf
-				      sb.st_mode, action);
9ceacf
+	rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
9ceacf
+				      fp->sb.st_mode, fp->action);
9ceacf
 
9ceacf
-	if (!XFA_SKIPPING(action))
9ceacf
-	    rc = fsmBackup(fi, action);
9ceacf
+	if (!XFA_SKIPPING(fp->action))
9ceacf
+	    rc = fsmBackup(fi, fp->action);
9ceacf
 
9ceacf
         /* Remove erased files. */
9ceacf
-        if (action == FA_ERASE) {
9ceacf
+        if (fp->action == FA_ERASE) {
9ceacf
 	    int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
9ceacf
 
9ceacf
-	    rc = fsmRemove(fpath, sb.st_mode);
9ceacf
+	    rc = fsmRemove(fp->fpath, fp->sb.st_mode);
9ceacf
 
9ceacf
 	    /*
9ceacf
 	     * Missing %ghost or %missingok entries are not errors.
9ceacf
@@ -1093,20 +1104,20 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	    if (rc) {
9ceacf
 		int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING;
9ceacf
 		rpmlog(lvl, _("%s %s: remove failed: %s\n"),
9ceacf
-			S_ISDIR(sb.st_mode) ? _("directory") : _("file"),
9ceacf
-			fpath, strerror(errno));
9ceacf
+			S_ISDIR(fp->sb.st_mode) ? _("directory") : _("file"),
9ceacf
+			fp->fpath, strerror(errno));
9ceacf
             }
9ceacf
         }
9ceacf
 
9ceacf
 	/* Run fsm file post hook for all plugins */
9ceacf
-	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
9ceacf
-				  sb.st_mode, action, rc);
9ceacf
+	rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
9ceacf
+				  fp->sb.st_mode, fp->action, rc);
9ceacf
 
9ceacf
         /* XXX Failure to remove is not (yet) cause for failure. */
9ceacf
         if (!strict_erasures) rc = 0;
9ceacf
 
9ceacf
 	if (rc)
9ceacf
-	    *failedFile = xstrdup(fpath);
9ceacf
+	    *failedFile = xstrdup(fp->fpath);
9ceacf
 
9ceacf
 	if (rc == 0) {
9ceacf
 	    /* Notify on success. */
9ceacf
@@ -1114,10 +1125,11 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	    rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi);
9ceacf
 	    rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount);
9ceacf
 	}
9ceacf
-	fpath = _free(fpath);
9ceacf
     }
9ceacf
 
9ceacf
-    free(fpath);
9ceacf
+    for (int i = 0; i < fc; i++)
9ceacf
+	free(fdata[i].fpath);
9ceacf
+    free(fdata);
9ceacf
     rpmfiFree(fi);
9ceacf
 
9ceacf
     return rc;
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 215525131b0e4e015d83c25828700d7531fec6dd Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 10:08:27 +0200
9ceacf
Subject: [PATCH 06/10] Refactor fsmMkfile() to take advantage of the new state
9ceacf
 struct
9ceacf
9ceacf
Move setmeta into the struct too (we'll want this later anyhow),
9ceacf
and now we only need to pass the struct to fsmMkfile(). One less
9ceacf
argument to pass around, it has way too many still.
9ceacf
9ceacf
No functional changes.
9ceacf
---
9ceacf
 lib/fsm.c | 22 +++++++++++-----------
9ceacf
 1 file changed, 11 insertions(+), 11 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index ab15c2bf3..6c873206c 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -39,6 +39,7 @@ static int strict_erasures = 0;
9ceacf
 #define _filePerms 0644
9ceacf
 
9ceacf
 struct filedata_s {
9ceacf
+    int setmeta;
9ceacf
     int skip;
9ceacf
     rpmFileAction action;
9ceacf
     const char *suffix;
9ceacf
@@ -279,8 +280,8 @@ exit:
9ceacf
     return rc;
9ceacf
 }
9ceacf
 
9ceacf
-static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
9ceacf
-		     rpmpsm psm, int nodigest, int *setmeta,
9ceacf
+static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files,
9ceacf
+		     rpmpsm psm, int nodigest,
9ceacf
 		     int * firsthardlink, FD_t *firstlinkfile)
9ceacf
 {
9ceacf
     int rc = 0;
9ceacf
@@ -290,11 +291,11 @@ static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
9ceacf
 	/* Create first hardlinked file empty */
9ceacf
 	if (*firsthardlink < 0) {
9ceacf
 	    *firsthardlink = rpmfiFX(fi);
9ceacf
-	    rc = wfd_open(firstlinkfile, dest);
9ceacf
+	    rc = wfd_open(firstlinkfile, fp->fpath);
9ceacf
 	} else {
9ceacf
 	    /* Create hard links for others */
9ceacf
 	    char *fn = rpmfilesFN(files, *firsthardlink);
9ceacf
-	    rc = link(fn, dest);
9ceacf
+	    rc = link(fn, fp->fpath);
9ceacf
 	    if (rc < 0) {
9ceacf
 		rc = RPMERR_LINK_FAILED;
9ceacf
 	    }
9ceacf
@@ -305,14 +306,14 @@ static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
9ceacf
        existing) file with content */
9ceacf
     if (numHardlinks<=1) {
9ceacf
 	if (!rc)
9ceacf
-	    rc = expandRegular(fi, dest, psm, nodigest);
9ceacf
+	    rc = expandRegular(fi, fp->fpath, psm, nodigest);
9ceacf
     } else if (rpmfiArchiveHasContent(fi)) {
9ceacf
 	if (!rc)
9ceacf
 	    rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
9ceacf
 	wfd_close(firstlinkfile);
9ceacf
 	*firsthardlink = -1;
9ceacf
     } else {
9ceacf
-	*setmeta = 0;
9ceacf
+	fp->setmeta = 0;
9ceacf
     }
9ceacf
 
9ceacf
     return rc;
9ceacf
@@ -898,6 +899,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	struct filedata_s *fp = &fdata[fx];
9ceacf
 	fp->action = rpmfsGetAction(fs, fx);
9ceacf
 	fp->skip = XFA_SKIPPING(fp->action);
9ceacf
+	fp->setmeta = 1;
9ceacf
 	if (fp->action != FA_TOUCH) {
9ceacf
 	    fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
9ceacf
 	} else {
9ceacf
@@ -924,8 +926,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	}
9ceacf
 
9ceacf
         if (!fp->skip) {
9ceacf
-	    int setmeta = 1;
9ceacf
-
9ceacf
 	    /* Directories replacing something need early backup */
9ceacf
 	    if (!fp->suffix) {
9ceacf
 		rc = fsmBackup(fi, fp->action);
9ceacf
@@ -951,8 +951,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 
9ceacf
             if (S_ISREG(fp->sb.st_mode)) {
9ceacf
 		if (rc == RPMERR_ENOENT) {
9ceacf
-		    rc = fsmMkfile(fi, fp->fpath, files, psm, nodigest,
9ceacf
-				   &setmeta, &firsthardlink, &firstlinkfile);
9ceacf
+		    rc = fsmMkfile(fi, fp, files, psm, nodigest,
9ceacf
+				   &firsthardlink, &firstlinkfile);
9ceacf
 		}
9ceacf
             } else if (S_ISDIR(fp->sb.st_mode)) {
9ceacf
                 if (rc == RPMERR_ENOENT) {
9ceacf
@@ -985,7 +985,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 
9ceacf
 touch:
9ceacf
 	    /* Set permissions, timestamps etc for non-hardlink entries */
9ceacf
-	    if (!rc && setmeta) {
9ceacf
+	    if (!rc && fp->setmeta) {
9ceacf
 		rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action,
9ceacf
 				&fp->sb, nofcaps);
9ceacf
 	    }
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 062167c2532a066301b15a0e85a1ac6cb6c07472 Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 10:24:22 +0200
9ceacf
Subject: [PATCH 07/10] Clarify file installation temporary suffix rule
9ceacf
9ceacf
We only use a temporary suffix for regular files that we are actually
9ceacf
creating, skipped and touched files should not have it. Add XFA_CREATING()
9ceacf
macro to accomppany XFA_SKIPPING() to easily check whether the file
9ceacf
is being created or something else.
9ceacf
9ceacf
No functional changes but makes the logic clearer.
9ceacf
---
9ceacf
 lib/fsm.c      | 5 +----
9ceacf
 lib/rpmfiles.h | 3 +++
9ceacf
 2 files changed, 4 insertions(+), 4 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index 6c873206c..e4842a200 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -900,11 +900,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	fp->action = rpmfsGetAction(fs, fx);
9ceacf
 	fp->skip = XFA_SKIPPING(fp->action);
9ceacf
 	fp->setmeta = 1;
9ceacf
-	if (fp->action != FA_TOUCH) {
9ceacf
+	if (XFA_CREATING(fp->action))
9ceacf
 	    fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
9ceacf
-	} else {
9ceacf
-	    fp->suffix = NULL;
9ceacf
-	}
9ceacf
 	fp->fpath = fsmFsPath(fi, fp->suffix);
9ceacf
 
9ceacf
 	/* Remap file perms, owner, and group. */
9ceacf
diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h
9ceacf
index 64b33281a..7f3f82662 100644
9ceacf
--- a/lib/rpmfiles.h
9ceacf
+++ b/lib/rpmfiles.h
9ceacf
@@ -90,6 +90,9 @@ typedef enum rpmFileAction_e {
9ceacf
 #define XFA_SKIPPING(_a)	\
9ceacf
     ((_a) == FA_SKIP || (_a) == FA_SKIPNSTATE || (_a) == FA_SKIPNETSHARED || (_a) == FA_SKIPCOLOR)
9ceacf
 
9ceacf
+#define XFA_CREATING(_a)	\
9ceacf
+    ((_a) == FA_CREATE || (_a) == FA_BACKUP || (_a) == FA_SAVE || (_a) == FA_ALTNAME)
9ceacf
+
9ceacf
 /**
9ceacf
  * We pass these around as an array with a sentinel.
9ceacf
  */
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From b3c6a3358dc4ec72fda0a229c22a5b79ac392848 Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 10:24:22 +0200
9ceacf
Subject: [PATCH 08/10] Clarify file installation temporary suffix rule
9ceacf
9ceacf
We only use a temporary suffix for regular files that we are actually
9ceacf
creating, skipped and touched files should not have it. Add XFA_CREATING()
9ceacf
macro to accomppany XFA_SKIPPING() to easily check whether the file
9ceacf
is being created or something else.
9ceacf
9ceacf
No functional changes but makes the logic clearer.
9ceacf
---
9ceacf
 lib/fsm.c | 4 ++--
9ceacf
 1 file changed, 2 insertions(+), 2 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index e4842a200..4d7c03895 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -900,8 +900,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	fp->action = rpmfsGetAction(fs, fx);
9ceacf
 	fp->skip = XFA_SKIPPING(fp->action);
9ceacf
 	fp->setmeta = 1;
9ceacf
-	if (XFA_CREATING(fp->action))
9ceacf
-	    fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
9ceacf
+	if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi)))
9ceacf
+	    fp->suffix = tid;
9ceacf
 	fp->fpath = fsmFsPath(fi, fp->suffix);
9ceacf
 
9ceacf
 	/* Remap file perms, owner, and group. */
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 9097021b19c658ff55f7515878bae0592fd86377 Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 11:25:10 +0200
9ceacf
Subject: [PATCH 09/10] Handle hardlink tracking with a file state pointer
9ceacf
9ceacf
No functional changes, just makes it a little cleaner as firstlink now
9ceacf
points to the actual file data instead of a index number somewhere.
9ceacf
---
9ceacf
 lib/fsm.c | 20 +++++++++-----------
9ceacf
 1 file changed, 9 insertions(+), 11 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index 4d7c03895..cef17e669 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -282,24 +282,22 @@ exit:
9ceacf
 
9ceacf
 static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files,
9ceacf
 		     rpmpsm psm, int nodigest,
9ceacf
-		     int * firsthardlink, FD_t *firstlinkfile)
9ceacf
+		     struct filedata_s ** firstlink, FD_t *firstlinkfile)
9ceacf
 {
9ceacf
     int rc = 0;
9ceacf
     int numHardlinks = rpmfiFNlink(fi);
9ceacf
 
9ceacf
     if (numHardlinks > 1) {
9ceacf
 	/* Create first hardlinked file empty */
9ceacf
-	if (*firsthardlink < 0) {
9ceacf
-	    *firsthardlink = rpmfiFX(fi);
9ceacf
+	if (*firstlink == NULL) {
9ceacf
+	    *firstlink = fp;
9ceacf
 	    rc = wfd_open(firstlinkfile, fp->fpath);
9ceacf
 	} else {
9ceacf
 	    /* Create hard links for others */
9ceacf
-	    char *fn = rpmfilesFN(files, *firsthardlink);
9ceacf
-	    rc = link(fn, fp->fpath);
9ceacf
+	    rc = link((*firstlink)->fpath, fp->fpath);
9ceacf
 	    if (rc < 0) {
9ceacf
 		rc = RPMERR_LINK_FAILED;
9ceacf
 	    }
9ceacf
-	    free(fn);
9ceacf
 	}
9ceacf
     }
9ceacf
     /* Write normal files or fill the last hardlinked (already
9ceacf
@@ -311,7 +309,7 @@ static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files,
9ceacf
 	if (!rc)
9ceacf
 	    rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
9ceacf
 	wfd_close(firstlinkfile);
9ceacf
-	*firsthardlink = -1;
9ceacf
+	*firstlink = NULL;
9ceacf
     } else {
9ceacf
 	fp->setmeta = 0;
9ceacf
     }
9ceacf
@@ -879,10 +877,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     int fc = rpmfilesFC(files);
9ceacf
     int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
9ceacf
     int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
9ceacf
-    int firsthardlink = -1;
9ceacf
     FD_t firstlinkfile = NULL;
9ceacf
     char *tid = NULL;
9ceacf
     struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
9ceacf
+    struct filedata_s *firstlink = NULL;
9ceacf
 
9ceacf
     if (fi == NULL) {
9ceacf
 	rc = RPMERR_BAD_MAGIC;
9ceacf
@@ -949,7 +947,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
             if (S_ISREG(fp->sb.st_mode)) {
9ceacf
 		if (rc == RPMERR_ENOENT) {
9ceacf
 		    rc = fsmMkfile(fi, fp, files, psm, nodigest,
9ceacf
-				   &firsthardlink, &firstlinkfile);
9ceacf
+				   &firstlink, &firstlinkfile);
9ceacf
 		}
9ceacf
             } else if (S_ISDIR(fp->sb.st_mode)) {
9ceacf
                 if (rc == RPMERR_ENOENT) {
9ceacf
@@ -986,13 +984,13 @@ touch:
9ceacf
 		rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action,
9ceacf
 				&fp->sb, nofcaps);
9ceacf
 	    }
9ceacf
-        } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
9ceacf
+        } else if (firstlink && rpmfiArchiveHasContent(fi)) {
9ceacf
 	    /* On FA_TOUCH no hardlinks are created thus this is skipped. */
9ceacf
 	    /* we skip the hard linked file containing the content */
9ceacf
 	    /* write the content to the first used instead */
9ceacf
 	    rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm);
9ceacf
 	    wfd_close(&firstlinkfile);
9ceacf
-	    firsthardlink = -1;
9ceacf
+	    firstlink = NULL;
9ceacf
 	}
9ceacf
 
9ceacf
         if (rc) {
9ceacf
-- 
9ceacf
2.34.1
9ceacf
9ceacf
9ceacf
From 357afe98316cb643a0757163ddbc276a071f1a18 Mon Sep 17 00:00:00 2001
9ceacf
From: Panu Matilainen <pmatilai@redhat.com>
9ceacf
Date: Wed, 10 Feb 2021 14:15:33 +0200
9ceacf
Subject: [PATCH 10/10] Handle file install failures more gracefully
9ceacf
9ceacf
Run the file installation in multiple stages:
9ceacf
1) gather intel
9ceacf
2) unpack the archive to temporary files
9ceacf
3) set file metadatas
9ceacf
4) commit files to final location
9ceacf
5) mop up leftovers on failure
9ceacf
9ceacf
This means we no longer leave behind a trail of untracked, potentially
9ceacf
harmful junk on installation failure.
9ceacf
9ceacf
If commit step fails the package can still be left in an inconsistent stage,
9ceacf
this could be further improved by first backing up old files to temporary
9ceacf
location to allow undo on failure, but leaving that for some other day.
9ceacf
Also unowned directories will still be left behind.
9ceacf
9ceacf
And yes, this is a somewhat scary change as it's the biggest ever change
9ceacf
to how rpm lays down files on install. Adopt the hardlink test spec
9ceacf
over to install tests and add some more tests for the new behavior.
9ceacf
9ceacf
Fixes: #967 (+ multiple reports over the years)
9ceacf
---
9ceacf
 lib/fsm.c                       | 147 ++++++++++++++++++++------------
9ceacf
 tests/data/SPECS/hlinktest.spec |   4 +
9ceacf
 tests/rpmbuild.at               |  33 -------
9ceacf
 tests/rpmi.at                   |  92 ++++++++++++++++++++
9ceacf
 4 files changed, 189 insertions(+), 87 deletions(-)
9ceacf
9ceacf
diff --git a/lib/fsm.c b/lib/fsm.c
9ceacf
index cef17e669..82af6f39f 100644
9ceacf
--- a/lib/fsm.c
9ceacf
+++ b/lib/fsm.c
9ceacf
@@ -38,7 +38,17 @@ static int strict_erasures = 0;
9ceacf
 #define _dirPerms 0755
9ceacf
 #define _filePerms 0644
9ceacf
 
9ceacf
+enum filestage_e {
9ceacf
+    FILE_COMMIT = -1,
9ceacf
+    FILE_NONE   = 0,
9ceacf
+    FILE_PRE    = 1,
9ceacf
+    FILE_UNPACK = 2,
9ceacf
+    FILE_PREP   = 3,
9ceacf
+    FILE_POST   = 4,
9ceacf
+};
9ceacf
+
9ceacf
 struct filedata_s {
9ceacf
+    int stage;
9ceacf
     int setmeta;
9ceacf
     int skip;
9ceacf
     rpmFileAction action;
9ceacf
@@ -868,10 +878,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
               rpmpsm psm, char ** failedFile)
9ceacf
 {
9ceacf
     FD_t payload = rpmtePayload(te);
9ceacf
-    rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
9ceacf
+    rpmfi fi = NULL;
9ceacf
     rpmfs fs = rpmteGetFileStates(te);
9ceacf
     rpmPlugins plugins = rpmtsPlugins(ts);
9ceacf
-    int saveerrno = errno;
9ceacf
     int rc = 0;
9ceacf
     int fx = -1;
9ceacf
     int fc = rpmfilesFC(files);
9ceacf
@@ -882,20 +891,17 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
     struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
9ceacf
     struct filedata_s *firstlink = NULL;
9ceacf
 
9ceacf
-    if (fi == NULL) {
9ceacf
-	rc = RPMERR_BAD_MAGIC;
9ceacf
-	goto exit;
9ceacf
-    }
9ceacf
-
9ceacf
     /* transaction id used for temporary path suffix while installing */
9ceacf
     rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
9ceacf
 
9ceacf
-    /* Detect and create directories not explicitly in package. */
9ceacf
-    rc = fsmMkdirs(files, fs, plugins);
9ceacf
-
9ceacf
+    /* Collect state data for the whole operation */
9ceacf
+    fi = rpmfilesIter(files, RPMFI_ITER_FWD);
9ceacf
     while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
 	struct filedata_s *fp = &fdata[fx];
9ceacf
-	fp->action = rpmfsGetAction(fs, fx);
9ceacf
+	if (rpmfiFFlags(fi) & RPMFILE_GHOST)
9ceacf
+            fp->action = FA_SKIP;
9ceacf
+	else
9ceacf
+	    fp->action = rpmfsGetAction(fs, fx);
9ceacf
 	fp->skip = XFA_SKIPPING(fp->action);
9ceacf
 	fp->setmeta = 1;
9ceacf
 	if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi)))
9ceacf
@@ -905,20 +911,32 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 	/* Remap file perms, owner, and group. */
9ceacf
 	rc = rpmfiStat(fi, 1, &fp->sb);
9ceacf
 
9ceacf
+	setFileState(fs, fx);
9ceacf
 	fsmDebug(fp->fpath, fp->action, &fp->sb);
9ceacf
 
9ceacf
-        /* Exit on error. */
9ceacf
-        if (rc)
9ceacf
-            break;
9ceacf
-
9ceacf
 	/* Run fsm file pre hook for all plugins */
9ceacf
 	rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
9ceacf
 				      fp->sb.st_mode, fp->action);
9ceacf
-	if (rc) {
9ceacf
-	    fp->skip = 1;
9ceacf
-	} else {
9ceacf
-	    setFileState(fs, fx);
9ceacf
-	}
9ceacf
+	fp->stage = FILE_PRE;
9ceacf
+    }
9ceacf
+    fi = rpmfiFree(fi);
9ceacf
+
9ceacf
+    if (rc)
9ceacf
+	goto exit;
9ceacf
+
9ceacf
+    fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
9ceacf
+    if (fi == NULL) {
9ceacf
+        rc = RPMERR_BAD_MAGIC;
9ceacf
+        goto exit;
9ceacf
+    }
9ceacf
+
9ceacf
+    /* Detect and create directories not explicitly in package. */
9ceacf
+    if (!rc)
9ceacf
+	rc = fsmMkdirs(files, fs, plugins);
9ceacf
+
9ceacf
+    /* Process the payload */
9ceacf
+    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
+	struct filedata_s *fp = &fdata[fx];
9ceacf
 
9ceacf
         if (!fp->skip) {
9ceacf
 	    /* Directories replacing something need early backup */
9ceacf
@@ -942,7 +960,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
 
9ceacf
 	    /* When touching we don't need any of this... */
9ceacf
 	    if (fp->action == FA_TOUCH)
9ceacf
-		goto touch;
9ceacf
+		continue;
9ceacf
 
9ceacf
             if (S_ISREG(fp->sb.st_mode)) {
9ceacf
 		if (rc == RPMERR_ENOENT) {
9ceacf
@@ -978,12 +996,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
9ceacf
                     rc = RPMERR_UNKNOWN_FILETYPE;
9ceacf
             }
9ceacf
 
9ceacf
-touch:
9ceacf
-	    /* Set permissions, timestamps etc for non-hardlink entries */
9ceacf
-	    if (!rc && fp->setmeta) {
9ceacf
-		rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action,
9ceacf
-				&fp->sb, nofcaps);
9ceacf
-	    }
9ceacf
         } else if (firstlink && rpmfiArchiveHasContent(fi)) {
9ceacf
 	    /* On FA_TOUCH no hardlinks are created thus this is skipped. */
9ceacf
 	    /* we skip the hard linked file containing the content */
9ceacf
@@ -993,47 +1005,74 @@ touch:
9ceacf
 	    firstlink = NULL;
9ceacf
 	}
9ceacf
 
9ceacf
-        if (rc) {
9ceacf
-            if (!fp->skip) {
9ceacf
-                /* XXX only erase if temp fn w suffix is in use */
9ceacf
-                if (fp->suffix) {
9ceacf
-		    (void) fsmRemove(fp->fpath, fp->sb.st_mode);
9ceacf
-                }
9ceacf
-                errno = saveerrno;
9ceacf
-            }
9ceacf
-        } else {
9ceacf
-	    /* Notify on success. */
9ceacf
+	/* Notify on success. */
9ceacf
+	if (rc)
9ceacf
+	    *failedFile = xstrdup(fp->fpath);
9ceacf
+	else
9ceacf
 	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
9ceacf
+	fp->stage = FILE_UNPACK;
9ceacf
+    }
9ceacf
+    fi = rpmfiFree(fi);
9ceacf
 
9ceacf
-	    if (!fp->skip) {
9ceacf
-		/* Backup file if needed. Directories are handled earlier */
9ceacf
-		if (fp->suffix)
9ceacf
-		    rc = fsmBackup(fi, fp->action);
9ceacf
+    if (!rc && fx < 0 && fx != RPMERR_ITER_END)
9ceacf
+	rc = fx;
9ceacf
 
9ceacf
-		if (!rc)
9ceacf
-		    rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix);
9ceacf
-	    }
9ceacf
+    /* Set permissions, timestamps etc for non-hardlink entries */
9ceacf
+    fi = rpmfilesIter(files, RPMFI_ITER_FWD);
9ceacf
+    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
+	struct filedata_s *fp = &fdata[fx];
9ceacf
+	if (!fp->skip && fp->setmeta) {
9ceacf
+	    rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action,
9ceacf
+			    &fp->sb, nofcaps);
9ceacf
 	}
9ceacf
-
9ceacf
 	if (rc)
9ceacf
 	    *failedFile = xstrdup(fp->fpath);
9ceacf
+	fp->stage = FILE_PREP;
9ceacf
+    }
9ceacf
+    fi = rpmfiFree(fi);
9ceacf
 
9ceacf
-	/* Run fsm file post hook for all plugins */
9ceacf
-	rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
9ceacf
-				  fp->sb.st_mode, fp->action, rc);
9ceacf
+    /* If all went well, commit files to final destination */
9ceacf
+    fi = rpmfilesIter(files, RPMFI_ITER_FWD);
9ceacf
+    while (!rc && (fx = rpmfiNext(fi)) >= 0) {
9ceacf
+	struct filedata_s *fp = &fdata[fx];
9ceacf
+
9ceacf
+	if (!fp->skip) {
9ceacf
+	    /* Backup file if needed. Directories are handled earlier */
9ceacf
+	    if (!rc && fp->suffix)
9ceacf
+		rc = fsmBackup(fi, fp->action);
9ceacf
+
9ceacf
+	    if (!rc)
9ceacf
+		rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix);
9ceacf
+
9ceacf
+	    if (!rc)
9ceacf
+		fp->stage = FILE_COMMIT;
9ceacf
+	    else
9ceacf
+		*failedFile = xstrdup(fp->fpath);
9ceacf
+	}
9ceacf
     }
9ceacf
+    fi = rpmfiFree(fi);
9ceacf
 
9ceacf
-    if (!rc && fx != RPMERR_ITER_END)
9ceacf
-	rc = fx;
9ceacf
+    /* Walk backwards in case we need to erase */
9ceacf
+    fi = rpmfilesIter(files, RPMFI_ITER_BACK);
9ceacf
+    while ((fx = rpmfiNext(fi)) >= 0) {
9ceacf
+	struct filedata_s *fp = &fdata[fx];
9ceacf
+	/* Run fsm file post hook for all plugins for all processed files */
9ceacf
+	if (fp->stage) {
9ceacf
+	    rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
9ceacf
+				      fp->sb.st_mode, fp->action, rc);
9ceacf
+	}
9ceacf
+
9ceacf
+	/* On failure, erase non-committed files */
9ceacf
+	if (rc && fp->stage > FILE_NONE && !fp->skip) {
9ceacf
+	    (void) fsmRemove(fp->fpath, fp->sb.st_mode);
9ceacf
+	}
9ceacf
+    }
9ceacf
 
9ceacf
     rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
9ceacf
     rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
9ceacf
 
9ceacf
 exit:
9ceacf
-
9ceacf
-    /* No need to bother with close errors on read */
9ceacf
-    rpmfiArchiveClose(fi);
9ceacf
-    rpmfiFree(fi);
9ceacf
+    fi = rpmfiFree(fi);
9ceacf
     Fclose(payload);
9ceacf
     free(tid);
9ceacf
     for (int i = 0; i < fc; i++)
9ceacf
diff --git a/tests/data/SPECS/hlinktest.spec b/tests/data/SPECS/hlinktest.spec
9ceacf
index b3b8866fc..1f453524e 100644
9ceacf
--- a/tests/data/SPECS/hlinktest.spec
9ceacf
+++ b/tests/data/SPECS/hlinktest.spec
9ceacf
@@ -1,5 +1,6 @@
9ceacf
 %bcond_with unpackaged_dirs
9ceacf
 %bcond_with unpackaged_files
9ceacf
+%bcond_with owned_dir
9ceacf
 
9ceacf
 Summary:          Testing hard link behavior
9ceacf
 Name:             hlinktest
9ceacf
@@ -38,4 +39,7 @@ touch $RPM_BUILD_ROOT/toot
9ceacf
 
9ceacf
 %files
9ceacf
 %defattr(-,root,root)
9ceacf
+%if %{with owned_dir}
9ceacf
+%dir /foo
9ceacf
+%endif
9ceacf
 /foo/*
9ceacf
diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at
9ceacf
index 4294fd97c..c99fa7a63 100644
9ceacf
--- a/tests/rpmbuild.at
9ceacf
+++ b/tests/rpmbuild.at
9ceacf
@@ -139,39 +139,6 @@ drwxrwxrwx zoot     zoot     /j/dir
9ceacf
 [])
9ceacf
 AT_CLEANUP
9ceacf
 
9ceacf
-# ------------------------------
9ceacf
-# hardlink tests
9ceacf
-AT_SETUP([rpmbuild hardlink])
9ceacf
-AT_KEYWORDS([build])
9ceacf
-AT_CHECK([
9ceacf
-RPMDB_CLEAR
9ceacf
-RPMDB_INIT
9ceacf
-rm -rf ${TOPDIR}
9ceacf
-
9ceacf
-runroot rpmbuild \
9ceacf
-  -bb --quiet /data/SPECS/hlinktest.spec
9ceacf
-
9ceacf
-runroot rpm -i /build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm
9ceacf
-
9ceacf
-runroot rpm -q --qf "[[%{filenlinks} %{filenames}\n]]%{longsize}\n" hlinktest
9ceacf
-runroot rpm -V --nouser --nogroup hlinktest
9ceacf
-ls -i "${RPMTEST}"/foo/hello* | awk {'print $1'} | sort -u | wc -l
9ceacf
-
9ceacf
-],
9ceacf
-[0],
9ceacf
-[2 /foo/aaaa
9ceacf
-1 /foo/copyllo
9ceacf
-4 /foo/hello
9ceacf
-4 /foo/hello-bar
9ceacf
-4 /foo/hello-foo
9ceacf
-4 /foo/hello-world
9ceacf
-2 /foo/zzzz
9ceacf
-87
9ceacf
-1
9ceacf
-],
9ceacf
-[])
9ceacf
-AT_CLEANUP
9ceacf
-
9ceacf
 AT_SETUP([rpmbuild unpackaged files])
9ceacf
 AT_KEYWORDS([build])
9ceacf
 AT_CHECK([
9ceacf
diff --git a/tests/rpmi.at b/tests/rpmi.at
9ceacf
index 71e17821b..bf1606b6d 100644
9ceacf
--- a/tests/rpmi.at
9ceacf
+++ b/tests/rpmi.at
9ceacf
@@ -767,3 +767,95 @@ runroot rpm -V --nouser --nogroup suicidal
9ceacf
 [],
9ceacf
 [])
9ceacf
 AT_CLEANUP
9ceacf
+
9ceacf
+# ------------------------------
9ceacf
+# hardlink tests
9ceacf
+AT_SETUP([rpm -i hardlinks])
9ceacf
+AT_KEYWORDS([build install])
9ceacf
+RPMDB_INIT
9ceacf
+
9ceacf
+# Need a reproducable test package
9ceacf
+runroot rpmbuild \
9ceacf
+  --define "%optflags -O2 -g" \
9ceacf
+  --define "%_target_platform noarch-linux" \
9ceacf
+  --define "%_binary_payload w.ufdio" \
9ceacf
+  --define "%_buildhost localhost" \
9ceacf
+  --define "%use_source_date_epoch_as_buildtime 1" \
9ceacf
+  --define "%source_date_epoch_from_changelog 1" \
9ceacf
+  --define "%clamp_mtime_to_source_date_epoch 1" \
9ceacf
+  --with owned_dir \
9ceacf
+  -bb --quiet /data/SPECS/hlinktest.spec
9ceacf
+
9ceacf
+pkg="/build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm"
9ceacf
+
9ceacf
+cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/1.rpm"
9ceacf
+dd if=/dev/zero of="${RPMTEST}/tmp/1.rpm" \
9ceacf
+   conv=notrunc bs=1 seek=8180 count=6 2> /dev/null
9ceacf
+
9ceacf
+cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/2.rpm"
9ceacf
+dd if=/dev/zero of="${RPMTEST}/tmp/2.rpm" \
9ceacf
+   conv=notrunc bs=1 seek=8150 count=6 2> /dev/null
9ceacf
+
9ceacf
+cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/3.rpm"
9ceacf
+dd if=/dev/zero of="${RPMTEST}/tmp/3.rpm" \
9ceacf
+   conv=notrunc bs=1 seek=8050 count=6 2> /dev/null
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+runroot rpm -i --noverify /tmp/1.rpm
9ceacf
+# test that nothing of the contents remains after failure
9ceacf
+test -d "${RPMTEST}/foo"
9ceacf
+],
9ceacf
+[1],
9ceacf
+[],
9ceacf
+[error: unpacking of archive failed: cpio: Archive file not in header
9ceacf
+error: hlinktest-1.0-1.noarch: install failed
9ceacf
+])
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+runroot rpm -i --noverify /tmp/2.rpm
9ceacf
+# test that nothing of the contents remains after failure
9ceacf
+test -d "${RPMTEST}/foo"
9ceacf
+],
9ceacf
+[1],
9ceacf
+[],
9ceacf
+[error: unpacking of archive failed: cpio: Bad/unreadable  header
9ceacf
+error: hlinktest-1.0-1.noarch: install failed
9ceacf
+])
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+runroot rpm -i --noverify /tmp/3.rpm 2>&1| sed 's/;.*:/:/g'
9ceacf
+# test that nothing of the contents remains after failure
9ceacf
+test -d "${RPMTEST}/foo"
9ceacf
+],
9ceacf
+[1],
9ceacf
+[error: unpacking of archive failed on file /foo/hello-world: Digest mismatch
9ceacf
+error: hlinktest-1.0-1.noarch: install failed
9ceacf
+],
9ceacf
+[])
9ceacf
+
9ceacf
+AT_CHECK([
9ceacf
+RPMDB_INIT
9ceacf
+runroot rpm -i /build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm
9ceacf
+runroot rpm -q --qf "[[%{filenlinks} %{filenames}\n]]%{longsize}\n" hlinktest
9ceacf
+ls -i "${RPMTEST}"/foo/hello* | awk {'print $1'} | sort -u | wc -l
9ceacf
+runroot rpm -e hlinktest
9ceacf
+
9ceacf
+],
9ceacf
+[0],
9ceacf
+[1 /foo
9ceacf
+2 /foo/aaaa
9ceacf
+1 /foo/copyllo
9ceacf
+4 /foo/hello
9ceacf
+4 /foo/hello-bar
9ceacf
+4 /foo/hello-foo
9ceacf
+4 /foo/hello-world
9ceacf
+2 /foo/zzzz
9ceacf
+87
9ceacf
+1
9ceacf
+],
9ceacf
+[])
9ceacf
+AT_CLEANUP
9ceacf
+
9ceacf
-- 
9ceacf
2.34.1
9ceacf