load.c revision 1.2
1/*	$NetBSD: load.c,v 1.2 2012/02/03 21:36:40 christos Exp $	*/
2
3
4/**
5 *  \file load.c
6 *  Time-stamp:      "2010-12-18 11:46:07 bkorb"
7 *
8 *  This file contains the routines that deal with processing text strings
9 *  for options, either from a NUL-terminated string passed in or from an
10 *  rc/ini file.
11 *
12 *  This file is part of AutoOpts, a companion to AutoGen.
13 *  AutoOpts is free software.
14 *  AutoOpts is Copyright (c) 1992-2011 by Bruce Korb - all rights reserved
15 *
16 *  AutoOpts is available under any one of two licenses.  The license
17 *  in use must be one of these two and the choice is under the control
18 *  of the user of the license.
19 *
20 *   The GNU Lesser General Public License, version 3 or later
21 *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
22 *
23 *   The Modified Berkeley Software Distribution License
24 *      See the file "COPYING.mbsd"
25 *
26 *  These files have the following md5sums:
27 *
28 *  43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3
29 *  06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3
30 *  66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd
31 */
32
33/* = = = START-STATIC-FORWARD = = = */
34static ag_bool
35insertProgramPath(char * pzBuf, size_t bufSize, char const * pzName,
36                  char const * pzProgPath);
37
38static ag_bool
39insertEnvVal(char * pzBuf, size_t bufSize, char const * pzName,
40             char const * pzProgPath);
41
42static char*
43assembleArgValue(char* pzTxt, tOptionLoadMode mode);
44/* = = = END-STATIC-FORWARD = = = */
45
46/*=export_func  optionMakePath
47 * private:
48 *
49 * what:  translate and construct a path
50 * arg:   + char*       + pzBuf      + The result buffer +
51 * arg:   + int         + bufSize    + The size of this buffer +
52 * arg:   + char const* + pzName     + The input name +
53 * arg:   + char const* + pzProgPath + The full path of the current program +
54 *
55 * ret-type: ag_bool
56 * ret-desc: AG_TRUE if the name was handled, otherwise AG_FALSE.
57 *           If the name does not start with ``$'', then it is handled
58 *           simply by copying the input name to the output buffer and
59 *           resolving the name with either
60 *           @code{canonicalize_file_name(3GLIBC)} or @code{realpath(3C)}.
61 *
62 * doc:
63 *
64 *  This routine will copy the @code{pzName} input name into the
65 *  @code{pzBuf} output buffer, not exceeding @code{bufSize} bytes.  If the
66 *  first character of the input name is a @code{'$'} character, then there
67 *  is special handling:
68 *  @*
69 *  @code{$$} is replaced with the directory name of the @code{pzProgPath},
70 *  searching @code{$PATH} if necessary.
71 *  @*
72 *  @code{$@} is replaced with the AutoGen package data installation directory
73 *  (aka @code{pkgdatadir}).
74 *  @*
75 *  @code{$NAME} is replaced by the contents of the @code{NAME} environment
76 *  variable.  If not found, the search fails.
77 *
78 *  Please note: both @code{$$} and @code{$NAME} must be at the start of the
79 *     @code{pzName} string and must either be the entire string or be followed
80 *     by the @code{'/'} (backslash on windows) character.
81 *
82 * err:  @code{AG_FALSE} is returned if:
83 *       @*
84 *       @bullet{} The input name exceeds @code{bufSize} bytes.
85 *       @*
86 *       @bullet{} @code{$$}, @code{$@@} or @code{$NAME} is not the full string
87 *                 and the next character is not '/'.
88 *       @*
89 *       @bullet{} libopts was built without PKGDATADIR defined and @code{$@@}
90 *                 was specified.
91 *       @*
92 *       @bullet{} @code{NAME} is not a known environment variable
93 *       @*
94 *       @bullet{} @code{canonicalize_file_name} or @code{realpath} return
95 *                 errors (cannot resolve the resulting path).
96=*/
97ag_bool
98optionMakePath(char * pzBuf, size_t bufSize, char const * pzName,
99               char const * pzProgPath)
100{
101    size_t name_len = strlen(pzName);
102
103    if ((bufSize <= name_len) || (name_len == 0))
104        return AG_FALSE;
105
106    /*
107     *  IF not an environment variable, just copy the data
108     */
109    if (*pzName != '$') {
110        char const*  pzS = pzName;
111        char* pzD = pzBuf;
112        int   ct  = bufSize;
113
114        for (;;) {
115            if ( (*(pzD++) = *(pzS++)) == NUL)
116                break;
117            if (--ct <= 0)
118                return AG_FALSE;
119        }
120    }
121
122    /*
123     *  IF the name starts with "$$", then it must be "$$" or
124     *  it must start with "$$/".  In either event, replace the "$$"
125     *  with the path to the executable and append a "/" character.
126     */
127    else switch (pzName[1]) {
128    case NUL:
129        return AG_FALSE;
130
131    case '$':
132        if (! insertProgramPath(pzBuf, bufSize, pzName, pzProgPath))
133            return AG_FALSE;
134        break;
135
136    case '@':
137        if (program_pkgdatadir[0] == NUL)
138            return AG_FALSE;
139
140        if ((size_t)snprintf(pzBuf, bufSize, "%s%s", program_pkgdatadir, pzName + 2)
141            >= bufSize)
142            return AG_FALSE;
143        break;
144
145    default:
146        if (! insertEnvVal(pzBuf, bufSize, pzName, pzProgPath))
147            return AG_FALSE;
148    }
149
150#if defined(HAVE_CANONICALIZE_FILE_NAME)
151    {
152        char * pz = canonicalize_file_name(pzBuf);
153        if (pz == NULL)
154            return AG_FALSE;
155
156        name_len = strlen(pz);
157        if (name_len >= bufSize) {
158            free(pz);
159            return AG_FALSE;
160        }
161
162        memcpy(pzBuf, pz, name_len + 1);
163        free(pz);
164    }
165
166#elif defined(HAVE_REALPATH)
167    {
168        char z[PATH_MAX+1];
169
170        if (realpath(pzBuf, z) == NULL)
171            return AG_FALSE;
172
173        name_len = strlen(z);
174        if (name_len >= bufSize)
175            return AG_FALSE;
176
177        memcpy(pzBuf, z, name_len + 1);
178    }
179#endif
180
181    return AG_TRUE;
182}
183
184
185static ag_bool
186insertProgramPath(char * pzBuf, size_t bufSize, char const * pzName,
187                  char const * pzProgPath)
188{
189    char const*    pzPath;
190    char const*    pz;
191    int     skip = 2;
192
193    switch (pzName[2]) {
194    case DIRCH:
195        skip = 3;
196    case NUL:
197        break;
198    default:
199        return AG_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(pzProgPath, DIRCH) != NULL)
208        pzPath = pzProgPath;
209    else {
210        pzPath = pathfind(getenv("PATH"), pzProgPath, "rx");
211
212        if (pzPath == NULL)
213            return AG_FALSE;
214    }
215
216    pz = strrchr(pzPath, 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 AG_FALSE;
224
225    pzName += skip;
226
227    /*
228     *  Concatenate the file name to the end of the executable path.
229     *  The result may be either a file or a directory.
230     */
231    if ((pz - pzPath)+1 + strlen(pzName) >= bufSize)
232        return AG_FALSE;
233
234    memcpy(pzBuf, pzPath, (size_t)((pz - pzPath)+1));
235    strcpy(pzBuf + (pz - pzPath) + 1, pzName);
236
237    /*
238     *  If the "pzPath" path was gotten from "pathfind()", then it was
239     *  allocated and we need to deallocate it.
240     */
241    if (pzPath != pzProgPath)
242        AGFREE(pzPath);
243    return AG_TRUE;
244}
245
246
247static ag_bool
248insertEnvVal(char * pzBuf, size_t bufSize, char const * pzName,
249             char const * pzProgPath)
250{
251    char* pzDir = pzBuf;
252
253    for (;;) {
254        int ch = (int)*++pzName;
255        if (! IS_VALUE_NAME_CHAR(ch))
256            break;
257        *(pzDir++) = (char)ch;
258    }
259
260    if (pzDir == pzBuf)
261        return AG_FALSE;
262
263    *pzDir = NUL;
264
265    pzDir = getenv(pzBuf);
266
267    /*
268     *  Environment value not found -- skip the home list entry
269     */
270    if (pzDir == NULL)
271        return AG_FALSE;
272
273    if (strlen(pzDir) + 1 + strlen(pzName) >= bufSize)
274        return AG_FALSE;
275
276    sprintf(pzBuf, "%s%s", pzDir, pzName);
277    return AG_TRUE;
278}
279
280
281LOCAL void
282mungeString(char* pzTxt, tOptionLoadMode mode)
283{
284    char* pzE;
285
286    if (mode == OPTION_LOAD_KEEP)
287        return;
288
289    if (IS_WHITESPACE_CHAR(*pzTxt)) {
290        char* pzS = pzTxt;
291        char* pzD = pzTxt;
292        while (IS_WHITESPACE_CHAR(*++pzS))  ;
293        while ((*(pzD++) = *(pzS++)) != NUL)   ;
294        pzE = pzD-1;
295    } else
296        pzE = pzTxt + strlen(pzTxt);
297
298    while ((pzE > pzTxt) && IS_WHITESPACE_CHAR(pzE[-1]))  pzE--;
299    *pzE = NUL;
300
301    if (mode == OPTION_LOAD_UNCOOKED)
302        return;
303
304    switch (*pzTxt) {
305    default: return;
306    case '"':
307    case '\'': break;
308    }
309
310    switch (pzE[-1]) {
311    default: return;
312    case '"':
313    case '\'': break;
314    }
315
316    (void)ao_string_cook(pzTxt, NULL);
317}
318
319
320static char*
321assembleArgValue(char* pzTxt, tOptionLoadMode mode)
322{
323    static char const zBrk[] = " \t\n:=";
324    char* pzEnd = strpbrk(pzTxt, zBrk);
325    int   space_break;
326
327    /*
328     *  Not having an argument to a configurable name is okay.
329     */
330    if (pzEnd == NULL)
331        return pzTxt + strlen(pzTxt);
332
333    /*
334     *  If we are keeping all whitespace, then the  modevalue starts with the
335     *  character that follows the end of the configurable name, regardless
336     *  of which character caused it.
337     */
338    if (mode == OPTION_LOAD_KEEP) {
339        *(pzEnd++) = NUL;
340        return pzEnd;
341    }
342
343    /*
344     *  If the name ended on a white space character, remember that
345     *  because we'll have to skip over an immediately following ':' or '='
346     *  (and the white space following *that*).
347     */
348    space_break = IS_WHITESPACE_CHAR(*pzEnd);
349    *(pzEnd++) = NUL;
350    while (IS_WHITESPACE_CHAR(*pzEnd))  pzEnd++;
351    if (space_break && ((*pzEnd == ':') || (*pzEnd == '=')))
352        while (IS_WHITESPACE_CHAR(*++pzEnd))  ;
353
354    return pzEnd;
355}
356
357
358/*
359 *  Load an option from a block of text.  The text must start with the
360 *  configurable/option name and be followed by its associated value.
361 *  That value may be processed in any of several ways.  See "tOptionLoadMode"
362 *  in autoopts.h.
363 */
364LOCAL void
365loadOptionLine(
366    tOptions*   pOpts,
367    tOptState*  pOS,
368    char*       pzLine,
369    tDirection  direction,
370    tOptionLoadMode   load_mode )
371{
372    while (IS_WHITESPACE_CHAR(*pzLine))  pzLine++;
373
374    {
375        char* pzArg = assembleArgValue(pzLine, load_mode);
376
377        if (! SUCCESSFUL(longOptionFind(pOpts, pzLine, pOS)))
378            return;
379        if (pOS->flags & OPTST_NO_INIT)
380            return;
381        pOS->pzOptArg = pzArg;
382    }
383
384    switch (pOS->flags & (OPTST_IMM|OPTST_DISABLE_IMM)) {
385    case 0:
386        /*
387         *  The selected option has no immediate action.
388         *  THEREFORE, if the direction is PRESETTING
389         *  THEN we skip this option.
390         */
391        if (PRESETTING(direction))
392            return;
393        break;
394
395    case OPTST_IMM:
396        if (PRESETTING(direction)) {
397            /*
398             *  We are in the presetting direction with an option we handle
399             *  immediately for enablement, but normally for disablement.
400             *  Therefore, skip if disabled.
401             */
402            if ((pOS->flags & OPTST_DISABLED) == 0)
403                return;
404        } else {
405            /*
406             *  We are in the processing direction with an option we handle
407             *  immediately for enablement, but normally for disablement.
408             *  Therefore, skip if NOT disabled.
409             */
410            if ((pOS->flags & OPTST_DISABLED) != 0)
411                return;
412        }
413        break;
414
415    case OPTST_DISABLE_IMM:
416        if (PRESETTING(direction)) {
417            /*
418             *  We are in the presetting direction with an option we handle
419             *  immediately for disablement, but normally for disablement.
420             *  Therefore, skip if NOT disabled.
421             */
422            if ((pOS->flags & OPTST_DISABLED) != 0)
423                return;
424        } else {
425            /*
426             *  We are in the processing direction with an option we handle
427             *  immediately for disablement, but normally for disablement.
428             *  Therefore, skip if disabled.
429             */
430            if ((pOS->flags & OPTST_DISABLED) == 0)
431                return;
432        }
433        break;
434
435    case OPTST_IMM|OPTST_DISABLE_IMM:
436        /*
437         *  The selected option is always for immediate action.
438         *  THEREFORE, if the direction is PROCESSING
439         *  THEN we skip this option.
440         */
441        if (PROCESSING(direction))
442            return;
443        break;
444    }
445
446    /*
447     *  Fix up the args.
448     */
449    if (OPTST_GET_ARGTYPE(pOS->pOD->fOptState) == OPARG_TYPE_NONE) {
450        if (*pOS->pzOptArg != NUL)
451            return;
452        pOS->pzOptArg = NULL;
453
454    } else if (pOS->pOD->fOptState & OPTST_ARG_OPTIONAL) {
455        if (*pOS->pzOptArg == NUL)
456             pOS->pzOptArg = NULL;
457        else {
458            AGDUPSTR(pOS->pzOptArg, pOS->pzOptArg, "option argument");
459            pOS->flags |= OPTST_ALLOC_ARG;
460        }
461
462    } else {
463        if (*pOS->pzOptArg == NUL)
464             pOS->pzOptArg = zNil;
465        else {
466            AGDUPSTR(pOS->pzOptArg, pOS->pzOptArg, "option argument");
467            pOS->flags |= OPTST_ALLOC_ARG;
468        }
469    }
470
471    {
472        tOptionLoadMode sv = option_load_mode;
473        option_load_mode = load_mode;
474        handle_opt(pOpts, pOS);
475        option_load_mode = sv;
476    }
477}
478
479
480/*=export_func  optionLoadLine
481 *
482 * what:  process a string for an option name and value
483 *
484 * arg:   tOptions*,   pOpts,  program options descriptor
485 * arg:   char const*, pzLine, NUL-terminated text
486 *
487 * doc:
488 *
489 *  This is a client program callable routine for setting options from, for
490 *  example, the contents of a file that they read in.  Only one option may
491 *  appear in the text.  It will be treated as a normal (non-preset) option.
492 *
493 *  When passed a pointer to the option struct and a string, it will find
494 *  the option named by the first token on the string and set the option
495 *  argument to the remainder of the string.  The caller must NUL terminate
496 *  the string.  Any embedded new lines will be included in the option
497 *  argument.  If the input looks like one or more quoted strings, then the
498 *  input will be "cooked".  The "cooking" is identical to the string
499 *  formation used in AutoGen definition files (@pxref{basic expression}),
500 *  except that you may not use backquotes.
501 *
502 * err:   Invalid options are silently ignored.  Invalid option arguments
503 *        will cause a warning to print, but the function should return.
504=*/
505void
506optionLoadLine(tOptions * pOpts, char const * pzLine)
507{
508    tOptState st = OPTSTATE_INITIALIZER(SET);
509    char* pz;
510    AGDUPSTR(pz, pzLine, "user option line");
511    loadOptionLine(pOpts, &st, pz, DIRECTION_PROCESS, OPTION_LOAD_COOKED);
512    AGFREE(pz);
513}
514/*
515 * Local Variables:
516 * mode: C
517 * c-file-style: "stroustrup"
518 * indent-tabs-mode: nil
519 * End:
520 * end of autoopts/load.c */
521