Blame lib/fsm.c

2ff057
/** \ingroup payload
2ff057
 * \file lib/fsm.c
2ff057
 * File state machine to handle a payload from a package.
2ff057
 */
2ff057
2ff057
#include "system.h"
2ff057
2ff057
#include <utime.h>
2ff057
#include <errno.h>
2ff057
#if WITH_CAP
2ff057
#include <sys/capability.h>
2ff057
#endif
2ff057
2ff057
#include <rpm/rpmte.h>
2ff057
#include <rpm/rpmts.h>
2ff057
#include <rpm/rpmlog.h>
2ff057
#include <rpm/rpmmacro.h>
2ff057
2ff057
#include "rpmio/rpmio_internal.h"	/* fdInit/FiniDigest */
2ff057
#include "lib/fsm.h"
2ff057
#include "lib/rpmte_internal.h"	/* XXX rpmfs */
2ff057
#include "lib/rpmplugins.h"	/* rpm plugins hooks */
2ff057
#include "lib/rpmug.h"
2ff057
2ff057
#include "debug.h"
2ff057
2ff057
#define	_FSM_DEBUG	0
2ff057
int _fsm_debug = _FSM_DEBUG;
2ff057
2ff057
/* XXX Failure to remove is not (yet) cause for failure. */
2ff057
static int strict_erasures = 0;
2ff057
2ff057
#define	SUFFIX_RPMORIG	".rpmorig"
2ff057
#define	SUFFIX_RPMSAVE	".rpmsave"
2ff057
#define	SUFFIX_RPMNEW	".rpmnew"
2ff057
2ff057
/* Default directory and file permissions if not mapped */
2ff057
#define _dirPerms 0755
2ff057
#define _filePerms 0644
2ff057
2ff057
/* 
2ff057
 * XXX Forward declarations for previously exported functions to avoid moving 
2ff057
 * things around needlessly 
2ff057
 */ 
2ff057
static const char * fileActionString(rpmFileAction a);
2ff057
2ff057
/** \ingroup payload
2ff057
 * Build path to file from file info, optionally ornamented with suffix.
2ff057
 * @param fi		file info iterator
2ff057
 * @param suffix	suffix to use (NULL disables)
2ff057
 * @retval		path to file (malloced)
2ff057
 */
2ff057
static char * fsmFsPath(rpmfi fi, const char * suffix)
2ff057
{
2ff057
    return rstrscat(NULL, rpmfiDN(fi), rpmfiBN(fi), suffix ? suffix : "", NULL);
2ff057
}
2ff057
2ff057
/** \ingroup payload
2ff057
 * Directory name iterator.
2ff057
 */
2ff057
typedef struct dnli_s {
2ff057
    rpmfiles fi;
2ff057
    char * active;
2ff057
    int reverse;
2ff057
    int isave;
2ff057
    int i;
2ff057
} * DNLI_t;
2ff057
2ff057
/** \ingroup payload
2ff057
 * Destroy directory name iterator.
2ff057
 * @param dnli		directory name iterator
2ff057
 * @retval		NULL always
2ff057
 */
2ff057
static DNLI_t dnlFreeIterator(DNLI_t dnli)
2ff057
{
2ff057
    if (dnli) {
2ff057
	if (dnli->active) free(dnli->active);
2ff057
	free(dnli);
2ff057
    }
2ff057
    return NULL;
2ff057
}
2ff057
2ff057
/** \ingroup payload
2ff057
 * Create directory name iterator.
2ff057
 * @param fi		file info set
2ff057
 * @param fs		file state set
2ff057
 * @param reverse	traverse directory names in reverse order?
2ff057
 * @return		directory name iterator
2ff057
 */
2ff057
static DNLI_t dnlInitIterator(rpmfiles fi, rpmfs fs, int reverse)
2ff057
{
2ff057
    DNLI_t dnli;
2ff057
    int i, j;
2ff057
    int dc;
2ff057
2ff057
    if (fi == NULL)
2ff057
	return NULL;
2ff057
    dc = rpmfilesDC(fi);
2ff057
    dnli = xcalloc(1, sizeof(*dnli));
2ff057
    dnli->fi = fi;
2ff057
    dnli->reverse = reverse;
2ff057
    dnli->i = (reverse ? dc : 0);
2ff057
2ff057
    if (dc) {
2ff057
	dnli->active = xcalloc(dc, sizeof(*dnli->active));
2ff057
	int fc = rpmfilesFC(fi);
2ff057
2ff057
	/* Identify parent directories not skipped. */
2ff057
	for (i = 0; i < fc; i++)
2ff057
            if (!XFA_SKIPPING(rpmfsGetAction(fs, i)))
2ff057
		dnli->active[rpmfilesDI(fi, i)] = 1;
2ff057
2ff057
	/* Exclude parent directories that are explicitly included. */
2ff057
	for (i = 0; i < fc; i++) {
2ff057
	    int dil;
2ff057
	    size_t dnlen, bnlen;
2ff057
2ff057
	    if (!S_ISDIR(rpmfilesFMode(fi, i)))
2ff057
		continue;
2ff057
2ff057
	    dil = rpmfilesDI(fi, i);
2ff057
	    dnlen = strlen(rpmfilesDN(fi, dil));
2ff057
	    bnlen = strlen(rpmfilesBN(fi, i));
2ff057
2ff057
	    for (j = 0; j < dc; j++) {
2ff057
		const char * dnl;
2ff057
		size_t jlen;
2ff057
2ff057
		if (!dnli->active[j] || j == dil)
2ff057
		    continue;
2ff057
		dnl = rpmfilesDN(fi, j);
2ff057
		jlen = strlen(dnl);
2ff057
		if (jlen != (dnlen+bnlen+1))
2ff057
		    continue;
2ff057
		if (!rstreqn(dnl, rpmfilesDN(fi, dil), dnlen))
2ff057
		    continue;
2ff057
		if (!rstreqn(dnl+dnlen, rpmfilesBN(fi, i), bnlen))
2ff057
		    continue;
2ff057
		if (dnl[dnlen+bnlen] != '/' || dnl[dnlen+bnlen+1] != '\0')
2ff057
		    continue;
2ff057
		/* This directory is included in the package. */
2ff057
		dnli->active[j] = 0;
2ff057
		break;
2ff057
	    }
2ff057
	}
2ff057
2ff057
	/* Print only once per package. */
2ff057
	if (!reverse) {
2ff057
	    j = 0;
2ff057
	    for (i = 0; i < dc; i++) {
2ff057
		if (!dnli->active[i]) continue;
2ff057
		if (j == 0) {
2ff057
		    j = 1;
2ff057
		    rpmlog(RPMLOG_DEBUG,
2ff057
	"========== Directories not explicitly included in package:\n");
2ff057
		}
2ff057
		rpmlog(RPMLOG_DEBUG, "%10d %s\n", i, rpmfilesDN(fi, i));
2ff057
	    }
2ff057
	    if (j)
2ff057
		rpmlog(RPMLOG_DEBUG, "==========\n");
2ff057
	}
2ff057
    }
2ff057
    return dnli;
2ff057
}
2ff057
2ff057
/** \ingroup payload
2ff057
 * Return next directory name (from file info).
2ff057
 * @param dnli		directory name iterator
2ff057
 * @return		next directory name
2ff057
 */
2ff057
static
2ff057
const char * dnlNextIterator(DNLI_t dnli)
2ff057
{
2ff057
    const char * dn = NULL;
2ff057
2ff057
    if (dnli) {
2ff057
	rpmfiles fi = dnli->fi;
2ff057
	int dc = rpmfilesDC(fi);
2ff057
	int i = -1;
2ff057
2ff057
	if (dnli->active)
2ff057
	do {
2ff057
	    i = (!dnli->reverse ? dnli->i++ : --dnli->i);
2ff057
	} while (i >= 0 && i < dc && !dnli->active[i]);
2ff057
2ff057
	if (i >= 0 && i < dc)
2ff057
	    dn = rpmfilesDN(fi, i);
2ff057
	else
2ff057
	    i = -1;
2ff057
	dnli->isave = i;
2ff057
    }
2ff057
    return dn;
2ff057
}
2ff057
2ff057
static int fsmSetFCaps(const char *path, const char *captxt)
2ff057
{
2ff057
    int rc = 0;
2ff057
#if WITH_CAP
2ff057
    if (captxt && *captxt != '\0') {
2ff057
	cap_t fcaps = cap_from_text(captxt);
2ff057
	if (fcaps == NULL || cap_set_file(path, fcaps) != 0) {
2ff057
	    rc = RPMERR_SETCAP_FAILED;
2ff057
	}
2ff057
	cap_free(fcaps);
2ff057
    } 
2ff057
#endif
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static void wfd_close(FD_t *wfdp)
2ff057
{
2ff057
    if (wfdp && *wfdp) {
2ff057
	int myerrno = errno;
2ff057
	static int oneshot = 0;
2ff057
	static int flush_io = 0;
2ff057
	if (!oneshot) {
2ff057
	    flush_io = rpmExpandNumeric("%{?_flush_io}");
2ff057
	    oneshot = 1;
2ff057
	}
2ff057
	if (flush_io) {
2ff057
	    int fdno = Fileno(*wfdp);
2ff057
	    fsync(fdno);
2ff057
	}
2ff057
	Fclose(*wfdp);
2ff057
	*wfdp = NULL;
2ff057
	errno = myerrno;
2ff057
    }
2ff057
}
2ff057
2ff057
static int wfd_open(FD_t *wfdp, const char *dest)
2ff057
{
2ff057
    int rc = 0;
2ff057
    /* Create the file with 0200 permissions (write by owner). */
2ff057
    {
2ff057
	mode_t old_umask = umask(0577);
2ff057
	*wfdp = Fopen(dest, "wx.ufdio");
2ff057
	umask(old_umask);
2ff057
    }
2ff057
    if (Ferror(*wfdp)) {
2ff057
	rc = RPMERR_OPEN_FAILED;
2ff057
	goto exit;
2ff057
    }
2ff057
2ff057
    return 0;
2ff057
2ff057
exit:
2ff057
    wfd_close(wfdp);
2ff057
    return rc;
2ff057
}
2ff057
2ff057
/** \ingroup payload
2ff057
 * Create file from payload stream.
2ff057
 * @return		0 on success
2ff057
 */
2ff057
static int expandRegular(rpmfi fi, const char *dest, rpmpsm psm, int nodigest)
2ff057
{
2ff057
    FD_t wfd = NULL;
2ff057
    int rc;
2ff057
2ff057
    rc = wfd_open(&wfd, dest);
2ff057
    if (rc != 0)
2ff057
        goto exit;
2ff057
2ff057
    rc = rpmfiArchiveReadToFilePsm(fi, wfd, nodigest, psm);
2ff057
    wfd_close(&wfd;;
2ff057
exit:
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmMkfile(rpmfi fi, const char *dest, rpmfiles files,
2ff057
		     rpmpsm psm, int nodigest, int *setmeta,
2ff057
		     int * firsthardlink, FD_t *firstlinkfile)
2ff057
{
2ff057
    int rc = 0;
2ff057
    int numHardlinks = rpmfiFNlink(fi);
2ff057
2ff057
    if (numHardlinks > 1) {
2ff057
	/* Create first hardlinked file empty */
2ff057
	if (*firsthardlink < 0) {
2ff057
	    *firsthardlink = rpmfiFX(fi);
2ff057
	    rc = wfd_open(firstlinkfile, dest);
2ff057
	} else {
2ff057
	    /* Create hard links for others */
2ff057
	    char *fn = rpmfilesFN(files, *firsthardlink);
2ff057
	    rc = link(fn, dest);
2ff057
	    if (rc < 0) {
2ff057
		rc = RPMERR_LINK_FAILED;
2ff057
	    }
2ff057
	    free(fn);
2ff057
	}
2ff057
    }
2ff057
    /* Write normal files or fill the last hardlinked (already
2ff057
       existing) file with content */
2ff057
    if (numHardlinks<=1) {
2ff057
	if (!rc)
2ff057
	    rc = expandRegular(fi, dest, psm, nodigest);
2ff057
    } else if (rpmfiArchiveHasContent(fi)) {
2ff057
	if (!rc)
2ff057
	    rc = rpmfiArchiveReadToFilePsm(fi, *firstlinkfile, nodigest, psm);
2ff057
	wfd_close(firstlinkfile);
2ff057
	*firsthardlink = -1;
2ff057
    } else {
2ff057
	*setmeta = 0;
2ff057
    }
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmReadLink(const char *path,
2ff057
		       char *buf, size_t bufsize, size_t *linklen)
2ff057
{
2ff057
    ssize_t llen = readlink(path, buf, bufsize - 1);
2ff057
    int rc = RPMERR_READLINK_FAILED;
2ff057
2ff057
    if (_fsm_debug) {
2ff057
        rpmlog(RPMLOG_DEBUG, " %8s (%s, buf, %d) %s\n",
2ff057
	       __func__,
2ff057
               path, (int)(bufsize -1), (llen < 0 ? strerror(errno) : ""));
2ff057
    }
2ff057
2ff057
    if (llen >= 0) {
2ff057
	buf[llen] = '\0';
2ff057
	rc = 0;
2ff057
	*linklen = llen;
2ff057
    }
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmStat(const char *path, int dolstat, struct stat *sb)
2ff057
{
2ff057
    int rc;
2ff057
    if (dolstat){
2ff057
	rc = lstat(path, sb);
2ff057
    } else {
2ff057
        rc = stat(path, sb);
2ff057
    }
2ff057
    if (_fsm_debug && rc && errno != ENOENT)
2ff057
        rpmlog(RPMLOG_DEBUG, " %8s (%s, ost) %s\n",
2ff057
               __func__,
2ff057
               path, (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0) {
2ff057
        rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_LSTAT_FAILED);
2ff057
	/* Ensure consistent struct content on failure */
2ff057
        memset(sb, 0, sizeof(*sb));
2ff057
    }
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmRmdir(const char *path)
2ff057
{
2ff057
    int rc = rmdir(path);
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
2ff057
	       path, (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)
2ff057
	switch (errno) {
2ff057
	case ENOENT:        rc = RPMERR_ENOENT;    break;
2ff057
	case ENOTEMPTY:     rc = RPMERR_ENOTEMPTY; break;
2ff057
	default:            rc = RPMERR_RMDIR_FAILED; break;
2ff057
	}
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmMkdir(const char *path, mode_t mode)
2ff057
{
2ff057
    int rc = mkdir(path, (mode & 07777));
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
2ff057
	       path, (unsigned)(mode & 07777),
2ff057
	       (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)	rc = RPMERR_MKDIR_FAILED;
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmMkfifo(const char *path, mode_t mode)
2ff057
{
2ff057
    int rc = mkfifo(path, (mode & 07777));
2ff057
2ff057
    if (_fsm_debug) {
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n",
2ff057
	       __func__, path, (unsigned)(mode & 07777),
2ff057
	       (rc < 0 ? strerror(errno) : ""));
2ff057
    }
2ff057
2ff057
    if (rc < 0)
2ff057
	rc = RPMERR_MKFIFO_FAILED;
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmMknod(const char *path, mode_t mode, dev_t dev)
2ff057
{
2ff057
    /* FIX: check S_IFIFO or dev != 0 */
2ff057
    int rc = mknod(path, (mode & ~07777), dev);
2ff057
2ff057
    if (_fsm_debug) {
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%o, 0x%x) %s\n",
2ff057
	       __func__, path, (unsigned)(mode & ~07777),
2ff057
	       (unsigned)dev, (rc < 0 ? strerror(errno) : ""));
2ff057
    }
2ff057
2ff057
    if (rc < 0)
2ff057
	rc = RPMERR_MKNOD_FAILED;
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
/**
2ff057
 * Create (if necessary) directories not explicitly included in package.
2ff057
 * @param files		file data
2ff057
 * @param fs		file states
2ff057
 * @param plugins	rpm plugins handle
2ff057
 * @return		0 on success
2ff057
 */
2ff057
static int fsmMkdirs(rpmfiles files, rpmfs fs, rpmPlugins plugins)
2ff057
{
2ff057
    DNLI_t dnli = dnlInitIterator(files, fs, 0);
2ff057
    struct stat sb;
2ff057
    const char *dpath;
2ff057
    int dc = rpmfilesDC(files);
2ff057
    int rc = 0;
2ff057
    int i;
2ff057
    int ldnlen = 0;
2ff057
    int ldnalloc = 0;
2ff057
    char * ldn = NULL;
2ff057
    short * dnlx = NULL; 
2ff057
2ff057
    dnlx = (dc ? xcalloc(dc, sizeof(*dnlx)) : NULL);
2ff057
2ff057
    if (dnlx != NULL)
2ff057
    while ((dpath = dnlNextIterator(dnli)) != NULL) {
2ff057
	size_t dnlen = strlen(dpath);
2ff057
	char * te, dn[dnlen+1];
2ff057
2ff057
	dc = dnli->isave;
2ff057
	if (dc < 0) continue;
2ff057
	dnlx[dc] = dnlen;
2ff057
	if (dnlen <= 1)
2ff057
	    continue;
2ff057
2ff057
	if (dnlen <= ldnlen && rstreq(dpath, ldn))
2ff057
	    continue;
2ff057
2ff057
	/* Copy as we need to modify the string */
2ff057
	(void) stpcpy(dn, dpath);
2ff057
2ff057
	/* Assume '/' directory exists, "mkdir -p" for others if non-existent */
2ff057
	for (i = 1, te = dn + 1; *te != '\0'; te++, i++) {
2ff057
	    if (*te != '/')
2ff057
		continue;
2ff057
2ff057
	    *te = '\0';
2ff057
2ff057
	    /* Already validated? */
2ff057
	    if (i < ldnlen &&
2ff057
		(ldn[i] == '/' || ldn[i] == '\0') && rstreqn(dn, ldn, i))
2ff057
	    {
2ff057
		*te = '/';
2ff057
		/* Move pre-existing path marker forward. */
2ff057
		dnlx[dc] = (te - dn);
2ff057
		continue;
2ff057
	    }
2ff057
2ff057
	    /* Validate next component of path. */
2ff057
	    rc = fsmStat(dn, 1, &sb); /* lstat */
2ff057
	    *te = '/';
2ff057
2ff057
	    /* Directory already exists? */
2ff057
	    if (rc == 0 && S_ISDIR(sb.st_mode)) {
2ff057
		/* Move pre-existing path marker forward. */
2ff057
		dnlx[dc] = (te - dn);
2ff057
	    } else if (rc == RPMERR_ENOENT) {
2ff057
		*te = '\0';
2ff057
		mode_t mode = S_IFDIR | (_dirPerms & 07777);
2ff057
		rpmFsmOp op = (FA_CREATE|FAF_UNOWNED);
2ff057
2ff057
		/* Run fsm file pre hook for all plugins */
2ff057
		rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op);
2ff057
2ff057
		if (!rc)
2ff057
		    rc = fsmMkdir(dn, mode);
2ff057
2ff057
		if (!rc) {
2ff057
		    rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn,
2ff057
						      mode, op);
2ff057
		}
2ff057
2ff057
		/* Run fsm file post hook for all plugins */
2ff057
		rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc);
2ff057
2ff057
		if (!rc) {
2ff057
		    rpmlog(RPMLOG_DEBUG,
2ff057
			    "%s directory created with perms %04o\n",
2ff057
			    dn, (unsigned)(mode & 07777));
2ff057
		}
2ff057
		*te = '/';
2ff057
	    }
2ff057
	    if (rc)
2ff057
		break;
2ff057
	}
2ff057
	if (rc) break;
2ff057
2ff057
	/* Save last validated path. */
2ff057
	if (ldnalloc < (dnlen + 1)) {
2ff057
	    ldnalloc = dnlen + 100;
2ff057
	    ldn = xrealloc(ldn, ldnalloc);
2ff057
	}
2ff057
	if (ldn != NULL) { /* XXX can't happen */
2ff057
	    strcpy(ldn, dn);
2ff057
	    ldnlen = dnlen;
2ff057
	}
2ff057
    }
2ff057
    free(dnlx);
2ff057
    free(ldn);
2ff057
    dnlFreeIterator(dnli);
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static void removeSBITS(const char *path)
2ff057
{
2ff057
    struct stat stb;
2ff057
    if (lstat(path, &stb) == 0 && S_ISREG(stb.st_mode)) {
2ff057
	if ((stb.st_mode & 06000) != 0) {
2ff057
	    (void) chmod(path, stb.st_mode & 0777);
2ff057
	}
2ff057
#if WITH_CAP
2ff057
	if (stb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) {
2ff057
	    (void) cap_set_file(path, NULL);
2ff057
	}
2ff057
#endif
2ff057
    }
2ff057
}
2ff057
2ff057
static void fsmDebug(const char *fpath, rpmFileAction action,
2ff057
		     const struct stat *st)
2ff057
{
2ff057
    rpmlog(RPMLOG_DEBUG, "%-10s %06o%3d (%4d,%4d)%6d %s\n",
2ff057
	   fileActionString(action), (int)st->st_mode,
2ff057
	   (int)st->st_nlink, (int)st->st_uid,
2ff057
	   (int)st->st_gid, (int)st->st_size,
2ff057
	    (fpath ? fpath : ""));
2ff057
}
2ff057
2ff057
static int fsmSymlink(const char *opath, const char *path)
2ff057
{
2ff057
    int rc = symlink(opath, path);
2ff057
2ff057
    if (_fsm_debug) {
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
2ff057
	       opath, path, (rc < 0 ? strerror(errno) : ""));
2ff057
    }
2ff057
2ff057
    if (rc < 0)
2ff057
	rc = RPMERR_SYMLINK_FAILED;
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmUnlink(const char *path)
2ff057
{
2ff057
    int rc = 0;
2ff057
    removeSBITS(path);
2ff057
    rc = unlink(path);
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s) %s\n", __func__,
2ff057
	       path, (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)
2ff057
	rc = (errno == ENOENT ? RPMERR_ENOENT : RPMERR_UNLINK_FAILED);
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmRename(const char *opath, const char *path)
2ff057
{
2ff057
    removeSBITS(path);
2ff057
    int rc = rename(opath, path);
2ff057
#if defined(ETXTBSY) && defined(__HPUX__)
2ff057
    /* XXX HP-UX (and other os'es) don't permit rename to busy files. */
2ff057
    if (rc && errno == ETXTBSY) {
2ff057
	char *rmpath = NULL;
2ff057
	rstrscat(&rmpath, path, "-RPMDELETE", NULL);
2ff057
	rc = rename(path, rmpath);
2ff057
	if (!rc) rc = rename(opath, path);
2ff057
	free(rmpath);
2ff057
    }
2ff057
#endif
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, %s) %s\n", __func__,
2ff057
	       opath, path, (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)
2ff057
	rc = (errno == EISDIR ? RPMERR_EXIST_AS_DIR : RPMERR_RENAME_FAILED);
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmRemove(const char *path, mode_t mode)
2ff057
{
2ff057
    return S_ISDIR(mode) ? fsmRmdir(path) : fsmUnlink(path);
2ff057
}
2ff057
2ff057
static int fsmChown(const char *path, mode_t mode, uid_t uid, gid_t gid)
2ff057
{
2ff057
    int rc = S_ISLNK(mode) ? lchown(path, uid, gid) : chown(path, uid, gid);
2ff057
    if (rc < 0) {
2ff057
	struct stat st;
2ff057
	if (lstat(path, &st) == 0 && st.st_uid == uid && st.st_gid == gid)
2ff057
	    rc = 0;
2ff057
    }
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, %d, %d) %s\n", __func__,
2ff057
	       path, (int)uid, (int)gid,
2ff057
	       (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)	rc = RPMERR_CHOWN_FAILED;
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmChmod(const char *path, mode_t mode)
2ff057
{
2ff057
    int rc = chmod(path, (mode & 07777));
2ff057
    if (rc < 0) {
2ff057
	struct stat st;
2ff057
	if (lstat(path, &st) == 0 && (st.st_mode & 07777) == (mode & 07777))
2ff057
	    rc = 0;
2ff057
    }
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
2ff057
	       path, (unsigned)(mode & 07777),
2ff057
	       (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)	rc = RPMERR_CHMOD_FAILED;
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmUtime(const char *path, mode_t mode, time_t mtime)
2ff057
{
2ff057
    int rc = 0;
2ff057
    struct timeval stamps[2] = {
2ff057
	{ .tv_sec = mtime, .tv_usec = 0 },
2ff057
	{ .tv_sec = mtime, .tv_usec = 0 },
2ff057
    };
2ff057
2ff057
#if HAVE_LUTIMES
2ff057
    rc = lutimes(path, stamps);
2ff057
#else
2ff057
    if (!S_ISLNK(mode))
2ff057
	rc = utimes(path, stamps);
2ff057
#endif
2ff057
    
2ff057
    if (_fsm_debug)
2ff057
	rpmlog(RPMLOG_DEBUG, " %8s (%s, 0x%x) %s\n", __func__,
2ff057
	       path, (unsigned)mtime, (rc < 0 ? strerror(errno) : ""));
2ff057
    if (rc < 0)	rc = RPMERR_UTIME_FAILED;
2ff057
    /* ...but utime error is not critical for directories */
2ff057
    if (rc && S_ISDIR(mode))
2ff057
	rc = 0;
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmVerify(const char *path, rpmfi fi)
2ff057
{
2ff057
    int rc;
2ff057
    int saveerrno = errno;
2ff057
    struct stat dsb;
2ff057
    mode_t mode = rpmfiFMode(fi);
2ff057
2ff057
    rc = fsmStat(path, 1, &dsb;;
2ff057
    if (rc)
2ff057
	return rc;
2ff057
2ff057
    if (S_ISREG(mode)) {
2ff057
	/* HP-UX (and other os'es) don't permit unlink on busy files. */
2ff057
	char *rmpath = rstrscat(NULL, path, "-RPMDELETE", NULL);
2ff057
	rc = fsmRename(path, rmpath);
2ff057
	/* XXX shouldn't we take unlink return code here? */
2ff057
	if (!rc)
2ff057
	    (void) fsmUnlink(rmpath);
2ff057
	else
2ff057
	    rc = RPMERR_UNLINK_FAILED;
2ff057
	free(rmpath);
2ff057
        return (rc ? rc : RPMERR_ENOENT);	/* XXX HACK */
2ff057
    } else if (S_ISDIR(mode)) {
2ff057
        if (S_ISDIR(dsb.st_mode)) return 0;
2ff057
        if (S_ISLNK(dsb.st_mode)) {
2ff057
	    uid_t luid = dsb.st_uid;
2ff057
            rc = fsmStat(path, 0, &dsb;;
2ff057
            if (rc == RPMERR_ENOENT) rc = 0;
2ff057
            if (rc) return rc;
2ff057
            errno = saveerrno;
2ff057
	    /* Only permit directory symlinks by target owner and root */
2ff057
            if (S_ISDIR(dsb.st_mode) && (luid == 0 || luid == dsb.st_uid))
2ff057
		    return 0;
2ff057
        }
2ff057
    } else if (S_ISLNK(mode)) {
2ff057
        if (S_ISLNK(dsb.st_mode)) {
2ff057
            char buf[8 * BUFSIZ];
2ff057
            size_t len;
2ff057
            rc = fsmReadLink(path, buf, 8 * BUFSIZ, &len;;
2ff057
            errno = saveerrno;
2ff057
            if (rc) return rc;
2ff057
            if (rstreq(rpmfiFLink(fi), buf)) return 0;
2ff057
        }
2ff057
    } else if (S_ISFIFO(mode)) {
2ff057
        if (S_ISFIFO(dsb.st_mode)) return 0;
2ff057
    } else if (S_ISCHR(mode) || S_ISBLK(mode)) {
2ff057
        if ((S_ISCHR(dsb.st_mode) || S_ISBLK(dsb.st_mode)) &&
2ff057
            (dsb.st_rdev == rpmfiFRdev(fi))) return 0;
2ff057
    } else if (S_ISSOCK(mode)) {
2ff057
        if (S_ISSOCK(dsb.st_mode)) return 0;
2ff057
    }
2ff057
    /* XXX shouldn't do this with commit/undo. */
2ff057
    rc = fsmUnlink(path);
2ff057
    if (rc == 0)	rc = RPMERR_ENOENT;
2ff057
    return (rc ? rc : RPMERR_ENOENT);	/* XXX HACK */
2ff057
}
2ff057
2ff057
#define	IS_DEV_LOG(_x)	\
2ff057
	((_x) != NULL && strlen(_x) >= (sizeof("/dev/log")-1) && \
2ff057
	rstreqn((_x), "/dev/log", sizeof("/dev/log")-1) && \
2ff057
	((_x)[sizeof("/dev/log")-1] == '\0' || \
2ff057
	 (_x)[sizeof("/dev/log")-1] == ';'))
2ff057
2ff057
2ff057
2ff057
/* Rename pre-existing modified or unmanaged file. */
2ff057
static int fsmBackup(rpmfi fi, rpmFileAction action)
2ff057
{
2ff057
    int rc = 0;
2ff057
    const char *suffix = NULL;
2ff057
2ff057
    if (!(rpmfiFFlags(fi) & RPMFILE_GHOST)) {
2ff057
	switch (action) {
2ff057
	case FA_SAVE:
2ff057
	    suffix = SUFFIX_RPMSAVE;
2ff057
	    break;
2ff057
	case FA_BACKUP:
2ff057
	    suffix = SUFFIX_RPMORIG;
2ff057
	    break;
2ff057
	default:
2ff057
	    break;
2ff057
	}
2ff057
    }
2ff057
2ff057
    if (suffix) {
2ff057
	char * opath = fsmFsPath(fi, NULL);
2ff057
	char * path = fsmFsPath(fi, suffix);
2ff057
	rc = fsmRename(opath, path);
2ff057
	if (!rc) {
2ff057
	    rpmlog(RPMLOG_WARNING, _("%s saved as %s\n"), opath, path);
2ff057
	}
2ff057
	free(path);
2ff057
	free(opath);
2ff057
    }
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmSetmeta(const char *path, rpmfi fi, rpmPlugins plugins,
2ff057
		      rpmFileAction action, const struct stat * st,
2ff057
		      int nofcaps)
2ff057
{
2ff057
    int rc = 0;
2ff057
    const char *dest = rpmfiFN(fi);
2ff057
2ff057
    if (!rc && !getuid()) {
2ff057
	rc = fsmChown(path, st->st_mode, st->st_uid, st->st_gid);
2ff057
    }
2ff057
    if (!rc && !S_ISLNK(st->st_mode)) {
2ff057
	rc = fsmChmod(path, st->st_mode);
2ff057
    }
2ff057
    /* Set file capabilities (if enabled) */
2ff057
    if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) {
2ff057
	rc = fsmSetFCaps(path, rpmfiFCaps(fi));
2ff057
    }
2ff057
    if (!rc) {
2ff057
	rc = fsmUtime(path, st->st_mode, rpmfiFMtime(fi));
2ff057
    }
2ff057
    if (!rc) {
2ff057
	rc = rpmpluginsCallFsmFilePrepare(plugins, fi,
2ff057
					  path, dest, st->st_mode, action);
2ff057
    }
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
static int fsmCommit(char **path, rpmfi fi, rpmFileAction action, const char *suffix)
2ff057
{
2ff057
    int rc = 0;
2ff057
2ff057
    /* XXX Special case /dev/log, which shouldn't be packaged anyways */
2ff057
    if (!(S_ISSOCK(rpmfiFMode(fi)) && IS_DEV_LOG(*path))) {
2ff057
	const char *nsuffix = (action == FA_ALTNAME) ? SUFFIX_RPMNEW : NULL;
2ff057
	char *dest = *path;
2ff057
	/* Construct final destination path (nsuffix is usually NULL) */
2ff057
	if (suffix)
2ff057
	    dest = fsmFsPath(fi, nsuffix);
2ff057
2ff057
	/* Rename temporary to final file name if needed. */
2ff057
	if (dest != *path) {
2ff057
	    rc = fsmRename(*path, dest);
2ff057
	    if (!rc && nsuffix) {
2ff057
		char * opath = fsmFsPath(fi, NULL);
2ff057
		rpmlog(RPMLOG_WARNING, _("%s created as %s\n"),
2ff057
		       opath, dest);
2ff057
		free(opath);
2ff057
	    }
2ff057
	    free(*path);
2ff057
	    *path = dest;
2ff057
	}
2ff057
    }
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
/**
2ff057
 * Return formatted string representation of file disposition.
2ff057
 * @param a		file disposition
2ff057
 * @return		formatted string
2ff057
 */
2ff057
static const char * fileActionString(rpmFileAction a)
2ff057
{
2ff057
    switch (a) {
2ff057
    case FA_UNKNOWN:	return "unknown";
2ff057
    case FA_CREATE:	return "create";
2ff057
    case FA_BACKUP:	return "backup";
2ff057
    case FA_SAVE:	return "save";
2ff057
    case FA_SKIP:	return "skip";
2ff057
    case FA_ALTNAME:	return "altname";
2ff057
    case FA_ERASE:	return "erase";
2ff057
    case FA_SKIPNSTATE: return "skipnstate";
2ff057
    case FA_SKIPNETSHARED: return "skipnetshared";
2ff057
    case FA_SKIPCOLOR:	return "skipcolor";
2ff057
    case FA_TOUCH:     return "touch";
2ff057
    default:		return "???";
2ff057
    }
2ff057
}
2ff057
2ff057
/* Remember any non-regular file state for recording in the rpmdb */
2ff057
static void setFileState(rpmfs fs, int i)
2ff057
{
2ff057
    switch (rpmfsGetAction(fs, i)) {
2ff057
    case FA_SKIPNSTATE:
2ff057
	rpmfsSetState(fs, i, RPMFILE_STATE_NOTINSTALLED);
2ff057
	break;
2ff057
    case FA_SKIPNETSHARED:
2ff057
	rpmfsSetState(fs, i, RPMFILE_STATE_NETSHARED);
2ff057
	break;
2ff057
    case FA_SKIPCOLOR:
2ff057
	rpmfsSetState(fs, i, RPMFILE_STATE_WRONGCOLOR);
2ff057
	break;
2ff057
    case FA_TOUCH:
2ff057
	rpmfsSetState(fs, i, RPMFILE_STATE_NORMAL);
2ff057
	break;
2ff057
    default:
2ff057
	break;
2ff057
    }
2ff057
}
2ff057
2ff057
int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
2ff057
              rpmpsm psm, char ** failedFile)
2ff057
{
2ff057
    FD_t payload = rpmtePayload(te);
2ff057
    rpmfi fi = rpmfiNewArchiveReader(payload, files, RPMFI_ITER_READ_ARCHIVE);
2ff057
    rpmfs fs = rpmteGetFileStates(te);
2ff057
    rpmPlugins plugins = rpmtsPlugins(ts);
2ff057
    struct stat sb;
2ff057
    int saveerrno = errno;
2ff057
    int rc = 0;
2ff057
    int nodigest = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOFILEDIGEST) ? 1 : 0;
2ff057
    int nofcaps = (rpmtsFlags(ts) & RPMTRANS_FLAG_NOCAPS) ? 1 : 0;
2ff057
    int firsthardlink = -1;
2ff057
    FD_t firstlinkfile = NULL;
2ff057
    int skip;
2ff057
    rpmFileAction action;
2ff057
    char *tid = NULL;
2ff057
    const char *suffix;
2ff057
    char *fpath = NULL;
2ff057
2ff057
    if (fi == NULL) {
2ff057
	rc = RPMERR_BAD_MAGIC;
2ff057
	goto exit;
2ff057
    }
2ff057
2ff057
    /* transaction id used for temporary path suffix while installing */
2ff057
    rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
2ff057
2ff057
    /* Detect and create directories not explicitly in package. */
2ff057
    rc = fsmMkdirs(files, fs, plugins);
2ff057
2ff057
    while (!rc) {
2ff057
	/* Read next payload header. */
2ff057
	rc = rpmfiNext(fi);
2ff057
2ff057
	if (rc < 0) {
2ff057
	    if (rc == RPMERR_ITER_END)
2ff057
		rc = 0;
2ff057
	    break;
2ff057
	}
2ff057
2ff057
	action = rpmfsGetAction(fs, rpmfiFX(fi));
2ff057
	skip = XFA_SKIPPING(action);
2ff057
	suffix = S_ISDIR(rpmfiFMode(fi)) ? NULL : tid;
2ff057
	if (action != FA_TOUCH) {
2ff057
	    fpath = fsmFsPath(fi, suffix);
2ff057
	} else {
2ff057
	    fpath = fsmFsPath(fi, "");
2ff057
	}
2ff057
2ff057
	/* Remap file perms, owner, and group. */
2ff057
	rc = rpmfiStat(fi, 1, &sb);
2ff057
2ff057
	fsmDebug(fpath, action, &sb);
2ff057
2ff057
        /* Exit on error. */
2ff057
        if (rc)
2ff057
            break;
2ff057
2ff057
	/* Run fsm file pre hook for all plugins */
2ff057
	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
2ff057
				      sb.st_mode, action);
2ff057
	if (rc) {
2ff057
	    skip = 1;
2ff057
	} else {
2ff057
	    setFileState(fs, rpmfiFX(fi));
2ff057
	}
2ff057
2ff057
        if (!skip) {
2ff057
	    int setmeta = 1;
2ff057
2ff057
	    /* Directories replacing something need early backup */
2ff057
	    if (!suffix) {
2ff057
		rc = fsmBackup(fi, action);
2ff057
	    }
2ff057
	    /* Assume file does't exist when tmp suffix is in use */
2ff057
	    if (!suffix) {
2ff057
		rc = fsmVerify(fpath, fi);
2ff057
	    } else {
2ff057
		rc = (action == FA_TOUCH) ? 0 : RPMERR_ENOENT;
2ff057
	    }
2ff057
2ff057
            if (S_ISREG(sb.st_mode)) {
2ff057
		if (rc == RPMERR_ENOENT) {
2ff057
		    rc = fsmMkfile(fi, fpath, files, psm, nodigest,
2ff057
				   &setmeta, &firsthardlink, &firstlinkfile);
2ff057
		}
2ff057
            } else if (S_ISDIR(sb.st_mode)) {
2ff057
                if (rc == RPMERR_ENOENT) {
2ff057
                    mode_t mode = sb.st_mode;
2ff057
                    mode &= ~07777;
2ff057
                    mode |=  00700;
2ff057
                    rc = fsmMkdir(fpath, mode);
2ff057
                }
2ff057
            } else if (S_ISLNK(sb.st_mode)) {
2ff057
		if (rc == RPMERR_ENOENT) {
2ff057
		    rc = fsmSymlink(rpmfiFLink(fi), fpath);
2ff057
		}
2ff057
            } else if (S_ISFIFO(sb.st_mode)) {
2ff057
                /* This mimics cpio S_ISSOCK() behavior but probably isn't right */
2ff057
                if (rc == RPMERR_ENOENT) {
2ff057
                    rc = fsmMkfifo(fpath, 0000);
2ff057
                }
2ff057
            } else if (S_ISCHR(sb.st_mode) ||
2ff057
                       S_ISBLK(sb.st_mode) ||
2ff057
                       S_ISSOCK(sb.st_mode))
2ff057
            {
2ff057
                if (rc == RPMERR_ENOENT) {
2ff057
                    rc = fsmMknod(fpath, sb.st_mode, sb.st_rdev);
2ff057
                }
2ff057
            } else {
2ff057
                /* XXX Special case /dev/log, which shouldn't be packaged anyways */
2ff057
                if (!IS_DEV_LOG(fpath))
2ff057
                    rc = RPMERR_UNKNOWN_FILETYPE;
2ff057
            }
2ff057
	    /* Set permissions, timestamps etc for non-hardlink entries */
2ff057
	    if (!rc && setmeta) {
2ff057
		rc = fsmSetmeta(fpath, fi, plugins, action, &sb, nofcaps);
2ff057
	    }
2ff057
        } else if (firsthardlink >= 0 && rpmfiArchiveHasContent(fi)) {
2ff057
	    /* we skip the hard linked file containing the content */
2ff057
	    /* write the content to the first used instead */
2ff057
	    char *fn = rpmfilesFN(files, firsthardlink);
2ff057
	    rc = rpmfiArchiveReadToFilePsm(fi, firstlinkfile, nodigest, psm);
2ff057
	    wfd_close(&firstlinkfile);
2ff057
	    firsthardlink = -1;
2ff057
	    free(fn);
2ff057
	}
2ff057
2ff057
        if (rc) {
2ff057
            if (!skip) {
2ff057
                /* XXX only erase if temp fn w suffix is in use */
2ff057
                if (suffix && (action != FA_TOUCH)) {
2ff057
		    (void) fsmRemove(fpath, sb.st_mode);
2ff057
                }
2ff057
                errno = saveerrno;
2ff057
            }
2ff057
        } else {
2ff057
	    /* Notify on success. */
2ff057
	    rpmpsmNotify(psm, RPMCALLBACK_INST_PROGRESS, rpmfiArchiveTell(fi));
2ff057
2ff057
	    if (!skip) {
2ff057
		/* Backup file if needed. Directories are handled earlier */
2ff057
		if (suffix)
2ff057
		    rc = fsmBackup(fi, action);
2ff057
2ff057
		if (!rc)
2ff057
		    rc = fsmCommit(&fpath, fi, action, suffix);
2ff057
	    }
2ff057
	}
2ff057
2ff057
	if (rc)
2ff057
	    *failedFile = xstrdup(fpath);
2ff057
2ff057
	/* Run fsm file post hook for all plugins */
2ff057
	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
2ff057
				  sb.st_mode, action, rc);
2ff057
	fpath = _free(fpath);
2ff057
    }
2ff057
2ff057
    rpmswAdd(rpmtsOp(ts, RPMTS_OP_UNCOMPRESS), fdOp(payload, FDSTAT_READ));
2ff057
    rpmswAdd(rpmtsOp(ts, RPMTS_OP_DIGEST), fdOp(payload, FDSTAT_DIGEST));
2ff057
2ff057
exit:
2ff057
2ff057
    /* No need to bother with close errors on read */
2ff057
    rpmfiArchiveClose(fi);
2ff057
    rpmfiFree(fi);
2ff057
    Fclose(payload);
2ff057
    free(tid);
2ff057
    free(fpath);
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057
2ff057
int rpmPackageFilesRemove(rpmts ts, rpmte te, rpmfiles files,
2ff057
              rpmpsm psm, char ** failedFile)
2ff057
{
2ff057
    rpmfi fi = rpmfilesIter(files, RPMFI_ITER_BACK);
2ff057
    rpmfs fs = rpmteGetFileStates(te);
2ff057
    rpmPlugins plugins = rpmtsPlugins(ts);
2ff057
    struct stat sb;
2ff057
    int rc = 0;
2ff057
    char *fpath = NULL;
2ff057
2ff057
    while (!rc && rpmfiNext(fi) >= 0) {
2ff057
	rpmFileAction action = rpmfsGetAction(fs, rpmfiFX(fi));
2ff057
	fpath = fsmFsPath(fi, NULL);
2ff057
	rc = fsmStat(fpath, 1, &sb);
2ff057
2ff057
	fsmDebug(fpath, action, &sb);
2ff057
2ff057
	/* Run fsm file pre hook for all plugins */
2ff057
	rc = rpmpluginsCallFsmFilePre(plugins, fi, fpath,
2ff057
				      sb.st_mode, action);
2ff057
2ff057
	if (!XFA_SKIPPING(action))
2ff057
	    rc = fsmBackup(fi, action);
2ff057
2ff057
        /* Remove erased files. */
2ff057
        if (action == FA_ERASE) {
2ff057
	    int missingok = (rpmfiFFlags(fi) & (RPMFILE_MISSINGOK | RPMFILE_GHOST));
2ff057
2ff057
	    rc = fsmRemove(fpath, sb.st_mode);
2ff057
2ff057
	    /*
2ff057
	     * Missing %ghost or %missingok entries are not errors.
2ff057
	     * XXX: Are non-existent files ever an actual error here? Afterall
2ff057
	     * that's exactly what we're trying to accomplish here,
2ff057
	     * and complaining about job already done seems like kinderkarten
2ff057
	     * level "But it was MY turn!" whining...
2ff057
	     */
2ff057
	    if (rc == RPMERR_ENOENT && missingok) {
2ff057
		rc = 0;
2ff057
	    }
2ff057
2ff057
	    /*
2ff057
	     * Dont whine on non-empty directories for now. We might be able
2ff057
	     * to track at least some of the expected failures though,
2ff057
	     * such as when we knowingly left config file backups etc behind.
2ff057
	     */
2ff057
	    if (rc == RPMERR_ENOTEMPTY) {
2ff057
		rc = 0;
2ff057
	    }
2ff057
2ff057
	    if (rc) {
2ff057
		int lvl = strict_erasures ? RPMLOG_ERR : RPMLOG_WARNING;
2ff057
		rpmlog(lvl, _("%s %s: remove failed: %s\n"),
2ff057
			S_ISDIR(sb.st_mode) ? _("directory") : _("file"),
2ff057
			fpath, strerror(errno));
2ff057
            }
2ff057
        }
2ff057
2ff057
	/* Run fsm file post hook for all plugins */
2ff057
	rpmpluginsCallFsmFilePost(plugins, fi, fpath,
2ff057
				  sb.st_mode, action, rc);
2ff057
2ff057
        /* XXX Failure to remove is not (yet) cause for failure. */
2ff057
        if (!strict_erasures) rc = 0;
2ff057
2ff057
	if (rc)
2ff057
	    *failedFile = xstrdup(fpath);
2ff057
2ff057
	if (rc == 0) {
2ff057
	    /* Notify on success. */
2ff057
	    /* On erase we're iterating backwards, fixup for progress */
2ff057
	    rpm_loff_t amount = rpmfiFC(fi) - rpmfiFX(fi);
2ff057
	    rpmpsmNotify(psm, RPMCALLBACK_UNINST_PROGRESS, amount);
2ff057
	}
2ff057
	fpath = _free(fpath);
2ff057
    }
2ff057
2ff057
    free(fpath);
2ff057
    rpmfiFree(fi);
2ff057
2ff057
    return rc;
2ff057
}
2ff057
2ff057