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