xref: /freebsd/contrib/ntp/sntp/libopts/load.c (revision a466cc55373fc3cf86837f09da729535b57e69a1)
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