/**
* \file lib/fprint.c
*/
#include "system.h"
#include <rpm/rpmfileutil.h> /* for rpmCleanPath */
#include <rpm/rpmts.h>
#include <rpm/rpmsq.h>
#include "lib/rpmdb_internal.h"
#include "lib/rpmfi_internal.h"
#include "lib/rpmte_internal.h"
#include "lib/fprint.h"
#include "lib/misc.h"
#include "debug.h"
#include <libgen.h>
/* Create new hash table type rpmFpEntryHash */
#undef HASHTYPE
#undef HTKEYTYPE
#undef HTDATATYPE
#define HASHTYPE rpmFpEntryHash
#define HTKEYTYPE rpmsid
#define HTDATATYPE const struct fprintCacheEntry_s *
#include "lib/rpmhash.H"
#include "lib/rpmhash.C"
/* Create by-fingerprint hash table */
#undef HASHTYPE
#undef HTKEYTYPE
#undef HTDATATYPE
#define HASHTYPE rpmFpHash
#define HTKEYTYPE const fingerPrint *
#define HTDATATYPE struct rpmffi_s
#include "lib/rpmhash.H"
#include "lib/rpmhash.C"
#undef HASHTYPE
#undef HTKEYTYPE
#undef HTDATATYPE
static unsigned int sidHash(rpmsid sid)
{
return sid;
}
static int sidCmp(rpmsid a, rpmsid b)
{
return (a != b);
}
/**
* Finger print cache entry.
* This is really a directory and symlink cache. We don't differentiate between
* the two. We can prepopulate it, which allows us to easily conduct "fake"
* installs of a system w/o actually mounting filesystems.
*/
struct fprintCacheEntry_s {
rpmsid dirId; /*!< path to existing directory */
dev_t dev; /*!< stat(2) device number */
ino_t ino; /*!< stat(2) inode number */
};
/**
* Associates a trailing sub-directory and final base name with an existing
* directory finger print.
*/
struct fingerPrint_s {
/*! directory finger print entry (the directory path is stat(2)-able */
const struct fprintCacheEntry_s * entry;
/*! trailing sub-directory path (directories that are not stat(2)-able */
rpmsid subDirId;
rpmsid baseNameId; /*!< file base name id */
};
#define FP_ENTRY_EQUAL(a, b) (((a)->dev == (b)->dev) && ((a)->ino == (b)->ino))
#define FP_EQUAL(a, b) ( \
FP_ENTRY_EQUAL((a).entry, (b).entry) && \
((a).baseNameId == (b).baseNameId) && \
((a).subDirId == (b).subDirId) \
)
/**
* Finger print cache.
*/
struct fprintCache_s {
rpmFpEntryHash ht; /*!< hashed by dirName */
rpmFpHash fp; /*!< hashed by fingerprint */
rpmstrPool pool; /*!< string pool */
};
fingerPrintCache fpCacheCreate(int sizeHint, rpmstrPool pool)
{
fingerPrintCache fpc;
fpc = xcalloc(1, sizeof(*fpc));
fpc->ht = rpmFpEntryHashCreate(sizeHint, sidHash, sidCmp,
NULL, (rpmFpEntryHashFreeData)free);
fpc->pool = (pool != NULL) ? rpmstrPoolLink(pool) : rpmstrPoolCreate();
return fpc;
}
fingerPrintCache fpCacheFree(fingerPrintCache cache)
{
if (cache) {
cache->ht = rpmFpEntryHashFree(cache->ht);
cache->fp = rpmFpHashFree(cache->fp);
cache->pool = rpmstrPoolFree(cache->pool);
free(cache);
}
return NULL;
}
/**
* Find directory name entry in cache.
* @param cache pointer to fingerprint cache
* @param dirId string id to locate in cache
* @return pointer to directory name entry (or NULL if not found).
*/
static const struct fprintCacheEntry_s * cacheContainsDirectory(
fingerPrintCache cache, rpmsid dirId)
{
const struct fprintCacheEntry_s ** data;
if (rpmFpEntryHashGetEntry(cache->ht, dirId, &data, NULL, NULL))
return data[0];
return NULL;
}
static char * canonDir(rpmstrPool pool, rpmsid dirNameId)
{
const char * dirName = rpmstrPoolStr(pool, dirNameId);
size_t cdnl = rpmstrPoolStrlen(pool, dirNameId);;
char *cdnbuf = NULL;
if (*dirName == '/') {
cdnbuf = xstrdup(dirName);
cdnbuf = rpmCleanPath(cdnbuf);
/* leave my trailing slashes along you b**** */
if (cdnl > 1)
cdnbuf = rstrcat(&cdnbuf, "/");
} else {
/* Using realpath on the arg isn't correct if the arg is a symlink,
* especially if the symlink is a dangling link. What we
* do instead is use realpath() on `.' and then append arg to
* the result.
*/
/* if the current directory doesn't exist, we might fail.
oh well. likewise if it's too long. */
/* XXX we should let realpath() allocate if it can */
cdnbuf = xmalloc(PATH_MAX);
cdnbuf[0] = '\0';
if (realpath(".", cdnbuf) != NULL) {
char *end = cdnbuf + strlen(cdnbuf);
if (end[-1] != '/') *end++ = '/';
end = stpncpy(end, dirName, PATH_MAX - (end - cdnbuf));
*end = '\0';
(void)rpmCleanPath(cdnbuf); /* XXX possible /../ from concatenation */
end = cdnbuf + strlen(cdnbuf);
if (end[-1] != '/') *end++ = '/';
*end = '\0';
} else {
cdnbuf = _free(cdnbuf);
}
}
return cdnbuf;
}
static int doLookupId(fingerPrintCache cache,
rpmsid dirNameId, rpmsid baseNameId,
fingerPrint *fp)
{
struct stat sb;
const struct fprintCacheEntry_s * cacheHit;
char *cdn = canonDir(cache->pool, dirNameId);
rpmsid fpId;
size_t fpLen;
if (cdn == NULL) goto exit; /* XXX only if realpath() above fails */
memset(fp, 0, sizeof(*fp));
fpId = rpmstrPoolId(cache->pool, cdn, 1);
fpLen = rpmstrPoolStrlen(cache->pool, fpId);;
while (1) {
/* as we're stating paths here, we want to follow symlinks */
cacheHit = cacheContainsDirectory(cache, fpId);
if (cacheHit != NULL) {
fp->entry = cacheHit;
} else if (!stat(rpmstrPoolStr(cache->pool, fpId), &sb)) {
struct fprintCacheEntry_s * newEntry = xmalloc(sizeof(* newEntry));
newEntry->ino = sb.st_ino;
newEntry->dev = sb.st_dev;
newEntry->dirId = fpId;
fp->entry = newEntry;
rpmFpEntryHashAddEntry(cache->ht, fpId, fp->entry);
}
if (fp->entry) {
const char * subDir = cdn + fpLen - 1;
/* XXX don't bother saving '/' as subdir */
if (subDir[0] == '\0' || (subDir[0] == '/' && subDir[1] == '\0'))
subDir = NULL;
fp->baseNameId = baseNameId;
if (subDir != NULL)
fp->subDirId = rpmstrPoolId(cache->pool, subDir, 1);
goto exit;
}
/* stat of '/' just failed! */
if (fpLen == 1)
abort();
/* Find the parent directory and its id for the next round */
fpLen--;
while (fpLen > 1 && cdn[fpLen-1] != '/')
fpLen--;
fpId = rpmstrPoolIdn(cache->pool, cdn, fpLen, 1);
}
exit:
free(cdn);
/* XXX TODO: failure from eg realpath() should be returned and handled */
return 0;
}
static int doLookup(fingerPrintCache cache,
const char * dirName, const char * baseName,
fingerPrint *fp)
{
rpmsid dirNameId = rpmstrPoolId(cache->pool, dirName, 1);
rpmsid baseNameId = rpmstrPoolId(cache->pool, baseName, 1);
return doLookupId(cache, dirNameId, baseNameId, fp);
}
int fpLookup(fingerPrintCache cache,
const char * dirName, const char * baseName,
fingerPrint **fp)
{
if (*fp == NULL)
*fp = xcalloc(1, sizeof(**fp));
return doLookup(cache, dirName, baseName, *fp);
}
/**
* Return hash value for a finger print.
* Hash based on dev and inode only!
* @param fp pointer to finger print entry
* @return hash value
*/
static unsigned int fpHashFunction(const fingerPrint * fp)
{
unsigned int hash = 0;
int j;
hash = sidHash(fp->baseNameId);
if (fp->subDirId) hash ^= sidHash(fp->subDirId);
hash ^= ((unsigned)fp->entry->dev);
for (j=0; j<4; j++) hash ^= ((fp->entry->ino >> (8*j)) & 0xFF) << ((3-j)*8);
return hash;
}
int fpEqual(const fingerPrint * k1, const fingerPrint * k2)
{
/* If the addresses are the same, so are the values. */
if (k1 == k2)
return 0;
/* Otherwise, compare fingerprints by value. */
if (FP_EQUAL(*k1, *k2))
return 0;
return 1;
}
const char * fpEntryDir(fingerPrintCache cache, fingerPrint *fp)
{
const char * dn = NULL;
if (fp && fp->entry)
dn = rpmstrPoolStr(cache->pool, fp->entry->dirId);
return dn;
}
dev_t fpEntryDev(fingerPrintCache cache, fingerPrint *fp)
{
return (fp && fp->entry) ? fp->entry->dev : 0;
}
int fpLookupEquals(fingerPrintCache cache, fingerPrint *fp,
const char * dirName, const char * baseName)
{
struct fingerPrint_s ofp;
doLookup(cache, dirName, baseName, &ofp);
return FP_EQUAL(*fp, ofp);
}
fingerPrint * fpLookupList(fingerPrintCache cache, rpmstrPool pool,
rpmsid * dirNames, rpmsid * baseNames,
const uint32_t * dirIndexes,
int fileCount)
{
fingerPrint * fps = xmalloc(fileCount * sizeof(*fps));
int i;
/*
* We could handle different pools easily enough, but there should be
* no need for that. Make sure we catch any oddball cases there might be
* lurking about.
*/
assert(cache->pool == pool);
for (i = 0; i < fileCount; i++) {
/* If this is in the same directory as the last file, don't bother
redoing all of this work */
if (i > 0 && dirIndexes[i - 1] == dirIndexes[i]) {
fps[i].entry = fps[i - 1].entry;
fps[i].subDirId = fps[i - 1].subDirId;
/* XXX if the pools are different, copy would be needed */
fps[i].baseNameId = baseNames[i];
} else {
doLookupId(cache, dirNames[dirIndexes[i]], baseNames[i], &fps[i]);
}
}
return fps;
}
/* Check file for to be installed symlinks in their path and correct their fp */
static void fpLookupSubdir(rpmFpHash symlinks, fingerPrintCache fpc, rpmte p, int filenr)
{
rpmfiles fi = rpmteFiles(p);
struct fingerPrint_s current_fp;
const char *currentsubdir;
size_t lensubDir, bnStart, bnEnd;
struct rpmffi_s * recs;
int numRecs;
int i;
fingerPrint *fp = rpmfilesFps(fi) + filenr;
int symlinkcount = 0;
struct rpmffi_s ffi = { p, filenr};
if (fp->subDirId == 0) {
rpmFpHashAddEntry(fpc->fp, fp, ffi);
goto exit;
}
currentsubdir = rpmstrPoolStr(fpc->pool, fp->subDirId);
lensubDir = rpmstrPoolStrlen(fpc->pool, fp->subDirId);
current_fp = *fp;
/* Set baseName to the upper most dir */
bnStart = bnEnd = 1;
while (bnEnd < lensubDir && currentsubdir[bnEnd] != '/')
bnEnd++;
/* no subDir for now */
current_fp.subDirId = 0;
while (bnEnd < lensubDir) {
char found = 0;
current_fp.baseNameId = rpmstrPoolIdn(fpc->pool,
currentsubdir + bnStart,
bnEnd - bnStart, 1);
rpmFpHashGetEntry(symlinks, ¤t_fp, &recs, &numRecs, NULL);
for (i = 0; i < numRecs; i++) {
rpmfiles foundfi = rpmteFiles(recs[i].p);
char const *linktarget = rpmfilesFLink(foundfi, recs[i].fileno);
char *link;
/* Ignore already removed (by eg %pretrans) links */
if (linktarget && rpmteType(recs[i].p) == TR_REMOVED) {
char *path = rpmfilesFN(foundfi, recs[i].fileno);
struct stat sb;
if (lstat(path, &sb) == -1)
linktarget = NULL;
free(path);
}
foundfi = rpmfilesFree(foundfi);
if (linktarget && *linktarget != '\0') {
const char *bn;
/* this "directory" is a symlink */
link = NULL;
if (*linktarget != '/') {
const char *dn, *subDir = NULL;
dn = rpmstrPoolStr(fpc->pool, current_fp.entry->dirId);
if (current_fp.subDirId) {
subDir = rpmstrPoolStr(fpc->pool, current_fp.subDirId);
}
rstrscat(&link, dn, subDir ? subDir : "", "/", NULL);
}
rstrscat(&link, linktarget, "/", NULL);
if (strlen(currentsubdir + bnEnd)) {
rstrscat(&link, currentsubdir + bnEnd, NULL);
}
bn = rpmstrPoolStr(fpc->pool, fp->baseNameId);
doLookup(fpc, link, bn, fp);
free(link);
symlinkcount++;
/* setup current_fp for the new path */
found = 1;
current_fp = *fp;
if (fp->subDirId == 0) {
/* directory exists - no need to look for symlinks */
rpmFpHashAddEntry(fpc->fp, fp, ffi);
goto exit;
}
currentsubdir = rpmstrPoolStr(fpc->pool, fp->subDirId);
lensubDir = rpmstrPoolStrlen(fpc->pool, fp->subDirId);
/* no subDir for now */
current_fp.subDirId = 0;
/* Set baseName to the upper most dir */
bnStart = bnEnd = 1;
while (bnEnd < lensubDir && currentsubdir[bnEnd] != '/')
bnEnd++;
break;
}
}
if (symlinkcount > 50) {
// found too many symlinks in the path
// most likley a symlink cicle
// giving up
// TODO warning/error
break;
}
if (found) {
continue; // restart loop after symlink
}
/* Set former baseName as subDir */
bnEnd++;
current_fp.subDirId = rpmstrPoolIdn(fpc->pool, currentsubdir, bnEnd, 1);
/* set baseName to the next lower dir */
bnStart = bnEnd;
while (bnEnd < lensubDir && currentsubdir[bnEnd] != '/')
bnEnd++;
}
rpmFpHashAddEntry(fpc->fp, fp, ffi);
exit:
rpmfilesFree(fi);
}
fingerPrint * fpCacheGetByFp(fingerPrintCache cache,
struct fingerPrint_s * fp, int ix,
struct rpmffi_s ** recs, int * numRecs)
{
if (rpmFpHashGetEntry(cache->fp, fp + ix, recs, numRecs, NULL))
return fp + ix;
else
return NULL;
}
void fpCachePopulate(fingerPrintCache fpc, rpmts ts, int fileCount)
{
rpmtsi pi;
rpmte p;
rpmfs fs;
rpmfiles fi;
int i, fc;
if (fpc->fp == NULL)
fpc->fp = rpmFpHashCreate(fileCount/2 + 10001, fpHashFunction, fpEqual,
NULL, NULL);
rpmFpHash symlinks = rpmFpHashCreate(fileCount/16+16, fpHashFunction, fpEqual, NULL, NULL);
pi = rpmtsiInit(ts);
while ((p = rpmtsiNext(pi, 0)) != NULL) {
fingerPrint *fpList;
(void) rpmsqPoll();
if ((fi = rpmteFiles(p)) == NULL)
continue;
(void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
rpmfilesFpLookup(fi, fpc);
fs = rpmteGetFileStates(p);
fc = rpmfsFC(fs);
fpList = rpmfilesFps(fi);
/* collect symbolic links */
for (i = 0; i < fc; i++) {
struct rpmffi_s ffi;
char const *linktarget;
if (XFA_SKIPPING(rpmfsGetAction(fs, i)))
continue;
linktarget = rpmfilesFLink(fi, i);
if (!(linktarget && *linktarget != '\0'))
continue;
ffi.p = p;
ffi.fileno = i;
rpmFpHashAddEntry(symlinks, fpList + i, ffi);
}
(void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), fc);
rpmfilesFree(fi);
}
rpmtsiFree(pi);
/* ===============================================
* Check fingerprints if they contain symlinks
* and add them to the hash table
*/
pi = rpmtsiInit(ts);
while ((p = rpmtsiNext(pi, 0)) != NULL) {
(void) rpmsqPoll();
if ((fi = rpmteFiles(p)) == NULL)
continue;
fs = rpmteGetFileStates(p);
fc = rpmfsFC(fs);
(void) rpmswEnter(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
for (i = 0; i < fc; i++) {
if (XFA_SKIPPING(rpmfsGetAction(fs, i)))
continue;
fpLookupSubdir(symlinks, fpc, p, i);
}
(void) rpmswExit(rpmtsOp(ts, RPMTS_OP_FINGERPRINT), 0);
rpmfilesFree(fi);
}
rpmtsiFree(pi);
rpmFpHashFree(symlinks);
}