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