xref: /freebsd/contrib/ntp/sntp/libopts/save.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
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-2018 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 #include "save-flags.h"
34 
35 /**
36  * find the config file directory name
37  *
38  * @param opts    the options descriptor
39  * @param p_free  tell caller if name was allocated or not
40  */
41 static char const *
42 find_dir_name(tOptions * opts, int * p_free)
43 {
44     char const * dir;
45 
46     if (  (opts->specOptIdx.save_opts == NO_EQUIVALENT)
47        || (opts->specOptIdx.save_opts == 0))
48         return NULL;
49 
50     dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
51     if ((dir != NULL) && (*dir != NUL)) {
52         char const * pz = strchr(dir, '>');
53         if (pz == NULL)
54             return dir;
55         while (*(++pz) == '>')  ;
56         pz += strspn(pz, " \t");
57         dir = pz;
58         if (*dir != NUL)
59             return dir;
60     }
61 
62     if (opts->papzHomeList == NULL)
63         return NULL;
64 
65     /*
66      *  This function only works if there is a directory where
67      *  we can stash the RC (INI) file.
68      */
69     for (int idx = 0;; idx++) {
70         char f_name[ AG_PATH_MAX+1 ];
71 
72         dir = opts->papzHomeList[idx];
73 
74         switch (*dir) {
75         case '$':
76             break;
77         case NUL:
78             continue;
79         default:
80             return dir;
81         }
82         if (optionMakePath(f_name, (int)sizeof(f_name), dir, opts->pzProgPath)) {
83             *p_free = true;
84             AGDUPSTR(dir, f_name, "homerc");
85             return dir;
86         }
87     }
88     return NULL;
89 }
90 
91 /**
92  * Find the name of the save-the-options file
93  *
94  * @param opts         the options descriptor
95  * @param p_free_name  tell caller if name was allocated or not
96  */
97 static char const *
98 find_file_name(tOptions * opts, int * p_free_name)
99 {
100     struct stat stBuf;
101     int    free_dir_name = 0;
102 
103     char const * res = find_dir_name(opts, &free_dir_name);
104     if (res == NULL)
105         return res;
106 
107     /*
108      *  See if we can find the specified directory.  We use a once-only loop
109      *  structure so we can bail out early.
110      */
111     if (stat(res, &stBuf) != 0) do {
112         char z[AG_PATH_MAX];
113         char * dirchp;
114 
115         /*
116          *  IF we could not, check to see if we got a full
117          *  path to a file name that has not been created yet.
118          */
119         if (errno != ENOENT) {
120         bogus_name:
121             fprintf(stderr, zsave_warn, opts->pzProgName, res);
122             fprintf(stderr, zNoStat, errno, strerror(errno), res);
123             if (free_dir_name)
124                 AGFREE(res);
125             return NULL;
126         }
127 
128         /*
129          *  Strip off the last component, stat the remaining string and
130          *  that string must name a directory
131          */
132         dirchp = strrchr(res, DIRCH);
133         if (dirchp == NULL) {
134             stBuf.st_mode = S_IFREG;
135             break; /* found directory -- viz.,  "." */
136         }
137 
138         if ((size_t)(dirchp - res) >= sizeof(z))
139             goto bogus_name;
140 
141         memcpy(z, res, (size_t)(dirchp - res));
142         z[dirchp - res] = NUL;
143 
144         if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode))
145             goto bogus_name;
146         stBuf.st_mode = S_IFREG; /* file within this directory */
147     } while (false);
148 
149     /*
150      *  IF what we found was a directory,
151      *  THEN tack on the config file name
152      */
153     if (S_ISDIR(stBuf.st_mode)) {
154 
155         {
156             size_t sz = strlen(res) + strlen(opts->pzRcName) + 2;
157             char * pzPath = (char *)AGALOC(sz, "file name");
158             if (   snprintf(pzPath, sz, "%s/%s", res, opts->pzRcName)
159                 >= (int)sz)
160                 option_exits(EXIT_FAILURE);
161 
162             if (free_dir_name)
163                 AGFREE(res);
164             res = pzPath;
165             free_dir_name = 1;
166         }
167 
168         /*
169          *  IF we cannot stat the object for any reason other than
170          *     it does not exist, then we bail out
171          */
172         if (stat(res, &stBuf) != 0) {
173             if (errno != ENOENT) {
174                 fprintf(stderr, zsave_warn, opts->pzProgName, res);
175                 fprintf(stderr, zNoStat, errno, strerror(errno),
176                         res);
177                 AGFREE(res);
178                 return NULL;
179             }
180 
181             /*
182              *  It does not exist yet, but it will be a regular file
183              */
184             stBuf.st_mode = S_IFREG;
185         }
186     }
187 
188     /*
189      *  Make sure that whatever we ultimately found, that it either is
190      *  or will soon be a file.
191      */
192     if (! S_ISREG(stBuf.st_mode)) {
193         fprintf(stderr, zsave_warn, opts->pzProgName, res);
194         if (free_dir_name)
195             AGFREE(res);
196         return NULL;
197     }
198 
199     /*
200      *  Get rid of the old file
201      */
202     *p_free_name = free_dir_name;
203     return res;
204 }
205 
206 /**
207  * print one option entry to the save file.
208  *
209  * @param[in] fp       the file pointer for the save file
210  * @param[in] od       the option descriptor to print
211  * @param[in] l_arg    the last argument for the option
212  * @param[in] save_fl  include usage in comments
213  */
214 static void
215 prt_entry(FILE * fp, tOptDesc * od, char const * l_arg, save_flags_mask_t save_fl)
216 {
217     int space_ct;
218 
219     if (save_fl & SVFL_USAGE)
220         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
221     if (UNUSED_OPT(od) && (save_fl & SVFL_DEFAULT))
222         fputs(ao_default_use, fp);
223 
224     /*
225      *  There is an argument.  Pad the name so values line up.
226      *  Not disabled *OR* this got equivalenced to another opt,
227      *  then use current option name.
228      *  Otherwise, there must be a disablement name.
229      */
230     {
231         char const * pz =
232             (od->pz_DisableName == NULL)
233             ? od->pz_Name
234             : (DISABLED_OPT(od)
235                ? od->pz_DisableName
236                : ((od->optEquivIndex == NO_EQUIVALENT)
237                   ? od->pz_Name : od->pz_DisableName)
238               );
239 
240         space_ct = 17 - strlen(pz);
241         fputs(pz, fp);
242     }
243 
244     if (  (l_arg == NULL)
245        && (OPTST_GET_ARGTYPE(od->fOptState) != OPARG_TYPE_NUMERIC))
246         goto end_entry;
247 
248     fputs(" = ", fp);
249     while (space_ct-- > 0)  fputc(' ', fp);
250 
251     /*
252      *  IF the option is numeric only,
253      *  THEN the char pointer is really the number
254      */
255     if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NUMERIC)
256         fprintf(fp, "%d", (int)(intptr_t)l_arg);
257 
258     else {
259         for (;;) {
260             char const * eol = strchr(l_arg, NL);
261 
262             /*
263              *  IF this is the last line
264              *  THEN bail and print it
265              */
266             if (eol == NULL)
267                 break;
268 
269             /*
270              *  Print the continuation and the text from the current line
271              */
272             (void)fwrite(l_arg, (size_t)(eol - l_arg), (size_t)1, fp);
273             l_arg = eol+1; /* advance the Last Arg pointer */
274             fputs("\\\n", fp);
275         }
276 
277         /*
278          *  Terminate the entry
279          */
280         fputs(l_arg, fp);
281     }
282 
283 end_entry:
284     fputc(NL, fp);
285 }
286 
287 /**
288  * print an option's value
289  *
290  * @param[in] fp          the file pointer for the save file
291  * @param[in] od          the option descriptor to print
292  */
293 static void
294 prt_value(FILE * fp, int depth, tOptDesc * od, tOptionValue const * ovp)
295 {
296     while (--depth >= 0)
297         putc(' ', fp), putc(' ', fp);
298 
299     switch (ovp->valType) {
300     default:
301     case OPARG_TYPE_NONE:
302         fprintf(fp, NULL_ATR_FMT, ovp->pzName);
303         break;
304 
305     case OPARG_TYPE_STRING:
306         prt_string(fp, ovp->pzName, ovp->v.strVal);
307         break;
308 
309     case OPARG_TYPE_ENUMERATION:
310     case OPARG_TYPE_MEMBERSHIP:
311         if (od != NULL) {
312             uint32_t  opt_state = od->fOptState;
313             uintptr_t val = od->optArg.argEnum;
314             char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION)
315                 ? "keyword" : "set-membership";
316 
317             fprintf(fp, TYPE_ATR_FMT, ovp->pzName, typ);
318 
319             /*
320              *  This is a magic incantation that will convert the
321              *  bit flag values back into a string suitable for printing.
322              */
323             (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od );
324             if (od->optArg.argString != NULL) {
325                 fputs(od->optArg.argString, fp);
326 
327                 if (ovp->valType != OPARG_TYPE_ENUMERATION) {
328                     /*
329                      *  set membership strings get allocated
330                      */
331                     AGFREE(od->optArg.argString);
332                 }
333             }
334 
335             od->optArg.argEnum = val;
336             od->fOptState = opt_state;
337             fprintf(fp, END_XML_FMT, ovp->pzName);
338             break;
339         }
340         /* FALLTHROUGH */
341 
342     case OPARG_TYPE_NUMERIC:
343         fprintf(fp, NUMB_ATR_FMT, ovp->pzName, ovp->v.longVal);
344         break;
345 
346     case OPARG_TYPE_BOOLEAN:
347         fprintf(fp, BOOL_ATR_FMT, ovp->pzName,
348                 ovp->v.boolVal ? "true" : "false");
349         break;
350 
351     case OPARG_TYPE_HIERARCHY:
352         prt_val_list(fp, ovp->pzName, ovp->v.nestVal);
353         break;
354     }
355 }
356 
357 /**
358  * Print a string value in XML format
359  *
360  * @param[in] fp          the file pointer for the save file
361  */
362 static void
363 prt_string(FILE * fp, char const * name, char const * pz)
364 {
365     fprintf(fp, OPEN_XML_FMT, name);
366     for (;;) {
367         int ch = ((int)*(pz++)) & 0xFF;
368 
369         switch (ch) {
370         case NUL: goto string_done;
371 
372         case '&':
373         case '<':
374         case '>':
375 #if __GNUC__ >= 4
376         case 1 ... (' ' - 1):
377         case ('~' + 1) ... 0xFF:
378 #endif
379             emit_special_char(fp, ch);
380             break;
381 
382         default:
383 #if __GNUC__ < 4
384             if (  ((ch >= 1) && (ch <= (' ' - 1)))
385                || ((ch >= ('~' + 1)) && (ch <= 0xFF)) ) {
386                 emit_special_char(fp, ch);
387                 break;
388             }
389 #endif
390             putc(ch, fp);
391         }
392     } string_done:;
393     fprintf(fp, END_XML_FMT, name);
394 }
395 
396 /**
397  * Print an option that can have multiple values in XML format
398  *
399  * @param[in] fp          file pointer
400  */
401 static void
402 prt_val_list(FILE * fp, char const * name, tArgList * al)
403 {
404     static int depth = 1;
405 
406     int sp_ct;
407     int opt_ct;
408     void ** opt_list;
409 
410     if (al == NULL)
411         return;
412     opt_ct   = al->useCt;
413     opt_list = (void **)al->apzArgs;
414 
415     if (opt_ct <= 0) {
416         fprintf(fp, OPEN_CLOSE_FMT, name);
417         return;
418     }
419 
420     fprintf(fp, NESTED_OPT_FMT, name);
421 
422     depth++;
423     while (--opt_ct >= 0) {
424         tOptionValue const * ovp = *(opt_list++);
425 
426         prt_value(fp, depth, NULL, ovp);
427     }
428     depth--;
429 
430     for (sp_ct = depth; --sp_ct >= 0;)
431         putc(' ', fp), putc(' ', fp);
432     fprintf(fp, "</%s>\n", name);
433 }
434 
435 /**
436  * printed a nested/hierarchical value
437  *
438  * @param[in] fp       file pointer
439  * @param[in] od       option descriptor
440  * @param[in] save_fl  include usage in comments
441  */
442 static void
443 prt_nested(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
444 {
445     int opt_ct;
446     tArgList * al = od->optCookie;
447     void ** opt_list;
448 
449     if (save_fl & SVFL_USAGE)
450         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
451 
452     /*
453      * Never show a default value if a hierarchical value is empty.
454      */
455     if (UNUSED_OPT(od) || (al == NULL))
456         return;
457 
458     opt_ct   = al->useCt;
459     opt_list = (void **)al->apzArgs;
460 
461     if (opt_ct <= 0)
462         return;
463 
464     do  {
465         tOptionValue const * base = *(opt_list++);
466         tOptionValue const * ovp = optionGetValue(base, NULL);
467 
468         if (ovp == NULL)
469             continue;
470 
471         fprintf(fp, NESTED_OPT_FMT, od->pz_Name);
472 
473         do  {
474             prt_value(fp, 1, od, ovp);
475 
476         } while (ovp = optionNextValue(base, ovp),
477                  ovp != NULL);
478 
479         fprintf(fp, "</%s>\n", od->pz_Name);
480     } while (--opt_ct > 0);
481 }
482 
483 #ifdef _MSC_VER
484 /**
485  * truncate() emulation for Microsoft C
486  *
487  * @param[in] fname  the save file name
488  * @param[in] newsz  new size of fname in octets
489  */
490 static int
491 truncate(char const* fname, size_t newsz)
492 {
493     int fd;
494     int err;
495 
496     fd = open(fname, O_RDWR);
497     if (fd < 0)
498             return fd;
499     err = _chsize_s(fd, newsz);
500     close(fd);
501     if (0 != err)
502             errno = err;
503     return err;
504 }
505 #endif /* _MSC_VER */
506 
507 /**
508  * remove the current program settings
509  *
510  * @param[in] opts  the program options structure
511  * @param[in] fname the save file name
512  */
513 static void
514 remove_settings(tOptions * opts, char const * fname)
515 {
516     size_t const name_len = strlen(opts->pzProgName);
517     tmap_info_t  map_info;
518     char *       text = text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &map_info);
519     char *       scan = text;
520 
521     for (;;) {
522         char * next = scan = strstr(scan, zCfgProg);
523         if (scan == NULL)
524             goto leave;
525 
526         scan = SPN_WHITESPACE_CHARS(scan + zCfgProg_LEN);
527         if (  (strneqvcmp(scan, opts->pzProgName, (int)name_len) == 0)
528            && (IS_END_XML_TOKEN_CHAR(scan[name_len])) )  {
529 
530             scan = next;
531             break;
532         }
533     }
534 
535     /*
536      * If not NULL, "scan" points to the "<?program" string introducing
537      * the program segment we are to remove. See if another segment follows.
538      * If so, copy text. If not se trim off this segment.
539      */
540     {
541         char * next = strstr(scan + zCfgProg_LEN, zCfgProg);
542         size_t new_sz;
543 
544         if (next == NULL)
545             new_sz = map_info.txt_size - strlen(scan);
546         else {
547             int fd = open(fname, O_RDWR);
548             if (fd < 0) return;
549             if (lseek(fd, (scan - text), SEEK_SET) < 0)
550                 scan = next;
551             else if (write(fd, next, strlen(next)) < 0)
552                 scan = next;
553             if (close(fd) < 0)
554                 scan = next;
555             new_sz = map_info.txt_size - (next - scan);
556         }
557         if (new_sz != map_info.txt_size)
558             if (truncate(fname, new_sz) < 0)
559                 scan = next; // we removed it, so shorten file
560     }
561 
562  leave:
563     text_munmap(&map_info);
564 }
565 
566 /**
567  * open the file for saving option state.
568  *
569  * @param[in] opts     the program options structure
570  * @param[in] save_fl  flags for saving data
571  * @returns the open file pointer.  It may be NULL.
572  */
573 static FILE *
574 open_sv_file(tOptions * opts, save_flags_mask_t save_fl)
575 {
576     FILE * fp;
577 
578     {
579         int   free_name = 0;
580         char const * fname = find_file_name(opts, &free_name);
581         if (fname == NULL)
582             return NULL;
583 
584         if (save_fl == 0)
585             unlink(fname);
586         else
587             remove_settings(opts, fname);
588 
589         fp = fopen(fname, "a" FOPEN_BINARY_FLAG);
590         if (fp == NULL) {
591             fprintf(stderr, zsave_warn, opts->pzProgName, fname);
592             fprintf(stderr, zNoCreat, errno, strerror(errno), fname);
593             if (free_name)
594                 AGFREE(fname);
595             return fp;
596         }
597 
598         if (free_name)
599             AGFREE(fname);
600     }
601 
602     do {
603         struct stat sbuf;
604         if (fstat(fileno(fp), &sbuf) < 0)
605             break;
606 
607         if (sbuf.st_size > zPresetFile_LEN) {
608             /* non-zero size implies save_fl is non-zero */
609             fprintf(fp, zFmtProg, opts->pzProgName);
610             return fp;
611         }
612     } while (false);
613 
614     /*
615      * We have a new file. Insert a header
616      */
617     fputs("#  ", fp);
618     {
619         char const * e = strchr(opts->pzUsageTitle, NL);
620         if (e++ != NULL)
621             fwrite(opts->pzUsageTitle, 1, e - opts->pzUsageTitle, fp);
622     }
623 
624     {
625         time_t  cur_time = time(NULL);
626         char *  time_str = ctime(&cur_time);
627 
628         fprintf(fp, zPresetFile, time_str);
629 #ifdef HAVE_ALLOCATED_CTIME
630         /*
631          *  The return values for ctime(), localtime(), and gmtime()
632          *  normally point to static data that is overwritten by each call.
633          *  The test to detect allocated ctime, so we leak the memory.
634          */
635         AGFREE(time_str);
636 #endif
637     }
638     if (save_fl != 0)
639         fprintf(fp, zFmtProg, opts->pzProgName);
640     return fp;
641 }
642 
643 /**
644  * print option without an arg
645  *
646  * @param[in] fp       file pointer
647  * @param[in] vod      value option descriptor
648  * @param[in] pod      primary option descriptor
649  * @param[in] save_fl  include usage in comments
650  */
651 static void
652 prt_no_arg_opt(FILE * fp, tOptDesc * vod, tOptDesc * pod, save_flags_mask_t save_fl)
653 {
654     /*
655      * The aliased to argument indicates whether or not the option
656      * is "disabled".  However, the original option has the name
657      * string, so we get that there, not with "vod".
658      */
659     char const * pznm =
660         (DISABLED_OPT(vod)) ? pod->pz_DisableName : pod->pz_Name;
661     /*
662      *  If the option was disabled and the disablement name is NULL,
663      *  then the disablement was caused by aliasing.
664      *  Use the name as the string to emit.
665      */
666     if (pznm == NULL)
667         pznm = pod->pz_Name;
668 
669     if (save_fl & SVFL_USAGE)
670         fprintf(fp, ao_name_use_fmt, pod->pz_Name, pod->pzText);
671     if (UNUSED_OPT(pod) && (save_fl & SVFL_DEFAULT))
672         fputs(ao_default_use, fp);
673 
674     fprintf(fp, "%s\n", pznm);
675 }
676 
677 /**
678  * print the string valued argument(s).
679  *
680  * @param[in] fp       file pointer
681  * @param[in] od       value option descriptor
682  * @param[in] save_fl  include usage in comments
683  */
684 static void
685 prt_str_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
686 {
687     if (UNUSED_OPT(od) || ((od->fOptState & OPTST_STACKED) == 0)) {
688         char const * arg = od->optArg.argString;
689         if (arg == NULL)
690             arg = "''";
691         prt_entry(fp, od, arg, save_fl);
692 
693     } else {
694         tArgList * pAL = (tArgList *)od->optCookie;
695         int        uct = pAL->useCt;
696         char const ** ppz = pAL->apzArgs;
697 
698         /*
699          *  un-disable multiple copies of disabled options.
700          */
701         if (uct > 1)
702             od->fOptState &= ~OPTST_DISABLED;
703 
704         while (uct-- > 0) {
705             prt_entry(fp, od, *(ppz++), save_fl);
706             save_fl &= ~SVFL_USAGE;
707         }
708     }
709 }
710 
711 /**
712  * print the string value of an enumeration.
713  *
714  * @param[in] fp       the file pointer to write to
715  * @param[in] od       the option descriptor with the enumerated value
716  * @param[in] save_fl  include usage in comments
717  */
718 static void
719 prt_enum_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
720 {
721     uintptr_t val = od->optArg.argEnum;
722 
723     /*
724      *  This is a magic incantation that will convert the
725      *  bit flag values back into a string suitable for printing.
726      */
727     (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od);
728     prt_entry(fp, od, VOIDP(od->optArg.argString), save_fl);
729 
730     od->optArg.argEnum = val;
731 }
732 
733 /**
734  * Print the bits set in a bit mask option.
735  *
736  * We call the option handling function with a magic value for
737  * the options pointer and it allocates and fills in the string.
738  * We print that with a call to prt_entry().
739  *
740  * @param[in] fp       the file pointer to write to
741  * @param[in] od       the option descriptor with a bit mask value type
742  * @param[in] save_fl  include usage in comments
743  */
744 static void
745 prt_set_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
746 {
747     char * list = optionMemberList(od);
748     size_t len  = strlen(list);
749     char * buf  = (char *)AGALOC(len + 3, "dir name");
750     *buf= '=';
751     memcpy(buf+1, list, len + 1);
752     prt_entry(fp, od, buf, save_fl);
753     AGFREE(buf);
754     AGFREE(list);
755 }
756 
757 /**
758  * figure out what the option file name argument is.
759  * If one can be found, call prt_entry() to emit it.
760  *
761  * @param[in] fp       the file pointer to write to.
762  * @param[in] od       the option descriptor with a bit mask value type
763  * @param[in] opts     the program options descriptor
764  * @param[in] save_fl  include usage in comments
765  */
766 static void
767 prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts, save_flags_mask_t save_fl)
768 {
769     /*
770      *  If the cookie is not NULL, then it has the file name, period.
771      *  Otherwise, if we have a non-NULL string argument, then....
772      */
773     if (od->optCookie != NULL)
774         prt_entry(fp, od, od->optCookie, save_fl);
775 
776     else if (HAS_originalOptArgArray(opts)) {
777         char const * orig =
778             opts->originalOptArgArray[od->optIndex].argString;
779 
780         if (od->optArg.argString == orig) {
781             if (save_fl)
782                 fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
783             return;
784         }
785 
786         prt_entry(fp, od, od->optArg.argString, save_fl);
787 
788     } else if (save_fl)
789         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
790 }
791 
792 /*=export_func  optionSaveFile
793  *
794  * what:  saves the option state to a file
795  *
796  * arg:   tOptions *,   opts,  program options descriptor
797  *
798  * doc:
799  *
800  * This routine will save the state of option processing to a file.  The name
801  * of that file can be specified with the argument to the @code{--save-opts}
802  * option, or by appending the @code{rcfile} attribute to the last
803  * @code{homerc} attribute.  If no @code{rcfile} attribute was specified, it
804  * will default to @code{.@i{programname}rc}.  If you wish to specify another
805  * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro.
806  *
807  * The recommend usage is as follows:
808  * @example
809  *    optionProcess(&progOptions, argc, argv);
810  *    if (i_want_a_non_standard_place_for_this)
811  *        SET_OPT_SAVE_OPTS("myfilename");
812  *    optionSaveFile(&progOptions);
813  * @end example
814  *
815  * err:
816  *
817  * If no @code{homerc} file was specified, this routine will silently return
818  * and do nothing.  If the output file cannot be created or updated, a message
819  * will be printed to @code{stderr} and the routine will return.
820 =*/
821 void
822 optionSaveFile(tOptions * opts)
823 {
824     tOptDesc *  od;
825     int         ct;
826     FILE *      fp;
827     save_flags_mask_t save_flags = SVFL_NONE;
828 
829     do {
830         char * temp_str;
831         char const * dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
832         size_t flen;
833 
834         if (dir == NULL)
835             break;
836         temp_str = strchr(dir, '>');
837         if (temp_str == NULL)
838             break;
839         if (temp_str[1] == '>')
840             save_flags = SVFL_UPDATE;
841         flen = (temp_str - dir);
842         if (flen == 0)
843             break;
844         temp_str = AGALOC(flen + 1, "flag search str");
845         memcpy(temp_str, dir, flen);
846         temp_str[flen] = NUL;
847         save_flags |= save_flags_str2mask(temp_str, SVFL_NONE);
848         AGFREE(temp_str);
849     } while (false);
850 
851     fp = open_sv_file(opts, save_flags & SVFL_UPDATE);
852     if (fp == NULL)
853         return;
854 
855     /*
856      *  FOR each of the defined options, ...
857      */
858     ct = opts->presetOptCt;
859     od = opts->pOptDesc;
860     do  {
861         tOptDesc * vod;
862 
863         /*
864          *  Equivalenced options get picked up when the equivalenced-to
865          *  option is processed. And do not save options with any state
866          *  bits in the DO_NOT_SAVE collection
867          *
868          * ** option cannot be preset
869          * #define OPTST_NO_INIT          0x0000100U
870          * ** disable from cmd line
871          * #define OPTST_NO_COMMAND       0x2000000U
872          * ** alias for other option
873          * #define OPTST_ALIAS            0x8000000U
874          */
875         if ((od->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
876             continue;
877 
878         if (  (od->optEquivIndex != NO_EQUIVALENT)
879            && (od->optEquivIndex != od->optIndex))
880             continue;
881 
882         if (UNUSED_OPT(od) && ((save_flags & SVFL_USAGE_DEFAULT_MASK) == SVFL_NONE))
883             continue;
884 
885         /*
886          *  The option argument data are found at the equivalenced-to option,
887          *  but the actual option argument type comes from the original
888          *  option descriptor.  Be careful!
889          */
890         vod = ((od->fOptState & OPTST_EQUIVALENCE) != 0)
891               ? (opts->pOptDesc + od->optActualIndex) : od;
892 
893         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
894         case OPARG_TYPE_NONE:
895             prt_no_arg_opt(fp, vod, od, save_flags);
896             break;
897 
898         case OPARG_TYPE_NUMERIC:
899             prt_entry(fp, vod, VOIDP(vod->optArg.argInt), save_flags);
900             break;
901 
902         case OPARG_TYPE_STRING:
903             prt_str_arg(fp, vod, save_flags);
904             break;
905 
906         case OPARG_TYPE_ENUMERATION:
907             prt_enum_arg(fp, vod, save_flags);
908             break;
909 
910         case OPARG_TYPE_MEMBERSHIP:
911             prt_set_arg(fp, vod, save_flags);
912             break;
913 
914         case OPARG_TYPE_BOOLEAN:
915             prt_entry(fp, vod, vod->optArg.argBool ? "true" : "false", save_flags);
916             break;
917 
918         case OPARG_TYPE_HIERARCHY:
919             prt_nested(fp, vod, save_flags);
920             break;
921 
922         case OPARG_TYPE_FILE:
923             prt_file_arg(fp, vod, opts, save_flags);
924             break;
925 
926         default:
927             break; /* cannot handle - skip it */
928         }
929     } while (od++, (--ct > 0));
930 
931     fclose(fp);
932 }
933 /** @}
934  *
935  * Local Variables:
936  * mode: C
937  * c-file-style: "stroustrup"
938  * indent-tabs-mode: nil
939  * End:
940  * end of autoopts/save.c */
941