/** \ingroup payload * \file lib/fsm.c * File state machine to handle a payload from a package. */ #include "system.h" #include #include #include #include #ifdef WITH_CAP #include #endif #include #include #include #include #include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */ #include "lib/fsm.h" #include "lib/rpmte_internal.h" /* XXX rpmfs */ #include "lib/rpmfi_internal.h" /* rpmfiSetOnChdir */ #include "lib/rpmplugins.h" /* rpm plugins hooks */ #include "lib/rpmug.h" #include "debug.h" #define _FSM_DEBUG 0 int _fsm_debug = _FSM_DEBUG; /* XXX Failure to remove is not (yet) cause for failure. */ static int strict_erasures = 0; #define SUFFIX_RPMORIG ".rpmorig" #define SUFFIX_RPMSAVE ".rpmsave" #define SUFFIX_RPMNEW ".rpmnew" /* Default directory and file permissions if not mapped */ #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) * @param[out] path to file (malloced) */ static char * fsmFsPath(rpmfi fi, const char * suffix) { const char *bn = rpmfiBN(fi); return rstrscat(NULL, *bn ? bn : "/", suffix ? suffix : "", NULL); } static int fsmLink(int odirfd, const char *opath, int dirfd, const char *path) { int rc = linkat(odirfd, opath, dirfd, path, 0); if (_fsm_debug) { rpmlog(RPMLOG_DEBUG, " %8s (%d %s, %d %s) %s\n", __func__, odirfd, opath, dirfd, path, (rc < 0 ? strerror(errno) : "")); } if (rc < 0) rc = RPMERR_LINK_FAILED; return rc; } #ifdef WITH_CAP static int cap_set_fileat(int dirfd, const char *path, cap_t fcaps) { 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 rc; } #endif static int fsmSetFCaps(int fd, int dirfd, const char *path, const char *captxt) { int rc = 0; #ifdef WITH_CAP if (captxt && *captxt != '\0') { cap_t fcaps = cap_from_text(captxt); 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 (%d - %d %s, %s) %s\n", __func__, fd, dirfd, path, captxt, (rc < 0 ? strerror(errno) : "")); } cap_free(fcaps); } #endif return rc; } static int fsmClose(int *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) { fsync(fdno); } 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 fsmOpen(int *wfdp, int dirfd, const char *dest) { int rc = 0; /* Create the file with 0200 permissions (write by owner). */ int fd = openat(dirfd, dest, O_WRONLY|O_EXCL|O_CREAT, 0200); if (fd < 0) rc = RPMERR_OPEN_FAILED; if (_fsm_debug) { rpmlog(RPMLOG_DEBUG, " %8s (%s [%d]) %s\n", __func__, dest, fd, (rc < 0 ? strerror(errno) : "")); } *wfdp = fd; return rc; } static int fsmUnpack(rpmfi fi, int fdno, rpmpsm psm, int nodigest) { 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(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 fd = -1; 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; } /* If the file has content, unpack it */ if (rpmfiArchiveHasContent(fi)) { if (!rc) 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; } static int fsmReadLink(const char *path, char *buf, size_t bufsize, size_t *linklen) { ssize_t llen = readlink(path, buf, bufsize - 1); int rc = RPMERR_READLINK_FAILED; if (_fsm_debug) { rpmlog(RPMLOG_DEBUG, " %8s (%s, buf, %d) %s\n", __func__, path, (int)(bufsize -1), (llen < 0 ? strerror(errno) : "")); } if (llen >= 0) { buf[llen] = '\0'; rc = 0; *linklen = llen; } return rc; } static int fsmStat(int dirfd, const char *path, int dolstat, struct stat *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 (%d %s, ost) %s\n", __func__, dirfd, path, (rc < 0 ? strerror(errno) : "")); if (rc < 0) { rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_LSTAT_FAILED); /* Ensure consistent struct content on failure */ memset(sb, 0, sizeof(*sb)); } return rc; } static int fsmRmdir(int dirfd, const char *path) { int rc = unlinkat(dirfd, path, AT_REMOVEDIR); if (_fsm_debug) 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; case ENOTEMPTY: rc = RPMERR_ENOTEMPTY; break; default: rc = RPMERR_RMDIR_FAILED; break; } return rc; } static int fsmMkdir(int dirfd, const char *path, mode_t mode) { int rc = mkdirat(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) : "")); if (rc < 0) rc = RPMERR_MKDIR_FAILED; return rc; } static int fsmOpenat(int dirfd, const char *path, int flags, int dir) { 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); } } /* O_DIRECTORY equivalent */ if (dir && fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode)) { errno = ENOTDIR; fsmClose(&fd); } return fd; } static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn, const char *apath, int owned, mode_t mode, int *fdp) { int rc; rpmFsmOp op = (FA_CREATE); if (!owned) op |= FAF_UNOWNED; /* 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) { rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, *fdp, apath, apath, mode, op); } /* Run fsm file post hook for all plugins */ rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc); if (!rc) { rpmlog(RPMLOG_DEBUG, "%s directory created with perms %04o\n", apath, (unsigned)(mode & 07777)); } return rc; } static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create, int quiet, int *dirfdp) { char *sp = NULL, *bn; char *apath = NULL; int oflags = O_RDONLY; int rc = 0; if (*dirfdp >= 0) return rc; int dirfd = fsmOpenat(-1, "/", oflags, 1); int fd = dirfd; /* special case of "/" */ char *path = xstrdup(p); char *dp = path; 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); } 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; } dp = NULL; } if (rc) { fsmClose(&fd); fsmClose(&dirfd); } else { rc = 0; } *dirfdp = dirfd; if (_fsm_debug) { rpmlog(RPMLOG_DEBUG, " %8s (%s: %d) %s\n", __func__, p, dirfd, (rc < 0 ? strerror(errno) : "")); } free(path); free(apath); return rc; } 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) : "")); } if (rc < 0) rc = RPMERR_MKFIFO_FAILED; return rc; } 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; int flags = AT_SYMLINK_NOFOLLOW; if (fstatat(dirfd, path, &stb, flags) == 0 && S_ISREG(stb.st_mode)) { if ((stb.st_mode & 06000) != 0) { (void) fchmodat(dirfd, path, stb.st_mode & 0777, flags); } #ifdef WITH_CAP if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) { (void) cap_set_fileat(dirfd, path, NULL); } #endif } } static void fsmDebug(const char *fpath, rpmFileAction action, const struct stat *st) { rpmlog(RPMLOG_DEBUG, "%-10s %06o%3d (%4d,%4d)%6d %s\n", fileActionString(action), (int)st->st_mode, (int)st->st_nlink, (int)st->st_uid, (int)st->st_gid, (int)st->st_size, (fpath ? fpath : "")); } static int fsmSymlink(const char *opath, int dirfd, const char *path) { int rc = symlinkat(opath, dirfd, path); if (_fsm_debug) { rpmlog(RPMLOG_DEBUG, " %8s (%s, %d %s) %s\n", __func__, opath, dirfd, path, (rc < 0 ? strerror(errno) : "")); } if (rc < 0) rc = RPMERR_SYMLINK_FAILED; return rc; } static int fsmUnlink(int dirfd, const char *path) { int rc = 0; removeSBITS(dirfd, path); rc = unlinkat(dirfd, path, 0); if (_fsm_debug) 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(int odirfd, const char *opath, int dirfd, const char *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); /* 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 (%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(int dirfd, const char *path, mode_t mode) { return S_ISDIR(mode) ? fsmRmdir(dirfd, path) : fsmUnlink(dirfd, path); } static int fsmChown(int fd, int dirfd, const char *path, mode_t mode, uid_t uid, gid_t gid) { 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 (%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(int fd, int dirfd, const char *path, mode_t mode) { 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 (%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(int fd, int dirfd, const char *path, mode_t mode, time_t mtime) { int rc = 0; struct timespec stamps[2] = { { .tv_sec = mtime, .tv_nsec = 0 }, { .tv_sec = mtime, .tv_nsec = 0 }, }; if (fd >= 0) rc = futimens(fd, stamps); else rc = utimensat(dirfd, path, stamps, AT_SYMLINK_NOFOLLOW); if (_fsm_debug) 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)) rc = 0; return rc; } 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(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(dirfd, path, dirfd, rmpath); /* XXX shouldn't we take unlink return code here? */ if (!rc) (void) fsmUnlink(dirfd, rmpath); else rc = RPMERR_UNLINK_FAILED; free(rmpath); return (rc ? rc : RPMERR_ENOENT); /* XXX HACK */ } else if (S_ISDIR(mode)) { if (S_ISDIR(dsb.st_mode)) return 0; if (S_ISLNK(dsb.st_mode)) { uid_t luid = dsb.st_uid; rc = fsmStat(dirfd, path, 0, &dsb); if (rc == RPMERR_ENOENT) rc = 0; if (rc) return rc; errno = saveerrno; /* Only permit directory symlinks by target owner and root */ if (S_ISDIR(dsb.st_mode) && (luid == 0 || luid == dsb.st_uid)) return 0; } } else if (S_ISLNK(mode)) { if (S_ISLNK(dsb.st_mode)) { char buf[8 * BUFSIZ]; size_t len; rc = fsmReadLink(path, buf, 8 * BUFSIZ, &len); errno = saveerrno; if (rc) return rc; if (rstreq(rpmfiFLink(fi), buf)) return 0; } } else if (S_ISFIFO(mode)) { if (S_ISFIFO(dsb.st_mode)) return 0; } else if (S_ISCHR(mode) || S_ISBLK(mode)) { if ((S_ISCHR(dsb.st_mode) || S_ISBLK(dsb.st_mode)) && (dsb.st_rdev == rpmfiFRdev(fi))) return 0; } else if (S_ISSOCK(mode)) { if (S_ISSOCK(dsb.st_mode)) return 0; } /* XXX shouldn't do this with commit/undo. */ rc = fsmUnlink(dirfd, path); if (rc == 0) rc = RPMERR_ENOENT; return (rc ? rc : RPMERR_ENOENT); /* XXX HACK */ } #define IS_DEV_LOG(_x) \ ((_x) != NULL && strlen(_x) >= (sizeof("/dev/log")-1) && \ rstreqn((_x), "/dev/log", sizeof("/dev/log")-1) && \ ((_x)[sizeof("/dev/log")-1] == '\0' || \ (_x)[sizeof("/dev/log")-1] == ';')) /* Rename pre-existing modified or unmanaged file. */ static int fsmBackup(int dirfd, rpmfi fi, rpmFileAction action) { int rc = 0; const char *suffix = NULL; if (!(rpmfiFFlags(fi) & RPMFILE_GHOST)) { switch (action) { case FA_SAVE: suffix = SUFFIX_RPMSAVE; break; case FA_BACKUP: suffix = SUFFIX_RPMORIG; break; default: break; } } if (suffix) { char * opath = fsmFsPath(fi, NULL); char * path = fsmFsPath(fi, suffix); rc = fsmRename(dirfd, opath, dirfd, path); if (!rc) { rpmlog(RPMLOG_WARNING, _("%s%s saved as %s%s\n"), rpmfiDN(fi), opath, rpmfiDN(fi), path); } free(path); free(opath); } return rc; } static int fsmSetmeta(int fd, int dirfd, const char *path, rpmfi fi, rpmPlugins plugins, rpmFileAction action, const struct stat * st, int nofcaps) { int rc = 0; const char *dest = rpmfiFN(fi); if (!rc && !getuid()) { rc = fsmChown(fd, dirfd, path, st->st_mode, st->st_uid, st->st_gid); } if (!rc && !S_ISLNK(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(fd, dirfd, path, rpmfiFCaps(fi)); } if (!rc) { rc = fsmUtime(fd, dirfd, path, st->st_mode, rpmfiFMtime(fi)); } if (!rc) { rc = rpmpluginsCallFsmFilePrepare(plugins, fi, fd, path, dest, st->st_mode, action); } return rc; } static int fsmCommit(int dirfd, char **path, rpmfi fi, rpmFileAction action, const char *suffix) { int rc = 0; /* XXX Special case /dev/log, which shouldn't be packaged anyways */ if (!(S_ISSOCK(rpmfiFMode(fi)) && IS_DEV_LOG(*path))) { const char *nsuffix = (action == FA_ALTNAME) ? SUFFIX_RPMNEW : NULL; char *dest = *path; /* Construct final destination path (nsuffix is usually NULL) */ if (suffix) dest = fsmFsPath(fi, nsuffix); /* Rename temporary to final file name if needed. */ if (dest != *path) { 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); } } return rc; } /** * Return formatted string representation of file disposition. * @param a file disposition * @return formatted string */ static const char * fileActionString(rpmFileAction a) { switch (a) { case FA_UNKNOWN: return "unknown"; case FA_CREATE: return "create"; case FA_BACKUP: return "backup"; case FA_SAVE: return "save"; case FA_SKIP: return "skip"; case FA_ALTNAME: return "altname"; case FA_ERASE: return "erase"; case FA_SKIPNSTATE: return "skipnstate"; case FA_SKIPNETSHARED: return "skipnetshared"; case FA_SKIPCOLOR: return "skipcolor"; case FA_TOUCH: return "touch"; default: return "???"; } } /* Remember any non-regular file state for recording in the rpmdb */ static void setFileState(rpmfs fs, int i) { switch (rpmfsGetAction(fs, i)) { case FA_SKIPNSTATE: rpmfsSetState(fs, i, RPMFILE_STATE_NOTINSTALLED); break; case FA_SKIPNETSHARED: rpmfsSetState(fs, i, RPMFILE_STATE_NETSHARED); break; case FA_SKIPCOLOR: rpmfsSetState(fs, i, RPMFILE_STATE_WRONGCOLOR); break; case FA_TOUCH: rpmfsSetState(fs, i, RPMFILE_STATE_NORMAL); break; default: break; } } 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 = NULL; rpmfs fs = rpmteGetFileStates(te); rpmPlugins plugins = rpmtsPlugins(ts); 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 firstlinkfile = -1; char *tid = NULL; 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)); /* 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); /* Remap file perms, owner, and group. */ rc = rpmfiStat(fi, 1, &fp->sb); /* Hardlinks are tricky and handled elsewhere for install */ fp->setmeta = (fp->skip == 0) && (fp->sb.st_nlink == 1 || fp->action == FA_TOUCH); setFileState(fs, fx); fsmDebug(fp->fpath, fp->action, &fp->sb); fp->stage = FILE_PRE; } fi = rpmfiFree(fi); if (rc) goto exit; fi = fsmIter(payload, files, payload ? RPMFI_ITER_READ_ARCHIVE : RPMFI_ITER_FWD, &di); if (fi == NULL) { rc = RPMERR_BAD_MAGIC; goto exit; } /* 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 (!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 (!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 && 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 (fp->action == FA_TOUCH) goto setmeta; if (S_ISREG(fp->sb.st_mode)) { if (rc == RPMERR_ENOENT) { rc = fsmMkfile(di.dirfd, fi, fp, files, psm, nodigest, &firstlink, &firstlinkfile, &di.firstdir, &fd); } } else if (S_ISDIR(fp->sb.st_mode)) { if (rc == RPMERR_ENOENT) { mode_t mode = fp->sb.st_mode; mode &= ~07777; mode |= 00700; rc = fsmMkdir(di.dirfd, fp->fpath, mode); } } else if (S_ISLNK(fp->sb.st_mode)) { if (rc == RPMERR_ENOENT) { rc = fsmSymlink(rpmfiFLink(fi), di.dirfd, fp->fpath); } } 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(di.dirfd, fp->fpath, 0000); } } 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(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(fp->fpath)) rc = RPMERR_UNKNOWN_FILETYPE; } 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; } 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 = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL); else rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi)); fp->stage = FILE_UNPACK; } fi = fsmIterFini(fi, &di); 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: fi = fsmIterFini(fi, &di); Fclose(payload); free(tid); for (int i = 0; i < fc; i++) free(fdata[i].fpath); free(fdata); return rc; } int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files, rpmpsm psm, char ** failedFile) { struct diriter_s di = { -1, -1 }; rpmfi fi = fsmIter(NULL, files, RPMFI_ITER_BACK, &di); rpmfs fs = rpmteGetFileStates(te); rpmPlugins plugins = rpmtsPlugins(ts); int fc = rpmfilesFC(files); int fx = -1; struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata)); int rc = 0; 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(fp->fpath, fp->action, &fp->sb); /* Run fsm file pre hook for all plugins */ rc = rpmpluginsCallFsmFilePre(plugins, fi, fp->fpath, fp->sb.st_mode, fp->action); rc = fsmBackup(di.dirfd, fi, fp->action); /* Remove erased files. */ if (fp->action == FA_ERASE) { int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST)); rc = fsmRemove(di.dirfd, fp->fpath, fp->sb.st_mode); /* * Missing %ghost or %missingok entries are not errors. * XXX: Are non-existent files ever an actual error here? Afterall * that's exactly what we're trying to accomplish here, * and complaining about job already done seems like kinderkarten * level "But it was MY turn!" whining... */ if (rc == RPMERR_ENOENT && missingok) { rc = 0; } /* * Dont whine on non-empty directories for now. We might be able * to track at least some of the expected failures though, * such as when we knowingly left config file backups etc behind. */ if (rc == RPMERR_ENOTEMPTY) { rc = 0; } if (rc) { int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING; rpmlog(lvl, _("%s %s: remove failed: %s\n"), S_ISDIR(fp->sb.st_mode) ? _("directory") : _("file"), fp->fpath, strerror(errno)); } } /* Run fsm file post hook for all plugins */ 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 = rstrscat(NULL, rpmfiDN(fi), fp->fpath, NULL); if (rc == 0) { /* Notify on success. */ /* On erase we're iterating backwards, fixup for progress */ rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi); rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount); } } for (int i = 0; i < fc; i++) free(fdata[i].fpath); free(fdata); fsmIterFini(fi, &di); return rc; }