From 36ee14a07630668629a0d461fba8b5b2248d7d71 Mon Sep 17 00:00:00 2001 From: Florian Festi 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 #include #include -#if WITH_CAP +#include +#ifdef WITH_CAP #include #endif @@ -17,10 +19,11 @@ #include #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; }