/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2002, 2005, 2006,
2007 Free Software Foundation, Inc.
GNU Mailutils is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Mailutils is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Mailutils. If not, see . */
#define MF_SOURCE_NAME MF_SOURCE_MU_DBM
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#ifdef HAVE_STRINGS_H
# include
#endif
#include
#include
#include
#include
#ifndef MU_ERR_EXISTS
# define MU_ERR_EXISTS EEXIST
#endif
int
mu_fcheck_perm (int fd, int mode)
{
struct stat st;
if (fstat (fd, &st) == -1)
{
if (errno == ENOENT)
return ENOENT;
else
return MU_ERR_FAILURE;
}
if ((st.st_mode & 0777) != mode)
return errno = MU_ERR_UNSAFE_PERMS;
return 0;
}
int
mu_check_perm (const char *name, int mode)
{
struct stat st;
if (mode == 0)
return 0;
if (stat (name, &st) == -1)
{
if (errno == ENOENT)
return ENOENT;
else
return MU_ERR_FAILURE;
}
if ((st.st_mode & 0777) != mode)
return errno = MU_ERR_UNSAFE_PERMS;
return 0;
}
#if defined(WITH_GDBM)
int
mu_dbm_open (char *name, DBM_FILE *db, int flags, int mode, int *ro)
{
int rc;
int f;
size_t i;
GDBM_FILE gdbm;
rc = mu_check_perm (name, mode);
if (rc && rc != ENOENT)
return MU_ERR_UNSAFE_PERMS;
if (ro)
*ro = 0;
switch (flags)
{
case MU_STREAM_CREAT:
f = GDBM_NEWDB;
break;
case MU_STREAM_READ:
f = GDBM_READER;
break;
case MU_STREAM_RDWR:
f = GDBM_WRCREAT;
break;
default:
errno = EINVAL;
return 1;
}
for (i = 0; i < lock_retry_count_option; i++)
{
gdbm = gdbm_open (name, 512, f, mode, NULL);
if (gdbm || errno != EAGAIN)
break;
sleep (lock_retry_timeout_option);
}
if (!gdbm)
{
if (ro)
{
gdbm = gdbm_open (name, 512, GDBM_READER, mode, NULL);
if (gdbm)
*ro = 1;
}
}
rc = (gdbm == NULL) ? MU_ERR_FAILURE : 0;
if (rc == 0)
{
db->db = gdbm;
db->name = strdup (name);
}
return rc;
}
int
mu_dbm_close (DBM_FILE *db)
{
gdbm_close(db->db);
free (db->name);
return 0;
}
int
mu_dbm_fetch (DBM_FILE *db, DBM_DATUM key, DBM_DATUM *ret)
{
*ret = gdbm_fetch(db->db, key);
if (ret->dptr == NULL)
return MU_ERR_NOENT;
return 0;
}
int
mu_dbm_delete (DBM_FILE *db, DBM_DATUM key)
{
return gdbm_delete (db->db, key) ? MU_ERR_FAILURE : 0;
}
int
mu_dbm_insert (DBM_FILE *db, DBM_DATUM key, DBM_DATUM contents, int replace)
{
switch (gdbm_store(db->db, key, contents,
replace ? GDBM_REPLACE : GDBM_INSERT))
{
case -1:
return MU_ERR_FAILURE;
case 0:
return 0;
case 1:
return MU_ERR_EXISTS;
}
return MU_ERR_FAILURE;
}
int
mu_dbm_firstkey (DBM_FILE *db, DBM_DATUM *pkey)
{
*pkey = gdbm_firstkey (db->db);
return (pkey->dptr == NULL) ? MU_ERR_NOENT : 0;
}
int
mu_dbm_nextkey (DBM_FILE *db, DBM_DATUM prevkey, DBM_DATUM *pkey)
{
*pkey = gdbm_nextkey (db->db, prevkey);
return (pkey->dptr == NULL) ? MU_ERR_NOENT : 0;
}
void
mu_dbm_datum_free (DBM_DATUM *datum)
{
void *ptr = MU_DATUM_PTR (*datum);
if (ptr)
free (ptr);
MU_DATUM_PTR (*datum) = 0;
}
const char *
mu_dbm_strerror ()
{
if (errno == MU_ERR_UNSAFE_PERMS)
return mu_strerror (errno);
return gdbm_strerror (gdbm_errno);
}
#elif defined(WITH_BDB)
int mu_dbm_errno;
static int
try_lock (int fd, int type, struct flock *fl)
{
int i, rc;
for (i = 0; i < lock_retry_count_option; i++)
{
alarm (lock_retry_timeout_option);
rc = fcntl (fd, F_SETLKW, fl);
alarm (0);
if (rc == 0)
return 0;
}
return errno;
}
static int
lock_file (const char *name, int fd, int type)
{
int rc = 0;
struct flock fl;
if (type == F_UNLCK)
debug1(10, "unlocking %s", name);
else
debug2(10, "locking %s for %s",
name,
(type == F_RDLCK) ? "reading" : "writing");
memset (&fl, 0, sizeof fl);
fl.l_whence = SEEK_SET;
fl.l_type = type;
rc = try_lock (fd, type, &fl);
if (rc)
{
fl.l_pid = 0;
if (fcntl (fd, F_GETLK, &fl) != -1)
{
if (fl.l_type == F_UNLCK)
{
memset (&fl, 0, sizeof fl);
fl.l_whence = SEEK_SET;
fl.l_type = type;
rc = try_lock (fd, type, &fl);
if (rc)
mu_error (_("%s: cannot aquire lock: %s"),
name, mu_strerror (rc));
}
else if (fl.l_pid)
{
if (kill (fl.l_pid, 0) == 0)
mu_error (_("%s is locked (%d) by process ID %lu"),
name,
fl.l_type,
(unsigned long) fl.l_pid);
else if (errno == ESRCH)
{
mu_error (_("%s is locked by nonexisting process ID %lu; "
"retrying"),
name, (unsigned long) fl.l_pid);
if (unlink (name))
mu_error (_("Cannot unlink file %s: %s"),
name, mu_strerror (errno));
else
{
rc = try_lock (fd, type, &fl);
if (rc)
mu_error (_("%s: cannot aquire lock: %s"),
name, mu_strerror (rc));
}
}
else
mu_error (_("%s seems locked by process ID %lu, "
"but its validity cannot be verified: %s"),
name, (unsigned long) fl.l_pid,
mu_strerror (errno));
}
else
mu_error (_("%s: cannot aquire lock: %s"),
name, mu_strerror (rc));
}
else
mu_error (_("Cannot obtain locker info for %s: %s"),
name, mu_strerror (errno));
}
return rc;
}
#ifndef DB_FCNTL_LOCKING
#define LOCK_SUFFIX ".lock"
static int
make_lockfile (char *name, int lktype, int *pfd, char **plockname)
{
int rc;
int fd;
char *p, *lockname;
size_t size, namesize;
size = strlen (mailfromd_state_dir);
if (strlen (name) > size
&& memcmp (name, mailfromd_state_dir, size) == 0
&& name[size] == '/')
p = name + size + 1;
else
p = name;
namesize = size + 1 + strlen (p) + sizeof LOCK_SUFFIX;
lockname = malloc (namesize);
if (!lockname)
return errno;
strcpy (lockname, mailfromd_state_dir);
strcat (lockname, "/");
strcat (lockname, p);
strcat (lockname, LOCK_SUFFIX);
for (p = lockname + size + 1; *p; p++)
if (*p == '/')
*p = '-';
fd = open (lockname, O_CREAT|O_RDWR, 0600);
if (fd == -1)
{
free (lockname);
return errno;
}
rc = lock_file (lockname, fd, lktype);
if (rc)
{
free (lockname);
return rc;
}
*plockname = lockname;
*pfd = fd;
return 0;
}
#endif
static void
mu_dbm_errcall_fcn (const char *errpfx, char *msg)
{
if (errpfx)
mu_error ("%s: %s: %s", errpfx, _("Berkeley DB error"), msg);
else
mu_error ("%s: %s", _("Berkeley DB error"), msg);
}
#if DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 1
# define DB_TXN_NULL NULL,
#else
# define DB_TXN_NULL
#endif
int
mu_dbm_open (char *name, DBM_FILE *dbm, int flags, int mode, int *ro)
{
int f = 0;
DB *db = NULL;
int oflags;
# define LKTYPE(o) (((o) & O_RDWR) ? F_WRLCK : F_RDLCK)
debug1(10, "opening database %s", name);
mu_dbm_errno = mu_check_perm (name, mode);
if (mu_dbm_errno && mu_dbm_errno != ENOENT)
return MU_ERR_UNSAFE_PERMS;
if (ro)
*ro = 0;
switch (flags)
{
case MU_STREAM_CREAT:
f = DB_CREATE | DB_TRUNCATE;
oflags = O_CREAT | O_TRUNC | O_RDWR;
break;
case MU_STREAM_READ:
if (mu_dbm_errno == ENOENT)
return MU_ERR_FAILURE;
f = DB_RDONLY;
oflags = O_RDONLY;
break;
case MU_STREAM_RDWR:
f = DB_CREATE;
oflags = O_CREAT | O_RDWR;
if (mu_dbm_errno == ENOENT)
f |= DB_TRUNCATE;
break;
default:
mu_dbm_errno = EINVAL;
return MU_ERR_FAILURE;
}
dbm->lockname = NULL;
#ifdef DB_FCNTL_LOCKING
f |= DB_FCNTL_LOCKING;
dbm->lockfd = open (name, oflags, mode);
if (dbm->lockfd == -1)
{
mu_dbm_errno = errno;
return MU_ERR_FAILURE;
}
mu_dbm_errno = lock_file (name, dbm->lockfd, LKTYPE(oflags));
if (mu_dbm_errno)
{
mu_error (_("Cannot lock file %s: %s"),
name, mu_strerror (mu_dbm_errno));
close (dbm->lockfd);
return MU_ERR_FAILURE;
}
#else
mu_dbm_errno = make_lockfile (name, LKTYPE(oflags), &dbm->lockfd,
&dbm->lockname);
if (mu_dbm_errno)
{
mu_error (_("Cannot lock file %s: %s"),
name, mu_strerror (mu_dbm_errno));
return MU_ERR_FAILURE;
}
#endif
#if DB_VERSION_MAJOR == 2
mu_dbm_errno = db_open (name, DB_HASH, f, mode, NULL, NULL, &db);
#else
mu_dbm_errno = db_create (&db, NULL, 0);
if (mu_dbm_errno != 0 || db == NULL)
return MU_ERR_FAILURE;
# if DB_VERSION_MAJOR == 3
mu_dbm_errno = db->open (db, name, NULL, DB_HASH, f, mode);
# else
mu_dbm_errno = db->open (db, DB_TXN_NULL name, NULL, DB_HASH, f, mode);
# endif
#endif
if (mu_dbm_errno)
{
if (dbm->lockname)
{
unlink (dbm->lockname);
free (dbm->lockname);
}
close (dbm->lockfd);
return MU_ERR_FAILURE;
}
db->sync (db, 0);
__DBG(1)
{
#if DB_VERSION_MAJOR > 2
db->set_errcall (db, mu_dbm_errcall_fcn);
#endif
}
dbm->name = strdup (name);
dbm->db = db;
dbm->dbc = NULL;
return 0;
}
int
mu_dbm_close (DBM_FILE *db)
{
debug1(10, "closing database %s", db->name);
if (db->dbc)
{
mu_dbm_errno = db->dbc->c_close(db->dbc);
db->dbc = NULL;
if (mu_dbm_errno)
return MU_ERR_FAILURE;
}
db->db->sync (db->db, 0);
mu_dbm_errno = db->db->close (db->db, 0);
if (db->lockname)
{
debug1(10, "removing lock file %s", db->lockname);
unlink (db->lockname);
free (db->lockname);
}
close (db->lockfd);
free (db->name);
return (mu_dbm_errno == 0) ? 0 : MU_ERR_FAILURE;
}
int
mu_dbm_fetch (DBM_FILE *db, DBM_DATUM key, DBM_DATUM *ret)
{
mu_dbm_errno = db->db->get (db->db, NULL, &key, ret, 0);
switch (mu_dbm_errno)
{
case 0:
return 0;
case DB_NOTFOUND:
return MU_ERR_NOENT;
default:
return MU_ERR_FAILURE;
}
}
int
mu_dbm_delete (DBM_FILE *dbm, DBM_DATUM key)
{
mu_dbm_errno = dbm->db->del (dbm->db, NULL, &key, 0);
return mu_dbm_errno ? MU_ERR_FAILURE : 0;
}
int
mu_dbm_insert (DBM_FILE *dbm, DBM_DATUM key, DBM_DATUM contents, int replace)
{
int flags = replace ? 0 : DB_NOOVERWRITE;
int rc = 0;
mu_dbm_errno = dbm->db->put (dbm->db, NULL, &key, &contents, flags);
switch (mu_dbm_errno)
{
case 0:
break;
case DB_KEYEXIST:
rc = MU_ERR_EXISTS;
default:
rc = MU_ERR_FAILURE;
}
return rc;
}
int
mu_dbm_firstkey (DBM_FILE *db, DBM_DATUM *pkey)
{
DBT data;
memset(pkey, 0, sizeof *pkey);
memset(&data, 0, sizeof data);
if (!db->dbc)
{
mu_dbm_errno = db->db->cursor(db->db, NULL, &db->dbc
BDB2_CURSOR_LASTARG);
if (mu_dbm_errno)
return MU_ERR_FAILURE;
}
mu_dbm_errno = db->dbc->c_get(db->dbc, pkey, &data, DB_FIRST);
switch (mu_dbm_errno)
{
case 0:
return 0;
case DB_NOTFOUND:
return MU_ERR_NOENT;
default:
return MU_ERR_FAILURE;
}
}
int
mu_dbm_nextkey (DBM_FILE *db, DBM_DATUM prevkey /*unused*/, DBM_DATUM *pkey)
{
DBT data;
memset(pkey, 0, sizeof *pkey);
memset(&data, 0, sizeof data);
if (!db->dbc)
{
mu_dbm_errno = EINVAL;
return MU_ERR_FAILURE;
}
mu_dbm_errno = db->dbc->c_get(db->dbc, pkey, &data, DB_NEXT);
switch (mu_dbm_errno)
{
case 0:
return 0;
case DB_NOTFOUND:
return MU_ERR_NOENT;
default:
return MU_ERR_FAILURE;
}
}
void
mu_dbm_datum_free(DBM_DATUM *datum)
{
/* empty */
}
const char *
mu_dbm_strerror ()
{
#if DB_VERSION_MAJOR == 2
switch (mu_dbm_errno)
{
case DB_INCOMPLETE:
return "Sync didn't finish";
case DB_KEYEMPTY:
return "The key/data pair was deleted or was never created by the user";
break;
case DB_KEYEXIST:
return "The key/data pair already exists";
case DB_LOCK_DEADLOCK:
return "Locker killed to resolve deadlock";
case DB_LOCK_NOTGRANTED:
return "Lock unavailable, no-wait set";
case DB_LOCK_NOTHELD:
return "Lock not held by locker";
case DB_NOTFOUND:
return "Key/data pair not found (EOF)";
default:
return mu_strerror (mu_dbm_errno);
}
#else
if (mu_dbm_errno < 0)
return db_strerror (mu_dbm_errno);
else
return mu_strerror (mu_dbm_errno);
#endif
}
#endif
/*
Local Variables:
c-file-style: "gnu"
End:
*/