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