load.c revision 290001
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 = = = */
35static bool
36get_realpath(char * buf, size_t b_sz);
37
38static bool
39add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path);
40
41static bool
42add_env_val(char * buf, int buf_sz, char const * name);
43
44static char *
45assemble_arg_val(char * txt, tOptionLoadMode mode);
46
47static char *
48trim_quotes(char * arg);
49
50static bool
51direction_ok(opt_state_mask_t f, int dir);
52/* = = = END-STATIC-FORWARD = = = */
53
54static bool
55get_realpath(char * buf, size_t b_sz)
56{
57#if defined(HAVE_CANONICALIZE_FILE_NAME)
58    {
59        size_t name_len;
60
61        char * pz = canonicalize_file_name(buf);
62        if (pz == NULL)
63            return false;
64
65        name_len = strlen(pz);
66        if (name_len >= (size_t)b_sz) {
67            free(pz);
68            return false;
69        }
70
71        memcpy(buf, pz, name_len + 1);
72        free(pz);
73    }
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    return true;
91}
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=*/
144bool
145optionMakePath(char * p_buf, int b_sz, char const * fname, char const * prg_path)
146{
147    {
148        size_t len = strlen(fname);
149
150        if (((size_t)b_sz <= len) || (len == 0))
151            return false;
152    }
153
154    /*
155     *  IF not an environment variable, just copy the data
156     */
157    if (*fname != '$') {
158        char   const * src = fname;
159        char * dst = p_buf;
160        int    ct  = b_sz;
161
162        for (;;) {
163            if ( (*(dst++) = *(src++)) == NUL)
164                break;
165            if (--ct <= 0)
166                return false;
167        }
168    }
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    else switch (fname[1]) {
176    case NUL:
177        return false;
178
179    case '$':
180        if (! add_prog_path(p_buf, b_sz, fname, prg_path))
181            return false;
182        break;
183
184    case '@':
185        if (program_pkgdatadir[0] == NUL)
186            return false;
187
188        if (snprintf(p_buf, (size_t)b_sz, "%s%s",
189                     program_pkgdatadir, fname + 2) >= b_sz)
190            return false;
191        break;
192
193    default:
194        if (! add_env_val(p_buf, b_sz, fname))
195            return false;
196    }
197
198    return get_realpath(p_buf, b_sz);
199}
200
201/**
202 * convert a leading "$$" into a path to the executable.
203 */
204static bool
205add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path)
206{
207    char const *   path;
208    char const *   pz;
209    int     skip = 2;
210
211    switch (fname[2]) {
212    case DIRCH:
213        skip = 3;
214    case NUL:
215        break;
216    default:
217        return false;
218    }
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    if (strchr(prg_path, DIRCH) != NULL)
226        path = prg_path;
227    else {
228        path = pathfind(getenv("PATH"), (char *)prg_path, "rx");
229
230        if (path == NULL)
231            return false;
232    }
233
234    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    if (pz == NULL)
241        return false;
242
243    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    if ((unsigned)(pz - path) + 1 + strlen(fname) >= (unsigned)b_sz)
250        return false;
251
252    memcpy(buf, path, (size_t)((pz - path)+1));
253    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    if (path != prg_path)
260        AGFREE(path);
261    return true;
262}
263
264/**
265 * Add an environment variable value.
266 */
267static bool
268add_env_val(char * buf, int buf_sz, char const * name)
269{
270    char * dir_part = buf;
271
272    for (;;) {
273        int ch = (int)*++name;
274        if (! IS_VALUE_NAME_CHAR(ch))
275            break;
276        *(dir_part++) = (char)ch;
277    }
278
279    if (dir_part == buf)
280        return false;
281
282    *dir_part = NUL;
283
284    dir_part = getenv(buf);
285
286    /*
287     *  Environment value not found -- skip the home list entry
288     */
289    if (dir_part == NULL)
290        return false;
291
292    if (strlen(dir_part) + 1 + strlen(name) >= (unsigned)buf_sz)
293        return false;
294
295    sprintf(buf, "%s%s", dir_part, name);
296    return true;
297}
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 */
307LOCAL void
308munge_str(char * txt, tOptionLoadMode mode)
309{
310    char * pzE;
311
312    if (mode == OPTION_LOAD_KEEP)
313        return;
314
315    if (IS_WHITESPACE_CHAR(*txt)) {
316        char * src = SPN_WHITESPACE_CHARS(txt+1);
317        size_t l   = strlen(src) + 1;
318        memmove(txt, src, l);
319        pzE = txt + l - 1;
320
321    } else
322        pzE = txt + strlen(txt);
323
324    pzE  = SPN_WHITESPACE_BACK(txt, pzE);
325    *pzE = NUL;
326
327    if (mode == OPTION_LOAD_UNCOOKED)
328        return;
329
330    switch (*txt) {
331    default: return;
332    case '"':
333    case '\'': break;
334    }
335
336    switch (pzE[-1]) {
337    default: return;
338    case '"':
339    case '\'': break;
340    }
341
342    (void)ao_string_cook(txt, NULL);
343}
344
345static char *
346assemble_arg_val(char * txt, tOptionLoadMode mode)
347{
348    char * end = strpbrk(txt, ARG_BREAK_STR);
349    int    space_break;
350
351    /*
352     *  Not having an argument to a configurable name is okay.
353     */
354    if (end == NULL)
355        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    if (mode == OPTION_LOAD_KEEP) {
363        *(end++) = NUL;
364        return end;
365    }
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    space_break = IS_WHITESPACE_CHAR(*end);
373    *(end++) = NUL;
374
375    end = SPN_WHITESPACE_CHARS(end);
376    if (space_break && ((*end == ':') || (*end == '=')))
377        end = SPN_WHITESPACE_CHARS(end+1);
378
379    return end;
380}
381
382static char *
383trim_quotes(char * arg)
384{
385    switch (*arg) {
386    case '"':
387    case '\'':
388        ao_string_cook(arg, NULL);
389    }
390    return arg;
391}
392
393/**
394 * See if the option is to be processed in the current scan direction
395 * (-1 or +1).
396 */
397static bool
398direction_ok(opt_state_mask_t f, int dir)
399{
400    if (dir == 0)
401        return true;
402
403    switch (f & (OPTST_IMM|OPTST_DISABLE_IMM)) {
404    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        if (PRESETTING(dir))
411            return false;
412        break;
413
414    case OPTST_IMM:
415        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            if ((f & OPTST_DISABLED) == 0)
422                return false;
423        } 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            if ((f & OPTST_DISABLED) != 0)
430                return false;
431        }
432        break;
433
434    case OPTST_DISABLE_IMM:
435        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            if ((f & OPTST_DISABLED) != 0)
442                return false;
443        } 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            if ((f & OPTST_DISABLED) == 0)
450                return false;
451        }
452        break;
453
454    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        if (PROCESSING(dir))
461            return false;
462        break;
463    }
464    return true;
465}
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 */
479LOCAL void
480load_opt_line(tOptions * opts, tOptState * opt_state, char * line,
481              tDirection direction, tOptionLoadMode load_mode )
482{
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    line = SPN_LOAD_LINE_SKIP_CHARS(line);
489
490    {
491        char * arg = assemble_arg_val(line, load_mode);
492
493        if (IS_OPTION_NAME_CHAR(line[1])) {
494
495            if (! SUCCESSFUL(opt_find_long(opts, line, opt_state)))
496                return;
497
498        } else if (! SUCCESSFUL(opt_find_short(opts, *line, opt_state)))
499            return;
500
501        if ((! CALLED(direction)) && (opt_state->flags & OPTST_NO_INIT))
502            return;
503
504        opt_state->pzOptArg = trim_quotes(arg);
505    }
506
507    if (! direction_ok(opt_state->flags, direction))
508        return;
509
510    /*
511     *  Fix up the args.
512     */
513    if (OPTST_GET_ARGTYPE(opt_state->pOD->fOptState) == OPARG_TYPE_NONE) {
514        if (*opt_state->pzOptArg != NUL)
515            return;
516        opt_state->pzOptArg = NULL;
517
518    } else if (opt_state->pOD->fOptState & OPTST_ARG_OPTIONAL) {
519        if (*opt_state->pzOptArg == NUL)
520             opt_state->pzOptArg = NULL;
521        else {
522            AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg");
523            opt_state->flags |= OPTST_ALLOC_ARG;
524        }
525
526    } else {
527        if (*opt_state->pzOptArg == NUL)
528             opt_state->pzOptArg = zNil;
529        else {
530            AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg");
531            opt_state->flags |= OPTST_ALLOC_ARG;
532        }
533    }
534
535    {
536        tOptionLoadMode sv = option_load_mode;
537        option_load_mode = load_mode;
538        handle_opt(opts, opt_state);
539        option_load_mode = sv;
540    }
541}
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=*/
569void
570optionLoadLine(tOptions * opts, char const * line)
571{
572    tOptState st = OPTSTATE_INITIALIZER(SET);
573    char *    pz;
574    proc_state_mask_t sv_flags = opts->fOptSet;
575    opts->fOptSet &= ~OPTPROC_ERRSTOP;
576    AGDUPSTR(pz, line, "opt line");
577    load_opt_line(opts, &st, pz, DIRECTION_CALLED, OPTION_LOAD_COOKED);
578    AGFREE(pz);
579    opts->fOptSet = sv_flags;
580}
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 */
589