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