chiark / gitweb /
REORG Delete everything that's not innduct or build system or changed for innduct
[inn-innduct.git] / storage / ovdb / ovdb.c
diff --git a/storage/ovdb/ovdb.c b/storage/ovdb/ovdb.c
deleted file mode 100644 (file)
index bd03481..0000000
+++ /dev/null
@@ -1,3004 +0,0 @@
-/*
- * ovdb.c
- * ovdb 2.00
- * Overview storage using BerkeleyDB 2.x/3.x/4.x
- *
- * 2004-02-17 : Need to track search cursors, since it's possible that
- *              ovdb_closesearch does not get called.  We now close
- *              any cursors still open in ovdb_close, or they'd be in
- *              the database indefinitely causing deadlocks.
- * 2002-08-13 : Change BOOL to bool, remove portability to < 2.4.
- * 2002-08-11 : Cleaned up use of sprintf and fixed a bunch of warnings.
- * 2002-02-28 : Update getartinfo for the overview API change in 2.4.  This
- *              breaks compatibility with INN 2.3.x....
- * 2000-12-12 : Add support for BerkeleyDB DB_SYSTEM_MEM option, controlled
- *            : by ovdb.conf 'useshm' and 'shmkey'
- * 2000-11-27 : Update for DB 3.2.x compatibility
- * 2000-11-13 : New 'readserver' feature
- * 2000-10-10 : ovdb_search now closes the cursor right after the last
- *              record is read.
- * 2000-10-05 : artnum member of struct datakey changed from ARTNUM to u_int32_t.
- *              OS's where sizeof(long)==8 will have to rebuild their databases
- *              after this update.
- * 2000-10-05 : from Dan Riley: struct datakey needs to be zero'd, for
- *              64-bit OSs where the struct has internal padding bytes.
- * 2000-09-29 : ovdb_expiregroup can now fix incorrect counts; use new
- *              inn/version.h so can have same ovdb.c for 2.3.0, 2.3.1, and 2.4
- * 2000-09-28 : low mark in ovdb_expiregroup still wasn't right
- * 2000-09-27 : Further improvements to ovdb_expiregroup: restructured the
- *              loop; now updates groupinfo as it goes along rather than
- *              counting records at the end, which prevents a possible
- *              deadlock.
- * 2000-09-19 : *lo wasn't being set in ovdb_expiregroup
- * 2000-09-15 : added ovdb_check_user(); tweaked some error msgs; fixed an
- *              improper use of RENEW
- * 2000-08-28:  New major release: version 2.00 (beta)
- *    + "groupsbyname" and "groupstats" databases replaced with "groupinfo".
- *    + ovdb_recover, ovdb_upgrade, and dbprocs are now deprecated; their
- *         functionality is now in ovdb_init and ovdb_monitor.
- *    + ovdb_init can upgrade a database from the old version of ovdb to
- *         work with this version.
- *    + Rewrote ovdb_expiregroup(); it can now re-write OV data rather
- *         than simply deleting old keys (which can leave 'holes' that result
- *         in inefficient disk-space use).
- *    + Add "nocompact" to ovdb.conf, which controls whether ovdb_expiregroup()
- *         rewrites OV data.
- *    + No longer needs the BerkeleyDB tools db_archive, db_checkpoint, and
- *         db_deadlock.  That functionality is now in ovdb_monitor.
- *    + ovdb_open() won't succeed if ovdb_monitor is not running.  This will
- *         prevent the problems that happen if the database is not regularly
- *         checkpointed and deadlock-tested.
- *    + Internal group IDs (32-bit ints) are now reused.
- *    + Add "maxlocks" to ovdb.conf, which will set the DB lk_max parameter.
- *    + Pull "test" code out into ovdb_stat.  ovdb_stat will also provide
- *         functionality similar to the BerkeleyDB "db_stat" command.
- *    + Update docs: write man pages for the new ovdb_* commands; update
- *         ovdb.pod
- *         
- * 2000-07-11 : fix possible alignment problem; add test code
- * 2000-07-07 : bugfix: timestamp handling
- * 2000-06-10 : Modified groupnum() interface; fix ovdb_add() to return false
- *              for certain groupnum() errors
- * 2000-06-08 : Added BerkeleyDB 3.1.x compatibility
- * 2000-04-09 : Tweak some default parameters; store aliased group info
- * 2000-03-29 : Add DB_RMW flag to the 'get' of get-modify-put sequences
- * 2000-02-17 : Update expire behavior to be consistent with current
- *              ov3 and buffindexed
- * 2000-01-13 : Fix to make compatible with unmodified nnrpd/article.c
- * 2000-01-04 : Added data versioning
- * 1999-12-20 : Added BerkeleyDB 3.x compatibility
- * 1999-12-06 : First Release -- H. Kehoe <hakehoe@avalon.net>
- */
-
-#include "config.h"
-#include "clibrary.h"
-#include "portable/socket.h"
-#include "portable/time.h"
-#include <errno.h>
-#include <fcntl.h>
-#ifdef HAVE_LIMITS_H
-# include <limits.h>
-#endif
-#include <pwd.h>
-#include <signal.h>
-#ifdef HAVE_SYS_SELECT_H
-# include <sys/select.h>
-#endif
-#include <syslog.h>
-
-#include "conffile.h"
-#include "inn/innconf.h"
-#include "inn/messages.h"
-#include "libinn.h"
-#include "paths.h"
-#include "storage.h"
-
-#include "ov.h"
-#include "ovinterface.h"
-#include "ovdb.h"
-#include "ovdb-private.h"
-
-#ifdef HAVE_UNIX_DOMAIN_SOCKETS
-# include <sys/un.h>
-#endif
-
-#ifndef USE_BERKELEY_DB
-
-/* Provide stub functions if we don't have db */
-
-bool ovdb_open(int mode UNUSED)
-{
-    syslog(L_FATAL, "OVDB: ovdb support not enabled");
-    return false;
-}
-
-bool ovdb_groupstats(char *group UNUSED, int *lo UNUSED, int *hi UNUSED, int *count UNUSED, int *flag UNUSED)
-{ return false; }
-
-bool ovdb_groupadd(char *group UNUSED, ARTNUM lo UNUSED, ARTNUM hi UNUSED, char *flag UNUSED)
-{ return false; }
-
-bool ovdb_groupdel(char *group UNUSED)
-{ return false; }
-
-bool ovdb_add(char *group UNUSED, ARTNUM artnum UNUSED, TOKEN token UNUSED, char *data UNUSED, int len UNUSED, time_t arrived UNUSED, time_t expires UNUSED)
-{ return false; }
-
-bool ovdb_cancel(TOKEN token UNUSED)
-{ return false; }
-
-void *ovdb_opensearch(char *group UNUSED, int low UNUSED, int high UNUSED)
-{ return NULL; }
-
-bool ovdb_search(void *handle UNUSED, ARTNUM *artnum UNUSED, char **data UNUSED, int *len UNUSED, TOKEN *token UNUSED, time_t *arrived UNUSED)
-{ return false; }
-
-void ovdb_closesearch(void *handle UNUSED) { }
-
-bool ovdb_getartinfo(char *group UNUSED, ARTNUM artnum UNUSED, TOKEN *token UNUSED)
-{ return false; }
-
-bool ovdb_expiregroup(char *group UNUSED, int *lo UNUSED, struct history *h UNUSED)
-{ return false; }
-
-bool ovdb_ctl(OVCTLTYPE type UNUSED, void *val UNUSED)
-{ return false; }
-
-void ovdb_close(void) { }
-
-#else /* USE_BERKELEY_DB */
-
-#define EXPIREGROUP_TXN_SIZE 100
-#define DELETE_TXN_SIZE 500
-
-struct ovdb_conf ovdb_conf;
-DB_ENV *OVDBenv = NULL;
-int ovdb_errmode = OVDB_ERR_SYSLOG;
-
-static int OVDBmode;
-static bool Cutofflow;
-static DB **dbs = NULL;
-static int oneatatime = 0;
-static int current_db = -1;
-static time_t eo_start = 0;
-static int clientmode = 0;
-
-static DB *groupinfo = NULL;
-static DB *groupaliases = NULL;
-
-#define OVDBtxn_nosync 1
-#define OVDBnumdbfiles 2
-#define OVDBpagesize   3
-#define OVDBcachesize  4
-#define OVDBminkey     5
-#define OVDBmaxlocks   6
-#define OVDBnocompact  7
-#define OVDBreadserver 8
-#define OVDBnumrsprocs 9
-#define OVDBmaxrsconn  10
-#define OVDBuseshm     11
-#define OVDBshmkey     12
-
-static CONFTOKEN toks[] = {
-  { OVDBtxn_nosync, "txn_nosync" },
-  { OVDBnumdbfiles, "numdbfiles" },
-  { OVDBpagesize, "pagesize" },
-  { OVDBcachesize, "cachesize" },
-  { OVDBminkey, "minkey" },
-  { OVDBmaxlocks, "maxlocks" },
-  { OVDBnocompact, "nocompact" },
-  { OVDBreadserver, "readserver" },
-  { OVDBnumrsprocs, "numrsprocs" },
-  { OVDBmaxrsconn, "maxrsconn" },
-  { OVDBuseshm, "useshm" },
-  { OVDBshmkey, "shmkey" },
-  { 0, NULL },
-};
-
-#define _PATH_OVDBCONF "ovdb.conf"
-
-/*********** readserver functions ***********/
-
-static int clientfd = -1;
-
-/* read client send and recieve functions. */
-
-static int
-csend(void *data, int n)
-{
-    ssize_t status;
-
-    if (n == 0)
-       return 0;
-    status = xwrite(clientfd, data, n);
-    if (status < 0)
-        syswarn("OVDB: rc: cant write");
-    return status;
-}
-
-static int crecv(void *data, int n)
-{
-    int r, p = 0;
-
-    if(n == 0)
-       return 0;
-
-    while(p < n) {
-       r = read(clientfd, (char *)data + p, n - p);
-       if(r <= 0) {
-           if(r < 0 && errno == EINTR)
-               continue;
-           syslog(LOG_ERR, "OVDB: rc: cant read: %m");
-           clientfd = -1;
-           exit(1);
-       }
-       p+= r;
-    }
-    return p;
-}
-
-/* Attempt to connect to the readserver.  If anything fails, we
-   return -1 so that ovdb_open can open the database directly. */
-
-static int client_connect()
-{
-    ssize_t r;
-    size_t p = 0;
-    char *path;
-#ifdef HAVE_UNIX_DOMAIN_SOCKETS
-    struct sockaddr_un sa;
-#else
-    struct sockaddr_in sa;
-#endif
-    char banner[sizeof(OVDB_SERVER_BANNER)];
-    fd_set fds;
-    struct timeval timeout;
-
-#ifdef HAVE_UNIX_DOMAIN_SOCKETS
-    clientfd = socket(AF_UNIX, SOCK_STREAM, 0);
-#else
-    clientfd = socket(AF_INET, SOCK_STREAM, 0);
-#endif
-    if(clientfd < 0) {
-       syslog(LOG_ERR, "OVDB: rc: socket: %m");
-       return -1;
-    }
-
-#ifdef HAVE_UNIX_DOMAIN_SOCKETS
-    sa.sun_family = AF_UNIX;
-    path = concatpath(innconf->pathrun, OVDB_SERVER_SOCKET);
-    strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
-    free(path);
-#else
-    sa.sin_family = AF_INET;
-    sa.sin_port = 0;
-    sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-    bind(clientfd, (struct sockaddr *) &sa, sizeof sa);
-    sa.sin_port = htons(OVDB_SERVER_PORT);
-#endif
-    if((r = connect(clientfd, (struct sockaddr *) &sa, sizeof sa)) != 0) {
-       syslog(LOG_ERR, "OVDB: rc: cant connect to server: %m");
-       close(clientfd);
-       clientfd = -1;
-       return -1;
-    }
-
-    while(p < sizeof(OVDB_SERVER_BANNER)) {
-       FD_ZERO(&fds);
-       FD_SET(clientfd, &fds);
-       timeout.tv_sec = 30;
-       timeout.tv_usec = 0;
-
-       r = select(clientfd+1, &fds, NULL, NULL, &timeout);
-
-       if(r < 0) {
-           if(errno == EINTR)
-               continue;
-           syslog(LOG_ERR, "OVDB: rc: select: %m");
-           close(clientfd);
-           clientfd = -1;
-           return -1;
-       }
-       if(r == 0) {
-           syslog(LOG_ERR, "OVDB: rc: timeout waiting for server");
-           close(clientfd);
-           clientfd = -1;
-           return -1;
-       }
-
-       r = read(clientfd, banner + p, sizeof(OVDB_SERVER_BANNER) - p);
-       if(r <= 0) {
-           if(r < 0 && errno == EINTR)
-               continue;
-           syslog(LOG_ERR, "OVDB: rc: cant read: %m");
-           close(clientfd);
-           clientfd = -1;
-           return -1;
-       }
-       p+= r;
-    }
-
-    if(memcmp(banner, OVDB_SERVER_BANNER, sizeof(OVDB_SERVER_BANNER))) {
-       syslog(LOG_ERR, "OVDB: rc: unknown reply from server");
-       close(clientfd);
-       clientfd = -1;
-       return -1;
-    }
-    return 0;
-}
-
-static void
-client_disconnect(void)
-{
-    struct rs_cmd rs;
-
-    if (clientfd != -1) {
-       rs.what = CMD_QUIT;
-       csend(&rs, sizeof(rs));
-    }
-    clientfd = -1;
-}
-
-
-/*********** internal functions ***********/
-
-#if DB_VERSION_MAJOR == 2
-char *db_strerror(int err)
-{
-    switch(err) {
-    case DB_RUNRECOVERY:
-       return "Recovery Needed";
-    default:
-       return strerror(err);
-    }
-}
-#endif /* DB_VERSION_MAJOR == 2 */
-
-
-static bool conf_bool_val(char *str, bool *value)
-{
-    if(strcasecmp(str, "on") == 0
-       || strcasecmp(str, "true") == 0
-       || strcasecmp(str, "yes") == 0) {
-       *value = true;
-       return true;
-    }
-    if(strcasecmp(str, "off") == 0
-       || strcasecmp(str, "false") == 0
-       || strcasecmp(str, "no") == 0) {
-       *value = false;
-       return true;
-    }
-    return false;
-}
-
-static bool conf_long_val(char *str, long *value)
-{
-    long v;
-
-    errno = 0;
-    v = strtol(str, NULL, 10);
-    if(v == 0 && errno != 0) {
-       return false;
-    }
-    *value = v;
-    return true;
-}
-
-void read_ovdb_conf(void)
-{
-    static int confread = 0;
-    int done = 0;
-    char *path;
-    CONFFILE *f;
-    CONFTOKEN *tok;
-    bool b;
-    long l;
-
-    if(confread)
-       return;
-
-    /* defaults */
-    ovdb_conf.home = innconf->pathoverview;
-    ovdb_conf.txn_nosync = 1;
-    ovdb_conf.numdbfiles = 32;
-    ovdb_conf.pagesize = 8192;
-    ovdb_conf.cachesize = 8000 * 1024;
-    ovdb_conf.minkey = 0;
-    ovdb_conf.maxlocks = 4000;
-    ovdb_conf.nocompact = 1;
-    ovdb_conf.readserver = 0;
-    ovdb_conf.numrsprocs = 5;
-    ovdb_conf.maxrsconn = 0;
-    ovdb_conf.useshm = 0;
-    ovdb_conf.shmkey = 6400;
-
-    path = concatpath(innconf->pathetc, _PATH_OVDBCONF);
-    f = CONFfopen(path);
-    free(path);
-
-    if(f) {
-       while(!done && (tok = CONFgettoken(toks, f))) {
-           switch(tok->type) {
-           case OVDBtxn_nosync:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_bool_val(tok->name, &b)) {
-                   ovdb_conf.txn_nosync = b;
-               }
-               break;
-           case OVDBnumdbfiles:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l > 0) {
-                   ovdb_conf.numdbfiles = l;
-               }
-               break;
-           case OVDBpagesize:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l > 0) {
-                   ovdb_conf.pagesize = l;
-               }
-               break;
-           case OVDBcachesize:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l > 0) {
-                   ovdb_conf.cachesize = l * 1024;
-               }
-               break;
-           case OVDBminkey:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l > 1) {
-                   ovdb_conf.minkey = l;
-               }
-               break;
-           case OVDBmaxlocks:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l > 0) {
-                   ovdb_conf.maxlocks = l;
-               }
-               break;
-           case OVDBnocompact:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l >= 0) {
-                   ovdb_conf.nocompact = l;
-               }
-               break;
-           case OVDBreadserver:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_bool_val(tok->name, &b)) {
-                   ovdb_conf.readserver = b;
-               }
-               break;
-           case OVDBnumrsprocs:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l > 0) {
-                   ovdb_conf.numrsprocs = l;
-               }
-               break;
-           case OVDBmaxrsconn:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l >= 0) {
-                   ovdb_conf.maxrsconn = l;
-               }
-               break;
-           case OVDBuseshm:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_bool_val(tok->name, &b)) {
-                   ovdb_conf.useshm = b;
-               }
-               break;
-           case OVDBshmkey:
-               tok = CONFgettoken(0, f);
-               if(!tok) {
-                   done = 1;
-                   continue;
-               }
-               if(conf_long_val(tok->name, &l) && l >= 0) {
-                   ovdb_conf.shmkey = l;
-               }
-               break;
-           }
-       }
-       CONFfclose(f);
-    }
-
-    /* If user did not specify minkey, choose one based on pagesize */
-    if(ovdb_conf.minkey == 0) {
-       ovdb_conf.minkey = ovdb_conf.pagesize / 2048 - 1;
-       if(ovdb_conf.minkey < 2)
-           ovdb_conf.minkey = 2;
-    }
-
-    confread = 1;
-}
-
-
-/* Function that db will use to report errors */
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
-static void OVDBerror(const DB_ENV *dbenv UNUSED, const char *db_errpfx UNUSED, const char *buffer)
-#else
-static void OVDBerror(const char *db_errpfx UNUSED, char *buffer)
-#endif
-{
-    switch(ovdb_errmode) {
-    case OVDB_ERR_SYSLOG:
-       syslog(L_ERROR, "OVDB: %s", buffer);
-       break;
-    case OVDB_ERR_STDERR:
-       fprintf(stderr, "OVDB: %s\n", buffer);
-       break;
-    }
-}
-
-static u_int32_t _db_flags = 0;
-#if DB_VERSION_MAJOR == 2
-static DB_INFO   _dbinfo;
-#endif
-
-static int open_db_file(int which)
-{
-    int ret;
-    char name[10];
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-    DB_TXN *tid;
-#endif
-
-    if(dbs[which] != NULL)
-       return 0;
-
-    snprintf(name, sizeof(name), "ov%05d", which);
-
-#if DB_VERSION_MAJOR == 2
-    ret = db_open(name, DB_BTREE, _db_flags, 0666, OVDBenv, &_dbinfo,
-                  &(dbs[which]));
-    if (ret != 0) {
-       dbs[which] = NULL;
-       return ret;
-    }
-#else
-    ret = db_create(&(dbs[which]), OVDBenv, 0);
-    if (ret != 0)
-       return ret;
-
-    if(ovdb_conf.minkey > 0)
-       (dbs[which])->set_bt_minkey(dbs[which], ovdb_conf.minkey);
-    if(ovdb_conf.pagesize > 0)
-       (dbs[which])->set_pagesize(dbs[which], ovdb_conf.pagesize);
-
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-    TXN_START(t_open_db_file, tid);
-    ret = (dbs[which])->open(dbs[which], tid, name, NULL, DB_BTREE, _db_flags,
-                             0666);
-    if (ret == 0)
-       TXN_COMMIT(t_open_db_file, tid);
-#else
-    ret = (dbs[which])->open(dbs[which], name, NULL, DB_BTREE, _db_flags,
-                             0666);
-#endif
-    if (ret != 0) {
-       (dbs[which])->close(dbs[which], 0);
-       dbs[which] = NULL;
-       return ret;
-    }
-#endif
-    return 0;
-}
-
-static void close_db_file(int which)
-{
-    if(which == -1 || dbs[which] == NULL)
-       return;
-    
-    dbs[which]->close(dbs[which], 0);
-    dbs[which] = NULL;
-}
-
-static int which_db(char *group)
-{
-    HASH grouphash;
-    unsigned int i;
-
-    grouphash = Hash(group, strlen(group));
-    memcpy(&i, &grouphash, sizeof(i));
-    return i % ovdb_conf.numdbfiles;
-}
-
-static DB *get_db_bynum(int which)
-{
-    int ret;
-    if(which >= ovdb_conf.numdbfiles)
-       return NULL;
-    if(oneatatime) {
-       if(which != current_db && current_db != -1)
-           close_db_file(current_db);
-
-        ret = open_db_file(which);
-       if (ret != 0)
-           syslog(L_ERROR, "OVDB: open_db_file failed: %s", db_strerror(ret));
-
-       current_db = which;
-    }
-    return(dbs[which]);
-}
-
-
-int ovdb_getgroupinfo(char *group, struct groupinfo *gi, int ignoredeleted, DB_TXN *tid, int getflags)
-{
-    int ret;
-    DBT key, val;
-
-    if(group == NULL)  /* just in case */
-       return DB_NOTFOUND;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    key.data = group;
-    key.size = strlen(group);
-    val.data = gi;
-    val.ulen = sizeof(struct groupinfo);
-    val.flags = DB_DBT_USERMEM;
-
-    ret = groupinfo->get(groupinfo, tid, &key, &val, getflags);
-    if (ret != 0)
-       return ret;
-
-    if(val.size != sizeof(struct groupinfo)) {
-       syslog(L_ERROR, "OVDB: wrong size for %s groupinfo (%u)",
-           group, val.size);
-       return DB_NOTFOUND;
-    }
-
-    if(ignoredeleted && (gi->status & GROUPINFO_DELETED))
-       return DB_NOTFOUND;
-
-    return 0;
-}
-
-#define GROUPID_MAX_FREELIST 10240
-#define GROUPID_MIN_FREELIST 100
-
-/* allocate a new group ID and return in gno */
-/* must be used in a transaction */
-static int groupid_new(group_id_t *gno, DB_TXN *tid)
-{
-    DBT key, val;
-    int ret, n;
-    group_id_t newgno, *freelist, one;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    key.data = (char *) "!groupid_freelist";
-    key.size = sizeof("!groupid_freelist");
-
-    ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
-    if (ret != 0) {
-       if(ret == DB_NOTFOUND) {
-           val.size = sizeof(group_id_t);
-           val.data = &one;
-           one = 1;
-       } else {
-           return ret;
-       }
-    }
-
-    if(val.size % sizeof(group_id_t)) {
-       syslog(L_ERROR, "OVDB: invalid size (%d) for !groupid_freelist",
-               val.size);
-       return EINVAL;
-    }
-
-    n = val.size / sizeof(group_id_t);
-    freelist = xmalloc(n * sizeof(group_id_t));
-    memcpy(freelist, val.data, val.size);
-    if(n <= GROUPID_MIN_FREELIST ) {
-       newgno = freelist[n-1];
-       (freelist[n-1])++;
-       val.data = freelist;
-    } else {
-       newgno = freelist[0];
-       val.data = &(freelist[1]);
-       val.size -= sizeof(group_id_t);
-    }
-
-    ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-    if (ret != 0) {
-       free(freelist);
-       return ret;
-    }
-
-    free(freelist);
-    *gno = newgno;
-    return 0;
-}
-
-/* mark given group ID as "unused" */
-/* must be used in a transaction */
-static int groupid_free(group_id_t gno, DB_TXN *tid)
-{
-    DBT key, val;
-    int ret, n, i;
-    group_id_t *freelist;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    key.data = (char *) "!groupid_freelist";
-    key.size = sizeof("!groupid_freelist");
-
-    ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
-    if (ret != 0) {
-       return ret;
-    }
-
-    if(val.size % sizeof(group_id_t)) {
-       syslog(L_ERROR, "OVDB: invalid size (%d) for !groupid_freelist",
-               val.size);
-       return EINVAL;
-    }
-
-    n = val.size / sizeof(group_id_t);
-    if(n > GROUPID_MAX_FREELIST)
-       return 0;
-    freelist = xmalloc((n + 1) * sizeof(group_id_t));
-    memcpy(freelist, val.data, val.size);
-
-    if(gno >= freelist[n-1]) { /* shouldn't happen */
-       free(freelist);
-       return 0;
-    }
-    for(i = 0; i < n-1; i++) {
-       if(gno == freelist[i]) {        /* already on freelist */
-           free(freelist);
-           return 0;
-       }
-    }
-       
-    freelist[n] = freelist[n-1];
-    freelist[n-1] = gno;
-    val.data = freelist;
-    val.size += sizeof(group_id_t);
-
-    ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-
-    free(freelist);
-    return ret;
-}
-
-/* Must be called outside of a transaction because it makes its own
-   transactions */
-static int delete_all_records(int whichdb, group_id_t gno)
-{
-    DB *db;
-    DBC *dbcursor;
-    DBT key, val;
-    struct datakey dk;
-    int count;
-    int ret;
-    DB_TXN *tid;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-    memset(&dk, 0, sizeof dk);
-
-    db = get_db_bynum(whichdb);
-    if(db == NULL)
-       return DB_NOTFOUND;
-
-    dk.groupnum = gno;
-    dk.artnum = 0;
-
-    while(1) {
-       TXN_START(t_del, tid);
-
-       /* get a cursor to traverse the ov records and delete them */
-       ret = db->cursor(db, tid, &dbcursor, 0);
-       switch(ret)
-       {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_del, tid);
-       default:
-           TXN_ABORT(t_del, tid);
-           syslog(L_ERROR, "OVDB: delete_all_records: db->cursor: %s", db_strerror(ret));
-           return ret;
-       }
-
-       key.data = &dk;
-       key.size = sizeof dk;
-       val.flags = DB_DBT_PARTIAL;
-
-       for(count = 0; count < DELETE_TXN_SIZE; count++) {
-           ret = dbcursor->c_get(dbcursor, &key, &val,
-                           count ? DB_NEXT : DB_SET_RANGE);
-           switch (ret)
-           {
-           case 0:
-               break;
-           case DB_NOTFOUND:
-               dbcursor->c_close(dbcursor);
-               TXN_COMMIT(t_del, tid);
-               return 0;
-           case TRYAGAIN:
-               dbcursor->c_close(dbcursor);
-               TXN_RETRY(t_del, tid);
-           default:
-               warn("OVDB: delete_all_records: DBcursor->c_get: %s",
-                    db_strerror(ret));
-               dbcursor->c_close(dbcursor);
-               TXN_ABORT(t_del, tid);
-               return ret;
-           }
-
-           if(key.size == sizeof dk
-                   && memcmp(key.data, &gno, sizeof gno)) {
-               break;
-           }
-
-           ret = dbcursor->c_del(dbcursor, 0);
-           switch (ret) {
-           case 0:
-           case DB_KEYEMPTY:
-               break;
-           case TRYAGAIN:
-               dbcursor->c_close(dbcursor);
-               TXN_RETRY(t_del, tid);
-           default:
-               warn("OVDB: delete_all_records: DBcursor->c_del: %s",
-                    db_strerror(ret));
-               dbcursor->c_close(dbcursor);
-               TXN_ABORT(t_del, tid);
-               return ret;
-           }
-       }
-       dbcursor->c_close(dbcursor);
-       TXN_COMMIT(t_del, tid);
-       if(count < DELETE_TXN_SIZE) {
-           break;
-       }
-    }
-    return 0;
-}
-
-/* Make a temporary groupinfo key using the given db number and group ID.
-   Must be called in a transaction */
-static int
-mk_temp_groupinfo(int whichdb, group_id_t gno, DB_TXN *tid)
-{
-    char keystr[1 + sizeof gno];
-    DBT key, val;
-    struct groupinfo gi;
-    int ret;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-    memset(&gi, 0, sizeof gi);
-
-    keystr[0] = 0;
-    memcpy(keystr + 1, &gno, sizeof gno);
-
-    gi.current_db = whichdb;
-    gi.current_gid = gno;
-    gi.status = GROUPINFO_DELETED;
-
-    key.data = keystr;
-    key.size = sizeof keystr;
-    val.data = &gi;
-    val.size = sizeof gi;
-
-    ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-    switch (ret) {
-    case 0:
-       break;
-    default:
-       syslog(L_ERROR, "OVDB: mk_temp_groupinfo: groupinfo->put: %s", db_strerror(ret));
-    case TRYAGAIN:
-       return ret;
-    }
-
-    return 0;
-}
-
-/* Delete a temporary groupinfo key created by mk_temp_groupid, then
-   frees the group id.
-   Must NOT be called within a transaction. */
-static int
-rm_temp_groupinfo(group_id_t gno)
-{
-    char keystr[1 + sizeof gno];
-    DB_TXN *tid;
-    DBT key;
-    int ret;
-
-    memset(&key, 0, sizeof key);
-
-    keystr[0] = 0;
-    memcpy(keystr + 1, &gno, sizeof gno);
-
-    key.data = keystr;
-    key.size = sizeof keystr;
-
-    TXN_START(t_tmp, tid);
-
-    ret = groupinfo->del(groupinfo, tid, &key, 0);
-    switch(ret) {
-    case 0:
-    case DB_NOTFOUND:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_tmp, tid);
-    default:
-       TXN_ABORT(t_tmp, tid);
-       syslog(L_ERROR, "OVDB: rm_temp_groupinfo: groupinfo->del: %s", db_strerror(ret));
-       return ret;
-    }
-
-    switch(groupid_free(gno, tid)) {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_tmp, tid);
-    default:
-       TXN_ABORT(t_tmp, tid);
-       syslog(L_ERROR, "OVDB: rm_temp_groupinfo: groupid_free: %s", db_strerror(ret));
-       return ret;
-    }
-
-    TXN_COMMIT(t_tmp, tid);
-    return 0;
-}
-
-/* This function deletes overview records for deleted or forgotton groups */
-/* argument: 0 = process deleted groups   1 = process forgotton groups */
-static bool delete_old_stuff(int forgotton)
-{
-    DBT key, val;
-    DBC *cursor;
-    DB_TXN *tid;
-    struct groupinfo gi;
-    char **dellist = NULL;
-    size_t *dellistsz = NULL;
-    int listlen, listcount;
-    int i, ret;
-
-    TXN_START(t_dellist, tid);
-    if (dellist != NULL) {
-        for (i = 0; i < listcount; ++i)
-            free(dellist[i]);
-        free(dellist);
-    }
-    if (dellistsz != NULL)
-       free(dellistsz);
-    listlen = 20;
-    listcount = 0;
-    dellist = xmalloc(listlen * sizeof(char *));
-    dellistsz = xmalloc(listlen * sizeof(size_t));
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    val.data = &gi;
-    val.ulen = val.dlen = sizeof gi;
-    val.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL;
-
-    /* get a cursor to traverse all of the groupinfo records */
-    ret = groupinfo->cursor(groupinfo, tid, &cursor, 0);
-    if (ret != 0) {
-       syslog(L_ERROR, "OVDB: delete_old_stuff: groupinfo->cursor: %s", db_strerror(ret));
-       free(dellist);
-       free(dellistsz);
-       return false;
-    }
-
-    while((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) {
-       if(key.size == sizeof("!groupid_freelist") &&
-               !strcmp("!groupid_freelist", key.data))
-           continue;
-       if(val.size != sizeof(struct groupinfo)) {
-           syslog(L_ERROR, "OVDB: delete_old_stuff: wrong size for groupinfo record");
-           continue;
-       }
-       if((!forgotton && (gi.status & GROUPINFO_DELETED))
-               || (forgotton && (gi.expired < eo_start))) {
-           dellist[listcount] = xmalloc(key.size);
-           memcpy(dellist[listcount], key.data, key.size);
-           dellistsz[listcount] = key.size;
-           listcount++;
-           if(listcount >= listlen) {
-               listlen += 20;
-                dellist = xrealloc(dellist, listlen * sizeof(char *));
-                dellistsz = xrealloc(dellistsz, listlen * sizeof(size_t));
-           }
-       }
-    }
-    cursor->c_close(cursor);
-    switch (ret) {
-    case 0:
-    case DB_NOTFOUND:
-        TXN_COMMIT(t_dellist, tid);
-        break;
-    case TRYAGAIN:
-        TXN_RETRY(t_dellist, tid);
-    default:
-        TXN_ABORT(t_dellist, tid);
-       syslog(L_ERROR, "OVDB: delete_old_stuff: cursor->c_get: %s", db_strerror(ret));
-        for (i = 0; i < listcount; ++i)
-            free(dellist[i]);
-        free(dellist);
-        free(dellistsz);
-        return false;
-    }
-
-    for(i = 0; i < listcount; i++) {
-       TXN_START(t_dos, tid);
-
-       /* Can't use ovdb_getgroupinfo here */
-       key.data = dellist[i];
-       key.size = dellistsz[i];
-       val.data = &gi;
-       val.ulen = sizeof(struct groupinfo);
-       val.flags = DB_DBT_USERMEM;
-
-       ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
-
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_dos, tid);
-       case DB_NOTFOUND:
-           TXN_ABORT(t_dos, tid);
-           continue;
-       default:
-           syslog(L_ERROR, "OVDB: delete_old_stuff: groupinfo->get: %s\n", db_strerror(ret));
-           TXN_ABORT(t_dos, tid);
-           continue;
-       }
-       if(val.size != sizeof(struct groupinfo)) {
-           TXN_ABORT(t_dos, tid);
-           continue;
-       }
-
-       /* This if() is true if this ISN'T a key created by mk_temp_groupinfo */
-       if(*((char *)key.data) != 0 || key.size != 1 + sizeof(group_id_t)) {
-
-           switch(mk_temp_groupinfo(gi.current_db, gi.current_gid, tid)) {
-           case 0:
-               break;
-           case TRYAGAIN:
-               TXN_RETRY(t_dos, tid);
-           default:
-               TXN_ABORT(t_dos, tid);
-               continue;
-           }
-           if(gi.status & GROUPINFO_MOVING) {
-               switch(mk_temp_groupinfo(gi.new_db, gi.new_gid, tid)) {
-               case 0:
-                   break;
-               case TRYAGAIN:
-                   TXN_RETRY(t_dos, tid);
-               default:
-                   TXN_ABORT(t_dos, tid);
-                   continue;
-               }
-           }
-
-           /* delete the corresponding groupaliases record (if exists) */
-           switch(groupaliases->del(groupaliases, tid, &key, 0)) {
-           case 0:
-           case DB_NOTFOUND:
-               break;
-           case TRYAGAIN:
-               TXN_RETRY(t_dos, tid);
-           default:
-               TXN_ABORT(t_dos, tid);
-               continue;
-           }
-
-           switch(groupinfo->del(groupinfo, tid, &key, 0)) {
-           case 0:
-           case DB_NOTFOUND:
-               break;
-           case TRYAGAIN:
-               TXN_RETRY(t_dos, tid);
-           default:
-               TXN_ABORT(t_dos, tid);
-               continue;
-           }
-       }
-
-       TXN_COMMIT(t_dos, tid);
-
-       if(delete_all_records(gi.current_db, gi.current_gid) == 0) {
-           rm_temp_groupinfo(gi.current_gid);
-       }
-       if(gi.status & GROUPINFO_MOVING) {
-           if(delete_all_records(gi.new_db, gi.new_gid) == 0) {
-               rm_temp_groupinfo(gi.new_gid);
-           }
-       }
-    }
-out:
-    for(i = 0; i < listcount; i++)
-       free(dellist[i]);
-    free(dellist);
-    free(dellistsz);
-    return true;
-}
-
-static int count_records(struct groupinfo *gi)
-{
-    int ret;
-    DB *db;
-    DBC *cursor;
-    DBT key, val;
-    struct datakey dk;
-    u_int32_t artnum, newlow = 0;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-    memset(&dk, 0, sizeof dk);
-
-    db = get_db_bynum(gi->current_db);
-    if(db == NULL)
-       return DB_NOTFOUND;
-
-    dk.groupnum = gi->current_gid;
-    dk.artnum = 0;
-    key.data = &dk;
-    key.size = key.ulen = sizeof dk;
-    key.flags = DB_DBT_USERMEM;
-    val.flags = DB_DBT_PARTIAL;
-
-    gi->count = 0;
-
-    ret = db->cursor(db, NULL, &cursor, 0);
-    if (ret)
-       return ret;
-
-    ret = cursor->c_get(cursor, &key, &val, DB_SET_RANGE);
-    switch (ret)
-    {
-    case 0:
-    case DB_NOTFOUND:
-       break;
-    default:
-       cursor->c_close(cursor);
-       return ret;
-    }
-    while(ret == 0 && key.size == sizeof(dk) && dk.groupnum == gi->current_gid) {
-       artnum = ntohl(dk.artnum);
-       if(newlow == 0 || newlow > artnum)
-           newlow = artnum;
-       if(artnum > gi->high)
-           gi->high = artnum;
-       gi->count++;
-
-       ret = cursor->c_get(cursor, &key, &val, DB_NEXT);
-    }
-    cursor->c_close(cursor);
-    if(gi->count == 0)
-       gi->low = gi->high + 1;
-    else
-       gi->low = newlow;
-
-    if(ret == DB_NOTFOUND)
-       return 0;
-    return ret;
-}
-
-
-/*
- * Locking:  OVopen() calls ovdb_getlock(OVDB_LOCK_NORMAL).  This
- * aquires a read (shared) lock on the lockfile.  Multiple processes
- * can have this file locked at the same time.  That way, if there
- * are any processes that are using the database, the lock file will
- * have one or more shared locks on it.
- *
- * ovdb_init, when starting, calls ovdb_getlock(OVDB_LOCK_EXCLUSIVE).
- * This tries to get a write (exclusive) lock, which will fail if
- * anyone has a shared lock.  This way, ovdb_init can tell if there
- * are any processes using the database.  If not, and the excl. lock
- * succeeds, ovdb_init is free to do DB_RUNRECOVER.
- *
- * ovdb_getlock() in the "normal" lock mode calls ovdb_check_monitor,
- * which looks for the OVDB_MONITOR_PIDFILE.  If said file does not
- * exist, or the PID in it does not exist, it will fail.  This will
- * prevent OVopen() from opening the database if ovdb_monitor is not running.
- *
- * The OVDB_LOCK_ADMIN mode is used by ovdb_monitor to get a shared lock
- * without testing the pidfile.
- */
-static int lockfd = -1;
-bool ovdb_getlock(int mode)
-{
-    if(lockfd == -1) {
-       char *lockfn = concatpath(innconf->pathrun, OVDB_LOCKFN);
-       lockfd = open(lockfn,
-               mode == OVDB_LOCK_NORMAL ? O_RDWR : O_CREAT|O_RDWR, 0660);
-       if(lockfd == -1) {
-           free(lockfn);
-           if(errno == ENOENT)
-               syslog(L_FATAL, "OVDB: can not open database unless ovdb_monitor is running.");
-           return false;
-       }
-       close_on_exec(lockfd, true);
-       free(lockfn);
-    } else
-       return true;
-
-    if(mode == OVDB_LOCK_NORMAL) {
-       if(!ovdb_check_pidfile(OVDB_MONITOR_PIDFILE)) {
-           syslog(L_FATAL, "OVDB: can not open database unless ovdb_monitor is running.");
-           return false;
-       }
-    }
-    if(mode == OVDB_LOCK_EXCLUSIVE) {
-       if(!inn_lock_file(lockfd, INN_LOCK_WRITE, false)) {
-           close(lockfd);
-           lockfd = -1;
-           return false;
-       }
-       return true;
-    } else {
-       if(!inn_lock_file(lockfd, INN_LOCK_READ, false)) {
-           close(lockfd);
-           lockfd = -1;
-           return false;
-       }
-       return true;
-    }
-}
-
-bool ovdb_releaselock(void)
-{
-    bool r;
-    if(lockfd == -1)
-       return true;
-    r = inn_lock_file(lockfd, INN_LOCK_UNLOCK, false);
-    close(lockfd);
-    lockfd = -1;
-    return r;
-}
-
-bool ovdb_check_pidfile(char *file)
-{
-    int f, pid;
-    char buf[SMBUF];
-    char *pidfn = concatpath(innconf->pathrun, file);
-
-    f = open(pidfn, O_RDONLY);
-    if(f == -1) {
-       if(errno != ENOENT)
-           syslog(L_FATAL, "OVDB: can't open %s: %m", pidfn);
-       free(pidfn);
-       return false;
-    }
-    memset(buf, 0, SMBUF);
-    if(read(f, buf, SMBUF-1) < 0) {
-       syslog(L_FATAL, "OVDB: can't read from %s: %m", pidfn);
-       free(pidfn);
-       close(f);
-       return false;
-    }
-    close(f);
-    free(pidfn);
-    pid = atoi(buf);
-    if(pid <= 1) {
-       return false;
-    }
-    if(kill(pid, 0) < 0 && errno == ESRCH) {
-       return false;
-    }
-    return true;
-}
-
-/* make sure the effective uid is that of NEWSUSER */
-bool ovdb_check_user(void)
-{
-    struct passwd *p;
-    static int result = -1;
-
-    if(result == -1) {
-       p = getpwnam(NEWSUSER);
-       if(!p) {
-           syslog(L_FATAL, "OVDB: getpwnam(" NEWSUSER ") failed: %m");
-           return false;
-       }
-       result = (p->pw_uid == geteuid());
-    }
-    return result;
-}
-
-static int check_version(void)
-{
-    int ret;
-    DB *vdb;
-    DBT key, val;
-    u_int32_t dv;
-
-#if DB_VERSION_MAJOR == 2
-    DB_INFO dbinfo;
-    memset(&dbinfo, 0, sizeof dbinfo);
-
-    ret = db_open("version", DB_BTREE, _db_flags, 0666, OVDBenv, &dbinfo,
-                  &vdb);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: db_open failed: %s", db_strerror(ret));
-       return ret;
-    }
-#else
-    /* open version db */
-    ret = db_create(&vdb, OVDBenv, 0);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: open: db_create: %s", db_strerror(ret));
-       return ret;
-    }
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-    ret = vdb->open(vdb, NULL, "version", NULL, DB_BTREE, _db_flags, 0666);
-#else
-    ret = vdb->open(vdb, "version", NULL, DB_BTREE, _db_flags, 0666);
-#endif
-    if (ret != 0) {
-       vdb->close(vdb, 0);
-       syslog(L_FATAL, "OVDB: open: version->open: %s", db_strerror(ret));
-       return ret;
-    }
-#endif
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-    key.data = (char *) "dataversion";
-    key.size = sizeof("dataversion");
-    ret = vdb->get(vdb, NULL, &key, &val, 0);
-    if (ret != 0) {
-       if(ret != DB_NOTFOUND) {
-           syslog(L_FATAL, "OVDB: open: can't retrieve version: %s", db_strerror(ret));
-           vdb->close(vdb, 0);
-           return ret;
-       }
-    }
-    if(ret == DB_NOTFOUND || val.size != sizeof dv) {
-       dv = DATA_VERSION;
-       if(!(OVDBmode & OV_WRITE)) {
-           vdb->close(vdb, 0);
-           return EACCES;
-       }
-       val.data = &dv;
-       val.size = sizeof dv;
-        ret = vdb->put(vdb, NULL, &key, &val, 0);
-       if (ret != 0) {
-           syslog(L_FATAL, "OVDB: open: can't store version: %s", db_strerror(ret));
-           vdb->close(vdb, 0);
-           return ret;
-       }
-    } else
-       memcpy(&dv, val.data, sizeof dv);
-
-    if(dv > DATA_VERSION) {
-       syslog(L_FATAL, "OVDB: can't open database: unknown version %d", dv);
-       vdb->close(vdb, 0);
-       return EINVAL;
-    }
-    if(dv < DATA_VERSION) {
-       syslog(L_FATAL, "OVDB: database is an old version, please run ovdb_init");
-       vdb->close(vdb, 0);
-       return EINVAL;
-    }
-
-    /* The version db also stores some config values, which will override the
-       corresponding ovdb.conf setting. */
-
-    key.data = (char *) "numdbfiles";
-    key.size = sizeof("numdbfiles");
-    ret = vdb->get(vdb, NULL, &key, &val, 0);
-    if (ret != 0) {
-       if(ret != DB_NOTFOUND) {
-           syslog(L_FATAL, "OVDB: open: can't retrieve numdbfiles: %s", db_strerror(ret));
-           vdb->close(vdb, 0);
-           return ret;
-       }
-    }
-    if(ret == DB_NOTFOUND || val.size != sizeof(ovdb_conf.numdbfiles)) {
-       if(!(OVDBmode & OV_WRITE)) {
-           vdb->close(vdb, 0);
-           return EACCES;
-       }
-       val.data = &(ovdb_conf.numdbfiles);
-       val.size = sizeof(ovdb_conf.numdbfiles);
-        ret = vdb->put(vdb, NULL, &key, &val, 0);
-       if (ret != 0) {
-           syslog(L_FATAL, "OVDB: open: can't store numdbfiles: %s", db_strerror(ret));
-           vdb->close(vdb, 0);
-           return ret;
-       }
-    } else {
-       memcpy(&(ovdb_conf.numdbfiles), val.data, sizeof(ovdb_conf.numdbfiles));
-    }
-
-    vdb->close(vdb, 0);
-    return 0;
-}
-
-
-int ovdb_open_berkeleydb(int mode, int flags)
-{
-    int ret;
-    u_int32_t ai_flags = DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN;
-
-    OVDBmode = mode;
-    read_ovdb_conf();
-
-    if(OVDBenv != NULL)
-       return 0;       /* already opened */
-
-    if(OVDBmode & OV_WRITE) {
-       _db_flags |= DB_CREATE;
-       ai_flags |= DB_CREATE;
-    } else {
-       _db_flags |= DB_RDONLY;
-    }
-    if(flags & OVDB_RECOVER)
-       ai_flags |= DB_RECOVER;
-
-#if DB_VERSION_MAJOR == 2 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR < 2)
-    if(ovdb_conf.txn_nosync)
-       ai_flags |= DB_TXN_NOSYNC;
-#endif
-
-#if DB_VERSION_MAJOR == 2
-
-    OVDBenv = xcalloc(1, sizeof(DB_ENV));
-
-    OVDBenv->db_errcall = OVDBerror;
-    OVDBenv->mp_size = ovdb_conf.cachesize;
-    OVDBenv->lk_max = ovdb_conf.maxlocks;
-
-    /* initialize environment */
-    ret = db_appinit(ovdb_conf.home, NULL, OVDBenv, ai_flags);
-    if (ret != 0) {
-       free(OVDBenv);
-       OVDBenv = NULL;
-       syslog(L_FATAL, "OVDB: db_appinit failed: %s", db_strerror(ret));
-       return ret;
-    }
-#else
-    ret = db_env_create(&OVDBenv, 0);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: db_env_create: %s", db_strerror(ret));
-       return ret;
-    }
-
-    if((flags & (OVDB_UPGRADE | OVDB_RECOVER)) == (OVDB_UPGRADE | OVDB_RECOVER))
-       ai_flags |= DB_PRIVATE;
-    if(!(ai_flags & DB_PRIVATE)) {
-       if(ovdb_conf.useshm)
-           ai_flags |= DB_SYSTEM_MEM;
-#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR > 0)
-       OVDBenv->set_shm_key(OVDBenv, ovdb_conf.shmkey);
-#endif
-    }
-
-    OVDBenv->set_errcall(OVDBenv, OVDBerror);
-    OVDBenv->set_cachesize(OVDBenv, 0, ovdb_conf.cachesize, 1);
-#if DB_VERSION_MAJOR >= 4
-    OVDBenv->set_lk_max_locks(OVDBenv, ovdb_conf.maxlocks);
-    OVDBenv->set_lk_max_lockers(OVDBenv, ovdb_conf.maxlocks);
-    OVDBenv->set_lk_max_objects(OVDBenv, ovdb_conf.maxlocks);
-#else
-    OVDBenv->set_lk_max(OVDBenv, ovdb_conf.maxlocks);
-#endif
-
-#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
-    if(ovdb_conf.txn_nosync)
-       OVDBenv->set_flags(OVDBenv, DB_TXN_NOSYNC, 1);
-#endif
-
-    if((flags & (OVDB_UPGRADE | OVDB_RECOVER)) != OVDB_UPGRADE) {
-#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
-       ret = OVDBenv->open(OVDBenv, ovdb_conf.home, NULL, ai_flags, 0666);
-#else
-       ret = OVDBenv->open(OVDBenv, ovdb_conf.home, ai_flags, 0666);
-#endif
-       if (ret != 0) {
-           OVDBenv->close(OVDBenv, 0);
-           OVDBenv = NULL;
-           syslog(L_FATAL, "OVDB: OVDBenv->open: %s", db_strerror(ret));
-           return ret;
-       }
-    }
-#endif /* DB_VERSION_MAJOR == 2 */
-
-    return 0;
-}
-
-bool ovdb_open(int mode)
-{
-    int i, ret;
-#if DB_VERSION_MAJOR == 2
-    DB_INFO dbinfo;
-#else
-    DB_TXN *tid;
-#endif
-
-    if(OVDBenv != NULL || clientmode) {
-       syslog(L_ERROR, "OVDB: ovdb_open called more than once");
-       return false;
-    }
-
-    read_ovdb_conf();
-    if(ovdb_conf.readserver && mode == OV_READ)
-       clientmode = 1;
-
-    if(mode & OVDB_SERVER)
-       clientmode = 0;
-
-    if(clientmode) {
-       if(client_connect() == 0)
-           return true;
-       clientmode = 0;
-    }
-    if(!ovdb_check_user()) {
-       syslog(L_FATAL, "OVDB: must be running as " NEWSUSER " to access overview.");
-       return false;
-    }
-    if(!ovdb_getlock(OVDB_LOCK_NORMAL)) {
-       syslog(L_FATAL, "OVDB: ovdb_getlock failed: %m");
-       return false;
-    }
-
-    if(ovdb_open_berkeleydb(mode, 0) != 0)
-       return false;
-
-    if(check_version() != 0)
-       return false;
-
-    if(mode & OV_WRITE || mode & OVDB_SERVER) {
-       oneatatime = 0;
-    } else {
-       oneatatime = 1;
-    }
-
-#if DB_VERSION_MAJOR == 2
-    memset(&_dbinfo, 0, sizeof _dbinfo);
-    _dbinfo.db_pagesize = ovdb_conf.pagesize;
-    _dbinfo.bt_minkey = ovdb_conf.minkey;
-#endif
-
-    dbs = xcalloc(ovdb_conf.numdbfiles, sizeof(DB *));
-    
-    if(!oneatatime) {
-       for(i = 0; i < ovdb_conf.numdbfiles; i++) {
-            ret = open_db_file(i);
-           if (ret != 0) {
-               syslog(L_FATAL, "OVDB: open_db_file failed: %s", db_strerror(ret));
-               return false;
-           }
-       }
-    }
-
-#if DB_VERSION_MAJOR == 2
-    memset(&dbinfo, 0, sizeof dbinfo);
-
-    ret = db_open("groupinfo", DB_BTREE, _db_flags, 0666, OVDBenv,
-                  &dbinfo, &groupinfo);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: db_open failed: %s", db_strerror(ret));
-       return false;
-    }
-
-    ret = db_open("groupaliases", DB_HASH, _db_flags, 0666, OVDBenv,
-                  &dbinfo, &groupaliases);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: db_open failed: %s", db_strerror(ret));
-       return false;
-    }
-#else
-    ret = db_create(&groupinfo, OVDBenv, 0);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: open: db_create: %s", db_strerror(ret));
-       return false;
-    }
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-    TXN_START(t_open_groupinfo, tid);
-    ret = groupinfo->open(groupinfo, tid, "groupinfo", NULL, DB_BTREE,
-                          _db_flags, 0666);
-    if (ret == 0)
-       TXN_COMMIT(t_open_groupinfo, tid);
-#else
-    ret = groupinfo->open(groupinfo, "groupinfo", NULL, DB_BTREE,
-                          _db_flags, 0666);
-#endif
-    if (ret != 0) {
-       groupinfo->close(groupinfo, 0);
-       syslog(L_FATAL, "OVDB: open: groupinfo->open: %s", db_strerror(ret));
-       return false;
-    }
-    ret = db_create(&groupaliases, OVDBenv, 0);
-    if (ret != 0) {
-       syslog(L_FATAL, "OVDB: open: db_create: %s", db_strerror(ret));
-       return false;
-    }
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-    TXN_START(t_open_groupaliases, tid);
-    ret = groupaliases->open(groupaliases, tid, "groupaliases", NULL, DB_HASH,
-                             _db_flags, 0666);
-    if (ret == 0)
-       TXN_COMMIT(t_open_groupaliases, tid);
-#else
-    ret = groupaliases->open(groupaliases, "groupaliases", NULL, DB_HASH,
-                             _db_flags, 0666);
-#endif
-    if (ret != 0) {
-       groupaliases->close(groupaliases, 0);
-       syslog(L_FATAL, "OVDB: open: groupaliases->open: %s", db_strerror(ret));
-       return false;
-    }
-#endif
-
-    Cutofflow = false;
-    return true;
-}
-
-
-bool
-ovdb_groupstats(char *group, int *lo, int *hi, int *count, int *flag)
-{
-    int ret;
-    struct groupinfo gi;
-
-    if (clientmode) {
-       struct rs_cmd rs;
-       struct rs_groupstats repl;
-
-       rs.what = CMD_GROUPSTATS;
-       rs.grouplen = strlen(group)+1;
-
-       if (csend(&rs, sizeof(rs)) < 0)
-            return false;
-       if (csend(group, rs.grouplen) < 0)
-            return false;
-       crecv(&repl, sizeof(repl));
-
-       if(repl.status != CMD_GROUPSTATS)
-           return false;
-
-       /* we don't use the alias yet, but the OV API will be extended
-          at some point so that the alias is returned also */
-       if(repl.aliaslen != 0) {
-           char *buf = xmalloc(repl.aliaslen);
-           crecv(buf, repl.aliaslen);
-           free(buf);
-       }
-
-       if(lo)
-           *lo = repl.lo;
-       if(hi)
-           *hi = repl.hi;
-       if(count)
-           *count = repl.count;
-       if(flag)
-           *flag = repl.flag;
-       return true;
-    }
-
-    ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case DB_NOTFOUND:
-       return false;
-    default:
-       syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
-       return false;
-    }
-
-    if(lo != NULL)
-       *lo = gi.low;
-    if(hi != NULL)
-       *hi = gi.high;
-    if(count != NULL)
-       *count = gi.count;
-    if(flag != NULL)
-       *flag = gi.flag;
-    return true;
-}
-
-bool ovdb_groupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag)
-{
-    DBT key, val;
-    struct groupinfo gi;
-    DB_TXN *tid;
-    int ret = 0;
-    int new;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    TXN_START(t_groupadd, tid);
-
-    if(tid==NULL)
-       return false;
-
-    new = 0;
-    ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
-    switch (ret)
-    {
-    case DB_NOTFOUND:
-       new = 1;
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_groupadd, tid);
-    default:
-       TXN_ABORT(t_groupadd, tid);
-       syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
-       return false;
-    }
-
-    if(!new && (gi.status & GROUPINFO_DELETED)
-               && !(gi.status & GROUPINFO_EXPIRING)
-               && !(gi.status & GROUPINFO_MOVING)) {
-       int s, c = 0;
-       char g[MAXHEADERSIZE];
-
-       strlcpy(g, group, sizeof(g));
-       s = strlen(g) + 1;
-       key.data = g;
-       key.size = s + sizeof(int);
-       do {
-           c++;
-           memcpy(g+s, &c, sizeof(int));
-           ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
-       } while(ret == 0);
-       if(ret == TRYAGAIN) {
-           TXN_RETRY(t_groupadd, tid);
-       }
-       val.data = &gi;
-       val.size = sizeof(gi);
-        ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_groupadd, tid);
-       default:
-           TXN_ABORT(t_groupadd, tid);
-           syslog(L_ERROR, "OVDB: groupinfo->put: %s", db_strerror(ret));
-           return false;
-       }
-       key.data = group;
-       key.size = strlen(group);
-        ret = groupinfo->del(groupinfo, tid, &key, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_groupadd, tid);
-       default:
-           TXN_ABORT(t_groupadd, tid);
-           syslog(L_ERROR, "OVDB: groupinfo->del: %s", db_strerror(ret));
-           return false;
-       }
-       new = 1;
-    }
-
-    if(new) {
-        ret = groupid_new(&gi.current_gid, tid);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_groupadd, tid);
-       default:
-           TXN_ABORT(t_groupadd, tid);
-           syslog(L_ERROR, "OVDB: groupid_new: %s", db_strerror(ret));
-           return false;
-       }
-       gi.low = lo ? lo : 1;
-       gi.high = hi;
-       gi.count = 0;
-       gi.flag = *flag;
-       gi.expired = time(NULL);
-       gi.expiregrouppid = 0;
-       gi.current_db = gi.new_db = which_db(group);
-       gi.new_gid = gi.current_gid;
-       gi.status = 0;
-    } else {
-       gi.flag = *flag;
-    }
-
-    key.data = group;
-    key.size = strlen(group);
-    val.data = &gi;
-    val.size = sizeof gi;
-
-    ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-    switch (ret) {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_groupadd, tid);
-    default:
-       TXN_ABORT(t_groupadd, tid);
-       syslog(L_ERROR, "OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
-       return false;
-    }
-
-    if(*flag == '=') {
-       key.data = group;
-       key.size = strlen(group);
-       val.data = flag + 1;
-       val.size = strcspn(flag, "\n") - 1;
-
-       switch(ret = groupaliases->put(groupaliases, tid, &key, &val, 0)) {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_groupadd, tid);
-       default:
-           TXN_ABORT(t_groupadd, tid);
-           syslog(L_ERROR, "OVDB: groupadd: groupaliases->put: %s", db_strerror(ret));
-           return false;
-       }
-    }
-
-    TXN_COMMIT(t_groupadd, tid);
-    return true;
-}
-
-bool ovdb_groupdel(char *group)
-{
-    DBT key, val;
-    struct groupinfo gi;
-    DB_TXN *tid;
-    int ret = 0;
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    /* We only need to set the deleted flag in groupinfo to prevent readers
-       from seeing this group.  The actual overview records aren't deleted
-       now, since that could take a significant amount of time (and innd
-       is who normally calls this function).  The expireover run will
-       clean up the deleted groups. */
-
-    TXN_START(t_groupdel, tid);
-
-    if(tid==NULL)
-       return false;
-
-    ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
-    switch (ret)
-    {
-    case DB_NOTFOUND:
-       return true;
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_groupdel, tid);
-    default:
-       syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
-       TXN_ABORT(t_groupdel, tid);
-       return false;
-    }
-
-    gi.status |= GROUPINFO_DELETED;
-
-    key.data = group;
-    key.size = strlen(group);
-    val.data = &gi;
-    val.size = sizeof gi;
-
-    switch(ret = groupinfo->put(groupinfo, tid, &key, &val, 0)) {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_groupdel, tid);
-    default:
-       TXN_ABORT(t_groupdel, tid);
-       syslog(L_ERROR, "OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
-       return false;
-    }
-
-    switch(ret = groupaliases->del(groupaliases, tid, &key, 0)) {
-    case 0:
-    case DB_NOTFOUND:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_groupdel, tid);
-    default:
-       syslog(L_ERROR, "OVDB: groupdel: groupaliases->del: %s", db_strerror(ret));
-       TXN_ABORT(t_groupdel, tid);
-       return false;
-    }
-
-    TXN_COMMIT(t_groupdel, tid);
-    return true;
-}
-
-bool ovdb_add(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires)
-{
-    static size_t databuflen = 0;
-    static char *databuf;
-    DB         *db;
-    DBT                key, val;
-    DB_TXN     *tid;
-    struct groupinfo gi;
-    struct datakey dk;
-    int ret = 0;
-
-    memset(&dk, 0, sizeof dk);
-
-    if(databuflen == 0) {
-       databuflen = BIG_BUFFER;
-       databuf = xmalloc(databuflen);
-    }
-    if(databuflen < len + sizeof(struct ovdata)) {
-       databuflen = len + sizeof(struct ovdata);
-        databuf = xrealloc(databuf, databuflen);
-    }
-
-    /* hmm... BerkeleyDB needs something like a 'struct iovec' so that we don't
-       have to make a new buffer and copy everything in to it */
-
-    ((struct ovdata *)databuf)->token = token;
-    ((struct ovdata *)databuf)->arrived = arrived;
-    ((struct ovdata *)databuf)->expires = expires;
-    memcpy(databuf + sizeof(struct ovdata), data, len);
-    len += sizeof(struct ovdata);
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-
-    /* start the transaction */
-    TXN_START(t_add, tid);
-
-    if(tid==NULL)
-       return false;
-
-    /* first, retrieve groupinfo */
-    ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case DB_NOTFOUND:
-       TXN_ABORT(t_add, tid);
-       return true;
-    case TRYAGAIN:
-       TXN_RETRY(t_add, tid);
-    default:
-       TXN_ABORT(t_add, tid);
-       syslog(L_ERROR, "OVDB: add: ovdb_getgroupinfo: %s", db_strerror(ret));
-       return false;
-    }
-
-    /* adjust groupinfo */
-    if(Cutofflow && gi.low > artnum) {
-       TXN_ABORT(t_add, tid);
-       return true;
-    }
-    if(gi.low == 0 || gi.low > artnum)
-       gi.low = artnum;
-    if(gi.high < artnum)
-       gi.high = artnum;
-    gi.count++;
-
-    /* store groupinfo */
-    key.data = group;
-    key.size = strlen(group);
-    val.data = &gi;
-    val.size = sizeof gi;
-
-    ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_add, tid);
-    default:
-       TXN_ABORT(t_add, tid);
-       syslog(L_ERROR, "OVDB: add: groupinfo->put: %s", db_strerror(ret));
-       return false;
-    }
-
-    /* store overview */
-    db = get_db_bynum(gi.current_db);
-    if(db == NULL) {
-       TXN_ABORT(t_add, tid);
-       return false;
-    }
-    dk.groupnum = gi.current_gid;
-    dk.artnum = htonl((u_int32_t)artnum);
-
-    key.data = &dk;
-    key.size = sizeof dk;
-    val.data = databuf;
-    val.size = len;
-
-    ret = db->put(db, tid, &key, &val, 0);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_add, tid);
-    default:
-       TXN_ABORT(t_add, tid);
-       syslog(L_ERROR, "OVDB: add: db->put: %s", db_strerror(ret));
-       return false;
-    }
-
-    if(artnum < gi.high && gi.status & GROUPINFO_MOVING) {
-       /* If the GROUPINFO_MOVING flag is set, then expireover
-          is writing overview records under a new groupid.
-          If this overview record is not at the highmark,
-          we need to also store it under the new groupid */
-       db = get_db_bynum(gi.new_db);
-       if(db == NULL) {
-           TXN_ABORT(t_add, tid);
-           return false;
-       }
-       dk.groupnum = gi.new_gid;
-
-        ret = db->put(db, tid, &key, &val, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_add, tid);
-       default:
-           TXN_ABORT(t_add, tid);
-           syslog(L_ERROR, "OVDB: add: db->put: %s", db_strerror(ret));
-           return false;
-       }
-    }
-
-    TXN_COMMIT(t_add, tid);
-    return true;
-}
-
-bool ovdb_cancel(TOKEN token UNUSED)
-{
-    return true;
-}
-
-struct ovdbsearch {
-    DBC *cursor;
-    group_id_t gid;
-    u_int32_t firstart;
-    u_int32_t lastart;
-    int state;
-};
-
-/* Even though nnrpd only does one search at a time, a read server process could
-   do many concurrent searches; hence we must keep track of an arbitrary number of
-   open searches */
-static struct ovdbsearch **searches = NULL;
-static int nsearches = 0;
-static int maxsearches = 0;
-
-void *
-ovdb_opensearch(char *group, int low, int high)
-{
-    DB *db;
-    struct ovdbsearch *s;
-    struct groupinfo gi;
-    int ret;
-
-    if(clientmode) {
-       struct rs_cmd rs;
-       struct rs_opensrch repl;
-
-       rs.what = CMD_OPENSRCH;
-       rs.grouplen = strlen(group)+1;
-       rs.artlo = low;
-       rs.arthi = high;
-
-       if (csend(&rs, sizeof(rs)) < 0)
-            return NULL;
-       if (csend(group, rs.grouplen) < 0)
-            return NULL;
-       crecv(&repl, sizeof(repl));
-
-       if(repl.status != CMD_OPENSRCH)
-           return NULL;
-
-       return repl.handle;
-    }
-
-    ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case DB_NOTFOUND:
-       return NULL;
-    default:
-       syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
-       return NULL;
-    }
-
-    s = xmalloc(sizeof(struct ovdbsearch));
-    db = get_db_bynum(gi.current_db);
-    if(db == NULL) {
-       free(s);
-       return NULL;
-    }
-
-    ret = db->cursor(db, NULL, &(s->cursor), 0);
-    if (ret != 0) {
-       syslog(L_ERROR, "OVDB: opensearch: s->db->cursor: %s", db_strerror(ret));
-       free(s);
-       return NULL;
-    }
-
-    s->gid = gi.current_gid;
-    s->firstart = low;
-    s->lastart = high;
-    s->state = 0;
-
-    if(searches == NULL) {
-       nsearches = 0;
-       maxsearches = 50;
-       searches = xmalloc(sizeof(struct ovdbsearch *) * maxsearches);
-    }
-    if(nsearches == maxsearches) {
-       maxsearches += 50;
-       searches = xrealloc(searches, sizeof(struct ovdbsearch *) * maxsearches);
-    }
-    searches[nsearches] = s;
-    nsearches++;
-
-    return (void *)s;
-}
-
-bool
-ovdb_search(void *handle, ARTNUM *artnum, char **data, int *len,
-            TOKEN *token, time_t *arrived)
-{
-    struct ovdbsearch *s = (struct ovdbsearch *)handle;
-    DBT key, val;
-    u_int32_t flags;
-    struct ovdata ovd;
-    struct datakey dk;
-    int ret;
-
-    if (clientmode) {
-       struct rs_cmd rs;
-       struct rs_srch repl;
-       static char *databuf;
-       static int buflen = 0;
-
-       rs.what = CMD_SRCH;
-       rs.handle = handle;
-
-       if (csend(&rs, sizeof(rs)) < 0)
-            return false;
-       if (crecv(&repl, sizeof(repl)) < 0)
-            return false;
-
-       if(repl.status != CMD_SRCH)
-           return false;
-       if(repl.len > buflen) {
-           if(buflen == 0) {
-               buflen = repl.len + 512;
-               databuf = xmalloc(buflen);
-           } else {
-               buflen = repl.len + 512;
-                databuf = xrealloc(databuf, buflen);
-           }
-       }
-       crecv(databuf, repl.len);
-
-       if(artnum)
-           *artnum = repl.artnum;
-       if(token)
-           *token = repl.token;
-       if(arrived)
-           *arrived = repl.arrived;
-       if(len)
-           *len = repl.len;
-       if(data)
-           *data = databuf;
-       return true;
-    }
-
-    switch(s->state) {
-    case 0:
-       flags = DB_SET_RANGE;
-       memset(&dk, 0, sizeof dk);
-       dk.groupnum = s->gid;
-       dk.artnum = htonl(s->firstart);
-       s->state = 1;
-       break;
-    case 1:
-       flags = DB_NEXT;
-       break;
-    case 2:
-       s->state = 3;
-       return false;
-    default:
-       syslog(L_ERROR, "OVDB: OVsearch called again after false return");
-       return false;
-    }
-
-    memset(&key, 0, sizeof key);
-    memset(&val, 0, sizeof val);
-    key.data = &dk;
-    key.size = key.ulen = sizeof(struct datakey);
-    key.flags = DB_DBT_USERMEM;
-
-    if(!data && !len) {
-       /* caller doesn't need data, so we don't have to retrieve it all */
-       val.flags |= DB_DBT_PARTIAL;
-
-       if(token || arrived)
-           val.dlen = sizeof(struct ovdata);
-    }
-
-    switch(ret = s->cursor->c_get(s->cursor, &key, &val, flags)) {
-    case 0:
-       break;
-    case DB_NOTFOUND:
-       s->state = 3;
-       s->cursor->c_close(s->cursor);
-       s->cursor = NULL;
-       return false;
-    default:
-       syslog(L_ERROR, "OVDB: search: c_get: %s", db_strerror(ret));
-       s->state = 3;
-       s->cursor->c_close(s->cursor);
-       s->cursor = NULL;
-       return false;
-    }
-
-    if(key.size != sizeof(struct datakey)) {
-       s->state = 3;
-       s->cursor->c_close(s->cursor);
-       s->cursor = NULL;
-       return false;
-    }
-
-    if(dk.groupnum != s->gid || ntohl(dk.artnum) > s->lastart) {
-       s->state = 3;
-       s->cursor->c_close(s->cursor);
-       s->cursor = NULL;
-       return false;
-    }
-
-    if( ((len || data) && val.size <= sizeof(struct ovdata))
-       || ((token || arrived) && val.size < sizeof(struct ovdata)) ) {
-       syslog(L_ERROR, "OVDB: search: bad value length");
-       s->state = 3;
-       s->cursor->c_close(s->cursor);
-       s->cursor = NULL;
-       return false;
-    }
-
-    if(ntohl(dk.artnum) == s->lastart) {
-       s->state = 2;
-       s->cursor->c_close(s->cursor);
-       s->cursor = NULL;
-    }
-
-    if(artnum)
-       *artnum = ntohl(dk.artnum);
-
-    if(token || arrived)
-       memcpy(&ovd, val.data, sizeof(struct ovdata));
-    if(token)
-       *token = ovd.token;
-    if(arrived)
-       *arrived = ovd.arrived;
-
-    if(len)
-       *len = val.size - sizeof(struct ovdata);
-    if(data)
-       *data = (char *)val.data + sizeof(struct ovdata);
-
-    return true;
-}
-
-void ovdb_closesearch(void *handle)
-{
-    int i;
-    if(clientmode) {
-       struct rs_cmd rs;
-
-       rs.what = CMD_CLOSESRCH;
-       rs.handle = handle;
-       csend(&rs, sizeof(rs));
-       /* no reply is sent for a CMD_CLOSESRCH */
-    } else {
-       struct ovdbsearch *s = (struct ovdbsearch *)handle;
-
-       if(s->cursor)
-           s->cursor->c_close(s->cursor);
-
-       for(i = 0; i < nsearches; i++) {
-           if(s == searches[i]) {
-               break;
-           }
-       }
-       nsearches--;
-       for( ; i < nsearches; i++) {
-           searches[i] = searches[i+1];
-       }
-
-       free(handle);
-    }
-}
-
-bool ovdb_getartinfo(char *group, ARTNUM artnum, TOKEN *token)
-{
-    int ret, cdb = 0;
-    group_id_t cgid = 0;
-    DB *db;
-    DBT key, val;
-    struct ovdata ovd;
-    struct datakey dk;
-    struct groupinfo gi;
-    int pass = 0;
-
-    if(clientmode) {
-       struct rs_cmd rs;
-       struct rs_artinfo repl;
-
-       rs.what = CMD_ARTINFO;
-       rs.grouplen = strlen(group)+1;
-       rs.artlo = artnum;
-
-       if (csend(&rs, sizeof(rs)) < 0)
-            return false;
-       if (csend(group, rs.grouplen) < 0)
-            return false;
-       crecv(&repl, sizeof(repl));
-
-       if(repl.status != CMD_ARTINFO)
-           return false;
-
-       if(token)
-           *token = repl.token;
-
-       return true;
-    }
-
-    while(1) {
-        ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case DB_NOTFOUND:
-           return false;
-       default:
-           syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
-           return false;
-       }
-
-       if(pass) {
-           /* This was our second groupinfo retrieval; because the article
-              retrieval came up empty.  If the group ID hasn't changed
-              since the first groupinfo retrieval, we can assume the article
-              is definitely not there.  Otherwise, we'll try to retrieve
-              it the article again. */
-           if(cdb == gi.current_db && cgid == gi.current_gid)
-               return false;
-       }
-
-       db = get_db_bynum(gi.current_db);
-       if(db == NULL)
-           return false;
-
-       memset(&dk, 0, sizeof dk);
-       dk.groupnum = gi.current_gid;
-       dk.artnum = htonl((u_int32_t)artnum);
-
-       memset(&key, 0, sizeof key);
-       memset(&val, 0, sizeof val);
-
-       key.data = &dk;
-       key.size = sizeof dk;
-
-        /* caller doesn't need data, so we don't have to retrieve it all */
-        val.flags = DB_DBT_PARTIAL;
-
-        if(token)
-            val.dlen = sizeof(struct ovdata);
-
-       switch(ret = db->get(db, NULL, &key, &val, 0)) {
-       case 0:
-       case DB_NOTFOUND:
-           break;
-       default:
-           syslog(L_ERROR, "OVDB: getartinfo: db->get: %s", db_strerror(ret));
-           return false;
-       }
-
-       if(ret == DB_NOTFOUND) {
-           /* If the group is being moved (i.e., its group ID is going
-              to change), there's a chance the article is now under the
-              new ID.  So we'll grab the groupinfo again to check for
-              that. */
-           if(!pass && (gi.status & GROUPINFO_MOVING)) {
-               cdb = gi.current_db;
-               cgid = gi.current_gid;
-               pass++;
-               continue;
-           }
-           return false;
-       }
-       break;
-    }
-
-    if(token && val.size < sizeof(struct ovdata)) {
-       syslog(L_ERROR, "OVDB: getartinfo: data too short");
-       return false;
-    }
-
-    if(token) {
-       memcpy(&ovd, val.data, sizeof(struct ovdata));
-       *token = ovd.token;
-    }
-    return true;
-}
-
-bool ovdb_expiregroup(char *group, int *lo, struct history *h)
-{
-    DB *db, *ndb = NULL;
-    DBT key, val, nkey, gkey, gval;
-    DB_TXN *tid;
-    DBC *cursor = NULL;
-    int ret = 0, delete, old_db = 0, cleanup;
-    struct groupinfo gi;
-    struct ovdata ovd;
-    struct datakey dk, ndk;
-    group_id_t old_gid = 0;
-    ARTHANDLE *ah;
-    u_int32_t artnum = 0, currentart, lowest;
-    int i, compact, done, currentcount, newcount;
-
-    if(eo_start == 0) {
-       eo_start = time(NULL);
-       delete_old_stuff(0);    /* remove deleted groups first */
-    }
-
-    /* Special case:  when called with NULL group, we're to clean out
-     * records for forgotton groups (groups removed from the active file
-     * but not from overview).
-     * This happens at the end of the expireover run, and only if all
-     * of the groups in the active file have been processed.
-     * delete_old_stuff(1) will remove groups that are in ovdb but
-     * have not been processed during this run.
-     */
-
-    if(group == NULL)
-       return delete_old_stuff(1);
-
-    memset(&key, 0, sizeof key);
-    memset(&nkey, 0, sizeof nkey);
-    memset(&val, 0, sizeof val);
-    memset(&dk, 0, sizeof dk);
-    memset(&ndk, 0, sizeof ndk);
-
-    TXN_START(t_expgroup_1, tid);
-
-    if(tid==NULL)
-       return false;
-
-    cleanup = 0;
-
-    ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_expgroup_1, tid);
-    default:
-       syslog(L_ERROR, "OVDB: expiregroup: ovdb_getgroupinfo failed: %s", db_strerror(ret));
-    case DB_NOTFOUND:
-       TXN_ABORT(t_expgroup_1, tid);
-       return false;
-    }
-
-    if(gi.status & GROUPINFO_EXPIRING) {
-       /* is there another expireover working on this group? */
-        ret = kill(gi.expiregrouppid, 0);
-       switch(ret)
-        {
-       case 0:
-       case EPERM:
-           TXN_ABORT(t_expgroup_1, tid);
-           return false;
-       }
-
-       /* a previous expireover run must've died.  We'll clean
-          up after it */
-       if(gi.status & GROUPINFO_MOVING) {
-           cleanup = 1;
-           old_db = gi.new_db;
-           old_gid = gi.new_gid;
-
-           ret = mk_temp_groupinfo(old_db, old_gid, tid);
-           switch(ret) {
-           case 0:
-               break;
-           case TRYAGAIN:
-               TXN_RETRY(t_expgroup_1, tid);
-           default:
-               TXN_ABORT(t_expgroup_1, tid);
-               return false;
-           }
-           gi.status &= ~GROUPINFO_MOVING;
-       }
-    }
-
-    if(gi.count < ovdb_conf.nocompact || ovdb_conf.nocompact == 0)
-       compact = 1;
-    else
-       compact = 0;
-
-    if(gi.count == 0)
-       compact = 0;
-
-    db = get_db_bynum(gi.current_db);
-    if(db == NULL) {
-       TXN_ABORT(t_expgroup_1, tid);
-       return false;
-    }
-
-    gi.status |= GROUPINFO_EXPIRING;
-    gi.expiregrouppid = getpid();
-    if(compact) {
-       gi.status |= GROUPINFO_MOVING;
-       gi.new_db = gi.current_db;
-       ndb = db;
-        ret = groupid_new(&gi.new_gid, tid);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_expgroup_1, tid);
-       default:
-           TXN_ABORT(t_expgroup_1, tid);
-           syslog(L_ERROR, "OVDB: expiregroup: groupid_new: %s", db_strerror(ret));
-           return false;
-       }
-    }
-
-    key.data = group;
-    key.size = strlen(group);
-    val.data = &gi;
-    val.size = sizeof gi;
-
-    ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
-    switch (ret)
-    {
-    case 0:
-       break;
-    case TRYAGAIN:
-       TXN_RETRY(t_expgroup_1, tid);
-    default:
-       TXN_ABORT(t_expgroup_1, tid);
-       syslog(L_ERROR, "OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
-       return false;
-    }
-    TXN_COMMIT(t_expgroup_1, tid);
-
-    if(cleanup) {
-       if(delete_all_records(old_db, old_gid) == 0) {
-           rm_temp_groupinfo(old_gid);
-       }
-    }
-
-    /*
-     * The following loop iterates over the OV records for the group in
-     * "batches", to limit transaction sizes.
-     *
-     * loop {
-     *    start transaction
-     *    get groupinfo
-     *    process EXPIREGROUP_TXN_SIZE records
-     *    write updated groupinfo
-     *    commit transaction
-     * }
-     */
-    currentart = 0;
-    lowest = currentcount = 0;
-
-    memset(&gkey, 0, sizeof gkey);
-    memset(&gval, 0, sizeof gval);
-    gkey.data = group;
-    gkey.size = strlen(group);
-    gval.data = &gi;
-    gval.size = sizeof gi;
-
-    while(1) {
-       TXN_START(t_expgroup_loop, tid);
-       if(tid==NULL)
-           return false;
-        done = 0;
-       newcount = 0;
-
-        ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_expgroup_loop, tid);
-       default:
-           TXN_ABORT(t_expgroup_loop, tid);
-           syslog(L_ERROR, "OVDB: expiregroup: ovdb_getgroupinfo: %s", db_strerror(ret));
-           return false;
-       }
-
-        ret = db->cursor(db, tid, &cursor, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_expgroup_loop, tid);
-       default:
-           TXN_ABORT(t_expgroup_loop, tid);
-           syslog(L_ERROR, "OVDB: expiregroup: db->cursor: %s", db_strerror(ret));
-           return false;
-       }
-
-       dk.groupnum = gi.current_gid;
-       dk.artnum = htonl(currentart);
-       key.data = &dk;
-       key.size = key.ulen = sizeof dk;
-       key.flags = DB_DBT_USERMEM;
-
-       for(i=0; i < EXPIREGROUP_TXN_SIZE; i++) {
-            ret = cursor->c_get(cursor, &key, &val,
-                                i == 0 ? DB_SET_RANGE : DB_NEXT);
-           switch (ret)
-            {
-           case 0:
-           case DB_NOTFOUND:
-               break;
-           case TRYAGAIN:
-               cursor->c_close(cursor);
-               TXN_RETRY(t_expgroup_loop, tid);
-           default:
-               cursor->c_close(cursor);
-               TXN_ABORT(t_expgroup_loop, tid);
-               syslog(L_ERROR, "OVDB: expiregroup: c_get: %s", db_strerror(ret));
-               return false;
-           }
-
-           /* stop if: there are no more keys, an unknown key is reached,
-              or reach a different group */
-
-           if(ret == DB_NOTFOUND
-                   || key.size != sizeof dk
-                   || dk.groupnum != gi.current_gid) {
-               done++;
-               break;
-           }
-
-           artnum = ntohl(dk.artnum);
-
-           delete = 0;
-           if(val.size < sizeof ovd) {
-               delete = 1;     /* must be corrupt, just delete it */
-           } else {
-               memcpy(&ovd, val.data, sizeof ovd);
-
-               ah = NULL;
-               if (!SMprobe(EXPENSIVESTAT, &ovd.token, NULL) || OVstatall) {
-                   if((ah = SMretrieve(ovd.token, RETR_STAT)) == NULL) {
-                       delete = 1;
-                   } else
-                       SMfreearticle(ah);
-               } else {
-                   if (!OVhisthasmsgid(h, (char *)val.data + sizeof(ovd))) {
-                       delete = 1;
-                   }
-               }
-               if (!delete && innconf->groupbaseexpiry &&
-                           OVgroupbasedexpire(ovd.token, group,
-                                   (char *)val.data + sizeof(ovd),
-                                   val.size - sizeof(ovd),
-                                   ovd.arrived, ovd.expires)) {
-                   delete = 1;
-               }
-           }
-
-           if(delete) {
-               if(!compact) {
-                   switch(ret = cursor->c_del(cursor, 0)) {
-                   case 0:
-                   case DB_NOTFOUND:
-                   case DB_KEYEMPTY:
-                       break;
-                   case TRYAGAIN:
-                       cursor->c_close(cursor);
-                       TXN_RETRY(t_expgroup_loop, tid);
-                   default:
-                       cursor->c_close(cursor);
-                       TXN_ABORT(t_expgroup_loop, tid);
-                       syslog(L_ERROR, "OVDB: expiregroup: c_del: %s", db_strerror(ret));
-                       return false;
-                   }
-               }
-               if(gi.count > 0)
-                   gi.count--;
-           } else {
-               if(compact) {
-                   ndk.groupnum = gi.new_gid;
-                   ndk.artnum = dk.artnum;
-                   nkey.data = &ndk;
-                   nkey.size = sizeof ndk;
-
-                   switch(ret = ndb->put(ndb, tid, &nkey, &val, 0)) {
-                   case 0:
-                       break;
-                   case TRYAGAIN:
-                       cursor->c_close(cursor);
-                       TXN_RETRY(t_expgroup_loop, tid);
-                   default:
-                       cursor->c_close(cursor);
-                       TXN_ABORT(t_expgroup_loop, tid);
-                       syslog(L_ERROR, "OVDB: expiregroup: ndb->put: %s", db_strerror(ret));
-                       return false;
-                   }
-               }
-               newcount++;
-               if(lowest != -1 && (lowest == 0 || artnum < lowest))
-                   lowest = artnum;
-           }
-       }
-       /* end of for loop */
-
-       if(cursor->c_close(cursor) == TRYAGAIN) {
-           TXN_RETRY(t_expgroup_loop, tid);
-       }
-
-       if(lowest != 0 && lowest != -1)
-           gi.low = lowest;
-
-       if(done) {
-           if(compact) {
-               old_db = gi.current_db;
-               gi.current_db = gi.new_db;
-               old_gid = gi.current_gid;
-               gi.current_gid = gi.new_gid;
-               gi.status &= ~GROUPINFO_MOVING;
-
-               ret = mk_temp_groupinfo(old_db, old_gid, tid);
-               switch(ret) {
-               case 0:
-                   break;
-               case TRYAGAIN:
-                   TXN_RETRY(t_expgroup_loop, tid);
-               default:
-                   TXN_ABORT(t_expgroup_loop, tid);
-                   return false;
-               }
-           }
-
-           gi.status &= ~GROUPINFO_EXPIRING;
-           gi.expired = time(NULL);
-           if(gi.count == 0 && lowest == 0)
-               gi.low = gi.high+1;
-       }
-
-        ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_expgroup_loop, tid);
-       default:
-           TXN_ABORT(t_expgroup_loop, tid);
-           syslog(L_ERROR, "OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
-           return false;
-       }
-        TXN_COMMIT(t_expgroup_loop, tid);
-
-       currentcount += newcount;
-       if(lowest != 0)
-           lowest = -1;
-
-       if(done)
-           break;
-
-       currentart = artnum+1;
-    }
-
-    if(compact) {
-        if(delete_all_records(old_db, old_gid) == 0) {
-           rm_temp_groupinfo(old_gid);
-       }
-    }
-
-    if(currentcount != gi.count) {
-       syslog(L_NOTICE, "OVDB: expiregroup: recounting %s", group);
-
-       TXN_START(t_expgroup_recount, tid);
-       if(tid == NULL)
-           return false;
-
-       switch(ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW)) {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_expgroup_recount, tid);
-       default:
-           TXN_ABORT(t_expgroup_recount, tid);
-           syslog(L_ERROR, "OVDB: expiregroup: ovdb_getgroupinfo: %s", db_strerror(ret));
-           return false;
-       }
-
-       if(count_records(&gi) != 0) {
-           TXN_ABORT(t_expgroup_recount, tid);
-           return false;
-       }
-
-        ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
-       switch (ret)
-        {
-       case 0:
-           break;
-       case TRYAGAIN:
-           TXN_RETRY(t_expgroup_recount, tid);
-       default:
-           TXN_ABORT(t_expgroup_recount, tid);
-           syslog(L_ERROR, "OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
-           return false;
-       }
-        TXN_COMMIT(t_expgroup_recount, tid);
-    }
-
-    if(lo)
-       *lo = gi.low;
-    return true;
-}
-
-bool ovdb_ctl(OVCTLTYPE type, void *val)
-{
-    int *i;
-    OVSORTTYPE *sorttype;
-    bool *boolval;
-
-    switch (type) {
-    case OVSPACE:
-        i = (int *)val;
-        *i = -1;
-        return true;
-    case OVSORT:
-        sorttype = (OVSORTTYPE *)val;
-        *sorttype = OVNEWSGROUP;
-        return true;
-    case OVCUTOFFLOW:
-        Cutofflow = *(bool *)val;
-        return true;
-    case OVSTATICSEARCH:
-       i = (int *)val;
-       *i = true;
-       return true;
-    case OVCACHEKEEP:
-    case OVCACHEFREE:
-        boolval = (bool *)val;
-        *boolval = false;
-        return true;
-    default:
-        return false;
-    }
-}
-
-void ovdb_close_berkeleydb(void)
-{
-    if(OVDBenv) {
-       /* close db environment */
-#if DB_VERSION_MAJOR == 2
-       db_appexit(OVDBenv);
-       free(OVDBenv);
-#else
-       OVDBenv->close(OVDBenv, 0);
-#endif
-       OVDBenv = NULL;
-    }
-}
-
-void ovdb_close(void)
-{
-    int i;
-
-    if(clientmode) {
-       client_disconnect();
-       return;
-    }
-
-    while(searches != NULL && nsearches) {
-       ovdb_closesearch(searches[0]);
-    }
-    if(searches != NULL) {
-       free(searches);
-       searches = NULL;
-    }
-
-    if(dbs) {
-       /* close databases */
-       for(i = 0; i < ovdb_conf.numdbfiles; i++)
-           close_db_file(i);
-
-       free(dbs);
-       dbs = NULL;
-    }
-    if(groupinfo) {
-       groupinfo->close(groupinfo, 0);
-       groupinfo = NULL;
-    }
-    if(groupaliases) {
-       groupaliases->close(groupaliases, 0);
-       groupaliases = NULL;
-    }
-
-    ovdb_close_berkeleydb();
-    ovdb_releaselock();
-}
-
-
-#endif /* USE_BERKELEY_DB */
-