1 /*
2 ** petopt.c - Command line argument parser
3 **
4 ** Copyright (c) 1999 Peter Eriksson <pen@lysator.liu.se>
5 **
6 ** This program is free software; you can redistribute it and/or
7 ** modify it as you wish - as long as you don't claim that you wrote
8 ** it.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 */
14 
15 #include "config.h"
16 
17 #include <stdio.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #ifdef HAVE_SYSLOG_H
23 #include <syslog.h>
24 #endif
25 #include "petopt.h"
26 
27 
28 
29 /*
30 ** Compare the user supplied long style command line option against
31 ** the program defined. Handle abbreviations, truncation and ambiguous
32 ** abbreviations.
33 */
34 static int
compare_long(const char * user_option,const char * aov_long,int len)35 compare_long(const char *user_option,
36 	     const char *aov_long,
37 	     int len)
38 {
39     unsigned char *uc, *ac;
40     int diff = 0;
41     int saved_len = len;
42 
43 
44     uc = (unsigned char *) user_option;
45     ac = (unsigned char *) aov_long;
46 
47     while (len > 0 && diff == 0)
48     {
49 	/* If we reach a word delimiter, skip to next word */
50 	if (*uc == '-')
51 	    while (*ac && *ac != '-')
52 		++ac;
53 
54 	diff = (int) tolower(*uc) - (int) tolower(*ac);
55 
56 	++uc;
57 	++ac;
58 	--len;
59     }
60 
61     if (diff)
62     {
63 	/* Check abbrev */
64 	int adiff = 0;
65 
66 
67 	uc = (unsigned char *) user_option;
68 	ac = (unsigned char *) aov_long;
69 	len = saved_len;
70 
71 	while (len > 0 && adiff == 0)
72 	{
73 	    /* Locate abbrev characters (uppercase) */
74 	    while (*ac && (islower(*ac) || *ac == '-'))
75 		++ac;
76 
77 	    adiff = (int) tolower(*uc) - (int) tolower(*ac);
78 
79 	    ++uc;
80 	    ++ac;
81 	    --len;
82 	}
83 
84 	if (adiff == 0)
85 	    diff = 0;
86     }
87     return diff;
88 }
89 
90 
91 
92 static int
petopt_parse_option(PETOPT * pop,PETOPTS ** pov,const char ** optarg)93 petopt_parse_option(PETOPT *pop,
94 		    PETOPTS **pov,
95 		    const char **optarg)
96 {
97     int i;
98 
99 
100     *pov = NULL;
101     *optarg = NULL;
102 
103 
104   Again:
105     /* Index out of bounds? */
106     if (pop->ai >= pop->argc)
107     {
108 	return POE_EOF;
109     }
110 
111     if (pop->ci == 0)
112     {
113 	/* Non option or "-" */
114 	if (pop->argv[pop->ai][0] != '-' ||
115 	    (pop->argv[pop->ai][0] == '-' &&
116 	     pop->argv[pop->ai][1] == '\0'))
117 	{
118 	    pop->oav[pop->oac++] = strdup(pop->argv[pop->ai++]);
119 	    goto Again;
120 	}
121 
122 	/* "--", stop parsing and skip */
123 	if (strcmp(pop->argv[pop->ai], "--") == 0)
124 	{
125 	    pop->ai++;
126 
127 	    while (pop->ai < pop->argc)
128 	    {
129 		pop->oav[pop->oac++] = strdup(pop->argv[pop->ai++]);
130 	    }
131 
132 	    return POE_EOF;
133 	}
134 
135 	if (pop->argv[pop->ai][1] == '-')
136 	{
137 	    /* Long option */
138 	    const char *opt, *arg;
139 	    int len, ix;
140 	    char *boolval = NULL;
141 	    int withval = 0;
142 
143 
144 	    pop->saved_ai = pop->ai;
145 	    pop->saved_ci = pop->ci;
146 
147 	    opt = pop->argv[pop->ai]+2;
148 	    if (strncasecmp(opt, "enable-", 7) == 0)
149 	    {
150 		boolval = "true";
151 		opt += 7;
152 	    }
153 	    else if (strncasecmp(opt, "disable-", 8) == 0)
154 	    {
155 		boolval = "false";
156 		opt += 8;
157 	    }
158 	    else if (strncasecmp(opt, "with-", 5) == 0)
159 	    {
160 		withval = 1;
161 		opt += 5;
162 	    }
163 	    else if (strncasecmp(opt, "without-", 8) == 0)
164 	    {
165 		withval = -1;
166 		opt += 8;
167 	    }
168 
169 
170 	    arg = strchr(opt, '=');
171 	    if (arg)
172 		len = arg - opt;
173 	    else
174 		len = strlen(opt);
175 
176 
177 	    ix = -1;
178 	    for (i = 0; pop->pov[i].s != -1; i++)
179 	    {
180 		if (pop->pov[i].l != NULL &&
181 		    compare_long(opt, pop->pov[i].l, len) == 0)
182 		{
183 		    /* Match found */
184 		    if (ix < 0)
185 			ix = i;
186 		    else
187 			return POE_MULTI;
188 		}
189 	    }
190 
191 
192 	    if (ix >= 0)
193 	    {
194 		/* Found a unique match */
195 
196 		pop->ai++;
197 
198 		*pov = &pop->pov[ix];
199 
200 		if (pop->pov[ix].f)
201 		{
202 		    /* Check for (optional) argument */
203 
204 		    if (boolval)
205 		    {
206 			/* --enable-XXX or --disable-XXX */
207 
208 			if (arg ||
209 			    (pop->pov[ix].f & POF_TYPEMASK) != POF_BOOL)
210 			{
211 			    return POE_INVALID;
212 			}
213 
214 			*optarg = boolval;
215 			return 0;
216 		    }
217 
218 		    if (withval)
219 		    {
220 			if (withval == 1)
221 			{
222 			    if (arg)
223 				*optarg = arg+1;
224 			    else
225 				*optarg = "yes";
226 			}
227 			else
228 			{
229 			    if (arg)
230 				return POE_INVALID;
231 			    else
232 				*optarg = NULL;
233 			}
234 			return 0;
235 		    }
236 
237 		    if (arg)
238 		    {
239 			/* Argument after "=" */
240 			*optarg = arg+1;
241 			return 0;
242 		    }
243 
244 		    if (pop->ai < pop->argc &&
245 			(pop->argv[pop->ai][0] != '-' ||
246 			 pop->argv[pop->ai][0] == '\0'))
247 		    {
248 			/* Have an argument, empty string or "-" */
249 			if (((pop->pov[ix].f & POF_TYPEMASK) == POF_STR) ||
250 			    isdigit((int) pop->argv[pop->ai][0]))
251 			{
252 			    *optarg = pop->argv[pop->ai];
253 			    pop->ai++;
254 			}
255 		    }
256 
257 		    /* Argument missing, and not optional? */
258 		    if (*optarg == NULL &&
259 			!((pop->pov[ix].f & POF_OPT) ||
260 			  ((pop->pov[ix].f & POF_TYPEMASK) == POF_BOOL)))
261 		    {
262 			pop->ai--;
263 			return POE_MISSING;
264 		    }
265 		}
266 
267 		return 0;
268 	    }
269 
270 	    /* Unknown long command line switch */
271 	    return POE_OPTION;
272 	}
273 	else
274 	{
275 	    /* Short option */
276 
277 	    pop->ci = 1;
278 	}
279     }
280 
281     /* Short option */
282 
283     pop->saved_ai = pop->ai;
284     pop->saved_ci = pop->ci;
285 
286     i = 0;
287     while (pop->pov[i].s != -1 && pop->pov[i].s != pop->argv[pop->ai][pop->ci])
288 	i++;
289 
290     if (pop->pov[i].s == -1)
291     {
292 	/* Unknown short command line switch */
293 	return POE_OPTION;
294     }
295 
296     /* Found a matching short option */
297 
298     *pov = &pop->pov[i];
299     pop->ci++;
300 
301     if (pop->argv[pop->ai][pop->ci] == '\0')
302     {
303 	pop->ai++;
304 	pop->ci = 0;
305     }
306 
307     /* Have an (optional) argument? */
308     if (pop->pov[i].f)
309     {
310 	if (pop->ai < pop->argc &&
311 	    (pop->ci != 0 ||
312 	     ((pop->argv[pop->ai][0] != '-') ||
313 	      pop->argv[pop->ai][1] == '\0')))
314 	{
315 	    if (((pop->pov[i].f & POF_TYPEMASK) == POF_STR) ||
316 		(isdigit((int) pop->argv[pop->ai][pop->ci]) ||
317 		 pop->argv[pop->ai][pop->ci] == '-'))
318 	    {
319 		*optarg = pop->argv[pop->ai]+pop->ci;
320 
321 		pop->ai++;
322 		pop->ci = 0;
323 	    }
324 	}
325     }
326 
327     /* Argument missing, and not optional? */
328     if (pop->pov[i].f &&
329 	!((pop->pov[i].f & POF_OPT) ||
330 	  (pop->pov[i].f & POF_TYPEMASK) == POF_BOOL) &&
331 	*optarg == NULL)
332     {
333 	if (pop->ci == 0)
334 	{
335 	    pop->ai--;
336 	    pop->ci = strlen(pop->argv[pop->ai])-1;
337 	}
338 
339 	return POE_MISSING;
340     }
341 
342     return 0;
343 }
344 
345 
346 int
petopt_print_error(PETOPT * pop,PETOPTS * pov,int err,FILE * fp)347 petopt_print_error(PETOPT *pop,
348 		   PETOPTS *pov,
349 		   int err,
350 		   FILE *fp)
351 {
352     char buf[3];
353     const char *arg;
354 
355 
356     if (pop->saved_ci > 0)
357     {
358 	buf[0] = '-';
359 	buf[1] = pop->argv[pop->saved_ai][pop->saved_ci];
360 	buf[2] = '\0';
361 	arg = buf;
362     }
363     else
364     {
365 	arg = pop->argv[pop->saved_ai];
366 	if (arg == NULL)
367 	    arg = "";
368     }
369 
370     switch (err)
371     {
372       case POE_EOF:
373 	return 0;
374 
375       case POE_OPTION:
376 #ifdef HAVE_SYSLOG
377 	if (pop->f & POF_SYSLOG)
378 	    syslog(LOG_ERR, "Unrecognized option: %s", arg);
379 #endif
380 	fprintf(fp, "%s: Unrecognized option: %s\n",
381 		pop->argv[0], arg);
382 	break;
383 
384       case POE_MULTI:
385 #ifdef HAVE_SYSLOG
386 	if (pop->f & POF_SYSLOG)
387 	    syslog(LOG_ERR, "Ambiguous option: %s", arg);
388 #endif
389 	fprintf(fp, "%s: Ambiguous option: %s\n",
390 		pop->argv[0], arg);
391 	break;
392 
393       case POE_MISSING:
394 #ifdef HAVE_SYSLOG
395 	if (pop->f & POF_SYSLOG)
396 	    syslog(LOG_ERR, "Missing argument for option: %s", arg);
397 #endif
398 
399 	fprintf(fp, "%s: Missing argument for option: %s\n",
400 		pop->argv[0], arg);
401 	break;
402 
403       case POE_INVALID:
404 #ifdef HAVE_SYSLOG
405 	if (pop->f & POF_SYSLOG)
406 	    syslog(LOG_ERR, "Invalid argument for option: %s", arg);
407 #endif
408 
409 	fprintf(fp, "%s: Invalid argument for option: %s\n",
410 		    pop->argv[0], arg);
411 	break;
412 
413       case POE_INTERNAL:
414 #ifdef HAVE_SYSLOG
415 	if (pop->f & POF_SYSLOG)
416 	    syslog(LOG_ERR, "Internal error parsing option: %s", arg);
417 #endif
418 
419 	fprintf(fp, "%s: Internal error parsing option: %s\n",
420 		    pop->argv[0], arg);
421 	break;
422 
423       default:
424 #ifdef HAVE_SYSLOG
425 	if (pop->f & POF_SYSLOG)
426 	    syslog(LOG_ERR, "Internal options parsing error: #%d", err);
427 #endif
428 
429 	fprintf(fp, "%s: Internal options parsing error: #%d\n",
430 		pop->argv[0], err);
431     }
432 
433     return -1;
434 }
435 
436 
437 int
petopt_parse(PETOPT * pop,int * o_argc,char *** o_argv)438 petopt_parse(PETOPT *pop,
439 	     int *o_argc,
440 	     char ***o_argv)
441 {
442     int err;
443     const char *arg;
444     PETOPTS *pov;
445 
446 
447     while ((err = petopt_parse_option(pop, &pov, &arg)) == 0)
448     {
449 	if (pop->parse &&
450 	    (err = pop->parse(pop, pov, arg)) < 0)
451 	{
452 	    goto Fail;
453 	}
454 
455 	/* Parser assigned the values */
456 	if (err == 0)
457 	    continue;
458 
459 	/* Option not handled, and no storage assigned? */
460 	if (pov->v == NULL)
461 	{
462 	    err = POE_INTERNAL;
463 	    goto Fail;
464 	}
465 
466 	switch (pov->f & POF_TYPEMASK)
467 	{
468 	  case POF_NONE:
469 	    break;
470 
471 	  case POF_BOOL:
472 	    if (arg && *arg != '\0')
473 	    {
474 		if (strcasecmp(arg, "true") == 0 ||
475 		    strcasecmp(arg, "yes") == 0 ||
476 		    strcasecmp(arg, "on") == 0)
477 		{
478 		    *(int *)(pov->v) = 1;
479 		}
480 		else if (strcasecmp(arg, "false") == 0 ||
481 		    strcasecmp(arg, "no") == 0 ||
482 		    strcasecmp(arg, "off") == 0)
483 		{
484 		    *(int *)(pov->v) = 0;
485 		}
486 		else
487 		{
488 		    err = POE_INVALID;
489 		    goto Fail;
490 		}
491 	    }
492 	    else
493 	    {
494 		*(int *)(pov->v) = 1;
495 	    }
496 	    break;
497 
498 	  case POF_INT:
499 	    if (arg && *arg != '\0')
500 	    {
501 		/* XXX: Check for integer number */
502 		*(int *)(pov->v) = atoi(arg);
503 	    }
504 	    else
505 	    {
506 		/* XXX: Check for POF_INC flag?? */
507 		++*(int *)(pov->v);
508 	    }
509 	    break;
510 
511 	  case POF_STR:
512 	    if (pov->f & POF_DUP)
513 		*(char **)(pov->v) = strdup(arg);
514 	    else
515 		*(char **)(pov->v) = (char *) arg;
516 	    break;
517 
518 	  default:
519 	    err = POE_OPTION;
520 	    goto Fail;
521 	}
522     }
523 
524     if (err == POE_EOF)
525 	err = 0;
526 
527   Fail:
528     if (err)
529 	err = pop->errpr(pop, pov, err,
530 			 (pop->f & POF_NOERRPRINT) ? NULL : stderr);
531 
532     *o_argc = pop->oac;
533     *o_argv = pop->oav;
534 
535     return err;
536 }
537 
538 
539 
540 int
petopt_print_usage(PETOPT * pop,FILE * fp)541 petopt_print_usage(PETOPT *pop,
542 		   FILE *fp)
543 {
544     int i, len;
545     struct petopt_option *pov;
546 
547 
548     pov = pop->pov;
549     for (i = 0; pov[i].s != -1; i++)
550     {
551 	if (pov[i].s > ' ' && pov[i].s <='~')
552 	    fprintf(fp, "  -%c, ", pov[i].s);
553 	else
554 	    fprintf(fp, "      ");
555 
556 	len = 0;
557 	if (pov[i].l)
558 	{
559 	    len = fprintf(fp, "--%s", pov[i].l);
560 	    if (pov[i].f)
561 	    {
562 		if (pov[i].f & POF_OPT)
563 		    len += fprintf(fp, " [ARG]");
564 		else
565 		    len += fprintf(fp, " ARG");
566 	    }
567 	}
568 
569 	while (len++ < 30)
570 	    putchar(' ');
571 
572 	if (pov[i].h)
573 	    fputs(pov[i].h, fp);
574 	if (pop->f & POF_PRDEFAULT && pov[i].v != NULL)
575 	    switch (pov[i].f & POF_TYPEMASK)
576 	    {
577 	      case POF_INT:
578 		  {
579 		      int *iv = pov[i].v;
580 		      if (*iv != 0)
581 			  fprintf(fp, " (Default: %d)", *iv);
582 		  }
583 		break;
584 
585 	      case POF_STR:
586 		  {
587 		      char **cv = pov[i].v;
588 		      if (*cv)
589 			  fprintf(fp, " (Default: \"%s\")", *cv);
590 		  }
591 		  break;
592 	    }
593 	putc('\n', fp);
594     }
595 
596     return 0;
597 }
598 
599 
600 int
petopt_setup(PETOPT ** popp,int f,int argc,char ** argv,PETOPTS * pov,int (* parse)(PETOPT * pop,PETOPTS * pov,const char * arg),int (* errpr)(PETOPT * pop,PETOPTS * pov,int err,FILE * fp))601 petopt_setup(PETOPT **popp,
602 	     int f,
603 	     int argc,
604 	     char **argv,
605 	     PETOPTS *pov,
606 	     int (*parse)(PETOPT *pop, PETOPTS *pov, const char *arg),
607 	     int (*errpr)(PETOPT *pop, PETOPTS *pov, int err, FILE *fp))
608 {
609     PETOPT *pop = malloc(sizeof(PETOPT));
610     if (pop == NULL)
611 	return errno;
612 
613     memset(pop, 0, sizeof(*pop));
614 
615     pop->f = f;
616 
617     pop->argc = argc;
618     pop->argv = (const char **) argv;
619     pop->pov = pov;
620     pop->parse = parse;
621 
622     if (errpr)
623 	pop->errpr = errpr;
624     else
625 	pop->errpr = petopt_print_error;
626 
627     pop->ai = 1;
628     pop->ci = 0;
629 
630     pop->saved_ai = 0;
631     pop->saved_ci = 0;
632 
633     pop->oac = 1;
634 
635     pop->oav = calloc(argc+1, sizeof(char *));
636     pop->oav[0] = strdup(pop->argv[0]);
637     pop->oav[1] = NULL;
638 
639     *popp = pop;
640     return 0;
641 }
642 
643 
644 int
petopt_rewind(PETOPT * pop)645 petopt_rewind(PETOPT *pop)
646 {
647     pop->ai = 1;
648     pop->ci = 0;
649 
650     pop->oac = 1;
651     pop->oav[1] = NULL;
652 
653     return 0;
654 }
655 
656 
657 int
petopt_cleanup(PETOPT * pop)658 petopt_cleanup(PETOPT *pop)
659 {
660     int i;
661 
662     if (pop->oav)
663     {
664 	for (i = 0; i < pop->oac; i++)
665 	    if (pop->oav[i])
666 		free(pop->oav[i]);
667 	free(pop->oav);
668     }
669 
670     free(pop);
671     return 0;
672 }
673