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