diff --git a/1534.patch b/1534.patch new file mode 100644 index 0000000..e2a7cf6 --- /dev/null +++ b/1534.patch @@ -0,0 +1,1162 @@ +From 22d18785b2c41f1a8937b712920b4342f7596a7e Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Mon, 8 Feb 2021 10:45:59 +0200 +Subject: [PATCH 1/9] Clean up file unpack iteration logic a bit + +Handle rpmfiNext() in the while-condition directly to make it more like +similar other constructs elsewhere, adjust for the end of iteration +code after the loop. Also take the file index from rpmfiNext() so +we don't need multiple calls to rpmfiFX() later. +--- + lib/fsm.c | 19 +++++++------------ + 1 file changed, 7 insertions(+), 12 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 35dcda081c..7c291adb02 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -841,6 +841,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct stat sb; + int saveerrno = errno; + int rc = 0; ++ int fx = -1; + int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0; + int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0; + int firsthardlink = -1; +@@ -862,17 +863,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Detect and create directories not explicitly in package. */ + rc = fsmMkdirs(files, fs, plugins); + +- while (!rc) { +- /* Read next payload header. */ +- rc = rpmfiNext(fi); +- +- if (rc < 0) { +- if (rc == RPMERR_ITER_END) +- rc = 0; +- break; +- } +- +- action = rpmfsGetAction(fs, rpmfiFX(fi)); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ action = rpmfsGetAction(fs, fx); + skip = XFA_SKIPPING(action); + if (action != FA_TOUCH) { + suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; +@@ -896,7 +888,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (rc) { + skip = 1; + } else { +- setFileState(fs, rpmfiFX(fi)); ++ setFileState(fs, fx); + } + + if (!skip) { +@@ -1005,6 +997,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + fpath = _free(fpath); + } + ++ if (!rc && fx != RPMERR_ITER_END) ++ rc = fx; ++ + rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ)); + rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST)); + + +From c0dc57b820791dd76ce8baafac59b9a58ab0f0d3 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 08:25:28 +0200 +Subject: [PATCH 2/9] Drop unused filename variable + +--- + lib/fsm.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 7c291adb02..41b6267ddc 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -959,11 +959,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* 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) { + +From dcb5791066afd5caa1003a5d35903a7f5dc79cc2 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 09:57:17 +0200 +Subject: [PATCH 3/9] Don't update path info if rename failed on file commit + +--- + lib/fsm.c | 16 +++++++++------- + 1 file changed, 9 insertions(+), 7 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 41b6267ddc..c581a918a5 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -773,14 +773,16 @@ static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *su + /* 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); ++ if (!rc) { ++ if (nsuffix) { ++ char * opath = fsmFsPath(fi, NULL); ++ rpmlog(RPMLOG_WARNING, _("%s created as %s\n"), ++ opath, dest); ++ free(opath); ++ } ++ free(*path); ++ *path = dest; + } +- free(*path); +- *path = dest; + } + } + + +From 7fdf248e7d29a244b97c46b19be0df64992fdfae Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 09:47:19 +0200 +Subject: [PATCH 4/9] Refactor file install and remove around a common struct + +Collect the common state info into a struct shared by both file install +and remove, update code accordingly. The change looks much more drastic +than it is - it's just adding fp-> prefix to a lot of places. +While we're at it, remember the state data throughout the operation. + +No functional changes here, just paving way for the next steps which +will look clearer with these pre-requisites in place. +--- + lib/fsm.c | 158 +++++++++++++++++++++++++++++------------------------- + 1 file changed, 85 insertions(+), 73 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index c581a918a5..9dba30560f 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -38,6 +38,14 @@ static int strict_erasures = 0; + #define _dirPerms 0755 + #define _filePerms 0644 + ++struct filedata_s { ++ 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 +@@ -840,19 +848,16 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); + 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; + char *tid = NULL; +- const char *suffix; +- char *fpath = NULL; ++ struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + + if (fi == NULL) { + rc = RPMERR_BAD_MAGIC; +@@ -866,96 +871,99 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rc = fsmMkdirs(files, fs, plugins); + + while (!rc && (fx = rpmfiNext(fi)) >= 0) { +- action = rpmfsGetAction(fs, fx); +- skip = XFA_SKIPPING(action); +- if (action != FA_TOUCH) { +- suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; ++ struct filedata_s *fp = &fdata[fx]; ++ fp->action = rpmfsGetAction(fs, fx); ++ fp->skip = XFA_SKIPPING(fp->action); ++ if (fp->action != FA_TOUCH) { ++ fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; + } else { +- suffix = NULL; ++ fp->suffix = NULL; + } +- fpath = fsmFsPath(fi, suffix); ++ fp->fpath = fsmFsPath(fi, fp->suffix); + + /* Remap file perms, owner, and group. */ +- rc = rpmfiStat(fi, 1, &sb); ++ rc = rpmfiStat(fi, 1, &fp->sb); + +- fsmDebug(fpath, action, &sb); ++ fsmDebug(fp->fpath, fp->action, &fp->sb); + + /* Exit on error. */ + if (rc) + break; + + /* 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 (rc) { +- skip = 1; ++ fp->skip = 1; + } else { + setFileState(fs, fx); + } + +- if (!skip) { ++ if (!fp->skip) { + int setmeta = 1; + + /* Directories replacing something need early backup */ +- if (!suffix) { +- rc = fsmBackup(fi, action); ++ if (!fp->suffix) { ++ rc = fsmBackup(fi, fp->action); + } + /* Assume file does't exist when tmp suffix is in use */ +- if (!suffix) { +- rc = fsmVerify(fpath, fi); ++ if (!fp->suffix) { ++ rc = fsmVerify(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) ++ if (fp->action == FA_TOUCH) + goto touch; + +- if (S_ISREG(sb.st_mode)) { ++ if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfile(fi, fpath, files, psm, nodigest, ++ rc = fsmMkfile(fi, fp->fpath, files, psm, nodigest, + &setmeta, &firsthardlink, &firstlinkfile); + } +- } 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(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), 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(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(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); ++ rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, ++ &fp->sb, nofcaps); + } + } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) { + /* On FA_TOUCH no hardlinks are created thus this is skipped. */ +@@ -967,10 +975,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + } + + if (rc) { +- if (!skip) { ++ if (!fp->skip) { + /* XXX only erase if temp fn w suffix is in use */ +- if (suffix) { +- (void) fsmRemove(fpath, sb.st_mode); ++ if (fp->suffix) { ++ (void) fsmRemove(fp->fpath, fp->sb.st_mode); + } + errno = saveerrno; + } +@@ -978,23 +986,22 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Notify on success. */ + rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); + +- if (!skip) { ++ if (!fp->skip) { + /* Backup file if needed. Directories are handled earlier */ +- if (suffix) +- rc = fsmBackup(fi, action); ++ if (fp->suffix) ++ rc = fsmBackup(fi, fp->action); + + if (!rc) +- rc = fsmCommit(&fpath, fi, action, suffix); ++ rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix); + } + } + + if (rc) +- *failedFile = xstrdup(fpath); ++ *failedFile = xstrdup(fp->fpath); + + /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, fi, fpath, +- sb.st_mode, action, rc); +- fpath = _free(fpath); ++ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action, rc); + } + + if (!rc && fx != RPMERR_ITER_END) +@@ -1010,7 +1017,9 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rpmfiFree(fi); + Fclose(payload); + free(tid); +- free(fpath); ++ for (int i = 0; i < fc; i++) ++ free(fdata[i].fpath); ++ free(fdata); + + return rc; + } +@@ -1022,29 +1031,31 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK); + 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)); ++ fp->fpath = fsmFsPath(fi, NULL); ++ rc = fsmStat(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); ++ if (!XFA_SKIPPING(fp->action)) ++ rc = fsmBackup(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(fp->fpath, fp->sb.st_mode); + + /* + * Missing %ghost or %missingok entries are not errors. +@@ -1069,20 +1080,20 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + 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 = xstrdup(fp->fpath); + + if (rc == 0) { + /* Notify on success. */ +@@ -1090,10 +1101,11 @@ int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, + rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi); + rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount); + } +- fpath = _free(fpath); + } + +- free(fpath); ++ for (int i = 0; i < fc; i++) ++ free(fdata[i].fpath); ++ free(fdata); + rpmfiFree(fi); + + return rc; + +From 202c9e9cd2e199b7e7c9655704a98ab2d4fad69c Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 10:08:27 +0200 +Subject: [PATCH 5/9] Refactor fsmMkfile() to take advantage of the new state + struct + +Move setmeta into the struct too (we'll want this later anyhow), +and now we only need to pass the struct to fsmMkfile(). One less +argument to pass around, it has way too many still. + +No functional changes. +--- + lib/fsm.c | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 9dba30560f..80ca234b1e 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -39,6 +39,7 @@ static int strict_erasures = 0; + #define _filePerms 0644 + + struct filedata_s { ++ int setmeta; + int skip; + rpmFileAction action; + const char *suffix; +@@ -279,8 +280,8 @@ static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest) + return rc; + } + +-static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, +- rpmpsm psm, int nodigest, int *setmeta, ++static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files, ++ rpmpsm psm, int nodigest, + int * firsthardlink, FD_t *firstlinkfile) + { + int rc = 0; +@@ -290,11 +291,11 @@ static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, + /* Create first hardlinked file empty */ + if (*firsthardlink < 0) { + *firsthardlink = rpmfiFX(fi); +- rc = wfd_open(firstlinkfile, dest); ++ rc = wfd_open(firstlinkfile, fp->fpath); + } else { + /* Create hard links for others */ + char *fn = rpmfilesFN(files, *firsthardlink); +- rc = link(fn, dest); ++ rc = link(fn, fp->fpath); + if (rc < 0) { + rc = RPMERR_LINK_FAILED; + } +@@ -305,14 +306,14 @@ static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files, + existing) file with content */ + if (numHardlinks<=1) { + if (!rc) +- rc = expandRegular(fi, dest, psm, nodigest); ++ rc = expandRegular(fi, fp->fpath, psm, nodigest); + } else if (rpmfiArchiveHasContent(fi)) { + if (!rc) + rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm); + wfd_close(firstlinkfile); + *firsthardlink = -1; + } else { +- *setmeta = 0; ++ fp->setmeta = 0; + } + + return rc; +@@ -874,6 +875,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct filedata_s *fp = &fdata[fx]; + fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); ++ fp->setmeta = 1; + if (fp->action != FA_TOUCH) { + fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; + } else { +@@ -900,8 +902,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + } + + if (!fp->skip) { +- int setmeta = 1; +- + /* Directories replacing something need early backup */ + if (!fp->suffix) { + rc = fsmBackup(fi, fp->action); +@@ -927,8 +927,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +- rc = fsmMkfile(fi, fp->fpath, files, psm, nodigest, +- &setmeta, &firsthardlink, &firstlinkfile); ++ rc = fsmMkfile(fi, fp, files, psm, nodigest, ++ &firsthardlink, &firstlinkfile); + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -961,7 +961,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + touch: + /* Set permissions, timestamps etc for non-hardlink entries */ +- if (!rc && setmeta) { ++ if (!rc && fp->setmeta) { + rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, + &fp->sb, nofcaps); + } + +From f014fc46325efe92b79841145f8dc0cb40896c64 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 10:24:22 +0200 +Subject: [PATCH 6/9] Clarify file installation temporary suffix rule + +We only use a temporary suffix for regular files that we are actually +creating, skipped and touched files should not have it. Add XFA_CREATING() +macro to accomppany XFA_SKIPPING() to easily check whether the file +is being created or something else. + +No functional changes but makes the logic clearer. +--- + lib/fsm.c | 5 +---- + lib/rpmfiles.h | 3 +++ + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 80ca234b1e..554ea712f5 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -876,11 +876,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); + fp->setmeta = 1; +- if (fp->action != FA_TOUCH) { ++ if (XFA_CREATING(fp->action)) + fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; +- } else { +- fp->suffix = NULL; +- } + fp->fpath = fsmFsPath(fi, fp->suffix); + + /* Remap file perms, owner, and group. */ +diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h +index 7ce1712323..e0adbd8aff 100644 +--- a/lib/rpmfiles.h ++++ b/lib/rpmfiles.h +@@ -121,6 +121,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. + */ + +From fc54439f8a10901cd72028cdbdb924e3e7501624 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 10:24:22 +0200 +Subject: [PATCH 7/9] Clarify file installation temporary suffix rule + +We only use a temporary suffix for regular files that we are actually +creating, skipped and touched files should not have it. Add XFA_CREATING() +macro to accomppany XFA_SKIPPING() to easily check whether the file +is being created or something else. + +No functional changes but makes the logic clearer. +--- + lib/fsm.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 554ea712f5..094f5e2bb6 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -876,8 +876,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); + fp->setmeta = 1; +- if (XFA_CREATING(fp->action)) +- fp->suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid; ++ if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi))) ++ fp->suffix = tid; + fp->fpath = fsmFsPath(fi, fp->suffix); + + /* Remap file perms, owner, and group. */ + +From 9294ffa898bc7f1f77ff80c0a3f4f0f70c4dfef2 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 11:25:10 +0200 +Subject: [PATCH 8/9] Handle hardlink tracking with a file state pointer + +No functional changes, just makes it a little cleaner as firstlink now +points to the actual file data instead of a index number somewhere. +--- + lib/fsm.c | 20 +++++++++----------- + 1 file changed, 9 insertions(+), 11 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index 094f5e2bb6..f86383a986 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -282,24 +282,22 @@ static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest) + + static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files, + rpmpsm psm, int nodigest, +- int * firsthardlink, FD_t *firstlinkfile) ++ struct filedata_s ** firstlink, FD_t *firstlinkfile) + { + int rc = 0; + int numHardlinks = rpmfiFNlink(fi); + + if (numHardlinks > 1) { + /* Create first hardlinked file empty */ +- if (*firsthardlink < 0) { +- *firsthardlink = rpmfiFX(fi); ++ if (*firstlink == NULL) { ++ *firstlink = fp; + rc = wfd_open(firstlinkfile, fp->fpath); + } else { + /* Create hard links for others */ +- char *fn = rpmfilesFN(files, *firsthardlink); +- rc = link(fn, fp->fpath); ++ rc = link((*firstlink)->fpath, fp->fpath); + if (rc < 0) { + rc = RPMERR_LINK_FAILED; + } +- free(fn); + } + } + /* Write normal files or fill the last hardlinked (already +@@ -311,7 +309,7 @@ static int fsmMkfile(rpmfi fi, struct filedata_s *fp, rpmfiles files, + if (!rc) + rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm); + wfd_close(firstlinkfile); +- *firsthardlink = -1; ++ *firstlink = NULL; + } else { + fp->setmeta = 0; + } +@@ -855,10 +853,10 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + 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; + char *tid = NULL; + struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); ++ struct filedata_s *firstlink = NULL; + + if (fi == NULL) { + rc = RPMERR_BAD_MAGIC; +@@ -925,7 +923,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { + rc = fsmMkfile(fi, fp, files, psm, nodigest, +- &firsthardlink, &firstlinkfile); ++ &firstlink, &firstlinkfile); + } + } else if (S_ISDIR(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -962,13 +960,13 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, + &fp->sb, nofcaps); + } +- } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) { ++ } else if (firstlink && 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 */ + rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm); + wfd_close(&firstlinkfile); +- firsthardlink = -1; ++ firstlink = NULL; + } + + if (rc) { + +From d6a9a00396a89d14858c6b6e2548eca2065c1d64 Mon Sep 17 00:00:00 2001 +From: Panu Matilainen +Date: Wed, 10 Feb 2021 14:15:33 +0200 +Subject: [PATCH 9/9] Handle file install failures more gracefully + +Run the file installation in multiple stages: +1) gather intel +2) unpack the archive to temporary files +3) set file metadatas +4) commit files to final location +5) mop up leftovers on failure + +This means we no longer leave behind a trail of untracked, potentially +harmful junk on installation failure. + +If commit step fails the package can still be left in an inconsistent stage, +this could be further improved by first backing up old files to temporary +location to allow undo on failure, but leaving that for some other day. +Also unowned directories will still be left behind. + +And yes, this is a somewhat scary change as it's the biggest ever change +to how rpm lays down files on install. Adopt the hardlink test spec +over to install tests and add some more tests for the new behavior. + +Fixes: #967 (+ multiple reports over the years) +--- + lib/fsm.c | 147 ++++++++++++++++++++------------ + tests/data/SPECS/hlinktest.spec | 4 + + tests/rpmbuild.at | 32 ------- + tests/rpmi.at | 92 ++++++++++++++++++++ + 4 files changed, 189 insertions(+), 86 deletions(-) + +diff --git a/lib/fsm.c b/lib/fsm.c +index f86383a986..6efd25bddd 100644 +--- a/lib/fsm.c ++++ b/lib/fsm.c +@@ -38,7 +38,17 @@ static int strict_erasures = 0; + #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; +@@ -844,10 +854,9 @@ 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); +- int saveerrno = errno; + int rc = 0; + int fx = -1; + int fc = rpmfilesFC(files); +@@ -858,20 +867,17 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); + struct filedata_s *firstlink = NULL; + +- if (fi == NULL) { +- rc = RPMERR_BAD_MAGIC; +- goto exit; +- } +- + /* 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]; +- fp->action = rpmfsGetAction(fs, fx); ++ if (rpmfiFFlags(fi) & RPMFILE_GHOST) ++ fp->action = FA_SKIP; ++ else ++ fp->action = rpmfsGetAction(fs, fx); + fp->skip = XFA_SKIPPING(fp->action); + fp->setmeta = 1; + if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi))) +@@ -881,20 +887,32 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + /* Remap file perms, owner, and group. */ + rc = rpmfiStat(fi, 1, &fp->sb); + ++ setFileState(fs, fx); + fsmDebug(fp->fpath, fp->action, &fp->sb); + +- /* Exit on error. */ +- if (rc) +- break; +- + /* Run fsm file pre hook for all plugins */ + rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, + fp->sb.st_mode, fp->action); +- if (rc) { +- fp->skip = 1; +- } else { +- setFileState(fs, fx); +- } ++ fp->stage = FILE_PRE; ++ } ++ fi = rpmfiFree(fi); ++ ++ if (rc) ++ goto exit; ++ ++ fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE); ++ if (fi == NULL) { ++ rc = RPMERR_BAD_MAGIC; ++ goto exit; ++ } ++ ++ /* Detect and create directories not explicitly in package. */ ++ if (!rc) ++ rc = fsmMkdirs(files, fs, plugins); ++ ++ /* Process the payload */ ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; + + if (!fp->skip) { + /* Directories replacing something need early backup */ +@@ -918,7 +936,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + + /* When touching we don't need any of this... */ + if (fp->action == FA_TOUCH) +- goto touch; ++ continue; + + if (S_ISREG(fp->sb.st_mode)) { + if (rc == RPMERR_ENOENT) { +@@ -954,12 +972,6 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + rc = RPMERR_UNKNOWN_FILETYPE; + } + +-touch: +- /* Set permissions, timestamps etc for non-hardlink entries */ +- if (!rc && fp->setmeta) { +- rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, +- &fp->sb, nofcaps); +- } + } else if (firstlink && rpmfiArchiveHasContent(fi)) { + /* On FA_TOUCH no hardlinks are created thus this is skipped. */ + /* we skip the hard linked file containing the content */ +@@ -969,47 +981,74 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files, + firstlink = NULL; + } + +- if (rc) { +- if (!fp->skip) { +- /* XXX only erase if temp fn w suffix is in use */ +- if (fp->suffix) { +- (void) fsmRemove(fp->fpath, fp->sb.st_mode); +- } +- errno = saveerrno; +- } +- } else { +- /* Notify on success. */ ++ /* Notify on success. */ ++ if (rc) ++ *failedFile = xstrdup(fp->fpath); ++ else + rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); ++ fp->stage = FILE_UNPACK; ++ } ++ fi = rpmfiFree(fi); + +- if (!fp->skip) { +- /* Backup file if needed. Directories are handled earlier */ +- if (fp->suffix) +- rc = fsmBackup(fi, fp->action); ++ if (!rc && fx < 0 && fx != RPMERR_ITER_END) ++ rc = fx; + +- if (!rc) +- rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix); +- } ++ /* Set permissions, timestamps etc for non-hardlink entries */ ++ fi = rpmfilesIter(files, RPMFI_ITER_FWD); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ if (!fp->skip && fp->setmeta) { ++ rc = fsmSetmeta(fp->fpath, fi, plugins, fp->action, ++ &fp->sb, nofcaps); + } +- + if (rc) + *failedFile = xstrdup(fp->fpath); ++ fp->stage = FILE_PREP; ++ } ++ fi = rpmfiFree(fi); + +- /* Run fsm file post hook for all plugins */ +- rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, +- fp->sb.st_mode, fp->action, rc); ++ /* If all went well, commit files to final destination */ ++ fi = rpmfilesIter(files, RPMFI_ITER_FWD); ++ while (!rc && (fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ ++ if (!fp->skip) { ++ /* Backup file if needed. Directories are handled earlier */ ++ if (!rc && fp->suffix) ++ rc = fsmBackup(fi, fp->action); ++ ++ if (!rc) ++ rc = fsmCommit(&fp->fpath, fi, fp->action, fp->suffix); ++ ++ if (!rc) ++ fp->stage = FILE_COMMIT; ++ else ++ *failedFile = xstrdup(fp->fpath); ++ } + } ++ fi = rpmfiFree(fi); + +- if (!rc && fx != RPMERR_ITER_END) +- rc = fx; ++ /* Walk backwards in case we need to erase */ ++ fi = rpmfilesIter(files, RPMFI_ITER_BACK); ++ while ((fx = rpmfiNext(fi)) >= 0) { ++ struct filedata_s *fp = &fdata[fx]; ++ /* Run fsm file post hook for all plugins for all processed files */ ++ if (fp->stage) { ++ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath, ++ fp->sb.st_mode, fp->action, rc); ++ } ++ ++ /* On failure, erase non-committed files */ ++ if (rc && fp->stage > FILE_NONE && !fp->skip) { ++ (void) fsmRemove(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 = rpmfiFree(fi); + Fclose(payload); + free(tid); + for (int i = 0; i < fc; i++) +diff --git a/tests/data/SPECS/hlinktest.spec b/tests/data/SPECS/hlinktest.spec +index 753c174cd8..3f1437d89c 100644 +--- a/tests/data/SPECS/hlinktest.spec ++++ b/tests/data/SPECS/hlinktest.spec +@@ -1,5 +1,6 @@ + %bcond_with unpackaged_dirs + %bcond_with unpackaged_files ++%bcond_with owned_dir + + Summary: Testing hard link behavior + Name: hlinktest +@@ -43,4 +44,7 @@ touch $RPM_BUILD_ROOT/teet + + %files + %defattr(-,root,root) ++%if %{with owned_dir} ++%dir /foo ++%endif + /foo/* +diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at +index 1f0e679c44..f0eef4eff0 100644 +--- a/tests/rpmbuild.at ++++ b/tests/rpmbuild.at +@@ -253,38 +253,6 @@ drwxrwxrwx zoot zoot /j/dir + []) + AT_CLEANUP + +-# ------------------------------ +-# hardlink tests +-AT_SETUP([rpmbuild hardlink]) +-AT_KEYWORDS([build]) +-RPMDB_INIT +-AT_CHECK([ +-RPMDB_INIT +- +-runroot rpmbuild \ +- -bb --quiet /data/SPECS/hlinktest.spec +- +-runroot rpm -i /build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm +- +-runroot rpm -q --qf "[[%{filenlinks} %{filenames}\n]]%{longsize}\n" hlinktest +-runroot rpm -V --nouser --nogroup hlinktest +-ls -i "${RPMTEST}"/foo/hello* | awk {'print $1'} | sort -u | wc -l +- +-], +-[0], +-[2 /foo/aaaa +-1 /foo/copyllo +-4 /foo/hello +-4 /foo/hello-bar +-4 /foo/hello-foo +-4 /foo/hello-world +-2 /foo/zzzz +-87 +-1 +-], +-[]) +-AT_CLEANUP +- + AT_SETUP([rpmbuild unpackaged files]) + AT_KEYWORDS([build]) + RPMDB_INIT +diff --git a/tests/rpmi.at b/tests/rpmi.at +index 42dc52ba35..295fbc230f 100644 +--- a/tests/rpmi.at ++++ b/tests/rpmi.at +@@ -722,3 +722,95 @@ runroot rpm -V --nouser --nogroup suicidal + [], + []) + AT_CLEANUP ++ ++# ------------------------------ ++# hardlink tests ++AT_SETUP([rpm -i hardlinks]) ++AT_KEYWORDS([build install]) ++RPMDB_INIT ++ ++# Need a reproducable test package ++runroot rpmbuild \ ++ --define "%optflags -O2 -g" \ ++ --define "%_target_platform noarch-linux" \ ++ --define "%_binary_payload w.ufdio" \ ++ --define "%_buildhost localhost" \ ++ --define "%use_source_date_epoch_as_buildtime 1" \ ++ --define "%source_date_epoch_from_changelog 1" \ ++ --define "%clamp_mtime_to_source_date_epoch 1" \ ++ --with owned_dir \ ++ -bb --quiet /data/SPECS/hlinktest.spec ++ ++pkg="/build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm" ++ ++cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/1.rpm" ++dd if=/dev/zero of="${RPMTEST}/tmp/1.rpm" \ ++ conv=notrunc bs=1 seek=8180 count=6 2> /dev/null ++ ++cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/2.rpm" ++dd if=/dev/zero of="${RPMTEST}/tmp/2.rpm" \ ++ conv=notrunc bs=1 seek=8150 count=6 2> /dev/null ++ ++cp "${RPMTEST}/${pkg}" "${RPMTEST}/tmp/3.rpm" ++dd if=/dev/zero of="${RPMTEST}/tmp/3.rpm" \ ++ conv=notrunc bs=1 seek=8050 count=6 2> /dev/null ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i --noverify /tmp/1.rpm ++# test that nothing of the contents remains after failure ++test -d "${RPMTEST}/foo" ++], ++[1], ++[], ++[error: unpacking of archive failed: cpio: Archive file not in header ++error: hlinktest-1.0-1.noarch: install failed ++]) ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i --noverify /tmp/2.rpm ++# test that nothing of the contents remains after failure ++test -d "${RPMTEST}/foo" ++], ++[1], ++[], ++[error: unpacking of archive failed: cpio: Bad/unreadable header ++error: hlinktest-1.0-1.noarch: install failed ++]) ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i --noverify /tmp/3.rpm 2>&1| sed 's/;.*:/:/g' ++# test that nothing of the contents remains after failure ++test -d "${RPMTEST}/foo" ++], ++[1], ++[error: unpacking of archive failed on file /foo/hello-world: Digest mismatch ++error: hlinktest-1.0-1.noarch: install failed ++], ++[]) ++ ++AT_CHECK([ ++RPMDB_INIT ++runroot rpm -i /build/RPMS/noarch/hlinktest-1.0-1.noarch.rpm ++runroot rpm -q --qf "[[%{filenlinks} %{filenames}\n]]%{longsize}\n" hlinktest ++ls -i "${RPMTEST}"/foo/hello* | awk {'print $1'} | sort -u | wc -l ++runroot rpm -e hlinktest ++ ++], ++[0], ++[1 /foo ++2 /foo/aaaa ++1 /foo/copyllo ++4 /foo/hello ++4 /foo/hello-bar ++4 /foo/hello-foo ++4 /foo/hello-world ++2 /foo/zzzz ++87 ++1 ++], ++[]) ++AT_CLEANUP ++ diff --git a/rpm.spec b/rpm.spec index 0ba7783..4e38dd8 100644 --- a/rpm.spec +++ b/rpm.spec @@ -134,6 +134,7 @@ Patch1982: 0033-Enable-fsverity-in-CI.patch Patch1983: 0034-rpmsign-Adopting-PKCS11-opaque-keys-support-in-libfsverity-for-fsverity-signatures.patch %endif +Patch9989: 1534.patch Patch9999: measure.patch # Partially GPL/LGPL dual-licensed and some bits with BSD @@ -682,6 +683,7 @@ fi * Fri Feb 25 2022 Manu Bretelle - 4.16.1.3-11.1 - Added fsverity backport - Added measure plugin support +- Apply GH1534 in preparation of RPM cow patch * Mon Feb 14 2022 Michal Domonkos - 4.16.1.3-11 - Fix IMA signature lengths assumed constant, take III (#2018937)