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