1 /*
2  *  params.c:		Parameter file and command line parsing
3  *
4  *  Written by:		Stefan Frank
5  *			Ullrich Hafner
6  *
7  *  This file is part of FIASCO (Fractal Image And Sequence COdec)
8  *  Copyright (C) 1994-2000 Ullrich Hafner
9  */
10 
11 /*
12  *  $Date: 2000/07/15 17:24:21 $
13  *  $Author: hafner $
14  *  $Revision: 5.2 $
15  *  $State: Exp $
16  */
17 
18 #define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */
19 #define _BSD_SOURCE 1
20     /* Make sure strdup() is in string.h and strcaseeq() is in nstring.h */
21 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
22 
23 #include "config.h"
24 
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <math.h>			/* strtod() on SUN sparc */
28 
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include <getopt.h>			/* system or ../lib */
33 
34 #include "pm_c_util.h"
35 #include "nstring.h"
36 
37 #include "types.h"
38 #include "macros.h"
39 #include "bit-io.h"
40 #include "misc.h"
41 #include "fiasco.h"
42 
43 #include "binerror.h"
44 
45 #include "params.h"
46 
47 /*****************************************************************************
48 
49 				prototypes
50 
51 *****************************************************************************/
52 
53 static void
54 read_parameter_file (param_t *params, FILE *file);
55 static int
56 get_parameter_index (const param_t *params, const char *search_string);
57 static void
58 set_parameter (param_t *parameter, const char *value);
59 static void
60 usage (const param_t *params, const char *progname, const char *synopsis,
61        const char *comment, const char *non_opt_string,
62        bool_t show_all_options, const char *sys_file_name,
63        const char *usr_file_name);
64 
65 /*****************************************************************************
66 
67 				public code
68 
69 *****************************************************************************/
70 
71 int
parseargs(param_t * usr_params,int argc,char ** argv,const char * synopsis,const char * comment,const char * non_opt_string,const char * path,const char * sys_file_name,const char * usr_file_name)72 parseargs (param_t *usr_params,
73            int argc, char **argv,
74            const char *synopsis,
75            const char *comment,
76            const char *non_opt_string,
77            const char *path,
78            const char *sys_file_name,
79            const char *usr_file_name)
80 /*
81  *  Perform the command line parsing.
82  *  List of allowed parameters is given by 'usr_params'.
83  *  Command line and number of parameters are given by 'argv' and 'argc'.
84  *  'synopsis' contains a brief description of the program and
85  *  'comment' may contain some additional advice.
86  *  Initialization order of parameters:
87  *	1.) Default values given by the param_t struct
88  *	2.) System parameter-file ('path'/'sys_file_name')
89  *	3.) User parameter-file ($HOME/'usr_file_name')
90  *	4.) Command line parameters
91  *	5.) Parameter-file forced by option -f (--config-file)
92  *
93  *  Return value:
94  *	index in ARGV of the first ARGV-element that is not an option.
95  *
96  *  Side effects:
97  *	the elements of ARGV are permuted
98  *      usr_params [].value is modified
99  */
100 {
101    extern int optind;			/* index in ARGV of the 1st element
102 					   that is not an option */
103    bool_t     detailed_help = NO;	/* NO if all parameters can be modified
104 					   with short options too */
105    unsigned   n1;			/* number of user parameters */
106    unsigned   n2;			/* number of system parameters */
107    bool_t     read_config_file = NO;	/* will override command line */
108    param_t    *params;			/* array of user and system params */
109    param_t    *sys_params;		/* array of system parameters */
110    param_t    detailed_sys_params [] =  /* detailed system parameters */
111    {
112       {"version", NULL, 'v', PFLAG, {0}, NULL,
113        "Print program version number, then exit."},
114       {"verbose", "NUM", 'V', PINT, {0}, "1",
115        "Set level of verbosity to `%s'."},
116       {"config", "FILE", 'f', PSTR, {0}, NULL,
117        "Load `%s' to initialize parameters."},
118       {"info", NULL, 'h', PFLAG, {0}, NULL,
119        "Print brief help, then exit."},
120       {"help", NULL, 'H', PFLAG, {0}, NULL,
121        "Print detailed help, then exit."},
122       {NULL, NULL, 0, PSTR, {0}, NULL, NULL }
123    };
124    param_t    short_sys_params [] =	/* short system parameters */
125    {
126       {"version", NULL, 'v', PFLAG, {0}, NULL,
127        "Print program version number, then exit."},
128       {"verbose", "NUM", 'V', PINT, {0}, "1",
129        "Set level of verbosity to `%s'."},
130       {"config", "FILE", 'f', PSTR, {0}, NULL,
131        "Load `%s' to initialize parameters."},
132       {"help", NULL, 'h', PFLAG, {0}, NULL,
133        "Print this help, then exit."},
134       {NULL, NULL, 0, PSTR, {0}, NULL, NULL }
135    };
136    char *sys_path;			/* path to system config file */
137 
138    sys_path = calloc (strlen (path) + strlen (sys_file_name) + 2,
139 		      sizeof (char));
140    if (!sys_path)
141       error ("Out of memory.");
142    sprintf (sys_path, "%s/%s", path, sys_file_name);
143 
144    /*
145     *  Set parameters defaults
146     */
147    {
148        param_t *p;
149 
150        for (p = usr_params; p->name != NULL; p++)
151        {
152            set_parameter (p, p->default_value);
153            if (p->optchar == '\0')
154                detailed_help = YES;
155        }
156 
157       sys_params = detailed_help ? detailed_sys_params : short_sys_params;
158 
159       for (p = sys_params; p->name != NULL; p++)
160           set_parameter (p, p->default_value);
161    }
162    /*
163     *  Append system command line option to user parameters
164     */
165    for (n1 = 0; usr_params [n1].name != NULL; n1++)
166       ;
167    for (n2 = 0; sys_params [n2].name != NULL; n2++)
168       ;
169    params = calloc (n1 + n2 + 1, sizeof (param_t));
170    if (!params)
171       error ("Out of memory.");
172 
173    memcpy (params, usr_params, n1 * sizeof (param_t));
174    memcpy (params + n1, sys_params, (n2 + 1) * sizeof (param_t));
175    /*
176     *  Try to open the system resource file 'path'/'sys_file_name'
177     */
178    {
179       FILE *parameter_file = open_file (sys_path, NULL, READ_ACCESS);
180       if (parameter_file == NULL)
181 /*
182 	 warning ("No system resource file found.");
183 */ {}
184       else
185       {
186 	 read_parameter_file (params, parameter_file);
187 	 fclose (parameter_file);
188       }
189    }
190    /*
191     *  Try to read user resource file $HOME/'usr_file_name'
192     */
193    {
194       FILE *parameter_file = open_file (usr_file_name, "HOME", READ_ACCESS);
195       if (parameter_file != NULL)
196       {
197 	 read_parameter_file (params, parameter_file);
198 	 fclose (parameter_file);
199       }
200    }
201    /*
202     *  Parse command line options
203     */
204    {
205       extern char   *optarg;		/* argument of current option */
206       struct option *long_options;	/* array of long options */
207       int	     option_index = 0;
208       char	     optstr [MAXSTRLEN]; /* string containing the legitimate
209 					    option characters */
210       int	     optchar;		/* found option character */
211 
212       /*
213        *  Build short option string for getopt_long ().
214        */
215       {
216 	 param_t *p;			/* counter */
217 	 char	 *ptr_optstr;		/* pointer to position in string */
218 
219 	 ptr_optstr = optstr;
220 	 for (p = params; p->name != NULL; p++)
221 	    if (p->optchar != '\0')
222 	    {
223 	       *ptr_optstr++ = p->optchar;
224 	       if (p->type == POSTR)
225 	       {
226 		  *ptr_optstr++ = ':';
227 		  *ptr_optstr++ = ':';
228 	       }
229 	       else if (p->type != PFLAG)
230 		  *ptr_optstr++ = ':';
231 	    }
232 	 *ptr_optstr = '\0';
233       }
234 
235       /*
236        *  Build long option string for getopt_long ().
237        */
238       {
239 	 int i;
240 
241 	 long_options = calloc (n1 + n2 + 1, sizeof (struct option));
242 	 if (!long_options)
243 	    error ("Out of memory.");
244 	 for (i = 0; params [i].name != NULL; i++)
245 	 {
246 	    long_options [i].name    = params [i].name;
247 	    switch (params [i].type)
248 	    {
249 	       case PFLAG:
250 		  long_options [i].has_arg = 0;
251 		  break;
252 	       case POSTR:
253 		  long_options [i].has_arg = 2;
254 		  break;
255 	       case PINT:
256 	       case PSTR:
257 	       case PFLOAT:
258 	       default:
259 		  long_options [i].has_arg = 1;
260 		  break;
261 	    }
262 	    long_options [i].has_arg = params [i].type != PFLAG;
263 	    long_options [i].flag    = NULL;
264 	    long_options [i].val     = 0;
265 	 }
266       }
267 
268       /*
269        *  Parse command line
270        */
271       while ((optchar = getopt_long (argc, argv, optstr, long_options,
272 				     &option_index)) != EOF)
273       {
274 	 int param_index = -1;
275 
276 	 switch (optchar)
277 	 {
278 	    case 0:
279 	       param_index = option_index;
280 	       break;
281 	    case ':':
282 	       if (detailed_help)
283 		  fprintf (stderr,
284 			   "Try `%s -h' or `%s --help' for "
285 			   "more information.\n",
286 			   argv [0], argv [0]);
287 	       else
288 		  fprintf (stderr, "Try `%s --help' for more information.\n",
289 			   argv [0]);
290 	       exit (2);
291 	       break;
292 	    case '?':
293 	       if (detailed_help)
294 		  fprintf (stderr,
295 			   "Try `%s -h' or `%s --help' "
296 			   "for more information.\n",
297 			   argv [0], argv [0]);
298 	       else
299 		  fprintf (stderr, "Try `%s --help' for more information.\n",
300 			   argv [0]);
301 	       exit (2);
302 	       break;
303 	    default:
304 	       {
305 		  int i;
306 
307 		  for (i = 0; params [i].name != NULL; i++)
308 		     if (params [i].optchar == optchar)
309 		     {
310 			param_index = i;
311 			break;
312 		     }
313 	       }
314 	 }
315 	 /*
316 	  *  Check for system options
317 	  */
318 	 if (param_index >= 0)
319 	 {
320 	    set_parameter (params + param_index, optarg ? optarg : "");
321 	    if (streq (params [param_index].name, "help"))
322 	       usage (params, argv [0], synopsis, comment, non_opt_string,
323 		      YES, sys_path, usr_file_name);
324 	    else if (streq (params [param_index].name, "info"))
325 	       usage (params, argv [0], synopsis, comment, non_opt_string,
326 		      NO, sys_path, usr_file_name);
327 	    else if (streq (params [param_index].name, "version"))
328 	    {
329            fprintf (stderr, "%s " VERSION "\n", argv [0]);
330            {
331               /* Kludge for standard Netpbm version announcement */
332               char * modified_argv[2];
333               int argc;
334               modified_argv[0] = argv[0];
335               modified_argv[1] = (char *) "--version";
336               argc = 2;
337               pm_proginit(&argc, (const char **) modified_argv);
338            }
339            exit (2);
340 	    }
341 	    else if (streq (params [param_index].name, "verbose"))
342 	       fiasco_set_verbosity (
343                * (fiasco_verbosity_e *) parameter_value (params,
344                                                          "verbose"));
345 	    else if (streq (params [param_index].name, "config"))
346 	       read_config_file = YES;
347 	    param_index = -1;		/* clear index flag */
348 	 }
349       }
350 
351       free (long_options);
352    }
353 
354    /*
355     *  Read config-file if specified by option -f
356     */
357    if (read_config_file)
358    {
359       char *filename;
360 
361       if ((filename = (char *) parameter_value (params, "config")) != NULL)
362       {
363 	 FILE *parameter_file;		/* input file */
364 
365 	 warning ("Options set in file `%s' will override"
366 		  " command line options.", filename);
367 	 parameter_file = open_file (filename, NULL, READ_ACCESS);
368 	 if (parameter_file != NULL)
369 	 {
370 	    read_parameter_file (params, parameter_file);
371 	    fclose (parameter_file);
372 	 }
373 	 else
374 	    file_error (filename);
375       }
376       else
377 	 error ("Invalid config filename.");
378    }
379 
380    memcpy (usr_params, params, n1 * sizeof (param_t)); /* fill user struct */
381    free (sys_path);
382 
383    return optind;
384 }
385 
386 void *
parameter_value(const param_t * params,const char * name)387 parameter_value (const param_t *params, const char *name)
388 /*
389  *  Extract value of parameter 'name.' of the given parameters 'params'.
390  *
391  *  Return value:
392  *	value of given parameter
393  */
394 {
395    int pind = get_parameter_index (params, name);
396 
397    if (pind < 0)
398       error ("Invalid parameter `%s'.", name);
399 
400    if (params [pind].type == PSTR || params [pind].type == POSTR)
401       return (void *) params [pind].value.s;
402 
403    return (void *) &(params [pind].value);
404 }
405 
406 void
ask_and_set(param_t * params,const char * name,const char * msg)407 ask_and_set (param_t *params, const char *name, const char *msg)
408 /*
409  *  Ask user (print given message 'msg') for missing mandatory
410  *  parameter 'name' of the given parameters 'params'.
411  *
412  *  No return value.
413  *
414  *  Side effects:
415  *	'params ['name'].value' is changed
416  */
417 {
418    char answer [MAXSTRLEN];
419    int  index = get_parameter_index (params, name);
420 
421    if (index < 0)
422       error ("Invalid parameter %s.", name);
423 
424    if (msg)
425       fprintf (stderr, "%s\n", msg);
426 
427    switch (params [index].type)
428    {
429       case PFLAG:			/* Unusual, at least. */
430 	 warning ("Flags should be initialized and set on demand, "
431 		  "not request");
432       case PINT:
433       case PSTR:
434       case POSTR:
435       case PFLOAT:
436 	 scanf (MAXSTRLEN_SCANF, answer);
437 	 set_parameter (&params [index], answer);
438 	 break;
439       default:
440 	 error ("Invalid parameter type for %s", name);
441    }
442 }
443 
444 void
write_parameters(const param_t * params,FILE * output)445 write_parameters (const param_t *params, FILE *output)
446 /*
447  *  Write all parameter settings to 'output'.
448  *
449  *  No return value.
450  */
451 {
452    int pind;
453 
454    if (!params || !output)
455       error ("Parameters must be not NULL.");
456 
457    for (pind = 0; params [pind].name != NULL; pind++)
458    {
459       fprintf (output, "# %s = ", params [pind].name);
460       switch (params [pind].type)
461       {
462 	 case PFLAG:
463 	    fprintf (output, "%s\n", params [pind].value.b ? "TRUE" : "FALSE");
464 	    break;
465 	 case PINT:
466 	    fprintf (output, "%d\n", params [pind].value.i);
467 	    break;
468 	 case PFLOAT:
469 	    fprintf (output, "%.4f\n", (double) params [pind].value.f);
470 	    break;
471 	 case PSTR:
472 	 case POSTR:
473 	    fprintf (output, "%s\n", params [pind].value.s);
474 	    break;
475 	 default:
476 	    error ("Invalid type %d for parameter %s",
477 		   params [pind].type, params [pind].name);
478       }
479    }
480    fputc ('\n', output);
481 }
482 
483 /*****************************************************************************
484 
485 				private code
486 
487 *****************************************************************************/
488 
489 static void
set_parameter(param_t * parameter,const char * value)490 set_parameter (param_t *parameter, const char *value)
491 /*
492  *  Set value of 'parameter' to 'value'.
493  *
494  *  No return value.
495  *
496  *  Side effects:
497  *	'parameter.value' is changed accordingly
498  */
499 {
500    assert (parameter);
501 
502    switch (parameter->type)
503    {
504       case PFLAG:
505 	 if (value != NULL && *value != '\0')
506 	 {
507 	    if (strcaseeq (value, "TRUE"))
508 	       parameter->value.b = YES;
509 	    else if (strcaseeq (value, "FALSE"))
510 	       parameter->value.b = NO;
511 	    else if (strcaseeq (value, "YES"))
512 	       parameter->value.b = YES;
513 	    else if (strcaseeq (value, "NO"))
514 	       parameter->value.b = NO;
515 	    else
516 	    {
517 	       long int	data;
518 	       char	*endptr;
519 
520 	       data = strtol (value, &endptr, 0);
521 	       if (*endptr != '\0' || endptr == value)
522 		  warning ("Invalid value `%s' converted to %d",
523 			   value, (int) data);
524 	       parameter->value.b = data ? YES : NO;
525 	    }
526 	 }
527 	 else
528 	    parameter->value.b = !parameter->value.b;
529 	 break;
530       case PINT:
531 	 {
532 	    long int  data;
533 	    char     *endptr;
534 
535 	    data = strtol (value, &endptr, 0);
536 	    if (*endptr != '\0' || endptr == value)
537 	       warning ("Invalid value `%s' converted to %d",
538 			value, (int) data);
539 	    parameter->value.i = data;
540 	 }
541 	 break;
542       case PFLOAT:
543 	 {
544 	    double	data;
545 	    char	*endptr;
546 
547 	    data = strtod (value, &endptr);
548 	    if (*endptr != '\0' || endptr == value)
549 	       warning ("Invalid value `%s' converted to %f",
550 			value, (double) data);
551 	    parameter->value.f = data;
552 	 }
553 	 break;
554       case PSTR:
555       case POSTR:
556 	 parameter->value.s = value ? strdup (value) : NULL;
557 	 break;
558       default:
559 	 error ("Invalid parameter type for %s", parameter->name);
560    }
561 }
562 
563 static int
get_parameter_index(const param_t * params,const char * search_string)564 get_parameter_index (const param_t *params, const char *search_string)
565 /*
566  *  Search for parameter with name 'search_string' in parameter struct.
567  *
568  *  Return value:
569  *	index of parameter or -1 if no matching parameter has been found
570  */
571 {
572    int n;
573    int index = -1;
574 
575    assert (params && search_string);
576 
577    for (n = 0; params [n].name != NULL; n++)
578       if (strcaseeq (params [n].name, search_string))
579       {
580 	 index = n;
581 	 break;
582       }
583 
584    return index;
585 }
586 
587 static void
read_parameter_file(param_t * params,FILE * file)588 read_parameter_file (param_t *params, FILE *file)
589 /*
590  *  Read parameter settings from 'file'.
591  *
592  *  No return value.
593  *
594  *  Side effects:
595  *	'params [].value' are changed if specified in 'file'
596  */
597 {
598    char buffer [MAXSTRLEN];
599    int  n = 0;
600 
601    assert (params && file);
602 
603    while (fgets (buffer, MAXSTRLEN, file) != NULL)
604    {
605       char *b;				/* temporary variable */
606       char *name;			/* parameter name */
607       char *value;			/* parameter value */
608       int   pind;			/* current argument number */
609 
610       b = strchr (buffer, '#');
611       if (b != NULL)			/* Strip comments. */
612 	 *b = '\0';
613 
614       b = strchr (buffer, '=');
615       if (b == NULL)			/* Strip lines that contain no '=' */
616 	 continue;
617       *b = '\0';			/* Replace '=' by string terminator */
618 
619       /*
620        *  Extract value of parameter
621        */
622       for (value = b + 1; ISSPACE (*value); value++)
623 	 ;				/* Delete leading spaces */
624 
625       for (b = value + strlen (value) - 1; b >= value && ISSPACE (*b); b--)
626 	 *b = '\0';			/* Delete trailing spaces. */
627 
628       /*
629        *  Extract parameter name
630        */
631       for (name = buffer; ISSPACE (*name); name++)
632 	 ;				/* Delete leading spaces */
633 
634       for (b = name + strlen (name) - 1; b >= name && ISSPACE (*b); b--)
635 	 *b = '\0';			/* Delete trailing spaces. */
636 
637       pind = get_parameter_index (params, name);
638       if (pind >= 0)
639 	 set_parameter (&params [pind], value);
640 
641       n++;
642    }
643 }
644 
645 
646 
647 static void
usage(const param_t * params,const char * progname,const char * synopsis,const char * comment,const char * non_opt_string,bool_t show_all_options,const char * sys_file_name,const char * usr_file_name)648 usage (const param_t *params, const char *progname, const char *synopsis,
649        const char *comment, const char *non_opt_string,
650        bool_t show_all_options, const char *sys_file_name,
651        const char *usr_file_name)
652 /*
653  *  Generates and prints command line description from param_t struct 'params'.
654  *  'progname' is the name of the executable, 'synopsis' a short program
655  *  description, and 'comment' some more advice.
656  *  If flag 'show_all_options' is set then print also options that are not
657  *  associated with a short option character.
658  *  'sys_file_name' and 'usr_file_name' are filenames to parameter files.
659  *
660  *  No return value.
661  */
662 {
663     int	  i;
664     size_t width = 0;
665 
666     fprintf (stderr, "Usage: %s [OPTION]...%s\n", progname,
667              non_opt_string ? non_opt_string : " ");
668     if (synopsis != NULL)
669         fprintf (stderr, "%s", synopsis);
670     fprintf (stderr, "\n\n");
671     fprintf (stderr, "Mandatory or optional arguments to long options "
672              "are mandatory or optional\nfor short options too. "
673              "Default values are surrounded by {}.\n");
674     for (i = 0; params [i].name != NULL; i++)
675         if (params [i].optchar != '\0' || show_all_options)
676         {
677             if (params [i].type == POSTR)
678                 width = MAX(width, (strlen (params [i].name)
679                                      + strlen (params [i].argument_name) + 2));
680             else if (params [i].type != PFLAG)
681                 width = MAX(width, (strlen (params [i].name)
682                                      + strlen (params [i].argument_name)));
683             else
684                 width = MAX(width, (strlen (params [i].name)) - 1);
685         }
686 
687     for (i = 0; params [i].name != NULL; i++)
688         if (params [i].optchar != '\0' || show_all_options)
689         {
690             if (params [i].optchar != '\0')
691                 fprintf (stderr, "  -%c, --", params [i].optchar);
692             else
693                 fprintf (stderr, "      --");
694 
695             if (params [i].type == POSTR)
696                 fprintf (stderr, "%s=[%s]%-*s  ", params [i].name,
697                          params [i].argument_name,
698                          (unsigned)
699                          MAX(0, (width - 2 - strlen (params [i].name)
700                                  - strlen (params [i].argument_name))), "");
701             else if (params [i].type != PFLAG)
702                 fprintf (stderr, "%s=%-*s  ", params [i].name,
703                          (unsigned)(width - strlen (params [i].name)),
704                          params [i].argument_name);
705             else
706                 fprintf (stderr, "%-*s  ",
707                          (unsigned)(width + 1), params [i].name);
708 
709             fprintf (stderr, params [i].use, params [i].argument_name);
710 
711             switch (params [i].type)
712             {
713             case PFLAG:
714                 break;
715             case PINT:
716                 fprintf (stderr, "{%d}", params [i].value.i);
717                 break;
718             case PFLOAT:
719                 fprintf (stderr, "{%.2f}", (double) params [i].value.f);
720                 break;
721             case PSTR:
722             case POSTR:
723                 if (params [i].value.s)
724                     fprintf (stderr, "{%s}", params [i].value.s);
725                 break;
726             default:
727                 error ("type %d for %s invalid",
728                        params [i].type, params [i].name);
729             }
730             fprintf (stderr, "\n");
731         }
732     fprintf (stderr, "\n");
733     fprintf (stderr, "Parameter initialization order:\n");
734     fprintf (stderr,
735              "1.) %s\n2.) $HOME/%s\t 3.) command line\t 4.) --config=file",
736              sys_file_name, usr_file_name);
737     fprintf (stderr, "\n\n");
738     if (comment != NULL)
739         fprintf (stderr, "%s\n", comment);
740 
741     exit (1);
742 }
743 
744