1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26/*
27 * logadm/opts.c -- options handling routines
28 */
29
30#include <stdio.h>
31#include <libintl.h>
32#include <stdlib.h>
33#include <ctype.h>
34#include <strings.h>
35#include <time.h>
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <errno.h>
39#include "err.h"
40#include "lut.h"
41#include "fn.h"
42#include "opts.h"
43
44/* forward declarations for private functions */
45static struct optinfo *opt_info(int c);
46static void opts_setcmdarg(struct opts *opts, const char *cmdarg);
47
48/* info created by opts_parse(), private to this module */
49struct opts {
50	struct lut *op_raw;		/* the raw text for the options */
51	struct lut *op_ints;		/* the int values for the options */
52	struct fn_list *op_cmdargs;	/* the op_cmdargs */
53};
54
55static off_t opts_parse_ctime(const char *o, const char *optarg);
56static off_t opts_parse_bytes(const char *o, const char *optarg);
57static off_t opts_parse_atopi(const char *o, const char *optarg);
58static off_t opts_parse_seconds(const char *o, const char *optarg);
59
60static struct lut *Info;		/* table driving parsing */
61
62/* table that drives argument parsing */
63struct optinfo Opttable[] = {
64	{ "e", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
65	{ "F", OPTTYPE_STRING,	NULL,			OPTF_CLI },
66	{ "f", OPTTYPE_STRING,	NULL,			OPTF_CLI },
67	{ "h", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
68	{ "l", OPTTYPE_BOOLEAN, NULL,			OPTF_CLI|OPTF_CONF },
69	{ "N", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI|OPTF_CONF },
70	{ "n", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
71	{ "r", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
72	{ "V", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
73	{ "v", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
74	{ "w", OPTTYPE_STRING,	NULL,			OPTF_CLI },
75	{ "p", OPTTYPE_INT,	opts_parse_seconds,	OPTF_CLI|OPTF_CONF },
76	{ "P", OPTTYPE_INT,	opts_parse_ctime,	OPTF_CLI|OPTF_CONF },
77	{ "s", OPTTYPE_INT,	opts_parse_bytes,	OPTF_CLI|OPTF_CONF },
78	{ "a", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
79	{ "b", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
80	{ "c", OPTTYPE_BOOLEAN, NULL,			OPTF_CLI|OPTF_CONF },
81	{ "g", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
82	{ "m", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
83	{ "M", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
84	{ "o", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
85	{ "R", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
86	{ "t", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
87	{ "z", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
88	{ "A", OPTTYPE_INT,	opts_parse_seconds,	OPTF_CLI|OPTF_CONF },
89	{ "C", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
90	{ "E", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
91	{ "S", OPTTYPE_INT,	opts_parse_bytes,	OPTF_CLI|OPTF_CONF },
92	{ "T", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
93};
94
95int Opttable_cnt = sizeof (Opttable) / sizeof (struct optinfo);
96
97/*
98 * opts_init -- set current options parsing table
99 */
100void
101opts_init(struct optinfo *table, int numentries)
102{
103	while (numentries-- > 0) {
104		Info = lut_add(Info, table->oi_o, table);
105		table++;
106	}
107}
108
109/*
110 * opt_info -- fetch the optinfo struct for the given option
111 */
112static struct optinfo *
113opt_info(int c)
114{
115	char lhs[2];
116	lhs[0] = c;
117	lhs[1] = '\0';
118	return ((struct optinfo *)lut_lookup(Info, lhs));
119}
120
121/*
122 * opts_parse -- parse an argv-style list of options
123 *
124 * prints a message to stderr and calls err(EF_FILE|EF_JMP, ...) on error
125 */
126struct opts *
127opts_parse(struct opts *opts, char **argv, int flags)
128{
129	int dashdash = 0;
130	char *ptr;
131
132	if (opts == NULL) {
133		opts = MALLOC(sizeof (*opts));
134		opts->op_raw = opts->op_ints = NULL;
135		opts->op_cmdargs = fn_list_new(NULL);
136	}
137
138	/* no words to process, just return empty opts struct */
139	if (argv == NULL)
140		return (opts);
141
142	/* foreach word... */
143	for (; (ptr = *argv) != NULL; argv++) {
144		if (dashdash || *ptr != '-') {
145			/* found a cmdarg */
146			opts_setcmdarg(opts, ptr);
147			continue;
148		}
149		if (*++ptr == '\0')
150			err(EF_FILE|EF_JMP, "Illegal option: dash by itself");
151		if (*ptr == '-') {
152			/* (here's where support for --longname would go) */
153			if (*(ptr + 1) != '\0')
154				err(EF_FILE|EF_JMP, "Illegal option: -%s", ptr);
155			dashdash++;
156			continue;
157		}
158		for (; *ptr; ptr++) {
159			struct optinfo *info = opt_info(*ptr);
160
161			/* see if option was in our parsing table */
162			if (info == NULL)
163				err(EF_FILE|EF_JMP, "Illegal option: %c", *ptr);
164
165			/* see if context allows this option */
166			if ((flags & OPTF_CLI) &&
167			    (info->oi_flags & OPTF_CLI) == 0)
168				err(EF_FILE|EF_JMP,
169				    "Option '%c' not allowed on "
170				    "command line", *ptr);
171
172			if ((flags & OPTF_CONF) &&
173			    (info->oi_flags & OPTF_CONF) == 0)
174				err(EF_FILE|EF_JMP,
175				    "Option '%c' not allowed in "
176				    "configuration file", *ptr);
177
178			/* for boolean options, we have all the info we need */
179			if (info->oi_t == OPTTYPE_BOOLEAN) {
180				(void) opts_set(opts, info->oi_o, "");
181				continue;
182			}
183
184			/* option expects argument */
185			if (*++ptr == '\0' &&
186			    ((ptr = *++argv) == NULL || *ptr == '-'))
187				err(EF_FILE|EF_JMP,
188				    "Option '%c' requires an argument",
189				    info->oi_o[0]);
190			opts_set(opts, info->oi_o, ptr);
191			break;
192		}
193	}
194
195	return (opts);
196}
197
198/*
199 * opts_free -- free a struct opts previously allocated by opts_parse()
200 */
201void
202opts_free(struct opts *opts)
203{
204	if (opts) {
205		lut_free(opts->op_raw, NULL);
206		lut_free(opts->op_ints, NULL);
207		fn_list_free(opts->op_cmdargs);
208		FREE(opts);
209	}
210}
211
212/*
213 * opts_set -- set an option
214 */
215void
216opts_set(struct opts *opts, const char *o, const char *optarg)
217{
218	off_t *rval;
219	struct optinfo *info = opt_info(*o);
220
221	rval = MALLOC(sizeof (off_t));
222	opts->op_raw = lut_add(opts->op_raw, o, (void *)optarg);
223
224	if (info->oi_parser) {
225		*rval = (*info->oi_parser)(o, optarg);
226		opts->op_ints = lut_add(opts->op_ints, o, (void *)rval);
227	}
228}
229
230/*
231 * opts_setcmdarg -- add a cmdarg to the list of op_cmdargs
232 */
233static void
234opts_setcmdarg(struct opts *opts, const char *cmdarg)
235{
236	fn_list_adds(opts->op_cmdargs, cmdarg);
237}
238
239/*
240 * opts_count -- return count of the options in *options that are set
241 */
242int
243opts_count(struct opts *opts, const char *options)
244{
245	int count = 0;
246
247	for (; *options; options++) {
248		char lhs[2];
249		lhs[0] = *options;
250		lhs[1] = '\0';
251		if (lut_lookup(opts->op_raw, lhs))
252			count++;
253	}
254	return (count);
255}
256
257/*
258 * opts_optarg -- return the optarg for the given option, NULL if not set
259 */
260const char *
261opts_optarg(struct opts *opts, const char *o)
262{
263	return ((char *)lut_lookup(opts->op_raw, o));
264}
265
266/*
267 * opts_optarg_int -- return the int value for the given option
268 */
269off_t
270opts_optarg_int(struct opts *opts, const char *o)
271{
272	off_t	*ret;
273
274	ret = (off_t *)lut_lookup(opts->op_ints, o);
275	if (ret != NULL)
276		return (*ret);
277	return (0);
278}
279
280/*
281 * opts_cmdargs -- return list of op_cmdargs
282 */
283struct fn_list *
284opts_cmdargs(struct opts *opts)
285{
286	return (opts->op_cmdargs);
287}
288
289static void
290merger(const char *lhs, void *rhs, void *arg)
291{
292	struct lut **destlutp = (struct lut **)arg;
293
294	*destlutp = lut_add(*destlutp, lhs, rhs);
295}
296
297/*
298 * opts_merge -- merge two option lists together
299 */
300struct opts *
301opts_merge(struct opts *back, struct opts *front)
302{
303	struct opts *ret = MALLOC(sizeof (struct opts));
304
305	ret->op_raw = lut_dup(back->op_raw);
306	lut_walk(front->op_raw, merger, &(ret->op_raw));
307
308	ret->op_ints = lut_dup(back->op_ints);
309	lut_walk(front->op_ints, merger, &(ret->op_ints));
310
311	ret->op_cmdargs = fn_list_dup(back->op_cmdargs);
312
313	return (ret);
314}
315
316/*
317 * opts_parse_ctime -- parse a ctime format optarg
318 */
319static off_t
320opts_parse_ctime(const char *o, const char *optarg)
321{
322	struct tm tm;
323	off_t ret;
324
325	if (strptime(optarg, "%a %b %e %T %Z %Y", &tm) == NULL &&
326	    strptime(optarg, "%c", &tm) == NULL)
327		err(EF_FILE|EF_JMP,
328		    "Option '%c' requires ctime-style time", *o);
329	errno = 0;
330	if ((ret = (off_t)mktime(&tm)) == -1 && errno)
331		err(EF_FILE|EF_SYS|EF_JMP, "Option '%c' Illegal time", *o);
332
333	return (ret);
334}
335
336/*
337 * opts_parse_atopi -- parse a positive integer format optarg
338 */
339static off_t
340opts_parse_atopi(const char *o, const char *optarg)
341{
342	off_t ret = atoll(optarg);
343
344	while (isdigit(*optarg))
345		optarg++;
346
347	if (*optarg)
348		err(EF_FILE|EF_JMP,
349		    "Option '%c' requires non-negative number", *o);
350
351	return (ret);
352}
353
354/*
355 * opts_parse_atopi -- parse a size format optarg into bytes
356 */
357static off_t
358opts_parse_bytes(const char *o, const char *optarg)
359{
360	off_t ret = atoll(optarg);
361	while (isdigit(*optarg))
362		optarg++;
363
364	switch (*optarg) {
365	case 'g':
366	case 'G':
367		ret *= 1024;
368		/*FALLTHROUGH*/
369	case 'm':
370	case 'M':
371		ret *= 1024;
372		/*FALLTHROUGH*/
373	case 'k':
374	case 'K':
375		ret *= 1024;
376		/*FALLTHROUGH*/
377	case 'b':
378	case 'B':
379		if (optarg[1] == '\0')
380			return (ret);
381	}
382
383	err(EF_FILE|EF_JMP,
384	    "Option '%c' requires number with suffix from [bkmg]", *o);
385	/*NOTREACHED*/
386	return (0);
387}
388
389/*
390 * opts_parse_seconds -- parse a time format optarg into seconds
391 */
392static off_t
393opts_parse_seconds(const char *o, const char *optarg)
394{
395	off_t ret;
396
397	if (strcasecmp(optarg, "now") == 0)
398		return (OPTP_NOW);
399
400	if (strcasecmp(optarg, "never") == 0)
401		return (OPTP_NEVER);
402
403	ret = atoll(optarg);
404	while (isdigit(*optarg))
405		optarg++;
406
407	if (optarg[1] == '\0')
408		switch (*optarg) {
409		case 'h':
410		case 'H':
411			ret *= 60 * 60;
412			return (ret);
413		case 'd':
414		case 'D':
415			ret *= 60 * 60 * 24;
416			return (ret);
417		case 'w':
418		case 'W':
419			ret *= 60 * 60 * 24 * 7;
420			return (ret);
421		case 'm':
422		case 'M':
423			ret *= 60 * 60 * 24 * 30;
424			return (ret);
425		case 'y':
426		case 'Y':
427			ret *= 60 * 60 * 24 * 365;
428			return (ret);
429		}
430
431	err(EF_FILE|EF_JMP,
432	    "Option '%c' requires number with suffix from [hdwmy]", *o);
433	/*NOTREACHED*/
434	return (0);
435}
436
437/* info passed between opts_print() and printer() */
438struct printerinfo {
439	FILE *stream;
440	int isswitch;
441	char *exclude;
442};
443
444/* helper function for opts_print() */
445static void
446printer(const char *lhs, void *rhs, void *arg)
447{
448	struct printerinfo *pip = (struct printerinfo *)arg;
449	char *s = (char *)rhs;
450
451	if (pip->isswitch) {
452		char *ep = pip->exclude;
453		while (ep && *ep)
454			if (*ep++ == *lhs)
455				return;
456	}
457
458	(void) fprintf(pip->stream, " %s%s", (pip->isswitch) ? "-" : "", lhs);
459	if (s && *s) {
460		(void) fprintf(pip->stream, " ");
461		opts_printword(s, pip->stream);
462	}
463}
464
465/*
466 * opts_printword -- print a word, quoting as necessary
467 */
468void
469opts_printword(const char *word, FILE *stream)
470{
471	char *q = "";
472
473	if (word != NULL) {
474		if (strchr(word, ' ') || strchr(word, '\t') ||
475		    strchr(word, '$') || strchr(word, '[') ||
476		    strchr(word, '?') || strchr(word, '{') ||
477		    strchr(word, '`') || strchr(word, ';')) {
478			if (strchr(word, '\'') == NULL)
479				q = "'";
480			else if (strchr(word, '"') == NULL)
481				q = "\"";
482			else
483				err(EF_FILE|EF_JMP,
484				    "Can't protect quotes in <%s>", word);
485			(void) fprintf(stream, "%s%s%s", q, word, q);
486		} else
487			(void) fprintf(stream, "%s", word);
488	}
489}
490
491/*
492 * opts_print -- print options to stream, leaving out those in "exclude"
493 */
494void
495opts_print(struct opts *opts, FILE *stream, char *exclude)
496{
497	struct printerinfo pi;
498	struct fn *fnp;
499
500	pi.stream = stream;
501	pi.isswitch = 1;
502	pi.exclude = exclude;
503
504	lut_walk(opts->op_raw, printer, &pi);
505
506	fn_list_rewind(opts->op_cmdargs);
507	while ((fnp = fn_list_next(opts->op_cmdargs)) != NULL) {
508		(void) fprintf(stream, " ");
509		opts_printword(fn_s(fnp), stream);
510	}
511}
512
513#ifdef	TESTMODULE
514
515/* table that drives argument parsing */
516static struct optinfo Testopttable[] = {
517	{ "a", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
518	{ "b", OPTTYPE_STRING,	NULL,			OPTF_CLI },
519	{ "c", OPTTYPE_INT,	opts_parse_seconds,	OPTF_CLI|OPTF_CONF },
520	{ "d", OPTTYPE_INT,	opts_parse_ctime,	OPTF_CLI|OPTF_CONF },
521	{ "e", OPTTYPE_INT,	opts_parse_bytes,	OPTF_CLI|OPTF_CONF },
522	{ "f", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
523};
524
525/*
526 * test main for opts module, usage: a.out options...
527 */
528int
529main(int argc, char *argv[])
530{
531	struct opts *opts;
532
533	err_init(argv[0]);
534	setbuf(stdout, NULL);
535
536	opts_init(Testopttable,
537	    sizeof (Testopttable) / sizeof (struct optinfo));
538
539	argv++;
540
541	if (SETJMP)
542		err(0, "opts parsing failed");
543	else
544		opts = opts_parse(NULL, argv, OPTF_CLI);
545
546	printf("options:");
547	opts_print(opts, stdout, NULL);
548	printf("\n");
549
550	err_done(0);
551	/* NOTREACHED */
552	return (0);
553}
554
555#endif	/* TESTMODULE */
556