/*****************************************************************************
NAME:
datastore_db_trad.c -- implements bogofilter's traditional
(non-transactional) datastore,
using Berkeley DB
AUTHORS:
Gyepi Sam <gyepi@praxis-sw.com> 2002 - 2003
Matthias Andree <matthias.andree@gmx.de> 2003 - 2004
David Relson <relson@osagesoftware.com> 2005
******************************************************************************/
#include "common.h"
#include <assert.h>
#include <error.h>
#include <errno.h>
#include <db.h>
#include "datastore.h"
#include "datastore_db_private.h"
#include "datastore_db.h"
#include "datastore_dbcommon.h"
#include "bool.h"
#include "db_lock.h"
#include "longoptions.h"
#include "mxcat.h"
#include "rand_sleep.h"
#include "xmalloc.h"
#include "xstrdup.h"
static int lockfd = -1; /* fd of lock file to prevent concurrent recovery */
/** Default flags for DB_ENV->open() */
static const u_int32_t dbenv_defflags = DB_INIT_MPOOL
| DB_INIT_LOG | DB_INIT_TXN;
/* public -- used in datastore.c */
static int dbx_begin (void *vhandle);
static int dbx_abort (void *vhandle);
static int dbx_commit (void *vhandle);
/* private -- used in datastore_db_*.c */
static DB_ENV *dbx_get_env_dbe (dbe_t *env);
static const char *dbx_database_name (const char *db_file);
static DB_ENV *dbx_recover_open (bfpath *bfp);
static int dbx_auto_commit_flags(void);
static int dbx_get_rmw_flag (int open_mode);
static ex_t dbx_common_close (DB_ENV *dbe, bfpath *bfp);
static int dbx_sync (DB_ENV *dbe, int ret);
static void dbx_log_flush (DB_ENV *dbe);
static dbe_t *dbx_init (bfpath *bfp);
static void dbx_cleanup (dbe_t *env);
static void dbx_cleanup_lite (dbe_t *env);
static ex_t dbe_env_purgelogs (DB_ENV *dbe);
static ex_t dbx_checkpoint (bfpath *bfp);
static ex_t dbx_purgelogs (bfpath *bfp);
static ex_t dbx_recover (bfpath *bfp, bool catastrophic, bool force);
static ex_t dbx_remove (bfpath *bfp);
static ex_t dbx_list_logfiles (bfpath *bfp, int argc, char **argv);
/* OO function lists */
dsm_t dsm_transactional = {
/* public -- used in datastore.c */
&dbx_begin,
&dbx_abort,
&dbx_commit,
/* private -- used in datastore_db_*.c */
&dbx_init,
&dbx_cleanup,
&dbx_cleanup_lite,
&dbx_get_env_dbe,
&dbx_database_name,
&dbx_recover_open,
&dbx_auto_commit_flags,
&dbx_get_rmw_flag,
&db_lock,
&dbx_common_close,
&dbx_sync,
&dbx_log_flush,
&db_pagesize,
&dbx_checkpoint,
&dbx_purgelogs,
&dbx_recover,
&dbx_remove,
&db_verify,
&dbx_list_logfiles,
&db_leafpages
};
/* non-OO static function prototypes */
static int plock(const char *path, short locktype, int mode);
static int db_try_glock(bfpath *bfp, short locktype, int lockcmd);
static int bf_dbenv_create(DB_ENV **dbe);
static void dbe_config(void *vhandle);
static dbe_t *dbe_xinit(dbe_t *env, bfpath *bfp, u_int32_t flags);
static DB_ENV *dbe_recover_open(bfpath *bfp, u_int32_t flags);
/* support functions */
static bool get_bool(const char *name, const char *arg)
{
bool b = str_to_bool(arg);
if (DEBUG_CONFIG(2))
fprintf(dbgout, "%s -> %s\n", name,
b ? "Yes" : "No");
return b;
}
static e_txn get_txn(const char *name, const char *arg)
{
e_txn t = get_bool(name, arg) ? T_ENABLED : T_DISABLED;
if (DEBUG_CONFIG(2))
fprintf(dbgout, "%s -> %s\n", name,
t ? "enabled" : "disabled");
return t;
}
/* non-OO static functions */
DB_ENV *dbx_get_env_dbe(dbe_t *env)
{
return env->dbe;
}
const char *dbx_database_name(const char *db_file)
{
const char *t;
t = strrchr(db_file, DIRSEP_C);
if (t != NULL)
t += 1;
return t;
}
int dbx_auto_commit_flags(void)
{
#if DB_AT_LEAST(4,1)
return DB_AUTO_COMMIT;
#else
return 0;
#endif
}
int dbx_get_rmw_flag(int open_mode)
{
(void)open_mode;
return 0;
}
/** print user-readable diagnostics and instructions after DB_ENV->open
* failed. */
static void diag_dbeopen(
/** DB_ENV->open() flags value */ u_int32_t flags,
/** env directory tried to open */ bfpath *bfp)
{
if (flags & DB_RECOVER) {
fprintf(stderr,
"\n"
"### Standard recovery failed. ###\n"
"\n"
"Please check section 3.3 in bogofilter's README.db file\n"
"for help.\n");
/* ask that the user runs catastrophic recovery */
} else if (flags & DB_RECOVER_FATAL) {
fprintf(stderr,
"\n"
"### Catastrophic recovery failed. ###\n"
"\n"
"Please check the README.db file that came with bogofilter for hints,\n"
"section 3.3, or remove all __db.*, log.* and *.db files in \"%s\"\n"
"and start from scratch.\n", bfp->dirname);
/* catastrophic recovery failed */
} else {
fprintf(stderr, "To recover, run: bogoutil -v --db-recover \"%s\"\n",
bfp->dirname);
}
}
/** run recovery, open environment and keep the exclusive lock */
static DB_ENV *dbe_recover_open(bfpath *bfp, u_int32_t flags)
{
const u_int32_t local_flags = flags | DB_CREATE;
DB_ENV *dbe;
int e;
if (DEBUG_DATABASE(0))
fprintf(dbgout, "trying to lock database directory\n");
db_try_glock(bfp, F_WRLCK, F_SETLKW); /* wait for exclusive lock */
/* run recovery */
bf_dbenv_create(&dbe);
if (DEBUG_DATABASE(0))
fprintf(dbgout, "dbe_recover_open() running regular data base recovery%s\n",
flags & DB_PRIVATE ? " and removing environment" : "");
/* quirk: DB_RECOVER requires DB_CREATE and cannot work with DB_JOINENV */
/*
* Hint from Keith Bostic, SleepyCat support, 2004-11-29,
* we can use the DB_PRIVATE flag, that rebuilds the database
* environment in heap memory, so we don't need to remove it.
*/
e = dbe->open(dbe, bfp->dirname,
dbenv_defflags | local_flags | DB_RECOVER, DS_MODE);
if (e != 0) {
print_error(__FILE__, __LINE__, "Cannot recover environment \"%s\": %s",
bfp->dirname, db_strerror(e));
if (e == DB_RUNRECOVERY)
diag_dbeopen(flags, bfp);
exit(EX_ERROR);
}
return dbe;
}
static DB_ENV *dbx_recover_open(bfpath *bfp)
{
return dbe_recover_open(bfp, 0);
}
static int dbx_begin(void *vhandle)
{
DB_TXN *t;
int ret;
dbh_t *dbh = (dbh_t *)vhandle;
dbe_t *env = dbh->dbenv;
assert(dbh);
assert(dbh->magic == MAGIC_DBH);
assert(dbh->txn == 0);
assert(env);
assert(env->dbe);
ret = BF_TXN_BEGIN(env->dbe, NULL, &t, 0);
if (ret) {
print_error(__FILE__, __LINE__, "DB_ENV->txn_begin(%p), err: %d, %s",
(void *)env->dbe, ret, db_strerror(ret));
return ret;
}
dbh->txn = t;
if (DEBUG_DATABASE(2))
fprintf(dbgout, "DB_ENV->dbx_begin(%p), tid: %lx\n",
(void *)env->dbe, (unsigned long)BF_TXN_ID(t));
return 0;
}
static int dbx_abort(void *vhandle)
{
int ret;
dbh_t *dbh = (dbh_t *)vhandle;
DB_TXN *t;
assert(dbh);
assert(dbh->magic == MAGIC_DBH);
t = dbh->txn;
assert(t);
ret = BF_TXN_ABORT(t);
if (ret)
print_error(__FILE__, __LINE__, "DB_TXN->abort(%lx) error: %s",
(unsigned long)BF_TXN_ID(t), db_strerror(ret));
else
if (DEBUG_DATABASE(2))
fprintf(dbgout, "DB_TXN->abort(%lx)\n",
(unsigned long)BF_TXN_ID(t));
dbh->txn = NULL;
switch (ret) {
case 0:
return DST_OK;
case DB_LOCK_DEADLOCK:
return DST_TEMPFAIL;
default:
return DST_FAILURE;
}
}
static int dbx_commit(void *vhandle)
{
int ret;
dbh_t *dbh = (dbh_t *)vhandle;
DB_TXN *t;
u_int32_t id;
assert(dbh);
assert(dbh->magic == MAGIC_DBH);
t = dbh->txn;
assert(t);
id = BF_TXN_ID(t);
ret = BF_TXN_COMMIT(t, 0);
if (ret)
print_error(__FILE__, __LINE__, "DB_TXN->commit(%lx) error: %s",
(unsigned long)id, db_strerror(ret));
else
if (DEBUG_DATABASE(2))
fprintf(dbgout, "DB_TXN->commit(%lx, 0)\n",
(unsigned long)id);
dbh->txn = NULL;
switch (ret) {
case 0:
/* push out buffer pages so that >=15% are clean - we
* can ignore errors here, as the log has all the data */
BF_MEMP_TRICKLE(dbh->dbenv->dbe, 15, NULL);
return DST_OK;
case DB_LOCK_DEADLOCK:
return DST_TEMPFAIL;
default:
return DST_FAILURE;
}
}
/** set an fcntl-style lock on \a path.
* \a locktype is F_RDLCK, F_WRLCK, F_UNLCK
* \a mode is F_SETLK or F_SETLKW
* \return file descriptor of locked file if successful
* negative value in case of error
*/
static int plock(const char *path, short locktype, int mode)
{
struct flock fl;
int fd, r;
fd = open(path, O_RDWR);
if (fd < 0) return fd;
fl.l_type = locktype;
fl.l_whence = SEEK_SET;
fl.l_start = (off_t)0;
fl.l_len = (off_t)0;
r = fcntl(fd, mode, &fl);
if (r < 0)
return r;
return fd;
}
static int db_try_glock(bfpath *bfp, short locktype, int lockcmd)
{
int ret;
char *t;
/* lock */
ret = bf_mkdir(bfp->dirname, DIR_MODE);
if (ret && errno != EEXIST) {
print_error(__FILE__, __LINE__, "mkdir(%s): %s",
bfp->dirname, strerror(errno));
exit(EX_ERROR);
}
t = mxcat(bfp->dirname, DIRSEP_S, "lockfile-d", NULL);
/* All we are interested in is that this file exists, we'll close it
* right away as plock down will open it again */
ret = open(t, O_RDWR|O_CREAT|O_EXCL, DS_MODE);
if (ret < 0 && errno != EEXIST) {
print_error(__FILE__, __LINE__, "open(%s): %s",
t, strerror(errno));
exit(EX_ERROR);
}
if (ret >= 0)
close(ret);
lockfd = plock(t, locktype, lockcmd);
if (lockfd < 0 && errno != EAGAIN && errno != EACCES) {
print_error(__FILE__, __LINE__, "lock(%s): %s",
t, strerror(errno));
exit(EX_ERROR);
}
xfree(t);
/* lock set up */
return lockfd;
}
/** Create environment or exit with EX_ERROR */
static int bf_dbenv_create(DB_ENV **env)
{
int ret = db_env_create(env, 0);
if (ret != 0) {
print_error(__FILE__, __LINE__, "db_env_create, err: %d, %s",
ret, db_strerror(ret));
exit(EX_ERROR);
}
if (DEBUG_DATABASE(1))
fprintf(dbgout, "db_env_create: %p\n", (void *)env);
(*env)->set_errfile(*env, stderr);
return ret;
}
static void dbe_config(void *vhandle)
{
dbe_t *env = (dbe_t *)vhandle;
int ret = 0;
u_int32_t logsize = 1048576; /* 1 MByte (default in BDB 10 MByte) */
/* configure log file size */
ret = env->dbe->set_lg_max(env->dbe, logsize);
if (ret) {
print_error(__FILE__, __LINE__, "DB_ENV->set_lg_max(%lu) err: %d, %s",
(unsigned long)logsize, ret, db_strerror(ret));
exit(EX_ERROR);
}
if (DEBUG_DATABASE(1))
fprintf(dbgout, "DB_ENV->set_lg_max(%lu)\n", (unsigned long)logsize);
}
static dbe_t *dbx_init(bfpath *bfp)
{
u_int32_t flags = 0;
dbe_t *env = (dbe_t *)xcalloc(1, sizeof(dbe_t));
env->magic = MAGIC_DBE; /* poor man's type checking */
env->directory = xstrdup(bfp->dirname);
/* open lock file, needed to detect previous crashes */
if (init_dbl(bfp->dirname))
exit(EX_ERROR);
/* run recovery if needed */
if (needs_recovery()) {
dbx_recover(bfp, false, false); /* DO NOT set force flag here, may cause
multiple recovery! */
/* reinitialize */
if (init_dbl(bfp->dirname))
exit(EX_ERROR);
}
/* set (or demote to) shared/read lock for regular operation */
db_try_glock(bfp, F_RDLCK, F_SETLKW);
/* set our cell lock in the crash detector */
if (set_lock()) {
exit(EX_ERROR);
}
/* initialize */
#ifdef FUTURE_DB_OPTIONS
#ifdef DB_BF_TXN_NOT_DURABLE
if (db_txn_durable)
flags ^= DB_BF_TXN_NOT_DURABLE;
#endif
#endif
dbe_xinit(env, bfp, flags);
return env;
}
/* dummy infrastructure, to be expanded by environment
* or transactional initialization/shutdown */
static dbe_t *dbe_xinit(dbe_t *env, bfpath *bfp, u_int32_t flags)
{
int ret;
env->magic = MAGIC_DBE; /* poor man's type checking */
ret = bf_dbenv_create(&env->dbe);
if (db_cachesize != 0 &&
(ret = env->dbe->set_cachesize(env->dbe, db_cachesize/1024, (db_cachesize % 1024) * 1024*1024, 1)) != 0) {
print_error(__FILE__, __LINE__, "DB_ENV->set_cachesize(%u), err: %d, %s",
db_cachesize, ret, db_strerror(ret));
exit(EX_ERROR);
}
if (DEBUG_DATABASE(1))
fprintf(dbgout, "DB_ENV->set_cachesize(%u)\n", db_cachesize);
dbe_config(env);
flags |= DB_CREATE | dbenv_defflags;
ret = env->dbe->open(env->dbe, bfp->dirname, flags, DS_MODE);
if (ret != 0) {
env->dbe->close(env->dbe, 0);
print_error(__FILE__, __LINE__, "DB_ENV->open, err: %d, %s", ret, db_strerror(ret));
switch (ret) {
case DB_RUNRECOVERY:
diag_dbeopen(flags, bfp);
break;
case EINVAL:
fprintf(stderr, "\n"
"If you have just got a message that only private environments are supported,\n"
"your Berkeley DB %d.%d was not configured properly.\n"
"Bogofilter requires shared environments to support Berkeley DB transactions.\n",
DB_VERSION_MAJOR, DB_VERSION_MINOR);
fprintf(stderr,
"Reconfigure and recompile Berkeley DB with the right mutex interface,\n"
"see the docs/ref/build_unix/conf.html file that comes with your db source code.\n"
"This can happen when the DB library was compiled with POSIX threads\n"
"but your system does not support NPTL.\n");
break;
}
exit(EX_ERROR);
}
if (DEBUG_DATABASE(1))
fprintf(dbgout, "DB_ENV->open(home=%s)\n", bfp->dirname);
return env;
}
static void dbx_cleanup(dbe_t *env)
{
dbx_cleanup_lite(env);
}
/* close the environment, but do not release locks */
static void dbx_cleanup_lite(dbe_t *env)
{
if (env) {
if (env->dbe) {
int ret;
/* checkpoint if more than 64 kB of logs have been written
* or 120 min have passed since the previous checkpoint */
/* kB min flags */
ret = BF_TXN_CHECKPOINT(env->dbe, 64, 120, 0);
ret = dbx_sync(env->dbe, ret);
if (ret)
print_error(__FILE__, __LINE__, "DBE->dbx_checkpoint err: %d, %s", ret, db_strerror(ret));
if (db_log_autoremove)
dbe_env_purgelogs(env->dbe);
ret = env->dbe->close(env->dbe, 0);
if (DEBUG_DATABASE(1) || ret)
fprintf(dbgout, "DB_ENV->close(%p): %s\n", (void *)env->dbe,
db_strerror(ret));
clear_lock();
if (lockfd >= 0)
close(lockfd); /* release locks */
}
xfree(env->directory);
xfree(env);
}
}
static int dbx_sync(DB_ENV *dbe, int ret)
{
#if DB_AT_LEAST(3,0) && DB_AT_MOST(4,0)
/* flush dirty pages in buffer pool */
while (ret == DB_INCOMPLETE) {
rand_sleep(10000,1000000);
ret = BF_MEMP_SYNC(dbe, NULL);
}
#else
(void)dbe;
ret = 0;
#endif
return ret;
}
ex_t dbx_recover(bfpath *bfp, bool catastrophic, bool force)
{
dbe_t *env = (dbe_t *)xcalloc(1, sizeof(dbe_t));
/* set exclusive/write lock for recovery */
while ((force || needs_recovery())
&& (db_try_glock(bfp, F_WRLCK, F_SETLKW) <= 0))
rand_sleep(10000,1000000);
/* ok, when we have the lock, a concurrent process may have
* proceeded with recovery */
if (!(force || needs_recovery())) {
dbx_cleanup_lite(env);
return EX_OK;
}
if (DEBUG_DATABASE(0))
fprintf(dbgout, "dbx_recover() running %s data base recovery\n",
catastrophic ? "catastrophic" : "regular");
env = dbe_xinit(env, bfp,
catastrophic ? DB_RECOVER_FATAL : DB_RECOVER);
if (env == NULL) {
exit(EX_ERROR);
}
clear_lockfile();
dbx_cleanup_lite(env);
return EX_OK;
}
static ex_t dbx_common_close(DB_ENV *dbe, bfpath *bfp)
{
int e;
if (db_log_autoremove)
dbe_env_purgelogs(dbe);
if (DEBUG_DATABASE(0))
fprintf(dbgout, "closing environment\n");
e = dbe->close(dbe, 0);
if (e != 0) {
print_error(__FILE__, __LINE__, "Error closing environment \"%s\": %s",
bfp->dirname, db_strerror(e));
exit(EX_ERROR);
}
clear_lock();
db_try_glock(bfp, F_UNLCK, F_SETLKW); /* release lock */
return EX_OK;
}
static ex_t dbe_env_purgelogs(DB_ENV *dbe)
{
char **i, **list;
int e;
/* figure redundant log files and nuke them */
e = BF_LOG_ARCHIVE(dbe, &list, DB_ARCH_ABS);
if (e != 0) {
print_error(__FILE__, __LINE__,
"DB_ENV->log_archive failed: %s",
db_strerror(e));
exit(EX_ERROR);
}
if (list != NULL) {
if (DEBUG_DATABASE(0))
fprintf(dbgout, "removing inactive logfiles\n");
for (i = list; *i != NULL; i++) {
if (DEBUG_DATABASE(1))
fprintf(dbgout, " removing logfile %s\n", *i);
if (unlink(*i)) {
if (errno != ENOENT)
print_error(__FILE__, __LINE__,
"cannot unlink \"%s\": %s", *i, strerror(errno));
/* proceed anyways */
}
}
xfree(list);
}
return EX_OK;
}
/** checkpoint the open environment \a dbe once and unconditionally */
static ex_t dbe_env_checkpoint(DB_ENV *dbe) {
int e;
if (DEBUG_DATABASE(0))
fprintf(dbgout, "checkpoint database\n");
/* checkpoint the transactional system */
e = BF_TXN_CHECKPOINT(dbe, 0, 0, 0);
e = dbx_sync(dbe, e);
if (e != 0) {
print_error(__FILE__, __LINE__, "DB_ENV->txn_checkpoint failed: %s",
db_strerror(e));
exit(EX_ERROR);
}
return EX_OK;
}
static ex_t dbe_simpleop(bfpath *bfp, ex_t func(DB_ENV *env))
{
ex_t e;
DB_ENV *dbe = dbe_recover_open(bfp, 0);
if (dbe == NULL)
exit(EX_ERROR);
e = func(dbe);
dbx_common_close(dbe, bfp);
return e;
}
ex_t dbx_checkpoint(bfpath *bfp)
{
return dbe_simpleop(bfp, dbe_env_checkpoint);
}
static ex_t i_purgelogs(DB_ENV *dbe)
{
ex_t e = dbe_env_checkpoint(dbe);
if (e != EX_OK)
return e;
else
return dbe_env_purgelogs(dbe);
}
ex_t dbx_purgelogs(bfpath *bfp)
{
return dbe_simpleop(bfp, i_purgelogs);
}
ex_t dbx_remove(bfpath *bfp)
{
DB_ENV *dbe = dbe_recover_open(bfp, DB_PRIVATE);
if (dbe == NULL)
exit(EX_ERROR);
return dbx_common_close(dbe, bfp);
}
void dbx_log_flush(DB_ENV *dbe)
{
int ret;
ret = BF_LOG_FLUSH(dbe, NULL);
if (DEBUG_DATABASE(1))
fprintf(dbgout, "DB_ENV->log_flush(%p): %s\n", (void *)dbe,
db_strerror(ret));
}
const char **dsm_help_bogofilter(void)
{
static const char *help_text[] = {
NULL
};
return &help_text[0];
}
const char **dsm_help_bogoutil(void)
{
static const char *help_text[] = {
"environment maintenance:\n",
" --db-transaction=BOOL - enable or disable transactions\n",
" (only effective at creation time)\n",
" --db-verify=file - verify data file.\n",
" --db-checkpoint=dir - flush buffer cache and checkpoint database\n",
" --db-list-logfiles=dir [FLAGS]\n"
" - list logfiles in environment\n",
" --db-prune=dir - remove inactive log files in dir.\n",
" --db-recover=dir - run recovery on database in dir.\n",
" --db-recover-harder=dir - run catastrophic recovery on database.\n",
" --db-remove-environment - remove environment.\n",
#ifdef HAVE_DECL_DB_CREATE
" --db-lk-max-locks - set max lock count.\n",
" --db-lk-max-objects - set max object count.\n",
" --db-log-autoremove - set autoremoving of logs.\n",
#ifdef FUTURE_DB_OPTIONS
" --db-txn-durable - set durable mode.\n",
#endif
#endif
"\n",
NULL
};
return &help_text[0];
}
bool dsm_options_bogofilter(int option, const char *name, const char *val)
{
switch (option) {
case O_DB_TRANSACTION:
eTransaction = get_txn(name, val); return true;
#ifdef HAVE_DECL_DB_CREATE
case O_DB_LOG_AUTOREMOVE: db_log_autoremove = get_bool(name, val); return true;
#ifdef FUTURE_DB_OPTIONS
case O_DB_TXN_DURABLE: db_txn_durable = get_bool(name, val); return true;
#endif
#endif
default: return false;
}
}
bool dsm_options_bogoutil(int option, cmd_t *flag, int *count, const char **ds_file, const char *name, const char *val)
{
switch (option) {
case O_DB_TRANSACTION:
eTransaction = get_txn(name, val);
return true;
case O_DB_RECOVER:
*flag = M_RECOVER;
*count += 1;
*ds_file = val;
return true;
case O_DB_RECOVER_HARDER:
*flag = M_CRECOVER;
*count += 1;
*ds_file = val;
return true;
case O_DB_CHECKPOINT:
*flag = M_CHECKPOINT;
*count += 1;
*ds_file = val;
return true;
case O_DB_LIST_LOGFILES:
*flag = M_LIST_LOGFILES;
*count += 1;
*ds_file = val;
return true;
case O_DB_PRUNE:
*flag = M_PURGELOGS;
*count += 1;
*ds_file = val;
return true;
case O_DB_REMOVE_ENVIRONMENT:
*flag = M_REMOVEENV;
*ds_file = val;
*count += 1;
return true;
#ifdef FUTURE_DB_OPTIONS
case O_DB_TXN_DURABLE:
db_txn_durable = get_bool(name, val);
return true;
#endif
default:
return false;
}
}
/** probe if the directory contains an environment, and if so,
* if it has transactions
*/
e_txn probe_txn(bfpath *bfp)
{
DB_ENV *dbe;
int r;
#if DB_AT_LEAST(4,2)
u_int32_t flags;
#endif
r = db_env_create(&dbe, 0);
if (r) {
print_error(__FILE__, __LINE__, "cannot create environment handle: %s",
db_strerror(r));
return T_ERROR;
}
/* we might call dbe->set_flags here to set DB_NOPANIC, but this is
* only supported from 4.1 onwards and probably not worth the
* effort, we'll just check for DB_RUNRECOVERY */
#if DB_AT_LEAST(3,2)
r = dbe->open(dbe, bfp->dirname, DB_JOINENV, DS_MODE);
#else
r = ENOENT;
#endif
if (r == DB_RUNRECOVERY) {
dbe->close(dbe, 0);
return T_ENABLED;
}
if (r == ENOENT) {
struct stat st;
char *t = bfp->filepath;
struct dirent *de;
e_txn rc = T_DONT_KNOW;
DIR *d;
/* no environment found by JOINENV, but clean up handle */
dbe->close(dbe, 0);
/* retry, looking for log\.[0-9]{10} files - needed for instance
* after bogoutil --db-remove DIR or when DB_JOINENV is
* unsupported */
d = opendir(bfp->dirname);
if (d == NULL) {
print_error(__FILE__, __LINE__, "cannot open directory %s: %s",
t, strerror(r));
rc = T_ERROR;
} else {
while ((errno = 0, de = readdir(d))) {
if (strlen(de->d_name) == 14
&& strncmp(de->d_name, "log.", 4) == 0
&& strspn(de->d_name + 4, "0123456789") == 10)
{
rc = T_ENABLED;
break;
}
}
if (errno)
rc = T_ERROR;
closedir(d);
if (rc != T_ERROR && rc != T_ENABLED) {
int w;
w = stat(t, &st);
if (w == 0) {
rc = T_DISABLED;
} else if (errno != ENOENT) {
rc = T_ERROR;
print_error(__FILE__, __LINE__, "cannot stat %s: %s",
t, db_strerror(r));
}
}
}
return rc;
} /* if (r == ENOENT) for environment join */
if (r != 0) {
print_error(__FILE__, __LINE__, "cannot join environment: %s",
db_strerror(r));
return T_ERROR;
}
/* environment found, validate if it has transactions */
#if DB_AT_LEAST(4,2)
r = dbe->get_open_flags(dbe, &flags);
if (r) {
print_error(__FILE__, __LINE__, "cannot query flags: %s",
db_strerror(r));
return T_ERROR;
}
dbe->close(dbe, 0);
if ((flags & DB_INIT_TXN) == 0) {
print_error(__FILE__, __LINE__,
"environment found but does not support transactions.");
return T_ERROR;
}
#else
dbe->close(dbe, 0);
#endif
return T_ENABLED;
}
static ex_t dbx_list_logfiles(bfpath *bfp, int argc, char **argv)
{
ex_t e;
char **list, **i;
int j;
u_int32_t flags = 0;
DB_ENV *dbe = dbe_recover_open(bfp, 0);
if (dbe == NULL)
exit(EX_ERROR);
for (j = 0; j < argc; j++) {
if (strcasecmp(argv[j], "all") == 0)
flags |= DB_ARCH_LOG;
if (strcasecmp(argv[j], "absolute") == 0)
flags |= DB_ARCH_ABS;
}
j = BF_LOG_ARCHIVE(dbe, &list, flags);
if (j != 0) {
print_error(__FILE__, __LINE__,
"DB_ENV->log_archive failed: %s",
db_strerror(j));
exit(EX_ERROR);
}
if (list) {
for (i = list; *i; i++) {
if (flags & DB_ARCH_ABS)
puts(*i);
else
printf("%s%s%s\n", bfp->dirname, DIRSEP_S, *i);
}
}
xfree(list);
fflush(stdout);
e = ferror(stdout) ? EX_ERROR : EX_OK;
if (dbx_common_close(dbe, bfp)) e = EX_ERROR;
return e;
}