1 2 /* 3 * save.c $Id: save.c,v 4.18 2007/04/15 19:01:18 bkorb Exp $ 4 * Time-stamp: "2007-04-15 11:11:10 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 11 /* 12 * Automated Options copyright 1992-2007 Bruce Korb 13 * 14 * Automated Options is free software. 15 * You may redistribute it and/or modify it under the terms of the 16 * GNU General Public License, as published by the Free Software 17 * Foundation; either version 2, or (at your option) any later version. 18 * 19 * Automated Options is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU General Public License for more details. 23 * 24 * You should have received a copy of the GNU General Public License 25 * along with Automated Options. See the file "COPYING". If not, 26 * write to: The Free Software Foundation, Inc., 27 * 51 Franklin Street, Fifth Floor, 28 * Boston, MA 02110-1301, USA. 29 * 30 * As a special exception, Bruce Korb gives permission for additional 31 * uses of the text contained in his release of AutoOpts. 32 * 33 * The exception is that, if you link the AutoOpts library with other 34 * files to produce an executable, this does not by itself cause the 35 * resulting executable to be covered by the GNU General Public License. 36 * Your use of that executable is in no way restricted on account of 37 * linking the AutoOpts library code into it. 38 * 39 * This exception does not however invalidate any other reasons why 40 * the executable file might be covered by the GNU General Public License. 41 * 42 * This exception applies only to the code released by Bruce Korb under 43 * the name AutoOpts. If you copy code from other sources under the 44 * General Public License into a copy of AutoOpts, as the General Public 45 * License permits, the exception does not apply to the code that you add 46 * in this way. To avoid misleading anyone as to the status of such 47 * modified files, you must delete this exception notice from them. 48 * 49 * If you write modifications of your own for AutoOpts, it is your choice 50 * whether to permit this exception to apply to your modifications. 51 * If you do not wish that, delete this exception notice. 52 */ 53 54 tSCC zWarn[] = "%s WARNING: cannot save options - "; 55 56 /* = = = START-STATIC-FORWARD = = = */ 57 /* static forward declarations maintained by :mkfwd */ 58 static tCC* 59 findDirName( tOptions* pOpts, int* p_free ); 60 61 static tCC* 62 findFileName( tOptions* pOpts, int* p_free_name ); 63 64 static void 65 printEntry( 66 FILE * fp, 67 tOptDesc * p, 68 tCC* pzLA ); 69 /* = = = END-STATIC-FORWARD = = = */ 70 71 static tCC* 72 findDirName( tOptions* pOpts, int* p_free ) 73 { 74 tCC* pzDir; 75 76 if (pOpts->specOptIdx.save_opts == 0) 77 return NULL; 78 79 pzDir = pOpts->pOptDesc[ pOpts->specOptIdx.save_opts ].optArg.argString; 80 if ((pzDir != NULL) && (*pzDir != NUL)) 81 return pzDir; 82 83 /* 84 * This function only works if there is a directory where 85 * we can stash the RC (INI) file. 86 */ 87 { 88 tCC* const* papz = pOpts->papzHomeList; 89 if (papz == NULL) 90 return NULL; 91 92 while (papz[1] != NULL) papz++; 93 pzDir = *papz; 94 } 95 96 /* 97 * IF it does not require deciphering an env value, then just copy it 98 */ 99 if (*pzDir != '$') 100 return pzDir; 101 102 { 103 tCC* pzEndDir = strchr( ++pzDir, DIRCH ); 104 char* pzFileName; 105 char* pzEnv; 106 107 if (pzEndDir != NULL) { 108 char z[ AO_NAME_SIZE ]; 109 if ((pzEndDir - pzDir) > AO_NAME_LIMIT ) 110 return NULL; 111 strncpy( z, pzDir, (size_t)(pzEndDir - pzDir) ); 112 z[ (pzEndDir - pzDir) ] = NUL; 113 pzEnv = getenv( z ); 114 } else { 115 116 /* 117 * Make sure we can get the env value (after stripping off 118 * any trailing directory or file names) 119 */ 120 pzEnv = getenv( pzDir ); 121 } 122 123 if (pzEnv == NULL) { 124 fprintf( stderr, zWarn, pOpts->pzProgName ); 125 fprintf( stderr, zNotDef, pzDir ); 126 return NULL; 127 } 128 129 if (pzEndDir == NULL) 130 return pzEnv; 131 132 { 133 size_t sz = strlen( pzEnv ) + strlen( pzEndDir ) + 2; 134 pzFileName = (char*)AGALOC( sz, "dir name" ); 135 } 136 137 if (pzFileName == NULL) 138 return NULL; 139 140 *p_free = 1; 141 /* 142 * Glue together the full name into the allocated memory. 143 * FIXME: We lose track of this memory. 144 */ 145 sprintf( pzFileName, "%s/%s", pzEnv, pzEndDir ); 146 return pzFileName; 147 } 148 } 149 150 151 static tCC* 152 findFileName( tOptions* pOpts, int* p_free_name ) 153 { 154 tCC* pzDir; 155 struct stat stBuf; 156 int free_dir_name = 0; 157 158 pzDir = findDirName( pOpts, &free_dir_name ); 159 if (pzDir == NULL) 160 return NULL; 161 162 /* 163 * See if we can find the specified directory. We use a once-only loop 164 * structure so we can bail out early. 165 */ 166 if (stat( pzDir, &stBuf ) != 0) do { 167 168 /* 169 * IF we could not, check to see if we got a full 170 * path to a file name that has not been created yet. 171 */ 172 if (errno == ENOENT) { 173 char z[AG_PATH_MAX]; 174 175 /* 176 * Strip off the last component, stat the remaining string and 177 * that string must name a directory 178 */ 179 char* pzDirCh = strrchr( pzDir, DIRCH ); 180 if (pzDirCh == NULL) { 181 stBuf.st_mode = S_IFREG; 182 continue; /* bail out of error condition */ 183 } 184 185 strncpy( z, pzDir, (size_t)(pzDirCh - pzDir)); 186 z[ pzDirCh - pzDir ] = NUL; 187 188 if ( (stat( z, &stBuf ) == 0) 189 && S_ISDIR( stBuf.st_mode )) { 190 191 /* 192 * We found the directory. Restore the file name and 193 * mark the full name as a regular file 194 */ 195 stBuf.st_mode = S_IFREG; 196 continue; /* bail out of error condition */ 197 } 198 } 199 200 /* 201 * We got a bogus name. 202 */ 203 fprintf( stderr, zWarn, pOpts->pzProgName ); 204 fprintf( stderr, zNoStat, errno, strerror( errno ), pzDir ); 205 if (free_dir_name) 206 AGFREE( (void*)pzDir ); 207 return NULL; 208 } while (0); 209 210 /* 211 * IF what we found was a directory, 212 * THEN tack on the config file name 213 */ 214 if (S_ISDIR( stBuf.st_mode )) { 215 size_t sz = strlen( pzDir ) + strlen( pOpts->pzRcName ) + 2; 216 217 { 218 char* pzPath = (char*)AGALOC( sz, "file name" ); 219 #ifdef HAVE_SNPRINTF 220 snprintf( pzPath, sz, "%s/%s", pzDir, pOpts->pzRcName ); 221 #else 222 sprintf( pzPath, "%s/%s", pzDir, pOpts->pzRcName ); 223 #endif 224 if (free_dir_name) 225 AGFREE( (void*)pzDir ); 226 pzDir = pzPath; 227 free_dir_name = 1; 228 } 229 230 /* 231 * IF we cannot stat the object for any reason other than 232 * it does not exist, then we bail out 233 */ 234 if (stat( pzDir, &stBuf ) != 0) { 235 if (errno != ENOENT) { 236 fprintf( stderr, zWarn, pOpts->pzProgName ); 237 fprintf( stderr, zNoStat, errno, strerror( errno ), 238 pzDir ); 239 AGFREE( (void*)pzDir ); 240 return NULL; 241 } 242 243 /* 244 * It does not exist yet, but it will be a regular file 245 */ 246 stBuf.st_mode = S_IFREG; 247 } 248 } 249 250 /* 251 * Make sure that whatever we ultimately found, that it either is 252 * or will soon be a file. 253 */ 254 if (! S_ISREG( stBuf.st_mode )) { 255 fprintf( stderr, zWarn, pOpts->pzProgName ); 256 fprintf( stderr, zNotFile, pzDir ); 257 if (free_dir_name) 258 AGFREE( (void*)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 static void 272 printEntry( 273 FILE * fp, 274 tOptDesc * p, 275 tCC* pzLA ) 276 { 277 /* 278 * There is an argument. Pad the name so values line up. 279 * Not disabled *OR* this got equivalenced to another opt, 280 * then use current option name. 281 * Otherwise, there must be a disablement name. 282 */ 283 { 284 char const * pz; 285 if (! DISABLED_OPT(p) || (p->optEquivIndex != NO_EQUIVALENT)) 286 pz = p->pz_Name; 287 else 288 pz = p->pz_DisableName; 289 290 fprintf(fp, "%-18s", pz); 291 } 292 /* 293 * IF the option is numeric only, 294 * THEN the char pointer is really the number 295 */ 296 if (OPTST_GET_ARGTYPE(p->fOptState) == OPARG_TYPE_NUMERIC) 297 fprintf( fp, " %d\n", (int)(t_word)pzLA ); 298 299 /* 300 * OTHERWISE, FOR each line of the value text, ... 301 */ 302 else if (pzLA == NULL) 303 fputc( '\n', fp ); 304 305 else { 306 fputc( ' ', fp ); fputc( ' ', fp ); 307 for (;;) { 308 tCC* pzNl = strchr( pzLA, '\n' ); 309 310 /* 311 * IF this is the last line 312 * THEN bail and print it 313 */ 314 if (pzNl == NULL) 315 break; 316 317 /* 318 * Print the continuation and the text from the current line 319 */ 320 (void)fwrite( pzLA, (size_t)(pzNl - pzLA), (size_t)1, fp ); 321 pzLA = pzNl+1; /* advance the Last Arg pointer */ 322 fputs( "\\\n", fp ); 323 } 324 325 /* 326 * Terminate the entry 327 */ 328 fputs( pzLA, fp ); 329 fputc( '\n', fp ); 330 } 331 } 332 333 334 /*=export_func optionSaveFile 335 * 336 * what: saves the option state to a file 337 * 338 * arg: tOptions*, pOpts, program options descriptor 339 * 340 * doc: 341 * 342 * This routine will save the state of option processing to a file. The name 343 * of that file can be specified with the argument to the @code{--save-opts} 344 * option, or by appending the @code{rcfile} attribute to the last 345 * @code{homerc} attribute. If no @code{rcfile} attribute was specified, it 346 * will default to @code{.@i{programname}rc}. If you wish to specify another 347 * file, you should invoke the @code{SET_OPT_SAVE_OPTS( @i{filename} )} macro. 348 * 349 * err: 350 * 351 * If no @code{homerc} file was specified, this routine will silently return 352 * and do nothing. If the output file cannot be created or updated, a message 353 * will be printed to @code{stderr} and the routine will return. 354 =*/ 355 void 356 optionSaveFile( tOptions* pOpts ) 357 { 358 tOptDesc* pOD; 359 int ct; 360 FILE* fp; 361 362 { 363 int free_name = 0; 364 tCC* pzFName = findFileName( pOpts, &free_name ); 365 if (pzFName == NULL) 366 return; 367 368 fp = fopen( pzFName, "w" FOPEN_BINARY_FLAG ); 369 if (fp == NULL) { 370 fprintf( stderr, zWarn, pOpts->pzProgName ); 371 fprintf( stderr, zNoCreat, errno, strerror( errno ), pzFName ); 372 if (free_name) 373 AGFREE((void*) pzFName ); 374 return; 375 } 376 377 if (free_name) 378 AGFREE( (void*)pzFName ); 379 } 380 381 { 382 char const* pz = pOpts->pzUsageTitle; 383 fputs( "# ", fp ); 384 do { fputc( *pz, fp ); } while (*(pz++) != '\n'); 385 } 386 387 { 388 time_t timeVal = time( NULL ); 389 char* pzTime = ctime( &timeVal ); 390 391 fprintf( fp, zPresetFile, pzTime ); 392 #ifdef HAVE_ALLOCATED_CTIME 393 /* 394 * The return values for ctime(), localtime(), and gmtime() 395 * normally point to static data that is overwritten by each call. 396 * The test to detect allocated ctime, so we leak the memory. 397 */ 398 AGFREE( (void*)pzTime ); 399 #endif 400 } 401 402 /* 403 * FOR each of the defined options, ... 404 */ 405 ct = pOpts->presetOptCt; 406 pOD = pOpts->pOptDesc; 407 do { 408 int arg_state; 409 tOptDesc* p; 410 411 /* 412 * IF the option has not been defined 413 * OR it does not take an initialization value 414 * OR it is equivalenced to another option 415 * THEN continue (ignore it) 416 */ 417 if (UNUSED_OPT( pOD )) 418 continue; 419 420 if ((pOD->fOptState & (OPTST_NO_INIT|OPTST_DOCUMENT|OPTST_OMITTED)) 421 != 0) 422 continue; 423 424 if ( (pOD->optEquivIndex != NO_EQUIVALENT) 425 && (pOD->optEquivIndex != pOD->optIndex)) 426 continue; 427 428 /* 429 * Set a temporary pointer to the real option description 430 * (i.e. account for equivalencing) 431 */ 432 p = ((pOD->fOptState & OPTST_EQUIVALENCE) != 0) 433 ? (pOpts->pOptDesc + pOD->optActualIndex) : pOD; 434 435 /* 436 * IF no arguments are allowed 437 * THEN just print the name and continue 438 */ 439 if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_NONE) { 440 char const * pznm = 441 (DISABLED_OPT( p )) ? p->pz_DisableName : p->pz_Name; 442 /* 443 * If the option was disabled and the disablement name is NULL, 444 * then the disablement was caused by aliasing. 445 * Use the name as the string to emit. 446 */ 447 if (pznm == NULL) 448 pznm = p->pz_Name; 449 450 fprintf(fp, "%s\n", pznm); 451 continue; 452 } 453 454 arg_state = OPTST_GET_ARGTYPE(p->fOptState); 455 switch (arg_state) { 456 case 0: 457 case OPARG_TYPE_NUMERIC: 458 printEntry( fp, p, (void*)(p->optArg.argInt)); 459 break; 460 461 case OPARG_TYPE_STRING: 462 if (p->fOptState & OPTST_STACKED) { 463 tArgList* pAL = (tArgList*)p->optCookie; 464 int uct = pAL->useCt; 465 tCC** ppz = pAL->apzArgs; 466 467 /* 468 * Disallow multiple copies of disabled options. 469 */ 470 if (uct > 1) 471 p->fOptState &= ~OPTST_DISABLED; 472 473 while (uct-- > 0) 474 printEntry( fp, p, *(ppz++) ); 475 } else { 476 printEntry( fp, p, p->optArg.argString ); 477 } 478 break; 479 480 case OPARG_TYPE_ENUMERATION: 481 case OPARG_TYPE_MEMBERSHIP: 482 { 483 uintptr_t val = p->optArg.argEnum; 484 /* 485 * This is a magic incantation that will convert the 486 * bit flag values back into a string suitable for printing. 487 */ 488 (*(p->pOptProc))( (tOptions*)2UL, p ); 489 printEntry( fp, p, (void*)(p->optArg.argString)); 490 491 if ( (p->optArg.argString != NULL) 492 && (arg_state != OPARG_TYPE_ENUMERATION)) { 493 /* 494 * set membership strings get allocated 495 */ 496 AGFREE( (void*)p->optArg.argString ); 497 p->fOptState &= ~OPTST_ALLOC_ARG; 498 } 499 500 p->optArg.argEnum = val; 501 break; 502 } 503 504 case OPARG_TYPE_BOOLEAN: 505 printEntry( fp, p, p->optArg.argBool ? "true" : "false" ); 506 break; 507 508 default: 509 break; /* cannot handle - skip it */ 510 } 511 } while ( (pOD++), (--ct > 0)); 512 513 fclose( fp ); 514 } 515 /* 516 * Local Variables: 517 * mode: C 518 * c-file-style: "stroustrup" 519 * indent-tabs-mode: nil 520 * End: 521 * end of autoopts/save.c */ 522