/* This file is part of Mailfromd. Copyright (C) 2005-2018 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 */ int predict_next_option; double predict_rate; /* Prediction rate for --list --format=rates*/ 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 debug verbosity level. Argument is a comma-" "separated list of debug specifications, each of which has the " "following form:\n" " [=]\n" "where is the name of a Mailfromd module, and " "is the desired verbosity level for that module."), 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); }