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