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