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