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 = = = */
37static bool
38get_realpath(char * buf, size_t b_sz);
39
40static bool
41add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path);
42
43static bool
44add_env_val(char * buf, int buf_sz, char const * name);
45
46static char *
47assemble_arg_val(char * txt, tOptionLoadMode mode);
48
49static char *
50trim_quotes(char * arg);
51
52static bool
53direction_ok(opt_state_mask_t f, int dir);
54/* = = = END-STATIC-FORWARD = = = */
55
56static bool
57get_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=*/
146bool
147optionMakePath(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 */
206static bool
207add_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 */
269static bool
270add_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 */
309LOCAL void
310munge_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
347static char *
348assemble_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
384static char *
385trim_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 */
399static bool
400direction_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 */
481LOCAL void
482load_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=*/
571void
572optionLoadLine(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