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 *
find_dir_name(tOptions * opts,int * p_free)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 *
find_file_name(tOptions * opts,int * p_free_name)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
prt_entry(FILE * fp,tOptDesc * od,char const * l_arg,save_flags_mask_t save_fl)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
prt_value(FILE * fp,int depth,tOptDesc * od,tOptionValue const * ovp)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
prt_string(FILE * fp,char const * name,char const * pz)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
prt_val_list(FILE * fp,char const * name,tArgList * al)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
prt_nested(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)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 /**
484  * remove the current program settings
485  *
486  * @param[in] opts  the program options structure
487  * @param[in] fname the save file name
488  */
489 static void
remove_settings(tOptions * opts,char const * fname)490 remove_settings(tOptions * opts, char const * fname)
491 {
492     size_t const name_len = strlen(opts->pzProgName);
493     tmap_info_t  map_info;
494     char *       text = text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &map_info);
495     char *       scan = text;
496 
497     for (;;) {
498         char * next = scan = strstr(scan, zCfgProg);
499         if (scan == NULL)
500             goto leave;
501 
502         scan = SPN_WHITESPACE_CHARS(scan + zCfgProg_LEN);
503         if (  (strneqvcmp(scan, opts->pzProgName, (int)name_len) == 0)
504            && (IS_END_XML_TOKEN_CHAR(scan[name_len])) )  {
505 
506             scan = next;
507             break;
508         }
509     }
510 
511     /*
512      * If not NULL, "scan" points to the "<?program" string introducing
513      * the program segment we are to remove. See if another segment follows.
514      * If so, copy text. If not se trim off this segment.
515      */
516     {
517         char * next = strstr(scan + zCfgProg_LEN, zCfgProg);
518         size_t new_sz;
519 
520         if (next == NULL)
521             new_sz = map_info.txt_size - strlen(scan);
522         else {
523             int fd = open(fname, O_RDWR);
524             if (fd < 0) return;
525             if (lseek(fd, (scan - text), SEEK_SET) < 0)
526                 scan = next;
527             else if (write(fd, next, strlen(next)) < 0)
528                 scan = next;
529             if (close(fd) < 0)
530                 scan = next;
531             new_sz = map_info.txt_size - (next - scan);
532         }
533         if (new_sz != map_info.txt_size)
534             if (truncate(fname, new_sz) < 0)
535                 scan = next; // we removed it, so shorten file
536     }
537 
538  leave:
539     text_munmap(&map_info);
540 }
541 
542 /**
543  * open the file for saving option state.
544  *
545  * @param[in] opts     the program options structure
546  * @param[in] save_fl  flags for saving data
547  * @returns the open file pointer.  It may be NULL.
548  */
549 static FILE *
open_sv_file(tOptions * opts,save_flags_mask_t save_fl)550 open_sv_file(tOptions * opts, save_flags_mask_t save_fl)
551 {
552     FILE * fp;
553 
554     {
555         int   free_name = 0;
556         char const * fname = find_file_name(opts, &free_name);
557         if (fname == NULL)
558             return NULL;
559 
560         if (save_fl == 0)
561             unlink(fname);
562         else
563             remove_settings(opts, fname);
564 
565         fp = fopen(fname, "a" FOPEN_BINARY_FLAG);
566         if (fp == NULL) {
567             fprintf(stderr, zsave_warn, opts->pzProgName, fname);
568             fprintf(stderr, zNoCreat, errno, strerror(errno), fname);
569             if (free_name)
570                 AGFREE(fname);
571             return fp;
572         }
573 
574         if (free_name)
575             AGFREE(fname);
576     }
577 
578     do {
579         struct stat sbuf;
580         if (fstat(fileno(fp), &sbuf) < 0)
581             break;
582 
583         if (sbuf.st_size > zPresetFile_LEN) {
584             /* non-zero size implies save_fl is non-zero */
585             fprintf(fp, zFmtProg, opts->pzProgName);
586             return fp;
587         }
588     } while (false);
589 
590     /*
591      * We have a new file. Insert a header
592      */
593     fputs("#  ", fp);
594     {
595         char const * e = strchr(opts->pzUsageTitle, NL);
596         if (e++ != NULL)
597             fwrite(opts->pzUsageTitle, 1, e - opts->pzUsageTitle, fp);
598     }
599 
600     {
601         time_t  cur_time = time(NULL);
602         char *  time_str = ctime(&cur_time);
603 
604         fprintf(fp, zPresetFile, time_str);
605 #ifdef HAVE_ALLOCATED_CTIME
606         /*
607          *  The return values for ctime(), localtime(), and gmtime()
608          *  normally point to static data that is overwritten by each call.
609          *  The test to detect allocated ctime, so we leak the memory.
610          */
611         AGFREE(time_str);
612 #endif
613     }
614     if (save_fl != 0)
615         fprintf(fp, zFmtProg, opts->pzProgName);
616     return fp;
617 }
618 
619 /**
620  * print option without an arg
621  *
622  * @param[in] fp       file pointer
623  * @param[in] vod      value option descriptor
624  * @param[in] pod      primary option descriptor
625  * @param[in] save_fl  include usage in comments
626  */
627 static void
prt_no_arg_opt(FILE * fp,tOptDesc * vod,tOptDesc * pod,save_flags_mask_t save_fl)628 prt_no_arg_opt(FILE * fp, tOptDesc * vod, tOptDesc * pod, save_flags_mask_t save_fl)
629 {
630     /*
631      * The aliased to argument indicates whether or not the option
632      * is "disabled".  However, the original option has the name
633      * string, so we get that there, not with "vod".
634      */
635     char const * pznm =
636         (DISABLED_OPT(vod)) ? pod->pz_DisableName : pod->pz_Name;
637     /*
638      *  If the option was disabled and the disablement name is NULL,
639      *  then the disablement was caused by aliasing.
640      *  Use the name as the string to emit.
641      */
642     if (pznm == NULL)
643         pznm = pod->pz_Name;
644 
645     if (save_fl & SVFL_USAGE)
646         fprintf(fp, ao_name_use_fmt, pod->pz_Name, pod->pzText);
647     if (UNUSED_OPT(pod) && (save_fl & SVFL_DEFAULT))
648         fputs(ao_default_use, fp);
649 
650     fprintf(fp, "%s\n", pznm);
651 }
652 
653 /**
654  * print the string valued argument(s).
655  *
656  * @param[in] fp       file pointer
657  * @param[in] od       value option descriptor
658  * @param[in] save_fl  include usage in comments
659  */
660 static void
prt_str_arg(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)661 prt_str_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
662 {
663     if (UNUSED_OPT(od) || ((od->fOptState & OPTST_STACKED) == 0)) {
664         char const * arg = od->optArg.argString;
665         if (arg == NULL)
666             arg = "''";
667         prt_entry(fp, od, arg, save_fl);
668 
669     } else {
670         tArgList * pAL = (tArgList *)od->optCookie;
671         int        uct = pAL->useCt;
672         char const ** ppz = pAL->apzArgs;
673 
674         /*
675          *  un-disable multiple copies of disabled options.
676          */
677         if (uct > 1)
678             od->fOptState &= ~OPTST_DISABLED;
679 
680         while (uct-- > 0) {
681             prt_entry(fp, od, *(ppz++), save_fl);
682             save_fl &= ~SVFL_USAGE;
683         }
684     }
685 }
686 
687 /**
688  * print the string value of an enumeration.
689  *
690  * @param[in] fp       the file pointer to write to
691  * @param[in] od       the option descriptor with the enumerated value
692  * @param[in] save_fl  include usage in comments
693  */
694 static void
prt_enum_arg(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)695 prt_enum_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
696 {
697     uintptr_t val = od->optArg.argEnum;
698 
699     /*
700      *  This is a magic incantation that will convert the
701      *  bit flag values back into a string suitable for printing.
702      */
703     (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od);
704     prt_entry(fp, od, VOIDP(od->optArg.argString), save_fl);
705 
706     od->optArg.argEnum = val;
707 }
708 
709 /**
710  * Print the bits set in a bit mask option.
711  *
712  * We call the option handling function with a magic value for
713  * the options pointer and it allocates and fills in the string.
714  * We print that with a call to prt_entry().
715  *
716  * @param[in] fp       the file pointer to write to
717  * @param[in] od       the option descriptor with a bit mask value type
718  * @param[in] save_fl  include usage in comments
719  */
720 static void
prt_set_arg(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)721 prt_set_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
722 {
723     char * list = optionMemberList(od);
724     size_t len  = strlen(list);
725     char * buf  = (char *)AGALOC(len + 3, "dir name");
726     *buf= '=';
727     memcpy(buf+1, list, len + 1);
728     prt_entry(fp, od, buf, save_fl);
729     AGFREE(buf);
730     AGFREE(list);
731 }
732 
733 /**
734  * figure out what the option file name argument is.
735  * If one can be found, call prt_entry() to emit it.
736  *
737  * @param[in] fp       the file pointer to write to.
738  * @param[in] od       the option descriptor with a bit mask value type
739  * @param[in] opts     the program options descriptor
740  * @param[in] save_fl  include usage in comments
741  */
742 static void
prt_file_arg(FILE * fp,tOptDesc * od,tOptions * opts,save_flags_mask_t save_fl)743 prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts, save_flags_mask_t save_fl)
744 {
745     /*
746      *  If the cookie is not NULL, then it has the file name, period.
747      *  Otherwise, if we have a non-NULL string argument, then....
748      */
749     if (od->optCookie != NULL)
750         prt_entry(fp, od, od->optCookie, save_fl);
751 
752     else if (HAS_originalOptArgArray(opts)) {
753         char const * orig =
754             opts->originalOptArgArray[od->optIndex].argString;
755 
756         if (od->optArg.argString == orig) {
757             if (save_fl)
758                 fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
759             return;
760         }
761 
762         prt_entry(fp, od, od->optArg.argString, save_fl);
763 
764     } else if (save_fl)
765         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
766 }
767 
768 /*=export_func  optionSaveFile
769  *
770  * what:  saves the option state to a file
771  *
772  * arg:   tOptions *,   opts,  program options descriptor
773  *
774  * doc:
775  *
776  * This routine will save the state of option processing to a file.  The name
777  * of that file can be specified with the argument to the @code{--save-opts}
778  * option, or by appending the @code{rcfile} attribute to the last
779  * @code{homerc} attribute.  If no @code{rcfile} attribute was specified, it
780  * will default to @code{.@i{programname}rc}.  If you wish to specify another
781  * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro.
782  *
783  * The recommend usage is as follows:
784  * @example
785  *    optionProcess(&progOptions, argc, argv);
786  *    if (i_want_a_non_standard_place_for_this)
787  *        SET_OPT_SAVE_OPTS("myfilename");
788  *    optionSaveFile(&progOptions);
789  * @end example
790  *
791  * err:
792  *
793  * If no @code{homerc} file was specified, this routine will silently return
794  * and do nothing.  If the output file cannot be created or updated, a message
795  * will be printed to @code{stderr} and the routine will return.
796 =*/
797 void
optionSaveFile(tOptions * opts)798 optionSaveFile(tOptions * opts)
799 {
800     tOptDesc *  od;
801     int         ct;
802     FILE *      fp;
803     save_flags_mask_t save_flags = SVFL_NONE;
804 
805     do {
806         char * temp_str;
807         char const * dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
808         size_t flen;
809 
810         if (dir == NULL)
811             break;
812         temp_str = strchr(dir, '>');
813         if (temp_str == NULL)
814             break;
815         if (temp_str[1] == '>')
816             save_flags = SVFL_UPDATE;
817         flen = (temp_str - dir);
818         if (flen == 0)
819             break;
820         temp_str = AGALOC(flen + 1, "flag search str");
821         memcpy(temp_str, dir, flen);
822         temp_str[flen] = NUL;
823         save_flags |= save_flags_str2mask(temp_str, SVFL_NONE);
824         AGFREE(temp_str);
825     } while (false);
826 
827     fp = open_sv_file(opts, save_flags & SVFL_UPDATE);
828     if (fp == NULL)
829         return;
830 
831     /*
832      *  FOR each of the defined options, ...
833      */
834     ct = opts->presetOptCt;
835     od = opts->pOptDesc;
836     do  {
837         tOptDesc * vod;
838 
839         /*
840          *  Equivalenced options get picked up when the equivalenced-to
841          *  option is processed. And do not save options with any state
842          *  bits in the DO_NOT_SAVE collection
843          *
844          * ** option cannot be preset
845          * #define OPTST_NO_INIT          0x0000100U
846          * ** disable from cmd line
847          * #define OPTST_NO_COMMAND       0x2000000U
848          * ** alias for other option
849          * #define OPTST_ALIAS            0x8000000U
850          */
851         if ((od->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
852             continue;
853 
854         if (  (od->optEquivIndex != NO_EQUIVALENT)
855            && (od->optEquivIndex != od->optIndex))
856             continue;
857 
858         if (UNUSED_OPT(od) && ((save_flags & SVFL_USAGE_DEFAULT_MASK) == SVFL_NONE))
859             continue;
860 
861         /*
862          *  The option argument data are found at the equivalenced-to option,
863          *  but the actual option argument type comes from the original
864          *  option descriptor.  Be careful!
865          */
866         vod = ((od->fOptState & OPTST_EQUIVALENCE) != 0)
867               ? (opts->pOptDesc + od->optActualIndex) : od;
868 
869         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
870         case OPARG_TYPE_NONE:
871             prt_no_arg_opt(fp, vod, od, save_flags);
872             break;
873 
874         case OPARG_TYPE_NUMERIC:
875             prt_entry(fp, vod, VOIDP(vod->optArg.argInt), save_flags);
876             break;
877 
878         case OPARG_TYPE_STRING:
879             prt_str_arg(fp, vod, save_flags);
880             break;
881 
882         case OPARG_TYPE_ENUMERATION:
883             prt_enum_arg(fp, vod, save_flags);
884             break;
885 
886         case OPARG_TYPE_MEMBERSHIP:
887             prt_set_arg(fp, vod, save_flags);
888             break;
889 
890         case OPARG_TYPE_BOOLEAN:
891             prt_entry(fp, vod, vod->optArg.argBool ? "true" : "false", save_flags);
892             break;
893 
894         case OPARG_TYPE_HIERARCHY:
895             prt_nested(fp, vod, save_flags);
896             break;
897 
898         case OPARG_TYPE_FILE:
899             prt_file_arg(fp, vod, opts, save_flags);
900             break;
901 
902         default:
903             break; /* cannot handle - skip it */
904         }
905     } while (od++, (--ct > 0));
906 
907     fclose(fp);
908 }
909 /** @}
910  *
911  * Local Variables:
912  * mode: C
913  * c-file-style: "stroustrup"
914  * indent-tabs-mode: nil
915  * End:
916  * end of autoopts/save.c */
917