gathering as much data as we can on the f14 boxes setup
[infrastructure.git] / bbox-f14 / rpm-changes / a / lib / rpmfi.c
diff --git a/bbox-f14/rpm-changes/a/lib/rpmfi.c b/bbox-f14/rpm-changes/a/lib/rpmfi.c
new file mode 100644 (file)
index 0000000..d36e1d2
--- /dev/null
@@ -0,0 +1,1366 @@
+/** \ingroup rpmdep
+ * \file lib/rpmfi.c
+ * Routines to handle file info tag sets.
+ */
+
+#include "system.h"
+
+#include <rpm/rpmlog.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmfileutil.h>   /* XXX rpmDoDigest */
+#include <rpm/rpmstring.h>
+#include <rpm/rpmmacro.h>      /* XXX rpmCleanPath */
+#include <rpm/rpmds.h>
+
+#include "lib/rpmfi_internal.h"
+#include "lib/rpmte_internal.h"        /* relocations */
+#include "lib/cpio.h"  /* XXX CPIO_FOO */
+
+#include "debug.h"
+
+/*
+ * Simple and stupid string "cache."
+ * Store each unique string just once, retrieve by index value. 
+ * For data where number of unique names is typically very low,
+ * the dumb linear lookup appears to be fast enough and hash table seems
+ * like an overkill.
+ */
+struct strcache_s {
+    char **uniq;
+    scidx_t num;
+};
+
+static struct strcache_s _ugcache = { NULL, 0 };
+static strcache ugcache = &_ugcache;
+static struct strcache_s _langcache = { NULL, 0 };
+static strcache langcache = &_langcache;
+
+static scidx_t strcachePut(strcache cache, const char *str)
+{
+    int found = 0;
+    scidx_t ret;
+
+    for (scidx_t i = 0; i < cache->num; i++) {
+       if (rstreq(str, cache->uniq[i])) {
+           ret = i;
+           found = 1;
+           break;
+       }
+    }
+    if (!found) {
+       /* blow up on index wraparound */
+       assert((scidx_t)(cache->num + 1) > cache->num);
+       cache->uniq = xrealloc(cache->uniq, 
+                               sizeof(*cache->uniq) * (cache->num+1));
+       cache->uniq[cache->num] = xstrdup(str);
+       ret = cache->num;
+       cache->num++;
+    }
+    return ret;
+}
+
+static const char *strcacheGet(strcache cache, scidx_t idx)
+{
+    const char *name = NULL;
+    if (idx >= 0 && idx < cache->num && cache->uniq != NULL)
+       name = cache->uniq[idx];
+    return name;
+}
+    
+static strcache strcacheNew(void)
+{
+    strcache cache = xcalloc(1, sizeof(*cache));
+    return cache;
+}
+
+static strcache strcacheFree(strcache cache)
+{
+    if (cache != NULL) {
+       for (scidx_t i = 0; i < cache->num; i++) {
+           free(cache->uniq[i]);
+       }
+       cache->uniq = _free(cache->uniq);
+       free(cache);
+    }
+    return NULL;
+} 
+
+static rpmfi rpmfiUnlink(rpmfi fi)
+{
+    if (fi)
+       fi->nrefs--;
+    return NULL;
+}
+
+rpmfi rpmfiLink(rpmfi fi)
+{
+    if (fi)
+       fi->nrefs++;
+    return fi;
+}
+
+rpm_count_t rpmfiFC(rpmfi fi)
+{
+    return (fi != NULL ? fi->fc : 0);
+}
+
+rpm_count_t rpmfiDC(rpmfi fi)
+{
+    return (fi != NULL ? fi->dc : 0);
+}
+
+#ifdef NOTYET
+int rpmfiDI(rpmfi fi)
+{
+}
+#endif
+
+int rpmfiFX(rpmfi fi)
+{
+    return (fi != NULL ? fi->i : -1);
+}
+
+int rpmfiSetFX(rpmfi fi, int fx)
+{
+    int i = -1;
+
+    if (fi != NULL && fx >= 0 && fx < fi->fc) {
+       i = fi->i;
+       fi->i = fx;
+       fi->j = fi->dil[fi->i];
+    }
+    return i;
+}
+
+int rpmfiDX(rpmfi fi)
+{
+    return (fi != NULL ? fi->j : -1);
+}
+
+int rpmfiSetDX(rpmfi fi, int dx)
+{
+    int j = -1;
+
+    if (fi != NULL && dx >= 0 && dx < fi->dc) {
+       j = fi->j;
+       fi->j = dx;
+    }
+    return j;
+}
+
+int rpmfiDIIndex(rpmfi fi, int dx)
+{
+    int j = -1;
+    if (fi != NULL && dx >= 0 && dx < fi->fc) {
+       if (fi->dil != NULL)
+           j = fi->dil[dx];
+    }
+    return j;
+}
+
+const char * rpmfiBNIndex(rpmfi fi, int ix)
+{
+    const char * BN = NULL;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->bnl != NULL)
+           BN = fi->bnl[ix];
+    }
+    return BN;
+}
+
+const char * rpmfiDNIndex(rpmfi fi, int jx)
+{
+    const char * DN = NULL;
+
+    if (fi != NULL && jx >= 0 && jx < fi->dc) {
+       if (fi->dnl != NULL)
+           DN = fi->dnl[jx];
+    }
+    return DN;
+}
+
+char * rpmfiFNIndex(rpmfi fi, int ix)
+{
+    char *fn = NULL;
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       fn = rstrscat(NULL, fi->dnl[fi->dil[ix]], fi->bnl[ix], NULL);
+    }
+    return fn;
+}
+
+rpmfileAttrs rpmfiFFlagsIndex(rpmfi fi, int ix)
+{
+    rpmfileAttrs FFlags = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fflags != NULL)
+           FFlags = fi->fflags[ix];
+    }
+    return FFlags;
+}
+
+rpmVerifyAttrs rpmfiVFlagsIndex(rpmfi fi, int ix)
+{
+    rpmVerifyAttrs VFlags = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->vflags != NULL)
+           VFlags = fi->vflags[ix];
+    }
+    return VFlags;
+}
+
+rpm_mode_t rpmfiFModeIndex(rpmfi fi, int ix)
+{
+    rpm_mode_t fmode = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fmodes != NULL)
+           fmode = fi->fmodes[ix];
+    }
+    return fmode;
+}
+
+rpmfileState rpmfiFStateIndex(rpmfi fi, int ix)
+{
+    rpmfileState fstate = RPMFILE_STATE_MISSING;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fstates != NULL)
+           fstate = fi->fstates[ix];
+    }
+    return fstate;
+}
+
+const unsigned char * rpmfiMD5(rpmfi fi)
+{
+    const unsigned char *digest;
+    int algo = 0;
+
+    digest = rpmfiFDigest(fi, &algo, NULL);
+    return (algo == PGPHASHALGO_MD5) ? digest : NULL;
+}
+
+int rpmfiDigestAlgo(rpmfi fi)
+{
+    return fi ? fi->digestalgo : 0;
+}
+
+const unsigned char * rpmfiFDigestIndex(rpmfi fi, int ix, int *algo, size_t *len)
+{
+    const unsigned char *digest = NULL;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       size_t diglen = rpmDigestLength(fi->digestalgo);
+       if (fi->digests != NULL)
+           digest = fi->digests + (diglen * ix);
+       if (len) 
+           *len = diglen;
+       if (algo) 
+           *algo = fi->digestalgo;
+    }
+    return digest;
+}
+
+char * rpmfiFDigestHex(rpmfi fi, int *algo)
+{
+    size_t diglen = 0;
+    char *fdigest = NULL;
+    const unsigned char *digest = rpmfiFDigest(fi, algo, &diglen);
+    if (digest) {
+       fdigest = pgpHexStr(digest, diglen);
+    }
+    return fdigest;
+}
+
+const char * rpmfiFLinkIndex(rpmfi fi, int ix)
+{
+    const char * flink = NULL;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->flinks != NULL)
+           flink = strcacheGet(fi->flinkcache, fi->flinks[ix]);
+    }
+    return flink;
+}
+
+rpm_loff_t rpmfiFSizeIndex(rpmfi fi, int ix)
+{
+    rpm_loff_t fsize = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fsizes != NULL)
+           fsize = fi->fsizes[ix];
+    }
+    return fsize;
+}
+
+rpm_rdev_t rpmfiFRdevIndex(rpmfi fi, int ix)
+{
+    rpm_rdev_t frdev = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->frdevs != NULL)
+           frdev = fi->frdevs[ix];
+    }
+    return frdev;
+}
+
+rpm_ino_t rpmfiFInodeIndex(rpmfi fi, int ix)
+{
+    rpm_ino_t finode = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->finodes != NULL)
+           finode = fi->finodes[ix];
+    }
+    return finode;
+}
+
+rpm_color_t rpmfiColor(rpmfi fi)
+{
+    rpm_color_t color = 0;
+
+    if (fi != NULL && fi->fcolors != NULL) {
+       for (int i = 0; i < fi->fc; i++)
+           color |= fi->fcolors[i];
+       /* XXX ignore all but lsnibble for now. */
+       color &= 0xf;
+    }
+    return color;
+}
+
+rpm_color_t rpmfiFColorIndex(rpmfi fi, int ix)
+{
+    rpm_color_t fcolor = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fcolors != NULL)
+           /* XXX ignore all but lsnibble for now. */
+           fcolor = (fi->fcolors[ix] & 0x0f);
+    }
+    return fcolor;
+}
+
+const char * rpmfiFClassIndex(rpmfi fi, int ix)
+{
+    const char * fclass = NULL;
+    int cdictx;
+
+    if (fi != NULL && fi->fcdictx != NULL && ix >= 0 && ix < fi->fc) {
+       cdictx = fi->fcdictx[ix];
+       if (fi->cdict != NULL && cdictx >= 0 && cdictx < fi->ncdict)
+           fclass = fi->cdict[cdictx];
+    }
+    return fclass;
+}
+
+uint32_t rpmfiFDependsIndex(rpmfi fi, int ix, const uint32_t ** fddictp)
+{
+    int fddictx = -1;
+    int fddictn = 0;
+    const uint32_t * fddict = NULL;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fddictn != NULL)
+           fddictn = fi->fddictn[ix];
+       if (fddictn > 0 && fi->fddictx != NULL)
+           fddictx = fi->fddictx[ix];
+       if (fi->ddict != NULL && fddictx >= 0 && (fddictx+fddictn) <= fi->nddict)
+           fddict = fi->ddict + fddictx;
+    }
+    if (fddictp)
+       *fddictp = fddict;
+    return fddictn;
+}
+
+uint32_t rpmfiFNlinkIndex(rpmfi fi, int ix)
+{
+    uint32_t nlink = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       /* XXX rpm-2.3.12 has not RPMTAG_FILEINODES */
+       if (fi->finodes && fi->frdevs) {
+           rpm_ino_t finode = fi->finodes[ix];
+           rpm_rdev_t frdev = fi->frdevs[ix];
+           int j;
+
+           for (j = 0; j < fi->fc; j++) {
+               if (fi->frdevs[j] == frdev && fi->finodes[j] == finode)
+                   nlink++;
+           }
+       }
+    }
+    return nlink;
+}
+
+rpm_time_t rpmfiFMtimeIndex(rpmfi fi, int ix)
+{
+    rpm_time_t fmtime = 0;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fmtimes != NULL)
+           fmtime = fi->fmtimes[ix];
+    }
+    return fmtime;
+}
+
+const char * rpmfiFUserIndex(rpmfi fi, int ix)
+{
+    const char * fuser = NULL;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fuser != NULL)
+           fuser = strcacheGet(ugcache, fi->fuser[ix]);
+    }
+    return fuser;
+}
+
+const char * rpmfiFGroupIndex(rpmfi fi, int ix)
+{
+    const char * fgroup = NULL;
+
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->fgroup != NULL)
+           fgroup = strcacheGet(ugcache, fi->fgroup[ix]);
+    }
+    return fgroup;
+}
+
+const char * rpmfiFCapsIndex(rpmfi fi, int ix)
+{
+    const char *fcaps = NULL;
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       fcaps = fi->fcapcache ? strcacheGet(fi->fcapcache, fi->fcaps[ix]) : "";
+    }
+    return fcaps;
+}
+
+const char * rpmfiFLangsIndex(rpmfi fi, int ix)
+{
+    const char *flangs = NULL;
+    if (fi != NULL && fi->flangs != NULL && ix >= 0 && ix < fi->fc) {
+       flangs = strcacheGet(langcache, fi->flangs[ix]);
+    }
+    return flangs;
+}
+
+struct fingerPrint_s *rpmfiFpsIndex(rpmfi fi, int ix)
+{
+    struct fingerPrint_s * fps = NULL;
+    if (fi != NULL && fi->fps != NULL && ix >= 0 && ix < fi->fc) {
+       fps = fi->fps + ix;
+    }
+    return fps;
+}
+
+int rpmfiNext(rpmfi fi)
+{
+    int i = -1;
+
+    if (fi != NULL && ++fi->i >= 0) {
+       if (fi->i < fi->fc) {
+           i = fi->i;
+           if (fi->dil != NULL)
+               fi->j = fi->dil[fi->i];
+       } else
+           fi->i = -1;
+    }
+
+    return i;
+}
+
+rpmfi rpmfiInit(rpmfi fi, int fx)
+{
+    if (fi != NULL) {
+       if (fx >= 0 && fx < fi->fc) {
+           fi->i = fx - 1;
+           fi->j = -1;
+       }
+    }
+
+    return fi;
+}
+
+int rpmfiNextD(rpmfi fi)
+{
+    int j = -1;
+
+    if (fi != NULL && ++fi->j >= 0) {
+       if (fi->j < fi->dc)
+           j = fi->j;
+       else
+           fi->j = -1;
+    }
+
+    return j;
+}
+
+rpmfi rpmfiInitD(rpmfi fi, int dx)
+{
+    if (fi != NULL) {
+       if (dx >= 0 && dx < fi->fc)
+           fi->j = dx - 1;
+       else
+           fi = NULL;
+    }
+
+    return fi;
+}
+
+/**
+ * Identify a file type.
+ * @param ft           file type
+ * @return             string to identify a file type
+ */
+static
+const char * ftstring (rpmFileTypes ft)
+{
+    switch (ft) {
+    case XDIR: return "directory";
+    case CDEV: return "char dev";
+    case BDEV: return "block dev";
+    case LINK: return "link";
+    case SOCK: return "sock";
+    case PIPE: return "fifo/pipe";
+    case REG:  return "file";
+    default:   return "unknown file type";
+    }
+}
+
+rpmFileTypes rpmfiWhatis(rpm_mode_t mode)
+{
+    if (S_ISDIR(mode)) return XDIR;
+    if (S_ISCHR(mode)) return CDEV;
+    if (S_ISBLK(mode)) return BDEV;
+    if (S_ISLNK(mode)) return LINK;
+    if (S_ISSOCK(mode))        return SOCK;
+    if (S_ISFIFO(mode))        return PIPE;
+    return REG;
+}
+
+int rpmfiCompareIndex(rpmfi afi, int aix, rpmfi bfi, int bix)
+{
+    mode_t amode = rpmfiFModeIndex(afi, aix);
+    mode_t bmode = rpmfiFModeIndex(bfi, bix);
+    rpmFileTypes awhat = rpmfiWhatis(amode);
+
+    if ((rpmfiFFlagsIndex(afi, aix) & RPMFILE_GHOST) ||
+       (rpmfiFFlagsIndex(bfi, bix) & RPMFILE_GHOST)) return 0;
+
+    if (amode != bmode) return 1;
+
+    if (!rstreq(rpmfiFUserIndex(afi, aix), rpmfiFUserIndex(bfi, bix)))
+       return 1;
+    if (!rstreq(rpmfiFGroupIndex(afi, aix), rpmfiFGroupIndex(bfi, bix)))
+       return 1;
+
+    if (awhat == LINK) {
+       const char * alink = rpmfiFLinkIndex(afi, aix);
+       const char * blink = rpmfiFLinkIndex(bfi, bix);
+       if (alink == blink) return 0;
+       if (alink == NULL) return 1;
+       if (blink == NULL) return -1;
+       return strcmp(alink, blink);
+    } else if (awhat == REG) {
+       size_t adiglen, bdiglen;
+       int aalgo, balgo;
+       const unsigned char * adigest, * bdigest;
+       adigest = rpmfiFDigestIndex(afi, aix, &aalgo, &adiglen);
+       bdigest = rpmfiFDigestIndex(bfi, bix, &balgo, &bdiglen);
+       if (adigest == bdigest) return 0;
+       if (adigest == NULL) return 1;
+       if (bdigest == NULL) return -1;
+       /* can't meaningfully compare different hash types */
+       if (aalgo != balgo || adiglen != bdiglen) return -1;
+       return memcmp(adigest, bdigest, adiglen);
+    } else if (awhat == CDEV || awhat == BDEV) {
+       if (rpmfiFRdevIndex(afi, aix) != rpmfiFRdevIndex(bfi, bix))
+           return 1;
+    }
+
+    return 0;
+}
+
+rpmFileAction rpmfiDecideFateIndex(rpmfi ofi, int oix, rpmfi nfi, int nix,
+                                  int skipMissing)
+{
+    char * fn = rpmfiFNIndex(nfi, nix);
+    rpmfileAttrs newFlags = rpmfiFFlagsIndex(nfi, nix);
+    char buffer[1024];
+    rpmFileTypes dbWhat, newWhat, diskWhat;
+    struct stat sb;
+    int save = (newFlags & RPMFILE_NOREPLACE) ? FA_ALTNAME : FA_SAVE;
+    int action = FA_CREATE; /* assume we can create */
+
+    if (lstat(fn, &sb)) {
+       /*
+        * The file doesn't exist on the disk. Create it unless the new
+        * package has marked it as missingok, or allfiles is requested.
+        */
+       if (skipMissing && (newFlags & RPMFILE_MISSINGOK)) {
+           rpmlog(RPMLOG_DEBUG, "%s skipped due to missingok flag\n",
+                       fn);
+           action = FA_SKIP;
+       }
+       goto exit;
+    }
+
+    diskWhat = rpmfiWhatis((rpm_mode_t)sb.st_mode);
+    dbWhat = rpmfiWhatis(rpmfiFModeIndex(ofi, oix));
+    newWhat = rpmfiWhatis(rpmfiFModeIndex(nfi, nix));
+
+    /*
+     * RPM >= 2.3.10 shouldn't create config directories -- we'll ignore
+     * them in older packages as well.
+     */
+    if (newWhat == XDIR)
+       goto exit;
+
+    if (diskWhat != newWhat && dbWhat != REG && dbWhat != LINK) {
+       action = save;
+       goto exit;
+    } else if (newWhat != dbWhat && diskWhat != dbWhat) {
+       action = save;
+       goto exit;
+    } else if (dbWhat != newWhat) {
+       goto exit;
+    } else if (dbWhat != LINK && dbWhat != REG) {
+       goto exit;
+    }
+
+    /*
+     * This order matters - we'd prefer to CREATE the file if at all
+     * possible in case something else (like the timestamp) has changed.
+     */
+    memset(buffer, 0, sizeof(buffer));
+    if (dbWhat == REG) {
+       int oalgo, nalgo;
+       size_t odiglen, ndiglen;
+       const unsigned char * odigest, * ndigest;
+       odigest = rpmfiFDigestIndex(ofi, oix, &oalgo, &odiglen);
+       if (diskWhat == REG) {
+           if (rpmDoDigest(oalgo, fn, 0, (unsigned char *)buffer, NULL))
+               goto exit;      /* assume file has been removed */
+           if (odigest && !memcmp(odigest, buffer, odiglen))
+               goto exit;      /* unmodified config file, replace. */
+       }
+       ndigest = rpmfiFDigestIndex(nfi, nix, &nalgo, &ndiglen);
+       /* Can't compare different hash types, backup to avoid data loss */
+       if (oalgo != nalgo || odiglen != ndiglen) {
+           action = save;
+           goto exit;
+       }
+       if (odigest && ndigest && !memcmp(odigest, ndigest, odiglen)) {
+           action = FA_SKIP;   /* identical file, don't bother. */
+           goto exit;
+       }
+       /* ... but otherwise backup will be needed */
+       action = save;
+    } else /* dbWhat == LINK */ {
+       const char * oFLink, * nFLink;
+       oFLink = rpmfiFLinkIndex(ofi, oix);
+       if (diskWhat == LINK) {
+           ssize_t link_len = readlink(fn, buffer, sizeof(buffer) - 1);
+           if (link_len == -1)
+               goto exit;      /* assume file has been removed */
+           buffer[link_len] = '\0';
+           if (oFLink && rstreq(oFLink, buffer))
+               goto exit;      /* unmodified config file, replace. */
+       }
+       nFLink = rpmfiFLinkIndex(nfi, nix);
+       if (oFLink && nFLink && rstreq(oFLink, nFLink)) {
+           action = FA_SKIP;   /* identical file, don't bother. */
+           goto exit;
+       }
+       /* ... but otherwise backup will be needed */
+       action = save;
+    }
+
+exit:
+    free(fn);
+    return action;
+}
+
+int rpmfiConfigConflictIndex(rpmfi fi, int ix)
+{
+    char * fn = NULL;
+    rpmfileAttrs flags = rpmfiFFlagsIndex(fi, ix);
+    char buffer[1024];
+    rpmFileTypes newWhat, diskWhat;
+    struct stat sb;
+    int rc = 0;
+
+    if (!(flags & RPMFILE_CONFIG))
+       return 0;
+
+    /* Only links and regular files can be %config, this is kinda moot */
+    /* XXX: Why are we returning 1 here? */
+    newWhat = rpmfiWhatis(rpmfiFModeIndex(fi, ix));
+    if (newWhat != LINK && newWhat != REG)
+       return 1;
+
+    /* If it's not on disk, there's nothing to be saved */
+    fn = rpmfiFNIndex(fi, ix);
+    if (lstat(fn, &sb))
+       goto exit;
+
+    /* Files of different types obviously are not identical */
+    diskWhat = rpmfiWhatis((rpm_mode_t)sb.st_mode);
+    if (diskWhat != newWhat) {
+       rc = 1;
+       goto exit;
+    }
+    
+    memset(buffer, 0, sizeof(buffer));
+    if (newWhat == REG) {
+       int algo;
+       size_t diglen;
+       const unsigned char *ndigest = rpmfiFDigestIndex(fi,ix, &algo, &diglen);
+       if (rpmDoDigest(algo, fn, 0, (unsigned char *)buffer, NULL))
+           goto exit;  /* assume file has been removed */
+       if (ndigest && !memcmp(ndigest, buffer, diglen))
+           goto exit;  /* unmodified config file */
+    } else /* newWhat == LINK */ {
+       const char * nFLink;
+       ssize_t link_len = readlink(fn, buffer, sizeof(buffer) - 1);
+       if (link_len == -1)
+           goto exit;  /* assume file has been removed */
+       buffer[link_len] = '\0';
+       nFLink = rpmfiFLinkIndex(fi, ix);
+       if (nFLink && rstreq(nFLink, buffer))
+           goto exit;  /* unmodified config file */
+    }
+
+    rc = 1;
+
+exit:
+    free(fn);
+    return rc;
+}
+
+static char **duparray(char ** src, int size)
+{
+    char **dest = xmalloc((size+1) * sizeof(*dest));
+    for (int i = 0; i < size; i++) {
+       dest[i] = xstrdup(src[i]);
+    }
+    free(src);
+    return dest;
+}
+
+static int addPrefixes(Header h, rpmRelocation *relocations, int numRelocations)
+{
+    struct rpmtd_s validRelocs;
+    const char *validprefix;
+    const char ** actualRelocations;
+    int numActual = 0;
+
+    headerGet(h, RPMTAG_PREFIXES, &validRelocs, HEADERGET_MINMEM);
+    /*
+     * If no relocations are specified (usually the case), then return the
+     * original header. If there are prefixes, however, then INSTPREFIXES
+     * should be added for RPM_INSTALL_PREFIX environ variables in scriptlets, 
+     * but, since relocateFileList() can be called more than once for 
+     * the same header, don't bother if already present.
+     */
+    if (relocations == NULL || numRelocations == 0) {
+       if (rpmtdCount(&validRelocs) > 0) {
+           if (!headerIsEntry(h, RPMTAG_INSTPREFIXES)) {
+               rpmtdSetTag(&validRelocs, RPMTAG_INSTPREFIXES);
+               headerPut(h, &validRelocs, HEADERPUT_DEFAULT);
+           }
+           rpmtdFreeData(&validRelocs);
+       }
+       return 0;
+    }
+
+    actualRelocations = xmalloc(rpmtdCount(&validRelocs) * sizeof(*actualRelocations));
+    rpmtdInit(&validRelocs);
+    while ((validprefix = rpmtdNextString(&validRelocs))) {
+       int j;
+       for (j = 0; j < numRelocations; j++) {
+           if (relocations[j].oldPath == NULL || /* XXX can't happen */
+               !rstreq(validprefix, relocations[j].oldPath))
+               continue;
+           /* On install, a relocate to NULL means skip the path. */
+           if (relocations[j].newPath) {
+               actualRelocations[numActual] = relocations[j].newPath;
+               numActual++;
+           }
+           break;
+       }
+       if (j == numRelocations) {
+           actualRelocations[numActual] = validprefix;
+           numActual++;
+       }
+    }
+    rpmtdFreeData(&validRelocs);
+
+    if (numActual) {
+       headerPutStringArray(h, RPMTAG_INSTPREFIXES, actualRelocations, numActual);
+    }
+    free(actualRelocations);
+    return numActual;
+}
+
+static void saveRelocs(Header h, rpmtd bnames, rpmtd dnames, rpmtd dindexes)
+{
+       struct rpmtd_s td;
+       headerGet(h, RPMTAG_BASENAMES, &td, HEADERGET_MINMEM);
+       rpmtdSetTag(&td, RPMTAG_ORIGBASENAMES);
+       headerPut(h, &td, HEADERPUT_DEFAULT);
+       rpmtdFreeData(&td);
+
+       headerGet(h, RPMTAG_DIRNAMES, &td, HEADERGET_MINMEM);
+       rpmtdSetTag(&td, RPMTAG_ORIGDIRNAMES);
+       headerPut(h, &td, HEADERPUT_DEFAULT);
+       rpmtdFreeData(&td);
+
+       headerGet(h, RPMTAG_DIRINDEXES, &td, HEADERGET_MINMEM);
+       rpmtdSetTag(&td, RPMTAG_ORIGDIRINDEXES);
+       headerPut(h, &td, HEADERPUT_DEFAULT);
+       rpmtdFreeData(&td);
+
+       headerMod(h, bnames);
+       headerMod(h, dnames);
+       headerMod(h, dindexes);
+}
+
+void rpmRelocateFileList(rpmRelocation *relocations, int numRelocations, 
+                        rpmfs fs, Header h)
+{
+    static int _printed = 0;
+    char ** baseNames;
+    char ** dirNames;
+    uint32_t * dirIndexes;
+    rpm_count_t fileCount, dirCount;
+    int nrelocated = 0;
+    int fileAlloced = 0;
+    char * fn = NULL;
+    int haveRelocatedBase = 0;
+    size_t maxlen = 0;
+    int i, j;
+    struct rpmtd_s bnames, dnames, dindexes, fmodes;
+
+    addPrefixes(h, relocations, numRelocations);
+
+    if (!_printed) {
+       _printed = 1;
+       rpmlog(RPMLOG_DEBUG, "========== relocations\n");
+       for (i = 0; i < numRelocations; i++) {
+           if (relocations[i].oldPath == NULL) continue; /* XXX can't happen */
+           if (relocations[i].newPath == NULL)
+               rpmlog(RPMLOG_DEBUG, "%5d exclude  %s\n",
+                       i, relocations[i].oldPath);
+           else
+               rpmlog(RPMLOG_DEBUG, "%5d relocate %s -> %s\n",
+                       i, relocations[i].oldPath, relocations[i].newPath);
+       }
+    }
+
+    for (i = 0; i < numRelocations; i++) {
+       if (relocations[i].newPath == NULL) continue;
+       size_t len = strlen(relocations[i].newPath);
+       if (len > maxlen) maxlen = len;
+    }
+
+    headerGet(h, RPMTAG_BASENAMES, &bnames, HEADERGET_MINMEM);
+    headerGet(h, RPMTAG_DIRINDEXES, &dindexes, HEADERGET_ALLOC);
+    headerGet(h, RPMTAG_DIRNAMES, &dnames, HEADERGET_MINMEM);
+    headerGet(h, RPMTAG_FILEMODES, &fmodes, HEADERGET_MINMEM);
+    /* TODO XXX ugh.. use rpmtd iterators & friends instead */
+    baseNames = bnames.data;
+    dirIndexes = dindexes.data;
+    fileCount = rpmtdCount(&bnames);
+    dirCount = rpmtdCount(&dnames);
+    /* XXX TODO: use rpmtdDup() instead */
+    dirNames = dnames.data = duparray(dnames.data, dirCount);
+    dnames.flags |= RPMTD_PTR_ALLOCED;
+
+    /*
+     * For all relocations, we go through sorted file/relocation lists 
+     * backwards so that /usr/local relocations take precedence over /usr 
+     * ones.
+     */
+
+    /* Relocate individual paths. */
+
+    for (i = fileCount - 1; i >= 0; i--) {
+       rpmFileTypes ft;
+       int fnlen;
+
+       size_t len = maxlen +
+               strlen(dirNames[dirIndexes[i]]) + strlen(baseNames[i]) + 1;
+       if (len >= fileAlloced) {
+           fileAlloced = len * 2;
+           fn = xrealloc(fn, fileAlloced);
+       }
+
+assert(fn != NULL);            /* XXX can't happen */
+       *fn = '\0';
+       fnlen = stpcpy( stpcpy(fn, dirNames[dirIndexes[i]]), baseNames[i]) - fn;
+
+       /*
+        * See if this file path needs relocating.
+        */
+       /*
+        * XXX FIXME: Would a bsearch of the (already sorted) 
+        * relocation list be a good idea?
+        */
+       for (j = numRelocations - 1; j >= 0; j--) {
+           if (relocations[j].oldPath == NULL) /* XXX can't happen */
+               continue;
+           len = !rstreq(relocations[j].oldPath, "/")
+               ? strlen(relocations[j].oldPath)
+               : 0;
+
+           if (fnlen < len)
+               continue;
+           /*
+            * Only subdirectories or complete file paths may be relocated. We
+            * don't check for '\0' as our directory names all end in '/'.
+            */
+           if (!(fn[len] == '/' || fnlen == len))
+               continue;
+
+           if (!rstreqn(relocations[j].oldPath, fn, len))
+               continue;
+           break;
+       }
+       if (j < 0) continue;
+
+       rpmtdSetIndex(&fmodes, i);
+       ft = rpmfiWhatis(rpmtdGetNumber(&fmodes));
+
+       /* On install, a relocate to NULL means skip the path. */
+       if (relocations[j].newPath == NULL) {
+           if (ft == XDIR) {
+               /* Start with the parent, looking for directory to exclude. */
+               for (j = dirIndexes[i]; j < dirCount; j++) {
+                   len = strlen(dirNames[j]) - 1;
+                   while (len > 0 && dirNames[j][len-1] == '/') len--;
+                   if (fnlen != len)
+                       continue;
+                   if (!rstreqn(fn, dirNames[j], fnlen))
+                       continue;
+                   break;
+               }
+           }
+           rpmfsSetAction(fs, i, FA_SKIPNSTATE);
+           rpmlog(RPMLOG_DEBUG, "excluding %s %s\n",
+                  ftstring(ft), fn);
+           continue;
+       }
+
+       /* Relocation on full paths only, please. */
+       if (fnlen != len) continue;
+
+       rpmlog(RPMLOG_DEBUG, "relocating %s to %s\n",
+              fn, relocations[j].newPath);
+       nrelocated++;
+
+       strcpy(fn, relocations[j].newPath);
+       {   char * te = strrchr(fn, '/');
+           if (te) {
+               if (te > fn) te++;      /* root is special */
+               fnlen = te - fn;
+           } else
+               te = fn + strlen(fn);
+           if (!rstreq(baseNames[i], te)) { /* basename changed too? */
+               if (!haveRelocatedBase) {
+                   /* XXX TODO: use rpmtdDup() instead */
+                   bnames.data = baseNames = duparray(baseNames, fileCount);
+                   bnames.flags |= RPMTD_PTR_ALLOCED;
+                   haveRelocatedBase = 1;
+               }
+               free(baseNames[i]);
+               baseNames[i] = xstrdup(te);
+           }
+           *te = '\0';                 /* terminate new directory name */
+       }
+
+       /* Does this directory already exist in the directory list? */
+       for (j = 0; j < dirCount; j++) {
+           if (fnlen != strlen(dirNames[j]))
+               continue;
+           if (!rstreqn(fn, dirNames[j], fnlen))
+               continue;
+           break;
+       }
+       
+       if (j < dirCount) {
+           dirIndexes[i] = j;
+           continue;
+       }
+
+       /* Creating new paths is a pita */
+       dirNames = dnames.data = xrealloc(dnames.data, 
+                              sizeof(*dirNames) * (dirCount + 1));
+
+       dirNames[dirCount] = xstrdup(fn);
+       dirIndexes[i] = dirCount;
+       dirCount++;
+       dnames.count++;
+    }
+
+    /* Finish off by relocating directories. */
+    for (i = dirCount - 1; i >= 0; i--) {
+       for (j = numRelocations - 1; j >= 0; j--) {
+
+           if (relocations[j].oldPath == NULL) /* XXX can't happen */
+               continue;
+           size_t len = !rstreq(relocations[j].oldPath, "/")
+               ? strlen(relocations[j].oldPath)
+               : 0;
+
+           if (len && !rstreqn(relocations[j].oldPath, dirNames[i], len))
+               continue;
+
+           /*
+            * Only subdirectories or complete file paths may be relocated. We
+            * don't check for '\0' as our directory names all end in '/'.
+            */
+           if (dirNames[i][len] != '/')
+               continue;
+
+           if (relocations[j].newPath) { /* Relocate the path */
+               char *t = NULL;
+               rstrscat(&t, relocations[j].newPath, (dirNames[i] + len), NULL);
+               /* Unfortunatly rpmCleanPath strips the trailing slash.. */
+               (void) rpmCleanPath(t);
+               rstrcat(&t, "/");
+
+               rpmlog(RPMLOG_DEBUG,
+                      "relocating directory %s to %s\n", dirNames[i], t);
+               free(dirNames[i]);
+               dirNames[i] = t;
+               nrelocated++;
+           }
+       }
+    }
+
+    /* Save original filenames in header and replace (relocated) filenames. */
+    if (nrelocated) {
+       saveRelocs(h, &bnames, &dnames, &dindexes);
+    }
+
+    rpmtdFreeData(&bnames);
+    rpmtdFreeData(&dnames);
+    rpmtdFreeData(&dindexes);
+    rpmtdFreeData(&fmodes);
+    free(fn);
+}
+
+rpmfi rpmfiFree(rpmfi fi)
+{
+    if (fi == NULL) return NULL;
+
+    if (fi->nrefs > 1)
+       return rpmfiUnlink(fi);
+
+    if (fi->fc > 0) {
+       fi->bnl = _free(fi->bnl);
+       fi->dnl = _free(fi->dnl);
+
+       fi->flinkcache = strcacheFree(fi->flinkcache);
+       fi->flinks = _free(fi->flinks);
+       fi->flangs = _free(fi->flangs);
+       fi->digests = _free(fi->digests);
+       fi->fcapcache = strcacheFree(fi->fcapcache);
+       fi->fcaps = _free(fi->fcaps);
+
+       fi->cdict = _free(fi->cdict);
+
+       fi->fuser = _free(fi->fuser);
+       fi->fgroup = _free(fi->fgroup);
+
+       fi->fstates = _free(fi->fstates);
+       fi->fps = _free(fi->fps);
+
+       /* these point to header memory if KEEPHEADER is used, dont free */
+       if (!(fi->fiflags & RPMFI_KEEPHEADER) && fi->h == NULL) {
+           fi->fmtimes = _free(fi->fmtimes);
+           fi->fmodes = _free(fi->fmodes);
+           fi->fflags = _free(fi->fflags);
+           fi->vflags = _free(fi->vflags);
+           fi->fsizes = _free(fi->fsizes);
+           fi->frdevs = _free(fi->frdevs);
+           fi->finodes = _free(fi->finodes);
+           fi->dil = _free(fi->dil);
+
+           fi->fcolors = _free(fi->fcolors);
+           fi->fcdictx = _free(fi->fcdictx);
+           fi->ddict = _free(fi->ddict);
+           fi->fddictx = _free(fi->fddictx);
+           fi->fddictn = _free(fi->fddictn);
+
+       }
+    }
+
+    fi->fn = _free(fi->fn);
+    fi->apath = _free(fi->apath);
+
+    fi->replacedSizes = _free(fi->replacedSizes);
+
+    fi->h = headerFree(fi->h);
+
+    (void) rpmfiUnlink(fi);
+    memset(fi, 0, sizeof(*fi));                /* XXX trash and burn */
+    fi = _free(fi);
+
+    return NULL;
+}
+
+/* Helper to push header tag data into a string cache */
+static scidx_t *cacheTag(strcache cache, Header h, rpmTag tag)
+{
+    scidx_t *idx = NULL;
+    struct rpmtd_s td;
+    if (headerGet(h, tag, &td, HEADERGET_MINMEM)) {
+       idx = xmalloc(sizeof(*idx) * rpmtdCount(&td));
+       int i = 0;
+       const char *str;
+       while ((str = rpmtdNextString(&td))) {
+          idx[i++] = strcachePut(cache, str);
+       }
+       rpmtdFreeData(&td);
+    }
+    return idx;
+}
+
+#define _hgfi(_h, _tag, _td, _flags, _data) \
+    if (headerGet((_h), (_tag), (_td), (_flags))) \
+       _data = (td.data)
+
+rpmfi rpmfiNew(const rpmts ts, Header h, rpmTagVal tagN, rpmfiFlags flags)
+{
+    rpmfi fi = xcalloc(1, sizeof(*fi)); 
+    unsigned char * t;
+    struct rpmtd_s fdigests, digalgo;
+    struct rpmtd_s td;
+    headerGetFlags scareFlags = (flags & RPMFI_KEEPHEADER) ? 
+                               HEADERGET_MINMEM : HEADERGET_ALLOC;
+    headerGetFlags defFlags = HEADERGET_ALLOC;
+
+    fi->magic = RPMFIMAGIC;
+    fi->i = -1;
+
+    fi->fiflags = flags;
+
+    _hgfi(h, RPMTAG_BASENAMES, &td, defFlags, fi->bnl);
+    fi->fc = rpmtdCount(&td);
+    if (fi->fc == 0) {
+       goto exit;
+    }
+
+    _hgfi(h, RPMTAG_DIRNAMES, &td, defFlags, fi->dnl);
+    fi->dc = rpmtdCount(&td);
+    _hgfi(h, RPMTAG_DIRINDEXES, &td, scareFlags, fi->dil);
+
+    /* Is our filename triplet sane? */
+    if (fi->dc == 0 || fi->dc > fi->fc || rpmtdCount(&td) != fi->fc)
+       goto errxit;
+
+    for (rpm_count_t i = 0; i < fi->fc; i++) {
+       if (fi->dil[i] >= fi->fc)
+           goto errxit;
+    }
+
+    /* XXX TODO: all these should be sanity checked, ugh... */
+    if (!(flags & RPMFI_NOFILEMODES))
+       _hgfi(h, RPMTAG_FILEMODES, &td, scareFlags, fi->fmodes);
+    if (!(flags & RPMFI_NOFILEFLAGS))
+       _hgfi(h, RPMTAG_FILEFLAGS, &td, scareFlags, fi->fflags);
+    if (!(flags & RPMFI_NOFILEVERIFYFLAGS))
+       _hgfi(h, RPMTAG_FILEVERIFYFLAGS, &td, scareFlags, fi->vflags);
+    if (!(flags & RPMFI_NOFILESIZES))
+       _hgfi(h, RPMTAG_FILESIZES, &td, scareFlags, fi->fsizes);
+
+    if (!(flags & RPMFI_NOFILECOLORS))
+       _hgfi(h, RPMTAG_FILECOLORS, &td, scareFlags, fi->fcolors);
+
+    if (!(flags & RPMFI_NOFILECLASS)) {
+       _hgfi(h, RPMTAG_CLASSDICT, &td, scareFlags, fi->cdict);
+       fi->ncdict = rpmtdCount(&td);
+       _hgfi(h, RPMTAG_FILECLASS, &td, scareFlags, fi->fcdictx);
+    }
+    if (!(flags & RPMFI_NOFILEDEPS)) {
+       _hgfi(h, RPMTAG_DEPENDSDICT, &td, scareFlags, fi->ddict);
+       fi->nddict = rpmtdCount(&td);
+       _hgfi(h, RPMTAG_FILEDEPENDSX, &td, scareFlags, fi->fddictx);
+       _hgfi(h, RPMTAG_FILEDEPENDSN, &td, scareFlags, fi->fddictn);
+    }
+
+    if (!(flags & RPMFI_NOFILESTATES))
+       _hgfi(h, RPMTAG_FILESTATES, &td, defFlags, fi->fstates);
+
+    if (!(flags & RPMFI_NOFILECAPS) && headerIsEntry(h, RPMTAG_FILECAPS)) {
+       fi->fcapcache = strcacheNew();
+       fi->fcaps = cacheTag(fi->fcapcache, h, RPMTAG_FILECAPS);
+    }
+
+    if (!(flags & RPMFI_NOFILELINKTOS)) {
+       fi->flinkcache = strcacheNew();
+       fi->flinks = cacheTag(fi->flinkcache, h, RPMTAG_FILELINKTOS);
+    }
+    /* FILELANGS are only interesting when installing */
+    if ((headerGetInstance(h) == 0) && !(flags & RPMFI_NOFILELANGS))
+       fi->flangs = cacheTag(langcache, h, RPMTAG_FILELANGS);
+
+    /* See if the package has non-md5 file digests */
+    fi->digestalgo = PGPHASHALGO_MD5;
+    if (headerGet(h, RPMTAG_FILEDIGESTALGO, &digalgo, HEADERGET_MINMEM)) {
+       uint32_t *algo = rpmtdGetUint32(&digalgo);
+       /* Hmm, what to do with unknown digest algorithms? */
+       if (algo && rpmDigestLength(*algo) != 0) {
+           fi->digestalgo = *algo;
+       }
+    }
+
+    fi->digests = NULL;
+    /* grab hex digests from header and store in binary format */
+    if (!(flags & RPMFI_NOFILEDIGESTS) &&
+       headerGet(h, RPMTAG_FILEDIGESTS, &fdigests, HEADERGET_MINMEM)) {
+       const char *fdigest;
+       size_t diglen = rpmDigestLength(fi->digestalgo);
+       fi->digests = t = xmalloc(rpmtdCount(&fdigests) * diglen);
+
+       while ((fdigest = rpmtdNextString(&fdigests))) {
+           if (!(fdigest && *fdigest != '\0')) {
+               memset(t, 0, diglen);
+               t += diglen;
+               continue;
+           }
+           for (int j = 0; j < diglen; j++, t++, fdigest += 2)
+               *t = (rnibble(fdigest[0]) << 4) | rnibble(fdigest[1]);
+       }
+       rpmtdFreeData(&fdigests);
+    }
+
+    /* XXX TR_REMOVED doesn;t need fmtimes, frdevs, finodes */
+    if (!(flags & RPMFI_NOFILEMTIMES))
+       _hgfi(h, RPMTAG_FILEMTIMES, &td, scareFlags, fi->fmtimes);
+    if (!(flags & RPMFI_NOFILERDEVS))
+       _hgfi(h, RPMTAG_FILERDEVS, &td, scareFlags, fi->frdevs);
+    if (!(flags & RPMFI_NOFILEINODES))
+       _hgfi(h, RPMTAG_FILEINODES, &td, scareFlags, fi->finodes);
+
+    if (!(flags & RPMFI_NOFILEUSER)) 
+       fi->fuser = cacheTag(ugcache, h, RPMTAG_FILEUSERNAME);
+    if (!(flags & RPMFI_NOFILEGROUP)) 
+       fi->fgroup = cacheTag(ugcache, h, RPMTAG_FILEGROUPNAME);
+
+    /* lazily alloced from rpmfiFN() */
+    fi->fn = NULL;
+
+exit:
+
+    if (fi != NULL) {
+       fi->h = (fi->fiflags & RPMFI_KEEPHEADER) ? headerLink(h) : NULL;
+    }
+
+    /* FIX: rpmfi null annotations */
+    return rpmfiLink(fi);
+
+errxit:
+    rpmfiFree(fi);
+    return NULL;
+}
+
+void rpmfiSetFReplacedSizeIndex(rpmfi fi, int ix, rpm_loff_t newsize)
+{
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->replacedSizes == NULL) {
+           fi->replacedSizes = xcalloc(fi->fc, sizeof(*fi->replacedSizes));
+       }
+       /* XXX watch out, replacedSizes is not rpm_loff_t (yet) */
+       fi->replacedSizes[ix] = (rpm_off_t) newsize;
+    }
+}
+
+rpm_loff_t rpmfiFReplacedSizeIndex(rpmfi fi, int ix)
+{
+    rpm_loff_t rsize = 0;
+    if (fi != NULL && ix >= 0 && ix < fi->fc) {
+       if (fi->replacedSizes) {
+           rsize = fi->replacedSizes[ix];
+       }
+    }
+    return rsize;
+}
+
+void rpmfiFpLookup(rpmfi fi, fingerPrintCache fpc)
+{
+    if (fi->fc > 0 && fi->fps == NULL) {
+       fi->fps = xcalloc(fi->fc, sizeof(*fi->fps));
+    }
+    fpLookupList(fpc, fi->dnl, fi->bnl, fi->dil, fi->fc, fi->fps);
+}
+
+/* 
+ * Generate iterator accessors function wrappers, these do nothing but
+ * call the corresponding rpmfiFooIndex(fi, fi->[ij])
+ */
+
+#define RPMFI_ITERFUNC(TYPE, NAME, IXV) \
+    TYPE rpmfi ## NAME(rpmfi fi) { return rpmfi ## NAME ## Index(fi, fi ? fi->IXV : -1); }
+
+RPMFI_ITERFUNC(const char *, BN, i)
+RPMFI_ITERFUNC(const char *, DN, j)
+RPMFI_ITERFUNC(const char *, FLink, i)
+RPMFI_ITERFUNC(const char *, FUser, i)
+RPMFI_ITERFUNC(const char *, FGroup, i)
+RPMFI_ITERFUNC(const char *, FCaps, i)
+RPMFI_ITERFUNC(const char *, FLangs, i)
+RPMFI_ITERFUNC(const char *, FClass, i)
+RPMFI_ITERFUNC(rpmfileState, FState, i)
+RPMFI_ITERFUNC(rpmfileAttrs, FFlags, i)
+RPMFI_ITERFUNC(rpmVerifyAttrs, VFlags, i)
+RPMFI_ITERFUNC(rpm_mode_t, FMode, i)
+RPMFI_ITERFUNC(rpm_rdev_t, FRdev, i)
+RPMFI_ITERFUNC(rpm_time_t, FMtime, i)
+RPMFI_ITERFUNC(rpm_ino_t, FInode, i)
+RPMFI_ITERFUNC(rpm_loff_t, FSize, i)
+RPMFI_ITERFUNC(rpm_color_t, FColor, i)
+RPMFI_ITERFUNC(uint32_t, FNlink, i)
+
+const char * rpmfiFN(rpmfi fi)
+{
+    const char *fn = ""; /* preserve behavior on errors */
+    if (fi != NULL) {
+       free(fi->fn);
+       fi->fn = rpmfiFNIndex(fi, fi->i);
+       if (fi->fn != NULL)
+           fn = fi->fn;
+    }
+    return fn;
+}
+
+const unsigned char * rpmfiFDigest(rpmfi fi, int *algo, size_t *len)
+{
+    return rpmfiFDigestIndex(fi, fi ? fi->i : -1, algo, len);
+}
+
+uint32_t rpmfiFDepends(rpmfi fi, const uint32_t ** fddictp)
+{
+    return rpmfiFDependsIndex(fi,  fi ? fi->i : -1, fddictp);
+}
+
+int rpmfiCompare(const rpmfi afi, const rpmfi bfi)
+{
+    return rpmfiCompareIndex(afi, afi ? afi->i : -1, bfi, bfi ? bfi->i : -1);
+}
+
+rpmFileAction rpmfiDecideFate(const rpmfi ofi, rpmfi nfi, int skipMissing)
+{
+    return rpmfiDecideFateIndex(ofi, ofi ? ofi->i : -1,
+                               nfi, nfi ? nfi->i : -1,
+                               skipMissing);
+}
+
+int rpmfiConfigConflict(const rpmfi fi)
+{
+    return rpmfiConfigConflictIndex(fi, fi ? fi->i : -1);
+}