Coverage Report

Created: 2026-02-26 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ntp-dev/sntp/libopts/save.c
Line
Count
Source
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 *
42
find_dir_name(tOptions * opts, int * p_free)
43
0
{
44
0
    char const * dir;
45
46
0
    if (  (opts->specOptIdx.save_opts == NO_EQUIVALENT)
47
0
       || (opts->specOptIdx.save_opts == 0))
48
0
        return NULL;
49
50
0
    dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
51
0
    if ((dir != NULL) && (*dir != NUL)) {
52
0
        char const * pz = strchr(dir, '>');
53
0
        if (pz == NULL)
54
0
            return dir;
55
0
        while (*(++pz) == '>')  ;
56
0
        pz += strspn(pz, " \t");
57
0
        dir = pz;
58
0
        if (*dir != NUL)
59
0
            return dir;
60
0
    }
61
62
0
    if (opts->papzHomeList == NULL)
63
0
        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
0
    for (int idx = 0;; idx++) {
70
0
        char f_name[ AG_PATH_MAX+1 ];
71
72
0
        dir = opts->papzHomeList[idx];
73
74
0
        switch (*dir) {
75
0
        case '$':
76
0
            break;
77
0
        case NUL:
78
0
            continue;
79
0
        default:
80
0
            return dir;
81
0
        }
82
0
        if (optionMakePath(f_name, (int)sizeof(f_name), dir, opts->pzProgPath)) {
83
0
            *p_free = true;
84
0
            AGDUPSTR(dir, f_name, "homerc");
85
0
            return dir;
86
0
        }
87
0
    }
88
0
    return NULL;
89
0
}
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 *
98
find_file_name(tOptions * opts, int * p_free_name)
99
0
{
100
0
    struct stat stBuf;
101
0
    int    free_dir_name = 0;
102
103
0
    char const * res = find_dir_name(opts, &free_dir_name);
104
0
    if (res == NULL)
105
0
        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
0
    if (stat(res, &stBuf) != 0) do {
112
0
        char z[AG_PATH_MAX];
113
0
        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
0
        if (errno != ENOENT) {
120
0
        bogus_name:
121
0
            fprintf(stderr, zsave_warn, opts->pzProgName, res);
122
0
            fprintf(stderr, zNoStat, errno, strerror(errno), res);
123
0
            if (free_dir_name)
124
0
                AGFREE(res);
125
0
            return NULL;
126
0
        }
127
128
        /*
129
         *  Strip off the last component, stat the remaining string and
130
         *  that string must name a directory
131
         */
132
0
        dirchp = strrchr(res, DIRCH);
133
0
        if (dirchp == NULL) {
134
0
            stBuf.st_mode = S_IFREG;
135
0
            break; /* found directory -- viz.,  "." */
136
0
        }
137
138
0
        if ((size_t)(dirchp - res) >= sizeof(z))
139
0
            goto bogus_name;
140
141
0
        memcpy(z, res, (size_t)(dirchp - res));
142
0
        z[dirchp - res] = NUL;
143
144
0
        if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode))
145
0
            goto bogus_name;
146
0
        stBuf.st_mode = S_IFREG; /* file within this directory */
147
0
    } while (false);
148
149
    /*
150
     *  IF what we found was a directory,
151
     *  THEN tack on the config file name
152
     */
153
0
    if (S_ISDIR(stBuf.st_mode)) {
154
155
0
        {
156
0
            size_t sz = strlen(res) + strlen(opts->pzRcName) + 2;
157
0
            char * pzPath = (char *)AGALOC(sz, "file name");
158
0
            if (   snprintf(pzPath, sz, "%s/%s", res, opts->pzRcName)
159
0
                >= (int)sz)
160
0
                option_exits(EXIT_FAILURE);
161
162
0
            if (free_dir_name)
163
0
                AGFREE(res);
164
0
            res = pzPath;
165
0
            free_dir_name = 1;
166
0
        }
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
0
        if (stat(res, &stBuf) != 0) {
173
0
            if (errno != ENOENT) {
174
0
                fprintf(stderr, zsave_warn, opts->pzProgName, res);
175
0
                fprintf(stderr, zNoStat, errno, strerror(errno),
176
0
                        res);
177
0
                AGFREE(res);
178
0
                return NULL;
179
0
            }
180
181
            /*
182
             *  It does not exist yet, but it will be a regular file
183
             */
184
0
            stBuf.st_mode = S_IFREG;
185
0
        }
186
0
    }
187
188
    /*
189
     *  Make sure that whatever we ultimately found, that it either is
190
     *  or will soon be a file.
191
     */
192
0
    if (! S_ISREG(stBuf.st_mode)) {
193
0
        fprintf(stderr, zsave_warn, opts->pzProgName, res);
194
0
        if (free_dir_name)
195
0
            AGFREE(res);
196
0
        return NULL;
197
0
    }
198
199
    /*
200
     *  Get rid of the old file
201
     */
202
0
    *p_free_name = free_dir_name;
203
0
    return res;
204
0
}
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
215
prt_entry(FILE * fp, tOptDesc * od, char const * l_arg, save_flags_mask_t save_fl)
216
0
{
217
0
    int space_ct;
218
219
0
    if (save_fl & SVFL_USAGE)
220
0
        fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
221
0
    if (UNUSED_OPT(od) && (save_fl & SVFL_DEFAULT))
222
0
        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
0
    {
231
0
        char const * pz =
232
0
            (od->pz_DisableName == NULL)
233
0
            ? od->pz_Name
234
0
            : (DISABLED_OPT(od)
235
0
               ? od->pz_DisableName
236
0
               : ((od->optEquivIndex == NO_EQUIVALENT)
237
0
                  ? od->pz_Name : od->pz_DisableName)
238
0
              );
239
        
240
0
        space_ct = 17 - strlen(pz);
241
0
        fputs(pz, fp);
242
0
    }
243
244
0
    if (  (l_arg == NULL)
245
0
       && (OPTST_GET_ARGTYPE(od->fOptState) != OPARG_TYPE_NUMERIC))
246
0
        goto end_entry;
247
248
0
    fputs(" = ", fp);
249
0
    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
0
    if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NUMERIC)
256
0
        fprintf(fp, "%d", (int)(intptr_t)l_arg);
257
258
0
    else {
259
0
        for (;;) {
260
0
            char const * eol = strchr(l_arg, NL);
261
262
            /*
263
             *  IF this is the last line
264
             *  THEN bail and print it
265
             */
266
0
            if (eol == NULL)
267
0
                break;
268
269
            /*
270
             *  Print the continuation and the text from the current line
271
             */
272
0
            (void)fwrite(l_arg, (size_t)(eol - l_arg), (size_t)1, fp);
273
0
            l_arg = eol+1; /* advance the Last Arg pointer */
274
0
            fputs("\\\n", fp);
275
0
        }
276
277
        /*
278
         *  Terminate the entry
279
         */
280
0
        fputs(l_arg, fp);
281
0
    }
282
283
0
end_entry:
284
0
    fputc(NL, fp);
285
0
}
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
294
prt_value(FILE * fp, int depth, tOptDesc * od, tOptionValue const * ovp)
295
0
{
296
0
    while (--depth >= 0)
297
0
        putc(' ', fp), putc(' ', fp);
298
299
0
    switch (ovp->valType) {
300
0
    default:
301
0
    case OPARG_TYPE_NONE:
302
0
        fprintf(fp, NULL_ATR_FMT, ovp->pzName);
303
0
        break;
304
305
0
    case OPARG_TYPE_STRING:
306
0
        prt_string(fp, ovp->pzName, ovp->v.strVal);
307
0
        break;
308
309
0
    case OPARG_TYPE_ENUMERATION:
310
0
    case OPARG_TYPE_MEMBERSHIP:
311
0
        if (od != NULL) {
312
0
            uint32_t  opt_state = od->fOptState;
313
0
            uintptr_t val = od->optArg.argEnum;
314
0
            char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION)
315
0
                ? "keyword" : "set-membership";
316
317
0
            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
0
            (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od );
324
0
            if (od->optArg.argString != NULL) {
325
0
                fputs(od->optArg.argString, fp);
326
327
0
                if (ovp->valType != OPARG_TYPE_ENUMERATION) {
328
                    /*
329
                     *  set membership strings get allocated
330
                     */
331
0
                    AGFREE(od->optArg.argString);
332
0
                }
333
0
            }
334
335
0
            od->optArg.argEnum = val;
336
0
            od->fOptState = opt_state;
337
0
            fprintf(fp, END_XML_FMT, ovp->pzName);
338
0
            break;
339
0
        }
340
        /* FALLTHROUGH */
341
342
0
    case OPARG_TYPE_NUMERIC:
343
0
        fprintf(fp, NUMB_ATR_FMT, ovp->pzName, ovp->v.longVal);
344
0
        break;
345
346
0
    case OPARG_TYPE_BOOLEAN:
347
0
        fprintf(fp, BOOL_ATR_FMT, ovp->pzName,
348
0
                ovp->v.boolVal ? "true" : "false");
349
0
        break;
350
351
0
    case OPARG_TYPE_HIERARCHY:
352
0
        prt_val_list(fp, ovp->pzName, ovp->v.nestVal);
353
0
        break;
354
0
    }
355
0
}
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
363
prt_string(FILE * fp, char const * name, char const * pz)
364
0
{
365
0
    fprintf(fp, OPEN_XML_FMT, name);
366
0
    for (;;) {
367
0
        int ch = ((int)*(pz++)) & 0xFF;
368
369
0
        switch (ch) {
370
0
        case NUL: goto string_done;
371
372
0
        case '&':
373
0
        case '<':
374
0
        case '>':
375
0
#if __GNUC__ >= 4
376
0
        case 1 ... (' ' - 1):
377
0
        case ('~' + 1) ... 0xFF:
378
0
#endif
379
0
            emit_special_char(fp, ch);
380
0
            break;
381
382
0
        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
0
            putc(ch, fp);
391
0
        }
392
0
    } string_done:;
393
0
    fprintf(fp, END_XML_FMT, name);
394
0
}
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
402
prt_val_list(FILE * fp, char const * name, tArgList * al)
403
0
{
404
0
    static int depth = 1;
405
406
0
    int sp_ct;
407
0
    int opt_ct;
408
0
    void ** opt_list;
409
410
0
    if (al == NULL)
411
0
        return;
412
0
    opt_ct   = al->useCt;
413
0
    opt_list = (void **)al->apzArgs;
414
415
0
    if (opt_ct <= 0) {
416
0
        fprintf(fp, OPEN_CLOSE_FMT, name);
417
0
        return;
418
0
    }
419
420
0
    fprintf(fp, NESTED_OPT_FMT, name);
421
422
0
    depth++;
423
0
    while (--opt_ct >= 0) {
424
0
        tOptionValue const * ovp = *(opt_list++);
425
426
0
        prt_value(fp, depth, NULL, ovp);
427
0
    }
428
0
    depth--;
429
430
0
    for (sp_ct = depth; --sp_ct >= 0;)
431
0
        putc(' ', fp), putc(' ', fp);
432
0
    fprintf(fp, "</%s>\n", name);
433
0
}
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
443
prt_nested(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
444
0
{
445
0
    int opt_ct;
446
0
    tArgList * al = od->optCookie;
447
0
    void ** opt_list;
448
449
0
    if (save_fl & SVFL_USAGE)
450
0
        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
0
    if (UNUSED_OPT(od) || (al == NULL))
456
0
        return;
457
458
0
    opt_ct   = al->useCt;
459
0
    opt_list = (void **)al->apzArgs;
460
461
0
    if (opt_ct <= 0)
462
0
        return;
463
464
0
    do  {
465
0
        tOptionValue const * base = *(opt_list++);
466
0
        tOptionValue const * ovp = optionGetValue(base, NULL);
467
468
0
        if (ovp == NULL)
469
0
            continue;
470
471
0
        fprintf(fp, NESTED_OPT_FMT, od->pz_Name);
472
473
0
        do  {
474
0
            prt_value(fp, 1, od, ovp);
475
476
0
        } while (ovp = optionNextValue(base, ovp),
477
0
                 ovp != NULL);
478
479
0
        fprintf(fp, "</%s>\n", od->pz_Name);
480
0
    } while (--opt_ct > 0);
481
0
}
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
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
514
remove_settings(tOptions * opts, char const * fname)
515
0
{
516
0
    size_t const name_len = strlen(opts->pzProgName);
517
0
    tmap_info_t  map_info;
518
0
    char *       text = text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &map_info);
519
0
    char *       scan = text;
520
521
0
    for (;;) {
522
0
        char * next = scan = strstr(scan, zCfgProg);
523
0
        if (scan == NULL)
524
0
            goto leave;
525
526
0
        scan = SPN_WHITESPACE_CHARS(scan + zCfgProg_LEN);
527
0
        if (  (strneqvcmp(scan, opts->pzProgName, (int)name_len) == 0)
528
0
           && (IS_END_XML_TOKEN_CHAR(scan[name_len])) )  {
529
530
0
            scan = next;
531
0
            break;
532
0
        }
533
0
    }
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
0
    {
541
0
        char * next = strstr(scan + zCfgProg_LEN, zCfgProg);
542
0
        size_t new_sz;
543
544
0
        if (next == NULL)
545
0
            new_sz = map_info.txt_size - strlen(scan);
546
0
        else {
547
0
            int fd = open(fname, O_RDWR);
548
0
            if (fd < 0) return;
549
0
            if (lseek(fd, (scan - text), SEEK_SET) < 0)
550
0
                scan = next;
551
0
            else if (write(fd, next, strlen(next)) < 0)
552
0
                scan = next;
553
0
            if (close(fd) < 0)
554
0
                scan = next;
555
0
            new_sz = map_info.txt_size - (next - scan);
556
0
        }
557
0
        if (new_sz != map_info.txt_size)
558
0
            if (truncate(fname, new_sz) < 0)
559
0
                scan = next; // we removed it, so shorten file
560
0
    }
561
562
0
 leave:
563
0
    text_munmap(&map_info);
564
0
}
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 *
574
open_sv_file(tOptions * opts, save_flags_mask_t save_fl)
575
0
{
576
0
    FILE * fp;
577
578
0
    {
579
0
        int   free_name = 0;
580
0
        char const * fname = find_file_name(opts, &free_name);
581
0
        if (fname == NULL)
582
0
            return NULL;
583
584
0
        if (save_fl == 0)
585
0
            unlink(fname);
586
0
        else
587
0
            remove_settings(opts, fname);
588
589
0
        fp = fopen(fname, "a" FOPEN_BINARY_FLAG);
590
0
        if (fp == NULL) {
591
0
            fprintf(stderr, zsave_warn, opts->pzProgName, fname);
592
0
            fprintf(stderr, zNoCreat, errno, strerror(errno), fname);
593
0
            if (free_name)
594
0
                AGFREE(fname);
595
0
            return fp;
596
0
        }
597
598
0
        if (free_name)
599
0
            AGFREE(fname);
600
0
    }
601
602
0
    do {
603
0
        struct stat sbuf;
604
0
        if (fstat(fileno(fp), &sbuf) < 0)
605
0
            break;
606
607
0
        if (sbuf.st_size > zPresetFile_LEN) {
608
            /* non-zero size implies save_fl is non-zero */
609
0
            fprintf(fp, zFmtProg, opts->pzProgName);
610
0
            return fp;
611
0
        }
612
0
    } while (false);
613
614
    /*
615
     * We have a new file. Insert a header
616
     */
617
0
    fputs("#  ", fp);
618
0
    {
619
0
        char const * e = strchr(opts->pzUsageTitle, NL);
620
0
        if (e++ != NULL)
621
0
            fwrite(opts->pzUsageTitle, 1, e - opts->pzUsageTitle, fp);
622
0
    }
623
624
0
    {
625
0
        time_t  cur_time = time(NULL);
626
0
        char *  time_str = ctime(&cur_time);
627
628
0
        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
0
    }
638
0
    if (save_fl != 0)
639
0
        fprintf(fp, zFmtProg, opts->pzProgName);
640
0
    return fp;
641
0
}
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
652
prt_no_arg_opt(FILE * fp, tOptDesc * vod, tOptDesc * pod, save_flags_mask_t save_fl)
653
0
{
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
0
    char const * pznm =
660
0
        (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
0
    if (pznm == NULL)
667
0
        pznm = pod->pz_Name;
668
669
0
    if (save_fl & SVFL_USAGE)
670
0
        fprintf(fp, ao_name_use_fmt, pod->pz_Name, pod->pzText);
671
0
    if (UNUSED_OPT(pod) && (save_fl & SVFL_DEFAULT))
672
0
        fputs(ao_default_use, fp);
673
674
0
    fprintf(fp, "%s\n", pznm);
675
0
}
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
685
prt_str_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
686
0
{
687
0
    if (UNUSED_OPT(od) || ((od->fOptState & OPTST_STACKED) == 0)) {
688
0
        char const * arg = od->optArg.argString;
689
0
        if (arg == NULL)
690
0
            arg = "''";
691
0
        prt_entry(fp, od, arg, save_fl);
692
693
0
    } else {
694
0
        tArgList * pAL = (tArgList *)od->optCookie;
695
0
        int        uct = pAL->useCt;
696
0
        char const ** ppz = pAL->apzArgs;
697
698
        /*
699
         *  un-disable multiple copies of disabled options.
700
         */
701
0
        if (uct > 1)
702
0
            od->fOptState &= ~OPTST_DISABLED;
703
704
0
        while (uct-- > 0) {
705
0
            prt_entry(fp, od, *(ppz++), save_fl);
706
0
            save_fl &= ~SVFL_USAGE;
707
0
        }
708
0
    }
709
0
}
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
719
prt_enum_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
720
0
{
721
0
    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
0
    (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od);
728
0
    prt_entry(fp, od, VOIDP(od->optArg.argString), save_fl);
729
730
0
    od->optArg.argEnum = val;
731
0
}
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
745
prt_set_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
746
0
{
747
0
    char * list = optionMemberList(od);
748
0
    size_t len  = strlen(list);
749
0
    char * buf  = (char *)AGALOC(len + 3, "dir name");
750
0
    *buf= '=';
751
0
    memcpy(buf+1, list, len + 1);
752
0
    prt_entry(fp, od, buf, save_fl);
753
0
    AGFREE(buf);
754
0
    AGFREE(list);
755
0
}
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
767
prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts, save_flags_mask_t save_fl)
768
0
{
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
0
    if (od->optCookie != NULL)
774
0
        prt_entry(fp, od, od->optCookie, save_fl);
775
776
0
    else if (HAS_originalOptArgArray(opts)) {
777
0
        char const * orig =
778
0
            opts->originalOptArgArray[od->optIndex].argString;
779
780
0
        if (od->optArg.argString == orig) {
781
0
            if (save_fl)
782
0
                fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
783
0
            return;
784
0
        }
785
786
0
        prt_entry(fp, od, od->optArg.argString, save_fl);
787
788
0
    } else if (save_fl)
789
0
        fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
790
0
}
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
822
optionSaveFile(tOptions * opts)
823
0
{
824
0
    tOptDesc *  od;
825
0
    int         ct;
826
0
    FILE *      fp;
827
0
    save_flags_mask_t save_flags = SVFL_NONE;
828
829
0
    do {
830
0
        char * temp_str;
831
0
        char const * dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
832
0
        size_t flen;
833
834
0
        if (dir == NULL)
835
0
            break;
836
0
        temp_str = strchr(dir, '>');
837
0
        if (temp_str == NULL)
838
0
            break;
839
0
        if (temp_str[1] == '>')
840
0
            save_flags = SVFL_UPDATE;
841
0
        flen = (temp_str - dir);
842
0
        if (flen == 0)
843
0
            break;
844
0
        temp_str = AGALOC(flen + 1, "flag search str");
845
0
        memcpy(temp_str, dir, flen);
846
0
        temp_str[flen] = NUL;
847
0
        save_flags |= save_flags_str2mask(temp_str, SVFL_NONE);
848
0
        AGFREE(temp_str);
849
0
    } while (false);
850
851
0
    fp = open_sv_file(opts, save_flags & SVFL_UPDATE);
852
0
    if (fp == NULL)
853
0
        return;
854
855
    /*
856
     *  FOR each of the defined options, ...
857
     */
858
0
    ct = opts->presetOptCt;
859
0
    od = opts->pOptDesc;
860
0
    do  {
861
0
        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
0
        if ((od->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
876
0
            continue;
877
878
0
        if (  (od->optEquivIndex != NO_EQUIVALENT)
879
0
           && (od->optEquivIndex != od->optIndex))
880
0
            continue;
881
882
0
        if (UNUSED_OPT(od) && ((save_flags & SVFL_USAGE_DEFAULT_MASK) == SVFL_NONE))
883
0
            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
0
        vod = ((od->fOptState & OPTST_EQUIVALENCE) != 0)
891
0
              ? (opts->pOptDesc + od->optActualIndex) : od;
892
893
0
        switch (OPTST_GET_ARGTYPE(od->fOptState)) {
894
0
        case OPARG_TYPE_NONE:
895
0
            prt_no_arg_opt(fp, vod, od, save_flags);
896
0
            break;
897
898
0
        case OPARG_TYPE_NUMERIC:
899
0
            prt_entry(fp, vod, VOIDP(vod->optArg.argInt), save_flags);
900
0
            break;
901
902
0
        case OPARG_TYPE_STRING:
903
0
            prt_str_arg(fp, vod, save_flags);
904
0
            break;
905
906
0
        case OPARG_TYPE_ENUMERATION:
907
0
            prt_enum_arg(fp, vod, save_flags);
908
0
            break;
909
910
0
        case OPARG_TYPE_MEMBERSHIP:
911
0
            prt_set_arg(fp, vod, save_flags);
912
0
            break;
913
914
0
        case OPARG_TYPE_BOOLEAN:
915
0
            prt_entry(fp, vod, vod->optArg.argBool ? "true" : "false", save_flags);
916
0
            break;
917
918
0
        case OPARG_TYPE_HIERARCHY:
919
0
            prt_nested(fp, vod, save_flags);
920
0
            break;
921
922
0
        case OPARG_TYPE_FILE:
923
0
            prt_file_arg(fp, vod, opts, save_flags);
924
0
            break;
925
926
0
        default:
927
0
            break; /* cannot handle - skip it */
928
0
        }
929
0
    } while (od++, (--ct > 0));
930
931
0
    fclose(fp);
932
0
}
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 */