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