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