1 2 /* 3 * $Id: load.c,v 4.20 2007/02/04 22:17:39 bkorb Exp $ 4 * Time-stamp: "2007-02-04 11:54:57 bkorb" 5 * 6 * This file contains the routines that deal with processing text strings 7 * for options, either from a NUL-terminated string passed in or from an 8 * rc/ini file. 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 tOptionLoadMode option_load_mode = OPTION_LOAD_UNCOOKED; 55 56 /* = = = START-STATIC-FORWARD = = = */ 57 /* static forward declarations maintained by :mkfwd */ 58 static ag_bool 59 insertProgramPath( 60 char* pzBuf, 61 int bufSize, 62 tCC* pzName, 63 tCC* pzProgPath ); 64 65 static ag_bool 66 insertEnvVal( 67 char* pzBuf, 68 int bufSize, 69 tCC* pzName, 70 tCC* pzProgPath ); 71 72 static char* 73 assembleArgValue( char* pzTxt, tOptionLoadMode mode ); 74 /* = = = END-STATIC-FORWARD = = = */ 75 76 /*=export_func optionMakePath 77 * private: 78 * 79 * what: translate and construct a path 80 * arg: + char* + pzBuf + The result buffer + 81 * arg: + int + bufSize + The size of this buffer + 82 * arg: + char const* + pzName + The input name + 83 * arg: + char const* + pzProgPath + The full path of the current program + 84 * 85 * ret-type: ag_bool 86 * ret-desc: AG_TRUE if the name was handled, otherwise AG_FALSE. 87 * If the name does not start with ``$'', then it is handled 88 * simply by copying the input name to the output buffer and 89 * resolving the name with either @code{canonicalize_file_name(3GLIBC)} 90 * or @code{realpath(3C)}. 91 * 92 * doc: 93 * 94 * This routine will copy the @code{pzName} input name into the @code{pzBuf} 95 * output buffer, carefully not exceeding @code{bufSize} bytes. If the 96 * first character of the input name is a @code{'$'} character, then there 97 * is special handling: 98 * @* 99 * @code{$$} is replaced with the directory name of the @code{pzProgPath}, 100 * searching @code{$PATH} if necessary. 101 * @* 102 * @code{$@} is replaced with the AutoGen package data installation directory 103 * (aka @code{pkgdatadir}). 104 * @* 105 * @code{$NAME} is replaced by the contents of the @code{NAME} environment 106 * variable. If not found, the search fails. 107 * 108 * Please note: both @code{$$} and @code{$NAME} must be at the start of the 109 * @code{pzName} string and must either be the entire string or be followed 110 * by the @code{'/'} (backslash on windows) character. 111 * 112 * err: @code{AG_FALSE} is returned if: 113 * @* 114 * @bullet{} The input name exceeds @code{bufSize} bytes. 115 * @* 116 * @bullet{} @code{$$}, @code{$@@} or @code{$NAME} is not the full string 117 * and the next character is not '/'. 118 * @* 119 * @bullet{} libopts was built without PKGDATADIR defined and @code{$@@} 120 * was specified. 121 * @* 122 * @bullet{} @code{NAME} is not a known environment variable 123 * @* 124 * @bullet{} @code{canonicalize_file_name} or @code{realpath} return 125 * errors (cannot resolve the resulting path). 126 =*/ 127 ag_bool 128 optionMakePath( 129 char* pzBuf, 130 int bufSize, 131 tCC* pzName, 132 tCC* pzProgPath ) 133 { 134 size_t name_len = strlen( pzName ); 135 136 # ifndef PKGDATADIR 137 # define PKGDATADIR "" 138 # endif 139 140 tSCC pkgdatadir[] = PKGDATADIR; 141 142 ag_bool res = AG_TRUE; 143 144 if (bufSize <= name_len) 145 return AG_FALSE; 146 147 /* 148 * IF not an environment variable, just copy the data 149 */ 150 if (*pzName != '$') { 151 tCC* pzS = pzName; 152 char* pzD = pzBuf; 153 int ct = bufSize; 154 155 for (;;) { 156 if ( (*(pzD++) = *(pzS++)) == NUL) 157 break; 158 if (--ct <= 0) 159 return AG_FALSE; 160 } 161 } 162 163 /* 164 * IF the name starts with "$$", then it must be "$$" or 165 * it must start with "$$/". In either event, replace the "$$" 166 * with the path to the executable and append a "/" character. 167 */ 168 else switch (pzName[1]) { 169 case NUL: 170 return AG_FALSE; 171 172 case '$': 173 res = insertProgramPath( pzBuf, bufSize, pzName, pzProgPath ); 174 break; 175 176 case '@': 177 if (pkgdatadir[0] == NUL) 178 return AG_FALSE; 179 180 if (name_len + sizeof (pkgdatadir) > bufSize) 181 return AG_FALSE; 182 183 strcpy(pzBuf, pkgdatadir); 184 strcpy(pzBuf + sizeof(pkgdatadir) - 1, pzName + 2); 185 break; 186 187 default: 188 res = insertEnvVal( pzBuf, bufSize, pzName, pzProgPath ); 189 } 190 191 if (! res) 192 return AG_FALSE; 193 194 #if defined(HAVE_CANONICALIZE_FILE_NAME) 195 { 196 char* pz = canonicalize_file_name(pzBuf); 197 if (pz == NULL) 198 return AG_FALSE; 199 if (strlen(pz) < bufSize) 200 strcpy(pzBuf, pz); 201 free(pz); 202 } 203 204 #elif defined(HAVE_REALPATH) 205 { 206 char z[ PATH_MAX+1 ]; 207 208 if (realpath( pzBuf, z ) == NULL) 209 return AG_FALSE; 210 211 if (strlen(z) < bufSize) 212 strcpy( pzBuf, z ); 213 } 214 #endif 215 216 return AG_TRUE; 217 } 218 219 220 static ag_bool 221 insertProgramPath( 222 char* pzBuf, 223 int bufSize, 224 tCC* pzName, 225 tCC* pzProgPath ) 226 { 227 tCC* pzPath; 228 tCC* pz; 229 int skip = 2; 230 231 switch (pzName[2]) { 232 case DIRCH: 233 skip = 3; 234 case NUL: 235 break; 236 default: 237 return AG_FALSE; 238 } 239 240 /* 241 * See if the path is included in the program name. 242 * If it is, we're done. Otherwise, we have to hunt 243 * for the program using "pathfind". 244 */ 245 if (strchr( pzProgPath, DIRCH ) != NULL) 246 pzPath = pzProgPath; 247 else { 248 pzPath = pathfind( getenv( "PATH" ), (char*)pzProgPath, "rx" ); 249 250 if (pzPath == NULL) 251 return AG_FALSE; 252 } 253 254 pz = strrchr( pzPath, DIRCH ); 255 256 /* 257 * IF we cannot find a directory name separator, 258 * THEN we do not have a path name to our executable file. 259 */ 260 if (pz == NULL) 261 return AG_FALSE; 262 263 pzName += skip; 264 265 /* 266 * Concatenate the file name to the end of the executable path. 267 * The result may be either a file or a directory. 268 */ 269 if ((pz - pzPath)+1 + strlen(pzName) >= bufSize) 270 return AG_FALSE; 271 272 memcpy( pzBuf, pzPath, (size_t)((pz - pzPath)+1) ); 273 strcpy( pzBuf + (pz - pzPath) + 1, pzName ); 274 275 /* 276 * If the "pzPath" path was gotten from "pathfind()", then it was 277 * allocated and we need to deallocate it. 278 */ 279 if (pzPath != pzProgPath) 280 free( (void*)pzPath ); 281 return AG_TRUE; 282 } 283 284 285 static ag_bool 286 insertEnvVal( 287 char* pzBuf, 288 int bufSize, 289 tCC* pzName, 290 tCC* pzProgPath ) 291 { 292 char* pzDir = pzBuf; 293 294 for (;;) { 295 int ch = (int)*++pzName; 296 if (! ISNAMECHAR( ch )) 297 break; 298 *(pzDir++) = (char)ch; 299 } 300 301 if (pzDir == pzBuf) 302 return AG_FALSE; 303 304 *pzDir = NUL; 305 306 pzDir = getenv( pzBuf ); 307 308 /* 309 * Environment value not found -- skip the home list entry 310 */ 311 if (pzDir == NULL) 312 return AG_FALSE; 313 314 if (strlen( pzDir ) + 1 + strlen( pzName ) >= bufSize) 315 return AG_FALSE; 316 317 sprintf( pzBuf, "%s%s", pzDir, pzName ); 318 return AG_TRUE; 319 } 320 321 322 LOCAL void 323 mungeString( char* pzTxt, tOptionLoadMode mode ) 324 { 325 char* pzE; 326 327 if (mode == OPTION_LOAD_KEEP) 328 return; 329 330 if (isspace( (int)*pzTxt )) { 331 char* pzS = pzTxt; 332 char* pzD = pzTxt; 333 while (isspace( (int)*++pzS )) ; 334 while ((*(pzD++) = *(pzS++)) != NUL) ; 335 pzE = pzD-1; 336 } else 337 pzE = pzTxt + strlen( pzTxt ); 338 339 while ((pzE > pzTxt) && isspace( (int)pzE[-1] )) pzE--; 340 *pzE = NUL; 341 342 if (mode == OPTION_LOAD_UNCOOKED) 343 return; 344 345 switch (*pzTxt) { 346 default: return; 347 case '"': 348 case '\'': break; 349 } 350 351 switch (pzE[-1]) { 352 default: return; 353 case '"': 354 case '\'': break; 355 } 356 357 (void)ao_string_cook( pzTxt, NULL ); 358 } 359 360 361 static char* 362 assembleArgValue( char* pzTxt, tOptionLoadMode mode ) 363 { 364 tSCC zBrk[] = " \t:="; 365 char* pzEnd = strpbrk( pzTxt, zBrk ); 366 int space_break; 367 368 /* 369 * Not having an argument to a configurable name is okay. 370 */ 371 if (pzEnd == NULL) 372 return pzTxt + strlen(pzTxt); 373 374 /* 375 * If we are keeping all whitespace, then the modevalue starts with the 376 * character that follows the end of the configurable name, regardless 377 * of which character caused it. 378 */ 379 if (mode == OPTION_LOAD_KEEP) { 380 *(pzEnd++) = NUL; 381 return pzEnd; 382 } 383 384 /* 385 * If the name ended on a white space character, remember that 386 * because we'll have to skip over an immediately following ':' or '=' 387 * (and the white space following *that*). 388 */ 389 space_break = isspace((int)*pzEnd); 390 *(pzEnd++) = NUL; 391 while (isspace((int)*pzEnd)) pzEnd++; 392 if (space_break && ((*pzEnd == ':') || (*pzEnd == '='))) 393 while (isspace((int)*++pzEnd)) ; 394 395 return pzEnd; 396 } 397 398 399 /* 400 * Load an option from a block of text. The text must start with the 401 * configurable/option name and be followed by its associated value. 402 * That value may be processed in any of several ways. See "tOptionLoadMode" 403 * in autoopts.h. 404 */ 405 LOCAL void 406 loadOptionLine( 407 tOptions* pOpts, 408 tOptState* pOS, 409 char* pzLine, 410 tDirection direction, 411 tOptionLoadMode load_mode ) 412 { 413 while (isspace( (int)*pzLine )) pzLine++; 414 415 { 416 char* pzArg = assembleArgValue( pzLine, load_mode ); 417 418 if (! SUCCESSFUL( longOptionFind( pOpts, pzLine, pOS ))) 419 return; 420 if (pOS->flags & OPTST_NO_INIT) 421 return; 422 pOS->pzOptArg = pzArg; 423 } 424 425 switch (pOS->flags & (OPTST_IMM|OPTST_DISABLE_IMM)) { 426 case 0: 427 /* 428 * The selected option has no immediate action. 429 * THEREFORE, if the direction is PRESETTING 430 * THEN we skip this option. 431 */ 432 if (PRESETTING(direction)) 433 return; 434 break; 435 436 case OPTST_IMM: 437 if (PRESETTING(direction)) { 438 /* 439 * We are in the presetting direction with an option we handle 440 * immediately for enablement, but normally for disablement. 441 * Therefore, skip if disabled. 442 */ 443 if ((pOS->flags & OPTST_DISABLED) == 0) 444 return; 445 } else { 446 /* 447 * We are in the processing direction with an option we handle 448 * immediately for enablement, but normally for disablement. 449 * Therefore, skip if NOT disabled. 450 */ 451 if ((pOS->flags & OPTST_DISABLED) != 0) 452 return; 453 } 454 break; 455 456 case OPTST_DISABLE_IMM: 457 if (PRESETTING(direction)) { 458 /* 459 * We are in the presetting direction with an option we handle 460 * immediately for disablement, but normally for disablement. 461 * Therefore, skip if NOT disabled. 462 */ 463 if ((pOS->flags & OPTST_DISABLED) != 0) 464 return; 465 } else { 466 /* 467 * We are in the processing direction with an option we handle 468 * immediately for disablement, but normally for disablement. 469 * Therefore, skip if disabled. 470 */ 471 if ((pOS->flags & OPTST_DISABLED) == 0) 472 return; 473 } 474 break; 475 476 case OPTST_IMM|OPTST_DISABLE_IMM: 477 /* 478 * The selected option is always for immediate action. 479 * THEREFORE, if the direction is PROCESSING 480 * THEN we skip this option. 481 */ 482 if (PROCESSING(direction)) 483 return; 484 break; 485 } 486 487 /* 488 * Fix up the args. 489 */ 490 if (OPTST_GET_ARGTYPE(pOS->pOD->fOptState) == OPARG_TYPE_NONE) { 491 if (*pOS->pzOptArg != NUL) 492 return; 493 pOS->pzOptArg = NULL; 494 495 } else if (pOS->pOD->fOptState & OPTST_ARG_OPTIONAL) { 496 if (*pOS->pzOptArg == NUL) 497 pOS->pzOptArg = NULL; 498 else { 499 AGDUPSTR( pOS->pzOptArg, pOS->pzOptArg, "option argument" ); 500 pOS->flags |= OPTST_ALLOC_ARG; 501 } 502 503 } else { 504 if (*pOS->pzOptArg == NUL) 505 pOS->pzOptArg = zNil; 506 else { 507 AGDUPSTR( pOS->pzOptArg, pOS->pzOptArg, "option argument" ); 508 pOS->flags |= OPTST_ALLOC_ARG; 509 } 510 } 511 512 { 513 tOptionLoadMode sv = option_load_mode; 514 option_load_mode = load_mode; 515 handleOption( pOpts, pOS ); 516 option_load_mode = sv; 517 } 518 } 519 520 521 /*=export_func optionLoadLine 522 * 523 * what: process a string for an option name and value 524 * 525 * arg: tOptions*, pOpts, program options descriptor 526 * arg: char const*, pzLine, NUL-terminated text 527 * 528 * doc: 529 * 530 * This is a client program callable routine for setting options from, for 531 * example, the contents of a file that they read in. Only one option may 532 * appear in the text. It will be treated as a normal (non-preset) option. 533 * 534 * When passed a pointer to the option struct and a string, it will find 535 * the option named by the first token on the string and set the option 536 * argument to the remainder of the string. The caller must NUL terminate 537 * the string. Any embedded new lines will be included in the option 538 * argument. If the input looks like one or more quoted strings, then the 539 * input will be "cooked". The "cooking" is identical to the string 540 * formation used in AutoGen definition files (@pxref{basic expression}), 541 * except that you may not use backquotes. 542 * 543 * err: Invalid options are silently ignored. Invalid option arguments 544 * will cause a warning to print, but the function should return. 545 =*/ 546 void 547 optionLoadLine( 548 tOptions* pOpts, 549 tCC* pzLine ) 550 { 551 tOptState st = OPTSTATE_INITIALIZER(SET); 552 char* pz; 553 AGDUPSTR( pz, pzLine, "user option line" ); 554 loadOptionLine( pOpts, &st, pz, DIRECTION_PROCESS, OPTION_LOAD_COOKED ); 555 AGFREE( pz ); 556 } 557 /* 558 * Local Variables: 559 * mode: C 560 * c-file-style: "stroustrup" 561 * indent-tabs-mode: nil 562 * End: 563 * end of autoopts/load.c */ 564