xref: /freebsd/contrib/ntp/sntp/libopts/load.c (revision 5861f9665471e98e544f6fa3ce73c4912229ff82)
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 
54 tOptionLoadMode option_load_mode = OPTION_LOAD_UNCOOKED;
55 
56 /* = = = START-STATIC-FORWARD = = = */
57 /* static forward declarations maintained by :mkfwd */
58 static ag_bool
59 insertProgramPath(
60     char*   pzBuf,
61     int     bufSize,
62     tCC*    pzName,
63     tCC*    pzProgPath );
64 
65 static ag_bool
66 insertEnvVal(
67     char*   pzBuf,
68     int     bufSize,
69     tCC*    pzName,
70     tCC*    pzProgPath );
71 
72 static char*
73 assembleArgValue( 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 =*/
127 ag_bool
128 optionMakePath(
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 
220 static ag_bool
221 insertProgramPath(
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 
285 static ag_bool
286 insertEnvVal(
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 
322 LOCAL void
323 mungeString( 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 
361 static char*
362 assembleArgValue( 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  */
405 LOCAL void
406 loadOptionLine(
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 =*/
546 void
547 optionLoadLine(
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