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