rpm/0001-Use-file-state-machine-from-rpm-4.19.patch
Florian Festi 94360abd84 Backport file handling code from rpm-4.19
Fixes CVE-2021-35937, CVE-2021-35938 and CVE-2021-35939

Patches include small changes found in other patches. See RHEL-8.10
patch set for intermediate steps.

Resolves: RHEL-14598 RHEL-14599 RHEL-14600
2023-11-11 10:22:18 +01:00

1632 lines
46 KiB
Diff

From 36ee14a07630668629a0d461fba8b5b2248d7d71 Mon Sep 17 00:00:00 2001
From: Florian Festi <ffesti@redhat.com>
Date: Tue, 10 Oct 2023 16:46:17 +0200
Subject: [PATCH] Use file state machine from rpm-4.19
This new implementation fixes several race conditions when placing down
files on disc
---
lib/fsm.c | 1164 +++++++++++++++++++++++++---------------------
lib/rpmarchive.h | 3 +
lib/rpmfiles.h | 3 +
diff --git a/lib/rpmarchive.h b/lib/rpmarchive.h
index c864e5b56..e5cda4f97 100644
--- a/lib/rpmarchive.h
+++ b/lib/rpmarchive.h
@@ -26,6 +26,8 @@ enum rpmfilesErrorCodes {
RPMERR_FILE_SIZE = -12,
RPMERR_ITER_SKIP = -13,
RPMERR_EXIST_AS_DIR = -14,
+ RPMERR_INVALID_SYMLINK = -15,
+ RPMERR_ENOTDIR = -16,
RPMERR_OPEN_FAILED = -32768,
RPMERR_CHMOD_FAILED = -32769,
@@ -47,6 +49,7 @@ enum rpmfilesErrorCodes {
RPMERR_COPY_FAILED = -32785,
RPMERR_LSETFCON_FAILED = -32786,
RPMERR_SETCAP_FAILED = -32787,
+ RPMERR_CLOSE_FAILED = -32788,
};
#ifdef __cplusplus
diff --git a/lib/rpmfiles.h b/lib/rpmfiles.h
index daf572cf4..e74bb2201 100644
--- a/lib/rpmfiles.h
+++ b/lib/rpmfiles.h
@@ -90,6 +90,9 @@ typedef enum rpmFileAction_e {
#define XFA_SKIPPING(_a) \
((_a) == FA_SKIP || (_a) == FA_SKIPNSTATE || (_a) == FA_SKIPNETSHARED || (_a) == FA_SKIPCOLOR)
+#define XFA_CREATING(_a) \
+ ((_a) == FA_CREATE || (_a) == FA_BACKUP || (_a) == FA_SAVE || (_a) == FA_ALTNAME)
+
/**
* We pass these around as an array with a sentinel.
*/
--- rpm-4.16.1.3/lib/fsm.c.orig 2023-11-11 10:05:19.208206675 +0100
+++ rpm-4.16.1.3/lib/fsm.c 2023-11-11 10:05:43.559432708 +0100
@@ -5,9 +5,11 @@
#include "system.h"
+#include <inttypes.h>
#include <utime.h>
#include <errno.h>
-#if WITH_CAP
+#include <fcntl.h>
+#ifdef WITH_CAP
#include <sys/capability.h>
#endif
@@ -17,10 +19,11 @@
#include <rpm/rpmmacro.h>
#include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */
-#include "lib/fsm.h"
-#include "lib/rpmte_internal.h" /* XXX rpmfs */
-#include "lib/rpmplugins.h" /* rpm plugins hooks */
-#include "lib/rpmug.h"
+#include "fsm.h"
+#include "rpmte_internal.h" /* XXX rpmfs */
+#include "rpmfi_internal.h" /* rpmfiSetOnChdir */
+#include "rpmplugins.h" /* rpm plugins hooks */
+#include "rpmug.h"
#include "debug.h"
@@ -38,172 +41,92 @@
#define _dirPerms 0755
#define _filePerms 0644
+enum filestage_e {
+ FILE_COMMIT = -1,
+ FILE_NONE = 0,
+ FILE_PRE = 1,
+ FILE_UNPACK = 2,
+ FILE_PREP = 3,
+ FILE_POST = 4,
+};
+
+struct filedata_s {
+ int stage;
+ int setmeta;
+ int skip;
+ rpmFileAction action;
+ const char *suffix;
+ char *fpath;
+ struct stat sb;
+};
+
/*
* XXX Forward declarations for previously exported functions to avoid moving
* things around needlessly
*/
static const char * fileActionString(rpmFileAction a);
+static int fsmOpenat(int dirfd, const char *path, int flags, int dir);
+static int fsmClose(int *wfdp);
/** \ingroup payload
* Build path to file from file info, optionally ornamented with suffix.
+ * "/" needs special handling to avoid appearing as empty path.
* @param fi file info iterator
* @param suffix suffix to use (NULL disables)
- * @retval path to file (malloced)
+ * @param[out] path to file (malloced)
*/
static char * fsmFsPath(rpmfi fi, const char * suffix)
{
- return rstrscat(NULL, rpmfiDN(fi), rpmfiBN(fi), suffix ? suffix : "", NULL);
-}
-
-/** \ingroup payload
- * Directory name iterator.
- */
-typedef struct dnli_s {
- rpmfiles fi;
- char * active;
- int reverse;
- int isave;
- int i;
-} * DNLI_t;
-
-/** \ingroup payload
- * Destroy directory name iterator.
- * @param dnli directory name iterator
- * @retval NULL always
- */
-static DNLI_t dnlFreeIterator(DNLI_t dnli)
-{
- if (dnli) {
- if (dnli->active) free(dnli->active);
- free(dnli);
- }
- return NULL;
+ const char *bn = rpmfiBN(fi);
+ return rstrscat(NULL, *bn ? bn : "/", suffix ? suffix : "", NULL);
}
-/** \ingroup payload
- * Create directory name iterator.
- * @param fi file info set
- * @param fs file state set
- * @param reverse traverse directory names in reverse order?
- * @return directory name iterator
- */
-static DNLI_t dnlInitIterator(rpmfiles fi, rpmfs fs, int reverse)
+static int fsmLink(int odirfd, const char *opath, int dirfd, const char *path)
{
- DNLI_t dnli;
- int i, j;
- int dc;
-
- if (fi == NULL)
- return NULL;
- dc = rpmfilesDC(fi);
- dnli = xcalloc(1, sizeof(*dnli));
- dnli->fi = fi;
- dnli->reverse = reverse;
- dnli->i = (reverse ? dc : 0);
-
- if (dc) {
- dnli->active = xcalloc(dc, sizeof(*dnli->active));
- int fc = rpmfilesFC(fi);
-
- /* Identify parent directories not skipped. */
- for (i = 0; i < fc; i++)
- if (!XFA_SKIPPING(rpmfsGetAction(fs, i)))
- dnli->active[rpmfilesDI(fi, i)] = 1;
-
- /* Exclude parent directories that are explicitly included. */
- for (i = 0; i < fc; i++) {
- int dil;
- size_t dnlen, bnlen;
+ int rc = linkat(odirfd, opath, dirfd, path, 0);
- if (!S_ISDIR(rpmfilesFMode(fi, i)))
- continue;
-
- dil = rpmfilesDI(fi, i);
- dnlen = strlen(rpmfilesDN(fi, dil));
- bnlen = strlen(rpmfilesBN(fi, i));
-
- for (j = 0; j < dc; j++) {
- const char * dnl;
- size_t jlen;
-
- if (!dnli->active[j] || j == dil)
- continue;
- dnl = rpmfilesDN(fi, j);
- jlen = strlen(dnl);
- if (jlen != (dnlen+bnlen+1))
- continue;
- if (!rstreqn(dnl, rpmfilesDN(fi, dil), dnlen))
- continue;
- if (!rstreqn(dnl+dnlen, rpmfilesBN(fi, i), bnlen))
- continue;
- if (dnl[dnlen+bnlen] != '/' || dnl[dnlen+bnlen+1] != '\0')
- continue;
- /* This directory is included in the package. */
- dnli->active[j] = 0;
- break;
- }
- }
-
- /* Print only once per package. */
- if (!reverse) {
- j = 0;
- for (i = 0; i < dc; i++) {
- if (!dnli->active[i]) continue;
- if (j == 0) {
- j = 1;
- rpmlog(RPMLOG_DEBUG,
- "========== Directories not explicitly included in package:\n");
- }
- rpmlog(RPMLOG_DEBUG, "%10d %s\n", i, rpmfilesDN(fi, i));
- }
- if (j)
- rpmlog(RPMLOG_DEBUG, "==========\n");
- }
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__,
+ odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : ""));
}
- return dnli;
+
+ if (rc < 0)
+ rc = RPMERR_LINK_FAILED;
+ return rc;
}
-/** \ingroup payload
- * Return next directory name (from file info).
- * @param dnli directory name iterator
- * @return next directory name
- */
-static
-const char * dnlNextIterator(DNLI_t dnli)
+#ifdef WITH_CAP
+static int cap_set_fileat(int dirfd, const char *path, cap_t fcaps)
{
- const char * dn = NULL;
-
- if (dnli) {
- rpmfiles fi = dnli->fi;
- int dc = rpmfilesDC(fi);
- int i = -1;
-
- if (dnli->active)
- do {
- i = (!dnli->reverse ? dnli->i++ : --dnli->i);
- } while (i >= 0 && i < dc && !dnli->active[i]);
-
- if (i >= 0 && i < dc)
- dn = rpmfilesDN(fi, i);
- else
- i = -1;
- dnli->isave = i;
+ int rc = -1;
+ int fd = fsmOpenat(dirfd, path, O_RDONLY|O_NOFOLLOW, 0);
+ if (fd >= 0) {
+ rc = cap_set_fd(fd, fcaps);
+ fsmClose(&fd);
}
- return dn;
+ return rc;
}
+#endif
-static int fsmSetFCaps(const char *path, const char *captxt)
+static int fsmSetFCaps(int fd, int dirfd, const char *path, const char *captxt)
{
int rc = 0;
-#if WITH_CAP
+
+#ifdef WITH_CAP
if (captxt && *captxt != '\0') {
cap_t fcaps = cap_from_text(captxt);
- if (fcaps == NULL || cap_set_file(path, fcaps) != 0) {
- rc = RPMERR_SETCAP_FAILED;
+
+ if (fd >= 0) {
+ if (fcaps == NULL || cap_set_fd(fd, fcaps))
+ rc = RPMERR_SETCAP_FAILED;
+ } else {
+ if (fcaps == NULL || cap_set_fileat(dirfd, path, fcaps))
+ rc = RPMERR_SETCAP_FAILED;
}
+
if (_fsm_debug) {
- rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
- path, captxt, (rc < 0 ? strerror(errno) : ""));
+ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %s) %s\n", __func__,
+ fd, dirfd, path, captxt, (rc < 0 ? strerror(errno) : ""));
}
cap_free(fcaps);
}
@@ -211,101 +134,104 @@
return rc;
}
-static void wfd_close(FD_t *wfdp)
+static int fsmClose(int *wfdp)
{
- if (wfdp && *wfdp) {
+ int rc = 0;
+ if (wfdp && *wfdp >= 0) {
int myerrno = errno;
static int oneshot = 0;
static int flush_io = 0;
+ int fdno = *wfdp;
+
if (!oneshot) {
flush_io = (rpmExpandNumeric("%{?_flush_io}") > 0);
oneshot = 1;
}
if (flush_io) {
- int fdno = Fileno(*wfdp);
fsync(fdno);
}
- Fclose(*wfdp);
- *wfdp = NULL;
+ if (close(fdno))
+ rc = RPMERR_CLOSE_FAILED;
+
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s ([%d]) %s\n", __func__,
+ fdno, (rc < 0 ? strerror(errno) : ""));
+ }
+ *wfdp = -1;
errno = myerrno;
}
+ return rc;
}
-static int wfd_open(FD_t *wfdp, const char *dest)
+static int fsmOpen(int *wfdp, int dirfd, const char *dest)
{
int rc = 0;
/* Create the file with 0200 permissions (write by owner). */
- {
- mode_t old_umask = umask(0577);
- *wfdp = Fopen(dest, "wx.ufdio");
- umask(old_umask);
- }
- if (Ferror(*wfdp)) {
+ int fd = openat(dirfd, dest, O_WRONLY|O_EXCL|O_CREAT, 0200);
+
+ if (fd < 0)
rc = RPMERR_OPEN_FAILED;
- goto exit;
- }
- return 0;
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%s [%d]) %s\n", __func__,
+ dest, fd, (rc < 0 ? strerror(errno) : ""));
+ }
+ *wfdp = fd;
-exit:
- wfd_close(wfdp);
return rc;
}
-/** \ingroup payload
- * Create file from payload stream.
- * @return 0 on success
- */
-static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest)
+static int fsmUnpack(rpmfi fi, int fdno, rpmpsm psm, int nodigest)
{
- FD_t wfd = NULL;
- int rc;
-
- rc = wfd_open(&wfd, dest);
- if (rc != 0)
- goto exit;
-
- rc = rpmfiArchiveReadToFilePsm(fi, wfd, nodigest, psm);
- wfd_close(&wfd);
-exit:
+ FD_t fd = fdDup(fdno);
+ int rc = rpmfiArchiveReadToFilePsm(fi, fd, nodigest, psm);
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%s %" PRIu64 " bytes [%d]) %s\n", __func__,
+ rpmfiFN(fi), rpmfiFSize(fi), Fileno(fd),
+ (rc < 0 ? strerror(errno) : ""));
+ }
+ Fclose(fd);
return rc;
}
-static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
- rpmpsm psm, int nodigest, int *setmeta,
- int * firsthardlink, FD_t *firstlinkfile)
+static int fsmMkfile(int dirfd, rpmfi fi, struct filedata_s *fp, rpmfiles files,
+ rpmpsm psm, int nodigest,
+ struct filedata_s ** firstlink, int *firstlinkfile,
+ int *firstdir, int *fdp)
{
int rc = 0;
- int numHardlinks = rpmfiFNlink(fi);
+ int fd = -1;
- if (numHardlinks > 1) {
- /* Create first hardlinked file empty */
- if (*firsthardlink < 0) {
- *firsthardlink = rpmfiFX(fi);
- rc = wfd_open(firstlinkfile, dest);
- } else {
- /* Create hard links for others */
- char *fn = rpmfilesFN(files, *firsthardlink);
- rc = link(fn, dest);
- if (rc < 0) {
- rc = RPMERR_LINK_FAILED;
- }
- free(fn);
+ if (*firstlink == NULL) {
+ /* First encounter, open file for writing */
+ rc = fsmOpen(&fd, dirfd, fp->fpath);
+ /* If it's a part of a hardlinked set, the content may come later */
+ if (fp->sb.st_nlink > 1) {
+ *firstlink = fp;
+ *firstlinkfile = fd;
+ *firstdir = dup(dirfd);
+ }
+ } else {
+ /* Create hard links for others and avoid redundant metadata setting */
+ if (*firstlink != fp) {
+ rc = fsmLink(*firstdir, (*firstlink)->fpath, dirfd, fp->fpath);
}
+ fd = *firstlinkfile;
}
- /* Write normal files or fill the last hardlinked (already
- existing) file with content */
- if (numHardlinks<=1) {
- if (!rc)
- rc = expandRegular(fi, dest, psm, nodigest);
- } else if (rpmfiArchiveHasContent(fi)) {
+
+ /* If the file has content, unpack it */
+ if (rpmfiArchiveHasContent(fi)) {
if (!rc)
- rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
- wfd_close(firstlinkfile);
- *firsthardlink = -1;
- } else {
- *setmeta = 0;
+ rc = fsmUnpack(fi, fd, psm, nodigest);
+ /* Last file of hardlink set, ensure metadata gets set */
+ if (*firstlink) {
+ fp->setmeta = 1;
+ *firstlink = NULL;
+ *firstlinkfile = -1;
+ fsmClose(firstdir);
+ }
}
+ *fdp = fd;
return rc;
}
@@ -330,18 +256,15 @@
return rc;
}
-static int fsmStat(const char *path, int dolstat, struct stat *sb)
+static int fsmStat(int dirfd, const char *path, int dolstat, struct stat *sb)
{
- int rc;
- if (dolstat){
- rc = lstat(path, sb);
- } else {
- rc = stat(path, sb);
- }
+ int flags = dolstat ? AT_SYMLINK_NOFOLLOW : 0;
+ int rc = fstatat(dirfd, path, sb, flags);
+
if (_fsm_debug && rc && errno != ENOENT)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n",
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, ost) %s\n",
__func__,
- path, (rc < 0 ? strerror(errno) : ""));
+ dirfd, path, (rc < 0 ? strerror(errno) : ""));
if (rc < 0) {
rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_LSTAT_FAILED);
/* Ensure consistent struct content on failure */
@@ -350,12 +273,12 @@
return rc;
}
-static int fsmRmdir(const char *path)
+static int fsmRmdir(int dirfd, const char *path)
{
- int rc = rmdir(path);
+ int rc = unlinkat(dirfd, path, AT_REMOVEDIR);
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
- path, (rc < 0 ? strerror(errno) : ""));
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s) %s\n", __func__,
+ dirfd, path, (rc < 0 ? strerror(errno) : ""));
if (rc < 0)
switch (errno) {
case ENOENT: rc = RPMERR_ENOENT; break;
@@ -365,148 +288,194 @@
return rc;
}
-static int fsmMkdir(const char *path, mode_t mode)
+static int fsmMkdir(int dirfd, const char *path, mode_t mode)
{
- int rc = mkdir(path, (mode & 07777));
+ int rc = mkdirat(dirfd, path, (mode & 07777));
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
- path, (unsigned)(mode & 07777),
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n", __func__,
+ dirfd, path, (unsigned)(mode & 07777),
(rc < 0 ? strerror(errno) : ""));
if (rc < 0) rc = RPMERR_MKDIR_FAILED;
return rc;
}
-static int fsmMkfifo(const char *path, mode_t mode)
+static int fsmOpenat(int dirfd, const char *path, int flags, int dir)
{
- int rc = mkfifo(path, (mode & 07777));
-
- if (_fsm_debug) {
- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n",
- __func__, path, (unsigned)(mode & 07777),
- (rc < 0 ? strerror(errno) : ""));
+ struct stat lsb, sb;
+ int sflags = flags | O_NOFOLLOW;
+ int fd = openat(dirfd, path, sflags);
+
+ /*
+ * Only ever follow symlinks by root or target owner. Since we can't
+ * open the symlink itself, the order matters: we stat the link *after*
+ * opening the target, and if the link ownership changed between the calls
+ * it could've only been the link owner or root.
+ */
+ if (fd < 0 && errno == ELOOP && flags != sflags) {
+ int ffd = openat(dirfd, path, flags);
+ if (ffd >= 0) {
+ if (fstatat(dirfd, path, &lsb, AT_SYMLINK_NOFOLLOW) == 0) {
+ if (fstat(ffd, &sb) == 0) {
+ if (lsb.st_uid == 0 || lsb.st_uid == sb.st_uid) {
+ fd = ffd;
+ }
+ }
+ }
+ if (ffd != fd)
+ close(ffd);
+ }
}
- if (rc < 0)
- rc = RPMERR_MKFIFO_FAILED;
-
- return rc;
+ /* O_DIRECTORY equivalent */
+ if (dir && fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode)) {
+ errno = ENOTDIR;
+ fsmClose(&fd);
+ }
+ return fd;
}
-static int fsmMknod(const char *path, mode_t mode, dev_t dev)
+static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn,
+ const char *apath,
+ int owned, mode_t mode, int *fdp)
{
- /* FIX: check S_IFIFO or dev != 0 */
- int rc = mknod(path, (mode & ~07777), dev);
+ int rc;
+ rpmFsmOp op = (FA_CREATE);
+ if (!owned)
+ op |= FAF_UNOWNED;
- if (_fsm_debug) {
- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%o, 0x%x) %s\n",
- __func__, path, (unsigned)(mode & ~07777),
- (unsigned)dev, (rc < 0 ? strerror(errno) : ""));
+ /* Run fsm file pre hook for all plugins */
+ rc = rpmpluginsCallFsmFilePre(plugins, NULL, apath, mode, op);
+
+ if (!rc)
+ rc = fsmMkdir(dirfd, dn, mode);
+
+ if (!rc) {
+ *fdp = fsmOpenat(dirfd, dn, O_RDONLY|O_NOFOLLOW, 1);
+ if (*fdp == -1)
+ rc = RPMERR_ENOTDIR;
}
- if (rc < 0)
- rc = RPMERR_MKNOD_FAILED;
+ if (!rc) {
+ rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, *fdp, apath, apath, mode, op);
+ }
+
+ /* Run fsm file post hook for all plugins */
+ rpmpluginsCallFsmFilePost(plugins, NULL, apath, mode, op, rc);
+
+ if (!rc) {
+ rpmlog(RPMLOG_DEBUG,
+ "%s directory created with perms %04o\n",
+ apath, (unsigned)(mode & 07777));
+ }
return rc;
}
-/**
- * Create (if necessary) directories not explicitly included in package.
- * @param files file data
- * @param fs file states
- * @param plugins rpm plugins handle
- * @return 0 on success
- */
-static int fsmMkdirs(rpmfiles files, rpmfs fs, rpmPlugins plugins)
+static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create,
+ int quiet, int *dirfdp)
{
- DNLI_t dnli = dnlInitIterator(files, fs, 0);
- struct stat sb;
- const char *dpath;
+ char *sp = NULL, *bn;
+ char *apath = NULL;
+ int oflags = O_RDONLY;
int rc = 0;
- int i;
- size_t ldnlen = 0;
- const char * ldn = NULL;
-
- while ((dpath = dnlNextIterator(dnli)) != NULL) {
- size_t dnlen = strlen(dpath);
- char * te, dn[dnlen+1];
- if (dnlen <= 1)
- continue;
+ if (*dirfdp >= 0)
+ return rc;
- if (dnlen == ldnlen && rstreq(dpath, ldn))
- continue;
+ int dirfd = fsmOpenat(-1, "/", oflags, 1);
+ int fd = dirfd; /* special case of "/" */
- /* Copy as we need to modify the string */
- (void) stpcpy(dn, dpath);
+ char *path = xstrdup(p);
+ char *dp = path;
- /* Assume '/' directory exists, "mkdir -p" for others if non-existent */
- for (i = 1, te = dn + 1; *te != '\0'; te++, i++) {
- if (*te != '/')
- continue;
+ while ((bn = strtok_r(dp, "/", &sp)) != NULL) {
+ fd = fsmOpenat(dirfd, bn, oflags, 1);
+ /* assemble absolute path for plugins benefit, sigh */
+ apath = rstrscat(&apath, "/", bn, NULL);
+
+ if (fd < 0 && errno == ENOENT && create) {
+ mode_t mode = S_IFDIR | (_dirPerms & 07777);
+ rc = fsmDoMkDir(plugins, dirfd, bn, apath, owned, mode, &fd);
+ }
- /* Already validated? */
- if (i < ldnlen &&
- (ldn[i] == '/' || ldn[i] == '\0') && rstreqn(dn, ldn, i))
- continue;
+ fsmClose(&dirfd);
+ if (fd >= 0) {
+ dirfd = fd;
+ } else {
+ if (!quiet) {
+ rpmlog(RPMLOG_ERR, _("failed to open dir %s of %s: %s\n"),
+ bn, p, strerror(errno));
+ }
+ rc = RPMERR_OPEN_FAILED;
+ break;
+ }
- /* Validate next component of path. */
- *te = '\0';
- rc = fsmStat(dn, 1, &sb); /* lstat */
- *te = '/';
+ dp = NULL;
+ }
- /* Directory already exists? */
- if (rc == 0 && S_ISDIR(sb.st_mode)) {
- continue;
- } else if (rc == RPMERR_ENOENT) {
- *te = '\0';
- mode_t mode = S_IFDIR | (_dirPerms & 07777);
- rpmFsmOp op = (FA_CREATE|FAF_UNOWNED);
-
- /* Run fsm file pre hook for all plugins */
- rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op);
-
- if (!rc)
- rc = fsmMkdir(dn, mode);
-
- if (!rc) {
- rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn,
- mode, op);
- }
+ if (rc) {
+ fsmClose(&fd);
+ fsmClose(&dirfd);
+ } else {
+ rc = 0;
+ }
+ *dirfdp = dirfd;
- /* Run fsm file post hook for all plugins */
- rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc);
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%s: %d) %s\n", __func__,
+ p, dirfd, (rc < 0 ? strerror(errno) : ""));
+ }
- if (!rc) {
- rpmlog(RPMLOG_DEBUG,
- "%s directory created with perms %04o\n",
- dn, (unsigned)(mode & 07777));
- }
- *te = '/';
- }
- if (rc)
- break;
- }
- if (rc) break;
+ free(path);
+ free(apath);
+ return rc;
+}
- /* Save last validated path. */
- ldn = dpath;
- ldnlen = dnlen;
+static int fsmMkfifo(int dirfd, const char *path, mode_t mode)
+{
+ int rc = mkfifoat(dirfd, path, (mode & 07777));
+
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n",
+ __func__, dirfd, path, (unsigned)(mode & 07777),
+ (rc < 0 ? strerror(errno) : ""));
}
- dnlFreeIterator(dnli);
+
+ if (rc < 0)
+ rc = RPMERR_MKFIFO_FAILED;
return rc;
}
-static void removeSBITS(const char *path)
+static int fsmMknod(int dirfd, const char *path, mode_t mode, dev_t dev)
+{
+ /* FIX: check S_IFIFO or dev != 0 */
+ int rc = mknodat(dirfd, path, (mode & ~07777), dev);
+
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%o, 0x%x) %s\n",
+ __func__, dirfd, path, (unsigned)(mode & ~07777),
+ (unsigned)dev, (rc < 0 ? strerror(errno) : ""));
+ }
+
+ if (rc < 0)
+ rc = RPMERR_MKNOD_FAILED;
+
+ return rc;
+}
+
+static void removeSBITS(int dirfd, const char *path)
{
struct stat stb;
- if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) {
+ int flags = AT_SYMLINK_NOFOLLOW;
+ if (fstatat(dirfd, path, &stb, flags) == 0 && S_ISREG(stb.st_mode)) {
+ /* We now know it's not a link so no need to worry about following */
if ((stb.st_mode & 06000) != 0) {
- (void) chmod(path, stb.st_mode & 0777);
+ (void) fchmodat(dirfd, path, stb.st_mode & 0777, 0);
}
-#if WITH_CAP
+#ifdef WITH_CAP
if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
- (void) cap_set_file(path, NULL);
+ (void) cap_set_fileat(dirfd, path, NULL);
}
#endif
}
@@ -522,13 +491,13 @@
(fpath ? fpath : ""));
}
-static int fsmSymlink(const char *opath, const char *path)
+static int fsmSymlink(const char *opath, int dirfd, const char *path)
{
- int rc = symlink(opath, path);
+ int rc = symlinkat(opath, dirfd, path);
if (_fsm_debug) {
- rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
- opath, path, (rc < 0 ? strerror(errno) : ""));
+ rpmlog(RPMLOG_DEBUG, " %8s (%s, %d %s) %s\n", __func__,
+ opath, dirfd, path, (rc < 0 ? strerror(errno) : ""));
}
if (rc < 0)
@@ -536,96 +505,125 @@
return rc;
}
-static int fsmUnlink(const char *path)
+static int fsmUnlink(int dirfd, const char *path)
{
int rc = 0;
- removeSBITS(path);
- rc = unlink(path);
+ removeSBITS(dirfd, path);
+ rc = unlinkat(dirfd, path, 0);
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
- path, (rc < 0 ? strerror(errno) : ""));
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s) %s\n", __func__,
+ dirfd, path, (rc < 0 ? strerror(errno) : ""));
if (rc < 0)
rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_UNLINK_FAILED);
return rc;
}
-static int fsmRename(const char *opath, const char *path)
+static int fsmRename(int odirfd, const char *opath, int dirfd, const char *path)
{
- removeSBITS(path);
- int rc = rename(opath, path);
+ removeSBITS(dirfd, path);
+ int rc = renameat(odirfd, opath, dirfd, path);
#if defined(ETXTBSY) && defined(__HPUX__)
/* XXX HP-UX (and other os'es) don't permit rename to busy files. */
if (rc && errno == ETXTBSY) {
char *rmpath = NULL;
rstrscat(&rmpath, path, "-RPMDELETE", NULL);
- rc = rename(path, rmpath);
- if (!rc) rc = rename(opath, path);
+ /* Rename within the original directory */
+ rc = renameat(odirfd, path, odirfd, rmpath);
+ if (!rc) rc = renameat(odirfd, opath, dirfd, path);
free(rmpath);
}
#endif
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
- opath, path, (rc < 0 ? strerror(errno) : ""));
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__,
+ odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : ""));
if (rc < 0)
rc = (errno == EISDIR ? RPMERR_EXIST_AS_DIR : RPMERR_RENAME_FAILED);
return rc;
}
-static int fsmRemove(const char *path, mode_t mode)
+static int fsmRemove(int dirfd, const char *path, mode_t mode)
{
- return S_ISDIR(mode) ? fsmRmdir(path) : fsmUnlink(path);
+ return S_ISDIR(mode) ? fsmRmdir(dirfd, path) : fsmUnlink(dirfd, path);
}
-static int fsmChown(const char *path, mode_t mode, uid_t uid, gid_t gid)
+static int fsmChown(int fd, int dirfd, const char *path, mode_t mode, uid_t uid, gid_t gid)
{
- int rc = S_ISLNK(mode) ? lchown(path, uid, gid) : chown(path, uid, gid);
- if (rc < 0) {
- struct stat st;
- if (lstat(path, &st) == 0 && st.st_uid == uid && st.st_gid == gid)
- rc = 0;
+ int rc;
+ struct stat st;
+
+ if (fd >= 0) {
+ rc = fchown(fd, uid, gid);
+ if (rc < 0) {
+ if (fstat(fd, &st) == 0 && (st.st_uid == uid && st.st_gid == gid)) {
+ rc = 0;
+ }
+ }
+ } else {
+ int flags = AT_SYMLINK_NOFOLLOW;
+ rc = fchownat(dirfd, path, uid, gid, flags);
+ if (rc < 0) {
+ struct stat st;
+ if (fstatat(dirfd, path, &st, flags) == 0 &&
+ (st.st_uid == uid && st.st_gid == gid)) {
+ rc = 0;
+ }
+ }
}
- if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", __func__,
- path, (int)uid, (int)gid,
+ if (_fsm_debug) {
+ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %d, %d) %s\n", __func__,
+ fd, dirfd, path, (int)uid, (int)gid,
(rc < 0 ? strerror(errno) : ""));
+ }
if (rc < 0) rc = RPMERR_CHOWN_FAILED;
return rc;
}
-static int fsmChmod(const char *path, mode_t mode)
+static int fsmChmod(int fd, int dirfd, const char *path, mode_t mode)
{
- int rc = chmod(path, (mode & 07777));
- if (rc < 0) {
- struct stat st;
- if (lstat(path, &st) == 0 && (st.st_mode & 07777) == (mode & 07777))
- rc = 0;
+ mode_t fmode = (mode & 07777);
+ int rc;
+ if (fd >= 0) {
+ rc = fchmod(fd, fmode);
+ if (rc < 0) {
+ struct stat st;
+ if (fstat(fd, &st) == 0 && (st.st_mode & 07777) == fmode) {
+ rc = 0;
+ }
+ }
+ } else {
+ rc = fchmodat(dirfd, path, fmode, 0);
+ if (rc < 0) {
+ struct stat st;
+ if (fstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW) == 0 &&
+ (st.st_mode & 07777) == fmode) {
+ rc = 0;
+ }
+ }
}
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
- path, (unsigned)(mode & 07777),
+ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, 0%04o) %s\n", __func__,
+ fd, dirfd, path, (unsigned)(mode & 07777),
(rc < 0 ? strerror(errno) : ""));
if (rc < 0) rc = RPMERR_CHMOD_FAILED;
return rc;
}
-static int fsmUtime(const char *path, mode_t mode, time_t mtime)
+static int fsmUtime(int fd, int dirfd, const char *path, mode_t mode, time_t mtime)
{
int rc = 0;
- struct timeval stamps[2] = {
- { .tv_sec = mtime, .tv_usec = 0 },
- { .tv_sec = mtime, .tv_usec = 0 },
+ struct timespec stamps[2] = {
+ { .tv_sec = mtime, .tv_nsec = 0 },
+ { .tv_sec = mtime, .tv_nsec = 0 },
};
-#if HAVE_LUTIMES
- rc = lutimes(path, stamps);
-#else
- if (!S_ISLNK(mode))
- rc = utimes(path, stamps);
-#endif
+ if (fd >= 0)
+ rc = futimens(fd, stamps);
+ else
+ rc = utimensat(dirfd, path, stamps, AT_SYMLINK_NOFOLLOW);
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", __func__,
- path, (unsigned)mtime, (rc < 0 ? strerror(errno) : ""));
+ rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, 0x%x) %s\n", __func__,
+ fd, dirfd, path, (unsigned)mtime, (rc < 0 ? strerror(errno) : ""));
if (rc < 0) rc = RPMERR_UTIME_FAILED;
/* ...but utime error is not critical for directories */
if (rc && S_ISDIR(mode))
@@ -633,24 +631,24 @@
return rc;
}
-static int fsmVerify(const char *path, rpmfi fi)
+static int fsmVerify(int dirfd, const char *path, rpmfi fi)
{
int rc;
int saveerrno = errno;
struct stat dsb;
mode_t mode = rpmfiFMode(fi);
- rc = fsmStat(path, 1, &dsb);
+ rc = fsmStat(dirfd, path, 1, &dsb);
if (rc)
return rc;
if (S_ISREG(mode)) {
/* HP-UX (and other os'es) don't permit unlink on busy files. */
char *rmpath = rstrscat(NULL, path, "-RPMDELETE", NULL);
- rc = fsmRename(path, rmpath);
+ rc = fsmRename(dirfd, path, dirfd, rmpath);
/* XXX shouldn't we take unlink return code here? */
if (!rc)
- (void) fsmUnlink(rmpath);
+ (void) fsmUnlink(dirfd, rmpath);
else
rc = RPMERR_UNLINK_FAILED;
free(rmpath);
@@ -659,7 +657,7 @@
if (S_ISDIR(dsb.st_mode)) return 0;
if (S_ISLNK(dsb.st_mode)) {
uid_t luid = dsb.st_uid;
- rc = fsmStat(path, 0, &dsb);
+ rc = fsmStat(dirfd, path, 0, &dsb);
if (rc == RPMERR_ENOENT) rc = 0;
if (rc) return rc;
errno = saveerrno;
@@ -685,7 +683,7 @@
if (S_ISSOCK(dsb.st_mode)) return 0;
}
/* XXX shouldn't do this with commit/undo. */
- rc = fsmUnlink(path);
+ rc = fsmUnlink(dirfd, path);
if (rc == 0) rc = RPMERR_ENOENT;
return (rc ? rc : RPMERR_ENOENT); /* XXX HACK */
}
@@ -699,7 +697,7 @@
/* Rename pre-existing modified or unmanaged file. */
-static int fsmBackup(rpmfi fi, rpmFileAction action)
+static int fsmBackup(int dirfd, rpmfi fi, rpmFileAction action)
{
int rc = 0;
const char *suffix = NULL;
@@ -720,9 +718,10 @@
if (suffix) {
char * opath = fsmFsPath(fi, NULL);
char * path = fsmFsPath(fi, suffix);
- rc = fsmRename(opath, path);
+ rc = fsmRename(dirfd, opath, dirfd, path);
if (!rc) {
- rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"), opath, path);
+ rpmlog(RPMLOG_WARNING, _("%s%s saved as %s%s\n"),
+ rpmfiDN(fi), opath, rpmfiDN(fi), path);
}
free(path);
free(opath);
@@ -730,7 +729,8 @@
return rc;
}
-static int fsmSetmeta(const char *path, rpmfi fi, rpmPlugins plugins,
+static int fsmSetmeta(int fd, int dirfd, const char *path,
+ rpmfi fi, rpmPlugins plugins,
rpmFileAction action, const struct stat * st,
int nofcaps)
{
@@ -738,27 +738,28 @@
const char *dest = rpmfiFN(fi);
if (!rc && !getuid()) {
- rc = fsmChown(path, st->st_mode, st->st_uid, st->st_gid);
+ rc = fsmChown(fd, dirfd, path, st->st_mode, st->st_uid, st->st_gid);
}
if (!rc && !S_ISLNK(st->st_mode)) {
- rc = fsmChmod(path, st->st_mode);
+ rc = fsmChmod(fd, dirfd, path, st->st_mode);
}
/* Set file capabilities (if enabled) */
if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) {
- rc = fsmSetFCaps(path, rpmfiFCaps(fi));
+ rc = fsmSetFCaps(fd, dirfd, path, rpmfiFCaps(fi));
}
if (!rc) {
- rc = fsmUtime(path, st->st_mode, rpmfiFMtime(fi));
+ rc = fsmUtime(fd, dirfd, path, st->st_mode, rpmfiFMtime(fi));
}
if (!rc) {
rc = rpmpluginsCallFsmFilePrepare(plugins, fi,
- path, dest, st->st_mode, action);
+ fd, path, dest,
+ st->st_mode, action);
}
return rc;
}
-static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *suffix)
+static int fsmCommit(int dirfd, char **path, rpmfi fi, rpmFileAction action, const char *suffix)
{
int rc = 0;
@@ -772,15 +773,18 @@
/* Rename temporary to final file name if needed. */
if (dest != *path) {
- rc = fsmRename(*path, dest);
- if (!rc && nsuffix) {
- char * opath = fsmFsPath(fi, NULL);
- rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
- opath, dest);
- free(opath);
- }
- free(*path);
- *path = dest;
+ rc = fsmRename(dirfd, *path, dirfd, dest);
+ if (!rc) {
+ if (nsuffix) {
+ char * opath = fsmFsPath(fi, NULL);
+ rpmlog(RPMLOG_WARNING, _("%s%s created as %s%s\n"),
+ rpmfiDN(fi), opath, rpmfiDN(fi), dest);
+ free(opath);
+ }
+ free(*path);
+ *path = dest;
+ } else
+ free(dest);
}
}
@@ -831,191 +835,277 @@
}
}
+struct diriter_s {
+ int dirfd;
+ int firstdir;
+};
+
+static int onChdir(rpmfi fi, void *data)
+{
+ struct diriter_s *di = data;
+
+ fsmClose(&(di->dirfd));
+ return 0;
+}
+
+static rpmfi fsmIter(FD_t payload, rpmfiles files, rpmFileIter iter, void *data)
+{
+ rpmfi fi;
+ if (payload)
+ fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
+ else
+ fi = rpmfilesIter(files, iter);
+ if (fi && data)
+ rpmfiSetOnChdir(fi, onChdir, data);
+ return fi;
+}
+
+static rpmfi fsmIterFini(rpmfi fi, struct diriter_s *di)
+{
+ fsmClose(&(di->dirfd));
+ fsmClose(&(di->firstdir));
+ return rpmfiFree(fi);
+}
+
int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
rpmpsm psm, char ** failedFile)
{
FD_t payload = rpmtePayload(te);
- rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
+ rpmfi fi = NULL;
rpmfs fs = rpmteGetFileStates(te);
rpmPlugins plugins = rpmtsPlugins(ts);
- struct stat sb;
- int saveerrno = errno;
int rc = 0;
+ int fx = -1;
+ int fc = rpmfilesFC(files);
int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
- int firsthardlink = -1;
- FD_t firstlinkfile = NULL;
- int skip;
- rpmFileAction action;
+ int firstlinkfile = -1;
char *tid = NULL;
- const char *suffix;
- char *fpath = NULL;
-
- if (fi == NULL) {
- rc = RPMERR_BAD_MAGIC;
- goto exit;
- }
+ struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
+ struct filedata_s *firstlink = NULL;
+ struct diriter_s di = { -1, -1 };
/* transaction id used for temporary path suffix while installing */
rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
- /* Detect and create directories not explicitly in package. */
- rc = fsmMkdirs(files, fs, plugins);
+ /* Collect state data for the whole operation */
+ fi = rpmfilesIter(files, RPMFI_ITER_FWD);
+ while (!rc && (fx = rpmfiNext(fi)) >= 0) {
+ struct filedata_s *fp = &fdata[fx];
+ if (rpmfiFFlags(fi) & RPMFILE_GHOST)
+ fp->action = FA_SKIP;
+ else
+ fp->action = rpmfsGetAction(fs, fx);
+ fp->skip = XFA_SKIPPING(fp->action);
+ if (XFA_CREATING(fp->action) && !S_ISDIR(rpmfiFMode(fi)))
+ fp->suffix = tid;
+ fp->fpath = fsmFsPath(fi, fp->suffix);
- while (!rc) {
- /* Read next payload header. */
- rc = rpmfiNext(fi);
+ /* Remap file perms, owner, and group. */
+ rc = rpmfiStat(fi, 1, &fp->sb);
- if (rc < 0) {
- if (rc == RPMERR_ITER_END)
- rc = 0;
- break;
- }
+ /* Hardlinks are tricky and handled elsewhere for install */
+ fp->setmeta = (fp->skip == 0) &&
+ (fp->sb.st_nlink == 1 || fp->action == FA_TOUCH);
- action = rpmfsGetAction(fs, rpmfiFX(fi));
- skip = XFA_SKIPPING(action);
- if (action != FA_TOUCH) {
- suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
- } else {
- suffix = NULL;
- }
- fpath = fsmFsPath(fi, suffix);
+ setFileState(fs, fx);
+ fsmDebug(fp->fpath, fp->action, &fp->sb);
- /* Remap file perms, owner, and group. */
- rc = rpmfiStat(fi, 1, &sb);
+ fp->stage = FILE_PRE;
+ }
+ fi = rpmfiFree(fi);
- fsmDebug(fpath, action, &sb);
+ if (rc)
+ goto exit;
- /* Exit on error. */
- if (rc)
- break;
+ fi = fsmIter(payload, files,
+ payload ? RPMFI_ITER_READ_ARCHIVE : RPMFI_ITER_FWD, &di);
- /* Run fsm file pre hook for all plugins */
- rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
- sb.st_mode, action);
- if (rc) {
- skip = 1;
- } else {
- setFileState(fs, rpmfiFX(fi));
- }
+ if (fi == NULL) {
+ rc = RPMERR_BAD_MAGIC;
+ goto exit;
+ }
- if (!skip) {
- int setmeta = 1;
+ /* Process the payload */
+ while (!rc && (fx = rpmfiNext(fi)) >= 0) {
+ struct filedata_s *fp = &fdata[fx];
+
+ /*
+ * Tricksy case: this file is a being skipped, but it's part of
+ * a hardlinked set and has the actual content linked with it.
+ * Write the content to the first non-skipped file of the set
+ * instead.
+ */
+ if (fp->skip && firstlink && rpmfiArchiveHasContent(fi))
+ fp = firstlink;
+
+ if (!fp->skip) {
+ int mayopen = 0;
+ int fd = -1;
+ rc = ensureDir(plugins, rpmfiDN(fi), 0,
+ (fp->action == FA_CREATE), 0, &di.dirfd);
/* Directories replacing something need early backup */
- if (!suffix) {
- rc = fsmBackup(fi, action);
+ if (!rc && !fp->suffix && fp != firstlink) {
+ rc = fsmBackup(di.dirfd, fi, fp->action);
}
+
+ /* Run fsm file pre hook for all plugins */
+ if (!rc)
+ rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
+ fp->sb.st_mode, fp->action);
+ if (rc)
+ goto setmeta; /* for error notification */
+
/* Assume file does't exist when tmp suffix is in use */
- if (!suffix) {
- rc = fsmVerify(fpath, fi);
+ if (!fp->suffix) {
+ if (fp->action == FA_TOUCH) {
+ struct stat sb;
+ rc = fsmStat(di.dirfd, fp->fpath, 1, &sb);
+ } else {
+ rc = fsmVerify(di.dirfd, fp->fpath, fi);
+ }
} else {
rc = RPMERR_ENOENT;
}
/* See if the file was removed while our attention was elsewhere */
- if (rc == RPMERR_ENOENT && action == FA_TOUCH) {
- rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n", fpath);
- action = FA_CREATE;
- fsmDebug(fpath, action, &sb);
+ if (rc == RPMERR_ENOENT && fp->action == FA_TOUCH) {
+ rpmlog(RPMLOG_DEBUG, "file %s vanished unexpectedly\n",
+ fp->fpath);
+ fp->action = FA_CREATE;
+ fsmDebug(fp->fpath, fp->action, &fp->sb);
}
/* When touching we don't need any of this... */
- if (action == FA_TOUCH)
- goto touch;
+ if (fp->action == FA_TOUCH)
+ goto setmeta;
- if (S_ISREG(sb.st_mode)) {
+ if (S_ISREG(fp->sb.st_mode)) {
if (rc == RPMERR_ENOENT) {
- rc = fsmMkfile(fi, fpath, files, psm, nodigest,
- &setmeta, &firsthardlink, &firstlinkfile);
+ rc = fsmMkfile(di.dirfd, fi, fp, files, psm, nodigest,
+ &firstlink, &firstlinkfile, &di.firstdir,
+ &fd);
}
- } else if (S_ISDIR(sb.st_mode)) {
+ } else if (S_ISDIR(fp->sb.st_mode)) {
if (rc == RPMERR_ENOENT) {
- mode_t mode = sb.st_mode;
+ mode_t mode = fp->sb.st_mode;
mode &= ~07777;
mode |= 00700;
- rc = fsmMkdir(fpath, mode);
+ rc = fsmMkdir(di.dirfd, fp->fpath, mode);
}
- } else if (S_ISLNK(sb.st_mode)) {
+ } else if (S_ISLNK(fp->sb.st_mode)) {
if (rc == RPMERR_ENOENT) {
- rc = fsmSymlink(rpmfiFLink(fi), fpath);
+ rc = fsmSymlink(rpmfiFLink(fi), di.dirfd, fp->fpath);
}
- } else if (S_ISFIFO(sb.st_mode)) {
+ } else if (S_ISFIFO(fp->sb.st_mode)) {
/* This mimics cpio S_ISSOCK() behavior but probably isn't right */
if (rc == RPMERR_ENOENT) {
- rc = fsmMkfifo(fpath, 0000);
+ rc = fsmMkfifo(di.dirfd, fp->fpath, 0000);
}
- } else if (S_ISCHR(sb.st_mode) ||
- S_ISBLK(sb.st_mode) ||
- S_ISSOCK(sb.st_mode))
+ } else if (S_ISCHR(fp->sb.st_mode) ||
+ S_ISBLK(fp->sb.st_mode) ||
+ S_ISSOCK(fp->sb.st_mode))
{
if (rc == RPMERR_ENOENT) {
- rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
+ rc = fsmMknod(di.dirfd, fp->fpath, fp->sb.st_mode, fp->sb.st_rdev);
}
} else {
/* XXX Special case /dev/log, which shouldn't be packaged anyways */
- if (!IS_DEV_LOG(fpath))
+ if (!IS_DEV_LOG(fp->fpath))
rc = RPMERR_UNKNOWN_FILETYPE;
}
-touch:
- /* Set permissions, timestamps etc for non-hardlink entries */
- if (!rc && setmeta) {
- rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps);
+setmeta:
+ /* Special files require path-based ops */
+ mayopen = S_ISREG(fp->sb.st_mode) || S_ISDIR(fp->sb.st_mode);
+ if (!rc && fd == -1 && mayopen) {
+ int flags = O_RDONLY;
+ /* Only follow safe symlinks, and never on temporary files */
+ if (fp->suffix)
+ flags |= AT_SYMLINK_NOFOLLOW;
+ fd = fsmOpenat(di.dirfd, fp->fpath, flags,
+ S_ISDIR(fp->sb.st_mode));
+ if (fd < 0)
+ rc = RPMERR_OPEN_FAILED;
}
- } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
- /* On FA_TOUCH no hardlinks are created thus this is skipped. */
- /* we skip the hard linked file containing the content */
- /* write the content to the first used instead */
- char *fn = rpmfilesFN(files, firsthardlink);
- rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm);
- wfd_close(&firstlinkfile);
- firsthardlink = -1;
- free(fn);
- }
-
- if (rc) {
- if (!skip) {
- /* XXX only erase if temp fn w suffix is in use */
- if (suffix) {
- (void) fsmRemove(fpath, sb.st_mode);
- }
- errno = saveerrno;
- }
- } else {
- /* Notify on success. */
- rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
- if (!skip) {
- /* Backup file if needed. Directories are handled earlier */
- if (suffix)
- rc = fsmBackup(fi, action);
-
- if (!rc)
- rc = fsmCommit(&fpath, fi, action, suffix);
+ if (!rc && fp->setmeta) {
+ rc = fsmSetmeta(fd, di.dirfd, fp->fpath,
+ fi, plugins, fp->action,
+ &fp->sb, nofcaps);
}
+
+ if (fd != firstlinkfile)
+ fsmClose(&fd);
}
+ /* Notify on success. */
if (rc)
- *failedFile = xstrdup(fpath);
+ *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL);
+ else
+ rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
+ fp->stage = FILE_UNPACK;
+ }
+ fi = fsmIterFini(fi, &di);
- /* Run fsm file post hook for all plugins */
- rpmpluginsCallFsmFilePost(plugins, fi, fpath,
- sb.st_mode, action, rc);
- fpath = _free(fpath);
+ if (!rc && fx < 0 && fx != RPMERR_ITER_END)
+ rc = fx;
+
+ /* If all went well, commit files to final destination */
+ fi = fsmIter(NULL, files, RPMFI_ITER_FWD, &di);
+ while (!rc && (fx = rpmfiNext(fi)) >= 0) {
+ struct filedata_s *fp = &fdata[fx];
+
+ if (!fp->skip) {
+ if (!rc)
+ rc = ensureDir(NULL, rpmfiDN(fi), 0, 0, 0, &di.dirfd);
+
+ /* Backup file if needed. Directories are handled earlier */
+ if (!rc && fp->suffix)
+ rc = fsmBackup(di.dirfd, fi, fp->action);
+
+ if (!rc)
+ rc = fsmCommit(di.dirfd, &fp->fpath, fi, fp->action, fp->suffix);
+
+ if (!rc)
+ fp->stage = FILE_COMMIT;
+ else
+ *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL);
+
+ /* Run fsm file post hook for all plugins for all processed files */
+ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
+ fp->sb.st_mode, fp->action, rc);
+ }
+ }
+ fi = fsmIterFini(fi, &di);
+
+ /* On failure, walk backwards and erase non-committed files */
+ if (rc) {
+ fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di);
+ while ((fx = rpmfiNext(fi)) >= 0) {
+ struct filedata_s *fp = &fdata[fx];
+
+ /* If the directory doesn't exist there's nothing to clean up */
+ if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd))
+ continue;
+
+ if (fp->stage > FILE_NONE && !fp->skip) {
+ (void) fsmRemove(di.dirfd, fp->fpath, fp->sb.st_mode);
+ }
+ }
}
rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
exit:
-
- /* No need to bother with close errors on read */
- rpmfiArchiveClose(fi);
- rpmfiFree(fi);
+ fi = fsmIterFini(fi, &di);
Fclose(payload);
free(tid);
- free(fpath);
+ for (int i = 0; i < fc; i++)
+ free(fdata[i].fpath);
+ free(fdata);
return rc;
}
@@ -1024,32 +1114,42 @@
int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
rpmpsm psm, char ** failedFile)
{
- rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK);
+ struct diriter_s di = { -1, -1 };
+ rpmfi fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di);
rpmfs fs = rpmteGetFileStates(te);
rpmPlugins plugins = rpmtsPlugins(ts);
- struct stat sb;
+ int fc = rpmfilesFC(files);
+ int fx = -1;
+ struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
int rc = 0;
- char *fpath = NULL;
- while (!rc && rpmfiNext(fi) >= 0) {
- rpmFileAction action = rpmfsGetAction(fs, rpmfiFX(fi));
- fpath = fsmFsPath(fi, NULL);
- rc = fsmStat(fpath, 1, &sb);
+ while (!rc && (fx = rpmfiNext(fi)) >= 0) {
+ struct filedata_s *fp = &fdata[fx];
+ fp->action = rpmfsGetAction(fs, rpmfiFX(fi));
+
+ if (XFA_SKIPPING(fp->action))
+ continue;
+
+ fp->fpath = fsmFsPath(fi, NULL);
+ /* If the directory doesn't exist there's nothing to clean up */
+ if (ensureDir(NULL, rpmfiDN(fi), 0, 0, 1, &di.dirfd))
+ continue;
+
+ rc = fsmStat(di.dirfd, fp->fpath, 1, &fp->sb);
- fsmDebug(fpath, action, &sb);
+ fsmDebug(fp->fpath, fp->action, &fp->sb);
/* Run fsm file pre hook for all plugins */
- rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
- sb.st_mode, action);
+ rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath,
+ fp->sb.st_mode, fp->action);
- if (!XFA_SKIPPING(action))
- rc = fsmBackup(fi, action);
+ rc = fsmBackup(di.dirfd, fi, fp->action);
/* Remove erased files. */
- if (action == FA_ERASE) {
+ if (fp->action == FA_ERASE) {
int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
- rc = fsmRemove(fpath, sb.st_mode);
+ rc = fsmRemove(di.dirfd, fp->fpath, fp->sb.st_mode);
/*
* Missing %ghost or %missingok entries are not errors.
@@ -1074,20 +1174,20 @@
if (rc) {
int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING;
rpmlog(lvl, _("%s %s: remove failed: %s\n"),
- S_ISDIR(sb.st_mode) ? _("directory") : _("file"),
- fpath, strerror(errno));
+ S_ISDIR(fp->sb.st_mode) ? _("directory") : _("file"),
+ fp->fpath, strerror(errno));
}
}
/* Run fsm file post hook for all plugins */
- rpmpluginsCallFsmFilePost(plugins, fi, fpath,
- sb.st_mode, action, rc);
+ rpmpluginsCallFsmFilePost(plugins, fi, fp->fpath,
+ fp->sb.st_mode, fp->action, rc);
/* XXX Failure to remove is not (yet) cause for failure. */
if (!strict_erasures) rc = 0;
if (rc)
- *failedFile = xstrdup(fpath);
+ *failedFile = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL);
if (rc == 0) {
/* Notify on success. */
@@ -1095,11 +1195,12 @@
rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi);
rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount);
}
- fpath = _free(fpath);
}
- free(fpath);
- rpmfiFree(fi);
+ for (int i = 0; i < fc; i++)
+ free(fdata[i].fpath);
+ free(fdata);
+ fsmIterFini(fi, &di);
return rc;
}