Wietse Venema wrote:
Howard Chu:
The first patch adds a lock handler to the dict interface. I needed this
first, because MDB is fully transactional and does its own locking/concurrency
management. In particular, it does MVCC so readers always run lockless; since
they're fully isolated there's no actual need for "shared locks."
I have added a lock function pointer to the dictionary data structure,
so that a specific dictionary can override it if needed.
This locking function is not exposed to applications: it is used
only by dict_open(). Applications rely on the dict_open()
"lock-on-open" feature for maps that aren't multi-writer safe.
A multi-writer safe table like MDB could override this function
with a NOOP (or it could leave the lock_fd member equal to -1 and
achieve the exact same result).
In the case of postmap/postalias which truncate databases, a global
lock is needed to prevent readers from looking at an incomplete
database. This has nothing to do with the dict_open() "lock-on-open"
feature for tables that aren't multi-writer safe.
OK, this is an updated MDB patch, with a bit of documentation added as well.
Based on the postfix-2.10-20121007 source.
--
-- Howard Chu
CTO, Symas Corp. http://www.symas.com
Director, Highland Sun http://highlandsun.com/hyc/
Chief Architect, OpenLDAP http://www.openldap.org/project/
>From c0dfc7754c91f036ecb69dc9d5493891d540ee4d Mon Sep 17 00:00:00 2001
From: Howard Chu <h...@symas.com>
Date: Thu, 20 Sep 2012 09:27:14 -0700
Subject: [PATCH] Add support for OpenLDAP MDB
---
proto/MDB_README.html | 109 +++++++++
src/global/Makefile.in | 4 +-
src/global/data_redirect.c | 2 +
src/global/mail_params.c | 9 +
src/global/mail_params.h | 7 +
src/global/mkmap.h | 1 +
src/global/mkmap_mdb.c | 92 ++++++++
src/global/mkmap_open.c | 4 +
src/util/Makefile.in | 9 +-
src/util/dict_mdb.c | 531 ++++++++++++++++++++++++++++++++++++++++++++
src/util/dict_mdb.h | 40 ++++
src/util/dict_open.c | 4 +
12 files changed, 807 insertions(+), 5 deletions(-)
create mode 100644 proto/MDB_README.html
create mode 100644 src/global/mkmap_mdb.c
create mode 100644 src/util/dict_mdb.c
create mode 100644 src/util/dict_mdb.h
diff --git a/proto/MDB_README.html b/proto/MDB_README.html
new file mode 100644
index 0000000..042ecef
--- /dev/null
+++ b/proto/MDB_README.html
@@ -0,0 +1,109 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix OpenLDAP MDB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix OpenLDAP MDB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix uses databases of various kinds to store and look up
+information. Postfix databases are specified as "type:name".
+OpenLDAP MDB implements the Postfix database type "mdb".
+The name of a Postfix OpenLDAP MDB database is the name
+of the database file without the ".mdb" suffix. OpenLDAP MDB databases
+are maintained with the postmap(1) command. </p>
+
+<p> This document describes: </p>
+
+<ol>
+
+<li> <p> How to build Postfix <a href="#with_mdb">with OpenLDAP
+MDB support</a>. </p>
+
+<li> <p> How to <a href="#configure">configure</a> MDB settings. </p>
+
+<li> <p> Missing <a href="#pthread">pthread</a> library trouble. </p>
+
+</ol>
+
+<h2><a name="with_mdb">Building Postfix with OpenLDAP MDB support</a></h2>
+
+<p> Postfix normally does not enable OpenLDAP MDB support.
+To build Postfix after you installed OpenLDAP MDB from
+source code, use something like: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_MDB -I/usr/local/include" \
+ AUXLIBS="-L/usr/local/lib -lmdb"
+% make
+</pre>
+</blockquote>
+
+<p> Solaris needs this: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_MDB -I/usr/local/include" \
+ AUXLIBS="-R/usr/local/lib -L/usr/local/lib -lmdb"
+% make
+</pre>
+</blockquote>
+
+<p> The exact pathnames depend on how OpenLDAP MDB was installed. </p>
+
+<h2><a name="configure">Configure MDB settings</a></h2>
+
+<p> Postfix provides a configuration parameter that controls how
+large an OpenLDAP MDB database may grow. </p>
+
+<ul>
+
+<li> <p> mdb_map_size (default: 10 MBytes per
+table). This setting controls how large any OpenLDAP MDB database
+may grow. It must be set large enough to accommodate the largest
+table that Postfix will use. </p>
+
+</ul>
+
+<h2><a name="pthread">Missing pthread library trouble</a></h2>
+
+<p> When building Postfix fails with: </p>
+
+<blockquote>
+<pre>
+undefined reference to `pthread_mutexattr_destroy'
+undefined reference to `pthread_mutexattr_init'
+undefined reference to `pthread_mutex_lock'
+</pre>
+</blockquote>
+
+<p> Add the "-lpthread" library to the "make makefiles" command. </p>
+
+<blockquote>
+<pre>
+% make makefiles .... AUXLIBS="... -lpthread"
+</pre>
+</blockquote>
+
+<p> Source code for OpenLDAP MDB is available at
+http://www.openldap.org.
+More information is available at
+http://highlandsun.com/hyc/mdb/. </p>
+
+</body>
+
+</html>
diff --git a/src/global/Makefile.in b/src/global/Makefile.in
index 664d772..92d93b8 100644
--- a/src/global/Makefile.in
+++ b/src/global/Makefile.in
@@ -16,7 +16,7 @@ SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
mail_params.c mail_pathname.c mail_queue.c mail_run.c \
mail_scan_dir.c mail_stream.c mail_task.c mail_trigger.c maps.c \
mark_corrupt.c match_parent_style.c mbox_conf.c mbox_open.c \
- mime_state.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c mkmap_open.c \
+ mime_state.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c mkmap_mdb.c mkmap_open.c \
mkmap_sdbm.c msg_stats_print.c msg_stats_scan.c mynetworks.c \
mypwd.c namadr_list.c off_cvt.c opened.c own_inet_addr.c \
pipe_command.c post_mail.c quote_821_local.c quote_822_local.c \
@@ -50,7 +50,7 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
mail_params.o mail_pathname.o mail_queue.o mail_run.o \
mail_scan_dir.o mail_stream.o mail_task.o mail_trigger.o maps.o \
mark_corrupt.o match_parent_style.o mbox_conf.o mbox_open.o \
- mime_state.o mkmap_cdb.o mkmap_db.o mkmap_dbm.o mkmap_open.o \
+ mime_state.o mkmap_cdb.o mkmap_db.o mkmap_dbm.o mkmap_mdb.o mkmap_open.o \
mkmap_sdbm.o msg_stats_print.o msg_stats_scan.o mynetworks.o \
mypwd.o namadr_list.o off_cvt.o opened.o own_inet_addr.o \
pipe_command.o post_mail.o quote_821_local.o quote_822_local.o \
diff --git a/src/global/data_redirect.c b/src/global/data_redirect.c
index 096e58c..f914155 100644
--- a/src/global/data_redirect.c
+++ b/src/global/data_redirect.c
@@ -72,6 +72,7 @@
#include <dict_db.h>
#include <dict_dbm.h>
#include <dict_cdb.h>
+#include <dict_mdb.h>
#include <warn_stat.h>
/* Global directory. */
@@ -99,6 +100,7 @@ static const NAME_CODE data_redirect_map_types[] = {
DICT_TYPE_HASH, 1,
DICT_TYPE_BTREE, 1,
DICT_TYPE_DBM, 1,
+ DICT_TYPE_MDB, 1,
DICT_TYPE_CDB, 1, /* not a read-write map type */
"sdbm", 1, /* legacy 3rd-party TLS */
"dbz", 1, /* just in case */
diff --git a/src/global/mail_params.c b/src/global/mail_params.c
index 0e098b3..a14afe6 100644
--- a/src/global/mail_params.c
+++ b/src/global/mail_params.c
@@ -96,6 +96,7 @@
/* char *var_proxywrite_service;
/* int var_db_create_buf;
/* int var_db_read_buf;
+/* int var_mdb_map_size;
/* int var_mime_maxdepth;
/* int var_mime_bound_len;
/* int var_header_limit;
@@ -177,6 +178,9 @@
#ifdef HAS_DB
#include <dict_db.h>
#endif
+#ifdef HAS_MDB
+#include <dict_mdb.h>
+#endif
#include <inet_proto.h>
#include <vstring_vstream.h>
#include <iostuff.h>
@@ -284,6 +288,7 @@ char *var_proxymap_service;
char *var_proxywrite_service;
int var_db_create_buf;
int var_db_read_buf;
+int var_mdb_map_size;
int var_mime_maxdepth;
int var_mime_bound_len;
int var_header_limit;
@@ -596,6 +601,7 @@ void mail_params_init()
VAR_FAULT_INJ_CODE, DEF_FAULT_INJ_CODE, &var_fault_inj_code, 0, 0,
VAR_DB_CREATE_BUF, DEF_DB_CREATE_BUF, &var_db_create_buf, 1, 0,
VAR_DB_READ_BUF, DEF_DB_READ_BUF, &var_db_read_buf, 1, 0,
+ VAR_MDB_MAP_SIZE, DEF_MDB_MAP_SIZE, &var_mdb_map_size, 1, 0,
VAR_HEADER_LIMIT, DEF_HEADER_LIMIT, &var_header_limit, 1, 0,
VAR_TOKEN_LIMIT, DEF_TOKEN_LIMIT, &var_token_limit, 1, 0,
VAR_MIME_MAXDEPTH, DEF_MIME_MAXDEPTH, &var_mime_maxdepth, 1, 0,
@@ -713,6 +719,9 @@ void mail_params_init()
#ifdef HAS_DB
dict_db_cache_size = var_db_read_buf;
#endif
+#ifdef HAS_MDB
+ dict_mdb_map_size = var_mdb_map_size;
+#endif
inet_windowsize = var_inet_windowsize;
/*
diff --git a/src/global/mail_params.h b/src/global/mail_params.h
index 57135a0..935ba22 100644
--- a/src/global/mail_params.h
+++ b/src/global/mail_params.h
@@ -2728,6 +2728,13 @@ extern int var_db_create_buf;
extern int var_db_read_buf;
/*
+ * OpenLDAP MDB memory map size.
+ */
+#define VAR_MDB_MAP_SIZE "mdb_map_size"
+#define DEF_MDB_MAP_SIZE (16 * 1024 *1024)
+extern int var_mdb_map_size;
+
+ /*
* Named queue file attributes.
*/
#define VAR_QATTR_COUNT_LIMIT "queue_file_attribute_count_limit"
diff --git a/src/global/mkmap.h b/src/global/mkmap.h
index d74a912..e9f3a57 100644
--- a/src/global/mkmap.h
+++ b/src/global/mkmap.h
@@ -39,6 +39,7 @@ extern MKMAP *mkmap_dbm_open(const char *);
extern MKMAP *mkmap_cdb_open(const char *);
extern MKMAP *mkmap_hash_open(const char *);
extern MKMAP *mkmap_btree_open(const char *);
+extern MKMAP *mkmap_mdb_open(const char *);
extern MKMAP *mkmap_sdbm_open(const char *);
extern MKMAP *mkmap_proxy_open(const char *);
extern MKMAP *mkmap_fail_open(const char *);
diff --git a/src/global/mkmap_mdb.c b/src/global/mkmap_mdb.c
new file mode 100644
index 0000000..d55c7a7
--- /dev/null
+++ b/src/global/mkmap_mdb.c
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* mkmap_mdb 3
+/* SUMMARY
+/* create or open database, MDB style
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* MKMAP *mkmap_mdb_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for creating MDB databases.
+/*
+/* mkmap_mdb_open() takes a file name, appends the ".mdb"
+/* suffix, and does whatever initialization is required
+/* before the OpenLDAP MDB open routine is called.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_mdb(3), MDB dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_mdb.h>
+#include <myflock.h>
+#include <warn_stat.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "mkmap.h"
+
+#ifdef HAS_MDB
+#ifdef PATH_MDB_H
+#include PATH_MDB_H
+#else
+#include <mdb.h>
+#endif
+
+/* mkmap_mdb_open */
+
+MKMAP *mkmap_mdb_open(const char *path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+
+ /*
+ * Override the default per-table map size for map (re)builds.
+ *
+ * mdb_map_size is defined in util/dict_mdb.c and defaults to 10MB.
+ * It needs to be large enough to contain the largest tables in use.
+ *
+ * XXX This should be specified via the DICT interface so that the buffer
+ * size becomes an object property, instead of being specified by poking
+ * a global variable so that it becomes a class property.
+ */
+ dict_mdb_map_size = var_mdb_map_size;
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->open = dict_mdb_open;
+
+ /*
+ * MDB uses MVCC so it needs no special lock management here.
+ */
+
+ return (mkmap);
+}
+#endif
diff --git a/src/global/mkmap_open.c b/src/global/mkmap_open.c
index d939d44..918c855 100644
--- a/src/global/mkmap_open.c
+++ b/src/global/mkmap_open.c
@@ -65,6 +65,7 @@
#include <dict_db.h>
#include <dict_cdb.h>
#include <dict_dbm.h>
+#include <dict_mdb.h>
#include <dict_sdbm.h>
#include <dict_proxy.h>
#include <dict_fail.h>
@@ -101,6 +102,9 @@ static const MKMAP_OPEN_INFO mkmap_types[] = {
DICT_TYPE_HASH, mkmap_hash_open,
DICT_TYPE_BTREE, mkmap_btree_open,
#endif
+#ifdef HAS_MDB
+ DICT_TYPE_MDB, mkmap_mdb_open,
+#endif
DICT_TYPE_FAIL, mkmap_fail_open,
0,
};
diff --git a/src/util/Makefile.in b/src/util/Makefile.in
index 4bef131..56e44e2 100644
--- a/src/util/Makefile.in
+++ b/src/util/Makefile.in
@@ -4,7 +4,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
attr_scan_plain.c auto_clnt.c base64_code.c basename.c binhash.c \
chroot_uid.c cidr_match.c clean_env.c close_on_exec.c concatenate.c \
ctable.c dict.c dict_alloc.c dict_cdb.c dict_cidr.c dict_db.c \
- dict_dbm.c dict_debug.c dict_env.c dict_ht.c dict_ni.c dict_nis.c \
+ dict_dbm.c dict_debug.c dict_env.c dict_ht.c dict_mdb.c dict_ni.c dict_nis.c \
dict_nisplus.c dict_open.c dict_pcre.c dict_regexp.c dict_sdbm.c \
dict_static.c dict_tcp.c dict_unix.c dir_forest.c doze.c dummy_read.c \
dummy_write.c duplex_pipe.c environ.c events.c exec_command.c \
@@ -41,7 +41,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
chroot_uid.o cidr_match.o clean_env.o close_on_exec.o concatenate.o \
ctable.o dict.o dict_alloc.o dict_cdb.o dict_cidr.o dict_db.o \
- dict_dbm.o dict_debug.o dict_env.o dict_ht.o dict_ni.o dict_nis.o \
+ dict_dbm.o dict_debug.o dict_env.o dict_ht.o dict_mdb.o dict_ni.o dict_nis.o \
dict_nisplus.o dict_open.o dict_pcre.o dict_regexp.o dict_sdbm.o \
dict_static.o dict_tcp.o dict_unix.o dir_forest.o doze.o dummy_read.o \
dummy_write.o duplex_pipe.o environ.o events.o exec_command.o \
@@ -76,7 +76,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \
dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \
- dict_ni.h dict_nis.h dict_nisplus.h dict_pcre.h dict_regexp.h \
+ dict_mdb.h dict_ni.h dict_nis.h dict_nisplus.h dict_pcre.h dict_regexp.h \
dict_sdbm.h dict_static.h dict_tcp.h dict_unix.h dir_forest.h \
events.h exec_command.h find_inet.h fsspace.h fullname.h \
get_domainname.h get_hostname.h hex_code.h hex_quote.h host_port.h \
@@ -983,6 +983,9 @@ dict_ht.o: sys_defs.h
dict_ht.o: vbuf.h
dict_ht.o: vstream.h
dict_ht.o: vstring.h
+dict_mdb.o: dict.h
+dict_mdb.o: dict_mdb.h
+dict_mdb.o: dict_mdb.c
dict_ni.o: dict_ni.c
dict_ni.o: sys_defs.h
dict_nis.o: argv.h
diff --git a/src/util/dict_mdb.c b/src/util/dict_mdb.c
new file mode 100644
index 0000000..fea9af5
--- /dev/null
+++ b/src/util/dict_mdb.c
@@ -0,0 +1,531 @@
+/*++
+/* NAME
+/* dict_mdb 3
+/* SUMMARY
+/* dictionary manager interface to OpenLDAP MDB files
+/* SYNOPSIS
+/* #include <dict_mdb.h>
+/*
+/* DICT *dict_mdb_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_mdb_open() opens the named MDB database and makes it available
+/* via the generic interface described in dict_open(3).
+/*
+/* The dict_mdb_map_size variable specifies a non-default per-table
+/* memory map size. The map size is 10MB. The map size is also the
+/* maximum size the table can grow to, so it must be set large enough
+/* to accomodate the largest tables in use.
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_MDB
+
+/* System library. */
+
+#include <sys/stat.h>
+#ifdef PATH_MDB_H
+#include PATH_MDB_H
+#else
+#include <mdb.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "htable.h"
+#include "iostuff.h"
+#include "vstring.h"
+#include "myflock.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_mdb.h"
+#include "warn_stat.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ MDB_env *env; /* MDB environment */
+ MDB_dbi dbi; /* database handle */
+ MDB_txn *txn; /* write transaction for O_TRUNC */
+ MDB_cursor *cursor; /* for sequence ops */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* result buffer */
+} DICT_MDB;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+size_t dict_mdb_map_size = (10 * 1024 * 1024); /* 10MB default mmap size */
+
+/* dict_mdb_lookup - find database entry */
+
+static const char *dict_mdb_lookup(DICT *dict, const char *name)
+{
+ DICT_MDB *dict_mdb = (DICT_MDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ MDB_txn *txn;
+ const char *result = 0;
+ int status, klen;
+
+ dict->error = 0;
+ klen = strlen(name);
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_mdb_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * 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));
+ }
+
+ /*
+ * Start a read transaction if there's no global txn.
+ */
+ if (dict_mdb->txn)
+ txn = dict_mdb->txn;
+ else if ((status = mdb_txn_begin(dict_mdb->env, NULL, MDB_RDONLY, &txn)))
+ msg_fatal("%s: txn_begin(read) dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+
+ /*
+ * See if this MDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen + 1;
+ status = mdb_get(txn, dict_mdb->dbi, &mdb_key, &mdb_value);
+ if (!status) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_mdb->val_buf, mdb_value.mv_data, mdb_value.mv_size);
+ }
+ }
+
+ /*
+ * See if this MDB file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen;
+ status = mdb_get(txn, dict_mdb->dbi, &mdb_key, &mdb_value);
+ if (!status) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_mdb->val_buf, mdb_value.mv_data, mdb_value.mv_size);
+ }
+ }
+
+ /*
+ * Close the read txn if it's not the global txn.
+ */
+ if (!dict_mdb->txn)
+ mdb_txn_abort(txn);
+
+ return (result);
+}
+
+/* dict_mdb_update - add or update database entry */
+
+static int dict_mdb_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_MDB *dict_mdb = (DICT_MDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ MDB_txn *txn;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_mdb_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * 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));
+ }
+ mdb_key.mv_data = (void *) name;
+ mdb_value.mv_data = (void *) value;
+ mdb_key.mv_size = strlen(name);
+ mdb_value.mv_size = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef MDB_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_size++;
+ mdb_value.mv_size++;
+ }
+
+ /*
+ * Start a write transaction if there's no global txn.
+ */
+ if (dict_mdb->txn)
+ txn = dict_mdb->txn;
+ else if ((status = mdb_txn_begin(dict_mdb->env, NULL, 0, &txn)))
+ msg_fatal("%s: txn_begin(write) dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+
+ /*
+ * Do the update.
+ */
+ status = mdb_put(txn, dict_mdb->dbi, &mdb_key, &mdb_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE);
+ if (status) {
+ if (status == MDB_KEYEXIST) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_mdb->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_mdb->dict.name, name);
+ } else {
+ msg_fatal("error writing MDB database %s: %s", dict_mdb->dict.name, mdb_strerror(status));
+ }
+ }
+
+ /*
+ * Commit the transaction if it's not the global txn.
+ */
+ if (!dict_mdb->txn && ((status = mdb_txn_commit(txn))))
+ msg_fatal("error committing MDB database %s: %s", dict_mdb->dict.name, mdb_strerror(status));
+
+ return (status);
+}
+
+/* dict_mdb_delete - delete one entry from the dictionary */
+
+static int dict_mdb_delete(DICT *dict, const char *name)
+{
+ DICT_MDB *dict_mdb = (DICT_MDB *) dict;
+ MDB_val mdb_key;
+ MDB_txn *txn;
+ int status = 1, klen, rc;
+
+ dict->error = 0;
+ klen = strlen(name);
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_mdb_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * 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));
+ }
+
+ /*
+ * Start a write transaction if there's no global txn.
+ */
+ if (dict_mdb->txn)
+ txn = dict_mdb->txn;
+ else if ((status = mdb_txn_begin(dict_mdb->env, NULL, 0, &txn)))
+ msg_fatal("%s: txn_begin(write) dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+
+ /*
+ * See if this MDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen + 1;
+ status = mdb_del(txn, dict_mdb->dbi, &mdb_key, NULL);
+ if (status) {
+ if (status == MDB_NOTFOUND)
+ status = 1;
+ else
+ msg_fatal("error deleting from %s: %s", dict_mdb->dict.name, mdb_strerror(status));
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this MDB file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen;
+ status = mdb_del(txn, dict_mdb->dbi, &mdb_key, NULL);
+ if (status) {
+ if (status == MDB_NOTFOUND)
+ status = 1;
+ else
+ msg_fatal("error deleting from %s: %s", dict_mdb->dict.name, mdb_strerror(status));
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Commit the transaction if it's not the global txn.
+ */
+ if (!dict_mdb->txn && ((rc = mdb_txn_commit(txn))))
+ msg_fatal("error committing MDB database %s: %s", dict_mdb->dict.name, mdb_strerror(rc));
+
+ return (status);
+}
+
+/* traverse the dictionary */
+
+static int dict_mdb_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_mdb_sequence";
+ DICT_MDB *dict_mdb = (DICT_MDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ MDB_txn *txn;
+ MDB_cursor_op op;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Determine the seek function.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ op = MDB_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ op = MDB_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ /*
+ * Open a read transaction and cursor if needed.
+ */
+ if (dict_mdb->cursor == 0) {
+ if ((status = mdb_txn_begin(dict_mdb->env, NULL, MDB_RDONLY, &txn)))
+ msg_fatal("%s: txn_begin(read) dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+ if ((status = mdb_cursor_open(txn, dict_mdb->dbi, &dict_mdb->cursor)))
+ msg_fatal("%s: cursor_open dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+ }
+
+ /*
+ * Database lookup.
+ */
+ status = mdb_cursor_get(dict_mdb->cursor, &mdb_key, &mdb_value, op);
+ if (status && status != MDB_NOTFOUND)
+ msg_fatal("%s: seeking dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+
+ if (status == MDB_NOTFOUND) {
+ /*
+ * Caller must read to end, to ensure cursor gets closed.
+ */
+ status = 1;
+ txn = mdb_cursor_txn(dict_mdb->cursor);
+ mdb_cursor_close(dict_mdb->cursor);
+ mdb_txn_abort(txn);
+ dict_mdb->cursor = 0;
+ } else {
+
+ /*
+ * Copy the key so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_mdb->key_buf, mdb_key.mv_data, mdb_key.mv_size);
+
+ if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0) {
+
+ /*
+ * Copy the value so that it is guaranteed null terminated.
+ */
+ *value = SCOPY(dict_mdb->val_buf, mdb_value.mv_data, mdb_value.mv_size);
+ status = 0;
+ }
+ }
+
+ return (status);
+}
+
+/* dict_mdb_lock - noop lock handler */
+
+static int dict_mdb_lock(DICT *dict, int unused_op)
+{
+ /* MDB does its own concurrency control */
+ return 0;
+}
+
+/* dict_mdb_close - disassociate from data base */
+
+static void dict_mdb_close(DICT *dict)
+{
+ DICT_MDB *dict_mdb = (DICT_MDB *) dict;
+
+ if (dict_mdb->txn) {
+ int status = mdb_txn_commit(dict_mdb->txn);
+ if (status)
+ msg_fatal("%s: closing dictionary: %s", dict_mdb->dict.name, mdb_strerror(status));
+ dict_mdb->cursor = NULL;
+ }
+ if (dict_mdb->cursor) {
+ MDB_txn *txn = mdb_cursor_txn(dict_mdb->cursor);
+ mdb_cursor_close(dict_mdb->cursor);
+ mdb_txn_abort(txn);
+ }
+ if (dict_mdb->dict.stat_fd >= 0)
+ close(dict_mdb->dict.stat_fd);
+ mdb_env_close(dict_mdb->env);
+ if (dict_mdb->key_buf)
+ vstring_free(dict_mdb->key_buf);
+ if (dict_mdb->val_buf)
+ vstring_free(dict_mdb->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_mdb_open - open MDB data base */
+
+DICT *dict_mdb_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_MDB *dict_mdb;
+ struct stat st;
+ MDB_env *env;
+ MDB_txn *txn;
+ MDB_dbi dbi;
+ char *mdb_path;
+ int env_flags, status;
+
+ mdb_path = concatenate(path, ".mdb", (char *) 0);
+
+ env_flags = MDB_NOSUBDIR;
+ if (open_flags == O_RDONLY)
+ env_flags |= MDB_RDONLY;
+
+ if ((status = mdb_env_create(&env)))
+ msg_fatal("env_create %s: %s", mdb_path, mdb_strerror(status));
+
+ if ((status = mdb_env_set_mapsize(env, dict_mdb_map_size)))
+ msg_fatal("env_set_mapsize %s: %s", mdb_path, mdb_strerror(status));
+
+ if ((status = mdb_env_open(env, mdb_path, env_flags, 0644)))
+ msg_fatal("env_open %s: %s", mdb_path, mdb_strerror(status));
+
+ if ((status = mdb_txn_begin(env, NULL, 0, &txn)))
+ msg_fatal("txn_begin %s: %s", mdb_path, mdb_strerror(status));
+
+ /* mdb_open requires a txn, but since the default DB always exists
+ * in an MDB environment, we don't need to do anything else with
+ * the txn.
+ */
+ if ((status = mdb_open(txn, NULL, 0, &dbi)))
+ msg_fatal("mdb_open %s: %s", mdb_path, mdb_strerror(status));
+
+ /* However, if O_TRUNC was specified, we need to do it now.
+ * Also with O_TRUNC we keep this write txn for as long as the
+ * database is open, since we'll probably be doing a bulk import
+ * immediately after.
+ */
+ if (open_flags & O_TRUNC) {
+ if ((status = mdb_drop(txn, dbi, 0)))
+ msg_fatal("truncate %s: %s", mdb_path, mdb_strerror(status));
+ } else {
+ mdb_txn_abort(txn);
+ txn = NULL;
+ }
+
+ dict_mdb = (DICT_MDB *) dict_alloc(DICT_TYPE_MDB, path, sizeof(*dict_mdb));
+ dict_mdb->dict.lookup = dict_mdb_lookup;
+ dict_mdb->dict.update = dict_mdb_update;
+ dict_mdb->dict.delete = dict_mdb_delete;
+ dict_mdb->dict.sequence = dict_mdb_sequence;
+ dict_mdb->dict.close = dict_mdb_close;
+ dict_mdb->dict.lock = dict_mdb_lock;
+ dict_mdb->dict.stat_fd = open(mdb_path, O_RDONLY);
+ if (fstat(dict_mdb->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_mdb_open: fstat: %m");
+ dict_mdb->dict.mtime = st.st_mtime;
+ dict_mdb->dict.owner.uid = st.st_uid;
+ dict_mdb->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_mdb->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", mdb_path, path);
+
+ close_on_exec(dict_mdb->dict.stat_fd, CLOSE_ON_EXEC);
+ dict_mdb->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_mdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_mdb->dict.fold_buf = vstring_alloc(10);
+ dict_mdb->env = env;
+ dict_mdb->dbi = dbi;
+
+ /* Save the write txn if we opened with O_TRUNC */
+ dict_mdb->txn = txn;
+
+ dict_mdb->cursor = 0;
+ dict_mdb->key_buf = 0;
+ dict_mdb->val_buf = 0;
+
+ myfree(mdb_path);
+
+ return (DICT_DEBUG (&dict_mdb->dict));
+}
+
+#endif
diff --git a/src/util/dict_mdb.h b/src/util/dict_mdb.h
new file mode 100644
index 0000000..551d836
--- /dev/null
+++ b/src/util/dict_mdb.h
@@ -0,0 +1,40 @@
+#ifndef _DICT_MDB_H_INCLUDED_
+#define _DICT_MDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_mdb 3h
+/* SUMMARY
+/* dictionary manager interface to OpenLDAP MDB files
+/* SYNOPSIS
+/* #include <dict_mdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_MDB "mdb"
+
+extern DICT *dict_mdb_open(const char *, int, int);
+
+ /*
+ * XXX Should be part of the DICT interface.
+ */
+extern size_t dict_mdb_map_size;
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+#endif
diff --git a/src/util/dict_open.c b/src/util/dict_open.c
index 0f17785..53404cb 100644
--- a/src/util/dict_open.c
+++ b/src/util/dict_open.c
@@ -229,6 +229,7 @@
#include <dict_sdbm.h>
#include <dict_dbm.h>
#include <dict_db.h>
+#include <dict_mdb.h>
#include <dict_nis.h>
#include <dict_nisplus.h>
#include <dict_ni.h>
@@ -271,6 +272,9 @@ static const DICT_OPEN_INFO dict_open_info[] = {
DICT_TYPE_HASH, dict_hash_open,
DICT_TYPE_BTREE, dict_btree_open,
#endif
+#ifdef HAS_MDB
+ DICT_TYPE_MDB, dict_mdb_open,
+#endif
#ifdef HAS_NIS
DICT_TYPE_NIS, dict_nis_open,
#endif
--
1.7.9.5