540 lines
14 KiB
C
540 lines
14 KiB
C
/**
|
|
* \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; /* XXX can't happen */
|
|
|
|
(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();
|
|
|
|
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);
|
|
}
|
|
rpmtsiFree(pi);
|
|
|
|
rpmFpHashFree(symlinks);
|
|
}
|
|
|