xref: /illumos-gate/usr/src/cmd/logadm/opts.c (revision 2aeafac3612e19716bf8164f89c3c9196342979c)
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 */
45 static struct optinfo *opt_info(int c);
46 static void opts_setcmdarg(struct opts *opts, const char *cmdarg);
47 
48 /* info created by opts_parse(), private to this module */
49 struct 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 
55 static off_t opts_parse_ctime(const char *o, const char *optarg);
56 static off_t opts_parse_bytes(const char *o, const char *optarg);
57 static off_t opts_parse_atopi(const char *o, const char *optarg);
58 static off_t opts_parse_seconds(const char *o, const char *optarg);
59 
60 static struct lut *Info;		/* table driving parsing */
61 
62 /* table that drives argument parsing */
63 struct 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 
95 int Opttable_cnt = sizeof (Opttable) / sizeof (struct optinfo);
96 
97 /*
98  * opts_init -- set current options parsing table
99  */
100 void
101 opts_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  */
112 static struct optinfo *
113 opt_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  */
126 struct opts *
127 opts_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  */
201 void
202 opts_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  */
215 void
216 opts_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  */
233 static void
234 opts_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  */
242 int
243 opts_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  */
260 const char *
261 opts_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  */
269 off_t
270 opts_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  */
283 struct fn_list *
284 opts_cmdargs(struct opts *opts)
285 {
286 	return (opts->op_cmdargs);
287 }
288 
289 static void
290 merger(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  */
300 struct opts *
301 opts_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  */
319 static off_t
320 opts_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  */
339 static off_t
340 opts_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  */
357 static off_t
358 opts_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  */
392 static off_t
393 opts_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() */
438 struct printerinfo {
439 	FILE *stream;
440 	int isswitch;
441 	char *exclude;
442 };
443 
444 /* helper function for opts_print() */
445 static void
446 printer(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  */
468 void
469 opts_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  */
494 void
495 opts_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 */
516 static 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  */
528 int
529 main(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