xref: /freebsd/contrib/ntp/sntp/libopts/nested.c (revision 7aa383846770374466b1dcb2cefd71bde9acf463)
1 
2 /*
3  *  $Id: nested.c,v 4.14 2007/02/04 17:44:12 bkorb Exp $
4  *  Time-stamp:      "2007-01-26 11:04:35 bkorb"
5  *
6  *   Automated Options Nested Values module.
7  */
8 
9 /*
10  *  Automated Options copyright 1992-2007 Bruce Korb
11  *
12  *  Automated Options is free software.
13  *  You may redistribute it and/or modify it under the terms of the
14  *  GNU General Public License, as published by the Free Software
15  *  Foundation; either version 2, or (at your option) any later version.
16  *
17  *  Automated Options is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with Automated Options.  See the file "COPYING".  If not,
24  *  write to:  The Free Software Foundation, Inc.,
25  *             51 Franklin Street, Fifth Floor,
26  *             Boston, MA  02110-1301, USA.
27  *
28  * As a special exception, Bruce Korb gives permission for additional
29  * uses of the text contained in his release of AutoOpts.
30  *
31  * The exception is that, if you link the AutoOpts library with other
32  * files to produce an executable, this does not by itself cause the
33  * resulting executable to be covered by the GNU General Public License.
34  * Your use of that executable is in no way restricted on account of
35  * linking the AutoOpts library code into it.
36  *
37  * This exception does not however invalidate any other reasons why
38  * the executable file might be covered by the GNU General Public License.
39  *
40  * This exception applies only to the code released by Bruce Korb under
41  * the name AutoOpts.  If you copy code from other sources under the
42  * General Public License into a copy of AutoOpts, as the General Public
43  * License permits, the exception does not apply to the code that you add
44  * in this way.  To avoid misleading anyone as to the status of such
45  * modified files, you must delete this exception notice from them.
46  *
47  * If you write modifications of your own for AutoOpts, it is your choice
48  * whether to permit this exception to apply to your modifications.
49  * If you do not wish that, delete this exception notice.
50  */
51 /* = = = START-STATIC-FORWARD = = = */
52 /* static forward declarations maintained by :mkfwd */
53 static void
54 removeBackslashes( char* pzSrc );
55 
56 static char const*
57 scanQuotedString( char const* pzTxt );
58 
59 static tOptionValue*
60 addStringValue( void** pp, char const* pzName, size_t nameLen,
61                 char const* pzValue, size_t dataLen );
62 
63 static tOptionValue*
64 addBoolValue( void** pp, char const* pzName, size_t nameLen,
65                 char const* pzValue, size_t dataLen );
66 
67 static tOptionValue*
68 addNumberValue( void** pp, char const* pzName, size_t nameLen,
69                 char const* pzValue, size_t dataLen );
70 
71 static tOptionValue*
72 addNestedValue( void** pp, char const* pzName, size_t nameLen,
73                 char* pzValue, size_t dataLen );
74 
75 static char const*
76 scanNameEntry(char const* pzName, tOptionValue* pRes);
77 
78 static char const*
79 scanXmlEntry( char const* pzName, tOptionValue* pRes );
80 
81 static void
82 unloadNestedArglist( tArgList* pAL );
83 
84 static void
85 sortNestedList( tArgList* pAL );
86 /* = = = END-STATIC-FORWARD = = = */
87 
88 /*  removeBackslashes
89  *
90  *  This function assumes that all newline characters were preceeded by
91  *  backslashes that need removal.
92  */
93 static void
94 removeBackslashes( char* pzSrc )
95 {
96     char* pzD = strchr(pzSrc, '\n');
97 
98     if (pzD == NULL)
99         return;
100     *--pzD = '\n';
101 
102     for (;;) {
103         char ch = ((*pzD++) = *(pzSrc++));
104         switch (ch) {
105         case '\n': *--pzD = ch; break;
106         case NUL:  return;
107         default:
108             ;
109         }
110     }
111 }
112 
113 
114 /*  scanQuotedString
115  *
116  *  Find the end of a quoted string, skipping escaped quote characters.
117  */
118 static char const*
119 scanQuotedString( char const* pzTxt )
120 {
121     char q = *(pzTxt++); /* remember the type of quote */
122 
123     for (;;) {
124         char ch = *(pzTxt++);
125         if (ch == NUL)
126             return pzTxt-1;
127 
128         if (ch == q)
129             return pzTxt;
130 
131         if (ch == '\\') {
132             ch = *(pzTxt++);
133             /*
134              *  IF the next character is NUL, drop the backslash, too.
135              */
136             if (ch == NUL)
137                 return pzTxt - 2;
138 
139             /*
140              *  IF the quote character or the escape character were escaped,
141              *  then skip both, as long as the string does not end.
142              */
143             if ((ch == q) || (ch == '\\')) {
144                 if (*(pzTxt++) == NUL)
145                     return pzTxt-1;
146             }
147         }
148     }
149 }
150 
151 
152 /*  addStringValue
153  *
154  *  Associate a name with either a string or no value.
155  */
156 static tOptionValue*
157 addStringValue( void** pp, char const* pzName, size_t nameLen,
158                 char const* pzValue, size_t dataLen )
159 {
160     tOptionValue* pNV;
161     size_t sz = nameLen + dataLen + sizeof(*pNV);
162 
163     pNV = AGALOC( sz, "option name/str value pair" );
164     if (pNV == NULL)
165         return NULL;
166 
167     if (pzValue == NULL) {
168         pNV->valType = OPARG_TYPE_NONE;
169         pNV->pzName = pNV->v.strVal;
170 
171     } else {
172         pNV->valType = OPARG_TYPE_STRING;
173         if (dataLen > 0)
174             memcpy( pNV->v.strVal, pzValue, dataLen );
175         pNV->v.strVal[dataLen] = NUL;
176         pNV->pzName = pNV->v.strVal + dataLen + 1;
177     }
178 
179     memcpy( pNV->pzName, pzName, nameLen );
180     pNV->pzName[ nameLen ] = NUL;
181     addArgListEntry( pp, pNV );
182     return pNV;
183 }
184 
185 
186 /*  addBoolValue
187  *
188  *  Associate a name with either a string or no value.
189  */
190 static tOptionValue*
191 addBoolValue( void** pp, char const* pzName, size_t nameLen,
192                 char const* pzValue, size_t dataLen )
193 {
194     tOptionValue* pNV;
195     size_t sz = nameLen + sizeof(*pNV) + 1;
196 
197     pNV = AGALOC( sz, "option name/bool value pair" );
198     if (pNV == NULL)
199         return NULL;
200     while (isspace( (int)*pzValue ) && (dataLen > 0)) {
201         dataLen--; pzValue++;
202     }
203     if (dataLen == 0)
204         pNV->v.boolVal = 0;
205     else if (isdigit( (int)*pzValue ))
206         pNV->v.boolVal = atoi( pzValue );
207     else switch (*pzValue) {
208     case 'f':
209     case 'F':
210     case 'n':
211     case 'N':
212         pNV->v.boolVal = 0; break;
213     default:
214         pNV->v.boolVal = 1;
215     }
216 
217     pNV->valType = OPARG_TYPE_BOOLEAN;
218     pNV->pzName = (char*)(pNV + 1);
219     memcpy( pNV->pzName, pzName, nameLen );
220     pNV->pzName[ nameLen ] = NUL;
221     addArgListEntry( pp, pNV );
222     return pNV;
223 }
224 
225 
226 /*  addNumberValue
227  *
228  *  Associate a name with either a string or no value.
229  */
230 static tOptionValue*
231 addNumberValue( void** pp, char const* pzName, size_t nameLen,
232                 char const* pzValue, size_t dataLen )
233 {
234     tOptionValue* pNV;
235     size_t sz = nameLen + sizeof(*pNV) + 1;
236 
237     pNV = AGALOC( sz, "option name/bool value pair" );
238     if (pNV == NULL)
239         return NULL;
240     while (isspace( (int)*pzValue ) && (dataLen > 0)) {
241         dataLen--; pzValue++;
242     }
243     if (dataLen == 0)
244         pNV->v.boolVal = 0;
245     else
246         pNV->v.boolVal = atoi( pzValue );
247 
248     pNV->valType = OPARG_TYPE_NUMERIC;
249     pNV->pzName = (char*)(pNV + 1);
250     memcpy( pNV->pzName, pzName, nameLen );
251     pNV->pzName[ nameLen ] = NUL;
252     addArgListEntry( pp, pNV );
253     return pNV;
254 }
255 
256 
257 /*  addNestedValue
258  *
259  *  Associate a name with either a string or no value.
260  */
261 static tOptionValue*
262 addNestedValue( void** pp, char const* pzName, size_t nameLen,
263                 char* pzValue, size_t dataLen )
264 {
265     tOptionValue* pNV;
266 
267     if (dataLen == 0) {
268         size_t sz = nameLen + sizeof(*pNV) + 1;
269         pNV = AGALOC( sz, "empty nested value pair" );
270         if (pNV == NULL)
271             return NULL;
272         pNV->v.nestVal = NULL;
273         pNV->valType = OPARG_TYPE_HIERARCHY;
274         pNV->pzName = (char*)(pNV + 1);
275         memcpy( pNV->pzName, pzName, nameLen );
276         pNV->pzName[ nameLen ] = NUL;
277 
278     } else {
279         pNV = optionLoadNested( pzValue, pzName, nameLen );
280     }
281 
282     if (pNV != NULL)
283         addArgListEntry( pp, pNV );
284 
285     return pNV;
286 }
287 
288 
289 /*  scanNameEntry
290  *
291  *  We have an entry that starts with a name.  Find the end of it, cook it
292  *  (if called for) and create the name/value association.
293  */
294 static char const*
295 scanNameEntry(char const* pzName, tOptionValue* pRes)
296 {
297     tOptionValue* pNV;
298     char const * pzScan = pzName+1;
299     char const * pzVal;
300     size_t       nameLen = 1;
301     size_t       dataLen = 0;
302 
303     while (ISNAMECHAR( (int)*pzScan ))  { pzScan++; nameLen++; }
304 
305     while (isspace( (int)*pzScan )) {
306         char ch = *(pzScan++);
307         if ((ch == '\n') || (ch == ',')) {
308             addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL,(size_t)0);
309             return pzScan - 1;
310         }
311     }
312 
313     switch (*pzScan) {
314     case '=':
315     case ':':
316         while (isspace( (int)*++pzScan ))  ;
317         switch (*pzScan) {
318         case ',':  goto comma_char;
319         case '"':
320         case '\'': goto quote_char;
321         case NUL:  goto nul_byte;
322         default:   goto default_char;
323         }
324 
325     case ',':
326     comma_char:
327         pzScan++;
328         /* FALLTHROUGH */
329 
330     case NUL:
331     nul_byte:
332         addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
333         break;
334 
335     case '"':
336     case '\'':
337     quote_char:
338         pzVal = pzScan;
339         pzScan = scanQuotedString( pzScan );
340         dataLen = pzScan - pzVal;
341         pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, pzVal,
342                               dataLen );
343         if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
344             ao_string_cook( pNV->v.strVal, NULL );
345         break;
346 
347     default:
348     default_char:
349         /*
350          *  We have found some strange text value.  It ends with a newline
351          *  or a comma.
352          */
353         pzVal = pzScan;
354         for (;;) {
355             char ch = *(pzScan++);
356             switch (ch) {
357             case NUL:
358                 pzScan--;
359                 dataLen = pzScan - pzVal;
360                 goto string_done;
361                 /* FALLTHROUGH */
362 
363             case '\n':
364                 if (   (pzScan > pzVal + 2)
365                     && (pzScan[-2] == '\\')
366                     && (pzScan[ 0] != NUL))
367                     continue;
368                 /* FALLTHROUGH */
369 
370             case ',':
371                 dataLen = (pzScan - pzVal) - 1;
372             string_done:
373                 pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen,
374                                       pzVal, dataLen );
375                 if (pNV != NULL)
376                     removeBackslashes( pNV->v.strVal );
377                 goto leave_scan_name;
378             }
379         }
380         break;
381     } leave_scan_name:;
382 
383     return pzScan;
384 }
385 
386 
387 /*  scanXmlEntry
388  *
389  *  We've found a '<' character.  We ignore this if it is a comment or a
390  *  directive.  If it is something else, then whatever it is we are looking
391  *  at is bogus.  Returning NULL stops processing.
392  */
393 static char const*
394 scanXmlEntry( char const* pzName, tOptionValue* pRes )
395 {
396     size_t nameLen = 1, valLen = 0;
397     char const*   pzScan = ++pzName;
398     char const*   pzVal;
399     tOptionValue  valu;
400     tOptionValue* pNewVal;
401     tOptionLoadMode save_mode = option_load_mode;
402 
403     if (! isalpha((int)*pzName)) {
404         switch (*pzName) {
405         default:
406             pzName = NULL;
407             break;
408 
409         case '!':
410             pzName = strstr( pzName, "-->" );
411             if (pzName != NULL)
412                 pzName += 3;
413             break;
414 
415         case '?':
416             pzName = strchr( pzName, '>' );
417             if (pzName != NULL)
418                 pzName++;
419             break;
420         }
421         return pzName;
422     }
423 
424     while (isalpha( (int)*++pzScan ))  nameLen++;
425     if (nameLen > 64)
426         return NULL;
427     valu.valType = OPARG_TYPE_STRING;
428 
429     switch (*pzScan) {
430     case ' ':
431     case '\t':
432         pzScan = parseAttributes(
433             NULL, (char*)pzScan, &option_load_mode, &valu );
434         if (*pzScan == '>') {
435             pzScan++;
436             break;
437         }
438 
439         if (*pzScan != '/') {
440             option_load_mode = save_mode;
441             return NULL;
442         }
443         /* FALLTHROUGH */
444 
445     case '/':
446         if (*++pzScan != '>') {
447             option_load_mode = save_mode;
448             return NULL;
449         }
450         addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
451         option_load_mode = save_mode;
452         return pzScan+2;
453 
454     default:
455         option_load_mode = save_mode;
456         return NULL;
457 
458     case '>':
459         pzScan++;
460         break;
461     }
462 
463     pzVal = pzScan;
464 
465     {
466         char z[68];
467         char* pzD = z;
468         int  ct = nameLen;
469         char const* pzS = pzName;
470 
471         *(pzD++) = '<';
472         *(pzD++) = '/';
473 
474         do  {
475             *(pzD++) = *(pzS++);
476         } while (--ct > 0);
477         *(pzD++) = '>';
478         *pzD = NUL;
479 
480         pzScan = strstr( pzScan, z );
481         if (pzScan == NULL) {
482             option_load_mode = save_mode;
483             return NULL;
484         }
485         valLen = (pzScan - pzVal);
486         pzScan += nameLen + 3;
487         while (isspace(  (int)*pzScan ))  pzScan++;
488     }
489 
490     switch (valu.valType) {
491     case OPARG_TYPE_NONE:
492         addStringValue( &(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
493         break;
494 
495     case OPARG_TYPE_STRING:
496         pNewVal = addStringValue(
497             &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);
498 
499         if (option_load_mode == OPTION_LOAD_KEEP)
500             break;
501         mungeString( pNewVal->v.strVal, option_load_mode );
502         break;
503 
504     case OPARG_TYPE_BOOLEAN:
505         addBoolValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );
506         break;
507 
508     case OPARG_TYPE_NUMERIC:
509         addNumberValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );
510         break;
511 
512     case OPARG_TYPE_HIERARCHY:
513     {
514         char* pz = AGALOC( valLen+1, "hierarchical scan" );
515         if (pz == NULL)
516             break;
517         memcpy( pz, pzVal, valLen );
518         pz[valLen] = NUL;
519         addNestedValue( &(pRes->v.nestVal), pzName, nameLen, pz, valLen );
520         AGFREE(pz);
521         break;
522     }
523 
524     case OPARG_TYPE_ENUMERATION:
525     case OPARG_TYPE_MEMBERSHIP:
526     default:
527         break;
528     }
529 
530     option_load_mode = save_mode;
531     return pzScan;
532 }
533 
534 
535 /*  unloadNestedArglist
536  *
537  *  Deallocate a list of option arguments.  This must have been gotten from
538  *  a hierarchical option argument, not a stacked list of strings.  It is
539  *  an internal call, so it is not validated.  The caller is responsible for
540  *  knowing what they are doing.
541  */
542 static void
543 unloadNestedArglist( tArgList* pAL )
544 {
545     int ct = pAL->useCt;
546     tCC** ppNV = pAL->apzArgs;
547 
548     while (ct-- > 0) {
549         tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++);
550         if (pNV->valType == OPARG_TYPE_HIERARCHY)
551             unloadNestedArglist( pNV->v.nestVal );
552         AGFREE( pNV );
553     }
554 
555     AGFREE( (void*)pAL );
556 }
557 
558 
559 /*=export_func  optionUnloadNested
560  *
561  * what:  Deallocate the memory for a nested value
562  * arg:   + tOptionValue const * + pOptVal + the hierarchical value +
563  *
564  * doc:
565  *  A nested value needs to be deallocated.  The pointer passed in should
566  *  have been gotten from a call to @code{configFileLoad()} (See
567  *  @pxref{libopts-configFileLoad}).
568 =*/
569 void
570 optionUnloadNested( tOptionValue const * pOV )
571 {
572     if (pOV == NULL) return;
573     if (pOV->valType != OPARG_TYPE_HIERARCHY) {
574         errno = EINVAL;
575         return;
576     }
577 
578     unloadNestedArglist( pOV->v.nestVal );
579 
580     AGFREE( (void*)pOV );
581 }
582 
583 
584 /*  sortNestedList
585  *
586  *  This is a _stable_ sort.  The entries are sorted alphabetically,
587  *  but within entries of the same name the ordering is unchanged.
588  *  Typically, we also hope the input is sorted.
589  */
590 static void
591 sortNestedList( tArgList* pAL )
592 {
593     int ix;
594     int lm = pAL->useCt;
595 
596     /*
597      *  This loop iterates "useCt" - 1 times.
598      */
599     for (ix = 0; ++ix < lm;) {
600         int iy = ix-1;
601         tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]);
602         tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]);
603 
604         /*
605          *  For as long as the new entry precedes the "old" entry,
606          *  move the old pointer.  Stop before trying to extract the
607          *  "-1" entry.
608          */
609         while (strcmp( pOldNV->pzName, pNewNV->pzName ) > 0) {
610             pAL->apzArgs[iy+1] = (void*)pOldNV;
611             pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]);
612             if (iy < 0)
613                 break;
614         }
615 
616         /*
617          *  Always store the pointer.  Sometimes it is redundant,
618          *  but the redundancy is cheaper than a test and branch sequence.
619          */
620         pAL->apzArgs[iy+1] = (void*)pNewNV;
621     }
622 }
623 
624 
625 /* optionLoadNested
626  * private:
627  *
628  * what:  parse a hierarchical option argument
629  * arg:   + char const*     + pzTxt   + the text to scan +
630  * arg:   + char const*     + pzName  + the name for the text +
631  * arg:   + size_t          + nameLen + the length of "name"  +
632  *
633  * ret_type:  tOptionValue*
634  * ret_desc:  An allocated, compound value structure
635  *
636  * doc:
637  *  A block of text represents a series of values.  It may be an
638  *  entire configuration file, or it may be an argument to an
639  *  option that takes a hierarchical value.
640  */
641 LOCAL tOptionValue*
642 optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen)
643 {
644     tOptionValue* pRes;
645     tArgList*     pAL;
646 
647     /*
648      *  Make sure we have some data and we have space to put what we find.
649      */
650     if (pzTxt == NULL) {
651         errno = EINVAL;
652         return NULL;
653     }
654     while (isspace( (int)*pzTxt ))  pzTxt++;
655     if (*pzTxt == NUL) {
656         errno = ENOENT;
657         return NULL;
658     }
659     pRes = AGALOC( sizeof(*pRes) + nameLen + 1, "nested args" );
660     if (pRes == NULL) {
661         errno = ENOMEM;
662         return NULL;
663     }
664     pRes->valType   = OPARG_TYPE_HIERARCHY;
665     pRes->pzName    = (char*)(pRes + 1);
666     memcpy( pRes->pzName, pzName, nameLen );
667     pRes->pzName[ nameLen ] = NUL;
668 
669     pAL = AGALOC( sizeof(*pAL), "nested arg list" );
670     if (pAL == NULL) {
671         AGFREE( pRes );
672         return NULL;
673     }
674     pRes->v.nestVal = pAL;
675     pAL->useCt   = 0;
676     pAL->allocCt = MIN_ARG_ALLOC_CT;
677 
678     /*
679      *  Scan until we hit a NUL.
680      */
681     do  {
682         while (isspace( (int)*pzTxt ))  pzTxt++;
683         if (isalpha( (int)*pzTxt )) {
684             pzTxt = scanNameEntry( pzTxt, pRes );
685         }
686         else switch (*pzTxt) {
687         case NUL: goto scan_done;
688         case '<': pzTxt = scanXmlEntry( pzTxt, pRes );
689                   if (*pzTxt == ',') pzTxt++;     break;
690         case '#': pzTxt = strchr( pzTxt, '\n' );  break;
691         default:  goto woops;
692         }
693     } while (pzTxt != NULL); scan_done:;
694 
695     pAL = pRes->v.nestVal;
696     if (pAL->useCt != 0) {
697         sortNestedList( pAL );
698         return pRes;
699     }
700 
701  woops:
702     AGFREE( pRes->v.nestVal );
703     AGFREE( pRes );
704     return NULL;
705 }
706 
707 
708 /*=export_func  optionNestedVal
709  * private:
710  *
711  * what:  parse a hierarchical option argument
712  * arg:   + tOptions* + pOpts    + program options descriptor +
713  * arg:   + tOptDesc* + pOptDesc + the descriptor for this arg +
714  *
715  * doc:
716  *  Nested value was found on the command line
717 =*/
718 void
719 optionNestedVal( tOptions* pOpts, tOptDesc* pOD )
720 {
721     tOptionValue* pOV = optionLoadNested(
722         pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name));
723 
724     if (pOV != NULL)
725         addArgListEntry( &(pOD->optCookie), (void*)pOV );
726 }
727 /*
728  * Local Variables:
729  * mode: C
730  * c-file-style: "stroustrup"
731  * indent-tabs-mode: nil
732  * End:
733  * end of autoopts/nested.c */
734