1:
2: /*
3: * \file save.c
4: *
5: * Time-stamp: "2011-04-06 09:21:44 bkorb"
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: * This file is part of AutoOpts, a companion to AutoGen.
12: * AutoOpts is free software.
13: * AutoOpts is Copyright (c) 1992-2011 by Bruce Korb - all rights reserved
14: *
15: * AutoOpts is available under any one of two licenses. The license
16: * in use must be one of these two and the choice is under the control
17: * of the user of the license.
18: *
19: * The GNU Lesser General Public License, version 3 or later
20: * See the files "COPYING.lgplv3" and "COPYING.gplv3"
21: *
22: * The Modified Berkeley Software Distribution License
23: * See the file "COPYING.mbsd"
24: *
25: * These files have the following md5sums:
26: *
27: * 43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3
28: * 06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3
29: * 66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd
30: */
31:
32: static char const zWarn[] = "%s WARNING: cannot save options - ";
33: static char const close_xml[] = "</%s>\n";
34:
35: /* = = = START-STATIC-FORWARD = = = */
36: static tCC*
37: findDirName(tOptions* pOpts, int* p_free);
38:
39: static char const *
40: findFileName(tOptions * pOpts, int * p_free_name);
41:
42: static void
43: printEntry(
44: FILE * fp,
45: tOptDesc * p,
46: tCC* pzLA );
47:
48: static void
49: print_a_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp);
50:
51: static void
52: print_a_string(FILE * fp, char const * name, char const * pz);
53:
54: static void
55: printValueList(FILE * fp, char const * name, tArgList * al);
56:
57: static void
58: printHierarchy(FILE * fp, tOptDesc * p);
59:
60: static FILE *
61: openSaveFile(tOptions* pOpts);
62:
63: static void
64: printNoArgOpt(FILE * fp, tOptDesc * p, tOptDesc * pOD);
65:
66: static void
67: printStringArg(FILE * fp, tOptDesc * pOD);
68:
69: static void
70: printEnumArg(FILE * fp, tOptDesc * pOD);
71:
72: static void
73: printSetMemberArg(FILE * fp, tOptDesc * pOD);
74:
75: static void
76: printFileArg(FILE * fp, tOptDesc * pOD, tOptions* pOpts);
77: /* = = = END-STATIC-FORWARD = = = */
78:
79: static tCC*
80: findDirName(tOptions* pOpts, int* p_free)
81: {
82: tCC* pzDir;
83:
84: if ( (pOpts->specOptIdx.save_opts == NO_EQUIVALENT)
85: || (pOpts->specOptIdx.save_opts == 0))
86: return NULL;
87:
88: pzDir = pOpts->pOptDesc[ pOpts->specOptIdx.save_opts ].optArg.argString;
89: if ((pzDir != NULL) && (*pzDir != NUL))
90: return pzDir;
91:
92: /*
93: * This function only works if there is a directory where
94: * we can stash the RC (INI) file.
95: */
96: {
97: tCC* const* papz = pOpts->papzHomeList;
98: if (papz == NULL)
99: return NULL;
100:
101: while (papz[1] != NULL) papz++;
102: pzDir = *papz;
103: }
104:
105: /*
106: * IF it does not require deciphering an env value, then just copy it
107: */
108: if (*pzDir != '$')
109: return pzDir;
110:
111: {
112: tCC* pzEndDir = strchr(++pzDir, DIRCH);
113: char* pzFileName;
114: char* pzEnv;
115:
116: if (pzEndDir != NULL) {
117: char z[ AO_NAME_SIZE ];
118: if ((pzEndDir - pzDir) > AO_NAME_LIMIT )
119: return NULL;
120: memcpy(z, pzDir, (size_t)(pzEndDir - pzDir));
121: z[pzEndDir - pzDir] = NUL;
122: pzEnv = getenv(z);
123: } else {
124:
125: /*
126: * Make sure we can get the env value (after stripping off
127: * any trailing directory or file names)
128: */
129: pzEnv = getenv(pzDir);
130: }
131:
132: if (pzEnv == NULL) {
133: fprintf(stderr, zWarn, pOpts->pzProgName);
134: fprintf(stderr, zNotDef, pzDir);
135: return NULL;
136: }
137:
138: if (pzEndDir == NULL)
139: return pzEnv;
140:
141: {
142: size_t sz = strlen(pzEnv) + strlen(pzEndDir) + 2;
143: pzFileName = (char*)AGALOC(sz, "dir name");
144: }
145:
146: if (pzFileName == NULL)
147: return NULL;
148:
149: *p_free = 1;
150: /*
151: * Glue together the full name into the allocated memory.
152: * FIXME: We lose track of this memory.
153: */
154: sprintf(pzFileName, "%s/%s", pzEnv, pzEndDir);
155: return pzFileName;
156: }
157: }
158:
159:
160: static char const *
161: findFileName(tOptions * pOpts, int * p_free_name)
162: {
163: struct stat stBuf;
164: int free_dir_name = 0;
165:
166: char const * pzDir = findDirName(pOpts, &free_dir_name);
167: if (pzDir == NULL)
168: return NULL;
169:
170: /*
171: * See if we can find the specified directory. We use a once-only loop
172: * structure so we can bail out early.
173: */
174: if (stat(pzDir, &stBuf) != 0) do {
175: char z[AG_PATH_MAX];
176: char * dirchp;
177:
178: /*
179: * IF we could not, check to see if we got a full
180: * path to a file name that has not been created yet.
181: */
182: if (errno != ENOENT) {
183: bogus_name:
184: fprintf(stderr, zWarn, pOpts->pzProgName);
185: fprintf(stderr, zNoStat, errno, strerror(errno), pzDir);
186: if (free_dir_name)
187: AGFREE((void*)pzDir);
188: return NULL;
189: }
190:
191: /*
192: * Strip off the last component, stat the remaining string and
193: * that string must name a directory
194: */
195: dirchp = strrchr(pzDir, DIRCH);
196: if (dirchp == NULL) {
197: stBuf.st_mode = S_IFREG;
198: break; /* found directory -- viz., "." */
199: }
200:
201: if ((dirchp - pzDir) >= sizeof(z))
202: goto bogus_name;
203:
204: memcpy(z, pzDir, (size_t)(dirchp - pzDir));
205: z[dirchp - pzDir] = NUL;
206:
207: if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode))
208: goto bogus_name;
209: stBuf.st_mode = S_IFREG; /* file within this directory */
210: } while (0);
211:
212: /*
213: * IF what we found was a directory,
214: * THEN tack on the config file name
215: */
216: if (S_ISDIR(stBuf.st_mode)) {
217: size_t sz = strlen(pzDir) + strlen(pOpts->pzRcName) + 2;
218:
219: {
220: char* pzPath = (char*)AGALOC(sz, "file name");
221: #ifdef HAVE_SNPRINTF
222: snprintf(pzPath, sz, "%s/%s", pzDir, pOpts->pzRcName);
223: #else
224: sprintf(pzPath, "%s/%s", pzDir, pOpts->pzRcName);
225: #endif
226: if (free_dir_name)
227: AGFREE((void*)pzDir);
228: pzDir = pzPath;
229: free_dir_name = 1;
230: }
231:
232: /*
233: * IF we cannot stat the object for any reason other than
234: * it does not exist, then we bail out
235: */
236: if (stat(pzDir, &stBuf) != 0) {
237: if (errno != ENOENT) {
238: fprintf(stderr, zWarn, pOpts->pzProgName);
239: fprintf(stderr, zNoStat, errno, strerror(errno),
240: pzDir);
241: AGFREE((void*)pzDir);
242: return NULL;
243: }
244:
245: /*
246: * It does not exist yet, but it will be a regular file
247: */
248: stBuf.st_mode = S_IFREG;
249: }
250: }
251:
252: /*
253: * Make sure that whatever we ultimately found, that it either is
254: * or will soon be a file.
255: */
256: if (! S_ISREG(stBuf.st_mode)) {
257: fprintf(stderr, zWarn, pOpts->pzProgName);
258: fprintf(stderr, zNotFile, pzDir);
259: if (free_dir_name)
260: AGFREE((void*)pzDir);
261: return NULL;
262: }
263:
264: /*
265: * Get rid of the old file
266: */
267: unlink(pzDir);
268: *p_free_name = free_dir_name;
269: return pzDir;
270: }
271:
272:
273: static void
274: printEntry(
275: FILE * fp,
276: tOptDesc * p,
277: tCC* pzLA )
278: {
279: /*
280: * There is an argument. Pad the name so values line up.
281: * Not disabled *OR* this got equivalenced to another opt,
282: * then use current option name.
283: * Otherwise, there must be a disablement name.
284: */
285: {
286: char const * pz;
287: if (! DISABLED_OPT(p) || (p->optEquivIndex != NO_EQUIVALENT))
288: pz = p->pz_Name;
289: else
290: pz = p->pz_DisableName;
291:
292: fprintf(fp, "%-18s", pz);
293: }
294: /*
295: * IF the option is numeric only,
296: * THEN the char pointer is really the number
297: */
298: if (OPTST_GET_ARGTYPE(p->fOptState) == OPARG_TYPE_NUMERIC)
299: fprintf(fp, " %d\n", (int)(t_word)pzLA);
300:
301: /*
302: * OTHERWISE, FOR each line of the value text, ...
303: */
304: else if (pzLA == NULL)
305: fputc('\n', fp);
306:
307: else {
308: fputc(' ', fp); fputc(' ', fp);
309: for (;;) {
310: tCC* pzNl = strchr(pzLA, '\n');
311:
312: /*
313: * IF this is the last line
314: * THEN bail and print it
315: */
316: if (pzNl == NULL)
317: break;
318:
319: /*
320: * Print the continuation and the text from the current line
321: */
322: (void)fwrite(pzLA, (size_t)(pzNl - pzLA), (size_t)1, fp);
323: pzLA = pzNl+1; /* advance the Last Arg pointer */
324: fputs("\\\n", fp);
325: }
326:
327: /*
328: * Terminate the entry
329: */
330: fputs(pzLA, fp);
331: fputc('\n', fp);
332: }
333: }
334:
335:
336: static void
337: print_a_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp)
338: {
339: static char const bool_atr[] = "<%1$s type=boolean>%2$s</%1$s>\n";
340: static char const numb_atr[] = "<%1$s type=integer>0x%2$lX</%1$s>\n";
341: static char const type_atr[] = "<%s type=%s>";
342: static char const null_atr[] = "<%s/>\n";
343:
344: while (--depth >= 0)
345: putc(' ', fp), putc(' ', fp);
346:
347: switch (ovp->valType) {
348: default:
349: case OPARG_TYPE_NONE:
350: fprintf(fp, null_atr, ovp->pzName);
351: break;
352:
353: case OPARG_TYPE_STRING:
354: print_a_string(fp, ovp->pzName, ovp->v.strVal);
355: break;
356:
357: case OPARG_TYPE_ENUMERATION:
358: case OPARG_TYPE_MEMBERSHIP:
359: if (pOD != NULL) {
360: tAoUI opt_state = pOD->fOptState;
361: uintptr_t val = pOD->optArg.argEnum;
362: char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION)
363: ? "keyword" : "set-membership";
364:
365: fprintf(fp, type_atr, ovp->pzName, typ);
366:
367: /*
368: * This is a magic incantation that will convert the
369: * bit flag values back into a string suitable for printing.
370: */
371: (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD );
372: if (pOD->optArg.argString != NULL) {
373: fputs(pOD->optArg.argString, fp);
374:
375: if (ovp->valType != OPARG_TYPE_ENUMERATION) {
376: /*
377: * set membership strings get allocated
378: */
379: AGFREE((void*)pOD->optArg.argString);
380: }
381: }
382:
383: pOD->optArg.argEnum = val;
384: pOD->fOptState = opt_state;
385: fprintf(fp, close_xml, ovp->pzName);
386: break;
387: }
388: /* FALLTHROUGH */
389:
390: case OPARG_TYPE_NUMERIC:
391: fprintf(fp, numb_atr, ovp->pzName, ovp->v.longVal);
392: break;
393:
394: case OPARG_TYPE_BOOLEAN:
395: fprintf(fp, bool_atr, ovp->pzName,
396: ovp->v.boolVal ? "true" : "false");
397: break;
398:
399: case OPARG_TYPE_HIERARCHY:
400: printValueList(fp, ovp->pzName, ovp->v.nestVal);
401: break;
402: }
403: }
404:
405:
406: static void
407: print_a_string(FILE * fp, char const * name, char const * pz)
408: {
409: static char const open_atr[] = "<%s>";
410:
411: fprintf(fp, open_atr, name);
412: for (;;) {
413: int ch = ((int)*(pz++)) & 0xFF;
414:
415: switch (ch) {
416: case NUL: goto string_done;
417:
418: case '&':
419: case '<':
420: case '>':
421: #if __GNUC__ >= 4
422: case 1 ... (' ' - 1):
423: case ('~' + 1) ... 0xFF:
424: #endif
425: emit_special_char(fp, ch);
426: break;
427:
428: default:
429: #if __GNUC__ < 4
430: if ( ((ch >= 1) && (ch <= (' ' - 1)))
431: || ((ch >= ('~' + 1)) && (ch <= 0xFF)) ) {
432: emit_special_char(fp, ch);
433: break;
434: }
435: #endif
436: putc(ch, fp);
437: }
438: } string_done:;
439: fprintf(fp, close_xml, name);
440: }
441:
442:
443: static void
444: printValueList(FILE * fp, char const * name, tArgList * al)
445: {
446: static int depth = 1;
447:
448: int sp_ct;
449: int opt_ct;
450: void ** opt_list;
451:
452: if (al == NULL)
453: return;
454: opt_ct = al->useCt;
455: opt_list = (void **)al->apzArgs;
456:
457: if (opt_ct <= 0) {
458: fprintf(fp, "<%s/>\n", name);
459: return;
460: }
461:
462: fprintf(fp, "<%s type=nested>\n", name);
463:
464: depth++;
465: while (--opt_ct >= 0) {
466: tOptionValue const * ovp = *(opt_list++);
467:
468: print_a_value(fp, depth, NULL, ovp);
469: }
470: depth--;
471:
472: for (sp_ct = depth; --sp_ct >= 0;)
473: putc(' ', fp), putc(' ', fp);
474: fprintf(fp, "</%s>\n", name);
475: }
476:
477:
478: static void
479: printHierarchy(FILE * fp, tOptDesc * p)
480: {
481: int opt_ct;
482: tArgList * al = p->optCookie;
483: void ** opt_list;
484:
485: if (al == NULL)
486: return;
487:
488: opt_ct = al->useCt;
489: opt_list = (void **)al->apzArgs;
490:
491: if (opt_ct <= 0)
492: return;
493:
494: do {
495: tOptionValue const * base = *(opt_list++);
496: tOptionValue const * ovp = optionGetValue(base, NULL);
497:
498: if (ovp == NULL)
499: continue;
500:
501: fprintf(fp, "<%s type=nested>\n", p->pz_Name);
502:
503: do {
504: print_a_value(fp, 1, p, ovp);
505:
506: } while (ovp = optionNextValue(base, ovp),
507: ovp != NULL);
508:
509: fprintf(fp, "</%s>\n", p->pz_Name);
510: } while (--opt_ct > 0);
511: }
512:
513:
514: static FILE *
515: openSaveFile(tOptions* pOpts)
516: {
517: FILE* fp;
518:
519: {
520: int free_name = 0;
521: tCC* pzFName = findFileName(pOpts, &free_name);
522: if (pzFName == NULL)
523: return NULL;
524:
525: fp = fopen(pzFName, "w" FOPEN_BINARY_FLAG);
526: if (fp == NULL) {
527: fprintf(stderr, zWarn, pOpts->pzProgName);
528: fprintf(stderr, zNoCreat, errno, strerror(errno), pzFName);
529: if (free_name)
530: AGFREE((void*) pzFName );
531: return fp;
532: }
533:
534: if (free_name)
535: AGFREE((void*)pzFName);
536: }
537:
538: {
539: char const* pz = pOpts->pzUsageTitle;
540: fputs("# ", fp);
541: do { fputc(*pz, fp); } while (*(pz++) != '\n');
542: }
543:
544: {
545: time_t timeVal = time(NULL);
546: char* pzTime = ctime(&timeVal);
547:
548: fprintf(fp, zPresetFile, pzTime);
549: #ifdef HAVE_ALLOCATED_CTIME
550: /*
551: * The return values for ctime(), localtime(), and gmtime()
552: * normally point to static data that is overwritten by each call.
553: * The test to detect allocated ctime, so we leak the memory.
554: */
555: AGFREE((void*)pzTime);
556: #endif
557: }
558:
559: return fp;
560: }
561:
562: static void
563: printNoArgOpt(FILE * fp, tOptDesc * p, tOptDesc * pOD)
564: {
565: /*
566: * The aliased to argument indicates whether or not the option
567: * is "disabled". However, the original option has the name
568: * string, so we get that there, not with "p".
569: */
570: char const * pznm =
571: (DISABLED_OPT(p)) ? pOD->pz_DisableName : pOD->pz_Name;
572: /*
573: * If the option was disabled and the disablement name is NULL,
574: * then the disablement was caused by aliasing.
575: * Use the name as the string to emit.
576: */
577: if (pznm == NULL)
578: pznm = pOD->pz_Name;
579:
580: fprintf(fp, "%s\n", pznm);
581: }
582:
583: static void
584: printStringArg(FILE * fp, tOptDesc * pOD)
585: {
586: if (pOD->fOptState & OPTST_STACKED) {
587: tArgList* pAL = (tArgList*)pOD->optCookie;
588: int uct = pAL->useCt;
589: tCC** ppz = pAL->apzArgs;
590:
591: /*
592: * un-disable multiple copies of disabled options.
593: */
594: if (uct > 1)
595: pOD->fOptState &= ~OPTST_DISABLED;
596:
597: while (uct-- > 0)
598: printEntry(fp, pOD, *(ppz++));
599: } else {
600: printEntry(fp, pOD, pOD->optArg.argString);
601: }
602: }
603:
604: static void
605: printEnumArg(FILE * fp, tOptDesc * pOD)
606: {
607: uintptr_t val = pOD->optArg.argEnum;
608:
609: /*
610: * This is a magic incantation that will convert the
611: * bit flag values back into a string suitable for printing.
612: */
613: (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
614: printEntry(fp, pOD, (void*)(pOD->optArg.argString));
615:
616: pOD->optArg.argEnum = val;
617: }
618:
619: static void
620: printSetMemberArg(FILE * fp, tOptDesc * pOD)
621: {
622: uintptr_t val = pOD->optArg.argEnum;
623:
624: /*
625: * This is a magic incantation that will convert the
626: * bit flag values back into a string suitable for printing.
627: */
628: (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
629: printEntry(fp, pOD, (void*)(pOD->optArg.argString));
630:
631: if (pOD->optArg.argString != NULL) {
632: /*
633: * set membership strings get allocated
634: */
635: AGFREE((void*)pOD->optArg.argString);
636: pOD->fOptState &= ~OPTST_ALLOC_ARG;
637: }
638:
639: pOD->optArg.argEnum = val;
640: }
641:
642: static void
643: printFileArg(FILE * fp, tOptDesc * pOD, tOptions* pOpts)
644: {
645: /*
646: * If the cookie is not NULL, then it has the file name, period.
647: * Otherwise, if we have a non-NULL string argument, then....
648: */
649: if (pOD->optCookie != NULL)
650: printEntry(fp, pOD, pOD->optCookie);
651:
652: else if (HAS_originalOptArgArray(pOpts)) {
653: char const * orig =
654: pOpts->originalOptArgArray[pOD->optIndex].argString;
655:
656: if (pOD->optArg.argString == orig)
657: return;
658:
659: printEntry(fp, pOD, pOD->optArg.argString);
660: }
661: }
662:
663:
664: /*=export_func optionSaveFile
665: *
666: * what: saves the option state to a file
667: *
668: * arg: tOptions*, pOpts, program options descriptor
669: *
670: * doc:
671: *
672: * This routine will save the state of option processing to a file. The name
673: * of that file can be specified with the argument to the @code{--save-opts}
674: * option, or by appending the @code{rcfile} attribute to the last
675: * @code{homerc} attribute. If no @code{rcfile} attribute was specified, it
676: * will default to @code{.@i{programname}rc}. If you wish to specify another
677: * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro.
678: *
679: * The recommend usage is as follows:
680: * @example
681: * optionProcess(&progOptions, argc, argv);
682: * if (i_want_a_non_standard_place_for_this)
683: * SET_OPT_SAVE_OPTS("myfilename");
684: * optionSaveFile(&progOptions);
685: * @end example
686: *
687: * err:
688: *
689: * If no @code{homerc} file was specified, this routine will silently return
690: * and do nothing. If the output file cannot be created or updated, a message
691: * will be printed to @code{stderr} and the routine will return.
692: =*/
693: void
694: optionSaveFile(tOptions* pOpts)
695: {
696: tOptDesc* pOD;
697: int ct;
698: FILE* fp = openSaveFile(pOpts);
699:
700: if (fp == NULL)
701: return;
702:
703: /*
704: * FOR each of the defined options, ...
705: */
706: ct = pOpts->presetOptCt;
707: pOD = pOpts->pOptDesc;
708: do {
709: tOptDesc* p;
710:
711: /*
712: * IF the option has not been defined
713: * OR it does not take an initialization value
714: * OR it is equivalenced to another option
715: * THEN continue (ignore it)
716: *
717: * Equivalenced options get picked up when the equivalenced-to
718: * option is processed.
719: */
720: if (UNUSED_OPT(pOD))
721: continue;
722:
723: if ((pOD->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
724: continue;
725:
726: if ( (pOD->optEquivIndex != NO_EQUIVALENT)
727: && (pOD->optEquivIndex != pOD->optIndex))
728: continue;
729:
730: /*
731: * The option argument data are found at the equivalenced-to option,
732: * but the actual option argument type comes from the original
733: * option descriptor. Be careful!
734: */
735: p = ((pOD->fOptState & OPTST_EQUIVALENCE) != 0)
736: ? (pOpts->pOptDesc + pOD->optActualIndex) : pOD;
737:
738: switch (OPTST_GET_ARGTYPE(pOD->fOptState)) {
739: case OPARG_TYPE_NONE:
740: printNoArgOpt(fp, p, pOD);
741: break;
742:
743: case OPARG_TYPE_NUMERIC:
744: printEntry(fp, p, (void*)(p->optArg.argInt));
745: break;
746:
747: case OPARG_TYPE_STRING:
748: printStringArg(fp, p);
749: break;
750:
751: case OPARG_TYPE_ENUMERATION:
752: printEnumArg(fp, p);
753: break;
754:
755: case OPARG_TYPE_MEMBERSHIP:
756: printSetMemberArg(fp, p);
757: break;
758:
759: case OPARG_TYPE_BOOLEAN:
760: printEntry(fp, p, p->optArg.argBool ? "true" : "false");
761: break;
762:
763: case OPARG_TYPE_HIERARCHY:
764: printHierarchy(fp, p);
765: break;
766:
767: case OPARG_TYPE_FILE:
768: printFileArg(fp, p, pOpts);
769: break;
770:
771: default:
772: break; /* cannot handle - skip it */
773: }
774: } while (pOD++, (--ct > 0));
775:
776: fclose(fp);
777: }
778: /*
779: * Local Variables:
780: * mode: C
781: * c-file-style: "stroustrup"
782: * indent-tabs-mode: nil
783: * End:
784: * end of autoopts/save.c */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>