/src/ntp-dev/sntp/libopts/load.c
Line | Count | Source |
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 |
35 | | get_realpath(char * buf, size_t b_sz) |
36 | 0 | { |
37 | 0 | #if defined(HAVE_CANONICALIZE_FILE_NAME) |
38 | 0 | { |
39 | 0 | size_t name_len; |
40 | |
|
41 | 0 | char * pz = canonicalize_file_name(buf); |
42 | 0 | if (pz == NULL) |
43 | 0 | return false; |
44 | | |
45 | 0 | name_len = strlen(pz); |
46 | 0 | if (name_len >= (size_t)b_sz) { |
47 | 0 | free(pz); |
48 | 0 | return false; |
49 | 0 | } |
50 | | |
51 | 0 | memcpy(buf, pz, name_len + 1); |
52 | 0 | free(pz); |
53 | 0 | } |
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 | 0 | return true; |
71 | 0 | } |
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 |
125 | | optionMakePath(char * p_buf, int b_sz, char const * fname, char const * prg_path) |
126 | 0 | { |
127 | 0 | { |
128 | 0 | size_t len = strlen(fname); |
129 | |
|
130 | 0 | if (((size_t)b_sz <= len) || (len == 0)) |
131 | 0 | return false; |
132 | 0 | } |
133 | | |
134 | | /* |
135 | | * IF not an environment variable, just copy the data |
136 | | */ |
137 | 0 | if (*fname != '$') { |
138 | 0 | char const * src = fname; |
139 | 0 | char * dst = p_buf; |
140 | 0 | int ct = b_sz; |
141 | |
|
142 | 0 | for (;;) { |
143 | 0 | if ( (*(dst++) = *(src++)) == NUL) |
144 | 0 | break; |
145 | 0 | if (--ct <= 0) |
146 | 0 | return false; |
147 | 0 | } |
148 | 0 | } |
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 | 0 | else switch (fname[1]) { |
156 | 0 | case NUL: |
157 | 0 | return false; |
158 | | |
159 | 0 | case '$': |
160 | 0 | if (! add_prog_path(p_buf, b_sz, fname, prg_path)) |
161 | 0 | return false; |
162 | 0 | break; |
163 | | |
164 | 0 | case '@': |
165 | 0 | if (program_pkgdatadir[0] == NUL) |
166 | 0 | return false; |
167 | | |
168 | 0 | if (snprintf(p_buf, (size_t)b_sz, "%s%s", |
169 | 0 | program_pkgdatadir, fname + 2) >= b_sz) |
170 | 0 | return false; |
171 | 0 | break; |
172 | | |
173 | 0 | default: |
174 | 0 | if (! add_env_val(p_buf, b_sz, fname)) |
175 | 0 | return false; |
176 | 0 | } |
177 | | |
178 | 0 | return get_realpath(p_buf, b_sz); |
179 | 0 | } |
180 | | |
181 | | /** |
182 | | * convert a leading "$$" into a path to the executable. |
183 | | */ |
184 | | static bool |
185 | | add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path) |
186 | 0 | { |
187 | 0 | char const * path; |
188 | 0 | char const * pz; |
189 | 0 | int skip = 2; |
190 | 0 | size_t fname_len; |
191 | 0 | size_t dir_len; //!< length of the directory portion of the path to the exe |
192 | |
|
193 | 0 | switch (fname[2]) { |
194 | 0 | case DIRCH: |
195 | 0 | skip = 3; |
196 | 0 | case NUL: |
197 | 0 | break; |
198 | 0 | default: |
199 | 0 | return false; |
200 | 0 | } |
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 | 0 | if (strchr(prg_path, DIRCH) != NULL) |
208 | 0 | path = prg_path; |
209 | 0 | else { |
210 | 0 | path = pathfind(getenv("PATH"), (char *)prg_path, "rx"); |
211 | |
|
212 | 0 | if (path == NULL) |
213 | 0 | return false; |
214 | 0 | } |
215 | | |
216 | 0 | 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 | 0 | if (pz == NULL) |
223 | 0 | return false; |
224 | | |
225 | 0 | fname += skip; |
226 | 0 | fname_len = strlen(fname) + 1; // + NUL byte |
227 | 0 | 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 | 0 | if (dir_len + fname_len > (unsigned)b_sz) |
234 | 0 | return false; |
235 | | |
236 | 0 | memcpy(buf, path, dir_len); |
237 | 0 | 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 | 0 | if (path != prg_path) |
244 | 0 | AGFREE(path); |
245 | 0 | return true; |
246 | 0 | } |
247 | | |
248 | | /** |
249 | | * Add an environment variable value. |
250 | | */ |
251 | | static bool |
252 | | add_env_val(char * buf, int buf_sz, char const * name) |
253 | 0 | { |
254 | 0 | char * dir_part = buf; |
255 | |
|
256 | 0 | for (;;) { |
257 | 0 | int ch = (int)*++name; |
258 | 0 | if (! IS_VALUE_NAME_CHAR(ch)) |
259 | 0 | break; |
260 | 0 | *(dir_part++) = (char)ch; |
261 | 0 | } |
262 | |
|
263 | 0 | if (dir_part == buf) |
264 | 0 | return false; |
265 | | |
266 | 0 | *dir_part = NUL; |
267 | |
|
268 | 0 | dir_part = getenv(buf); |
269 | | |
270 | | /* |
271 | | * Environment value not found -- skip the home list entry |
272 | | */ |
273 | 0 | if (dir_part == NULL) |
274 | 0 | return false; |
275 | | |
276 | 0 | { |
277 | 0 | size_t dir_len = strlen(dir_part); |
278 | 0 | size_t nm_len = strlen(name) + 1; |
279 | | |
280 | 0 | if (dir_len + nm_len >= (unsigned)buf_sz) |
281 | 0 | return false; |
282 | 0 | memcpy(buf, dir_part, dir_len); |
283 | 0 | memcpy(buf + dir_len, name, nm_len); |
284 | 0 | } |
285 | | |
286 | 0 | return true; |
287 | 0 | } |
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 |
298 | | munge_str(char * txt, tOptionLoadMode mode) |
299 | 0 | { |
300 | 0 | char * end; |
301 | |
|
302 | 0 | if (mode == OPTION_LOAD_KEEP) |
303 | 0 | return; |
304 | | |
305 | 0 | if (IS_WHITESPACE_CHAR(*txt)) { |
306 | 0 | char * src = SPN_WHITESPACE_CHARS(txt+1); |
307 | 0 | size_t l = strlen(src) + 1; |
308 | 0 | memmove(txt, src, l); |
309 | 0 | end = txt + l - 1; |
310 | |
|
311 | 0 | } else |
312 | 0 | end = txt + strlen(txt); |
313 | |
|
314 | 0 | end = SPN_WHITESPACE_BACK(txt, end); |
315 | 0 | *end = NUL; |
316 | |
|
317 | 0 | if (mode == OPTION_LOAD_UNCOOKED) |
318 | 0 | return; |
319 | | |
320 | 0 | switch (*txt) { |
321 | 0 | default: return; |
322 | 0 | case '"': |
323 | 0 | case '\'': break; |
324 | 0 | } |
325 | | |
326 | 0 | switch (end[-1]) { |
327 | 0 | default: return; |
328 | 0 | case '"': |
329 | 0 | case '\'': break; |
330 | 0 | } |
331 | | |
332 | 0 | (void)ao_string_cook(txt, NULL); |
333 | 0 | } |
334 | | |
335 | | static char * |
336 | | assemble_arg_val(char * txt, tOptionLoadMode mode) |
337 | 0 | { |
338 | 0 | char * end = strpbrk(txt, ARG_BREAK_STR); |
339 | 0 | int space_break; |
340 | | |
341 | | /* |
342 | | * Not having an argument to a configurable name is okay. |
343 | | */ |
344 | 0 | if (end == NULL) |
345 | 0 | 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 | 0 | if (mode == OPTION_LOAD_KEEP) { |
353 | 0 | *(end++) = NUL; |
354 | 0 | return end; |
355 | 0 | } |
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 | 0 | space_break = IS_WHITESPACE_CHAR(*end); |
363 | 0 | *(end++) = NUL; |
364 | |
|
365 | 0 | end = SPN_WHITESPACE_CHARS(end); |
366 | 0 | if (space_break && ((*end == ':') || (*end == '='))) |
367 | 0 | end = SPN_WHITESPACE_CHARS(end+1); |
368 | |
|
369 | 0 | return end; |
370 | 0 | } |
371 | | |
372 | | static char * |
373 | | trim_quotes(char * arg) |
374 | 0 | { |
375 | 0 | switch (*arg) { |
376 | 0 | case '"': |
377 | 0 | case '\'': |
378 | 0 | ao_string_cook(arg, NULL); |
379 | 0 | } |
380 | 0 | return arg; |
381 | 0 | } |
382 | | |
383 | | /** |
384 | | * See if the option is to be processed in the current scan direction |
385 | | * (-1 or +1). |
386 | | */ |
387 | | static bool |
388 | | direction_ok(opt_state_mask_t f, int dir) |
389 | 0 | { |
390 | 0 | if (dir == 0) |
391 | 0 | return true; |
392 | | |
393 | 0 | switch (f & (OPTST_IMM|OPTST_DISABLE_IMM)) { |
394 | 0 | 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 | 0 | if (PRESETTING(dir)) |
401 | 0 | return false; |
402 | 0 | break; |
403 | | |
404 | 0 | case OPTST_IMM: |
405 | 0 | 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 | 0 | if ((f & OPTST_DISABLED) == 0) |
412 | 0 | return false; |
413 | 0 | } 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 | 0 | if ((f & OPTST_DISABLED) != 0) |
420 | 0 | return false; |
421 | 0 | } |
422 | 0 | break; |
423 | | |
424 | 0 | case OPTST_DISABLE_IMM: |
425 | 0 | 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 | 0 | if ((f & OPTST_DISABLED) != 0) |
432 | 0 | return false; |
433 | 0 | } 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 | 0 | if ((f & OPTST_DISABLED) == 0) |
440 | 0 | return false; |
441 | 0 | } |
442 | 0 | break; |
443 | | |
444 | 0 | 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 | 0 | if (PROCESSING(dir)) |
451 | 0 | return false; |
452 | 0 | break; |
453 | 0 | } |
454 | 0 | return true; |
455 | 0 | } |
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 |
470 | | load_opt_line(tOptions * opts, tOptState * opt_state, char * line, |
471 | | tDirection direction, tOptionLoadMode load_mode ) |
472 | 0 | { |
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 | 0 | line = SPN_LOAD_LINE_SKIP_CHARS(line); |
479 | |
|
480 | 0 | { |
481 | 0 | char * arg = assemble_arg_val(line, load_mode); |
482 | |
|
483 | 0 | if (IS_OPTION_NAME_CHAR(line[1])) { |
484 | |
|
485 | 0 | if (! SUCCESSFUL(opt_find_long(opts, line, opt_state))) |
486 | 0 | return; |
487 | |
|
488 | 0 | } else if (! SUCCESSFUL(opt_find_short(opts, *line, opt_state))) |
489 | 0 | return; |
490 | | |
491 | 0 | if ((! CALLED(direction)) && (opt_state->flags & OPTST_NO_INIT)) |
492 | 0 | return; |
493 | | |
494 | 0 | opt_state->pzOptArg = trim_quotes(arg); |
495 | 0 | } |
496 | | |
497 | 0 | if (! direction_ok(opt_state->flags, direction)) |
498 | 0 | return; |
499 | | |
500 | | /* |
501 | | * Fix up the args. |
502 | | */ |
503 | 0 | if (OPTST_GET_ARGTYPE(opt_state->pOD->fOptState) == OPARG_TYPE_NONE) { |
504 | 0 | if (*opt_state->pzOptArg != NUL) |
505 | 0 | return; |
506 | 0 | opt_state->pzOptArg = NULL; |
507 | |
|
508 | 0 | } else if (opt_state->pOD->fOptState & OPTST_ARG_OPTIONAL) { |
509 | 0 | if (*opt_state->pzOptArg == NUL) |
510 | 0 | opt_state->pzOptArg = NULL; |
511 | 0 | else { |
512 | 0 | AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg"); |
513 | 0 | opt_state->flags |= OPTST_ALLOC_ARG; |
514 | 0 | } |
515 | |
|
516 | 0 | } else { |
517 | 0 | if (*opt_state->pzOptArg == NUL) |
518 | 0 | opt_state->pzOptArg = zNil; |
519 | 0 | else { |
520 | 0 | AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg"); |
521 | 0 | opt_state->flags |= OPTST_ALLOC_ARG; |
522 | 0 | } |
523 | 0 | } |
524 | | |
525 | 0 | { |
526 | 0 | tOptionLoadMode sv = option_load_mode; |
527 | 0 | option_load_mode = load_mode; |
528 | 0 | handle_opt(opts, opt_state); |
529 | 0 | option_load_mode = sv; |
530 | 0 | } |
531 | 0 | } |
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 |
560 | | optionLoadLine(tOptions * opts, char const * line) |
561 | 0 | { |
562 | 0 | tOptState st = OPTSTATE_INITIALIZER(SET); |
563 | 0 | char * pz; |
564 | 0 | proc_state_mask_t sv_flags = opts->fOptSet; |
565 | 0 | opts->fOptSet &= ~OPTPROC_ERRSTOP; |
566 | 0 | AGDUPSTR(pz, line, "opt line"); |
567 | 0 | load_opt_line(opts, &st, pz, DIRECTION_CALLED, OPTION_LOAD_COOKED); |
568 | 0 | AGFREE(pz); |
569 | 0 | opts->fOptSet = sv_flags; |
570 | 0 | } |
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 */ |