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 #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
truncate(char const * fname,size_t newsz)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
remove_settings(tOptions * opts,char const * fname)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 *
open_sv_file(tOptions * opts,save_flags_mask_t save_fl)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
prt_no_arg_opt(FILE * fp,tOptDesc * vod,tOptDesc * pod,save_flags_mask_t save_fl)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
prt_str_arg(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)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
prt_enum_arg(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)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
prt_set_arg(FILE * fp,tOptDesc * od,save_flags_mask_t save_fl)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
prt_file_arg(FILE * fp,tOptDesc * od,tOptions * opts,save_flags_mask_t save_fl)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
optionSaveFile(tOptions * opts)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