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