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