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