summaryrefslogtreecommitdiffstats
path: root/src/util/dict_cdb.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/dict_cdb.c446
1 files changed, 446 insertions, 0 deletions
diff --git a/src/util/dict_cdb.c b/src/util/dict_cdb.c
new file mode 100644
index 0000000..85e49a4
--- /dev/null
+++ b/src/util/dict_cdb.c
@@ -0,0 +1,446 @@
+/*++
+/* 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 */
+} 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_lookup - find database entry, query mode */
+
+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;
+
+ /* 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) {
+ 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;
+ }
+ /* No locking so not release the lock. */
+
+ return (result);
+}
+
+/* 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);
+ 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));
+#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);
+
+ /*
+ * 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_DEBUG (&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; /* successefully 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_DEBUG (&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);
+ default:
+ msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
+ " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");
+ }
+}
+
+#endif /* HAS_CDB */