1 /*
2 ** Copyright (C) 2001-2020 by Carnegie Mellon University.
3 **
4 ** @OPENSOURCE_LICENSE_START@
5 ** See license information in ../../LICENSE.txt
6 ** @OPENSOURCE_LICENSE_END@
7 */
8 
9 /*
10 **  sku-options.c
11 **
12 **  Suresh L Konda
13 **
14 **  12/4/2001
15 **
16 **  Routines to support long option parsing with multiple sets of options.
17 **
18 **  Four functions are exported:
19 **  skOptionsSetup();
20 **  skOptionsTeardown();
21 **  skOptionsRegister();
22 **  skOptionsParse();
23 **
24 **  Each client calls skOptionsRegister with:
25 **      1. a pointer to struct option []
26 **      2. a handler to process the option. The handler will be called with two
27 **         arguments:
28 **              1. the clientData
29 **              2. the original val value passed to the registry via
30 **                 options associated with this option
31 **              3. the optarg returned by getopt();
32 **      3. an opaque pointer to arbitrary data called clientData which
33 **      Error Return : 1
34 **
35 **  Once all clients have registered, then call skOptionsParse with argc, argv
36 **  which parses the options and calls the handler as required.
37 **
38 **  It returns -1 on error or optind if OK.  Thus, argv[optind] is the first
39 **  non-option argument given to the application.
40 **
41 **  Currently, we do NOT do flag versus val handling: flag is always
42 **  assumed to be NULL and val is the appropriate unique entity that
43 **  allows the handler to deal with the option to be parsed.  It is
44 **  suggested that the caller use a distinct index value in the val part.
45 **
46 **
47 */
48 
49 #include <silk/silk.h>
50 
51 RCSIDENT("$SiLK: sku-options.c ef14e54179be 2020-04-14 21:57:45Z mthomas $");
52 
53 #include <silk/utils.h>
54 #include <silk/sksite.h>
55 #include <silk/silk_files.h>
56 #include <silk/skstringmap.h>
57 
58 
59 /* TYPEDEFS AND DEFINES */
60 
61 /* Where to write --version information */
62 #define VERS_FH stdout
63 
64 /* Start options at this offset to avoid having an option with index
65  * of '?' (63) which is the value used to indicate an error. */
66 #define OPTION_OFFSET 64
67 
68 /* Initial size of options arrays, and number of options to add to the
69  * arrays */
70 #define OPTION_ARRAY_NUM_ENTRIES  16
71 
72 /* Message to print when out of memory */
73 #define SK_OPTION_NO_MEMORY(nomem_obj)                          \
74     skAppPrintOutOfMemory(#nomem_obj)
75 
76 /* Name of environment variable containing the default value for the
77  * --ip-format switch */
78 #define SK_IP_FORMAT_ENVAR              "SILK_IP_FORMAT"
79 
80 /* Name of environment variable containing the default value for the
81  * --timestamp-format switch */
82 #define SK_TIMESTAMP_FORMAT_ENVAR       "SILK_TIMESTAMP_FORMAT"
83 
84 
85 /*
86  *  struct option has the following definition:
87  *
88  *  struct option {
89  *      char *name;
90  *      int has_arg;
91  *      int *flag;
92  *      int val;
93  *  };
94  *
95  */
96 
97 typedef struct sk_options_map_st {
98     /* the callback function provided by the caller */
99     optHandler    om_handler;
100     /* the callback data provided by the caller */
101     clientData    om_data;
102     /* the index provided by the caller*/
103     int           om_index;
104 } sk_options_map_t;
105 
106 typedef struct sk_options_st {
107     /* function to use to print usage */
108     usage_fn_t          o_usage_fn;
109     /* for printing version info */
110     usage_fn_t          o_version_fn;
111     /* array of all options for this app */
112     struct option      *o_options;
113     /* array mapping options to a particular options handler */
114     sk_options_map_t   *o_map;
115     /* global option count */
116     size_t              o_count;
117     /* size of the arrays */
118     size_t              o_capacity;
119 } sk_options_t;
120 
121 
122 
123 /* LOCAL VARIABLES */
124 
125 static sk_options_t app_options_static;
126 
127 static sk_options_t *app_options = &app_options_static;
128 
129 typedef enum {
130     OPT_VAL_HELP, OPT_VAL_VERSION
131 } defaultOptionsEnum;
132 
133 /* options that everyone gets */
134 static struct option defaultOptions[] = {
135     {"help",        NO_ARG,       0, OPT_VAL_HELP},
136     {"version",     NO_ARG,       0, OPT_VAL_VERSION},
137     {0,0,0,0}       /* sentinel */
138 };
139 
140 static const char *defaultHelp[] = {
141     "Print this usage output and exit. Def. No",
142     "Print this program's version and exit. Def. No",
143     (char*)NULL /* sentinel */
144 };
145 
146 /* All shortened forms of help should invoke help.  This lets us
147  * define options like --help-foo and --help-bar. */
148 static const struct option optionAliases[] = {
149     {"hel" ,        NO_ARG,       0, OPT_VAL_HELP},
150     {"he",          NO_ARG,       0, OPT_VAL_HELP},
151     {"h",           NO_ARG,       0, OPT_VAL_HELP},
152     {0,0,0,0}       /* sentinel */
153 };
154 
155 
156 /* FUNCTION DEFINITONS */
157 
158 void
skOptionsDefaultUsage(FILE * fh)159 skOptionsDefaultUsage(
160     FILE               *fh)
161 {
162     int i;
163     for (i = 0; defaultOptions[i].name; ++i) {
164         fprintf(fh, "--%s %s. %s\n", defaultOptions[i].name,
165                 SK_OPTION_HAS_ARG(defaultOptions[i]), defaultHelp[i]);
166     }
167 }
168 
169 
170 /*
171  *  printVersion();
172  *
173  *    Print version information and information about how SiLK was
174  *    configured.
175  */
176 static void
printVersion(void)177 printVersion(
178     void)
179 {
180 #define COPYRIGHT_LICENSE                                       \
181     ("Copyright (C) 2001-2020 by Carnegie Mellon University\n"  \
182      "GNU General Public License (GPL) Rights"                  \
183      " pursuant to Version 2, June 1991.\n"                     \
184      "Some included library code covered by LGPL 2.1;"          \
185      " see source for details.\n"                               \
186      "Government Purpose License Rights (GPLR)"                 \
187      " pursuant to DFARS 252.227-7013.")
188 
189     uint8_t default_compmethod;
190     uint8_t i;
191     char comp_name[SK_MAX_STRLEN_FILE_FORMAT+1];
192     const char *packing_logic;
193     const char *python_dir = SILK_PYTHON_SITE_PKG;
194 
195     fprintf(VERS_FH, "%s: part of %s %s; configuration settings:\n",
196             skAppName(), SK_PACKAGE_NAME, SK_PACKAGE_VERSION);
197 
198     fprintf(VERS_FH, "    * %-32s  %s\n",
199             "Root of packed data tree:", sksiteGetDefaultRootDir());
200 
201 #ifndef SK_PACKING_LOGIC_PATH
202     packing_logic = "Run-time plug-in";
203 #else
204     packing_logic = SK_PACKING_LOGIC_PATH;
205     if (strrchr(packing_logic, '/')) {
206         packing_logic = 1 + strrchr(packing_logic, '/');
207     }
208 #endif
209     fprintf(VERS_FH, "    * %-32s  %s\n",
210             "Packing logic:", packing_logic);
211 
212     fprintf(VERS_FH, "    * %-32s  %s\n",
213             "Timezone support:",
214 #if  SK_ENABLE_LOCALTIME
215             "local"
216 #else
217             "UTC"
218 #endif
219             );
220 
221     default_compmethod = skCompMethodGetDefault();
222     skCompMethodGetName(comp_name, sizeof(comp_name), default_compmethod);
223     fprintf(VERS_FH, "    * %-32s  %s [default]",
224             "Available compression methods:", comp_name);
225 
226     for (i = 0; skCompMethodCheck(i); ++i) {
227         if (i == default_compmethod) {
228             continue;
229         }
230         if (SK_COMPMETHOD_IS_AVAIL != skCompMethodCheck(i)) {
231             /* not available */
232             continue;
233         }
234         skCompMethodGetName(comp_name, sizeof(comp_name), i);
235         fprintf(VERS_FH, ", %s", comp_name);
236     }
237     fprintf(VERS_FH, "\n");
238 
239     fprintf(VERS_FH, "    * %-32s  %s\n",
240             "IPv6 network connections:",
241 #if SK_ENABLE_INET6_NETWORKING
242             "yes"
243 #else
244             "no"
245 #endif
246             );
247 
248     fprintf(VERS_FH, "    * %-32s  %s\n",
249             "IPv6 flow record support:",
250 #if SK_ENABLE_IPV6
251             "yes"
252 #else
253             "no"
254 #endif
255             );
256 
257     fprintf(VERS_FH, "    * %-32s  %s\n",
258             "IPset record compatibility:",
259 #if !defined(SK_IPSET_DEFAULT_VERSION)
260             "1.0.0"
261 #elif SK_IPSET_DEFAULT_VERSION == 5
262             "3.14.0"
263 #elif SK_IPSET_DEFAULT_VERSION == 4
264             "3.7.0"
265 #else
266             "1.0.0"
267 #endif
268             );
269 
270     fprintf(VERS_FH, "    * %-32s  %s\n",
271             "IPFIX/NetFlow9/sFlow collection:",
272 #if   SK_ENABLE_IPFIX
273             "ipfix,netflow9,sflow"
274 #else
275             "no"
276 #endif
277             );
278 
279 
280     fprintf(VERS_FH, "    * %-32s  %s\n",
281             "Transport encryption:",
282 #if SK_ENABLE_GNUTLS
283             "GnuTLS"
284 #else
285             "no"
286 #endif
287             );
288 
289     fprintf(VERS_FH, "    * %-32s  %s\n",
290             "PySiLK support:", ((python_dir[0]) ? python_dir : "no"));
291 
292     fprintf(VERS_FH, "    * %-32s  %s\n",
293             "Enable assert():",
294 #ifndef NDEBUG
295             "yes"
296 #else
297             "no"
298 #endif
299             );
300 
301     fprintf(VERS_FH,
302             ("%s\n"
303              "Send bug reports, feature requests, and comments to %s.\n"),
304             COPYRIGHT_LICENSE, SK_PACKAGE_BUGREPORT);
305 }
306 
307 
308 /*
309  *  status = defaultOptionsHandler(cData, opt_index, opt_arg);
310  *
311  *    Called by skOptionsParse() to handle the default/global options
312  *    defined in the defaultOptions[] array.  This handler will exit
313  *    the application.
314  */
315 static int
defaultOptionsHandler(clientData UNUSED (cData),int opt_index,char UNUSED (* opt_arg))316 defaultOptionsHandler(
317     clientData   UNUSED(cData),
318     int                 opt_index,
319     char        UNUSED(*opt_arg))
320 {
321     switch ((defaultOptionsEnum)opt_index) {
322       case OPT_VAL_HELP:
323         app_options->o_usage_fn();
324         break;
325 
326       case OPT_VAL_VERSION:
327         app_options->o_version_fn();
328         break;
329     }
330 
331     skAppUnregister();
332     exit(EXIT_SUCCESS);
333     return 0; /* NOTREACHED */
334 }
335 
336 
337 static void
defaultHelpOutput(void)338 defaultHelpOutput(
339     void)
340 {
341     skAppStandardUsage(stdout, "", NULL, NULL);
342 }
343 
344 
345 void
skOptionsSetup(void)346 skOptionsSetup(
347     void)
348 {
349     /* check whether already called */
350     if (app_options->o_usage_fn) {
351         return;
352     }
353 
354     /* tell getopt_long() that it should print errors */
355     opterr = 1;
356 
357     /* set a default usage function */
358     skOptionsSetUsageCallback(&defaultHelpOutput);
359 
360     /* set the version function */
361     skOptionsSetVersionCallback(&printVersion);
362 
363     /* allocate initial space */
364     app_options->o_options = (struct option*)calloc(OPTION_ARRAY_NUM_ENTRIES,
365                                                     sizeof(struct option));
366     app_options->o_map = (sk_options_map_t*)calloc(OPTION_ARRAY_NUM_ENTRIES,
367                                                    sizeof(sk_options_map_t));
368     if (!app_options->o_options || !app_options->o_map) {
369         SK_OPTION_NO_MEMORY(app_options->o_options);
370         exit(EXIT_FAILURE);
371     }
372 
373     app_options->o_count = 0;
374     app_options->o_capacity = OPTION_ARRAY_NUM_ENTRIES;
375 
376     /* add default switches */
377     if (skOptionsRegister(defaultOptions, defaultOptionsHandler, NULL)) {
378         skAppPrintErr("Unable to set default options");
379         exit(EXIT_FAILURE);
380     }
381     if (skOptionsRegister(optionAliases, defaultOptionsHandler, NULL)) {
382         skAppPrintErr("Unable to set default options");
383         exit(EXIT_FAILURE);
384     }
385 }
386 
387 
388 void
skOptionsSetUsageCallback(usage_fn_t help_fn)389 skOptionsSetUsageCallback(
390     usage_fn_t          help_fn)
391 {
392     app_options->o_usage_fn = help_fn;
393 }
394 
395 
396 void
skOptionsSetVersionCallback(usage_fn_t version_fn)397 skOptionsSetVersionCallback(
398     usage_fn_t          version_fn)
399 {
400     app_options->o_version_fn = version_fn;
401 }
402 
403 
404 void
skOptionsTeardown(void)405 skOptionsTeardown(
406     void)
407 {
408     if ( app_options->o_options == 0) {
409         return;
410     }
411     free(app_options->o_options);
412     free(app_options->o_map);
413     app_options->o_options = /* (struct option (*)[1]) */ NULL;
414     app_options->o_map = /* (sk_options_map (*)[1]) */ NULL;
415     return;
416 }
417 
418 
419 int
skOptionsRegister(const struct option * options,optHandler handler,clientData cData)420 skOptionsRegister(
421     const struct option    *options,
422     optHandler              handler,
423     clientData              cData)
424 {
425     return skOptionsRegisterCount(options, 0, handler, cData);
426 }
427 
428 
429 int
skOptionsRegisterCount(const struct option * new_options,size_t num_options,optHandler handler,clientData cData)430 skOptionsRegisterCount(
431     const struct option    *new_options,
432     size_t                  num_options,
433     optHandler              handler,
434     clientData              cData)
435 {
436     struct option *cur_options;
437     size_t i;
438     size_t j;
439     size_t new_capacity;
440     void *old_mem;
441 
442     if (app_options->o_usage_fn == NULL) {
443         skAppPrintErr("Must call skOptionsSetup() before registering options");
444         return -1;
445     }
446 
447     /* count the options that were passed in */
448     if (num_options == 0) {
449         for (j = 0; new_options[j].name; ++j)
450             ;
451         num_options = j;
452     } else {
453         for (j = 0; j < num_options && new_options[j].name; ++j)
454             ;
455         num_options = j;
456     }
457 
458     if (0 == num_options) {
459         /* empty options list */
460         return 0;
461     }
462 
463     /*  New total number of options will be the current value plus the
464      *  number of options passed in.  */
465     new_capacity = app_options->o_count + num_options;
466 
467     /* Determine whether we need to grow the arrays.  The capacity
468      * must remain greater than the count, because we need to have one
469      * blank space for the sentinel. */
470     if (new_capacity >= app_options->o_capacity) {
471 
472         /* allow space for several additional entries */
473         new_capacity += OPTION_ARRAY_NUM_ENTRIES;
474 
475         /*
476          *  Get or grow the space for the arrays.
477          */
478         old_mem = app_options->o_options;
479         app_options->o_options
480             = (struct option*)realloc(app_options->o_options,
481                                       (new_capacity * sizeof(struct option)));
482         if (app_options->o_options == NULL) {
483             app_options->o_options = (struct option*)old_mem;
484             SK_OPTION_NO_MEMORY(app_options->o_options);
485             return -1;
486         }
487 
488         old_mem = app_options->o_map;
489         app_options->o_map
490             = (sk_options_map_t*)realloc(app_options->o_map,
491                                          (new_capacity
492                                           * sizeof(sk_options_map_t)));
493         if (app_options->o_map == NULL) {
494             app_options->o_map = (sk_options_map_t*)old_mem;
495             SK_OPTION_NO_MEMORY(app_options->o_map);
496             return -1;
497         }
498 
499         app_options->o_capacity = new_capacity;
500     }
501 
502     for (j = 0; j < num_options; ++j, ++new_options) {
503         /* check for name clashes */
504         for (i = 0, cur_options = app_options->o_options;
505              i < app_options->o_count;
506              ++i, ++cur_options)
507         {
508             if (strcmp(cur_options->name, new_options->name)==0) {
509                 skAppPrintErr("Cannot register option '%s': name already used",
510                               new_options->name);
511                 return -1;
512             }
513         }
514 
515         assert(cur_options == &app_options->o_options[app_options->o_count]);
516 
517         /* a clean new entry. record it. */
518         cur_options->name    = new_options->name;
519         cur_options->has_arg = new_options->has_arg;
520         cur_options->flag    = new_options->flag;
521 
522         /* the 'val' used internally is the OPTION_OFFSET plus the
523          * index into the 'o_map' array; the o_map array will be used to
524          * get the 'val' the called handed us. */
525         cur_options->val     = OPTION_OFFSET + app_options->o_count;
526 
527         /* original val to be returned with handler */
528         app_options->o_map[app_options->o_count].om_index   = new_options->val;
529         app_options->o_map[app_options->o_count].om_handler = handler;
530         app_options->o_map[app_options->o_count].om_data    = cData;
531 
532         ++app_options->o_count;
533     }
534 
535     /* set the sentinel for o_options */
536     memset(&app_options->o_options[app_options->o_count], 0,
537            sizeof(struct option));
538 
539     return 0;
540 }
541 
542 
543 /*
544  *  skOptionsParse:
545  *      Adjust the global options array to allow for the help
546  *      option. If help is selected by the user, call the stashed
547  *      usageFunction.  Parse input options given a set of
548  *      pre-registered options and their handlers.  For each
549  *      legitimate option, call the handler.
550  *  SideEffects:
551  *      The individual handlers update whatever datastruture they wish
552  *      to via the clientData argument to the handler.
553  *  Return:
554  *      optind which points at the first non-option argument passed if
555  *      all is OK.  If not OK, the return -1 for error.
556  */
557 int
skOptionsParse(int argc,char ** argv)558 skOptionsParse(
559     int                 argc,
560     char              **argv)
561 {
562     int done = 0;
563     int c;
564     int idx;
565 
566     while (! done) {
567         int option_index;
568 #ifdef SK_HAVE_GETOPT_LONG_ONLY
569         c = getopt_long_only(argc, argv, "",
570                              (const struct option *)app_options->o_options,
571                              &option_index);
572 #else
573         c = _getopt_internal(argc, argv, "",
574                              (const struct option *)app_options->o_options,
575                              &option_index, 1);
576 #endif
577         switch (c) {
578 
579           case '?':
580             /*skAppPrintErr("Invalid or ambiguous option"); */
581             return -1;
582 
583           case -1:
584             done = 1;
585             break;
586 
587           default:
588             /* a legit value: call the handler */
589             idx = c - OPTION_OFFSET;
590             if (app_options->o_map[idx].om_handler(
591                     app_options->o_map[idx].om_data,
592                     app_options->o_map[idx].om_index,
593                     optarg))
594             {
595                 /* handler indicated an error */
596                 return -1;
597             }
598             break;
599         }
600     }
601 
602     return optind;
603 }
604 
605 
606 /* find shortest unique prefix for the option 'option_name' */
607 int
skOptionsGetShortestPrefix(const char * option_name)608 skOptionsGetShortestPrefix(
609     const char         *option_name)
610 {
611     struct option *opt = NULL;
612     const char *cp;
613     const char *sp;
614     int longest = 0;
615     size_t i;
616     int j;
617 
618     /* check that the input inupt */
619     if (option_name == NULL || option_name[0] == '\0') {
620         return -1;
621     }
622 
623     /* find 'option_name' in the list of all options */
624     for (i = 0, opt = app_options->o_options;
625          i < app_options->o_count;
626          ++i, ++opt)
627     {
628         if (0 == strcmp(option_name, opt->name)) {
629             break;
630         }
631     }
632 
633     if (i == app_options->o_count) {
634         /* did not find 'option_name' in the list of options, or no
635          * options have been registered. */
636         return -1;
637     }
638 
639     for (i = 0; i < app_options->o_count; ++i) {
640         if (opt->val == app_options->o_options[i].val) {
641             /* skip options that map to same value as 'option_name' */
642             continue;
643         }
644 
645         /* find the character where the strings differ */
646         for (j = 1, cp = option_name, sp = app_options->o_options[i].name;
647              *cp && *sp && *cp == *sp;
648              ++j, ++cp, ++sp)
649             ;  /* empty */
650 
651         if (*cp == '\0') {
652             /* reached end of option_name.  if *sp is NUL, we have
653              * matched ourself, which we should have avoided
654              * above. */
655             assert(*sp != '\0');
656 
657             /* since option_name is a substring of
658              * o_options[].name, the full option name is always
659              * required. */
660             return j;
661         }
662 
663         if (j > longest) {
664             longest = j;
665         }
666     }
667 
668     return longest;
669 }
670 
671 
672 /* check whether dirname exists */
673 int
skOptionsCheckDirectory(const char * dirname,const char * option_name)674 skOptionsCheckDirectory(
675     const char         *dirname,
676     const char         *option_name)
677 {
678     if (!dirname || !dirname[0]) {
679         skAppPrintErr("Invalid %s: The directory name is empty",
680                       option_name);
681         return -1;
682     }
683     if (strlen(dirname)+1 >= PATH_MAX) {
684         skAppPrintErr("Invalid %s: The directory name is too long",
685                       option_name);
686         return -1;
687     }
688     if (!skDirExists(dirname)) {
689         skAppPrintErr("Invalid %s: Nonexistent path '%s'",
690                       option_name, dirname);
691         return -1;
692     }
693     if (dirname[0] != '/') {
694         skAppPrintErr(("Invalid %s: Must use complete path"
695                        " ('%s' does not begin with slash)"),
696                       option_name, dirname);
697         return -1;
698     }
699     return 0;
700 }
701 
702 
703 #if 0
704 /* verify argument contains printable characters other than space */
705 int
706 skOptionsCheckContainsPrintable(
707     const char         *opt_argument,
708     const char         *option_name)
709 {
710     const char *cp;
711 
712     if (opt_argument) {
713         cp = opt_argument;
714         while (*cp && (!isprint((int)*cp) || *cp == ' ')) {
715             ++cp;
716         }
717         if (*cp == '\0') {
718             skAppPrintErr(("Invalid %s: Argument does not contain printable"
719                            " characters"),
720                           option_name);
721             return -1;
722         }
723     }
724     return 0;
725 }
726 #endif  /* 0 */
727 
728 
729 /* *******************************************************************
730  *    Support for setting the temporary directory
731  */
732 
733 static struct option tempdir_option[] = {
734     {"temp-directory",      REQUIRED_ARG, 0, 0},
735     {0,0,0,0}               /* sentinel */
736 };
737 
738 static int
tempdir_option_handler(clientData cData,int UNUSED (opt_index),char * opt_arg)739 tempdir_option_handler(
740     clientData          cData,
741     int          UNUSED(opt_index),
742     char               *opt_arg)
743 {
744     const char **var_location = (const char**)cData;
745 
746     assert(opt_index == 0);
747     assert(opt_arg);
748     *var_location = opt_arg;
749     return 0;
750 }
751 
752 int
skOptionsTempDirRegister(const char ** var_location)753 skOptionsTempDirRegister(
754     const char        **var_location)
755 {
756     if (var_location == NULL) {
757         return -1;
758     }
759     return skOptionsRegister(tempdir_option, tempdir_option_handler,
760                              (clientData)var_location);
761 }
762 
763 void
skOptionsTempDirUsage(FILE * fh)764 skOptionsTempDirUsage(
765     FILE               *fh)
766 {
767     fprintf(fh,
768             ("--%s %s. Store temporary files in this directory.\n"
769              "\tDef. $" SK_TEMPDIR_ENVAR1 " or $" SK_TEMPDIR_ENVAR2
770 #ifdef SK_TEMPDIR_DEFAULT
771              " or " SK_TEMPDIR_DEFAULT
772 #endif
773              "\n"),
774             tempdir_option[0].name, SK_OPTION_HAS_ARG(tempdir_option[0]));
775 }
776 
777 
778 
779 /* *******************************************************************
780  *    Support for formatting IP addresses
781  */
782 
783 /* flags passed to skOptionsIPFormatRegister() that determines what
784  * switches to enable */
785 static uint32_t ip_format_flags = 0;
786 
787 /* some values in ipformat_names[] may not be combined. this array
788  * holds values used to check for invalid combinations.  Each entry is
789  * two 16 bit values where the lower bits indicate the parameter and
790  * the upper 16 bits are the mask of values it conflicts with.  */
791 static const uint32_t ip_format_param_group[] = {
792     /* bits | mask */
793     0x0001 | (0x000F << 16),  /* canonical   */
794     0x0002 | (0x000F << 16),  /* decimal     */
795     0x0004 | (0x000F << 16),  /* hexadecimal */
796     0x0008 | (0x000F << 16),  /* no-mixed    */
797     0x0000 | (0x0000 << 16),  /* zero-padded */
798     0x0010 | (0x0030 << 16),  /* map-v4      */
799     0x0020 | (0x0030 << 16),  /* unmap-v6    */
800     0x0018 | (0x003F << 16)   /* force-ipv6  */
801 };
802 
803 
804 enum ipformat_option_en {
805     OPT_VAL_IP_FORMAT, OPT_VAL_INTEGER_IPS, OPT_VAL_ZERO_PAD_IPS
806 };
807 
808 static const struct option ipformat_option[] = {
809     {"ip-format",           REQUIRED_ARG, 0, OPT_VAL_IP_FORMAT},
810     {"integer-ips",         NO_ARG,       0, OPT_VAL_INTEGER_IPS},
811     {"zero-pad-ips",        NO_ARG,       0, OPT_VAL_ZERO_PAD_IPS},
812     {0,0,0,0}               /* sentinel */
813 };
814 
815 /* printed IP address formats: the first of these will be the default */
816 static const sk_stringmap_entry_t ipformat_names[] = {
817     {"canonical",       SKIPADDR_CANONICAL,
818      "in canonical format (192.0.2.1, 2001:db8::1)",
819      &ip_format_param_group[0]},
820     {"decimal",         SKIPADDR_DECIMAL,
821      "as integer number in decimal format",
822      &ip_format_param_group[1]},
823     {"hexadecimal",     SKIPADDR_HEXADECIMAL,
824      "as integer number in hexadecimal format",
825      &ip_format_param_group[2]},
826     {"no-mixed",        SKIPADDR_NO_MIXED,
827      "in canonical format but no mixed IPv4/IPv6 for IPv6 IPs",
828      &ip_format_param_group[3]},
829     {"zero-padded",     SKIPADDR_ZEROPAD,
830      "pad result to its maximum width with zeros",
831      &ip_format_param_group[4]},
832     {"map-v4",          SKIPADDR_MAP_V4,
833      "map IPv4 into ::ffff:0:0/96 netblock prior to formatting",
834      &ip_format_param_group[5]},
835     {"unmap-v6",        SKIPADDR_UNMAP_V6,
836      "convert IPv6 in ::ffff:0:0/96 to IPv4 prior to formatting",
837      &ip_format_param_group[6]},
838     {"force-ipv6",      SKIPADDR_FORCE_IPV6,
839      "alias equivalent to \"map-v4,no-mixed\"",
840      &ip_format_param_group[7]},
841     SK_STRINGMAP_SENTINEL
842 };
843 
844 
845 /*
846  *    If the SK_OPTION_IP_FORMAT_UNMAP_V6 flag was passed to
847  *    skOptionsIPFormatRegister(), enable unmap-v6 (SKIPADDR_UNMAP_V6)
848  *    in the in the ip formatting flags unless the user selected
849  *    decimal or hexadecimal as the format, or the user specified
850  *    map-v4 (SKIPADDR_MAP_V4).
851  */
852 static void
ipformat_check_unmapv6(uint32_t * out_flags)853 ipformat_check_unmapv6(
854     uint32_t           *out_flags)
855 {
856     if (ip_format_flags & SK_OPTION_IP_FORMAT_UNMAP_V6) {
857         switch (*out_flags & 0x7f) {
858           case SKIPADDR_DECIMAL:
859           case SKIPADDR_HEXADECIMAL:
860             break;
861           default:
862             if (0 == (SKIPADDR_MAP_V4 & *out_flags)) {
863                 *out_flags |= SKIPADDR_UNMAP_V6;
864             }
865             break;
866         }
867     }
868 }
869 
870 
871 /*
872  *  status = ipformat_option_parse(format_string, out_flags);
873  *
874  *    Parse the ip-format value contained in 'format_string' and set
875  *    'out_flags' to the result of parsing the string.  Return 0 on
876  *    success, or -1 if parsing of the value fails.
877  */
878 static int
ipformat_option_parse(const char * format,uint32_t * out_flags,const char * option_name)879 ipformat_option_parse(
880     const char         *format,
881     uint32_t           *out_flags,
882     const char         *option_name)
883 {
884     char *errmsg;
885     sk_stringmap_t *str_map = NULL;
886     sk_stringmap_iter_t *iter = NULL;
887     sk_stringmap_entry_t *found_entry;
888     const sk_stringmap_entry_t *entry;
889     uint32_t groups_seen = 0;
890     uint32_t bits;
891     uint32_t mask;
892     int rv = -1;
893 
894     assert(sizeof(ip_format_param_group)/sizeof(ip_format_param_group[0])
895            == (sizeof(ipformat_names)/sizeof(ipformat_names[0]) - 1));
896 
897     /* create a stringmap of the available ip formats */
898     if (SKSTRINGMAP_OK != skStringMapCreate(&str_map)) {
899         skAppPrintOutOfMemory(NULL);
900         goto END;
901     }
902     if (skStringMapAddEntries(str_map, -1, ipformat_names) != SKSTRINGMAP_OK){
903         skAppPrintOutOfMemory(NULL);
904         goto END;
905     }
906 
907     /* attempt to match */
908     if (skStringMapParse(str_map, format, SKSTRINGMAP_DUPES_ERROR,
909                          &iter, &errmsg))
910     {
911         skAppPrintErr("Invalid %s: %s", option_name, errmsg);
912         goto END;
913     }
914 
915     *out_flags = 0;
916 
917     while (skStringMapIterNext(iter, &found_entry, NULL) == SK_ITERATOR_OK) {
918         bits = 0xFFFF & *(uint32_t *)found_entry->userdata;
919         mask = (*(uint32_t *)found_entry->userdata) >> 16;
920         /* check whether have seen another argument in this group */
921         if (groups_seen & (mask & ~bits)) {
922             /* yes, we have; generate error msg and return */
923             char buf[256] = "";
924             int first = 1;
925             for (entry = ipformat_names; entry->name; ++entry) {
926                 uint32_t b = 0xFFFF & *(uint32_t *)entry->userdata;
927                 if (b & (groups_seen & mask)) {
928                     if (first) {
929                         first = 0;
930                     } else {
931                         strncat(buf, ",", sizeof(buf)-strlen(buf)-1);
932                     }
933                     strncat(buf, entry->name, sizeof(buf)-strlen(buf)-1);
934                 }
935             }
936             skAppPrintErr("Invalid %s: May not combine %s with %s",
937                           option_name, found_entry->name, buf);
938             goto END;
939         }
940         groups_seen |= bits;
941         *out_flags |= found_entry->id;
942     }
943 
944     ipformat_check_unmapv6(out_flags);
945 
946     rv = 0;
947 
948   END:
949     skStringMapDestroy(str_map);
950     skStringMapIterDestroy(iter);
951     return rv;
952 }
953 
954 static int
ipformat_option_handler(clientData cData,int opt_index,char * opt_arg)955 ipformat_option_handler(
956     clientData          cData,
957     int                 opt_index,
958     char               *opt_arg)
959 {
960     uint32_t *var_location = (uint32_t*)cData;
961 
962     switch ((enum ipformat_option_en)opt_index) {
963       case OPT_VAL_IP_FORMAT:
964         if (ipformat_option_parse(
965                 opt_arg, var_location, ipformat_option[opt_index].name))
966         {
967             return 1;
968         }
969         break;
970       case OPT_VAL_INTEGER_IPS:
971         assert(ip_format_flags & SK_OPTION_IP_FORMAT_INTEGER_IPS);
972         if (ipformat_option_parse(
973                 "decimal", var_location, ipformat_option[opt_index].name))
974         {
975             skAbort();
976         }
977         break;
978       case OPT_VAL_ZERO_PAD_IPS:
979         assert(ip_format_flags & SK_OPTION_IP_FORMAT_ZERO_PAD_IPS);
980         if (ipformat_option_parse(
981                 "zero-padded", var_location, ipformat_option[opt_index].name))
982         {
983             skAbort();
984         }
985         break;
986     }
987 
988     return 0;
989 }
990 
991 int
skOptionsIPFormatRegister(uint32_t * var_location,uint32_t flags)992 skOptionsIPFormatRegister(
993     uint32_t           *var_location,
994     uint32_t            flags)
995 {
996     struct option opts[2];
997     const char *env;
998     uint32_t tmp_val = 0;
999     unsigned int i;
1000     int rv = 0;
1001 
1002     if (var_location == NULL) {
1003         return -1;
1004     }
1005 
1006     ip_format_flags = flags;
1007     ipformat_check_unmapv6(var_location);
1008 
1009     env = getenv(SK_IP_FORMAT_ENVAR);
1010     if (env && env[0]) {
1011         if (0 == ipformat_option_parse(env, &tmp_val, SK_IP_FORMAT_ENVAR)) {
1012             *var_location = tmp_val;
1013         }
1014     }
1015 
1016     memset(opts, 0, sizeof(opts));
1017 
1018     for (i = 0; ipformat_option[i].name; ++i) {
1019         if ((0 == i) || (ip_format_flags & (1 << (i - 1)))) {
1020             memcpy(opts, &ipformat_option[i], sizeof(struct option));
1021             rv = skOptionsRegister(opts, ipformat_option_handler,
1022                                    (clientData)var_location);
1023             if (rv) {
1024                 return rv;
1025             }
1026         }
1027     }
1028     return rv;
1029 }
1030 
1031 
1032 /*
1033  *  skOptionsIPFormatUsage(fh);
1034  *
1035  *    Print the description of the argument to the --ip-format
1036  *    switch to the 'fh' file handle.
1037  */
1038 void
skOptionsIPFormatUsage(FILE * fh)1039 skOptionsIPFormatUsage(
1040     FILE               *fh)
1041 {
1042     const sk_stringmap_entry_t *e;
1043     char defaults[256] = "";
1044 
1045     if (0 == (ip_format_flags & SK_OPTION_IP_FORMAT_UNMAP_V6)) {
1046         strncpy(defaults, ipformat_names[0].name, sizeof(defaults));
1047     } else {
1048         for (e = ipformat_names; e->name; ++e) {
1049             if (e->id == SKIPADDR_UNMAP_V6) {
1050                 snprintf(defaults, sizeof(defaults), "%s,%s",
1051                          ipformat_names[0].name, e->name);
1052                 break;
1053             }
1054         }
1055     }
1056     assert(defaults[0]);
1057 
1058     fprintf(fh, ("--%s %s. Print each IP address in the specified format.\n"
1059                  "\tDef. $" SK_IP_FORMAT_ENVAR " or %s.  Choices:\n"),
1060             ipformat_option[OPT_VAL_IP_FORMAT].name,
1061             SK_OPTION_HAS_ARG(ipformat_option[OPT_VAL_IP_FORMAT]), defaults);
1062     for (e = ipformat_names; e->name; ++e) {
1063         if (e->id == SKIPADDR_ZEROPAD) {
1064             fprintf(fh, "\tThe following may be combined with the above:\n");
1065         }
1066         fprintf(fh, "\t%-11s - %s\n", e->name, e->description);
1067     }
1068 
1069     if (ip_format_flags & SK_OPTION_IP_FORMAT_INTEGER_IPS) {
1070         fprintf(fh, "--%s %s. DEPRECATED. Equivalent to --ip-format=decimal\n",
1071                 ipformat_option[OPT_VAL_INTEGER_IPS].name,
1072                 SK_OPTION_HAS_ARG(ipformat_option[OPT_VAL_INTEGER_IPS]));
1073     }
1074     if (ip_format_flags & SK_OPTION_IP_FORMAT_ZERO_PAD_IPS) {
1075         fprintf(fh,
1076                 "--%s %s. DEPRECATED. Equivalent to --ip-format=zero-padded\n",
1077                 ipformat_option[OPT_VAL_ZERO_PAD_IPS].name,
1078                 SK_OPTION_HAS_ARG(ipformat_option[OPT_VAL_ZERO_PAD_IPS]));
1079     }
1080 }
1081 
1082 
1083 /* *******************************************************************
1084  *    Support for formatting Timestamps
1085  */
1086 
1087 static uint32_t time_format_flags = 0;
1088 
1089 static char time_format_epoch_name[256];
1090 
1091 enum time_format_option_en {
1092     OPT_VAL_TIMESTAMP_FORMAT, OPT_VAL_EPOCH_TIME, OPT_VAL_LEGACY_TIMESTAMPS
1093 };
1094 
1095 static const struct option time_format_option[] = {
1096     {"timestamp-format",    REQUIRED_ARG, 0, OPT_VAL_TIMESTAMP_FORMAT},
1097     {"epoch-time",          NO_ARG,       0, OPT_VAL_EPOCH_TIME},
1098     {"legacy-timestamps",   OPTIONAL_ARG, 0, OPT_VAL_LEGACY_TIMESTAMPS},
1099     {0,0,0,0}               /* sentinel */
1100 };
1101 
1102 /* timestamp formats: the first of these will be the default */
1103 static const sk_stringmap_entry_t time_format_names[] = {
1104     {"default", 0,                      "yyyy/mm/ddThh:mm:ss", NULL},
1105     {"iso",     SKTIMESTAMP_ISO,        "yyyy-mm-dd hh:mm:ss", NULL},
1106     {"m/d/y",   SKTIMESTAMP_MMDDYYYY,   "mm/dd/yyyy hh:mm:ss", NULL},
1107     {"epoch",   SKTIMESTAMP_EPOCH,
1108      "seconds since UNIX epoch; ignores timezone", NULL},
1109     SK_STRINGMAP_SENTINEL
1110 };
1111 static const sk_stringmap_entry_t time_format_zones[] = {
1112     {"utc",     SKTIMESTAMP_UTC,        "use UTC", NULL},
1113     {"local",   SKTIMESTAMP_LOCAL,
1114      "use TZ environment variable or local timezone", NULL},
1115     SK_STRINGMAP_SENTINEL
1116 };
1117 static const sk_stringmap_entry_t time_format_misc[] = {
1118     {"no-msec", SKTIMESTAMP_NOMSEC,     "truncate milliseconds", NULL},
1119     SK_STRINGMAP_SENTINEL
1120 };
1121 
1122 /*
1123  *  status = time_format_option_parse(format_string, out_flags, from_environ);
1124  *
1125  *    Parse the timestamp-format value contained in 'format_string'
1126  *    and set 'out_flags' to the result of parsing the string.  Return
1127  *    0 on success, or -1 if parsing of the value fails.
1128  *
1129  *    If 'from_environ' is true, assume 'format' was set from an
1130  *    environment variable.
1131  */
1132 static int
time_format_option_parse(const char * format,uint32_t * out_flags,const char * option_name)1133 time_format_option_parse(
1134     const char         *format,
1135     uint32_t           *out_flags,
1136     const char         *option_name)
1137 {
1138     char buf[256];
1139     char *errmsg;
1140     sk_stringmap_t *str_map = NULL;
1141     sk_stringmap_iter_t *iter = NULL;
1142     sk_stringmap_entry_t *found_entry;
1143     const sk_stringmap_entry_t *entry;
1144     int name_seen = 0;
1145     int zone_seen = 0;
1146     int from_environ = 0;
1147     int rv = -1;
1148 
1149     if (option_name && 0 == strcmp(option_name, SK_TIMESTAMP_FORMAT_ENVAR)) {
1150         from_environ = 1;
1151     }
1152 
1153     /* create a stringmap of the available timestamp formats */
1154     if (SKSTRINGMAP_OK != skStringMapCreate(&str_map)) {
1155         skAppPrintOutOfMemory(NULL);
1156         goto END;
1157     }
1158     if (skStringMapAddEntries(str_map, -1, time_format_names)
1159         != SKSTRINGMAP_OK)
1160     {
1161         skAppPrintOutOfMemory(NULL);
1162         goto END;
1163     }
1164     if (skStringMapAddEntries(str_map, -1, time_format_zones)
1165         != SKSTRINGMAP_OK)
1166     {
1167         skAppPrintOutOfMemory(NULL);
1168         goto END;
1169     }
1170     if (from_environ
1171         || 0 == (time_format_flags & (SK_OPTION_TIMESTAMP_NEVER_MSEC
1172                                       | SK_OPTION_TIMESTAMP_ALWAYS_MSEC)))
1173     {
1174         if (skStringMapAddEntries(str_map, -1, time_format_misc)
1175             != SKSTRINGMAP_OK)
1176         {
1177             skAppPrintOutOfMemory(NULL);
1178             goto END;
1179         }
1180     }
1181 
1182     /* attempt to match */
1183     if (skStringMapParse(str_map, format, SKSTRINGMAP_DUPES_ERROR,
1184                          &iter, &errmsg))
1185     {
1186         skAppPrintErr("Invalid %s: %s", option_name, errmsg);
1187         goto END;
1188     }
1189 
1190     *out_flags = 0;
1191     if (time_format_flags & SK_OPTION_TIMESTAMP_NEVER_MSEC) {
1192         *out_flags |= SKTIMESTAMP_NOMSEC;
1193     }
1194 
1195     while (skStringMapIterNext(iter, &found_entry, NULL) == SK_ITERATOR_OK) {
1196         *out_flags |= found_entry->id;
1197         switch (found_entry->id) {
1198           case SKTIMESTAMP_NOMSEC:
1199             if (time_format_flags & SK_OPTION_TIMESTAMP_ALWAYS_MSEC) {
1200                 /* this should only occur when 'from_environ' is true;
1201                  * disable the no-msec setting. */
1202                 assert(from_environ);
1203                 *out_flags = *out_flags & ~SKTIMESTAMP_NOMSEC;
1204             }
1205             break;
1206 
1207           case 0:
1208           case SKTIMESTAMP_EPOCH:
1209           case SKTIMESTAMP_ISO:
1210           case SKTIMESTAMP_MMDDYYYY:
1211             if (name_seen) {
1212                 entry = time_format_names;
1213                 strncpy(buf, entry->name, sizeof(buf));
1214                 for (++entry; entry->name; ++entry) {
1215                     strncat(buf, ",", sizeof(buf)-strlen(buf)-1);
1216                     strncat(buf, entry->name, sizeof(buf)-strlen(buf)-1);
1217                 }
1218                 skAppPrintErr("Invalid %s: May only specify one of %s",
1219                               option_name, buf);
1220                 goto END;
1221             }
1222             name_seen = 1;
1223             break;
1224 
1225           case SKTIMESTAMP_UTC:
1226           case SKTIMESTAMP_LOCAL:
1227             if (zone_seen) {
1228                 entry = time_format_zones;
1229                 strncpy(buf, entry->name, sizeof(buf));
1230                 for (++entry; entry->name; ++entry) {
1231                     strncat(buf, ",", sizeof(buf)-strlen(buf)-1);
1232                     strncat(buf, entry->name, sizeof(buf)-strlen(buf)-1);
1233                 }
1234                 skAppPrintErr("Invalid %s: May only specify one of %s",
1235                               option_name, buf);
1236                 goto END;
1237             }
1238             zone_seen = 1;
1239             break;
1240 
1241           default:
1242             skAbortBadCase(found_entry->id);
1243         }
1244     }
1245 
1246     rv = 0;
1247 
1248   END:
1249     if (str_map) {
1250         skStringMapDestroy(str_map);
1251     }
1252     if (iter) {
1253         skStringMapIterDestroy(iter);
1254     }
1255     return rv;
1256 }
1257 
1258 static int
time_format_option_handler(clientData cData,int opt_index,char * opt_arg)1259 time_format_option_handler(
1260     clientData          cData,
1261     int                 opt_index,
1262     char               *opt_arg)
1263 {
1264     uint32_t *var_location = (uint32_t*)cData;
1265 
1266     switch ((enum time_format_option_en)opt_index) {
1267       case OPT_VAL_TIMESTAMP_FORMAT:
1268         if (time_format_option_parse(
1269                 opt_arg, var_location, time_format_option[opt_index].name))
1270         {
1271             return 1;
1272         }
1273         break;
1274 
1275       case OPT_VAL_EPOCH_TIME:
1276         if (time_format_option_parse(
1277                 "epoch", var_location, time_format_option[opt_index].name))
1278         {
1279             skAbort();
1280         }
1281         break;
1282 
1283       case OPT_VAL_LEGACY_TIMESTAMPS:
1284         if ((opt_arg == NULL) || (opt_arg[0] == '\0') || (opt_arg[0] == '1')) {
1285             if (time_format_flags & (SK_OPTION_TIMESTAMP_NEVER_MSEC
1286                                      | SK_OPTION_TIMESTAMP_ALWAYS_MSEC))
1287             {
1288                 if (time_format_option_parse(
1289                         "m/d/y", var_location,
1290                         time_format_option[opt_index].name))
1291                 {
1292                     skAbort();
1293                 }
1294             } else {
1295                 if (time_format_option_parse(
1296                         "m/d/y,no-msec", var_location,
1297                         time_format_option[opt_index].name))
1298                 {
1299                     skAbort();
1300                 }
1301             }
1302         } else if (time_format_option_parse(
1303                        time_format_names[0].name, var_location,
1304                        time_format_option[opt_index].name))
1305         {
1306             skAbort();
1307         }
1308         break;
1309     }
1310     return 0;
1311 }
1312 
1313 int
skOptionsTimestampFormatRegister(uint32_t * var_location,uint32_t flags,...)1314 skOptionsTimestampFormatRegister(
1315     uint32_t           *var_location,
1316     uint32_t            flags,
1317     ...)
1318 {
1319     struct option opts[4];
1320     const struct option *tfo;
1321     const char *env;
1322     uint32_t tmp_val = 0;
1323     unsigned int num_opts;
1324     va_list arg;
1325 
1326     assert(sizeof(opts) >= sizeof(time_format_option));
1327 
1328     va_start(arg, flags);
1329     if (var_location == NULL) {
1330         va_end(arg);
1331         return -1;
1332     }
1333 
1334     time_format_flags = flags;
1335     if (time_format_flags & SK_OPTION_TIMESTAMP_NEVER_MSEC) {
1336         *var_location |= SKTIMESTAMP_NOMSEC;
1337     }
1338 
1339     env = getenv(SK_TIMESTAMP_FORMAT_ENVAR);
1340     if (env && env[0]) {
1341         if (time_format_option_parse(env, &tmp_val, SK_TIMESTAMP_FORMAT_ENVAR)
1342             == 0)
1343         {
1344             *var_location = tmp_val;
1345         }
1346     }
1347 
1348     /* copy --timestamp-format */
1349     memset(opts, 0, sizeof(opts));
1350     num_opts = 0;
1351 
1352     for (tfo = time_format_option; tfo->name; ++tfo) {
1353         assert(num_opts < sizeof(opts)/sizeof(opts[0]));
1354         switch ((enum time_format_option_en)tfo->val) {
1355           case OPT_VAL_TIMESTAMP_FORMAT:
1356             memcpy(&opts[num_opts], tfo, sizeof(opts[0]));
1357             ++num_opts;
1358             break;
1359 
1360           case OPT_VAL_LEGACY_TIMESTAMPS:
1361             if (time_format_flags & SK_OPTION_TIMESTAMP_OPTION_LEGACY){
1362                 memcpy(&opts[num_opts], tfo, sizeof(opts[0]));
1363                 ++num_opts;
1364             }
1365             break;
1366 
1367           case OPT_VAL_EPOCH_TIME:
1368             if (time_format_flags & SK_OPTION_TIMESTAMP_OPTION_EPOCH_NAME) {
1369                 snprintf(time_format_epoch_name,sizeof(time_format_epoch_name),
1370                          "%s", va_arg(arg, char *));
1371                 memcpy(&opts[num_opts], tfo, sizeof(opts[0]));
1372                 opts[num_opts].name = time_format_epoch_name;
1373                 ++num_opts;
1374             } else if (time_format_flags & SK_OPTION_TIMESTAMP_OPTION_EPOCH) {
1375                 memcpy(&opts[num_opts], tfo, sizeof(opts[0]));
1376                 ++num_opts;
1377             }
1378             break;
1379         }
1380     }
1381 
1382     va_end(arg);
1383 
1384     return skOptionsRegister(opts, time_format_option_handler,
1385                              (clientData)var_location);
1386 }
1387 
1388 
1389 void
skOptionsTimestampFormatUsage(FILE * fh)1390 skOptionsTimestampFormatUsage(
1391     FILE               *fh)
1392 {
1393     const sk_stringmap_entry_t *e;
1394     const char *label;
1395     const char *sss;
1396     const struct option *tfo;
1397 
1398     /* whether to include milliseconds in timestamp help */
1399     if (time_format_flags & SK_OPTION_TIMESTAMP_NEVER_MSEC) {
1400         sss = "";
1401     } else {
1402         sss = ".sss";
1403     }
1404 
1405     for (tfo = time_format_option; tfo->name; ++tfo) {
1406         switch ((enum time_format_option_en)tfo->val) {
1407           case OPT_VAL_TIMESTAMP_FORMAT:
1408             fprintf(
1409                 fh,
1410                 ("--%s %s. Print each timestamp in this format and timezone.\n"
1411                  "\tDef. $" SK_TIMESTAMP_FORMAT_ENVAR " or %s,%s.  Choices:\n"),
1412                 tfo->name, SK_OPTION_HAS_ARG(*tfo),
1413                 time_format_names[0].name,
1414                 time_format_zones[(SK_ENABLE_LOCALTIME != 0)].name);
1415             label = "Format:";
1416             for (e = time_format_names; e->name; ++e) {
1417                 if (SKTIMESTAMP_EPOCH == e->id) {
1418                     sss = "";
1419                 }
1420                 fprintf(fh, "\t%-10s%-8s - %s%s\n",
1421                         label, e->name, e->description, sss);
1422                 label = "";
1423             }
1424             label = "Timezone:";
1425             for (e = time_format_zones; e->name; ++e) {
1426                 fprintf(fh, "\t%-10s%-8s - %s\n",
1427                         label, e->name, e->description);
1428                 label = "";
1429             }
1430             if (0 == (time_format_flags & (SK_OPTION_TIMESTAMP_NEVER_MSEC
1431                                            | SK_OPTION_TIMESTAMP_ALWAYS_MSEC)))
1432             {
1433                 label = "Misc:";
1434                 for (e = time_format_misc; e->name; ++e) {
1435                     fprintf(fh, "\t%-10s%-8s - %s\n",
1436                             label, e->name, e->description);
1437                     label = "";
1438                 }
1439             }
1440             break;
1441 
1442           case OPT_VAL_EPOCH_TIME:
1443             if (time_format_flags & SK_OPTION_TIMESTAMP_OPTION_EPOCH_NAME) {
1444                 fprintf(fh, ("--%s %s. DEPRECATED."
1445                              " Equivalent to --%s=epoch\n"),
1446                         time_format_epoch_name, SK_OPTION_HAS_ARG(*tfo),
1447                         time_format_option[OPT_VAL_TIMESTAMP_FORMAT].name);
1448             } else if (time_format_flags & SK_OPTION_TIMESTAMP_OPTION_EPOCH) {
1449                 fprintf(fh, ("--%s %s. DEPRECATED."
1450                              " Equivalent to --%s=epoch\n"),
1451                         tfo->name, SK_OPTION_HAS_ARG(*tfo),
1452                         time_format_option[OPT_VAL_TIMESTAMP_FORMAT].name);
1453             }
1454             break;
1455 
1456           case OPT_VAL_LEGACY_TIMESTAMPS:
1457             if (time_format_flags & SK_OPTION_TIMESTAMP_OPTION_LEGACY) {
1458                 fprintf(
1459                     fh, "--%s %s. DEPRECATED. Equivalent to --%s=m/d/y%s\n",
1460                     tfo->name, SK_OPTION_HAS_ARG(*tfo),
1461                     time_format_option[OPT_VAL_TIMESTAMP_FORMAT].name,
1462                     ((time_format_flags & (SK_OPTION_TIMESTAMP_NEVER_MSEC
1463                                            | SK_OPTION_TIMESTAMP_ALWAYS_MSEC))
1464                      ? ""
1465                      : ",no-msec"));
1466             }
1467             break;
1468         }
1469     }
1470 }
1471 
1472 
1473 #if  SK_SUPPORT_CONF_FILE
1474 /*
1475  *  readline:
1476  *      Read a line (including newline) from a file.  Will also read a
1477  *      last line (terminated by EOF) properly.
1478  *  SideEffects:
1479  *      Moves the read position of file to the next line.
1480  *  Return:
1481  *      A newly allocated string containing the next line.  NULL at
1482  *      EOF, or if there is a problem.
1483  */
1484 static char *
readline(FILE * file)1485 readline(
1486     FILE               *file)
1487 {
1488     static int gapsize = 64;
1489     char *line;
1490     int blocksize = 1;
1491     int writepoint = 0;
1492     char *retval = NULL;
1493 
1494     if (file == NULL) {
1495         return NULL;
1496     }
1497 
1498     /* Initial allocation for line */
1499     line = (char *)malloc(sizeof(char) * gapsize);
1500     if (line == NULL) {
1501         return NULL;
1502     }
1503 
1504     for (;;) {
1505         /* How many chars are left? */
1506         size_t empty = gapsize * blocksize - writepoint;
1507         char *wp = &line[writepoint];
1508 
1509         /* Get chars */
1510         if (fgets(wp, empty, file) == NULL) {
1511             if (writepoint != 0) {
1512                 /* End of file */
1513                 retval = strdup(line);
1514             }
1515             goto end;
1516         }
1517 
1518         /* If we haven't reached the end of the line, realloc. */
1519         if ((strlen(wp) == empty - 1) &&
1520             (wp[empty - 2] != '\n'))
1521         {
1522             char *tmpline;
1523             writepoint = gapsize * blocksize - 1;
1524             tmpline = realloc(line, sizeof(char) * (gapsize * (++blocksize)));
1525             if (tmpline) {
1526                 line = tmpline;
1527             } else {
1528                 goto end;
1529             }
1530         } else {
1531             /* We've reached the end of the line. */
1532             break;
1533         }
1534     }
1535 
1536     /* Allocate only enough space for the line. */
1537     retval = strdup(line);
1538 
1539   end:
1540     /* Cleanup */
1541     free(line);
1542 
1543     return retval;
1544 }
1545 
1546 
1547 /*
1548  * optionsHandleConfFile:
1549  *
1550  *     Loads a configuration file.  The configuration file consists of
1551  *     a series of newline-terminated lines.  A line consisting of
1552  *     only whitespace, or whose first non-whitespace character is a
1553  *     `#' character is ignored.  All other lines should consist of a
1554  *     single option name followed by the option's value (if any),
1555  *     separated by whitespace.  Whitespace at the beginning and end
1556  *     of the line is ignored.
1557  *
1558  * BUGS:
1559  *     If you intersperse switches (options) and arguments, arguments
1560  *     before the configuration file is parsed will not be seen.
1561  *
1562  *  Return:
1563  *      0 if ok. -1 else
1564  */
1565 int
optionsHandleConfFile(char * filename)1566 optionsHandleConfFile(
1567     char               *filename)
1568 {
1569     static int gapsize = 10;
1570     int num_lines = 0;
1571     int num_alloc = 0;
1572     char **lines = NULL;
1573     char *line = NULL;
1574     FILE *file;
1575     int retval = -1;
1576     int i;
1577     char **argv = NULL;
1578     int argc = 0;
1579     int saved_optind;
1580 
1581     if (filename == NULL) {
1582         skAppPrintErr("NULL configuration filename");
1583         return -1;
1584     }
1585 
1586     /* Open the file */
1587     file = fopen(filename, "r");
1588     if (file == NULL) {
1589         skAppPrintErr("Could not open \"%s\" for reading.", filename);
1590         return -1;
1591     }
1592 
1593     /* Alloc the line buffer */
1594     num_alloc = gapsize;
1595     lines = (char **)malloc(sizeof(char *) * num_alloc);
1596     if (lines == NULL) {
1597         skAppPrintErr("Memory allocation error.");
1598         goto end;
1599     }
1600 
1601     /* Read in the lines */
1602     while ((line = readline(file))) {
1603         char *newline;
1604         size_t len;
1605         char *c;
1606 
1607         /* Strip it */
1608         len = skStrip(line);
1609 
1610         /* Elide commented or empty lines. */
1611         if (line[0] == '\0' || line[0] == '#') {
1612             free(line);
1613             continue;
1614         }
1615 
1616         /* Allocate space for the line, plus two characters. */
1617         c = newline = (char *)malloc(sizeof(char) * (len + 3));
1618 
1619         /* Copy the line, prepending hyphens  */
1620         *c++ = '-';
1621         *c++ = '-';
1622         strncpy(c, line, (len+1));
1623         free(line);
1624         lines[num_lines++] = newline;
1625 
1626         /* Allocate more space, if necessary */
1627         if (num_lines > num_alloc) {
1628             char **tmp;
1629 
1630             num_alloc += gapsize;
1631             tmp = realloc(lines, sizeof(char *) * num_alloc);
1632             if (tmp == NULL) {
1633                 goto end;
1634             }
1635             lines = tmp;
1636         }
1637     }
1638 
1639     /* Allocate space for argv-style pointer */
1640     argv = (char **)malloc(sizeof(char *) * num_lines * 2 + 1);
1641     if (argv == NULL) {
1642         goto end;
1643     }
1644     /* First operand is program name, ignored */
1645     argv[argc++] = "";
1646 
1647     /* Parse the lines. */
1648     for (i = 0; i < num_lines; i++) {
1649         /* Set the next argument to the beginning of the line */
1650         char *c = argv[argc++] = lines[i];
1651 
1652         /* Find a space */
1653         while (*c && !isspace((int)*c)) {
1654             c++;
1655         }
1656         if (*c) {
1657             /* If we found a space, end the first arg, and find the
1658                option value. */
1659             *c++ = '\0';
1660             while (isspace((int)*c)) { /* Don't need to check for 0
1661                                           due to strip */
1662                 c++;
1663             }
1664             /* Set the next argument to the option value. */
1665             argv[argc++] = c;
1666         }
1667     }
1668 
1669     saved_optind = optind;
1670 #ifdef SK_USE_OPTRESET
1671     optreset = 1;
1672 #endif
1673 #ifdef SK_HAVE_GETOPT_LONG_ONLY
1674     optind = 1;
1675 #else
1676     optind = 0;
1677 #endif
1678     /* Parse the options */
1679     if (skOptionsParse(argc, argv) != -1) {
1680         retval = 0;
1681     }
1682     optind = saved_optind;
1683 #ifdef SK_USE_OPTRESET
1684     optreset = 1;
1685 #endif
1686 
1687   end:
1688     /* Cleanup */
1689     if (file) {
1690         fclose(file);
1691     }
1692     if (argv) {
1693         free(argv);
1694     }
1695     if (lines) {
1696         for (i = 0; i < num_lines; i++) {
1697             free(lines[i]);
1698         }
1699         free(lines);
1700     }
1701     return retval;
1702 }
1703 #endif /* SK_SUPPORT_CONF_FILE */
1704 
1705 
1706 /*
1707 ** Local Variables:
1708 ** mode:c
1709 ** indent-tabs-mode:nil
1710 ** c-basic-offset:4
1711 ** End:
1712 */
1713