616 lines
15 KiB
C
616 lines
15 KiB
C
/* NeoStats - IRC Statistical Services
|
|
** Copyright (c) 1999-2004 Adam Rutter, Justin Hammond
|
|
** http://www.neostats.net/
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
** USA
|
|
**
|
|
** NeoStats CVS Identification
|
|
** $Id$
|
|
*/
|
|
/*
|
|
* KEEPER: A configuration reading and writing library
|
|
*
|
|
* Copyright (C) 1999-2000 Miklos Szeredi
|
|
* Email: mszeredi@inf.bme.hu
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the Free
|
|
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
|
* MA 02111-1307, USA
|
|
*/
|
|
|
|
#include "kp_util.h"
|
|
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
/* One physical file's cached data */
|
|
typedef struct _kp_fil {
|
|
kp_path kpp;
|
|
kp_key *keys;
|
|
int dirty;
|
|
time_t modif;
|
|
time_t use;
|
|
time_t check;
|
|
struct _kp_fil *next;
|
|
} kp_fil;
|
|
|
|
/* The cache */
|
|
static kp_fil *kp_cachef = NULL;
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Free a key list
|
|
* ------------------------------------------------------------------------- */
|
|
static void kp_free_keys(kp_key * k)
|
|
{
|
|
kp_key *nextk;
|
|
|
|
while (k != NULL) {
|
|
nextk = k->next;
|
|
kp_value_destroy(k);
|
|
free(k);
|
|
k = nextk;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* If the file is in the cache, return it, otherwise return NULL
|
|
* ------------------------------------------------------------------------- */
|
|
static kp_fil *kp_get_cached_file(kp_path * kpp)
|
|
{
|
|
kp_fil *fil;
|
|
char *path = kpp->path;
|
|
|
|
for (fil = kp_cachef; fil != NULL; fil = fil->next)
|
|
if (strcmp(fil->kpp.path, path) == 0) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_cached_file: %s\n", fil->kpp.path);
|
|
#endif
|
|
break;
|
|
|
|
}
|
|
return fil;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Create a new chaced file, and insert it into the cache
|
|
* ------------------------------------------------------------------------- */
|
|
static kp_fil *kp_new_cached_file(kp_path * kpp)
|
|
{
|
|
kp_fil *fil;
|
|
|
|
fil = (kp_fil *) malloc_check(sizeof(kp_fil));
|
|
fil->kpp.path = strdup_check(kpp->path);
|
|
fil->kpp.dbindex = kpp->dbindex;
|
|
fil->keys = NULL;
|
|
fil->dirty = 0;
|
|
fil->modif = 0;
|
|
fil->use = 0;
|
|
fil->check = 0;
|
|
fil->next = kp_cachef;
|
|
kp_cachef = fil;
|
|
#ifdef KPDEBUG
|
|
printf("kp_new_cached_file: %s\n", fil->kpp.path);
|
|
#endif
|
|
|
|
return fil;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Free data associated with a cached file
|
|
* ------------------------------------------------------------------------- */
|
|
static void kp_free_file(kp_fil * fil)
|
|
{
|
|
#ifdef KPDEBUG
|
|
printf("kp_free_file: %s\n", fil->kpp.path);
|
|
#endif
|
|
free(fil->kpp.path);
|
|
kp_free_keys(fil->keys);
|
|
free(fil);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Remove a file from a cache and free it
|
|
* ------------------------------------------------------------------------- */
|
|
static void kp_remove_file_from_cache(kp_fil * rfil)
|
|
{
|
|
kp_fil **filp;
|
|
|
|
for (filp = &kp_cachef; *filp != NULL; filp = &(*filp)->next) {
|
|
if (*filp == rfil) {
|
|
*filp = rfil->next;
|
|
#ifdef KPDEBUG
|
|
printf("kp_remove_file_from_cache: %s\n", rfil->kpp.path);
|
|
#endif
|
|
kp_free_file(rfil);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Should never happen */
|
|
fprintf(stderr, "keeper: Internal Error: Corrupted cache data\n");
|
|
abort();
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Remove all files from the cache
|
|
* ------------------------------------------------------------------------- */
|
|
void _kp_clear_cache(void)
|
|
{
|
|
#ifdef KPDEBUG
|
|
printf("kp_clear_cache\n");
|
|
#endif
|
|
while (kp_cachef != NULL)
|
|
kp_remove_file_from_cache(kp_cachef);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Remove all old, non-dirty files from the cache
|
|
* ------------------------------------------------------------------------- */
|
|
static void kp_clean_up_cache(time_t now)
|
|
{
|
|
kp_fil **filp;
|
|
kp_fil *fil;
|
|
time_t expire;
|
|
|
|
expire = now - 10;
|
|
|
|
for (filp = &kp_cachef; *filp != NULL;) {
|
|
fil = *filp;
|
|
if (fil->use < expire && !fil->dirty) {
|
|
*filp = fil->next;
|
|
#ifdef KPDEBUG
|
|
printf("kp_clean_up_cache: %d < %d (Dirty: %d): %s\n", fil->use, expire, fil->dirty, fil->kpp.path);
|
|
#endif
|
|
kp_free_file(fil);
|
|
} else
|
|
filp = &(*filp)->next;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* If the key is in the cached file, return it, otherwise return NULL
|
|
* ------------------------------------------------------------------------- */
|
|
static kp_key *kp_get_cached_key(kp_fil * fil, const char *name)
|
|
{
|
|
kp_key *key;
|
|
|
|
for (key = fil->keys; key != NULL; key = key->next)
|
|
if (strcmp(key->name, name) == 0) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_cached_file: %s-%s\n", fil->kpp.path, name);
|
|
#endif
|
|
break;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Find a key in a cached file. If it exists return it. If there exists a
|
|
* subkey of it, or it is a subkey of one, then return an error
|
|
* ------------------------------------------------------------------------- */
|
|
static int kp_check_insert(kp_fil * fil, char *name, kp_key ** keyretp)
|
|
{
|
|
kp_key *key;
|
|
|
|
for (key = fil->keys; key != NULL; key = key->next) {
|
|
if (strcmp(key->name, name) == 0) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_check_insert: Got key %s-%s\n", fil->kpp.path, name);
|
|
#endif
|
|
break;
|
|
}
|
|
if (kp_is_subkey(name, key->name)
|
|
|| kp_is_subkey(key->name, name))
|
|
return KPERR_BADKEY;
|
|
}
|
|
#ifdef KPDEBUG
|
|
printf("kp_check_insert: Got it\n");
|
|
#endif
|
|
*keyretp = key;
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* This function returns a cached file. If the file was not in the cache or
|
|
* was outdated, then it reads it into the cache. If the path was refering
|
|
* to a directory, then it returns this in the *isdirp flag.
|
|
* ------------------------------------------------------------------------- */
|
|
static int kp_get_file(kp_path * kpp, kp_fil ** fp, int *isdirp)
|
|
{
|
|
kp_fil *fil;
|
|
int res;
|
|
struct stat stbuf;
|
|
time_t now;
|
|
|
|
*fp = NULL;
|
|
*isdirp = 0;
|
|
|
|
fil = kp_get_cached_file(kpp);
|
|
now = time(NULL);
|
|
|
|
/* This check-magic is needed, because stat()-ing nonexistent
|
|
files on NFS seems to be very expensive on some (Solaris)
|
|
architectures */
|
|
|
|
/* If the file was checked less then one second ago, then we
|
|
believe it hasn't changed. */
|
|
|
|
/* FIXME: even if the file is dirty, the non-dirty keys should be
|
|
updated from a changed file. This is not very important
|
|
though. */
|
|
|
|
if (fil != NULL && !fil->dirty && now != fil->check) {
|
|
res = stat(kpp->path, &stbuf);
|
|
|
|
if (fil->keys == NULL && res == -1 && errno == ENOENT) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_file: does not exist: %s\n",kpp->path);
|
|
#endif
|
|
fil->check = now;
|
|
} else if (res == -1 || !S_ISREG(stbuf.st_mode) ||
|
|
stbuf.st_mtime != fil->modif) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_file: not regular file: %s\n", kpp->path);
|
|
#endif
|
|
kp_remove_file_from_cache(fil);
|
|
fil = NULL;
|
|
} else {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_file: ok %s\n", kpp->path);
|
|
#endif
|
|
fil->check = now;
|
|
}
|
|
}
|
|
|
|
if (fil != NULL) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_file: existing file\n");
|
|
#endif
|
|
fil->use = now;
|
|
} else {
|
|
kp_key *keys = NULL;
|
|
|
|
/* Clean up old cached files */
|
|
kp_clean_up_cache(now);
|
|
|
|
res = stat(kpp->path, &stbuf);
|
|
if (res != 0) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_file: stat failed\n");
|
|
#endif
|
|
res = _kp_errno_to_kperr(errno);
|
|
} else if (!S_ISREG(stbuf.st_mode)) {
|
|
res = KPERR_BADKEY;
|
|
if (S_ISDIR(stbuf.st_mode))
|
|
*isdirp = 1;
|
|
} else {
|
|
#ifdef KPDEBUG
|
|
printf("kp_get_file: kp_read_file called\n");
|
|
#endif
|
|
res = _kp_read_file(kpp->path, &keys);
|
|
if (res == 0) {
|
|
res = stat(kpp->path, &stbuf);
|
|
if (res == -1) {
|
|
kp_free_keys(keys);
|
|
res = _kp_errno_to_kperr(errno);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We cache nonexistent files also */
|
|
if (res != 0 && res != KPERR_NOKEY)
|
|
return res;
|
|
|
|
fil = kp_new_cached_file(kpp);
|
|
now = time(NULL);
|
|
|
|
fil->use = now;
|
|
fil->check = now;
|
|
|
|
if (res == 0) {
|
|
fil->keys = keys;
|
|
fil->modif = stbuf.st_mtime;
|
|
}
|
|
}
|
|
*fp = fil;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Set a key in the cache. If it exists overwrite it, if not, then create
|
|
* it. Removed (negative) keys are also inserted into the cache.
|
|
* ------------------------------------------------------------------------- */
|
|
int _kp_cache_set(kp_path * kpp, kp_key * ck)
|
|
{
|
|
kp_fil *fil;
|
|
kp_key *key;
|
|
int res;
|
|
int isdir;
|
|
|
|
res = kp_get_file(kpp, &fil, &isdir);
|
|
if (res != 0)
|
|
return res;
|
|
|
|
if ((ck->flags & KPFL_REMOVED) != 0) {
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_set: get cached key %s\n", kpp->path);
|
|
#endif
|
|
key = kp_get_cached_key(fil, ck->name);
|
|
|
|
if (key == NULL)
|
|
return KPERR_NOKEY;
|
|
}
|
|
|
|
if (!(ck->flags & KPFL_REMOVED)) {
|
|
#ifdef KPDEBUG
|
|
printf("Kp_cache_set: kp_check_insert %s\n", kpp->path);
|
|
#endif
|
|
res = kp_check_insert(fil, ck->name, &key);
|
|
if (res != 0)
|
|
return res;
|
|
}
|
|
|
|
fil->dirty = 1;
|
|
|
|
if (key != NULL)
|
|
kp_value_destroy(key);
|
|
else {
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_set: create new %s\n", kpp->path);
|
|
#endif
|
|
key = (kp_key *) malloc_check(sizeof(kp_key));
|
|
key->next = fil->keys;
|
|
fil->keys = key;
|
|
}
|
|
|
|
key->type = ck->type;
|
|
key->len = ck->len;
|
|
key->data = ck->data;
|
|
key->name = ck->name;
|
|
key->flags = ck->flags;
|
|
key->flags |= KPFL_DIRTY;
|
|
|
|
/* Data is MOVED to key, not COPIED */
|
|
ck->data = NULL;
|
|
ck->name = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Get the value of a key from a given section
|
|
* ------------------------------------------------------------------------- */
|
|
int _kp_cache_get(kp_path * kpp, const char *keyname, kpval_t type,
|
|
kp_key * ck)
|
|
{
|
|
kp_fil *fil;
|
|
kp_key *key;
|
|
int res;
|
|
int isdir;
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_get\n");
|
|
#endif
|
|
|
|
res = kp_get_file(kpp, &fil, &isdir);
|
|
if (res != 0)
|
|
return res;
|
|
|
|
key = kp_get_cached_key(fil, keyname);
|
|
|
|
if (key == NULL || key->flags & KPFL_REMOVED)
|
|
return KPERR_NOKEY;
|
|
|
|
if (key->flags & KPFL_BADDB)
|
|
return KPERR_BADDB;
|
|
|
|
if (type != KPVAL_UNKNOWN && key->type != type)
|
|
return KPERR_BADTYPE;
|
|
|
|
/* Data is COPIED */
|
|
kp_value_new(ck, key->type, key->len, key->data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Get the type of a key from a given section
|
|
* ------------------------------------------------------------------------- */
|
|
int _kp_cache_get_type(kp_path * kpp, char *keyname, int iskeyfile,
|
|
kpval_t * tp)
|
|
{
|
|
kp_fil *fil;
|
|
kp_key *key;
|
|
int res;
|
|
unsigned int keynamelen;
|
|
int isdir;
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_get_type\n");
|
|
#endif
|
|
|
|
res = kp_get_file(kpp, &fil, &isdir);
|
|
if (isdir) {
|
|
if (iskeyfile)
|
|
return KPERR_BADKEY;
|
|
else {
|
|
*tp = KPVAL_DIR;
|
|
return 0;
|
|
}
|
|
}
|
|
if (res != 0)
|
|
return res;
|
|
|
|
if (fil->keys == NULL)
|
|
return KPERR_NOKEY;
|
|
|
|
keynamelen = strlen(keyname);
|
|
|
|
for (key = fil->keys; key != NULL; key = key->next) {
|
|
if (strcmp(key->name, keyname) == 0) {
|
|
|
|
if (key->flags & KPFL_REMOVED)
|
|
return KPERR_NOKEY;
|
|
|
|
if (key->flags & KPFL_BADDB)
|
|
return KPERR_BADDB;
|
|
|
|
if (iskeyfile && keynamelen == 0)
|
|
return KPERR_BADKEY;
|
|
|
|
*tp = key->type;
|
|
return 0;
|
|
}
|
|
if (!(key->flags & KPFL_REMOVED)) {
|
|
if (keynamelen == 0) {
|
|
if (!iskeyfile)
|
|
return KPERR_BADKEY;
|
|
|
|
*tp = KPVAL_DIR;
|
|
return 0;
|
|
}
|
|
if (strncmp(key->name, keyname, keynamelen) == 0 &&
|
|
key->name[keynamelen] == '/') {
|
|
*tp = KPVAL_DIR;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return KPERR_NOKEY;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Collect the subkeys of a given key in a file
|
|
* ------------------------------------------------------------------------- */
|
|
static int kp_get_subkeys_file(kp_path * kpp, const char *keyname,
|
|
struct key_array *keys)
|
|
{
|
|
kp_fil *fil;
|
|
kp_key *key;
|
|
int res;
|
|
unsigned int keynamelen;
|
|
int found;
|
|
char *s;
|
|
char *ent;
|
|
int isdir;
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_get_subkeys_file\n");
|
|
#endif
|
|
|
|
res = kp_get_file(kpp, &fil, &isdir);
|
|
if (res != 0)
|
|
return res;
|
|
|
|
if (fil->keys == NULL)
|
|
return KPERR_NOKEY;
|
|
|
|
keynamelen = strlen(keyname);
|
|
|
|
found = 0;
|
|
for (key = fil->keys; key != NULL; key = key->next) {
|
|
if ((key->flags & KPFL_REMOVED) != 0)
|
|
continue;
|
|
|
|
if (keynamelen == 0 ||
|
|
(strlen(key->name) > keynamelen
|
|
&& key->name[keynamelen] == '/'
|
|
&& strncmp(key->name, keyname, keynamelen) == 0)) {
|
|
|
|
s = key->name + keynamelen;
|
|
if (*s == '/')
|
|
s++;
|
|
|
|
ent = strdup_check(s);
|
|
s = ent;
|
|
while (*s && *s != '/')
|
|
s++;
|
|
|
|
*s = '\0';
|
|
|
|
_kp_add_subkey_check(keys, ent);
|
|
free(ent);
|
|
|
|
found = 1;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return KPERR_NOKEY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Get the subkeys of a given key, from a directory or from a file
|
|
* ------------------------------------------------------------------------- */
|
|
int _kp_cache_get_subkeys(kp_path * kpp, const char *keypath,
|
|
int iskeyfile, struct key_array *keys)
|
|
{
|
|
int res;
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_get_subkeys\n");
|
|
#endif
|
|
|
|
if (!iskeyfile) {
|
|
res = _kp_get_subkeys_dir(kpp->path, keys);
|
|
} else
|
|
res = kp_get_subkeys_file(kpp, keypath, keys);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Flush the dirty files in the cache to the disk
|
|
* ------------------------------------------------------------------------- */
|
|
int _kp_cache_flush()
|
|
{
|
|
int res;
|
|
int finalres = 0;
|
|
kp_fil *fil;
|
|
#ifdef KPDEBUG
|
|
printf("kp_cache_flush\n");
|
|
#endif
|
|
for (fil = kp_cachef; fil != NULL; fil = fil->next) {
|
|
if (fil->dirty) {
|
|
res = _kp_write_file(&fil->kpp, fil->keys);
|
|
|
|
/* The return value of the flush will be the value of the
|
|
last error that occured */
|
|
|
|
if (res != 0)
|
|
finalres = res;
|
|
|
|
fil->dirty = 0;
|
|
}
|
|
}
|
|
|
|
return finalres;
|
|
}
|
|
|
|
/* End of kp_cache.c */
|