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