view src/doveadm/doveadm.c @ 19609:e7e593e68fce

doveadm: Changed most print formatters to write to ostream. This allows all the formatters to be used when sending replies to the upcoming doveadm HTTP server responses. The "table" formatter wasn't modified, because it writes to both stdout and stderr.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Fri, 22 Jan 2016 19:06:59 +0200
parents d9189bd01219
children cecbfa036d9f
line wrap: on
line source

/* Copyright (c) 2009-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "ostream.h"
#include "env-util.h"
#include "execv-const.h"
#include "dict.h"
#include "master-service-private.h"
#include "master-service-settings.h"
#include "settings-parser.h"
#include "doveadm-print-private.h"
#include "doveadm-dump.h"
#include "doveadm-mail.h"
#include "doveadm-settings.h"
#include "doveadm-dsync.h"
#include "doveadm.h"

#include <unistd.h>

const struct doveadm_print_vfuncs *doveadm_print_vfuncs_all[] = {
	&doveadm_print_flow_vfuncs,
	&doveadm_print_tab_vfuncs,
	&doveadm_print_table_vfuncs,
	&doveadm_print_pager_vfuncs,
	NULL
};

int doveadm_exit_code = 0;

static void failure_exit_callback(int *status)
{
	enum fatal_exit_status fatal_status = *status;

	switch (fatal_status) {
	case FATAL_LOGWRITE:
	case FATAL_LOGERROR:
	case FATAL_LOGOPEN:
	case FATAL_OUTOFMEM:
	case FATAL_EXEC:
	case FATAL_DEFAULT:
		*status = EX_TEMPFAIL;
		break;
	}
}

static void
doveadm_usage_compress_lines(FILE *out, const char *str, const char *prefix)
{
	const char *cmd, *args, *p, *short_name, *sub_name;
	const char *prev_name = "", *prev_sub_name = "";
	const char **lines;
	unsigned int i, count, prefix_len = strlen(prefix);

	/* split lines */
	lines = (void *)p_strsplit(pool_datastack_create(), str, "\n");
	for (count = 0; lines[count] != NULL; count++) ;

	/* sort lines */
	i_qsort(lines, count, sizeof(*lines), i_strcmp_p);

	/* print lines, compress subcommands into a single line */
	for (i = 0; i < count; i++) {
		args = strchr(lines[i], '\t');
		if (args == NULL) {
			cmd = lines[i];
			args = "";
		} else {
			cmd = t_strdup_until(lines[i], args);
			args++;
		}
		if (*prefix != '\0') {
			if (strncmp(cmd, prefix, prefix_len) != 0 ||
			    cmd[prefix_len] != ' ')
				continue;
			cmd += prefix_len + 1;
		}

		p = strchr(cmd, ' ');
		if (p == NULL) {
			if (*prev_name != '\0') {
				fprintf(out, "\n");
				prev_name = "";
			}
			fprintf(out, USAGE_CMDNAME_FMT" %s\n", cmd, args);
		} else {
			short_name = t_strdup_until(cmd, p);
			if (strcmp(prev_name, short_name) != 0) {
				if (*prev_name != '\0')
					fprintf(out, "\n");
				fprintf(out, USAGE_CMDNAME_FMT" %s",
					short_name, t_strcut(p + 1, ' '));
				prev_name = short_name;
				prev_sub_name = "";
			} else {
				sub_name = t_strcut(p + 1, ' ');
				if (strcmp(prev_sub_name, sub_name) != 0) {
					fprintf(out, "|%s", sub_name);
					prev_sub_name = sub_name;
				}
			}
		}
	}
	if (*prev_name != '\0')
		fprintf(out, "\n");
}

static void ATTR_NORETURN
usage_to(FILE *out, const char *prefix)
{
	const struct doveadm_cmd *cmd;
	string_t *str = t_str_new(1024);

	fprintf(out, "usage: doveadm [-Dv] [-f <formatter>] ");
	if (*prefix != '\0')
		fprintf(out, "%s ", prefix);
	fprintf(out, "<command> [<args>]\n");

	array_foreach(&doveadm_cmds, cmd)
		str_printfa(str, "%s\t%s\n", cmd->name, cmd->short_usage);

	doveadm_mail_usage(str);
	doveadm_usage_compress_lines(out, str_c(str), prefix);

	exit(EX_USAGE);
}

void usage(void)
{
	usage_to(stderr, "");
}

static void ATTR_NORETURN
help_to(const struct doveadm_cmd *cmd, FILE *out)
{
	fprintf(out, "doveadm %s %s\n", cmd->name, cmd->short_usage);
	exit(EX_USAGE);
}

void help(const struct doveadm_cmd *cmd)
{
	help_to(cmd, stdout);
}

static void cmd_help(int argc ATTR_UNUSED, char *argv[])
{
	const char *man_argv[3];

	if (argv[1] == NULL)
		usage_to(stdout, "");

	env_put("MANPATH="MANDIR);
	man_argv[0] = "man";
	man_argv[1] = t_strconcat("doveadm-", argv[1], NULL);
	man_argv[2] = NULL;
	execvp_const(man_argv[0], man_argv);
}

static struct doveadm_cmd doveadm_cmd_help = {
	cmd_help, "help", "<cmd>"
};

static void cmd_config(int argc ATTR_UNUSED, char *argv[])
{
	env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
		master_service_get_config_path(master_service), NULL));
	argv[0] = BINDIR"/doveconf";
	(void)execv(argv[0], argv);
	i_fatal("execv(%s) failed: %m", argv[0]);
}

static struct doveadm_cmd doveadm_cmd_config = {
	cmd_config, "config", "[doveconf parameters]"
};

static void cmd_exec(int argc ATTR_UNUSED, char *argv[]);
static struct doveadm_cmd doveadm_cmd_exec = {
	cmd_exec, "exec", "<binary> [binary parameters]"
};

static void cmd_exec(int argc ATTR_UNUSED, char *argv[])
{
	const char *path, *binary = argv[1];

	if (binary == NULL)
		help(&doveadm_cmd_exec);

	path = t_strdup_printf("%s/%s", doveadm_settings->libexec_dir, binary);
	argv++;
	argv[0] = t_strdup_noconst(path);
	(void)execv(argv[0], argv);
	i_fatal("execv(%s) failed: %m", argv[0]);
}

static bool doveadm_try_run(const char *cmd_name, int argc, char *argv[])
{
	const struct doveadm_cmd *cmd;

	cmd = doveadm_cmd_find(cmd_name, &argc, &argv);
	if (cmd == NULL)
		return FALSE;
	cmd->cmd(argc, argv);
	return TRUE;
}

static bool doveadm_has_subcommands(const char *cmd_name)
{
	const struct doveadm_cmd *cmd;
	unsigned int len = strlen(cmd_name);

	array_foreach(&doveadm_cmds, cmd) {
		if (strncmp(cmd->name, cmd_name, len) == 0 &&
		    cmd->name[len] == ' ')
			return TRUE;
	}
	return doveadm_mail_has_subcommands(cmd_name);
}

static void doveadm_read_settings(void)
{
	static const struct setting_parser_info *set_roots[] = {
		&doveadm_setting_parser_info,
		NULL
	};
	struct master_service_settings_input input;
	struct master_service_settings_output output;
	const struct doveadm_settings *set;
	const char *error;

	memset(&input, 0, sizeof(input));
	input.roots = set_roots;
	input.module = "doveadm";
	input.preserve_user = TRUE;
	input.preserve_home = TRUE;
	if (master_service_settings_read(master_service, &input,
					 &output, &error) < 0)
		i_fatal("Error reading configuration: %s", error);

	service_set = master_service_settings_get(master_service);
	service_set = settings_dup(&master_service_setting_parser_info,
				   service_set, pool_datastack_create());

	set = master_service_settings_get_others(master_service)[0];
	doveadm_settings = settings_dup(&doveadm_setting_parser_info, set,
					pool_datastack_create());
}

static struct doveadm_cmd *doveadm_cmdline_commands[] = {
	&doveadm_cmd_help,
	&doveadm_cmd_config,
	&doveadm_cmd_exec,
	&doveadm_cmd_dump,
	&doveadm_cmd_pw,
	&doveadm_cmd_stats_top,
	&doveadm_cmd_stats_reset,
	&doveadm_cmd_zlibconnect
};

int main(int argc, char *argv[])
{
	enum master_service_flags service_flags =
		MASTER_SERVICE_FLAG_STANDALONE |
		MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
	const char *cmd_name;
	unsigned int i;
	bool quick_init = FALSE;
	int c;

	i_set_failure_exit_callback(failure_exit_callback);
	doveadm_dsync_main(&argc, &argv);

	/* "+" is GNU extension to stop at the first non-option.
	   others just accept -+ option. */
	master_service = master_service_init("doveadm", service_flags,
					     &argc, &argv, "+Df:hv");
	while ((c = master_getopt(master_service)) > 0) {
		switch (c) {
		case 'D':
			doveadm_debug = TRUE;
			doveadm_verbose = TRUE;
			break;
		case 'f':
			doveadm_print_init(optarg);
			break;
		case 'h':
			doveadm_print_hide_titles = TRUE;
			break;
		case 'v':
			doveadm_verbose = TRUE;
			break;
		default:
			return FATAL_DEFAULT;
		}
	}
	cmd_name = argv[optind];

	if (cmd_name != NULL && strcmp(cmd_name, "help") == 0 &&
	    argv[optind+1] != NULL) {
		/* "help cmd" doesn't need any configuration */
		quick_init = TRUE;
	} else {
		doveadm_read_settings();
	}
	master_service_init_log(master_service, "doveadm: ");

	doveadm_cmds_init();
	for (i = 0; i < N_ELEMENTS(doveadm_cmdline_commands); i++)
		doveadm_register_cmd(doveadm_cmdline_commands[i]);
	if (cmd_name != NULL && (quick_init ||
				 strcmp(cmd_name, "config") == 0 ||
				 strcmp(cmd_name, "stop") == 0 ||
				 strcmp(cmd_name, "reload") == 0)) {
		/* special case commands: even if there is something wrong
		   with the config (e.g. mail_plugins), don't fail these
		   commands */
		quick_init = TRUE;
	} else {
		quick_init = FALSE;
		doveadm_print_ostream = o_stream_create_fd(STDOUT_FILENO, 0, FALSE);
		o_stream_set_no_error_handling(doveadm_print_ostream, TRUE);
		doveadm_dump_init();
		doveadm_mail_init();
		dict_drivers_register_builtin();
		doveadm_load_modules();

		if (cmd_name == NULL) {
			/* show usage after registering all plugins */
			usage_to(stdout, "");
		}
	}

	argc -= optind;
	argv += optind;
	i_getopt_reset();

	master_service_init_finish(master_service);
	if (!doveadm_debug) {
		/* disable debugging unless -D is given */
		i_set_debug_file("/dev/null");
	}

	if (!doveadm_try_run(cmd_name, argc, argv) &&
	    !doveadm_mail_try_run(cmd_name, argc, argv)) {
		if (doveadm_has_subcommands(cmd_name))
			usage_to(stdout, cmd_name);
		if (doveadm_has_unloaded_plugin(cmd_name)) {
			i_fatal("Unknown command '%s', but plugin %s exists. "
				"Try to set mail_plugins=%s",
				cmd_name, cmd_name, cmd_name);
		}
		usage();
	}

	if (!quick_init) {
		doveadm_mail_deinit();
		doveadm_dump_deinit();
		doveadm_unload_modules();
		dict_drivers_unregister_builtin();
		doveadm_print_deinit();
		o_stream_unref(&doveadm_print_ostream);
	}
	doveadm_cmds_deinit();
	master_service_deinit(&master_service);
	return doveadm_exit_code;
}