/* 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); }