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