/*-
* 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