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 (¶ms [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 (¶ms [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