262 lines
7.9 KiB
Diff
262 lines
7.9 KiB
Diff
From a82251b44ee2d2802ee8aea1b3d89f88beee4bad Mon Sep 17 00:00:00 2001
|
|
From: Panu Matilainen <pmatilai@redhat.com>
|
|
Date: Wed, 10 Feb 2021 14:15:33 +0200
|
|
Subject: [PATCH] Handle file install failures more gracefully
|
|
|
|
Conflict:adapt context; delete testcode
|
|
Reference:https://github.com/rpm-software-management/rpm/commit/a82251b44ee2d2802ee8aea1b3d89f88beee4bad
|
|
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 ++++++++++++++++++++------------
|
|
1 files changed, 93 insertions(+), 54 deletions(-)
|
|
|
|
diff --git a/lib/fsm.c b/lib/fsm.c
|
|
index f86383a98..6efd25bdd 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 (fp->action != FA_TOUCH) {
|
|
@@ -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 @@ touch:
|
|
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++)
|
|
--
|
|
2.27.0
|
|
|