/*++
/* NAME
/* dict_cdb 3
/* SUMMARY
/* dictionary manager interface to CDB files
/* SYNOPSIS
/* #include <dict_cdb.h>
/*
/* DICT *dict_cdb_open(path, open_flags, dict_flags)
/* const char *path;
/* int open_flags;
/* int dict_flags;
/*
/* DESCRIPTION
/* dict_cdb_open() opens the specified CDB database. The result is
/* a pointer to a structure that can be used to access the dictionary
/* using the generic methods documented in dict_open(3).
/*
/* Arguments:
/* .IP path
/* The database pathname, not including the ".cdb" suffix.
/* .IP open_flags
/* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC.
/* .IP dict_flags
/* Flags used by the dictionary interface.
/* SEE ALSO
/* dict(3) generic dictionary manager
/* DIAGNOSTICS
/* Fatal errors: cannot open file, write error, out of memory.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
#include "sys_defs.h"
/* System library. */
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
/* Utility library. */
#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
/* Application-specific. */
typedef struct {
DICT dict; /* generic members */
struct cdb cdb; /* cdb structure */
VSTRING *val_buf; /* value result */
#ifdef TINYCDB_VERSION
VSTRING *key_buf; /* key result */
unsigned seq_cptr; /* current sequence pointer */
#endif
} DICT_CDBQ; /* query interface */
typedef struct {
DICT dict; /* generic members */
struct cdb_make cdbm; /* cdb_make structure */
char *cdb_path; /* cdb pathname (.cdb) */
char *tmp_path; /* temporary pathname (.tmp) */
} DICT_CDBM; /* rebuild interface */
/* dict_cdbq_getdata - get data out of the cdb using given buffer */
static const char *dict_cdbq_get_data(DICT_CDBQ *dict_cdbq,
VSTRING **bufp, unsigned len, unsigned pos)
{
VSTRING *buf = *bufp;
if (!buf)
buf = *bufp = vstring_alloc(len < 20 ? 20 : len);
VSTRING_RESET(buf);
VSTRING_SPACE(buf, len);
if (cdb_read(&dict_cdbq->cdb, vstring_str(buf), len, pos) < 0 )
msg_fatal("error reading %s: %m" , dict_cdbq->dict.name);
vstring_set_payload_size(buf, len);
VSTRING_TERMINATE(buf);
return vstring_str(buf);
}
/* dict_cdbq_lookup - find database entry, query mode */
static const char *dict_cdbq_lookup(DICT *dict, const char *name)
{
DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
int status = 0 ;
const char *result = 0 ;
dict->error = 0 ;
/* CDB is constant, so do not try to acquire a lock. */
/*
* Optionally fold the key .
*/
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));
}
/*
* See if this CDB file was written with one null byte appended to key
* and value .
*/
if (dict->flags & DICT_FLAG_TRY1NULL) {
status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1 );
if (status > 0 )
dict->flags &= ~DICT_FLAG_TRY0NULL;
}
/*
* See if this CDB file was written with no null byte appended to key and
* value .
*/
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) {
result = dict_cdbq_get_data(dict_cdbq, &dict_cdbq->val_buf,
cdb_datalen(&dict_cdbq->cdb), cdb_datapos(&dict_cdbq->cdb));
}
/* No locking so not release the lock. */
return (result);
}
#ifdef TINYCDB_VERSION
/* dict_cdbq_sequence - traverse the dictionary */
static int dict_cdbq_sequence(DICT *dict, int function,
const char **key, const char **value)
{
const char *myname = "dict_cdbq_sequence" ;
DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
int status;
switch (function) {
case DICT_SEQ_FUN_FIRST:
cdb_seqinit(&dict_cdbq->seq_cptr, &dict_cdbq->cdb);
break ;
case DICT_SEQ_FUN_NEXT:
if (!dict_cdbq->seq_cptr)
msg_panic("%s: %s: no cursor" , myname, dict_cdbq->dict.name);
break ;
default :
msg_panic("%s: invalid function %d" , myname, function);
}
status = cdb_seqnext(&dict_cdbq->seq_cptr, &dict_cdbq->cdb);
if (status < 0 )
msg_fatal("error seeking %s: %m" , dict_cdbq->dict.name);
if (!status) {
dict_cdbq->seq_cptr = 0 ;
return -1 ; /* not found */
}
*key = dict_cdbq_get_data(dict_cdbq, &dict_cdbq->key_buf,
cdb_keylen(&dict_cdbq->cdb), cdb_keypos(&dict_cdbq->cdb));
*value = dict_cdbq_get_data(dict_cdbq, &dict_cdbq->val_buf,
cdb_datalen(&dict_cdbq->cdb), cdb_datapos(&dict_cdbq->cdb));
return 0 ;
}
#endif /* TINYCDB_VERSION */
/* dict_cdbq_close - close data base, query mode */
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);
if (dict_cdbq->val_buf)
vstring_free(dict_cdbq->val_buf);
#ifdef TINYCDB_VERSION
if (dict_cdbq->key_buf)
vstring_free(dict_cdbq->key_buf);
#endif
dict_free(dict);
}
/* dict_cdbq_open - open data base, query mode */
static DICT *dict_cdbq_open(const char *path, int dict_flags)
{
DICT_CDBQ *dict_cdbq;
struct stat st;
char *cdb_path;
int fd;
/*
* Let the optimizer worry about eliminating redundant code .
*/
#define DICT_CDBQ_OPEN_RETURN(d) do { \
DICT *__d = (d); \
myfree(cdb_path); \
return (__d); \
} while (0 )
cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0 );
if ((fd = open(cdb_path, O_RDONLY)) < 0 )
DICT_CDBQ_OPEN_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));
dict_cdbq->val_buf = 0 ;
#if defined (TINYCDB_VERSION)
dict_cdbq->key_buf = 0 ;
dict_cdbq->seq_cptr = 0 ;
if (cdb_init(&(dict_cdbq->cdb), fd) != 0 )
msg_fatal("dict_cdbq_open: unable to init %s: %m" , cdb_path);
dict_cdbq->dict.sequence = dict_cdbq_sequence;
#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);
/*
* Warn if the source file is newer than the indexed file , except when
* the source file changed only seconds ago .
*/
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 undecided about appending a null byte to key and value , choose to
* try both in query mode .
*/
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 );
DICT_CDBQ_OPEN_RETURN(&dict_cdbq->dict);
}
/* dict_cdbm_update - add database entry, create mode */
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 ;
/*
* Optionally fold the key .
*/
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);
/*
* Optionally append a null byte to key and value .
*/
if (dict->flags & DICT_FLAG_TRY1NULL) {
ksize++;
vsize++;
}
/*
* Do the add operation . No locking is done .
*/
#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))
/* void */ ;
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
}
/* dict_cdbm_close - close data base and rename file.tmp to file.cdb */
static void dict_cdbm_close(DICT *dict)
{
DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
int fd = cdb_fileno(&dict_cdbm->cdbm);
/*
* Note : if FCNTL locking is used , closing any file descriptor on a
* locked file cancels all locks that the process may have on that file .
* CDB is FCNTL locking safe , because it uses the same file descriptor
* for database I / O and locking .
*/
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 ) /* releases a lock */
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);
}
/* dict_cdbm_open - create database as file.tmp */
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;
/*
* Let the optimizer worry about eliminating redundant code .
*/
#define DICT_CDBM_OPEN_RETURN(d) do { \
DICT *__d = (d); \
if (cdb_path) \
myfree(cdb_path); \
if (tmp_path) \
myfree(tmp_path); \
return (__d); \
} while (0 )
cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0 );
tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0 );
/*
* Repeat until we have opened * and * locked * existing * file . Since the
* new ( tmp ) file will be renamed to be . cdb file , locking here is
* somewhat funny to work around possible race conditions . Note that we
* can ' t open a file with O_TRUNC as we can ' t know if another process
* isn ' t creating it at the same time .
*/
for (;;) {
if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644 )) < 0 )
DICT_CDBM_OPEN_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);
/*
* Get an exclusive lock - we ' re going to change the database so we
* can ' t have any spectators .
*/
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);
/*
* Compare file ' s state before and after lock : should be the same ,
* and nlinks should be > 0 , or else we opened non - existing file . . .
*/
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 ; /* successfully opened */
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;
cdb_path = tmp_path = 0 ; /* DICT_CDBM_OPEN_RETURN() */
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 undecided about appending a null byte to key and value , choose a
* default to not append a null byte when creating a cdb .
*/
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 );
DICT_CDBM_OPEN_RETURN(&dict_cdbm->dict);
}
/* dict_cdb_open - open data base for query mode or create mode */
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: /* query mode */
return dict_cdbq_open(path, dict_flags);
case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */
case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */
return dict_cdbm_open(path, dict_flags);
case O_RDWR | O_CREAT:
case O_RDWR:
/* User error. */
return (dict_surrogate(DICT_TYPE_CDB, path, open_flags, dict_flags,
"unsupported non-bulk change request" ));
default :
/* Programmer error. */
msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
" - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC" );
}
}
#endif /* HAS_CDB */
Messung V0.5 in Prozent C=65 H=96 G=81
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-08-09)
¤
*© Formatika GbR, Deutschland