1 /* $NetBSD: load.c,v 1.9 2020/05/25 20:47:34 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 = = = */
37 static bool
38 get_realpath(char * buf, size_t b_sz);
39
40 static bool
41 add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path);
42
43 static bool
44 add_env_val(char * buf, int buf_sz, char const * name);
45
46 static char *
47 assemble_arg_val(char * txt, tOptionLoadMode mode);
48
49 static char *
50 trim_quotes(char * arg);
51
52 static bool
53 direction_ok(opt_state_mask_t f, int dir);
54 /* = = = END-STATIC-FORWARD = = = */
55
56 static bool
get_realpath(char * buf,size_t b_sz)57 get_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 =*/
146 bool
optionMakePath(char * p_buf,int b_sz,char const * fname,char const * prg_path)147 optionMakePath(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 */
206 static bool
add_prog_path(char * buf,int b_sz,char const * fname,char const * prg_path)207 add_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 */
269 static bool
add_env_val(char * buf,int buf_sz,char const * name)270 add_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 */
309 LOCAL void
munge_str(char * txt,tOptionLoadMode mode)310 munge_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
347 static char *
assemble_arg_val(char * txt,tOptionLoadMode mode)348 assemble_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
384 static char *
trim_quotes(char * arg)385 trim_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 */
399 static bool
direction_ok(opt_state_mask_t f,int dir)400 direction_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 */
481 LOCAL void
load_opt_line(tOptions * opts,tOptState * opt_state,char * line,tDirection direction,tOptionLoadMode load_mode)482 load_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 =*/
571 void
optionLoadLine(tOptions * opts,char const * line)572 optionLoadLine(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