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