#include "sys_defs.h"
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include "msg.h"
#include "mymalloc.h"
#include "vstring.h"
#include "stringops.h"
#include "iostuff.h"
#include "myflock.h"
#include "stringops.h"
#include "dict.h"
#include "dict_cdb.h"
#include "warn_stat.h"
#ifdef HAS_CDB
#include <cdb.h>
#ifndef TINYCDB_VERSION
#include <cdb_make.h>
#endif
#ifndef cdb_fileno
#define cdb_fileno(c) ((c)->fd)
#endif
#ifndef CDB_SUFFIX
#define CDB_SUFFIX ".cdb"
#endif
#ifndef CDB_TMP_SUFFIX
#define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp"
#endif
typedef struct {
DICT dict;
struct cdb cdb;
} DICT_CDBQ;
typedef struct {
DICT dict;
struct cdb_make cdbm;
char *cdb_path;
char *tmp_path;
} DICT_CDBM;
static const char *dict_cdbq_lookup(DICT *dict, const char *name)
{
DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
unsigned vlen;
int status = 0;
static char *buf;
static unsigned len;
const char *result = 0;
dict->error = 0;
if (dict->flags & DICT_FLAG_FOLD_FIX) {
if (dict->fold_buf == 0)
dict->fold_buf = vstring_alloc(10);
vstring_strcpy(dict->fold_buf, name);
name = lowercase(vstring_str(dict->fold_buf));
}
if (dict->flags & DICT_FLAG_TRY1NULL) {
status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1);
if (status > 0)
dict->flags &= ~DICT_FLAG_TRY0NULL;
}
if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
status = cdb_find(&dict_cdbq->cdb, name, strlen(name));
if (status > 0)
dict->flags &= ~DICT_FLAG_TRY1NULL;
}
if (status < 0)
msg_fatal("error reading %s: %m", dict->name);
if (status) {
vlen = cdb_datalen(&dict_cdbq->cdb);
if (len < vlen) {
if (buf == 0)
buf = mymalloc(vlen + 1);
else
buf = myrealloc(buf, vlen + 1);
len = vlen;
}
if (cdb_read(&dict_cdbq->cdb, buf, vlen,
cdb_datapos(&dict_cdbq->cdb)) < 0)
msg_fatal("error reading %s: %m", dict->name);
buf[vlen] = '\0';
result = buf;
}
return (result);
}
static void dict_cdbq_close(DICT *dict)
{
DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
cdb_free(&dict_cdbq->cdb);
close(dict->stat_fd);
if (dict->fold_buf)
vstring_free(dict->fold_buf);
dict_free(dict);
}
static DICT *dict_cdbq_open(const char *path, int dict_flags)
{
DICT_CDBQ *dict_cdbq;
struct stat st;
char *cdb_path;
int fd;
cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
if ((fd = open(cdb_path, O_RDONLY)) < 0)
return (dict_surrogate(DICT_TYPE_CDB, path, O_RDONLY, dict_flags,
"open database %s: %m", cdb_path));
dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB,
cdb_path, sizeof(*dict_cdbq));
#if defined(TINYCDB_VERSION)
if (cdb_init(&(dict_cdbq->cdb), fd) != 0)
msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path);
#else
cdb_init(&(dict_cdbq->cdb), fd);
#endif
dict_cdbq->dict.lookup = dict_cdbq_lookup;
dict_cdbq->dict.close = dict_cdbq_close;
dict_cdbq->dict.stat_fd = fd;
if (fstat(fd, &st) < 0)
msg_fatal("dict_dbq_open: fstat: %m");
dict_cdbq->dict.mtime = st.st_mtime;
dict_cdbq->dict.owner.uid = st.st_uid;
dict_cdbq->dict.owner.status = (st.st_uid != 0);
close_on_exec(fd, CLOSE_ON_EXEC);
if (stat(path, &st) == 0
&& st.st_mtime > dict_cdbq->dict.mtime
&& st.st_mtime < time((time_t *) 0) - 100)
msg_warn("database %s is older than source file %s", cdb_path, path);
if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL;
dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED;
if (dict_flags & DICT_FLAG_FOLD_FIX)
dict_cdbq->dict.fold_buf = vstring_alloc(10);
myfree(cdb_path);
return (&dict_cdbq->dict);
}
static int dict_cdbm_update(DICT *dict, const char *name, const char *value)
{
DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
unsigned ksize, vsize;
int r;
dict->error = 0;
if (dict->flags & DICT_FLAG_FOLD_FIX) {
if (dict->fold_buf == 0)
dict->fold_buf = vstring_alloc(10);
vstring_strcpy(dict->fold_buf, name);
name = lowercase(vstring_str(dict->fold_buf));
}
ksize = strlen(name);
vsize = strlen(value);
if (dict->flags & DICT_FLAG_TRY1NULL) {
ksize++;
vsize++;
}
#ifdef TINYCDB_VERSION
#ifndef CDB_PUT_ADD
#error please upgrate tinycdb to at least 0.5 version
#endif
if (dict->flags & DICT_FLAG_DUP_IGNORE)
r = CDB_PUT_ADD;
else if (dict->flags & DICT_FLAG_DUP_REPLACE)
r = CDB_PUT_REPLACE;
else
r = CDB_PUT_INSERT;
r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r);
if (r < 0)
msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
else if (r > 0) {
if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE))
;
else if (dict->flags & DICT_FLAG_DUP_WARN)
msg_warn("%s: duplicate entry: \"%s\"",
dict_cdbm->dict.name, name);
else
msg_fatal("%s: duplicate entry: \"%s\"",
dict_cdbm->dict.name, name);
}
return (r);
#else
if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0)
msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
return (0);
#endif
}
static void dict_cdbm_close(DICT *dict)
{
DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
int fd = cdb_fileno(&dict_cdbm->cdbm);
if (cdb_make_finish(&dict_cdbm->cdbm) < 0)
msg_fatal("finish database %s: %m", dict_cdbm->tmp_path);
if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0)
msg_fatal("rename database from %s to %s: %m",
dict_cdbm->tmp_path, dict_cdbm->cdb_path);
if (close(fd) < 0)
msg_fatal("close database %s: %m", dict_cdbm->cdb_path);
myfree(dict_cdbm->cdb_path);
myfree(dict_cdbm->tmp_path);
if (dict->fold_buf)
vstring_free(dict->fold_buf);
dict_free(dict);
}
static DICT *dict_cdbm_open(const char *path, int dict_flags)
{
DICT_CDBM *dict_cdbm;
char *cdb_path;
char *tmp_path;
int fd;
struct stat st0, st1;
cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0);
for (;;) {
if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
return (dict_surrogate(DICT_TYPE_CDB, path, O_RDWR, dict_flags,
"open database %s: %m", tmp_path));
if (fstat(fd, &st0) < 0)
msg_fatal("fstat(%s): %m", tmp_path);
if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
msg_fatal("lock %s: %m", tmp_path);
if (stat(tmp_path, &st1) < 0)
msg_fatal("stat(%s): %m", tmp_path);
if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev
&& st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink
&& st0.st_nlink > 0)
break;
close(fd);
}
#ifndef NO_FTRUNCATE
if (st0.st_size)
ftruncate(fd, 0);
#endif
dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path,
sizeof(*dict_cdbm));
if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0)
msg_fatal("initialize database %s: %m", tmp_path);
dict_cdbm->dict.close = dict_cdbm_close;
dict_cdbm->dict.update = dict_cdbm_update;
dict_cdbm->cdb_path = cdb_path;
dict_cdbm->tmp_path = tmp_path;
dict_cdbm->dict.owner.uid = st1.st_uid;
dict_cdbm->dict.owner.status = (st1.st_uid != 0);
close_on_exec(fd, CLOSE_ON_EXEC);
if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
dict_flags |= DICT_FLAG_TRY0NULL;
else if ((dict_flags & DICT_FLAG_TRY1NULL)
&& (dict_flags & DICT_FLAG_TRY0NULL))
dict_flags &= ~DICT_FLAG_TRY0NULL;
dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
if (dict_flags & DICT_FLAG_FOLD_FIX)
dict_cdbm->dict.fold_buf = vstring_alloc(10);
return (&dict_cdbm->dict);
}
DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags)
{
switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
case O_RDONLY:
return dict_cdbq_open(path, dict_flags);
case O_WRONLY | O_CREAT | O_TRUNC:
case O_RDWR | O_CREAT | O_TRUNC:
return dict_cdbm_open(path, dict_flags);
default:
msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
" - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");
}
}
#endif