/* This file is part of Mailfromd.
Copyright (C) 2005, 2006, 2007, 2008 Sergey Poznyakoff
This program 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.
This program 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 this program. If not, see . */
#define MF_SOURCE_NAME MF_SOURCE_DB
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include "mailfromd.h"
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include
#include
#include
struct db_fmt_list {
struct db_fmt_list *next;
struct db_format fmt;
};
static struct db_fmt_list *db_fmt_list;
void
db_format_enumerate(symbol_enumerator_t fp, void *data)
{
struct db_fmt_list *p;
for (p = db_fmt_list; p; p = p->next)
fp(&p->fmt, data);
}
struct db_format *
db_format_install(struct db_format *fmt)
{
struct db_fmt_list *p;
if (!fmt)
return NULL;
p = xmalloc(sizeof *p);
p->fmt = *fmt;
p->fmt.dbname = strdup(p->fmt.dbname);
p->next = db_fmt_list;
db_fmt_list = p;
return &p->fmt;
}
struct db_format *
db_format_lookup(const char *name)
{
struct db_fmt_list *p;
for (p = db_fmt_list; p; p = p->next)
if (strcmp(p->fmt.name, name) == 0)
return &p->fmt;
return NULL;
}
int
db_list_item(char *dbname, char *email, db_item_printer_t fun)
{
DBM_FILE db;
DBM_DATUM key, contents;
int rc = 1;
int res;
if (mu_dbm_open(dbname, &db, MU_STREAM_READ, 0600, NULL)) {
mu_error(_("mu_dbm_open(%s) failed: %s"), dbname,
mu_dbm_strerror());
return 1;
}
memset (&contents, 0, sizeof contents);
memset(&key, 0, sizeof key);
MU_DATUM_PTR(key) = email;
MU_DATUM_SIZE(key) = strlen (email) + 1;
if ((res = mu_dbm_fetch(&db, key, &contents)) == 0) {
fun(MU_DATUM_PTR(key),
MU_DATUM_SIZE(key),
MU_DATUM_PTR(contents));
rc = 0;
} else if (res != MU_ERR_NOENT)
mu_error(_("Cannot fetch record `%s' from `%s': %s"),
email, db.name, mu_dbm_strerror());
mu_dbm_close(&db);
return rc;
}
typedef int (*db_enum_func)(DBM_DATUM key, DBM_DATUM cnt, void *data);
int
db_enumerate(DBM_FILE *db, db_enum_func fun, void *data)
{
int rc = 0;
int res;
DBM_DATUM key, prev, contents;
for (res = mu_dbm_firstkey (db, &key); res == 0;
prev = key,
res = mu_dbm_nextkey (db, prev, &key),
mu_dbm_datum_free(&prev)) {
memset (&contents, 0, sizeof contents);
if (mu_dbm_fetch (db, key, &contents) == 0) {
int fr = fun(key, contents, data);
mu_dbm_datum_free(&contents);
if (fr) {
mu_dbm_datum_free(&key);
break;
}
} else {
mu_error(_("Cannot fetch data `%*.*s' from `%s': %s"),
MU_DATUM_SIZE(key), MU_DATUM_SIZE(key),
(char*) MU_DATUM_PTR(key),
db->name,
mu_dbm_strerror());
rc = 1;
}
}
if (res != MU_ERR_NOENT) {
mu_error(_("Unexpected error scanning database %s: %s"),
db->name, mu_dbm_strerror());
rc = 1;
}
return rc;
}
static int
db_list_func(DBM_DATUM key, DBM_DATUM contents, void *data)
{
db_item_printer_t fun = data;
fun(MU_DATUM_PTR(key), MU_DATUM_SIZE(key), MU_DATUM_PTR(contents));
return 0;
}
int
db_list(char *dbname, db_item_printer_t fun)
{
DBM_FILE db;
if (mu_dbm_open(dbname, &db, MU_STREAM_READ, 0600, NULL)) {
mu_error(_("mu_dbm_open(%s) failed: %s"), dbname,
mu_dbm_strerror());
return 1;
}
db_enumerate(&db, db_list_func, fun);
mu_dbm_close(&db);
return 1;
}
struct expire_data {
db_expire_t fun;
struct obstack stk;
size_t key_count;
};
static int
db_expire_func(DBM_DATUM key, DBM_DATUM contents, void *data)
{
struct expire_data *dp = data;
if (dp->fun(MU_DATUM_PTR(contents))) {
size_t size = MU_DATUM_SIZE(key);
obstack_grow(&dp->stk, &size, sizeof size);
obstack_grow(&dp->stk, MU_DATUM_PTR(key), size);
dp->key_count++;
}
return 0;
}
int
db_expire(char *dbname, db_expire_t fun)
{
DBM_FILE db;
DBM_DATUM key;
struct expire_data ed;
size_t size;
char *base, *p;
size_t i;
int rc = 0;
if (mu_dbm_open(dbname, &db, MU_STREAM_RDWR, 0600, NULL)) {
mu_error(_("mu_dbm_open(%s) failed: %s"), dbname,
mu_dbm_strerror());
return 1;
}
ed.fun = fun;
obstack_init(&ed.stk);
ed.key_count = 0;
debug1(1, "Expiring %s: mark phase", dbname);
if (db_enumerate(&db, db_expire_func, &ed))
rc = !ignore_failed_reads_option;
size = 0;
obstack_grow(&ed.stk, &size, sizeof size);
debug2(1, "Number of keys to expire: %lu, memory used: %u",
(unsigned long) ed.key_count,
(unsigned) obstack_memory_used(&ed.stk));
debug1(1, "Expiring %s: sweep phase", dbname);
base = obstack_finish(&ed.stk);
for (i = 0, p = base; i < ed.key_count; i++) {
size = *(size_t*)p;
p += sizeof(size_t);
debug3(50, "Remove: %*.*s", size, size, p);
MU_DATUM_PTR(key) = p;
MU_DATUM_SIZE(key) = size;
if (mu_dbm_delete(&db, key)) {
mu_error (_("Cannot remove record `%*.*s' from `%s': %s"),
size, size, p, db.name, mu_dbm_strerror());
rc = 1;
}
p += size;
}
debug1(1, "Finished expiring %s", dbname);
obstack_free(&ed.stk, NULL);
mu_dbm_close(&db);
return rc;
}
int
db_delete(char *dbname, char *id)
{
DBM_FILE db;
DBM_DATUM key;
int rc;
if (mu_dbm_open(dbname, &db, MU_STREAM_RDWR, 0600, NULL)) {
mu_error(_("mu_dbm_open(%s) failed: %s"), dbname,
mu_dbm_strerror());
return 1;
}
memset(&key, 0, sizeof key);
MU_DATUM_PTR(key) = id;
MU_DATUM_SIZE(key) = strlen (id) + 1;
if (mu_dbm_delete(&db, key)) {
mu_error(_("Cannot remove record `%s' from `%s': %s"), id,
dbname, mu_dbm_strerror());
rc = 1;
} else
rc = 0;
mu_dbm_close(&db);
return rc;
}
static char *
make_tmp_name(char *dbname)
{
char *suf = strrchr(dbname, '.');
char *p = strrchr(dbname, '/');
size_t len;
char *newname;
char buf[80];
if (p)
len = p - dbname;
else if (suf)
len = suf - dbname;
else
len = strlen(dbname);
snprintf(buf, sizeof buf, "%lu", (unsigned long) getpid());
newname = xmalloc(len + 1 + strlen(buf) + 1);
memcpy(newname, dbname, len);
newname[len] = '/';
p = strcpy(newname + len + 1, buf);
return newname;
}
struct compact_data {
int rc;
db_expire_t fun;
DBM_FILE ndb;
};
static int
db_compact_func(DBM_DATUM key, DBM_DATUM contents, void *data)
{
struct compact_data *dp = data;
if (!dp->fun || dp->fun(MU_DATUM_PTR(contents)) == 0) {
if (((char*)MU_DATUM_PTR(key))[MU_DATUM_SIZE(key) - 1]) {
/* Old database format. Convert. */
size_t size;
char *p;
DBM_DATUM newkey;
size = MU_DATUM_SIZE(key);
p = malloc(size+1);
if (!MU_DATUM_PTR(newkey)) {
mu_error(_("Not enough memory"));
return 1;
}
memcpy(p, MU_DATUM_PTR(key), size);
p[size] = 0;
memset(&newkey, 0, sizeof newkey);
MU_DATUM_SIZE(newkey) = size+1;
MU_DATUM_PTR(newkey) = p;
if (mu_dbm_insert(&dp->ndb, newkey, contents, 1)) {
mu_error(_("Cannot insert datum `%s' into `%s': %s"),
p, dp->ndb.name,
mu_dbm_strerror());
dp->rc = 1;
}
free(p);
} else if (mu_dbm_insert(&dp->ndb, key, contents, 1)) {
mu_error(_("Cannot insert datum `%*.*s' into `%s': %s"),
MU_DATUM_SIZE(key), MU_DATUM_SIZE(key),
(char*) MU_DATUM_PTR(key),
dp->ndb.name,
mu_dbm_strerror());
dp->rc = 1;
}
}
return 0;
}
int
db_compact(char *dbname, db_expire_t fun)
{
DBM_FILE odb;
char *tmpname;
struct compact_data dat;
debug1(1, "Compacting database `%s'", dbname);
tmpname = make_tmp_name(dbname);
if (mu_dbm_open(dbname, &odb, MU_STREAM_READ, 0600, NULL)) {
mu_error(_("mu_dbm_open(%s) failed: %s"), dbname,
mu_dbm_strerror());
return 1;
}
dat.rc = 0;
dat.fun = fun;
if (mu_dbm_open(tmpname, &dat.ndb, MU_STREAM_CREAT, 0600, NULL)) {
mu_error(_("mu_dbm_open(%s) failed: %s"), tmpname,
mu_dbm_strerror());
return 1;
}
db_enumerate(&odb, db_compact_func, &dat);
mu_dbm_close(&dat.ndb);
mu_dbm_close(&odb);
if (dat.rc == 0) {
if (unlink(dbname)) {
mu_error(_("Cannot unlink %s: %s"),
dbname, mu_strerror(errno));
dat.rc = 1;
} else if (rename(tmpname, dbname)) {
mu_error(_("Cannot rename %s to %s: %s"),
tmpname, dbname, mu_strerror(errno));
dat.rc = 1;
}
}
free(tmpname);
return dat.rc;
}
size_t
format_time_str(FILE *fp, time_t timestamp)
{
struct tm tm;
localtime_r(×tamp, &tm);
return fprintftime(fp, time_format_string, &tm, 0, 0);
}