Home | History | Annotate | Line # | Download | only in libopts
      1 /*	$NetBSD: save.c,v 1.12 2024/08/18 20:47:25 christos Exp $	*/
      2 
      3 
      4 /*
      5  * \file save.c
      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  * @addtogroup autoopts
     12  * @{
     13  */
     14 /*
     15  *  This file is part of AutoOpts, a companion to AutoGen.
     16  *  AutoOpts is free software.
     17  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
     18  *
     19  *  AutoOpts is available under any one of two licenses.  The license
     20  *  in use must be one of these two and the choice is under the control
     21  *  of the user of the license.
     22  *
     23  *   The GNU Lesser General Public License, version 3 or later
     24  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
     25  *
     26  *   The Modified Berkeley Software Distribution License
     27  *      See the file "COPYING.mbsd"
     28  *
     29  *  These files have the following sha256 sums:
     30  *
     31  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
     32  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
     33  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
     34  */
     35 #include "save-flags.h"
     36 
     37 /**
     38  * find the config file directory name
     39  *
     40  * @param opts    the options descriptor
     41  * @param p_free  tell caller if name was allocated or not
     42  */
     43 static char const *
     44 find_dir_name(tOptions * opts, int * p_free)
     45 {
     46     char const * dir;
     47 
     48     if (  (opts->specOptIdx.save_opts == NO_EQUIVALENT)
     49        || (opts->specOptIdx.save_opts == 0))
     50         return NULL;
     51 
     52     dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
     53     if ((dir != NULL) && (*dir != NUL)) {
     54         char const * pz = strchr(dir, '>');
     55         if (pz == NULL)
     56             return dir;
     57         while (*(++pz) == '>')  ;
     58         pz += strspn(pz, " \t");
     59         dir = pz;
     60         if (*dir != NUL)
     61             return dir;
     62     }
     63 
     64     if (opts->papzHomeList == NULL)
     65         return NULL;
     66 
     67     /*
     68      *  This function only works if there is a directory where
     69      *  we can stash the RC (INI) file.
     70      */
     71     for (int idx = 0;; idx++) {
     72         char f_name[ AG_PATH_MAX+1 ];
     73 
     74         dir = opts->papzHomeList[idx];
     75 
     76         switch (*dir) {
     77         case '$':
     78             break;
     79         case NUL:
     80             continue;
     81         default:
     82             return dir;
     83         }
     84         if (optionMakePath(f_name, (int)sizeof(f_name), dir, opts->pzProgPath)) {
     85             *p_free = true;
     86             AGDUPSTR(dir, f_name, "homerc");
     87             return dir;
     88         }
     89     }
     90     return NULL;
     91 }
     92 
     93 /**
     94  * Find the name of the save-the-options file
     95  *
     96  * @param opts         the options descriptor
     97  * @param p_free_name  tell caller if name was allocated or not
     98  */
     99 static char const *
    100 find_file_name(tOptions * opts, int * p_free_name)
    101 {
    102     struct stat stBuf;
    103     int    free_dir_name = 0;
    104 
    105     char const * res = find_dir_name(opts, &free_dir_name);
    106     if (res == NULL)
    107         return res;
    108 
    109     /*
    110      *  See if we can find the specified directory.  We use a once-only loop
    111      *  structure so we can bail out early.
    112      */
    113     if (stat(res, &stBuf) != 0) do {
    114         char z[AG_PATH_MAX];
    115         char * dirchp;
    116 
    117         /*
    118          *  IF we could not, check to see if we got a full
    119          *  path to a file name that has not been created yet.
    120          */
    121         if (errno != ENOENT) {
    122         bogus_name:
    123             fprintf(stderr, zsave_warn, opts->pzProgName, res);
    124             fprintf(stderr, zNoStat, errno, strerror(errno), res);
    125             if (free_dir_name)
    126                 AGFREE(res);
    127             return NULL;
    128         }
    129 
    130         /*
    131          *  Strip off the last component, stat the remaining string and
    132          *  that string must name a directory
    133          */
    134         dirchp = strrchr(res, DIRCH);
    135         if (dirchp == NULL) {
    136             stBuf.st_mode = S_IFREG;
    137             break; /* found directory -- viz.,  "." */
    138         }
    139 
    140         if ((size_t)(dirchp - res) >= sizeof(z))
    141             goto bogus_name;
    142 
    143         memcpy(z, res, (size_t)(dirchp - res));
    144         z[dirchp - res] = NUL;
    145 
    146         if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode))
    147             goto bogus_name;
    148         stBuf.st_mode = S_IFREG; /* file within this directory */
    149     } while (false);
    150 
    151     /*
    152      *  IF what we found was a directory,
    153      *  THEN tack on the config file name
    154      */
    155     if (S_ISDIR(stBuf.st_mode)) {
    156 
    157         {
    158             size_t sz = strlen(res) + strlen(opts->pzRcName) + 2;
    159             char * pzPath = (char *)AGALOC(sz, "file name");
    160             if (   snprintf(pzPath, sz, "%s/%s", res, opts->pzRcName)
    161                 >= (int)sz)
    162                 option_exits(EXIT_FAILURE);
    163 
    164             if (free_dir_name)
    165                 AGFREE(res);
    166             res = pzPath;
    167             free_dir_name = 1;
    168         }
    169 
    170         /*
    171          *  IF we cannot stat the object for any reason other than
    172          *     it does not exist, then we bail out
    173          */
    174         if (stat(res, &stBuf) != 0) {
    175             if (errno != ENOENT) {
    176                 fprintf(stderr, zsave_warn, opts->pzProgName, res);
    177                 fprintf(stderr, zNoStat, errno, strerror(errno),
    178                         res);
    179                 AGFREE(res);
    180                 return NULL;
    181             }
    182 
    183             /*
    184              *  It does not exist yet, but it will be a regular file
    185              */
    186             stBuf.st_mode = S_IFREG;
    187         }
    188     }
    189 
    190     /*
    191      *  Make sure that whatever we ultimately found, that it either is
    192      *  or will soon be a file.
    193      */
    194     if (! S_ISREG(stBuf.st_mode)) {
    195         fprintf(stderr, zsave_warn, opts->pzProgName, res);
    196         if (free_dir_name)
    197             AGFREE(res);
    198         return NULL;
    199     }
    200 
    201     /*
    202      *  Get rid of the old file
    203      */
    204     *p_free_name = free_dir_name;
    205     return res;
    206 }
    207 
    208 /**
    209  * print one option entry to the save file.
    210  *
    211  * @param[in] fp       the file pointer for the save file
    212  * @param[in] od       the option descriptor to print
    213  * @param[in] l_arg    the last argument for the option
    214  * @param[in] save_fl  include usage in comments
    215  */
    216 static void
    217 prt_entry(FILE * fp, tOptDesc * od, char const * l_arg, save_flags_mask_t save_fl)
    218 {
    219     int space_ct;
    220 
    221     if (save_fl & SVFL_USAGE)
    222         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
    223     if (UNUSED_OPT(od) && (save_fl & SVFL_DEFAULT))
    224         fputs(ao_default_use, fp);
    225 
    226     /*
    227      *  There is an argument.  Pad the name so values line up.
    228      *  Not disabled *OR* this got equivalenced to another opt,
    229      *  then use current option name.
    230      *  Otherwise, there must be a disablement name.
    231      */
    232     {
    233         char const * pz =
    234             (od->pz_DisableName == NULL)
    235             ? od->pz_Name
    236             : (DISABLED_OPT(od)
    237                ? od->pz_DisableName
    238                : ((od->optEquivIndex == NO_EQUIVALENT)
    239                   ? od->pz_Name : od->pz_DisableName)
    240               );
    241 
    242         space_ct = 17 - strlen(pz);
    243         fputs(pz, fp);
    244     }
    245 
    246     if (  (l_arg == NULL)
    247        && (OPTST_GET_ARGTYPE(od->fOptState) != OPARG_TYPE_NUMERIC))
    248         goto end_entry;
    249 
    250     fputs(" = ", fp);
    251     while (space_ct-- > 0)  fputc(' ', fp);
    252 
    253     /*
    254      *  IF the option is numeric only,
    255      *  THEN the char pointer is really the number
    256      */
    257     if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NUMERIC)
    258         fprintf(fp, "%d", (int)(intptr_t)l_arg);
    259 
    260     else {
    261         for (;;) {
    262             char const * eol = strchr(l_arg, NL);
    263 
    264             /*
    265              *  IF this is the last line
    266              *  THEN bail and print it
    267              */
    268             if (eol == NULL)
    269                 break;
    270 
    271             /*
    272              *  Print the continuation and the text from the current line
    273              */
    274             (void)fwrite(l_arg, (size_t)(eol - l_arg), (size_t)1, fp);
    275             l_arg = eol+1; /* advance the Last Arg pointer */
    276             fputs("\\\n", fp);
    277         }
    278 
    279         /*
    280          *  Terminate the entry
    281          */
    282         fputs(l_arg, fp);
    283     }
    284 
    285 end_entry:
    286     fputc(NL, fp);
    287 }
    288 
    289 /**
    290  * print an option's value
    291  *
    292  * @param[in] fp          the file pointer for the save file
    293  * @param[in] od          the option descriptor to print
    294  */
    295 static void
    296 prt_value(FILE * fp, int depth, tOptDesc * od, tOptionValue const * ovp)
    297 {
    298     while (--depth >= 0)
    299         putc(' ', fp), putc(' ', fp);
    300 
    301     switch (ovp->valType) {
    302     default:
    303     case OPARG_TYPE_NONE:
    304         fprintf(fp, NULL_ATR_FMT, ovp->pzName);
    305         break;
    306 
    307     case OPARG_TYPE_STRING:
    308         prt_string(fp, ovp->pzName, ovp->v.strVal);
    309         break;
    310 
    311     case OPARG_TYPE_ENUMERATION:
    312     case OPARG_TYPE_MEMBERSHIP:
    313         if (od != NULL) {
    314             uint32_t  opt_state = od->fOptState;
    315             uintptr_t val = od->optArg.argEnum;
    316             char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION)
    317                 ? "keyword" : "set-membership";
    318 
    319             fprintf(fp, TYPE_ATR_FMT, ovp->pzName, typ);
    320 
    321             /*
    322              *  This is a magic incantation that will convert the
    323              *  bit flag values back into a string suitable for printing.
    324              */
    325             (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od );
    326             if (od->optArg.argString != NULL) {
    327                 fputs(od->optArg.argString, fp);
    328 
    329                 if (ovp->valType != OPARG_TYPE_ENUMERATION) {
    330                     /*
    331                      *  set membership strings get allocated
    332                      */
    333                     AGFREE(od->optArg.argString);
    334                 }
    335             }
    336 
    337             od->optArg.argEnum = val;
    338             od->fOptState = opt_state;
    339             fprintf(fp, END_XML_FMT, ovp->pzName);
    340             break;
    341         }
    342         /* FALLTHROUGH */
    343 
    344     case OPARG_TYPE_NUMERIC:
    345         fprintf(fp, NUMB_ATR_FMT, ovp->pzName, ovp->v.longVal);
    346         break;
    347 
    348     case OPARG_TYPE_BOOLEAN:
    349         fprintf(fp, BOOL_ATR_FMT, ovp->pzName,
    350                 ovp->v.boolVal ? "true" : "false");
    351         break;
    352 
    353     case OPARG_TYPE_HIERARCHY:
    354         prt_val_list(fp, ovp->pzName, ovp->v.nestVal);
    355         break;
    356     }
    357 }
    358 
    359 /**
    360  * Print a string value in XML format
    361  *
    362  * @param[in] fp          the file pointer for the save file
    363  */
    364 static void
    365 prt_string(FILE * fp, char const * name, char const * pz)
    366 {
    367     fprintf(fp, OPEN_XML_FMT, name);
    368     for (;;) {
    369         int ch = ((int)*(pz++)) & 0xFF;
    370 
    371         switch (ch) {
    372         case NUL: goto string_done;
    373 
    374         case '&':
    375         case '<':
    376         case '>':
    377 #if __GNUC__ >= 4
    378         case 1 ... (' ' - 1):
    379         case ('~' + 1) ... 0xFF:
    380 #endif
    381             emit_special_char(fp, ch);
    382             break;
    383 
    384         default:
    385 #if __GNUC__ < 4
    386             if (  ((ch >= 1) && (ch <= (' ' - 1)))
    387                || ((ch >= ('~' + 1)) && (ch <= 0xFF)) ) {
    388                 emit_special_char(fp, ch);
    389                 break;
    390             }
    391 #endif
    392             putc(ch, fp);
    393         }
    394     } string_done:;
    395     fprintf(fp, END_XML_FMT, name);
    396 }
    397 
    398 /**
    399  * Print an option that can have multiple values in XML format
    400  *
    401  * @param[in] fp          file pointer
    402  */
    403 static void
    404 prt_val_list(FILE * fp, char const * name, tArgList * al)
    405 {
    406     static int depth = 1;
    407 
    408     int sp_ct;
    409     int opt_ct;
    410     void ** opt_list;
    411 
    412     if (al == NULL)
    413         return;
    414     opt_ct   = al->useCt;
    415     opt_list = __UNCONST(al->apzArgs);
    416 
    417     if (opt_ct <= 0) {
    418         fprintf(fp, OPEN_CLOSE_FMT, name);
    419         return;
    420     }
    421 
    422     fprintf(fp, NESTED_OPT_FMT, name);
    423 
    424     depth++;
    425     while (--opt_ct >= 0) {
    426         tOptionValue const * ovp = *(opt_list++);
    427 
    428         prt_value(fp, depth, NULL, ovp);
    429     }
    430     depth--;
    431 
    432     for (sp_ct = depth; --sp_ct >= 0;)
    433         putc(' ', fp), putc(' ', fp);
    434     fprintf(fp, "</%s>\n", name);
    435 }
    436 
    437 /**
    438  * printed a nested/hierarchical value
    439  *
    440  * @param[in] fp       file pointer
    441  * @param[in] od       option descriptor
    442  * @param[in] save_fl  include usage in comments
    443  */
    444 static void
    445 prt_nested(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
    446 {
    447     int opt_ct;
    448     tArgList * al = od->optCookie;
    449     void ** opt_list;
    450 
    451     if (save_fl & SVFL_USAGE)
    452         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
    453 
    454     /*
    455      * Never show a default value if a hierarchical value is empty.
    456      */
    457     if (UNUSED_OPT(od) || (al == NULL))
    458         return;
    459 
    460     opt_ct   = al->useCt;
    461     opt_list = __UNCONST(al->apzArgs);
    462 
    463     if (opt_ct <= 0)
    464         return;
    465 
    466     do  {
    467         tOptionValue const * base = *(opt_list++);
    468         tOptionValue const * ovp = optionGetValue(base, NULL);
    469 
    470         if (ovp == NULL)
    471             continue;
    472 
    473         fprintf(fp, NESTED_OPT_FMT, od->pz_Name);
    474 
    475         do  {
    476             prt_value(fp, 1, od, ovp);
    477 
    478         } while (ovp = optionNextValue(base, ovp),
    479                  ovp != NULL);
    480 
    481         fprintf(fp, "</%s>\n", od->pz_Name);
    482     } while (--opt_ct > 0);
    483 }
    484 
    485 #ifdef _MSC_VER
    486 /**
    487  * truncate() emulation for Microsoft C
    488  *
    489  * @param[in] fname  the save file name
    490  * @param[in] newsz  new size of fname in octets
    491  */
    492 static int
    493 truncate(char const* fname, size_t newsz)
    494 {
    495     int fd;
    496     int err;
    497 
    498     fd = open(fname, O_RDWR);
    499     if (fd < 0)
    500             return fd;
    501     err = _chsize_s(fd, newsz);
    502     close(fd);
    503     if (0 != err)
    504             errno = err;
    505     return err;
    506 }
    507 #endif /* _MSC_VER */
    508 
    509 /**
    510  * remove the current program settings
    511  *
    512  * @param[in] opts  the program options structure
    513  * @param[in] fname the save file name
    514  */
    515 static void
    516 remove_settings(tOptions * opts, char const * fname)
    517 {
    518     size_t const name_len = strlen(opts->pzProgName);
    519     tmap_info_t  map_info;
    520     char *       text = text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &map_info);
    521     char *       scan = text;
    522 
    523     for (;;) {
    524         char * next = scan = strstr(scan, zCfgProg);
    525         if (scan == NULL)
    526             goto leave;
    527 
    528         scan = SPN_WHITESPACE_CHARS(scan + zCfgProg_LEN);
    529         if (  (strneqvcmp(scan, opts->pzProgName, (int)name_len) == 0)
    530            && (IS_END_XML_TOKEN_CHAR(scan[name_len])) )  {
    531 
    532             scan = next;
    533             break;
    534         }
    535     }
    536 
    537     /*
    538      * If not NULL, "scan" points to the "<?program" string introducing
    539      * the program segment we are to remove. See if another segment follows.
    540      * If so, copy text. If not se trim off this segment.
    541      */
    542     {
    543         char * next = strstr(scan + zCfgProg_LEN, zCfgProg);
    544         size_t new_sz;
    545 
    546         if (next == NULL)
    547             new_sz = map_info.txt_size - strlen(scan);
    548         else {
    549             int fd = open(fname, O_RDWR);
    550             if (fd < 0) return;
    551             if (lseek(fd, (scan - text), SEEK_SET) < 0)
    552                 scan = next;
    553             else if (write(fd, next, strlen(next)) < 0)
    554                 scan = next;
    555             if (close(fd) < 0)
    556                 scan = next;
    557             new_sz = map_info.txt_size - (next - scan);
    558         }
    559         if (new_sz != map_info.txt_size)
    560             if (truncate(fname, new_sz) < 0)
    561                 scan = next; // we removed it, so shorten file
    562     }
    563 
    564  leave:
    565     text_munmap(&map_info);
    566 }
    567 
    568 /**
    569  * open the file for saving option state.
    570  *
    571  * @param[in] opts     the program options structure
    572  * @param[in] save_fl  flags for saving data
    573  * @returns the open file pointer.  It may be NULL.
    574  */
    575 static FILE *
    576 open_sv_file(tOptions * opts, save_flags_mask_t save_fl)
    577 {
    578     FILE * fp;
    579 
    580     {
    581         int   free_name = 0;
    582         char const * fname = find_file_name(opts, &free_name);
    583         if (fname == NULL)
    584             return NULL;
    585 
    586         if (save_fl == 0)
    587             unlink(fname);
    588         else
    589             remove_settings(opts, fname);
    590 
    591         fp = fopen(fname, "a" FOPEN_BINARY_FLAG);
    592         if (fp == NULL) {
    593             fprintf(stderr, zsave_warn, opts->pzProgName, fname);
    594             fprintf(stderr, zNoCreat, errno, strerror(errno), fname);
    595             if (free_name)
    596                 AGFREE(fname);
    597             return fp;
    598         }
    599 
    600         if (free_name)
    601             AGFREE(fname);
    602     }
    603 
    604     do {
    605         struct stat sbuf;
    606         if (fstat(fileno(fp), &sbuf) < 0)
    607             break;
    608 
    609         if (sbuf.st_size > zPresetFile_LEN) {
    610             /* non-zero size implies save_fl is non-zero */
    611             fprintf(fp, zFmtProg, opts->pzProgName);
    612             return fp;
    613         }
    614     } while (false);
    615 
    616     /*
    617      * We have a new file. Insert a header
    618      */
    619     fputs("#  ", fp);
    620     {
    621         char const * e = strchr(opts->pzUsageTitle, NL);
    622         if (e++ != NULL)
    623             fwrite(opts->pzUsageTitle, 1, e - opts->pzUsageTitle, fp);
    624     }
    625 
    626     {
    627         time_t  cur_time = time(NULL);
    628         char *  time_str = ctime(&cur_time);
    629 
    630         fprintf(fp, zPresetFile, time_str);
    631 #ifdef HAVE_ALLOCATED_CTIME
    632         /*
    633          *  The return values for ctime(), localtime(), and gmtime()
    634          *  normally point to static data that is overwritten by each call.
    635          *  The test to detect allocated ctime, so we leak the memory.
    636          */
    637         AGFREE(time_str);
    638 #endif
    639     }
    640     if (save_fl != 0)
    641         fprintf(fp, zFmtProg, opts->pzProgName);
    642     return fp;
    643 }
    644 
    645 /**
    646  * print option without an arg
    647  *
    648  * @param[in] fp       file pointer
    649  * @param[in] vod      value option descriptor
    650  * @param[in] pod      primary option descriptor
    651  * @param[in] save_fl  include usage in comments
    652  */
    653 static void
    654 prt_no_arg_opt(FILE * fp, tOptDesc * vod, tOptDesc * pod, save_flags_mask_t save_fl)
    655 {
    656     /*
    657      * The aliased to argument indicates whether or not the option
    658      * is "disabled".  However, the original option has the name
    659      * string, so we get that there, not with "vod".
    660      */
    661     char const * pznm =
    662         (DISABLED_OPT(vod)) ? pod->pz_DisableName : pod->pz_Name;
    663     /*
    664      *  If the option was disabled and the disablement name is NULL,
    665      *  then the disablement was caused by aliasing.
    666      *  Use the name as the string to emit.
    667      */
    668     if (pznm == NULL)
    669         pznm = pod->pz_Name;
    670 
    671     if (save_fl & SVFL_USAGE)
    672         fprintf(fp, ao_name_use_fmt, pod->pz_Name, pod->pzText);
    673     if (UNUSED_OPT(pod) && (save_fl & SVFL_DEFAULT))
    674         fputs(ao_default_use, fp);
    675 
    676     fprintf(fp, "%s\n", pznm);
    677 }
    678 
    679 /**
    680  * print the string valued argument(s).
    681  *
    682  * @param[in] fp       file pointer
    683  * @param[in] od       value option descriptor
    684  * @param[in] save_fl  include usage in comments
    685  */
    686 static void
    687 prt_str_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
    688 {
    689     if (UNUSED_OPT(od) || ((od->fOptState & OPTST_STACKED) == 0)) {
    690         char const * arg = od->optArg.argString;
    691         if (arg == NULL)
    692             arg = "''";
    693         prt_entry(fp, od, arg, save_fl);
    694 
    695     } else {
    696         tArgList * pAL = (tArgList *)od->optCookie;
    697         int        uct = pAL->useCt;
    698         char const ** ppz = pAL->apzArgs;
    699 
    700         /*
    701          *  un-disable multiple copies of disabled options.
    702          */
    703         if (uct > 1)
    704             od->fOptState &= ~OPTST_DISABLED;
    705 
    706         while (uct-- > 0) {
    707             prt_entry(fp, od, *(ppz++), save_fl);
    708             save_fl &= ~SVFL_USAGE;
    709         }
    710     }
    711 }
    712 
    713 /**
    714  * print the string value of an enumeration.
    715  *
    716  * @param[in] fp       the file pointer to write to
    717  * @param[in] od       the option descriptor with the enumerated value
    718  * @param[in] save_fl  include usage in comments
    719  */
    720 static void
    721 prt_enum_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
    722 {
    723     uintptr_t val = od->optArg.argEnum;
    724 
    725     /*
    726      *  This is a magic incantation that will convert the
    727      *  bit flag values back into a string suitable for printing.
    728      */
    729     (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od);
    730     prt_entry(fp, od, VOIDP(od->optArg.argString), save_fl);
    731 
    732     od->optArg.argEnum = val;
    733 }
    734 
    735 /**
    736  * Print the bits set in a bit mask option.
    737  *
    738  * We call the option handling function with a magic value for
    739  * the options pointer and it allocates and fills in the string.
    740  * We print that with a call to prt_entry().
    741  *
    742  * @param[in] fp       the file pointer to write to
    743  * @param[in] od       the option descriptor with a bit mask value type
    744  * @param[in] save_fl  include usage in comments
    745  */
    746 static void
    747 prt_set_arg(FILE * fp, tOptDesc * od, save_flags_mask_t save_fl)
    748 {
    749     char * list = optionMemberList(od);
    750     size_t len  = strlen(list);
    751     char * buf  = (char *)AGALOC(len + 3, "dir name");
    752     *buf= '=';
    753     memcpy(buf+1, list, len + 1);
    754     prt_entry(fp, od, buf, save_fl);
    755     AGFREE(buf);
    756     AGFREE(list);
    757 }
    758 
    759 /**
    760  * figure out what the option file name argument is.
    761  * If one can be found, call prt_entry() to emit it.
    762  *
    763  * @param[in] fp       the file pointer to write to.
    764  * @param[in] od       the option descriptor with a bit mask value type
    765  * @param[in] opts     the program options descriptor
    766  * @param[in] save_fl  include usage in comments
    767  */
    768 static void
    769 prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts, save_flags_mask_t save_fl)
    770 {
    771     /*
    772      *  If the cookie is not NULL, then it has the file name, period.
    773      *  Otherwise, if we have a non-NULL string argument, then....
    774      */
    775     if (od->optCookie != NULL)
    776         prt_entry(fp, od, od->optCookie, save_fl);
    777 
    778     else if (HAS_originalOptArgArray(opts)) {
    779         char const * orig =
    780             opts->originalOptArgArray[od->optIndex].argString;
    781 
    782         if (od->optArg.argString == orig) {
    783             if (save_fl)
    784                 fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
    785             return;
    786         }
    787 
    788         prt_entry(fp, od, od->optArg.argString, save_fl);
    789 
    790     } else if (save_fl)
    791         fprintf(fp, ao_name_use_fmt, od->pz_Name, od->pzText);
    792 }
    793 
    794 /*=export_func  optionSaveFile
    795  *
    796  * what:  saves the option state to a file
    797  *
    798  * arg:   tOptions *,   opts,  program options descriptor
    799  *
    800  * doc:
    801  *
    802  * This routine will save the state of option processing to a file.  The name
    803  * of that file can be specified with the argument to the @code{--save-opts}
    804  * option, or by appending the @code{rcfile} attribute to the last
    805  * @code{homerc} attribute.  If no @code{rcfile} attribute was specified, it
    806  * will default to @code{.@i{programname}rc}.  If you wish to specify another
    807  * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro.
    808  *
    809  * The recommend usage is as follows:
    810  * @example
    811  *    optionProcess(&progOptions, argc, argv);
    812  *    if (i_want_a_non_standard_place_for_this)
    813  *        SET_OPT_SAVE_OPTS("myfilename");
    814  *    optionSaveFile(&progOptions);
    815  * @end example
    816  *
    817  * err:
    818  *
    819  * If no @code{homerc} file was specified, this routine will silently return
    820  * and do nothing.  If the output file cannot be created or updated, a message
    821  * will be printed to @code{stderr} and the routine will return.
    822 =*/
    823 void
    824 optionSaveFile(tOptions * opts)
    825 {
    826     tOptDesc *  od;
    827     int         ct;
    828     FILE *      fp;
    829     save_flags_mask_t save_flags = SVFL_NONE;
    830 
    831     do {
    832         char * temp_str;
    833         char const * dir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString;
    834         size_t flen;
    835 
    836         if (dir == NULL)
    837             break;
    838         temp_str = strchr(dir, '>');
    839         if (temp_str == NULL)
    840             break;
    841         if (temp_str[1] == '>')
    842             save_flags = SVFL_UPDATE;
    843         flen = (temp_str - dir);
    844         if (flen == 0)
    845             break;
    846         temp_str = AGALOC(flen + 1, "flag search str");
    847         memcpy(temp_str, dir, flen);
    848         temp_str[flen] = NUL;
    849         save_flags |= save_flags_str2mask(temp_str, SVFL_NONE);
    850         AGFREE(temp_str);
    851     } while (false);
    852 
    853     fp = open_sv_file(opts, save_flags & SVFL_UPDATE);
    854     if (fp == NULL)
    855         return;
    856 
    857     /*
    858      *  FOR each of the defined options, ...
    859      */
    860     ct = opts->presetOptCt;
    861     od = opts->pOptDesc;
    862     do  {
    863         tOptDesc * vod;
    864 
    865         /*
    866          *  Equivalenced options get picked up when the equivalenced-to
    867          *  option is processed. And do not save options with any state
    868          *  bits in the DO_NOT_SAVE collection
    869          *
    870          * ** option cannot be preset
    871          * #define OPTST_NO_INIT          0x0000100U
    872          * ** disable from cmd line
    873          * #define OPTST_NO_COMMAND       0x2000000U
    874          * ** alias for other option
    875          * #define OPTST_ALIAS            0x8000000U
    876          */
    877         if ((od->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0)
    878             continue;
    879 
    880         if (  (od->optEquivIndex != NO_EQUIVALENT)
    881            && (od->optEquivIndex != od->optIndex))
    882             continue;
    883 
    884         if (UNUSED_OPT(od) && ((save_flags & SVFL_USAGE_DEFAULT_MASK) == SVFL_NONE))
    885             continue;
    886 
    887         /*
    888          *  The option argument data are found at the equivalenced-to option,
    889          *  but the actual option argument type comes from the original
    890          *  option descriptor.  Be careful!
    891          */
    892         vod = ((od->fOptState & OPTST_EQUIVALENCE) != 0)
    893               ? (opts->pOptDesc + od->optActualIndex) : od;
    894 
    895         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
    896         case OPARG_TYPE_NONE:
    897             prt_no_arg_opt(fp, vod, od, save_flags);
    898             break;
    899 
    900         case OPARG_TYPE_NUMERIC:
    901             prt_entry(fp, vod, VOIDP(vod->optArg.argInt), save_flags);
    902             break;
    903 
    904         case OPARG_TYPE_STRING:
    905             prt_str_arg(fp, vod, save_flags);
    906             break;
    907 
    908         case OPARG_TYPE_ENUMERATION:
    909             prt_enum_arg(fp, vod, save_flags);
    910             break;
    911 
    912         case OPARG_TYPE_MEMBERSHIP:
    913             prt_set_arg(fp, vod, save_flags);
    914             break;
    915 
    916         case OPARG_TYPE_BOOLEAN:
    917             prt_entry(fp, vod, vod->optArg.argBool ? "true" : "false", save_flags);
    918             break;
    919 
    920         case OPARG_TYPE_HIERARCHY:
    921             prt_nested(fp, vod, save_flags);
    922             break;
    923 
    924         case OPARG_TYPE_FILE:
    925             prt_file_arg(fp, vod, opts, save_flags);
    926             break;
    927 
    928         default:
    929             break; /* cannot handle - skip it */
    930         }
    931     } while (od++, (--ct > 0));
    932 
    933     fclose(fp);
    934 }
    935 /** @}
    936  *
    937  * Local Variables:
    938  * mode: C
    939  * c-file-style: "stroustrup"
    940  * indent-tabs-mode: nil
    941  * End:
    942  * end of autoopts/save.c */
    943