xref: /netbsd/external/bsd/ntp/dist/sntp/libopts/load.c (revision 9034ec65)
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