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