xref: /netbsd/external/bsd/ntp/dist/sntp/libopts/save.c (revision 9034ec65)
1 /*	$NetBSD: save.c,v 1.11 2020/05/25 20:47:35 christos Exp $	*/
2 
3 
4 /*
5  * \file save.c
6  *
7  *  This module's routines will take the currently set options and
8  *  store them into an ".rc" file for re-interpretation the next
9  *  time the invoking program is run.
10  *
11  * @addtogroup autoopts
12  * @{
13  */
14 /*
15  *  This file is part of AutoOpts, a companion to AutoGen.
16  *  AutoOpts is free software.
17  *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
18  *
19  *  AutoOpts is available under any one of two licenses.  The license
20  *  in use must be one of these two and the choice is under the control
21  *  of the user of the license.
22  *
23  *   The GNU Lesser General Public License, version 3 or later
24  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
25  *
26  *   The Modified Berkeley Software Distribution License
27  *      See the file "COPYING.mbsd"
28  *
29  *  These files have the following sha256 sums:
30  *
31  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
32  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
33  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
34  */
35 
36 /* = = = START-STATIC-FORWARD = = = */
37 static char const *
38 find_dir_name(tOptions * opts, int * p_free);
39 
40 static char const *
41 find_file_name(tOptions * opts, int * p_free_name);
42 
43 static void
44 prt_entry(FILE * fp, tOptDesc * od, char const * l_arg);
45 
46 static void
47 prt_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp);
48 
49 static void
50 prt_string(FILE * fp, char const * name, char const * pz);
51 
52 static void
53 prt_val_list(FILE * fp, char const * name, tArgList * al);
54 
55 static void
56 prt_nested(FILE * fp, tOptDesc * p);
57 
58 static FILE *
59 open_sv_file(tOptions * opts);
60 
61 static void
62 prt_no_arg_opt(FILE * fp, tOptDesc * p, tOptDesc * pOD);
63 
64 static void
65 prt_str_arg(FILE * fp, tOptDesc * pOD);
66 
67 static void
68 prt_enum_arg(FILE * fp, tOptDesc * od);
69 
70 static void
71 prt_set_arg(FILE * fp, tOptDesc * od);
72 
73 static void
74 prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts);
75 /* = = = END-STATIC-FORWARD = = = */
76 
77 /**
78  */
79 static char const *
find_dir_name(tOptions * opts,int * p_free)80 find_dir_name(tOptions * opts, int * p_free)
81 {
82     char const * pzDir;
83 
84     if (  (opts->specOptIdx.save_opts == NO_EQUIVALENT)
85        || (opts->specOptIdx.save_opts == 0))
86         return NULL;
87 
88     pzDir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
89     if ((pzDir != NULL) && (*pzDir != NUL))
90         return pzDir;
91 
92     /*
93      *  This function only works if there is a directory where
94      *  we can stash the RC (INI) file.
95      */
96     {
97         char const * const * papz = opts->papzHomeList;
98         if (papz == NULL)
99             return NULL;
100 
101         while (papz[1] != NULL) papz++;
102         pzDir = *papz;
103     }
104 
105     /*
106      *  IF it does not require deciphering an env value, then just copy it
107      */
108     if (*pzDir != '$')
109         return pzDir;
110 
111     {
112         char const * pzEndDir = strchr(++pzDir, DIRCH);
113         char * pzFileName;
114         char * pzEnv;
115 
116         if (pzEndDir != NULL) {
117             char z[ AO_NAME_SIZE ];
118             if ((pzEndDir - pzDir) > AO_NAME_LIMIT )
119                 return NULL;
120             memcpy(z, pzDir, (size_t)(pzEndDir - pzDir));
121             z[pzEndDir - pzDir] = NUL;
122             pzEnv = getenv(z);
123         } else {
124 
125             /*
126              *  Make sure we can get the env value (after stripping off
127              *  any trailing directory or file names)
128              */
129             pzEnv = getenv(pzDir);
130         }
131 
132         if (pzEnv == NULL) {
133             fprintf(stderr, zsave_warn, opts->pzProgName);
134             fprintf(stderr, zNotDef, pzDir);
135             return NULL;
136         }
137 
138         if (pzEndDir == NULL)
139             return pzEnv;
140 
141         {
142             size_t sz = strlen(pzEnv) + strlen(pzEndDir) + 2;
143             pzFileName = (char *)AGALOC(sz, "dir name");
144         }
145 
146         if (pzFileName == NULL)
147             return NULL;
148 
149         *p_free = 1;
150         /*
151          *  Glue together the full name into the allocated memory.
152          *  FIXME: We lose track of this memory.
153          */
154         sprintf(pzFileName, "%s/%s", pzEnv, pzEndDir);
155         return pzFileName;
156     }
157 }
158 
159 /**
160  */
161 static char const *
find_file_name(tOptions * opts,int * p_free_name)162 find_file_name(tOptions * opts, int * p_free_name)
163 {
164     struct stat stBuf;
165     int    free_dir_name = 0;
166 
167     char const * pzDir = find_dir_name(opts, &free_dir_name);
168     if (pzDir == NULL)
169         return NULL;
170 
171     /*
172      *  See if we can find the specified directory.  We use a once-only loop
173      *  structure so we can bail out early.
174      */
175     if (stat(pzDir, &stBuf) != 0) do {
176         char z[AG_PATH_MAX];
177         char * dirchp;
178 
179         /*
180          *  IF we could not, check to see if we got a full
181          *  path to a file name that has not been created yet.
182          */
183         if (errno != ENOENT) {
184         bogus_name:
185             fprintf(stderr, zsave_warn, opts->pzProgName);
186             fprintf(stderr, zNoStat, errno, strerror(errno), pzDir);
187             if (free_dir_name)
188                 AGFREE(pzDir);
189             return NULL;
190         }
191 
192         /*
193          *  Strip off the last component, stat the remaining string and
194          *  that string must name a directory
195          */
196         dirchp = strrchr(pzDir, DIRCH);
197         if (dirchp == NULL) {
198             stBuf.st_mode = S_IFREG;
199             break; /* found directory -- viz.,  "." */
200         }
201 
202         if ((size_t)(dirchp - pzDir) >= sizeof(z))
203             goto bogus_name;
204 
205         memcpy(z, pzDir, (size_t)(dirchp - pzDir));
206         z[dirchp - pzDir] = NUL;
207 
208         if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode))
209             goto bogus_name;
210         stBuf.st_mode = S_IFREG; /* file within this directory */
211     } while (false);
212 
213     /*
214      *  IF what we found was a directory,
215      *  THEN tack on the config file name
216      */
217     if (S_ISDIR(stBuf.st_mode)) {
218         size_t sz = strlen(pzDir) + strlen(opts->pzRcName) + 2;
219 
220         {
221             char * pzPath = (char *)AGALOC(sz, "file name");
222 #ifdef HAVE_SNPRINTF
223             snprintf(pzPath, sz, "%s/%s", pzDir, opts->pzRcName);
224 #else
225             sprintf(pzPath, "%s/%s", pzDir, opts->pzRcName);
226 #endif
227             if (free_dir_name)
228                 AGFREE(pzDir);
229             pzDir = pzPath;
230             free_dir_name = 1;
231         }
232 
233         /*
234          *  IF we cannot stat the object for any reason other than
235          *     it does not exist, then we bail out
236          */
237         if (stat(pzDir, &stBuf) != 0) {
238             if (errno != ENOENT) {
239                 fprintf(stderr, zsave_warn, opts->pzProgName);
240                 fprintf(stderr, zNoStat, errno, strerror(errno),
241                         pzDir);
242                 AGFREE(pzDir);
243                 return NULL;
244             }
245 
246             /*
247              *  It does not exist yet, but it will be a regular file
248              */
249             stBuf.st_mode = S_IFREG;
250         }
251     }
252 
253     /*
254      *  Make sure that whatever we ultimately found, that it either is
255      *  or will soon be a file.
256      */
257     if (! S_ISREG(stBuf.st_mode)) {
258         fprintf(stderr, zsave_warn, opts->pzProgName, pzDir);
259         if (free_dir_name)
260             AGFREE(pzDir);
261         return NULL;
262     }
263 
264     /*
265      *  Get rid of the old file
266      */
267     unlink(pzDir);
268     *p_free_name = free_dir_name;
269     return pzDir;
270 }
271 
272 /**
273  * print one option entry to the save file.
274  *
275  * @param[in] fp    the file pointer for the save file
276  * @param[in] od    the option descriptor to print
277  * @param[in] l_arg the last argument for the option
278  */
279 static void
prt_entry(FILE * fp,tOptDesc * od,char const * l_arg)280 prt_entry(FILE * fp, tOptDesc * od, char const * l_arg)
281 {
282     int space_ct;
283 
284     /*
285      *  There is an argument.  Pad the name so values line up.
286      *  Not disabled *OR* this got equivalenced to another opt,
287      *  then use current option name.
288      *  Otherwise, there must be a disablement name.
289      */
290     {
291         char const * pz =
292             (! DISABLED_OPT(od) || (od->optEquivIndex != NO_EQUIVALENT))
293             ? od->pz_Name
294             : od->pz_DisableName;
295         space_ct = 17 - strlen(pz);
296         fputs(pz, fp);
297     }
298 
299     if (  (l_arg == NULL)
300        && (OPTST_GET_ARGTYPE(od->fOptState) != OPARG_TYPE_NUMERIC))
301         goto end_entry;
302 
303     fputs(" = ", fp);
304     while (space_ct-- > 0)  fputc(' ', fp);
305 
306     /*
307      *  IF the option is numeric only,
308      *  THEN the char pointer is really the number
309      */
310     if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NUMERIC)
311         fprintf(fp, "%d", (int)(intptr_t)l_arg);
312 
313     else {
314         for (;;) {
315             char const * eol = strchr(l_arg, NL);
316 
317             /*
318              *  IF this is the last line
319              *  THEN bail and print it
320              */
321             if (eol == NULL)
322                 break;
323 
324             /*
325              *  Print the continuation and the text from the current line
326              */
327             (void)fwrite(l_arg, (size_t)(eol - l_arg), (size_t)1, fp);
328             l_arg = eol+1; /* advance the Last Arg pointer */
329             fputs("\\\n", fp);
330         }
331 
332         /*
333          *  Terminate the entry
334          */
335         fputs(l_arg, fp);
336     }
337 
338 end_entry:
339     fputc(NL, fp);
340 }
341 
342 /**
343  */
344 static void
prt_value(FILE * fp,int depth,tOptDesc * pOD,tOptionValue const * ovp)345 prt_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp)
346 {
347     while (--depth >= 0)
348         putc(' ', fp), putc(' ', fp);
349 
350     switch (ovp->valType) {
351     default:
352     case OPARG_TYPE_NONE:
353         fprintf(fp, NULL_ATR_FMT, ovp->pzName);
354         break;
355 
356     case OPARG_TYPE_STRING:
357         prt_string(fp, ovp->pzName, ovp->v.strVal);
358         break;
359 
360     case OPARG_TYPE_ENUMERATION:
361     case OPARG_TYPE_MEMBERSHIP:
362         if (pOD != NULL) {
363             uint32_t  opt_state = pOD->fOptState;
364             uintptr_t val = pOD->optArg.argEnum;
365             char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION)
366                 ? "keyword" : "set-membership";
367 
368             fprintf(fp, TYPE_ATR_FMT, ovp->pzName, typ);
369 
370             /*
371              *  This is a magic incantation that will convert the
372              *  bit flag values back into a string suitable for printing.
373              */
374             (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD );
375             if (pOD->optArg.argString != NULL) {
376                 fputs(pOD->optArg.argString, fp);
377 
378                 if (ovp->valType != OPARG_TYPE_ENUMERATION) {
379                     /*
380                      *  set membership strings get allocated
381                      */
382                     AGFREE(pOD->optArg.argString);
383                 }
384             }
385 
386             pOD->optArg.argEnum = val;
387             pOD->fOptState = opt_state;
388             fprintf(fp, END_XML_FMT, ovp->pzName);
389             break;
390         }
391         /* FALLTHROUGH */
392 
393     case OPARG_TYPE_NUMERIC:
394         fprintf(fp, NUMB_ATR_FMT, ovp->pzName, ovp->v.longVal);
395         break;
396 
397     case OPARG_TYPE_BOOLEAN:
398         fprintf(fp, BOOL_ATR_FMT, ovp->pzName,
399                 ovp->v.boolVal ? "true" : "false");
400         break;
401 
402     case OPARG_TYPE_HIERARCHY:
403         prt_val_list(fp, ovp->pzName, ovp->v.nestVal);
404         break;
405     }
406 }
407 
408 /**
409  */
410 static void
prt_string(FILE * fp,char const * name,char const * pz)411 prt_string(FILE * fp, char const * name, char const * pz)
412 {
413     fprintf(fp, OPEN_XML_FMT, name);
414     for (;;) {
415         int ch = ((int)*(pz++)) & 0xFF;
416 
417         switch (ch) {
418         case NUL: goto string_done;
419 
420         case '&':
421         case '<':
422         case '>':
423 #if __GNUC__ >= 4
424         case 1 ... (' ' - 1):
425         case ('~' + 1) ... 0xFF:
426 #endif
427             emit_special_char(fp, ch);
428             break;
429 
430         default:
431 #if __GNUC__ < 4
432             if (  ((ch >= 1) && (ch <= (' ' - 1)))
433                || ((ch >= ('~' + 1)) && (ch <= 0xFF)) ) {
434                 emit_special_char(fp, ch);
435                 break;
436             }
437 #endif
438             putc(ch, fp);
439         }
440     } string_done:;
441     fprintf(fp, END_XML_FMT, name);
442 }
443 
444 /**
445  */
446 static void
prt_val_list(FILE * fp,char const * name,tArgList * al)447 prt_val_list(FILE * fp, char const * name, tArgList * al)
448 {
449     static int depth = 1;
450 
451     int sp_ct;
452     int opt_ct;
453     void ** opt_list;
454 
455     if (al == NULL)
456         return;
457     opt_ct   = al->useCt;
458     opt_list = VOIDP(al->apzArgs);
459 
460     if (opt_ct <= 0) {
461         fprintf(fp, OPEN_CLOSE_FMT, name);
462         return;
463     }
464 
465     fprintf(fp, NESTED_OPT_FMT, name);
466 
467     depth++;
468     while (--opt_ct >= 0) {
469         tOptionValue const * ovp = *(opt_list++);
470 
471         prt_value(fp, depth, NULL, ovp);
472     }
473     depth--;
474 
475     for (sp_ct = depth; --sp_ct >= 0;)
476         putc(' ', fp), putc(' ', fp);
477     fprintf(fp, "</%s>\n", name);
478 }
479 
480 /**
481  */
482 static void
prt_nested(FILE * fp,tOptDesc * p)483 prt_nested(FILE * fp, tOptDesc * p)
484 {
485     int opt_ct;
486     tArgList * al = p->optCookie;
487     void ** opt_list;
488 
489     if (al == NULL)
490         return;
491 
492     opt_ct   = al->useCt;
493     opt_list = VOIDP(al->apzArgs);
494 
495     if (opt_ct <= 0)
496         return;
497 
498     do  {
499         tOptionValue const * base = *(opt_list++);
500         tOptionValue const * ovp = optionGetValue(base, NULL);
501 
502         if (ovp == NULL)
503             continue;
504 
505         fprintf(fp, NESTED_OPT_FMT, p->pz_Name);
506 
507         do  {
508             prt_value(fp, 1, p, ovp);
509 
510         } while (ovp = optionNextValue(base, ovp),
511                  ovp != NULL);
512 
513         fprintf(fp, "</%s>\n", p->pz_Name);
514     } while (--opt_ct > 0);
515 }
516 
517 /**
518  * open the file for saving option state.
519  *
520  * @param[in] opts  the program options structure
521  * @returns the open file pointer.  It may be NULL.
522  */
523 static FILE *
open_sv_file(tOptions * opts)524 open_sv_file(tOptions * opts)
525 {
526     FILE * fp;
527 
528     {
529         int   free_name = 0;
530         char const * pzFName = find_file_name(opts, &free_name);
531         if (pzFName == NULL)
532             return NULL;
533 
534         fp = fopen(pzFName, "w" FOPEN_BINARY_FLAG);
535         if (fp == NULL) {
536             fprintf(stderr, zsave_warn, opts->pzProgName);
537             fprintf(stderr, zNoCreat, errno, strerror(errno), pzFName);
538             if (free_name)
539                 AGFREE(pzFName);
540             return fp;
541         }
542 
543         if (free_name)
544             AGFREE(pzFName);
545     }
546 
547     fputs("#  ", fp);
548     {
549         char const * e = strchr(opts->pzUsageTitle, NL);
550         if (e++ != NULL)
551             fwrite(opts->pzUsageTitle, 1, e - opts->pzUsageTitle, fp);
552     }
553 
554     {
555         time_t  cur_time = time(NULL);
556         char *  time_str = ctime(&cur_time);
557 
558         fprintf(fp, zPresetFile, time_str);
559 #ifdef HAVE_ALLOCATED_CTIME
560         /*
561          *  The return values for ctime(), localtime(), and gmtime()
562          *  normally point to static data that is overwritten by each call.
563          *  The test to detect allocated ctime, so we leak the memory.
564          */
565         AGFREE(time_str);
566 #endif
567     }
568 
569     return fp;
570 }
571 
572 /**
573  */
574 static void
prt_no_arg_opt(FILE * fp,tOptDesc * p,tOptDesc * pOD)575 prt_no_arg_opt(FILE * fp, tOptDesc * p, tOptDesc * pOD)
576 {
577     /*
578      * The aliased to argument indicates whether or not the option
579      * is "disabled".  However, the original option has the name
580      * string, so we get that there, not with "p".
581      */
582     char const * pznm =
583         (DISABLED_OPT(p)) ? pOD->pz_DisableName : pOD->pz_Name;
584     /*
585      *  If the option was disabled and the disablement name is NULL,
586      *  then the disablement was caused by aliasing.
587      *  Use the name as the string to emit.
588      */
589     if (pznm == NULL)
590         pznm = pOD->pz_Name;
591 
592     fprintf(fp, "%s\n", pznm);
593 }
594 
595 /**
596  */
597 static void
prt_str_arg(FILE * fp,tOptDesc * pOD)598 prt_str_arg(FILE * fp, tOptDesc * pOD)
599 {
600     if (pOD->fOptState & OPTST_STACKED) {
601         tArgList * pAL = (tArgList *)pOD->optCookie;
602         int        uct = pAL->useCt;
603         char const ** ppz = pAL->apzArgs;
604 
605         /*
606          *  un-disable multiple copies of disabled options.
607          */
608         if (uct > 1)
609             pOD->fOptState &= ~OPTST_DISABLED;
610 
611         while (uct-- > 0)
612             prt_entry(fp, pOD, *(ppz++));
613     } else {
614         prt_entry(fp, pOD, pOD->optArg.argString);
615     }
616 }
617 
618 /**
619  * print the string value of an enumeration.
620  *
621  * @param[in] fp  the file pointer to write to
622  * @param[in] od  the option descriptor with the enumerated value
623  */
624 static void
prt_enum_arg(FILE * fp,tOptDesc * od)625 prt_enum_arg(FILE * fp, tOptDesc * od)
626 {
627     uintptr_t val = od->optArg.argEnum;
628 
629     /*
630      *  This is a magic incantation that will convert the
631      *  bit flag values back into a string suitable for printing.
632      */
633     (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od);
634     prt_entry(fp, od, VOIDP(od->optArg.argString));
635 
636     od->optArg.argEnum = val;
637 }
638 
639 /**
640  * Print the bits set in a bit mask option.
641  * We call the option handling function with a magic value for
642  * the options pointer and it allocates and fills in the string.
643  * We print that with a call to prt_entry().
644  *
645  * @param[in] fp  the file pointer to write to
646  * @param[in] od  the option descriptor with a bit mask value type
647  */
648 static void
prt_set_arg(FILE * fp,tOptDesc * od)649 prt_set_arg(FILE * fp, tOptDesc * od)
650 {
651     char * list = optionMemberList(od);
652     size_t len  = strlen(list);
653     char * buf  = (char *)AGALOC(len + 3, "dir name");
654     *buf= '=';
655     memcpy(buf+1, list, len + 1);
656     prt_entry(fp, od, buf);
657     AGFREE(buf);
658     AGFREE(list);
659 }
660 
661 /**
662  * figure out what the option file name argument is.
663  * If one can be found, call prt_entry() to emit it.
664  *
665  * @param[in] fp   the file pointer to write to.
666  * @param[in] od   the option descriptor with a bit mask value type
667  * @param[in] opts the program options descriptor
668  */
669 static void
prt_file_arg(FILE * fp,tOptDesc * od,tOptions * opts)670 prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts)
671 {
672     /*
673      *  If the cookie is not NULL, then it has the file name, period.
674      *  Otherwise, if we have a non-NULL string argument, then....
675      */
676     if (od->optCookie != NULL)
677         prt_entry(fp, od, od->optCookie);
678 
679     else if (HAS_originalOptArgArray(opts)) {
680         char const * orig =
681             opts->originalOptArgArray[od->optIndex].argString;
682 
683         if (od->optArg.argString == orig)
684             return;
685 
686         prt_entry(fp, od, od->optArg.argString);
687     }
688 }
689 
690 /*=export_func  optionSaveFile
691  *
692  * what:  saves the option state to a file
693  *
694  * arg:   tOptions *,   opts,  program options descriptor
695  *
696  * doc:
697  *
698  * This routine will save the state of option processing to a file.  The name
699  * of that file can be specified with the argument to the @code{--save-opts}
700  * option, or by appending the @code{rcfile} attribute to the last
701  * @code{homerc} attribute.  If no @code{rcfile} attribute was specified, it
702  * will default to @code{.@i{programname}rc}.  If you wish to specify another
703  * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro.
704  *
705  * The recommend usage is as follows:
706  * @example
707  *    optionProcess(&progOptions, argc, argv);
708  *    if (i_want_a_non_standard_place_for_this)
709  *        SET_OPT_SAVE_OPTS("myfilename");
710  *    optionSaveFile(&progOptions);
711  * @end example
712  *
713  * err:
714  *
715  * If no @code{homerc} file was specified, this routine will silently return
716  * and do nothing.  If the output file cannot be created or updated, a message
717  * will be printed to @code{stderr} and the routine will return.
718 =*/
719 void
optionSaveFile(tOptions * opts)720 optionSaveFile(tOptions * opts)
721 {
722     tOptDesc *  od;
723     int         ct;
724     FILE *      fp = open_sv_file(opts);
725 
726     if (fp == NULL)
727         return;
728 
729     /*
730      *  FOR each of the defined options, ...
731      */
732     ct = opts->presetOptCt;
733     od = opts->pOptDesc;
734     do  {
735         tOptDesc * p;
736 
737         /*
738          *  IF    the option has not been defined
739          *     OR it does not take an initialization value
740          *     OR it is equivalenced to another option
741          *  THEN continue (ignore it)
742          *
743          *  Equivalenced options get picked up when the equivalenced-to
744          *  option is processed.
745          */
746         if (UNUSED_OPT(od))
747             continue;
748 
749         if ((od->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
750             continue;
751 
752         if (  (od->optEquivIndex != NO_EQUIVALENT)
753            && (od->optEquivIndex != od->optIndex))
754             continue;
755 
756         /*
757          *  The option argument data are found at the equivalenced-to option,
758          *  but the actual option argument type comes from the original
759          *  option descriptor.  Be careful!
760          */
761         p = ((od->fOptState & OPTST_EQUIVALENCE) != 0)
762             ? (opts->pOptDesc + od->optActualIndex) : od;
763 
764         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
765         case OPARG_TYPE_NONE:
766             prt_no_arg_opt(fp, p, od);
767             break;
768 
769         case OPARG_TYPE_NUMERIC:
770             prt_entry(fp, p, VOIDP(p->optArg.argInt));
771             break;
772 
773         case OPARG_TYPE_STRING:
774             prt_str_arg(fp, p);
775             break;
776 
777         case OPARG_TYPE_ENUMERATION:
778             prt_enum_arg(fp, p);
779             break;
780 
781         case OPARG_TYPE_MEMBERSHIP:
782             prt_set_arg(fp, p);
783             break;
784 
785         case OPARG_TYPE_BOOLEAN:
786             prt_entry(fp, p, p->optArg.argBool ? "true" : "false");
787             break;
788 
789         case OPARG_TYPE_HIERARCHY:
790             prt_nested(fp, p);
791             break;
792 
793         case OPARG_TYPE_FILE:
794             prt_file_arg(fp, p, opts);
795             break;
796 
797         default:
798             break; /* cannot handle - skip it */
799         }
800     } while (od++, (--ct > 0));
801 
802     fclose(fp);
803 }
804 /** @}
805  *
806  * Local Variables:
807  * mode: C
808  * c-file-style: "stroustrup"
809  * indent-tabs-mode: nil
810  * End:
811  * end of autoopts/save.c */
812