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