/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* logadm/opts.c -- options handling routines
*/
#include <stdio.h>
#include <libintl.h>
#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "err.h"
#include "lut.h"
#include "fn.h"
#include "opts.h"
/* forward declarations for private functions */
static struct optinfo *opt_info(int c);
static void opts_setcmdarg(struct opts *opts, const char *cmdarg);
/* info created by opts_parse(), private to this module */
struct opts {
struct lut *op_raw; /* the raw text for the options */
struct lut *op_ints; /* the int values for the options */
struct fn_list *op_cmdargs; /* the op_cmdargs */
};
static off_t opts_parse_ctime(const char *o, const char *optarg);
static off_t opts_parse_bytes(const char *o, const char *optarg);
static off_t opts_parse_atopi(const char *o, const char *optarg);
static off_t opts_parse_seconds(const char *o, const char *optarg);
static struct lut *Info; /* table driving parsing */
/* table that drives argument parsing */
struct optinfo Opttable[] = {
{ "e", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "F", OPTTYPE_STRING, NULL, OPTF_CLI },
{ "f", OPTTYPE_STRING, NULL, OPTF_CLI },
{ "h", OPTTYPE_BOOLEAN, NULL, OPTF_CLI },
{ "l", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF },
{ "N", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF },
{ "n", OPTTYPE_BOOLEAN, NULL, OPTF_CLI },
{ "r", OPTTYPE_BOOLEAN, NULL, OPTF_CLI },
{ "V", OPTTYPE_BOOLEAN, NULL, OPTF_CLI },
{ "v", OPTTYPE_BOOLEAN, NULL, OPTF_CLI },
{ "w", OPTTYPE_STRING, NULL, OPTF_CLI },
{ "p", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF },
{ "P", OPTTYPE_INT, opts_parse_ctime, OPTF_CLI|OPTF_CONF },
{ "s", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF },
{ "a", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "b", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "c", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF },
{ "g", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "m", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF },
{ "M", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "o", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "R", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "t", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "z", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF },
{ "A", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF },
{ "C", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF },
{ "E", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
{ "S", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF },
{ "T", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF },
};
int Opttable_cnt = sizeof (Opttable) / sizeof (struct optinfo);
/*
* opts_init -- set current options parsing table
*/
void
opts_init(struct optinfo *table, int numentries)
{
while (numentries-- > 0) {
Info = lut_add(Info, table->oi_o, table);
table++;
}
}
/*
* opt_info -- fetch the optinfo struct for the given option
*/
static struct optinfo *
opt_info(int c)
{
char lhs[2];
lhs[0] = c;
lhs[1] = '\0';
return ((struct optinfo *)lut_lookup(Info, lhs));
}
/*
* opts_parse -- parse an argv-style list of options
*
* prints a message to stderr and calls err(EF_FILE|EF_JMP, ...) on error
*/
struct opts *
opts_parse(struct opts *opts, char **argv, int flags)
{
int dashdash = 0;
char *ptr;
if (opts == NULL) {
opts = MALLOC(sizeof (*opts));
opts->op_raw = opts->op_ints = NULL;
opts->op_cmdargs = fn_list_new(NULL);
}
/* no words to process, just return empty opts struct */
if (argv == NULL)
return (opts);
/* foreach word... */
for (; (ptr = *argv) != NULL; argv++) {
if (dashdash || *ptr != '-') {
/* found a cmdarg */
opts_setcmdarg(opts, ptr);
continue;
}
if (*++ptr == '\0')
err(EF_FILE|EF_JMP, "Illegal option: dash by itself");
if (*ptr == '-') {
/* (here's where support for --longname would go) */
if (*(ptr + 1) != '\0')
err(EF_FILE|EF_JMP, "Illegal option: -%s", ptr);
dashdash++;
continue;
}
for (; *ptr; ptr++) {
struct optinfo *info = opt_info(*ptr);
/* see if option was in our parsing table */
if (info == NULL)
err(EF_FILE|EF_JMP, "Illegal option: %c", *ptr);
/* see if context allows this option */
if ((flags & OPTF_CLI) &&
(info->oi_flags & OPTF_CLI) == 0)
err(EF_FILE|EF_JMP,
"Option '%c' not allowed on "
"command line", *ptr);
if ((flags & OPTF_CONF) &&
(info->oi_flags & OPTF_CONF) == 0)
err(EF_FILE|EF_JMP,
"Option '%c' not allowed in "
"configuration file", *ptr);
/* for boolean options, we have all the info we need */
if (info->oi_t == OPTTYPE_BOOLEAN) {
(void) opts_set(opts, info->oi_o, "");
continue;
}
/* option expects argument */
if (*++ptr == '\0' &&
((ptr = *++argv) == NULL || *ptr == '-'))
err(EF_FILE|EF_JMP,
"Option '%c' requires an argument",
info->oi_o[0]);
opts_set(opts, info->oi_o, ptr);
break;
}
}
return (opts);
}
/*
* opts_free -- free a struct opts previously allocated by opts_parse()
*/
void
opts_free(struct opts *opts)
{
if (opts) {
lut_free(opts->op_raw, NULL);
lut_free(opts->op_ints, NULL);
fn_list_free(opts->op_cmdargs);
FREE(opts);
}
}
/*
* opts_set -- set an option
*/
void
opts_set(struct opts *opts, const char *o, const char *optarg)
{
off_t *rval;
struct optinfo *info = opt_info(*o);
rval = MALLOC(sizeof (off_t));
opts->op_raw = lut_add(opts->op_raw, o, (void *)optarg);
if (info->oi_parser) {
*rval = (*info->oi_parser)(o, optarg);
opts->op_ints = lut_add(opts->op_ints, o, (void *)rval);
}
}
/*
* opts_setcmdarg -- add a cmdarg to the list of op_cmdargs
*/
static void
opts_setcmdarg(struct opts *opts, const char *cmdarg)
{
fn_list_adds(opts->op_cmdargs, cmdarg);
}
/*
* opts_count -- return count of the options in *options that are set
*/
int
opts_count(struct opts *opts, const char *options)
{
int count = 0;
for (; *options; options++) {
char lhs[2];
lhs[0] = *options;
lhs[1] = '\0';
if (lut_lookup(opts->op_raw, lhs))
count++;
}
return (count);
}
/*
* opts_optarg -- return the optarg for the given option, NULL if not set
*/
const char *
opts_optarg(struct opts *opts, const char *o)
{
return ((char *)lut_lookup(opts->op_raw, o));
}
/*
* opts_optarg_int -- return the int value for the given option
*/
off_t
opts_optarg_int(struct opts *opts, const char *o)
{
off_t *ret;
ret = (off_t *)lut_lookup(opts->op_ints, o);
if (ret != NULL)
return (*ret);
return (0);
}
/*
* opts_cmdargs -- return list of op_cmdargs
*/
struct fn_list *
opts_cmdargs(struct opts *opts)
{
return (opts->op_cmdargs);
}
static void
merger(const char *lhs, void *rhs, void *arg)
{
struct lut **destlutp = (struct lut **)arg;
*destlutp = lut_add(*destlutp, lhs, rhs);
}
/*
* opts_merge -- merge two option lists together
*/
struct opts *
opts_merge(struct opts *back, struct opts *front)
{
struct opts *ret = MALLOC(sizeof (struct opts));
ret->op_raw = lut_dup(back->op_raw);
lut_walk(front->op_raw, merger, &(ret->op_raw));
ret->op_ints = lut_dup(back->op_ints);
lut_walk(front->op_ints, merger, &(ret->op_ints));
ret->op_cmdargs = fn_list_dup(back->op_cmdargs);
return (ret);
}
/*
* opts_parse_ctime -- parse a ctime format optarg
*/
static off_t
opts_parse_ctime(const char *o, const char *optarg)
{
struct tm tm;
off_t ret;
if (strptime(optarg, "%a %b %e %T %Z %Y", &tm) == NULL &&
strptime(optarg, "%c", &tm) == NULL)
err(EF_FILE|EF_JMP,
"Option '%c' requires ctime-style time", *o);
errno = 0;
if ((ret = (off_t)mktime(&tm)) == -1 && errno)
err(EF_FILE|EF_SYS|EF_JMP, "Option '%c' Illegal time", *o);
return (ret);
}
/*
* opts_parse_atopi -- parse a positive integer format optarg
*/
static off_t
opts_parse_atopi(const char *o, const char *optarg)
{
off_t ret = atoll(optarg);
while (isdigit(*optarg))
optarg++;
if (*optarg)
err(EF_FILE|EF_JMP,
"Option '%c' requires non-negative number", *o);
return (ret);
}
/*
* opts_parse_atopi -- parse a size format optarg into bytes
*/
static off_t
opts_parse_bytes(const char *o, const char *optarg)
{
off_t ret = atoll(optarg);
while (isdigit(*optarg))
optarg++;
switch (*optarg) {
case 'g':
case 'G':
ret *= 1024;
/*FALLTHROUGH*/
case 'm':
case 'M':
ret *= 1024;
/*FALLTHROUGH*/
case 'k':
case 'K':
ret *= 1024;
/*FALLTHROUGH*/
case 'b':
case 'B':
if (optarg[1] == '\0')
return (ret);
}
err(EF_FILE|EF_JMP,
"Option '%c' requires number with suffix from [bkmg]", *o);
/*NOTREACHED*/
return (0);
}
/*
* opts_parse_seconds -- parse a time format optarg into seconds
*/
static off_t
opts_parse_seconds(const char *o, const char *optarg)
{
off_t ret;
if (strcasecmp(optarg, "now") == 0)
return (OPTP_NOW);
if (strcasecmp(optarg, "never") == 0)
return (OPTP_NEVER);
ret = atoll(optarg);
while (isdigit(*optarg))
optarg++;
if (optarg[1] == '\0')
switch (*optarg) {
case 'h':
case 'H':
ret *= 60 * 60;
return (ret);
case 'd':
case 'D':
ret *= 60 * 60 * 24;
return (ret);
case 'w':
case 'W':
ret *= 60 * 60 * 24 * 7;
return (ret);
case 'm':
case 'M':
ret *= 60 * 60 * 24 * 30;
return (ret);
case 'y':
case 'Y':
ret *= 60 * 60 * 24 * 365;
return (ret);
}
err(EF_FILE|EF_JMP,
"Option '%c' requires number with suffix from [hdwmy]", *o);
/*NOTREACHED*/
return (0);
}
/* info passed between opts_print() and printer() */
struct printerinfo {
FILE *stream;
int isswitch;
char *exclude;
};
/* helper function for opts_print() */
static void
printer(const char *lhs, void *rhs, void *arg)
{
struct printerinfo *pip = (struct printerinfo *)arg;
char *s = (char *)rhs;
if (pip->isswitch) {
char *ep = pip->exclude;
while (ep && *ep)
if (*ep++ == *lhs)
return;
}
(void) fprintf(pip->stream, " %s%s", (pip->isswitch) ? "-" : "", lhs);
if (s && *s) {
(void) fprintf(pip->stream, " ");
opts_printword(s, pip->stream);
}
}
/*
* opts_printword -- print a word, quoting as necessary
*/
void
opts_printword(const char *word, FILE *stream)
{
char *q = "";
if (word != NULL) {
if (strchr(word, ' ') || strchr(word, '\t') ||
strchr(word, '$') || strchr(word, '[') ||
strchr(word, '?') || strchr(word, '{') ||
strchr(word, '`') || strchr(word, ';')) {
if (strchr(word, '\'') == NULL)
q = "'";
else if (strchr(word, '"') == NULL)
q = "\"";
else
err(EF_FILE|EF_JMP,
"Can't protect quotes in <%s>", word);
(void) fprintf(stream, "%s%s%s", q, word, q);
} else
(void) fprintf(stream, "%s", word);
}
}
/*
* opts_print -- print options to stream, leaving out those in "exclude"
*/
void
opts_print(struct opts *opts, FILE *stream, char *exclude)
{
struct printerinfo pi;
struct fn *fnp;
pi.stream = stream;
pi.isswitch = 1;
pi.exclude = exclude;
lut_walk(opts->op_raw, printer, &pi);
fn_list_rewind(opts->op_cmdargs);
while ((fnp = fn_list_next(opts->op_cmdargs)) != NULL) {
(void) fprintf(stream, " ");
opts_printword(fn_s(fnp), stream);
}
}
#ifdef TESTMODULE
/* table that drives argument parsing */
static struct optinfo Testopttable[] = {
{ "a", OPTTYPE_BOOLEAN, NULL, OPTF_CLI },
{ "b", OPTTYPE_STRING, NULL, OPTF_CLI },
{ "c", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF },
{ "d", OPTTYPE_INT, opts_parse_ctime, OPTF_CLI|OPTF_CONF },
{ "e", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF },
{ "f", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF },
};
/*
* test main for opts module, usage: a.out options...
*/
int
main(int argc, char *argv[])
{
struct opts *opts;
err_init(argv[0]);
setbuf(stdout, NULL);
opts_init(Testopttable,
sizeof (Testopttable) / sizeof (struct optinfo));
argv++;
if (SETJMP)
err(0, "opts parsing failed");
else
opts = opts_parse(NULL, argv, OPTF_CLI);
printf("options:");
opts_print(opts, stdout, NULL);
printf("\n");
err_done(0);
/* NOTREACHED */
return (0);
}
#endif /* TESTMODULE */