save.c revision 294905
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-2015 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 34/* = = = START-STATIC-FORWARD = = = */ 35static char const * 36find_dir_name(tOptions * opts, int * p_free); 37 38static char const * 39find_file_name(tOptions * opts, int * p_free_name); 40 41static void 42prt_entry(FILE * fp, tOptDesc * od, char const * l_arg); 43 44static void 45prt_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp); 46 47static void 48prt_string(FILE * fp, char const * name, char const * pz); 49 50static void 51prt_val_list(FILE * fp, char const * name, tArgList * al); 52 53static void 54prt_nested(FILE * fp, tOptDesc * p); 55 56static FILE * 57open_sv_file(tOptions * opts); 58 59static void 60prt_no_arg_opt(FILE * fp, tOptDesc * p, tOptDesc * pOD); 61 62static void 63prt_str_arg(FILE * fp, tOptDesc * pOD); 64 65static void 66prt_enum_arg(FILE * fp, tOptDesc * od); 67 68static void 69prt_set_arg(FILE * fp, tOptDesc * od); 70 71static void 72prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts); 73/* = = = END-STATIC-FORWARD = = = */ 74 75/** 76 */ 77static char const * 78find_dir_name(tOptions * opts, int * p_free) 79{ 80 char const * pzDir; 81 82 if ( (opts->specOptIdx.save_opts == NO_EQUIVALENT) 83 || (opts->specOptIdx.save_opts == 0)) 84 return NULL; 85 86 pzDir = opts->pOptDesc[ opts->specOptIdx.save_opts ].optArg.argString; 87 if ((pzDir != NULL) && (*pzDir != NUL)) 88 return pzDir; 89 90 /* 91 * This function only works if there is a directory where 92 * we can stash the RC (INI) file. 93 */ 94 { 95 char const * const * papz = opts->papzHomeList; 96 if (papz == NULL) 97 return NULL; 98 99 while (papz[1] != NULL) papz++; 100 pzDir = *papz; 101 } 102 103 /* 104 * IF it does not require deciphering an env value, then just copy it 105 */ 106 if (*pzDir != '$') 107 return pzDir; 108 109 { 110 char const * pzEndDir = strchr(++pzDir, DIRCH); 111 char * pzFileName; 112 char * pzEnv; 113 114 if (pzEndDir != NULL) { 115 char z[ AO_NAME_SIZE ]; 116 if ((pzEndDir - pzDir) > AO_NAME_LIMIT ) 117 return NULL; 118 memcpy(z, pzDir, (size_t)(pzEndDir - pzDir)); 119 z[pzEndDir - pzDir] = NUL; 120 pzEnv = getenv(z); 121 } else { 122 123 /* 124 * Make sure we can get the env value (after stripping off 125 * any trailing directory or file names) 126 */ 127 pzEnv = getenv(pzDir); 128 } 129 130 if (pzEnv == NULL) { 131 fprintf(stderr, zsave_warn, opts->pzProgName); 132 fprintf(stderr, zNotDef, pzDir); 133 return NULL; 134 } 135 136 if (pzEndDir == NULL) 137 return pzEnv; 138 139 { 140 size_t sz = strlen(pzEnv) + strlen(pzEndDir) + 2; 141 pzFileName = (char *)AGALOC(sz, "dir name"); 142 } 143 144 if (pzFileName == NULL) 145 return NULL; 146 147 *p_free = 1; 148 /* 149 * Glue together the full name into the allocated memory. 150 * FIXME: We lose track of this memory. 151 */ 152 sprintf(pzFileName, "%s/%s", pzEnv, pzEndDir); 153 return pzFileName; 154 } 155} 156 157/** 158 */ 159static char const * 160find_file_name(tOptions * opts, int * p_free_name) 161{ 162 struct stat stBuf; 163 int free_dir_name = 0; 164 165 char const * pzDir = find_dir_name(opts, &free_dir_name); 166 if (pzDir == NULL) 167 return NULL; 168 169 /* 170 * See if we can find the specified directory. We use a once-only loop 171 * structure so we can bail out early. 172 */ 173 if (stat(pzDir, &stBuf) != 0) do { 174 char z[AG_PATH_MAX]; 175 char * dirchp; 176 177 /* 178 * IF we could not, check to see if we got a full 179 * path to a file name that has not been created yet. 180 */ 181 if (errno != ENOENT) { 182 bogus_name: 183 fprintf(stderr, zsave_warn, opts->pzProgName); 184 fprintf(stderr, zNoStat, errno, strerror(errno), pzDir); 185 if (free_dir_name) 186 AGFREE(pzDir); 187 return NULL; 188 } 189 190 /* 191 * Strip off the last component, stat the remaining string and 192 * that string must name a directory 193 */ 194 dirchp = strrchr(pzDir, DIRCH); 195 if (dirchp == NULL) { 196 stBuf.st_mode = S_IFREG; 197 break; /* found directory -- viz., "." */ 198 } 199 200 if ((size_t)(dirchp - pzDir) >= sizeof(z)) 201 goto bogus_name; 202 203 memcpy(z, pzDir, (size_t)(dirchp - pzDir)); 204 z[dirchp - pzDir] = NUL; 205 206 if ((stat(z, &stBuf) != 0) || ! S_ISDIR(stBuf.st_mode)) 207 goto bogus_name; 208 stBuf.st_mode = S_IFREG; /* file within this directory */ 209 } while (false); 210 211 /* 212 * IF what we found was a directory, 213 * THEN tack on the config file name 214 */ 215 if (S_ISDIR(stBuf.st_mode)) { 216 size_t sz = strlen(pzDir) + strlen(opts->pzRcName) + 2; 217 218 { 219 char * pzPath = (char *)AGALOC(sz, "file name"); 220#ifdef HAVE_SNPRINTF 221 snprintf(pzPath, sz, "%s/%s", pzDir, opts->pzRcName); 222#else 223 sprintf(pzPath, "%s/%s", pzDir, opts->pzRcName); 224#endif 225 if (free_dir_name) 226 AGFREE(pzDir); 227 pzDir = pzPath; 228 free_dir_name = 1; 229 } 230 231 /* 232 * IF we cannot stat the object for any reason other than 233 * it does not exist, then we bail out 234 */ 235 if (stat(pzDir, &stBuf) != 0) { 236 if (errno != ENOENT) { 237 fprintf(stderr, zsave_warn, opts->pzProgName); 238 fprintf(stderr, zNoStat, errno, strerror(errno), 239 pzDir); 240 AGFREE(pzDir); 241 return NULL; 242 } 243 244 /* 245 * It does not exist yet, but it will be a regular file 246 */ 247 stBuf.st_mode = S_IFREG; 248 } 249 } 250 251 /* 252 * Make sure that whatever we ultimately found, that it either is 253 * or will soon be a file. 254 */ 255 if (! S_ISREG(stBuf.st_mode)) { 256 fprintf(stderr, zsave_warn, opts->pzProgName, pzDir); 257 if (free_dir_name) 258 AGFREE(pzDir); 259 return NULL; 260 } 261 262 /* 263 * Get rid of the old file 264 */ 265 unlink(pzDir); 266 *p_free_name = free_dir_name; 267 return pzDir; 268} 269 270/** 271 * print one option entry to the save file. 272 * 273 * @param[in] fp the file pointer for the save file 274 * @param[in] od the option descriptor to print 275 * @param[in] l_arg the last argument for the option 276 */ 277static void 278prt_entry(FILE * fp, tOptDesc * od, char const * l_arg) 279{ 280 int space_ct; 281 282 /* 283 * There is an argument. Pad the name so values line up. 284 * Not disabled *OR* this got equivalenced to another opt, 285 * then use current option name. 286 * Otherwise, there must be a disablement name. 287 */ 288 { 289 char const * pz = 290 (! DISABLED_OPT(od) || (od->optEquivIndex != NO_EQUIVALENT)) 291 ? od->pz_Name 292 : od->pz_DisableName; 293 space_ct = 17 - strlen(pz); 294 fputs(pz, fp); 295 } 296 297 if ( (l_arg == NULL) 298 && (OPTST_GET_ARGTYPE(od->fOptState) != OPARG_TYPE_NUMERIC)) 299 goto end_entry; 300 301 fputs(" = ", fp); 302 while (space_ct-- > 0) fputc(' ', fp); 303 304 /* 305 * IF the option is numeric only, 306 * THEN the char pointer is really the number 307 */ 308 if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NUMERIC) 309 fprintf(fp, "%d", (int)(intptr_t)l_arg); 310 311 else { 312 for (;;) { 313 char const * eol = strchr(l_arg, NL); 314 315 /* 316 * IF this is the last line 317 * THEN bail and print it 318 */ 319 if (eol == NULL) 320 break; 321 322 /* 323 * Print the continuation and the text from the current line 324 */ 325 (void)fwrite(l_arg, (size_t)(eol - l_arg), (size_t)1, fp); 326 l_arg = eol+1; /* advance the Last Arg pointer */ 327 fputs("\\\n", fp); 328 } 329 330 /* 331 * Terminate the entry 332 */ 333 fputs(l_arg, fp); 334 } 335 336end_entry: 337 fputc(NL, fp); 338} 339 340/** 341 */ 342static void 343prt_value(FILE * fp, int depth, tOptDesc * pOD, tOptionValue const * ovp) 344{ 345 while (--depth >= 0) 346 putc(' ', fp), putc(' ', fp); 347 348 switch (ovp->valType) { 349 default: 350 case OPARG_TYPE_NONE: 351 fprintf(fp, NULL_ATR_FMT, ovp->pzName); 352 break; 353 354 case OPARG_TYPE_STRING: 355 prt_string(fp, ovp->pzName, ovp->v.strVal); 356 break; 357 358 case OPARG_TYPE_ENUMERATION: 359 case OPARG_TYPE_MEMBERSHIP: 360 if (pOD != NULL) { 361 uint32_t opt_state = pOD->fOptState; 362 uintptr_t val = pOD->optArg.argEnum; 363 char const * typ = (ovp->valType == OPARG_TYPE_ENUMERATION) 364 ? "keyword" : "set-membership"; 365 366 fprintf(fp, TYPE_ATR_FMT, ovp->pzName, typ); 367 368 /* 369 * This is a magic incantation that will convert the 370 * bit flag values back into a string suitable for printing. 371 */ 372 (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD ); 373 if (pOD->optArg.argString != NULL) { 374 fputs(pOD->optArg.argString, fp); 375 376 if (ovp->valType != OPARG_TYPE_ENUMERATION) { 377 /* 378 * set membership strings get allocated 379 */ 380 AGFREE(pOD->optArg.argString); 381 } 382 } 383 384 pOD->optArg.argEnum = val; 385 pOD->fOptState = opt_state; 386 fprintf(fp, END_XML_FMT, ovp->pzName); 387 break; 388 } 389 /* FALLTHROUGH */ 390 391 case OPARG_TYPE_NUMERIC: 392 fprintf(fp, NUMB_ATR_FMT, ovp->pzName, ovp->v.longVal); 393 break; 394 395 case OPARG_TYPE_BOOLEAN: 396 fprintf(fp, BOOL_ATR_FMT, ovp->pzName, 397 ovp->v.boolVal ? "true" : "false"); 398 break; 399 400 case OPARG_TYPE_HIERARCHY: 401 prt_val_list(fp, ovp->pzName, ovp->v.nestVal); 402 break; 403 } 404} 405 406/** 407 */ 408static void 409prt_string(FILE * fp, char const * name, char const * pz) 410{ 411 fprintf(fp, OPEN_XML_FMT, name); 412 for (;;) { 413 int ch = ((int)*(pz++)) & 0xFF; 414 415 switch (ch) { 416 case NUL: goto string_done; 417 418 case '&': 419 case '<': 420 case '>': 421#if __GNUC__ >= 4 422 case 1 ... (' ' - 1): 423 case ('~' + 1) ... 0xFF: 424#endif 425 emit_special_char(fp, ch); 426 break; 427 428 default: 429#if __GNUC__ < 4 430 if ( ((ch >= 1) && (ch <= (' ' - 1))) 431 || ((ch >= ('~' + 1)) && (ch <= 0xFF)) ) { 432 emit_special_char(fp, ch); 433 break; 434 } 435#endif 436 putc(ch, fp); 437 } 438 } string_done:; 439 fprintf(fp, END_XML_FMT, name); 440} 441 442/** 443 */ 444static void 445prt_val_list(FILE * fp, char const * name, tArgList * al) 446{ 447 static int depth = 1; 448 449 int sp_ct; 450 int opt_ct; 451 void ** opt_list; 452 453 if (al == NULL) 454 return; 455 opt_ct = al->useCt; 456 opt_list = VOIDP(al->apzArgs); 457 458 if (opt_ct <= 0) { 459 fprintf(fp, OPEN_CLOSE_FMT, name); 460 return; 461 } 462 463 fprintf(fp, NESTED_OPT_FMT, name); 464 465 depth++; 466 while (--opt_ct >= 0) { 467 tOptionValue const * ovp = *(opt_list++); 468 469 prt_value(fp, depth, NULL, ovp); 470 } 471 depth--; 472 473 for (sp_ct = depth; --sp_ct >= 0;) 474 putc(' ', fp), putc(' ', fp); 475 fprintf(fp, "</%s>\n", name); 476} 477 478/** 479 */ 480static void 481prt_nested(FILE * fp, tOptDesc * p) 482{ 483 int opt_ct; 484 tArgList * al = p->optCookie; 485 void ** opt_list; 486 487 if (al == NULL) 488 return; 489 490 opt_ct = al->useCt; 491 opt_list = VOIDP(al->apzArgs); 492 493 if (opt_ct <= 0) 494 return; 495 496 do { 497 tOptionValue const * base = *(opt_list++); 498 tOptionValue const * ovp = optionGetValue(base, NULL); 499 500 if (ovp == NULL) 501 continue; 502 503 fprintf(fp, NESTED_OPT_FMT, p->pz_Name); 504 505 do { 506 prt_value(fp, 1, p, ovp); 507 508 } while (ovp = optionNextValue(base, ovp), 509 ovp != NULL); 510 511 fprintf(fp, "</%s>\n", p->pz_Name); 512 } while (--opt_ct > 0); 513} 514 515/** 516 * open the file for saving option state. 517 * 518 * @param[in] opts the program options structure 519 * @returns the open file pointer. It may be NULL. 520 */ 521static FILE * 522open_sv_file(tOptions * opts) 523{ 524 FILE * fp; 525 526 { 527 int free_name = 0; 528 char const * pzFName = find_file_name(opts, &free_name); 529 if (pzFName == NULL) 530 return NULL; 531 532 fp = fopen(pzFName, "w" FOPEN_BINARY_FLAG); 533 if (fp == NULL) { 534 fprintf(stderr, zsave_warn, opts->pzProgName); 535 fprintf(stderr, zNoCreat, errno, strerror(errno), pzFName); 536 if (free_name) 537 AGFREE(pzFName); 538 return fp; 539 } 540 541 if (free_name) 542 AGFREE(pzFName); 543 } 544 545 fputs("# ", fp); 546 { 547 char const * e = strchr(opts->pzUsageTitle, NL); 548 if (e++ != NULL) 549 fwrite(opts->pzUsageTitle, 1, e - opts->pzUsageTitle, fp); 550 } 551 552 { 553 time_t cur_time = time(NULL); 554 char * time_str = ctime(&cur_time); 555 556 fprintf(fp, zPresetFile, time_str); 557#ifdef HAVE_ALLOCATED_CTIME 558 /* 559 * The return values for ctime(), localtime(), and gmtime() 560 * normally point to static data that is overwritten by each call. 561 * The test to detect allocated ctime, so we leak the memory. 562 */ 563 AGFREE(time_str); 564#endif 565 } 566 567 return fp; 568} 569 570/** 571 */ 572static void 573prt_no_arg_opt(FILE * fp, tOptDesc * p, tOptDesc * pOD) 574{ 575 /* 576 * The aliased to argument indicates whether or not the option 577 * is "disabled". However, the original option has the name 578 * string, so we get that there, not with "p". 579 */ 580 char const * pznm = 581 (DISABLED_OPT(p)) ? pOD->pz_DisableName : pOD->pz_Name; 582 /* 583 * If the option was disabled and the disablement name is NULL, 584 * then the disablement was caused by aliasing. 585 * Use the name as the string to emit. 586 */ 587 if (pznm == NULL) 588 pznm = pOD->pz_Name; 589 590 fprintf(fp, "%s\n", pznm); 591} 592 593/** 594 */ 595static void 596prt_str_arg(FILE * fp, tOptDesc * pOD) 597{ 598 if (pOD->fOptState & OPTST_STACKED) { 599 tArgList * pAL = (tArgList *)pOD->optCookie; 600 int uct = pAL->useCt; 601 char const ** ppz = pAL->apzArgs; 602 603 /* 604 * un-disable multiple copies of disabled options. 605 */ 606 if (uct > 1) 607 pOD->fOptState &= ~OPTST_DISABLED; 608 609 while (uct-- > 0) 610 prt_entry(fp, pOD, *(ppz++)); 611 } else { 612 prt_entry(fp, pOD, pOD->optArg.argString); 613 } 614} 615 616/** 617 * print the string value of an enumeration. 618 * 619 * @param[in] fp the file pointer to write to 620 * @param[in] od the option descriptor with the enumerated value 621 */ 622static void 623prt_enum_arg(FILE * fp, tOptDesc * od) 624{ 625 uintptr_t val = od->optArg.argEnum; 626 627 /* 628 * This is a magic incantation that will convert the 629 * bit flag values back into a string suitable for printing. 630 */ 631 (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od); 632 prt_entry(fp, od, VOIDP(od->optArg.argString)); 633 634 od->optArg.argEnum = val; 635} 636 637/** 638 * Print the bits set in a bit mask option. 639 * We call the option handling function with a magic value for 640 * the options pointer and it allocates and fills in the string. 641 * We print that with a call to prt_entry(). 642 * 643 * @param[in] fp the file pointer to write to 644 * @param[in] od the option descriptor with a bit mask value type 645 */ 646static void 647prt_set_arg(FILE * fp, tOptDesc * od) 648{ 649 char * list = optionMemberList(od); 650 size_t len = strlen(list); 651 char * buf = (char *)AGALOC(len + 3, "dir name"); 652 *buf= '='; 653 memcpy(buf+1, list, len + 1); 654 prt_entry(fp, od, buf); 655 AGFREE(buf); 656 AGFREE(list); 657} 658 659/** 660 * figure out what the option file name argument is. 661 * If one can be found, call prt_entry() to emit it. 662 * 663 * @param[in] fp the file pointer to write to. 664 * @param[in] od the option descriptor with a bit mask value type 665 * @param[in] opts the program options descriptor 666 */ 667static void 668prt_file_arg(FILE * fp, tOptDesc * od, tOptions * opts) 669{ 670 /* 671 * If the cookie is not NULL, then it has the file name, period. 672 * Otherwise, if we have a non-NULL string argument, then.... 673 */ 674 if (od->optCookie != NULL) 675 prt_entry(fp, od, od->optCookie); 676 677 else if (HAS_originalOptArgArray(opts)) { 678 char const * orig = 679 opts->originalOptArgArray[od->optIndex].argString; 680 681 if (od->optArg.argString == orig) 682 return; 683 684 prt_entry(fp, od, od->optArg.argString); 685 } 686} 687 688/*=export_func optionSaveFile 689 * 690 * what: saves the option state to a file 691 * 692 * arg: tOptions *, opts, program options descriptor 693 * 694 * doc: 695 * 696 * This routine will save the state of option processing to a file. The name 697 * of that file can be specified with the argument to the @code{--save-opts} 698 * option, or by appending the @code{rcfile} attribute to the last 699 * @code{homerc} attribute. If no @code{rcfile} attribute was specified, it 700 * will default to @code{.@i{programname}rc}. If you wish to specify another 701 * file, you should invoke the @code{SET_OPT_SAVE_OPTS(@i{filename})} macro. 702 * 703 * The recommend usage is as follows: 704 * @example 705 * optionProcess(&progOptions, argc, argv); 706 * if (i_want_a_non_standard_place_for_this) 707 * SET_OPT_SAVE_OPTS("myfilename"); 708 * optionSaveFile(&progOptions); 709 * @end example 710 * 711 * err: 712 * 713 * If no @code{homerc} file was specified, this routine will silently return 714 * and do nothing. If the output file cannot be created or updated, a message 715 * will be printed to @code{stderr} and the routine will return. 716=*/ 717void 718optionSaveFile(tOptions * opts) 719{ 720 tOptDesc * od; 721 int ct; 722 FILE * fp = open_sv_file(opts); 723 724 if (fp == NULL) 725 return; 726 727 /* 728 * FOR each of the defined options, ... 729 */ 730 ct = opts->presetOptCt; 731 od = opts->pOptDesc; 732 do { 733 tOptDesc * p; 734 735 /* 736 * IF the option has not been defined 737 * OR it does not take an initialization value 738 * OR it is equivalenced to another option 739 * THEN continue (ignore it) 740 * 741 * Equivalenced options get picked up when the equivalenced-to 742 * option is processed. 743 */ 744 if (UNUSED_OPT(od)) 745 continue; 746 747 if ((od->fOptState & OPTST_DO_NOT_SAVE_MASK) != 0) 748 continue; 749 750 if ( (od->optEquivIndex != NO_EQUIVALENT) 751 && (od->optEquivIndex != od->optIndex)) 752 continue; 753 754 /* 755 * The option argument data are found at the equivalenced-to option, 756 * but the actual option argument type comes from the original 757 * option descriptor. Be careful! 758 */ 759 p = ((od->fOptState & OPTST_EQUIVALENCE) != 0) 760 ? (opts->pOptDesc + od->optActualIndex) : od; 761 762 switch (OPTST_GET_ARGTYPE(od->fOptState)) { 763 case OPARG_TYPE_NONE: 764 prt_no_arg_opt(fp, p, od); 765 break; 766 767 case OPARG_TYPE_NUMERIC: 768 prt_entry(fp, p, VOIDP(p->optArg.argInt)); 769 break; 770 771 case OPARG_TYPE_STRING: 772 prt_str_arg(fp, p); 773 break; 774 775 case OPARG_TYPE_ENUMERATION: 776 prt_enum_arg(fp, p); 777 break; 778 779 case OPARG_TYPE_MEMBERSHIP: 780 prt_set_arg(fp, p); 781 break; 782 783 case OPARG_TYPE_BOOLEAN: 784 prt_entry(fp, p, p->optArg.argBool ? "true" : "false"); 785 break; 786 787 case OPARG_TYPE_HIERARCHY: 788 prt_nested(fp, p); 789 break; 790 791 case OPARG_TYPE_FILE: 792 prt_file_arg(fp, p, opts); 793 break; 794 795 default: 796 break; /* cannot handle - skip it */ 797 } 798 } while (od++, (--ct > 0)); 799 800 fclose(fp); 801} 802/** @} 803 * 804 * Local Variables: 805 * mode: C 806 * c-file-style: "stroustrup" 807 * indent-tabs-mode: nil 808 * End: 809 * end of autoopts/save.c */ 810