1101 lines
27 KiB
C
1101 lines
27 KiB
C
/** \ingroup payload
|
|
* \file lib/fsm.c
|
|
* File state machine to handle a payload from a package.
|
|
*/
|
|
|
|
#include "system.h"
|
|
|
|
#include <utime.h>
|
|
#include <errno.h>
|
|
#if WITH_CAP
|
|
#include <sys/capability.h>
|
|
#endif
|
|
|
|
#include <rpm/rpmte.h>
|
|
#include <rpm/rpmts.h>
|
|
#include <rpm/rpmlog.h>
|
|
#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 "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
|
|
|
|
/*
|
|
* XXX Forward declarations for previously exported functions to avoid moving
|
|
* things around needlessly
|
|
*/
|
|
static const char * fileActionString(rpmFileAction a);
|
|
|
|
/** \ingroup payload
|
|
* Build path to file from file info, optionally ornamented with suffix.
|
|
* @param fi file info iterator
|
|
* @param suffix suffix to use (NULL disables)
|
|
* @retval 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;
|
|
}
|
|
|
|
/** \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)
|
|
{
|
|
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;
|
|
|
|
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");
|
|
}
|
|
}
|
|
return dnli;
|
|
}
|
|
|
|
/** \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)
|
|
{
|
|
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;
|
|
}
|
|
return dn;
|
|
}
|
|
|
|
static int fsmSetFCaps(const char *path, const char *captxt)
|
|
{
|
|
int rc = 0;
|
|
#if 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 (_fsm_debug) {
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
|
|
path, captxt, (rc < 0 ? strerror(errno) : ""));
|
|
}
|
|
cap_free(fcaps);
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
static void wfd_close(FD_t *wfdp)
|
|
{
|
|
if (wfdp && *wfdp) {
|
|
int myerrno = errno;
|
|
static int oneshot = 0;
|
|
static int flush_io = 0;
|
|
if (!oneshot) {
|
|
flush_io = (rpmExpandNumeric("%{?_flush_io}") > 0);
|
|
oneshot = 1;
|
|
}
|
|
if (flush_io) {
|
|
int fdno = Fileno(*wfdp);
|
|
fsync(fdno);
|
|
}
|
|
Fclose(*wfdp);
|
|
*wfdp = NULL;
|
|
errno = myerrno;
|
|
}
|
|
}
|
|
|
|
static int wfd_open(FD_t *wfdp, 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)) {
|
|
rc = RPMERR_OPEN_FAILED;
|
|
goto exit;
|
|
}
|
|
|
|
return 0;
|
|
|
|
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)
|
|
{
|
|
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:
|
|
return rc;
|
|
}
|
|
|
|
static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
|
|
rpmpsm psm, int nodigest, int *setmeta,
|
|
int * firsthardlink, FD_t *firstlinkfile)
|
|
{
|
|
int rc = 0;
|
|
int numHardlinks = rpmfiFNlink(fi);
|
|
|
|
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);
|
|
}
|
|
}
|
|
/* 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 (!rc)
|
|
rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
|
|
wfd_close(firstlinkfile);
|
|
*firsthardlink = -1;
|
|
} else {
|
|
*setmeta = 0;
|
|
}
|
|
|
|
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(const char *path, int dolstat, struct stat *sb)
|
|
{
|
|
int rc;
|
|
if (dolstat){
|
|
rc = lstat(path, sb);
|
|
} else {
|
|
rc = stat(path, sb);
|
|
}
|
|
if (_fsm_debug && rc && errno != ENOENT)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n",
|
|
__func__,
|
|
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(const char *path)
|
|
{
|
|
int rc = rmdir(path);
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
|
|
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(const char *path, mode_t mode)
|
|
{
|
|
int rc = mkdir(path, (mode & 07777));
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
|
|
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)
|
|
{
|
|
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) : ""));
|
|
}
|
|
|
|
if (rc < 0)
|
|
rc = RPMERR_MKFIFO_FAILED;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int fsmMknod(const char *path, mode_t mode, dev_t dev)
|
|
{
|
|
/* FIX: check S_IFIFO or dev != 0 */
|
|
int rc = mknod(path, (mode & ~07777), dev);
|
|
|
|
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) : ""));
|
|
}
|
|
|
|
if (rc < 0)
|
|
rc = RPMERR_MKNOD_FAILED;
|
|
|
|
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)
|
|
{
|
|
DNLI_t dnli = dnlInitIterator(files, fs, 0);
|
|
struct stat sb;
|
|
const char *dpath;
|
|
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 (dnlen == ldnlen && rstreq(dpath, ldn))
|
|
continue;
|
|
|
|
/* Copy as we need to modify the string */
|
|
(void) stpcpy(dn, dpath);
|
|
|
|
/* Assume '/' directory exists, "mkdir -p" for others if non-existent */
|
|
for (i = 1, te = dn + 1; *te != '\0'; te++, i++) {
|
|
if (*te != '/')
|
|
continue;
|
|
|
|
/* Already validated? */
|
|
if (i < ldnlen &&
|
|
(ldn[i] == '/' || ldn[i] == '\0') && rstreqn(dn, ldn, i))
|
|
continue;
|
|
|
|
/* Validate next component of path. */
|
|
*te = '\0';
|
|
rc = fsmStat(dn, 1, &sb); /* lstat */
|
|
*te = '/';
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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",
|
|
dn, (unsigned)(mode & 07777));
|
|
}
|
|
*te = '/';
|
|
}
|
|
if (rc)
|
|
break;
|
|
}
|
|
if (rc) break;
|
|
|
|
/* Save last validated path. */
|
|
ldn = dpath;
|
|
ldnlen = dnlen;
|
|
}
|
|
dnlFreeIterator(dnli);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void removeSBITS(const char *path)
|
|
{
|
|
struct stat stb;
|
|
if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) {
|
|
if ((stb.st_mode & 06000) != 0) {
|
|
(void) chmod(path, stb.st_mode & 0777);
|
|
}
|
|
#if WITH_CAP
|
|
if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
|
|
(void) cap_set_file(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, const char *path)
|
|
{
|
|
int rc = symlink(opath, path);
|
|
|
|
if (_fsm_debug) {
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
|
|
opath, path, (rc < 0 ? strerror(errno) : ""));
|
|
}
|
|
|
|
if (rc < 0)
|
|
rc = RPMERR_SYMLINK_FAILED;
|
|
return rc;
|
|
}
|
|
|
|
static int fsmUnlink(const char *path)
|
|
{
|
|
int rc = 0;
|
|
removeSBITS(path);
|
|
rc = unlink(path);
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
|
|
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)
|
|
{
|
|
removeSBITS(path);
|
|
int rc = rename(opath, 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);
|
|
free(rmpath);
|
|
}
|
|
#endif
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
|
|
opath, 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)
|
|
{
|
|
return S_ISDIR(mode) ? fsmRmdir(path) : fsmUnlink(path);
|
|
}
|
|
|
|
static int fsmChown(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;
|
|
}
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", __func__,
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
|
|
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)
|
|
{
|
|
int rc = 0;
|
|
struct timeval stamps[2] = {
|
|
{ .tv_sec = mtime, .tv_usec = 0 },
|
|
{ .tv_sec = mtime, .tv_usec = 0 },
|
|
};
|
|
|
|
#if HAVE_LUTIMES
|
|
rc = lutimes(path, stamps);
|
|
#else
|
|
if (!S_ISLNK(mode))
|
|
rc = utimes(path, stamps);
|
|
#endif
|
|
|
|
if (_fsm_debug)
|
|
rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", __func__,
|
|
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(const char *path, rpmfi fi)
|
|
{
|
|
int rc;
|
|
int saveerrno = errno;
|
|
struct stat dsb;
|
|
mode_t mode = rpmfiFMode(fi);
|
|
|
|
rc = fsmStat(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);
|
|
/* XXX shouldn't we take unlink return code here? */
|
|
if (!rc)
|
|
(void) fsmUnlink(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(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(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(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(opath, path);
|
|
if (!rc) {
|
|
rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"), opath, path);
|
|
}
|
|
free(path);
|
|
free(opath);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int fsmSetmeta(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(path, st->st_mode, st->st_uid, st->st_gid);
|
|
}
|
|
if (!rc && !S_ISLNK(st->st_mode)) {
|
|
rc = fsmChmod(path, st->st_mode);
|
|
}
|
|
/* Set file capabilities (if enabled) */
|
|
if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) {
|
|
rc = fsmSetFCaps(path, rpmfiFCaps(fi));
|
|
}
|
|
if (!rc) {
|
|
rc = fsmUtime(path, st->st_mode, rpmfiFMtime(fi));
|
|
}
|
|
if (!rc) {
|
|
rc = rpmpluginsCallFsmFilePrepare(plugins, fi,
|
|
path, dest, st->st_mode, action);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int fsmCommit(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(*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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
rpmfs fs = rpmteGetFileStates(te);
|
|
rpmPlugins plugins = rpmtsPlugins(ts);
|
|
struct stat sb;
|
|
int saveerrno = errno;
|
|
int rc = 0;
|
|
int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
|
|
int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
|
|
int firsthardlink = -1;
|
|
FD_t firstlinkfile = NULL;
|
|
int skip;
|
|
rpmFileAction action;
|
|
char *tid = NULL;
|
|
const char *suffix;
|
|
char *fpath = NULL;
|
|
|
|
if (fi == NULL) {
|
|
rc = RPMERR_BAD_MAGIC;
|
|
goto exit;
|
|
}
|
|
|
|
/* transaction id used for temporary path suffix while installing */
|
|
rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
|
|
|
|
/* Detect and create directories not explicitly in package. */
|
|
rc = fsmMkdirs(files, fs, plugins);
|
|
|
|
while (!rc) {
|
|
/* Read next payload header. */
|
|
rc = rpmfiNext(fi);
|
|
|
|
if (rc < 0) {
|
|
if (rc == RPMERR_ITER_END)
|
|
rc = 0;
|
|
break;
|
|
}
|
|
|
|
action = rpmfsGetAction(fs, rpmfiFX(fi));
|
|
skip = XFA_SKIPPING(action);
|
|
if (action != FA_TOUCH) {
|
|
suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
|
|
} else {
|
|
suffix = NULL;
|
|
}
|
|
fpath = fsmFsPath(fi, suffix);
|
|
|
|
/* Remap file perms, owner, and group. */
|
|
rc = rpmfiStat(fi, 1, &sb);
|
|
|
|
fsmDebug(fpath, action, &sb);
|
|
|
|
/* Exit on error. */
|
|
if (rc)
|
|
break;
|
|
|
|
/* 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 (!skip) {
|
|
int setmeta = 1;
|
|
|
|
/* When touching we don't need any of this... */
|
|
if (action == FA_TOUCH)
|
|
goto touch;
|
|
|
|
/* Directories replacing something need early backup */
|
|
if (!suffix) {
|
|
rc = fsmBackup(fi, action);
|
|
}
|
|
/* Assume file does't exist when tmp suffix is in use */
|
|
if (!suffix) {
|
|
rc = fsmVerify(fpath, fi);
|
|
} else {
|
|
rc = RPMERR_ENOENT;
|
|
}
|
|
|
|
if (S_ISREG(sb.st_mode)) {
|
|
if (rc == RPMERR_ENOENT) {
|
|
rc = fsmMkfile(fi, fpath, files, psm, nodigest,
|
|
&setmeta, &firsthardlink, &firstlinkfile);
|
|
}
|
|
} else if (S_ISDIR(sb.st_mode)) {
|
|
if (rc == RPMERR_ENOENT) {
|
|
mode_t mode = sb.st_mode;
|
|
mode &= ~07777;
|
|
mode |= 00700;
|
|
rc = fsmMkdir(fpath, mode);
|
|
}
|
|
} else if (S_ISLNK(sb.st_mode)) {
|
|
if (rc == RPMERR_ENOENT) {
|
|
rc = fsmSymlink(rpmfiFLink(fi), fpath);
|
|
}
|
|
} else if (S_ISFIFO(sb.st_mode)) {
|
|
/* This mimics cpio S_ISSOCK() behavior but probably isn't right */
|
|
if (rc == RPMERR_ENOENT) {
|
|
rc = fsmMkfifo(fpath, 0000);
|
|
}
|
|
} else if (S_ISCHR(sb.st_mode) ||
|
|
S_ISBLK(sb.st_mode) ||
|
|
S_ISSOCK(sb.st_mode))
|
|
{
|
|
if (rc == RPMERR_ENOENT) {
|
|
rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
|
|
}
|
|
} else {
|
|
/* XXX Special case /dev/log, which shouldn't be packaged anyways */
|
|
if (!IS_DEV_LOG(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);
|
|
}
|
|
} 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)
|
|
*failedFile = xstrdup(fpath);
|
|
|
|
/* Run fsm file post hook for all plugins */
|
|
rpmpluginsCallFsmFilePost(plugins, fi, fpath,
|
|
sb.st_mode, action, rc);
|
|
fpath = _free(fpath);
|
|
}
|
|
|
|
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);
|
|
Fclose(payload);
|
|
free(tid);
|
|
free(fpath);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
|
|
rpmpsm psm, char ** failedFile)
|
|
{
|
|
rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK);
|
|
rpmfs fs = rpmteGetFileStates(te);
|
|
rpmPlugins plugins = rpmtsPlugins(ts);
|
|
struct stat sb;
|
|
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);
|
|
|
|
fsmDebug(fpath, action, &sb);
|
|
|
|
/* Run fsm file pre hook for all plugins */
|
|
rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
|
|
sb.st_mode, action);
|
|
|
|
if (!XFA_SKIPPING(action))
|
|
rc = fsmBackup(fi, action);
|
|
|
|
/* Remove erased files. */
|
|
if (action == FA_ERASE) {
|
|
int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
|
|
|
|
rc = fsmRemove(fpath, 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(sb.st_mode) ? _("directory") : _("file"),
|
|
fpath, strerror(errno));
|
|
}
|
|
}
|
|
|
|
/* Run fsm file post hook for all plugins */
|
|
rpmpluginsCallFsmFilePost(plugins, fi, fpath,
|
|
sb.st_mode, action, rc);
|
|
|
|
/* XXX Failure to remove is not (yet) cause for failure. */
|
|
if (!strict_erasures) rc = 0;
|
|
|
|
if (rc)
|
|
*failedFile = xstrdup(fpath);
|
|
|
|
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);
|
|
}
|
|
fpath = _free(fpath);
|
|
}
|
|
|
|
free(fpath);
|
|
rpmfiFree(fi);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|