load.c revision 1.10 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