/*-
 * Copyright (c) 2000-2004 Andrey Simonenko
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipastat.c,v 1.2.2.5 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/param.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <regex.h>
#include <unistd.h>

#ifdef WITH_PTHREAD
# include <pthread.h>
#endif

#include "ipa_mod.h"

#include "dlapi.h"
#include "confcommon.h"
#include "pathnames.h"
#include "memfunc.h"

#include "ipastat_conf.h"
#include "ipastat_log.h"
#include "ipastat_main.h"
#include "ipastat_rules.h"
#include "ipastat_st.h"
#include "ipastat_time.h"

static const char *user_name = NULL;	/* -u <user> */
static const char *group_name = NULL;	/* -g <group> */

static const char *envprogname;

static void	exit_err(const char *, ...) ATTR_NORETURN ATTR_FORMAT(printf, 1, 2);
static void	exit_errx(const char *, ...) ATTR_NORETURN ATTR_FORMAT(printf, 1, 2);

/*
 * Output the program name, a message, an error message and exit.
 */
static void
exit_err(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsg(IPA_LOG_ERR, format, ap);
	va_end(ap);
	exit(1);
}

/*
 * Output the program name, a message and exit.
 */
static void
exit_errx(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsgx(IPA_LOG_ERR, format, ap);
	va_end(ap);
	exit(1);
}

/*
 * Output version number (-v and -h switches).
 */
static void
show_version(void)
{
	printf(IPASTAT_NAME ", version " PACKAGE_VERSION"\nRuntime settings: "
#ifdef SYM_PREFIX
	"symbol prefix \"" SYM_PREFIX "\", "
#else
	"symbol prefix is not used, "
#endif
#ifdef WITH_PTHREAD
	"thread-safe mode, "
#else
	"single-threaded mode, "
#endif
#ifdef USE_LIBLTDL
	"using libtool's ltdl library, "
#else
	"using dlopen interface, "
#endif
#ifdef WITH_MEMFUNC_DEBUG
	"memfunc debugging is enabled"
#else
	"memfunc debugging is disabled"
#endif
	"\nSupports: rules"
#ifdef WITH_LIMITS
	", limits"
#endif
#ifdef WITH_THRESHOLDS
	", thresholds"
#endif
	".\n");
}

/*
 * Set supplementary groups for process, gid is a GID from getpwnam(),
 * NGROUPS_MAX is defined in SUSv3.
 */
static int
setsuppgids(const char *user, gid_t gid)
{
	int	i, ngids = 1;
	gid_t	gids[NGROUPS_MAX + 1];
	char	**gr_user;
	const struct group *grp;

	gids[0] = gid;

	errno = 0;
	while ( (grp = getgrent()) != NULL) {
		for (i = 0; i < ngids; ++i)
			if (grp->gr_gid == gids[i])
				goto next;
		for (gr_user = grp->gr_mem; *gr_user != NULL; ++gr_user) {
			if (strcmp(user, *gr_user) == 0) {
				if (ngids < NGROUPS_MAX + 1) {
					gids[ngids++] = grp->gr_gid;
					break;
				}
			}
		}
next:
		;
	}

	if (errno != 0) {
		logmsg(IPA_LOG_ERR, "setsuppgids: getgrent");
		return -1;
	}

	if (setgroups(ngids, gids) < 0) {
		logmsg(IPA_LOG_ERR, "setsuppgids: setgroups");
		return -1;
	}

	return 0;
}

/*
 * Output help message (-h switch).
 */
static void
usage(void)
{
	show_version();
	printf("\
Usage: %s [options]\n\
 Statistics viewer\n\
 Options are:\n\
  -f <config-file>\tUse given <config-file> instead of using default\n\
\t\t\tconfiguration file "IPASTAT_CONF_FILE"\n\
  -t\t\t\tCheck the configuration file (two switches mimic\n\
\t\t\treal configuration regime) and exit\n\
  -c <directory>\tSet the directory "IPASTAT_NAME" should chroot(2) into\n\
\t\t\timmediately, working directory is not changed\n\
  -u <user>\t\tChange UID of the running copy of "IPASTAT_NAME" to <user>\n\
  -g <group>\t\tChange GID of the running copy of "IPASTAT_NAME" to <group>\n\
  -q <options>\t\tQuery statistics from modules\n\
  -h\t\t\tOutput this help message and exit\n\
  -v\t\t\tShow version number and exit\n",
	    envprogname);

	printf(" Options for the -q switch (details in the manual page):\n\
  -a rules\t\tOutput all rules\n"
#ifdef WITH_LIMITS
"  -a limits\t\tOutput all limits (should be use with -r option)\n"
#endif
#ifdef WITH_THRESHOLDS
"  -a thresholds\t\tOutput all thresholds (should be use with -r option)\n"
#endif
"  -s <stat>\t\tUse given statistics system\n\
  -x <regexp>\t\tFilter output with the given POSIX regular expression\n\
  -r <rule>\t\tSpecify rule name\n"
#ifdef WITH_LIMITS
"  -l <limit>\t\tSpecify limit name\n"
#endif
#ifdef WITH_THRESHOLDS
"  -t <threshold>\tSpecify threshold name\n"
#endif
"  -i|I <interval>\tSpecify time interval\n"
	    );
}

/*
 * Implement -u <user> and -g <group> options.
 */
static void
change_user(void)
{
	char	*endptr;
	uid_t	uid;
	gid_t	gid;
	const struct passwd *pwd;
	const struct group *grp;

	if (group_name != NULL) {
		/* -g <group> */
		errno = 0;
		if ( (grp = getgrnam(group_name)) == NULL) {
			if (errno != 0)
				exit_err("getgrnam(%s)", group_name);
			if (isdigit(*group_name) == 0)
				exit_errx("cannot find group %s", group_name);
			errno = 0;
			gid = (gid_t)strtoul(group_name, &endptr, 10);
			if (errno != 0)
				exit_err("change_user: strtoul");
			if (group_name == endptr || *endptr != '\0')
				exit_errx("cannot find group %s", group_name);
		} else
			gid = grp->gr_gid;
		if (setgroups(1, &gid) < 0)
			exit_err("change_user: setgroups(1, [%lu])", (u_long)gid);
	}

	if (user_name != NULL) {
		/* -u <user> */
		errno = 0;
		if ( (pwd = getpwnam(user_name)) == NULL) {
			if (errno != 0)
				exit_err("getpwnam(%s)", user_name);
			if (isdigit(*user_name) == 0)
				exit_errx("cannot find user %s", user_name);
			errno = 0;
			uid = (uid_t)strtoul(user_name, &endptr, 10);
			if (errno != 0)
				exit_err("change_user: strtoul");
			if (user_name == endptr || *endptr != '\0')
				exit_errx("cannot find user %s", user_name);
			if (group_name == NULL) {
				errno = 0;
				if ( (pwd = getpwuid(uid)) == NULL) {
					if (errno != 0)
						exit_err("getpwuid(%lu)",
						    (u_long)uid);
					else
						exit_errx("cannot find user with UID %lu and -g option was not given",
						    (u_long)uid);
				}
			}
		} else
			uid = pwd->pw_uid;
		if (pwd != NULL && group_name == NULL) {
			gid = pwd->pw_gid;
			if (setsuppgids(pwd->pw_name, gid) < 0)
				exit_err("change_user: cannot set all groups for user %s", pwd->pw_name);
		}
	}

	if (setgid(gid) < 0)
		exit_err("change_user: setgid(%lu)", (u_long)gid);

	if (user_name != NULL)
		if (setuid(uid) < 0)
			exit_err("change_user: setuid(%lu)", (u_long)uid);

	/* To be sure, that passwd and group files are closed. */
	endpwent();
	endgrent();
}

#ifdef WITH_LIMITS
# define Q_OPTSTRING_LIMIT "l:"
#else
# define Q_OPTSTRING_LIMIT ""
#endif

#ifdef WITH_THRESHOLDS
# define Q_OPTSTRING_THRESHOLD "t:"
#else
# define Q_OPTSTRING_THRESHOLD ""
#endif

#define Q_OPTSTRING ":a:i:I:r:s:" Q_OPTSTRING_LIMIT Q_OPTSTRING_THRESHOLD "x:"

static void
parse_query(int argc, char *argv[])
{
	int	opt;
	size_t	len;
#ifdef WITH_ANY_LIMITS
	u_int	type;
	const struct opt_rule *opt_rule;
#endif

	while ( (opt = getopt(argc, argv, Q_OPTSTRING)) != -1)
		switch (opt) {
		case 'a':
			if (a_flag != A_FLAG_ABSENT)
				exit_errx("query: duplicated the -a option");
			len = strlen(optarg);
			if (strncmp(optarg, "rules", len) == 0)
				a_flag = A_FLAG_RULES;
			else
#ifdef WITH_LIMITS
			if (strncmp(optarg, "limits", len) == 0)
				a_flag = A_FLAG_LIMITS;
			else
#endif
#ifdef WITH_THRESHOLDS
			if (strncmp(optarg, "thresholds", len) == 0)
				a_flag = A_FLAG_THRESHOLDS;
			else
#endif
				exit_errx("query: unknown option value \"%s\" for -a", optarg);
			break;
		case 'i':
			if (add_opt_tint(optarg, 0) < 0)
				exit_errx("query: cannot add time interval");
			break;
		case 'I':
			if (add_opt_tint(optarg, 1) < 0)
				exit_errx("query: cannot add time interval");
			break;
#ifdef WITH_LIMITS
		case 'l':
			if (cur_opt_rule == NULL)
				exit_errx("query: the -l option can be used only after the -r option");
			if (add_opt_limit(optarg) < 0)
				exit_errx("query: cannot add limit");
			break;
#endif
		case 'r':
			if (add_opt_rule(optarg) < 0)
				exit_errx("query: cannot add rule");
			break;
		case 's':
			if (add_opt_st(optarg) < 0)
				exit_errx("query: cannot add statistics systems names");
			break;
#ifdef WITH_THRESHOLDS
		case 't':
			if (cur_opt_rule == NULL)
				exit_errx("query: the -t option can be used only after the -r option");
			if (add_opt_threshold(optarg) < 0)
				exit_errx("query: cannot add threshold");
			break;
#endif
		case 'x':
			x_pat = optarg;
			x_reg_ptr = &x_reg;
			break;
		case ':':
			exit_errx("query: option requires an argument -- %c", optopt);
			/* NOTREACHED */
		case '?':
			exit_errx("query: illegal option -- %c", optopt);
			/* NOTREACHED */
		default:
			exit_errx("query: unexpected option -- %c", optopt);
		}

	if (optind < argc)
		exit_errx("query: non-switch argument \"%s\"", argv[optind]);

	switch (a_flag) {
	case A_FLAG_ABSENT:
		break;
	case A_FLAG_RULES:
		if (!STAILQ_EMPTY(&opt_rules_list) ||
		    !STAILQ_EMPTY(&opt_tint_list))
			exit_errx("query: \"-a rules\" should be used alone");
		break;
#ifdef WITH_LIMITS
	case A_FLAG_LIMITS:
		if (STAILQ_EMPTY(&opt_rules_list))
			exit_errx("query: \"-a limits\" requires -r option(s)");
		if (has_opt_limits || has_opt_thresholds)
			exit_errx("query: \"-a limits\" requires only -r option(s)");
		break;
#endif
#ifdef WITH_THRESHOLDS
	case A_FLAG_THRESHOLDS:
		if (STAILQ_EMPTY(&opt_rules_list))
			exit_errx("query: \"-a thresholds\" requires -r option(s)");
		if (has_opt_limits || has_opt_thresholds)
			exit_errx("query: \"-a thresholds\" requires only -r option(s)");
		break;
#endif
	}

#if defined(WITH_ANY_LIMITS) && defined(WITH_THRESHOLDS)
	if (has_opt_limits && has_opt_thresholds)
		exit_errx("query: do not mix -l and -t options");
#endif

#ifdef WITH_ANY_LIMITS
	if (!STAILQ_EMPTY(&opt_rules_list)) {
		type = STAILQ_FIRST(&opt_rules_list)->type;
		STAILQ_FOREACH(opt_rule, &opt_rules_list, link)
			if (opt_rule->type != type)
				exit_errx("query: incorrect combination of -l or -t options with -r options");

# ifdef WITH_THRESHOLDS
		if (type == OPT_RULE_THRESHOLD && !STAILQ_EMPTY(&opt_tint_list))
			exit_errx("query: do not use -i options with -t options");
# endif
	}
#endif /* WITH_ANY_LIMITS */

	if (x_pat != NULL)
		if ( (re_errcode = regcomp(&x_reg, x_pat, REG_EXTENDED|REG_NOSUB)) != 0) {
			re_form_errbuf();
			exit_errx("query: regcomp(%s): %s", x_pat, re_errbuf);
		}
}

int
main(int argc, char *argv[])
{
	int	opt;			/* Current option. */
	int	ret;			/* Return code. */
	int	q_flag = 0;		/* Non-zero if -q. */
	int	t_flag = 0;		/* Non-zero if -t. */

	/* Save the program name. */
	if ( (envprogname = strrchr(argv[0], '/')) != NULL)
		++envprogname;
	else
		envprogname = argv[0];

	log_ident = envprogname;

	mypid = (long)getpid();

	mvlogmsgx = mvlogmsgx_wrapper;

	memfunc_pre_init();

	if (memfunc_init() < 0)
		exit_errx("main: memfunc_init failed");

	if ( (m_anon = mem_type_new_local(MTYPE_NAME(anonymous), "Anonymous memory", 0)) == NULL)
		exit_errx("main: mem_type_new_local failed");

	if (init_time_data() < 0)
		exit_errx("main: init_time_data failed");

	opterr = 0;
	while ( (opt = getopt(argc, argv, ":c:f:g:hi:qtu:v")) != -1)
		switch (opt) {
		case 'c':
			if (*optarg != '/')
				exit_errx("path in the -c option should be absolute");
			if (chroot(optarg) < 0)
				exit_err("main: chroot(%s)", optarg);
			break;
		case 'f':
			ipastat_conf_file = optarg;
			break;
		case 'g':
			group_name = optarg;
			break;
		case 'h':
			usage();
			return 0;
		case 'i':
			log_ident = optarg;
			break;
		case 'q':
			q_flag = 1;
			goto getopt_done;
		case 't':
			if (t_flag)
				mimic_real_config = 1;
			else
				t_flag = 1;
			break;
		case 'u':
			user_name = optarg;
			break;
		case 'v':
			show_version();
			return 0;
		case ':':
			exit_errx("option requires an argument -- %c", optopt);
			/* NOTREACHED */
		case '?':
			exit_errx("illegal option -- %c", optopt);
			/* NOTREACHED */
		default:
			exit_errx("unexpected option -- %c", optopt);
		}

	if (optind < argc)
		exit_errx("non-switch argument \"%s\"", argv[optind]);

getopt_done:

	if (user_name != NULL || group_name != NULL)
		change_user();

#ifdef HAVE_DL_INIT
	if (dl_init() != 0)
		exit_errx("main: dl_init failed");
#endif

	setlocale(LC_ALL, "");

	if (t_flag) {
		if (configure(TEST_PARSING) < 0)
			return 1;
		show_config();
		return 0;
	}

	if (!q_flag)
		exit_errx("statistics query is missed, use the -q option");

	parse_query(argc, argv);

	ret = 1;

	if (configure(CONFIG_PARSING) < 0) {
		logmsgx(IPA_LOG_ERR, "main: configure failed");
		goto done;
	}

	if (parse_opt_st_list() < 0) {
		logmsgx(IPA_LOG_ERR, "main: cannot parse statistics names given in command line");
		goto done;
	}

	if (parse_opt_rules() < 0) {
		logmsgx(IPA_LOG_ERR, "main: cannot completely parse command line");
		goto done;
	}

	if (ipastat_main() < 0)
		logmsgx(IPA_LOG_ERR, "main: cannot perform query");
	else
		ret = 0;

	if (deinit_all() < 0) {
		logmsgx(IPA_LOG_ERR, "main: deinit_all failed");
		ret = 1;
	}

	free_opt_tint_list();
	free_opt_st_list();
	free_opt_rules();

	if (free_all() < 0) {
		logmsgx(IPA_LOG_ERR, "main: free_all failed");
		ret = 1;
	}

done:

#ifdef HAVE_DL_EXIT
	if (dl_exit() != 0) {
		logmsgx(IPA_LOG_ERR, "main: dl_exit failed");
		ret = 1;
	}
#endif

	if (x_pat != NULL)
		regfree(&x_reg);

	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1