Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37512520
en ru br
Репозитории ALT

Группа :: Система/Настройка/Пакеты
Пакет: rpmhdrcache

 Главная   Изменения   Спек   Патчи   Исходники   Загрузить   Gear   Bugs and FR  Repocop 

pax_global_header00006660000000000000000000000064116424775540014531gustar00rootroot0000000000000052 comment=e0c556dde0ce951644494dea8d98bb22be25a4ca
rpmhdrcache-0.3/000075500000000000000000000000001164247755400136515ustar00rootroot00000000000000rpmhdrcache-0.3/.gear/000075500000000000000000000000001164247755400146455ustar00rootroot00000000000000rpmhdrcache-0.3/.gear/rules000064400000000000000000000000421164247755400157160ustar00rootroot00000000000000tar: . name=rpmhdrcache-@version@
rpmhdrcache-0.3/cache.c000064400000000000000000000350421164247755400150640ustar00rootroot00000000000000#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/file.h>
#include <dirent.h>

#include <db.h>
#include "cache.h"

#if DB_VERSION_MAJOR < 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 4)
#error "berkeley db 4.4+ required"
#endif

struct cache {
DB_ENV *env;
DB *db;
int dirfd;
int pid;
sigset_t bset, oset;
unsigned umask, omask;
unsigned short now;
};

#define ERROR(fmt, args...) \
fprintf(stderr, "%s: %s: " fmt "\n", \
program_invocation_short_name, __func__, ##args)

#define BLOCK_SIGNALS(cache) \
if (sigprocmask(SIG_BLOCK, &cache->bset, &cache->oset)) \
ERROR("SIG_BLOCK: %m")
#define UNBLOCK_SIGNALS(cache) \
if (sigprocmask(SIG_SETMASK, &cache->oset, NULL)) \
ERROR("SIG_SETMASK: %m")

#define LOCK_DIR(cache, op) \
{ int rc_; \
do \
rc_ = flock(cache->dirfd, op); \
while (rc_ < 0 && errno == EINTR); \
if (rc_) \
ERROR("%s: %m", #op); \
}
#define UNLOCK_DIR(cache) \
if (flock(cache->dirfd, LOCK_UN)) \
ERROR("LOCK_UN: %m")

#define SET_UMASK(cache) \
cache->omask = umask(cache->umask)
#define UNSET_UMASK(cache) \
if (cache->omask != cache->umask) \
umask(cache->omask)

static
void errcall(const DB_ENV *env, const char *prefix, const char *msg)
{
(void) env;
(void) prefix;
ERROR("%s", msg);
}

static
void msgcall(const DB_ENV *env, const char *msg)
{
(void) env;
ERROR("%s", msg);
}

#include <openssl/sha.h>

// All user keys are hashed with sha1, and sha1 sum is then used as db key.
// To avoid double hashing, we also use part of sha1 sum as internal db hash.
static
unsigned h_hash(DB *db, const void *key, unsigned keysize)
{
(void) db;
if (keysize == 20)
return *(unsigned *) key;

// handle CHARKEY test string and possibly other data
unsigned char sha1[20] __attribute__((aligned(4)));
SHA1(key, keysize, sha1);
return *(unsigned *) sha1;
}

struct cache *cache_open(const char *dir)
{
int rc;

// allocate cache
struct cache *cache = malloc(sizeof(*cache) + strlen(dir));
if (cache == NULL) {
ERROR("malloc: %m");
return NULL;
}

// initialize signals which we will block
sigemptyset(&cache->bset);
sigaddset(&cache->bset, SIGHUP);
sigaddset(&cache->bset, SIGINT);
sigaddset(&cache->bset, SIGQUIT);
sigaddset(&cache->bset, SIGPIPE);
sigaddset(&cache->bset, SIGTERM);

// initialize timestamp
cache->now = time(NULL) / 3600 / 24;

// remember our process
cache->pid = getpid();

// allocate env
rc = db_env_create(&cache->env, 0);
if (rc) {
ERROR("env_create: %s", db_strerror(rc));
free(cache);
return NULL;
}

// configure env
cache->env->set_errcall(cache->env, errcall);
cache->env->set_msgcall(cache->env, msgcall);
cache->env->set_cachesize(cache->env, 0, 1 << 20, 1);

// open dir
cache->dirfd = open(dir, O_RDONLY | O_DIRECTORY);
if (cache->dirfd < 0) {
// probably ENOENT
ERROR("%s: %m", dir);
cache->env->close(cache->env, 0);
free(cache);
return NULL;
}

// initialize cache umask
struct stat st;
rc = fstat(cache->dirfd, &st);
if (rc < 0) {
ERROR("fstat: %m");
st.st_mode = 0755;
}
cache->umask = (~st.st_mode & 022);

// enter ciritical section
LOCK_DIR(cache, LOCK_EX);
BLOCK_SIGNALS(cache);
SET_UMASK(cache);

// open env
rc = (cache->env->open)(cache->env, dir,
DB_CREATE | DB_INIT_MPOOL, 0666);
if (rc) {
ERROR("env_open %s: %s", dir, db_strerror(rc));
undo:
// env->close combines both close() and free()
// in undo, we should close while in critical section
cache->env->close(cache->env, 0);
// leave critical section
UNSET_UMASK(cache);
UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);
// clean up and return
close(cache->dirfd);
free(cache);
return NULL;
}

// allocate db
rc = db_create(&cache->db, cache->env, 0);
if (rc) {
ERROR("db_create: %s", db_strerror(rc));
goto undo;
}

// configure db
cache->db->set_h_hash(cache->db, h_hash);

// open db - this is the final goal
rc = cache->db->open(cache->db, NULL, "cache.db", NULL,
DB_HASH, DB_CREATE, 0666);
if (rc) {
ERROR("db_open: %s", db_strerror(rc));
cache->db->close(cache->db, 0);
goto undo;
}

// leave critical section
UNSET_UMASK(cache);
UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);

return cache;
}

void cache_close(struct cache *cache)
{
if (cache == NULL)
return;

// don't close after fork
if (cache->pid != getpid())
return;

int rc;

LOCK_DIR(cache, LOCK_EX);
BLOCK_SIGNALS(cache);

// close db
rc = cache->db->close(cache->db, 0);
if (rc)
ERROR("db_close: %s", db_strerror(rc));

// close env
rc = cache->env->close(cache->env, 0);
if (rc)
ERROR("env_close: %s", db_strerror(rc));

UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);

close(cache->dirfd);
free(cache);
}

// Convert 20-byte sha1 to "XX/YYY..." filename.
static
void sha1_filename(const unsigned char *sha1, char *fname, bool tmp)
{
static const char hex[] = "0123456789abcdef";
inline
void sha1_byte()
{
*fname++ = hex[*sha1 & 0x0f];
*fname++ = hex[*sha1++ >> 4];
}
sha1_byte();
*fname++ = '/';
int i;
for (i = 1; i < 20; i++)
sha1_byte();
if (tmp) {
*fname++ = '.';
int rnd = rand();
sha1 = (const unsigned char *) &rnd;
for (i = 0; i < 4; i++)
sha1_byte();
}
*fname = '\0';
}

// Fast compression and decompression from Google.
#include <snappy-c.h>

// Values with compressed size larger than this will be backed by fs.
#define MAX_DB_VAL_SIZE (32 << 10)

struct cache_ent {
#define V_SNAPPY (1 << 0)
unsigned short flags;
unsigned short mtime;
unsigned short atime;
unsigned short pad;
};

// Will use mmap for fs get and fs put.
#include <sys/mman.h>

bool cache_get(struct cache *cache,
const void *key, int keysize,
void **valp, int *valsizep)
{
if (valp)
*valp = NULL;
if (valsizep)
*valsizep = 0;

unsigned char sha1[20] __attribute__((aligned(4)));
SHA1(key, keysize, sha1);
DBT k = { sha1, 20 };

char vbuf[sizeof(struct cache_ent) + MAX_DB_VAL_SIZE] __attribute__((aligned(4)));
DBT v = { vbuf, 0 };
v.ulen = sizeof(vbuf);
v.flags |= DB_DBT_USERMEM;

struct cache_ent *vent = (void *) vbuf;
int ventsize = 0;

// read lock
LOCK_DIR(cache, LOCK_SH);

// db->get can trigger mpool->put
BLOCK_SIGNALS(cache);

int rc = cache->db->get(cache->db, NULL, &k, &v, 0);

UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);

if (rc == 0)
ventsize = v.size;

if (rc && rc != DB_NOTFOUND)
ERROR("db_get: %s", db_strerror(rc));

if (rc) {
// fs get
char fname[42];
sha1_filename(sha1, fname, false);
int fd = openat(cache->dirfd, fname, O_RDONLY);
if (fd < 0) {
if (errno != ENOENT)
ERROR("openat: %m");
return false;
}
struct stat st;
rc = fstat(fd, &st);
if (rc < 0) {
ERROR("fstat: %m");
return false;
}
ventsize = st.st_size;
vent = mmap(NULL, ventsize, PROT_READ, MAP_SHARED, fd, 0);
if (vent == MAP_FAILED) {
ERROR("mmap: %m");
close(fd);
return false;
}
close(fd);
}

// validate
if (ventsize < sizeof(*vent)) {
ERROR("vent too small");
munmap:
if (vent != (void *) vbuf) {
rc = munmap(vent, ventsize);
if (rc < 0)
ERROR("munmap: %m");
}
return false;
}

// update db atime
if (vent == (void *) vbuf && vent->atime < cache->now) {
LOCK_DIR(cache, LOCK_EX);
BLOCK_SIGNALS(cache);

// partial update, user data unchanged
v.flags |= DB_DBT_PARTIAL;
v.dlen = sizeof(*vent);

// the record must be still there
rc = cache->db->get(cache->db, NULL, &k, &v, DB_GET_BOTH);
if (rc) {
UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);
if (rc != DB_NOTFOUND)
ERROR("db_get: %s", db_strerror(rc));
}
else {
// actual update
v.size = sizeof(*vent);
vent->atime = cache->now;
rc = cache->db->put(cache->db, NULL, &k, &v, 0);
UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);
if (rc)
ERROR("db_put: %s", db_strerror(rc));
}
}

// prepare for return
if (vent->flags & V_SNAPPY) {
// uncompress
int csize = ventsize - sizeof(*vent);
size_t usize;
if (snappy_uncompressed_length(vent + 1, csize, &usize)) {
ERROR("snappy_uncompressed_length: invalid data");
goto munmap;
}
if (valp) {
if ((*valp = malloc(usize + 1)) == NULL) {
ERROR("malloc: %m");
goto munmap;
}
if (snappy_uncompress(vent + 1, csize, *valp, &usize)) {
ERROR("snappy_uncompress: invalid data");
free(*valp);
*valp = NULL;
goto munmap;
}
((char *) *valp)[usize] = '\0';
}
else {
if (snappy_validate_compressed_buffer(vent + 1, csize)) {
ERROR("snappy_validate_compressed_buffer: invalid data");
goto munmap;
}
}
if (valsizep)
*valsizep = usize;
}
else {
int size = ventsize - sizeof(*vent);
if (size && valp) {
if ((*valp = malloc(size + 1)) == NULL) {
ERROR("malloc: %m");
goto munmap;
}
memcpy(*valp, vent + 1, size);
((char *) *valp)[size] = '\0';
}
if (valsizep)
*valsizep = size;
}

if (vent != (void *) vbuf) {
rc = munmap(vent, ventsize);
if (rc < 0)
ERROR("munmap: %m");
}

return true;
}

void cache_put(struct cache *cache,
const void *key, int keysize,
const void *val, int valsize)
{
int rc;

unsigned char sha1[20] __attribute__((aligned(4)));
SHA1(key, keysize, sha1);

int max_csize = snappy_max_compressed_length(valsize);
struct cache_ent *vent = malloc(sizeof(*vent) + max_csize);
if (vent == NULL) {
ERROR("malloc: %m");
return;
}
vent->flags = 0;
vent->atime = 0;
vent->mtime = 0;
vent->pad = 0;
int ventsize;
size_t csize = max_csize;
if (snappy_compress(val, valsize, vent + 1, &csize)) {
ERROR("snappy_compress: error");
uncompressed:
memcpy(vent + 1, val, valsize);
ventsize = sizeof(*vent) + valsize;
}
else if (csize >= valsize)
goto uncompressed;
else {
vent->flags |= V_SNAPPY;
ventsize = sizeof(*vent) + csize;
}

if (ventsize - sizeof(*vent) <= MAX_DB_VAL_SIZE) {
// db put
DBT k = { sha1, 20 };
DBT v = { vent, ventsize };
vent->mtime = cache->now;
vent->atime = cache->now;

LOCK_DIR(cache, LOCK_EX);
BLOCK_SIGNALS(cache);

rc = cache->db->put(cache->db, NULL, &k, &v, 0);

UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);

if (rc)
ERROR("db_put: %s", db_strerror(rc));

free(vent);
return;
}
else {
// db del
DBT k = { sha1, 20 };

LOCK_DIR(cache, LOCK_EX);
BLOCK_SIGNALS(cache);

rc = cache->db->del(cache->db, NULL, &k, 0);

UNBLOCK_SIGNALS(cache);
UNLOCK_DIR(cache);

if (rc && rc != DB_NOTFOUND)
ERROR("db_del: %s", db_strerror(rc));
}

// fs put: open tmp file
char fname[51];
sha1_filename(sha1, fname, true);
fname[2] = '\0';
SET_UMASK(cache);
rc = mkdirat(cache->dirfd, fname, 0777);
if (rc < 0 && errno != EEXIST)
ERROR("mkdirat: %m");
fname[2] = '/';
int fd = openat(cache->dirfd, fname, O_RDWR | O_CREAT | O_EXCL, 0666);
if (fd < 0) {
ERROR("openat: %m");
UNSET_UMASK(cache);
free(vent);
return;
}
UNSET_UMASK(cache);

// extend and mmap for write
rc = ftruncate(fd, ventsize);
if (rc < 0) {
ERROR("ftruncate: %m");
close(fd);
free(vent);
return;
}
void *dest = mmap(NULL, ventsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (dest == MAP_FAILED) {
ERROR("mmap: %m");
close(fd);
free(vent);
return;
}
close(fd);

// write data
memcpy(dest, vent, ventsize);
rc = munmap(dest, ventsize);
if (rc < 0)
ERROR("munmap: %m");
free(vent);

// move to permanent location
char outfname[42];
memcpy(outfname, fname, 41);
outfname[41] = '\0';
rc = renameat(cache->dirfd, fname, cache->dirfd, outfname);
if (rc < 0)
ERROR("renameat: %m");
}

void cache_clean(struct cache *cache, int days)
{
if (days < 1) {
ERROR("days must be greater than 0, got %d", days);
return;
}

int rc;

// db clean
LOCK_DIR(cache, LOCK_EX);

DBC *dbc;
BLOCK_SIGNALS(cache);
rc = cache->db->cursor(cache->db, NULL, &dbc, 0);
UNBLOCK_SIGNALS(cache);

if (rc) {
UNLOCK_DIR(cache);
ERROR("db_cursor: %s", db_strerror(rc));
return;
}

while (1) {
unsigned char sha1[20] __attribute__((aligned(4)));
DBT k = { sha1, 0 };
k.ulen = sizeof(sha1);
k.flags |= DB_DBT_USERMEM;

struct cache_ent vbuf, *vent = &vbuf;
DBT v = { &vbuf, 0 };
v.ulen = sizeof(vbuf);
v.dlen = sizeof(vbuf);
v.flags |= DB_DBT_USERMEM;
v.flags |= DB_DBT_PARTIAL;

BLOCK_SIGNALS(cache);
rc = dbc->get(dbc, &k, &v, DB_NEXT);
UNBLOCK_SIGNALS(cache);

if (rc) {
if (rc != DB_NOTFOUND)
ERROR("dbc_get: %s", db_strerror(rc));
break;
}

if (k.size < sizeof(sha1)) {
ERROR("sha1 too small");
continue;
}

if (v.size < sizeof(*vent)) {
ERROR("vent too small");
continue;
}

if (vent->mtime + days >= cache->now) continue;
if (vent->atime + days >= cache->now) continue;

BLOCK_SIGNALS(cache);
rc = dbc->del(dbc, 0);
UNBLOCK_SIGNALS(cache);

if (rc)
ERROR("dbc_del: %s", db_strerror(rc));
}

BLOCK_SIGNALS(cache);
rc = dbc->close(dbc);
UNBLOCK_SIGNALS(cache);

if (rc)
ERROR("dbc_close: %s", db_strerror(rc));

UNLOCK_DIR(cache);

// fs clean
static const char hex[] = "0123456789abcdef";
const char *a1, *a2;
for (a1 = hex; *a1; a1++)
for (a2 = hex; *a2; a2++) {
const char dir[] = { *a1, *a2, '\0' };
int dirfd = openat(cache->dirfd, dir, O_RDONLY | O_DIRECTORY);
if (dirfd < 0) {
if (errno != ENOENT)
ERROR("openat: %m");
continue;
}

DIR *dirp = fdopendir(dirfd);
if (dirp == NULL) {
ERROR("fdopendir: %m");
close(dirfd);
continue;
}

struct dirent *dent;
while ((dent = readdir(dirp)) != NULL) {
int len = strlen(dent->d_name);
if (len < 38)
continue;

struct stat st;
rc = fstatat(dirfd, dent->d_name, &st, 0);
if (rc) {
ERROR("fstatat: %m");
continue;
}

short mtime = st.st_mtime / 3600 / 24;
short atime = st.st_atime / 3600 / 24;
if (len == 38) {
if (mtime + days >= cache->now) continue;
if (atime + days >= cache->now) continue;
}
else {
// stale temporary files?
if (mtime + 1 >= cache->now) continue;
if (atime + 1 >= cache->now) continue;
}

rc = unlinkat(dirfd, dent->d_name, 0);
if (rc)
ERROR("unlinkat: %m");
}

rc = closedir(dirp);
if (rc)
ERROR("closedir: %m");
}
}

// ex:ts=8 sts=4 sw=4 noet
rpmhdrcache-0.3/cache.h000064400000000000000000000020721164247755400150660ustar00rootroot00000000000000#ifndef QA_CACHE_H
#define QA_CACHE_H

#ifdef __cplusplus
extern "C" {
#endif

struct cache *cache_open(const char *dir);
void cache_clean(struct cache *cache, int days);
void cache_close(struct cache *cache);

#ifndef __cplusplus
#include <stdbool.h>
#endif

/*
* Note that it is possible to store empty values by specifying valsize = 0
* argument to cache_put (val is ignored in this case). When fetching empty
* value, cache_get returns true with *valsizep set to 0 and *valp set to NULL.
*
* Otherwise, when fetching non-empty values, cache_get will implicitly add
* trailing null byte past the end of the value. This is useful when values
* are strings: *valsizep will be set to strlen and *valp can be immediately
* used as a null-terminated string. Note that the null byte itself is not
* stored in the cache.
*/

bool cache_get(struct cache *cache,
const void *key, int keysize,
void **valp /* malloc'd */, int *valsizep);
void cache_put(struct cache *cache,
const void *key, int keysize,
const void *val, int valsize);

#ifdef __cplusplus
}
#endif

#endif
rpmhdrcache-0.3/clean.c000064400000000000000000000012531164247755400151000ustar00rootroot00000000000000#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "cache.h"

int main(int argc, const char *argv[])
{
if (argc < 3) {
usage:
fprintf(stderr, "Usage: %s DAYS DIR...\n",
program_invocation_short_name);
return 2;
}
int days = atoi(argv[1]);
if (days < 1)
goto usage;
int rc = 0;
int i;
for (i = 2; i < argc; i++) {
const char *dir = argv[i];
struct cache *cache = cache_open(dir);
if (!cache) {
// warning issued by the library
rc = 1;
continue;
}
cache_clean(cache, days);
cache_close(cache);
}
return rc;
}

// ex: set ts=8 sts=4 sw=4 noet:
rpmhdrcache-0.3/hdrcache.c000064400000000000000000000051521164247755400155610ustar00rootroot00000000000000#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <rpm/rpmlib.h>
#include "cache.h"
#include "hdrcache.h"

static __thread
struct cache *cache;

static
void finalize()
{
cache_close(cache);
}

static inline
const char *opt_(const char *name)
{
const char *str = getenv(name);
return (str && *str) ? str : NULL;
}

#define opt(name) opt_("RPMHDRCACHE_" name)

static
int initialize()
{
static __thread
int initialized;
if (initialized)
return initialized;
if (opt("DISABLE")) {
initialized = -1;
return initialized;
}
const char *dir = opt("DIR");
if (dir == NULL)
dir = "/tmp/.rpmhdrcache";
cache = cache_open(dir);
if (cache == NULL) {
initialized = -1;
return initialized;
}
initialized = 1;
atexit(finalize);
return initialized;
}

static
int make_key(const char *path, const struct stat *st, char *key)
{
const char *bn = strrchr(path, '/');
bn = bn ? (bn + 1) : path;
strcpy(key, bn);
unsigned sm[2] = { st->st_size, st->st_mtime };
int len = strlen(bn);
memcpy(key + len + 1, sm, sizeof sm);
return len + 1 + sizeof sm;
}

#define ERROR(fmt, args...) \
fprintf(stderr, "%s: %s: " fmt "\n", \
program_invocation_short_name, __func__, ##args)

struct cache_ent {
unsigned off;
char blob[1];
};

Header hdrcache_get(const char *path, const struct stat *st, unsigned *off)
{
if (initialize() < 0)
return NULL;
char key[4096];
int keysize = make_key(path, st, key);
struct cache_ent *data;
int datasize;
if (!cache_get(cache, key, keysize, (void **) &data, &datasize))
return NULL;
Header h = headerCopyLoad(data->blob);
if (h == NULL) {
ERROR("%s: headerLoad failed", key);
return NULL;
}
if (off)
*off = data->off;
free(data);
return h;
}

void hdrcache_put(const char *path, const struct stat *st, Header h, unsigned off)
{
if (initialize() < 0)
return;
char key[4096];
int keysize = make_key(path, st, key);
int blobsize = headerSizeof(h, HEADER_MAGIC_NO);
void *blob = headerUnload(h);
if (blob == NULL) {
ERROR("%s: headerLoad failed", key);
return;
}
int datasize = sizeof(unsigned) + blobsize;
struct cache_ent *data = malloc(datasize);
if (data == NULL) {
ERROR("malloc: %m");
return;
}
data->off = off;
memcpy(data->blob, blob, blobsize);
free(blob);
cache_put(cache, key, keysize, data, datasize);
free(data);
}

// ex: set ts=8 sts=4 sw=4 noet:
rpmhdrcache-0.3/hdrcache.h000064400000000000000000000002411164247755400155600ustar00rootroot00000000000000Header hdrcache_get(const char *path, const struct stat *st, unsigned *off);
void hdrcache_put(const char *path, const struct stat *st, Header h, unsigned off);
rpmhdrcache-0.3/preload.c000064400000000000000000000052751164247755400154540ustar00rootroot00000000000000#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <rpm/rpmio.h>
#include <rpm/rpmlib.h>

static inline
bool endswith(const char *str, const char *suffix)
{
size_t len1 = strlen(str);
size_t len2 = strlen(suffix);
if (len1 < len2)
return false;
str += (len1 - len2);
if (strcmp(str, suffix))
return false;
return true;
}

static __thread
const char *last_path;

static __thread
FD_t last_fd;

static __thread
struct stat last_st;

FD_t Fopen(const char *path, const char *fmode)
{
last_path = _free(last_path);
last_fd = NULL;
static __thread
FD_t (*Fopen_next)(const char *path, const char *fmode);
if (Fopen_next == NULL) {
Fopen_next = dlsym(RTLD_NEXT, __func__);
assert(Fopen_next);
}
FD_t fd = Fopen_next(path, fmode);
if (fd && endswith(path, ".rpm") && *fmode == 'r' &&
stat(path, &last_st) == 0 && S_ISREG(last_st.st_mode))
{
last_path = strdup(path);
if (last_path)
last_fd = fd;
}
return fd;
}

#include "hdrcache.h"

rpmRC rpmReadPackageHeader(FD_t fd, Header *hdrp,
int *isSource, int *major, int *minor)
{
Header hdr_;
int isSource_, major_, minor_;
if (hdrp == NULL)
hdrp = &hdr_;
if (isSource == NULL)
isSource = &isSource_;
if (major == NULL)
major = &major_;
if (minor == NULL)
minor = &minor_;
static __thread
rpmRC (*rpmReadPackageHeader_next)(FD_t fd, Header *hdrp,
int *isSource, int *major, int *minor);
if (rpmReadPackageHeader_next == NULL) {
rpmReadPackageHeader_next = dlsym(RTLD_NEXT, __func__);
assert(rpmReadPackageHeader_next);
}
struct stat st;
bool match =
fd && fd == last_fd &&
stat(last_path, &st) == 0 && S_ISREG(st.st_mode) &&
st.st_dev == last_st.st_dev && st.st_ino == last_st.st_ino &&
st.st_size == last_st.st_size && st.st_mtime == last_st.st_mtime;
if (match) {
unsigned off;
*hdrp = hdrcache_get(last_path, &st, &off);
if (*hdrp) {
int pos = lseek(Fileno(fd), off, SEEK_SET);
if (pos != off)
*hdrp = headerFree(*hdrp);
else {
*isSource = !headerIsEntry(*hdrp, RPMTAG_SOURCERPM);
*major = 3;
*minor = 0;
return RPMRC_OK;
}
}
}
rpmRC rc = rpmReadPackageHeader_next(fd, hdrp, isSource, major, minor);
if (match) {
if (rc == RPMRC_OK && *major == 3 && *minor == 0) {
int realSource = !headerIsEntry(*hdrp, RPMTAG_SOURCERPM);
if (realSource == *isSource) {
int pos = lseek(Fileno(fd), 0, SEEK_CUR);
if (pos > 0)
hdrcache_put(last_path, &st, *hdrp, pos);
}
}
}
return rc;
}

// ex: set ts=8 sts=4 sw=4 noet:
rpmhdrcache-0.3/rpmhdrcache.spec000064400000000000000000000066661164247755400170230ustar00rootroot00000000000000Name: rpmhdrcache
Version: 0.3
Release: alt1

Summary: Cached reading of rpm package headers
License: GPLv2+
Group: System/Configuration/Packaging

URL: http://git.altlinux.org/gears/r/rpmhdrcache.git
Source: %name-%version.tar

Requires: libqacache = %version-%release

# Automatically added by buildreq on Thu Jun 23 2011
BuildRequires: libdb4-devel librpm-devel libsnappy-devel libssl-devel

%description
Sisyphus repository currently has more than 10K source packages (which is
more than 60K rpm files with subpackages). To assist repeated repo scanning
(which is required for each repo update), this package provides rpmhdrcache.so
perloadable module. This module intercepts rpmReadPackageHeader calls and
caches the result using libqacache library.

%prep
%setup -q

%build
gcc -shared -fPIC -D_GNU_SOURCE %optflags -o libqacache.so.0 -Wl,-soname,libqacache.so.0 cache.c \
-ldb -lcrypto -lsnappy -Wl,-z,defs
gcc -shared -fPIC -D_GNU_SOURCE %optflags -o rpmhdrcache.so preload.c hdrcache.c \
-Wl,--no-as-needed -lrpmio -lrpm -Wl,--as-needed -lrpmdb -ldl libqacache.so.0 -Wl,-z,defs
gcc -D_GNU_SOURCE %optflags -o qacache-clean clean.c libqacache.so.0

%install
install -pD -m644 cache.h %buildroot%_includedir/qa/cache.h
install -pD -m644 libqacache.so.0 %buildroot%_libdir/libqacache.so.0
ln -s libqacache.so.0 %buildroot%_libdir/libqacache.so
install -pD -m644 rpmhdrcache.so %buildroot%_libdir/rpmhdrcache.so
install -pD -m755 qacache-clean %buildroot%_bindir/qacache-clean

%files
%_libdir/rpmhdrcache.so

%package -n libqacache
Summary: NoSQL solution for data caching
Group: System/Libraries

%package -n libqacache-devel
Summary: NoSQL solution for data caching
Group: Development/C
Requires: libqacache = %version-%release

%description -n libqacache
This library implements simple key-value cache API with limited support
for concurrent reads, atomic writes, data integrity, and atime cleanup.
Small- to medium-sized cache entries (up to 32K compressed with snappy)
are stored in a Berkeley DB, larger entries are backed by filesystem.

%description -n libqacache-devel
This library implements simple key-value cache API with limited support
for concurrent reads, atomic writes, data integrity, and atime cleanup.
Small- to medium-sized cache entries (up to 32K compressed with snappy)
are stored in a Berkeley DB, larger entries are backed by filesystem.

%files -n libqacache
%_libdir/libqacache.so.0
%_bindir/qacache-clean

%files -n libqacache-devel
%dir %_includedir/qa
%_includedir/qa/cache.h
%_libdir/libqacache.so

%changelog
* Tue Oct 04 2011 Alexey Tourbin <at@altlinux.ru> 0.3-alt1
- cache.c: implemented db and fs atime cleanup
- clean.c: implemented /usr/bin/qacache-clean helper program

* Thu Sep 08 2011 Alexey Tourbin <at@altlinux.ru> 0.2.3-alt1
- don't close cache db in child processes

* Wed Sep 07 2011 Alexey Tourbin <at@altlinux.ru> 0.2.2-alt1
- implemented db atime update using DB_DBT_PARTIAL

* Mon Jun 27 2011 Alexey Tourbin <at@altlinux.ru> 0.2.1-alt1
- when fetching empty value, cache_get will set *valp to NULL
- otherwise, trailing null byte will be added past the end of value

* Fri Jun 24 2011 Alexey Tourbin <at@altlinux.ru> 0.2-alt1
- version 0.1 was not released properly
- factored libqacache library (similar to qa::cache perl module)
- replaced LZO compression with snappy
- replaced CDS with dir flock
- replaced Btree with SHA1-based hash DB
- implemented filesystem-backed store for large cache entries
- atime update and cleanup not cooked up yet
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin