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
get_realpath(char * buf,size_t b_sz)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
optionMakePath(char * p_buf,int b_sz,char const * fname,char const * prg_path)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
add_prog_path(char * buf,int b_sz,char const * fname,char const * prg_path)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
add_env_val(char * buf,int buf_sz,char const * name)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
munge_str(char * txt,tOptionLoadMode mode)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 *
assemble_arg_val(char * txt,tOptionLoadMode mode)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 *
trim_quotes(char * arg)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
direction_ok(opt_state_mask_t f,int dir)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
load_opt_line(tOptions * opts,tOptState * opt_state,char * line,tDirection direction,tOptionLoadMode load_mode)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
optionLoadLine(tOptions * opts,char const * line)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