/* This file is part of Mailfromd.
Copyright (C) 2005-2020 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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include "libmf.h"
#include "mfdb.h"
#include "filenames.h"
char *state_dir = DEFAULT_STATE_DIR;
char *file_option; /* File name for DB management commands */
struct db_format *format_option;
time_t expire_interval = 0; /* When set, overrides the expire interval
for the selected database */
int all_option; /* Process all databases */
char *db_type_str = DEFAULT_DB_TYPE;
void (*run)(int argc, char **argv) = NULL;
void
assert_db_format()
{
if (!format_option) {
mu_error(_("operation is not applicable"));
exit(EX_USAGE);
}
}
char *
get_db_name()
{
assert_db_format();
if (file_option)
return file_option;
if (format_option->dbname)
return format_option->dbname;
mu_error(_("database file name is not given"));
exit(EX_USAGE);
}
int
convert_rate(const char *arg, double *rate)
{
double count;
char *p;
count = strtod(arg, &p);
if (*p) {
time_t div;
const char *endp;
while (*p && mu_isspace(*p))
p++;
if (mu_isalpha(*p)) {
if (strlen(p) > 3
&& memcmp(p, "per", 3) == 0 && mu_isspace(p[3]))
p += 3;
} else if (mu_ispunct(*p))
p++;
if (parse_time_interval(p, &div, &endp)) {
mu_error(_("unrecognized time format (near `%s')"),
endp);
return 1;
}
count /= div;
}
*rate = count;
return 0;
}
void
mfdbtool_delete(int argc, char **argv)
{
int i;
char *name = get_db_name();
for (i = 0; i < argc; i++)
db_delete(name, argv[i]);
}
void
mfdbtool_list(int argc, char **argv)
{
int rc;
char *name = get_db_name();
if (!format_option->print_item) {
/* Should not happen */
mu_error(_("list is not applicable to this DB format"));
exit(EX_USAGE);
}
rc = 0;
if (argc) {
int i;
for (i = 0; i < argc; i++)
rc += db_list_item(name, argv[i],
format_option->print_item);
} else
rc = db_list(name, format_option->print_item);
exit(rc != 0);
}
static int
db_proc_enumerator(struct db_format *fmt, void *data)
{
int (*func)(char *name, db_expire_t exp) = data;
if (fmt->expire)
func(fmt->dbname, fmt->expire);
return 0;
}
void
mfdbtool_expire(int argc, char **argv)
{
if (all_option)
db_format_enumerate(db_proc_enumerator, db_expire);
else {
if (!format_option->expire) {
mu_error(_("expire is not applicable to this DB format"));
exit(EX_USAGE);
}
exit(db_expire(get_db_name(), format_option->expire) != 0);
}
}
void
mfdbtool_compact(int argc, char **argv)
{
if (all_option)
db_format_enumerate(db_proc_enumerator, db_compact);
else {
if (!format_option->expire) {
mu_error(_("compact is not applicable to this DB format"));
exit(EX_USAGE);
}
exit(db_compact(get_db_name(), format_option->expire) != 0);
}
}
const char *program_version = "mfdbtool (" PACKAGE_STRING ")";
static char prog_doc[] = N_("mfdbtool -- Mailfromd database management tool");
static void
opt_delete(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
run = mfdbtool_delete;
}
static void
opt_list(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
run = mfdbtool_list;
}
static void
opt_expire(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
run = mfdbtool_expire;
}
static void
opt_compact(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
run = mfdbtool_compact;
}
static void
opt_format(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
format_option = db_format_lookup(arg);
if (format_option == NULL) {
mu_parseopt_error(po, _("unknown database format: %s"), arg);
exit(po->po_exit_error);
}
}
static void
opt_predict(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
if (convert_rate(arg, &predict_rate) == 0) {
format_option = db_format_lookup("rate");
predict_next_option = 1;
run = mfdbtool_list;
}
}
static void
opt_debug(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
mu_debug_parse_spec(arg);
}
static void
opt_expire_interval(struct mu_parseopt *po, struct mu_option *op,
char const *arg)
{
time_t interval;
const char *endp;
if (parse_time_interval(arg, &interval, &endp)) {
mu_parseopt_error(po,
_("unrecognized time format (near `%s')"),
endp);
exit(po->po_exit_error);
}
expire_interval = interval;
}
static struct mu_option mfdbtool_options[] = {
MU_OPTION_GROUP(N_("Database management commands")),
{ "delete", 0, NULL, MU_OPTION_DEFAULT,
N_("delete given entries from the database"),
mu_c_string, NULL, opt_delete },
{ "list", 0, NULL, MU_OPTION_DEFAULT,
N_("list given database (default cache)"),
mu_c_string, NULL, opt_list },
{ "expire", 0, NULL, MU_OPTION_DEFAULT,
N_("delete expired entries from the database"),
mu_c_string, NULL, opt_expire },
{ "compact", 0, NULL, MU_OPTION_DEFAULT,
N_("compact the database"),
mu_c_string, NULL, opt_compact },
MU_OPTION_GROUP(N_("Command modifiers")),
{ "file", 'f', N_("FILE"), MU_OPTION_DEFAULT,
N_("DB file name to operate upon"),
mu_c_string, &file_option },
{ "format", 'H', N_("FORMAT"), MU_OPTION_DEFAULT,
N_("format of the DB file to operate upon"),
mu_c_string, NULL, opt_format },
{ "ignore-failed-reads", 0, NULL, MU_OPTION_DEFAULT,
N_("ignore failed reads while compacting the database"),
mu_c_bool, &ignore_failed_reads_option },
{ "predict", 0, N_("RATE"), MU_OPTION_DEFAULT,
N_("predict when the user will be able to send next message"),
mu_c_string, NULL, opt_predict },
{ "all", 0, NULL, MU_OPTION_DEFAULT,
N_("with --compact or --expire: apply the operation to all "
"available databases"),
mu_c_bool, &all_option },
/* FIXME
{ "time-format", 0, N_("FMT"), MU_OPTION_DEFAULT,
N_("output timestamps using given format (default \"%c\")"),
},
*/
{ "debug", 'd', N_("LEVEL"), MU_OPTION_DEFAULT,
N_("set debugging level"),
mu_c_string, NULL, opt_debug },
MU_OPTION_GROUP(N_("Configuration modifiers")),
{ "state-directory", 0, N_("DIR"), MU_OPTION_DEFAULT,
N_("set new program state directory"),
mu_c_string, &state_dir },
{ "expire-interval", 'e', N_("NUMBER"), MU_OPTION_DEFAULT,
N_("set database expiration interval to NUMBER seconds"),
mu_c_string, NULL, opt_expire_interval },
MU_OPTION_END
}, *options[] = { mfdbtool_options, NULL };
static int
db_proc_set_expire(struct db_format *fmt, void *data)
{
fmt->expire_interval = expire_interval;
return 0;
}
static int
cb_debug(void *data, mu_config_value_t *arg)
{
if (mu_cfg_assert_value_type(arg, MU_CFG_STRING))
return 1;
mu_debug_parse_spec(arg->v.string);
return 0;
}
struct mu_cfg_param mfdbtool_cfg_param[] = {
{ "database-type", mu_c_string, &db_type_str, 0, NULL,
N_("Default database type"),
N_("type") },
{ "database", mu_cfg_section, NULL, 0, NULL, NULL },
{ "database-mode", mu_cfg_callback,
&mf_database_mode, 0, cb_database_mode,
N_("Configure file mode for database files"),
N_("mode: octal") },
{ "state-directory", mu_c_string, &state_dir, 0, NULL,
N_("Set program state directory."),
N_("dir") },
{ "debug", mu_cfg_callback, NULL, 0, cb_debug,
N_("Set mailfromd debugging level. Argument is a semicolon-separated list "
"of debugging specifications. A simplified specification syntax is:\n"
" [!][.,...]\n"
"For details, please see the Mailfromd manual, section 3.18 \"Logging\n"
"and Debugging\", GNU Mailutils manual, section 3.3 \"Debugging\",\n"
"or visit ."),
N_("spec: list") },
{ "lock-retry-count", mu_cfg_callback, NULL, 0,
config_cb_lock_retry_count,
N_("Retry acquiring DBM file lock this number of times."),
N_("arg: number")},
{ "lock-retry-timeout", mu_cfg_callback, NULL, 0,
config_cb_lock_retry_timeout,
N_("Set the time span between the two DBM locking attempts."),
N_("time: interval") },
{ NULL }
};
static char *capa[] = {
"debug",
"locking",
NULL
};
struct mu_cli_setup cli = {
.optv = options,
.cfg = mfdbtool_cfg_param,
.prog_doc = prog_doc,
};
void
alloc_die_fun(void)
{
mu_error(_("not enough memory"));
abort();
}
int
main(int argc, char **argv)
{
int rc;
mf_init_nls();
mu_alloc_die_hook = alloc_die_fun;
db_format_setup();
database_cfg_init();
mf_getopt(&cli, &argc, &argv, capa, MF_GETOPT_DEFAULT);
if (db_type_str) {
mu_url_t dbhint;
if ((rc = mu_url_create_null(&dbhint)) ||
(rc = mu_url_set_scheme(dbhint, db_type_str))) {
mu_error(_("cannot initialize DBM hint: %s"),
mu_strerror(rc));
exit(EX_SOFTWARE);
}
mu_url_destroy(&mu_dbm_hint);
mu_dbm_hint = dbhint;
}
if (!run) {
mu_error(_("you must specify one of --list, --delete,"
" --expire or --compat"));
exit(EX_USAGE);
}
if (all_option &&
!(run == mfdbtool_compact || run == mfdbtool_expire)) {
mu_error(_("--all is meaningful only with "
"--expire or --compact option"));
exit(EX_USAGE);
}
if (all_option && format_option) {
mu_error(_("--format is incompatible with --all"));
exit(EX_USAGE);
}
if (!format_option)
format_option = db_format_lookup("cache");
if (!format_option) {
mu_error("INTERNAL ERROR: cache database not defined");
exit(EX_SOFTWARE);
}
if (expire_interval)
db_format_enumerate(db_proc_set_expire, NULL);
if (chdir(state_dir)) {
mu_error(_("cannot change to %s: %s"),
state_dir, strerror(errno));
exit(EX_OSERR);
}
run(argc, argv);
exit(0);
}