xref: /netbsd/external/bsd/ntp/dist/sntp/libopts/nested.c (revision 9034ec65)
1 /*	$NetBSD: nested.c,v 1.12 2020/05/25 20:47:34 christos Exp $	*/
2 
3 
4 /**
5  * \file nested.c
6  *
7  *  Handle options with arguments that contain nested values.
8  *
9  * @addtogroup autoopts
10  * @{
11  */
12 /*
13  *   Automated Options Nested Values module.
14  *
15  *  This file is part of AutoOpts, a companion to AutoGen.
16  *  AutoOpts is free software.
17  *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
18  *
19  *  AutoOpts is available under any one of two licenses.  The license
20  *  in use must be one of these two and the choice is under the control
21  *  of the user of the license.
22  *
23  *   The GNU Lesser General Public License, version 3 or later
24  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
25  *
26  *   The Modified Berkeley Software Distribution License
27  *      See the file "COPYING.mbsd"
28  *
29  *  These files have the following sha256 sums:
30  *
31  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
32  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
33  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
34  */
35 
36 typedef struct {
37     int     xml_ch;
38     int     xml_len;
39     char    xml_txt[8];
40 } xml_xlate_t;
41 
42 static xml_xlate_t const xml_xlate[] = {
43     { '&', 4, "amp;"  },
44     { '<', 3, "lt;"   },
45     { '>', 3, "gt;"   },
46     { '"', 5, "quot;" },
47     { '\'',5, "apos;" }
48 };
49 
50 #ifndef ENOMSG
51 #define ENOMSG ENOENT
52 #endif
53 
54 /* = = = START-STATIC-FORWARD = = = */
55 static void
56 remove_continuation(char * src);
57 
58 static char const *
59 scan_q_str(char const * pzTxt);
60 
61 static tOptionValue *
62 add_string(void ** pp, char const * name, size_t nm_len,
63            char const * val, size_t d_len);
64 
65 static tOptionValue *
66 add_bool(void ** pp, char const * name, size_t nm_len,
67          char const * val, size_t d_len);
68 
69 static tOptionValue *
70 add_number(void ** pp, char const * name, size_t nm_len,
71            char const * val, size_t d_len);
72 
73 static tOptionValue *
74 add_nested(void ** pp, char const * name, size_t nm_len,
75            char * val, size_t d_len);
76 
77 static char const *
78 scan_name(char const * name, tOptionValue * res);
79 
80 static char const *
81 unnamed_xml(char const * txt);
82 
83 static char const *
84 scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
85 
86 static char const *
87 find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
88 
89 static char const *
90 scan_xml(char const * xml_name, tOptionValue * res_val);
91 
92 static void
93 sort_list(tArgList * arg_list);
94 /* = = = END-STATIC-FORWARD = = = */
95 
96 /**
97  *  Backslashes are used for line continuations.  We keep the newline
98  *  characters, but trim out the backslash:
99  */
100 static void
remove_continuation(char * src)101 remove_continuation(char * src)
102 {
103     char * pzD;
104 
105     do  {
106         while (*src == NL)  src++;
107         pzD = strchr(src, NL);
108         if (pzD == NULL)
109             return;
110 
111         /*
112          *  pzD has skipped at least one non-newline character and now
113          *  points to a newline character.  It now becomes the source and
114          *  pzD goes to the previous character.
115          */
116         src = pzD--;
117         if (*pzD != '\\')
118             pzD++;
119     } while (pzD == src);
120 
121     /*
122      *  Start shifting text.
123      */
124     for (;;) {
125         char ch = ((*pzD++) = *(src++));
126         switch (ch) {
127         case NUL:  return;
128         case '\\':
129             if (*src == NL)
130                 --pzD; /* rewrite on next iteration */
131         }
132     }
133 }
134 
135 /**
136  *  Find the end of a quoted string, skipping escaped quote characters.
137  */
138 static char const *
scan_q_str(char const * pzTxt)139 scan_q_str(char const * pzTxt)
140 {
141     char q = *(pzTxt++); /* remember the type of quote */
142 
143     for (;;) {
144         char ch = *(pzTxt++);
145         if (ch == NUL)
146             return pzTxt-1;
147 
148         if (ch == q)
149             return pzTxt;
150 
151         if (ch == '\\') {
152             ch = *(pzTxt++);
153             /*
154              *  IF the next character is NUL, drop the backslash, too.
155              */
156             if (ch == NUL)
157                 return pzTxt - 2;
158 
159             /*
160              *  IF the quote character or the escape character were escaped,
161              *  then skip both, as long as the string does not end.
162              */
163             if ((ch == q) || (ch == '\\')) {
164                 if (*(pzTxt++) == NUL)
165                     return pzTxt-1;
166             }
167         }
168     }
169 }
170 
171 
172 /**
173  *  Associate a name with either a string or no value.
174  *
175  * @param[in,out] pp        argument list to add to
176  * @param[in]     name      the name of the "suboption"
177  * @param[in]     nm_len    the length of the name
178  * @param[in]     val       the string value for the suboption
179  * @param[in]     d_len     the length of the value
180  *
181  * @returns the new value structure
182  */
183 static tOptionValue *
add_string(void ** pp,char const * name,size_t nm_len,char const * val,size_t d_len)184 add_string(void ** pp, char const * name, size_t nm_len,
185            char const * val, size_t d_len)
186 {
187     tOptionValue * pNV;
188     size_t sz = nm_len + d_len + sizeof(*pNV);
189 
190     pNV = AGALOC(sz, "option name/str value pair");
191 
192     if (val == NULL) {
193         pNV->valType = OPARG_TYPE_NONE;
194         pNV->pzName = pNV->v.strVal;
195 
196     } else {
197         pNV->valType = OPARG_TYPE_STRING;
198         if (d_len > 0) {
199             char const * src = val;
200             char * pzDst = pNV->v.strVal;
201             int    ct    = (int)d_len;
202             do  {
203                 int ch = *(src++) & 0xFF;
204                 if (ch == NUL) goto data_copy_done;
205                 if (ch == '&')
206                     ch = get_special_char(&src, &ct);
207                 *(pzDst++) = (char)ch;
208             } while (--ct > 0);
209         data_copy_done:
210             *pzDst = NUL;
211 
212         } else {
213             pNV->v.strVal[0] = NUL;
214         }
215 
216         pNV->pzName = pNV->v.strVal + d_len + 1;
217     }
218 
219     memcpy(pNV->pzName, name, nm_len);
220     pNV->pzName[ nm_len ] = NUL;
221     addArgListEntry(pp, pNV);
222     return pNV;
223 }
224 
225 /**
226  *  Associate a name with a boolean value
227  *
228  * @param[in,out] pp        argument list to add to
229  * @param[in]     name      the name of the "suboption"
230  * @param[in]     nm_len    the length of the name
231  * @param[in]     val       the boolean value for the suboption
232  * @param[in]     d_len     the length of the value
233  *
234  * @returns the new value structure
235  */
236 static tOptionValue *
add_bool(void ** pp,char const * name,size_t nm_len,char const * val,size_t d_len)237 add_bool(void ** pp, char const * name, size_t nm_len,
238          char const * val, size_t d_len)
239 {
240     size_t sz = nm_len + sizeof(tOptionValue) + 1;
241     tOptionValue * new_val = AGALOC(sz, "bool val");
242 
243     /*
244      * Scan over whitespace is constrained by "d_len"
245      */
246     while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
247         d_len--; val++;
248     }
249 
250     if (d_len == 0)
251         new_val->v.boolVal = 0;
252 
253     else if (IS_DEC_DIGIT_CHAR(*val))
254         new_val->v.boolVal = (unsigned)atoi(val);
255 
256     else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
257 
258     new_val->valType = OPARG_TYPE_BOOLEAN;
259     new_val->pzName = (char *)(new_val + 1);
260     memcpy(new_val->pzName, name, nm_len);
261     new_val->pzName[ nm_len ] = NUL;
262     addArgListEntry(pp, new_val);
263     return new_val;
264 }
265 
266 /**
267  *  Associate a name with strtol() value, defaulting to zero.
268  *
269  * @param[in,out] pp        argument list to add to
270  * @param[in]     name      the name of the "suboption"
271  * @param[in]     nm_len    the length of the name
272  * @param[in]     val       the numeric value for the suboption
273  * @param[in]     d_len     the length of the value
274  *
275  * @returns the new value structure
276  */
277 static tOptionValue *
add_number(void ** pp,char const * name,size_t nm_len,char const * val,size_t d_len)278 add_number(void ** pp, char const * name, size_t nm_len,
279            char const * val, size_t d_len)
280 {
281     size_t sz = nm_len + sizeof(tOptionValue) + 1;
282     tOptionValue * new_val = AGALOC(sz, "int val");
283 
284     /*
285      * Scan over whitespace is constrained by "d_len"
286      */
287     while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
288         d_len--; val++;
289     }
290     if (d_len == 0)
291         new_val->v.longVal = 0;
292     else
293         new_val->v.longVal = strtol(val, 0, 0);
294 
295     new_val->valType = OPARG_TYPE_NUMERIC;
296     new_val->pzName  = (char *)(new_val + 1);
297     memcpy(new_val->pzName, name, nm_len);
298     new_val->pzName[ nm_len ] = NUL;
299     addArgListEntry(pp, new_val);
300     return new_val;
301 }
302 
303 /**
304  *  Associate a name with a nested/hierarchical value.
305  *
306  * @param[in,out] pp        argument list to add to
307  * @param[in]     name      the name of the "suboption"
308  * @param[in]     nm_len    the length of the name
309  * @param[in]     val       the nested values for the suboption
310  * @param[in]     d_len     the length of the value
311  *
312  * @returns the new value structure
313  */
314 static tOptionValue *
add_nested(void ** pp,char const * name,size_t nm_len,char * val,size_t d_len)315 add_nested(void ** pp, char const * name, size_t nm_len,
316            char * val, size_t d_len)
317 {
318     tOptionValue * new_val;
319 
320     if (d_len == 0) {
321         size_t sz = nm_len + sizeof(*new_val) + 1;
322         new_val = AGALOC(sz, "empty nest");
323         new_val->v.nestVal = NULL;
324         new_val->valType = OPARG_TYPE_HIERARCHY;
325         new_val->pzName = (char *)(new_val + 1);
326         memcpy(new_val->pzName, name, nm_len);
327         new_val->pzName[ nm_len ] = NUL;
328 
329     } else {
330         new_val = optionLoadNested(val, name, nm_len);
331     }
332 
333     if (new_val != NULL)
334         addArgListEntry(pp, new_val);
335 
336     return new_val;
337 }
338 
339 /**
340  *  We have an entry that starts with a name.  Find the end of it, cook it
341  *  (if called for) and create the name/value association.
342  */
343 static char const *
scan_name(char const * name,tOptionValue * res)344 scan_name(char const * name, tOptionValue * res)
345 {
346     tOptionValue * new_val;
347     char const *   pzScan = name+1; /* we know first char is a name char */
348     char const *   pzVal;
349     size_t         nm_len = 1;
350     size_t         d_len = 0;
351 
352     /*
353      *  Scan over characters that name a value.  These names may not end
354      *  with a colon, but they may contain colons.
355      */
356     pzScan = SPN_VALUE_NAME_CHARS(name + 1);
357     if (pzScan[-1] == ':')
358         pzScan--;
359     nm_len = (size_t)(pzScan - name);
360 
361     pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
362 
363  re_switch:
364 
365     switch (*pzScan) {
366     case '=':
367     case ':':
368         pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
369         if ((*pzScan == '=') || (*pzScan == ':'))
370             goto default_char;
371         goto re_switch;
372 
373     case NL:
374     case ',':
375         pzScan++;
376         /* FALLTHROUGH */
377 
378     case NUL:
379         add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
380         break;
381 
382     case '"':
383     case '\'':
384         pzVal = pzScan;
385         pzScan = scan_q_str(pzScan);
386         d_len = (size_t)(pzScan - pzVal);
387         new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
388                          d_len);
389         if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
390             ao_string_cook(new_val->v.strVal, NULL);
391         break;
392 
393     default:
394     default_char:
395         /*
396          *  We have found some strange text value.  It ends with a newline
397          *  or a comma.
398          */
399         pzVal = pzScan;
400         for (;;) {
401             char ch = *(pzScan++);
402             switch (ch) {
403             case NUL:
404                 pzScan--;
405                 d_len = (size_t)(pzScan - pzVal);
406                 goto string_done;
407                 /* FALLTHROUGH */
408 
409             case NL:
410                 if (   (pzScan > pzVal + 2)
411                     && (pzScan[-2] == '\\')
412                     && (pzScan[ 0] != NUL))
413                     continue;
414                 /* FALLTHROUGH */
415 
416             case ',':
417                 d_len = (size_t)(pzScan - pzVal) - 1;
418             string_done:
419                 new_val = add_string(&(res->v.nestVal), name, nm_len,
420                                      pzVal, d_len);
421                 if (new_val != NULL)
422                     remove_continuation(new_val->v.strVal);
423                 goto leave_scan_name;
424             }
425         }
426         break;
427     } leave_scan_name:;
428 
429     return pzScan;
430 }
431 
432 /**
433  * Some xml element that does not start with a name.
434  * The next character must be either '!' (introducing a comment),
435  * or '?' (introducing an XML meta-marker of some sort).
436  * We ignore these and indicate an error (NULL result) otherwise.
437  *
438  * @param[in] txt  the text within an xml bracket
439  * @returns the address of the character after the closing marker, or NULL.
440  */
441 static char const *
unnamed_xml(char const * txt)442 unnamed_xml(char const * txt)
443 {
444     switch (*txt) {
445     default:
446         txt = NULL;
447         break;
448 
449     case '!':
450         txt = strstr(txt, "-->");
451         if (txt != NULL)
452             txt += 3;
453         break;
454 
455     case '?':
456         txt = strchr(txt, '>');
457         if (txt != NULL)
458             txt++;
459         break;
460     }
461     return txt;
462 }
463 
464 /**
465  *  Scan off the xml element name, and the rest of the header, too.
466  *  Set the value type to NONE if it ends with "/>".
467  *
468  * @param[in]  name    the first name character (alphabetic)
469  * @param[out] nm_len  the length of the name
470  * @param[out] val     set valType field to STRING or NONE.
471  *
472  * @returns the scan resumption point, or NULL on error
473  */
474 static char const *
scan_xml_name(char const * name,size_t * nm_len,tOptionValue * val)475 scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
476 {
477     char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
478     *nm_len = (size_t)(scan - name);
479     if (*nm_len > 64)
480         return NULL;
481     val->valType = OPARG_TYPE_STRING;
482 
483     if (IS_WHITESPACE_CHAR(*scan)) {
484         /*
485          * There are attributes following the name.  Parse 'em.
486          */
487         scan = SPN_WHITESPACE_CHARS(scan);
488         scan = parse_attrs(NULL, scan, &option_load_mode, val);
489         if (scan == NULL)
490             return NULL; /* oops */
491     }
492 
493     if (! IS_END_XML_TOKEN_CHAR(*scan))
494         return NULL; /* oops */
495 
496     if (*scan == '/') {
497         /*
498          * Single element XML entries get inserted as an empty string.
499          */
500         if (*++scan != '>')
501             return NULL;
502         val->valType = OPARG_TYPE_NONE;
503     }
504     return scan+1;
505 }
506 
507 /**
508  * We've found a closing '>' without a preceding '/', thus we must search
509  * the text for '<name/>' where "name" is the name of the XML element.
510  *
511  * @param[in]  name     the start of the name in the element header
512  * @param[in]  nm_len   the length of that name
513  * @param[out] len      the length of the value (string between header and
514  *                      the trailer/tail.
515  * @returns the character after the trailer, or NULL if not found.
516  */
517 static char const *
find_end_xml(char const * src,size_t nm_len,char const * val,size_t * len)518 find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
519 {
520     char z[72] = "</";
521     char * dst = z + 2;
522 
523     do  {
524         *(dst++) = *(src++);
525     } while (--nm_len > 0); /* nm_len is known to be 64 or less */
526     *(dst++) = '>';
527     *dst = NUL;
528 
529     {
530         char const * res = strstr(val, z);
531 
532         if (res != NULL) {
533             char const * end = (option_load_mode != OPTION_LOAD_KEEP)
534                 ? SPN_WHITESPACE_BACK(val, res)
535                 : res;
536             *len = (size_t)(end - val); /* includes trailing white space */
537             res =  SPN_WHITESPACE_CHARS(res + (dst - z));
538         }
539         return res;
540     }
541 }
542 
543 /**
544  *  We've found a '<' character.  We ignore this if it is a comment or a
545  *  directive.  If it is something else, then whatever it is we are looking
546  *  at is bogus.  Returning NULL stops processing.
547  *
548  * @param[in]     xml_name  the name of an xml bracket (usually)
549  * @param[in,out] res_val   the option data derived from the XML element
550  *
551  * @returns the place to resume scanning input
552  */
553 static char const *
scan_xml(char const * xml_name,tOptionValue * res_val)554 scan_xml(char const * xml_name, tOptionValue * res_val)
555 {
556     size_t          nm_len, v_len;
557     char const *    scan;
558     char const *    val_str;
559     tOptionValue    valu;
560     tOptionLoadMode save_mode = option_load_mode;
561 
562     if (! IS_VAR_FIRST_CHAR(*++xml_name))
563         return unnamed_xml(xml_name);
564 
565     /*
566      * "scan_xml_name()" may change "option_load_mode".
567      */
568     val_str = scan_xml_name(xml_name, &nm_len, &valu);
569     if (val_str == NULL)
570         goto bail_scan_xml;
571 
572     if (valu.valType == OPARG_TYPE_NONE)
573         scan = val_str;
574     else {
575         if (option_load_mode != OPTION_LOAD_KEEP)
576             val_str = SPN_WHITESPACE_CHARS(val_str);
577         scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
578         if (scan == NULL)
579             goto bail_scan_xml;
580     }
581 
582     /*
583      * "scan" now points to where the scan is to resume after returning.
584      * It either points after "/>" at the end of the XML element header,
585      * or it points after the "</name>" tail based on the name in the header.
586      */
587 
588     switch (valu.valType) {
589     case OPARG_TYPE_NONE:
590         add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
591         break;
592 
593     case OPARG_TYPE_STRING:
594     {
595         tOptionValue * new_val = add_string(
596             &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
597 
598         if (option_load_mode != OPTION_LOAD_KEEP)
599             munge_str(new_val->v.strVal, option_load_mode);
600 
601         break;
602     }
603 
604     case OPARG_TYPE_BOOLEAN:
605         add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
606         break;
607 
608     case OPARG_TYPE_NUMERIC:
609         add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
610         break;
611 
612     case OPARG_TYPE_HIERARCHY:
613     {
614         char * pz = AGALOC(v_len+1, "h scan");
615         memcpy(pz, val_str, v_len);
616         pz[v_len] = NUL;
617         add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len);
618         AGFREE(pz);
619         break;
620     }
621 
622     case OPARG_TYPE_ENUMERATION:
623     case OPARG_TYPE_MEMBERSHIP:
624     default:
625         break;
626     }
627 
628     option_load_mode = save_mode;
629     return scan;
630 
631 bail_scan_xml:
632     option_load_mode = save_mode;
633     return NULL;
634 }
635 
636 
637 /**
638  *  Deallocate a list of option arguments.  This must have been gotten from
639  *  a hierarchical option argument, not a stacked list of strings.  It is
640  *  an internal call, so it is not validated.  The caller is responsible for
641  *  knowing what they are doing.
642  */
643 LOCAL void
unload_arg_list(tArgList * arg_list)644 unload_arg_list(tArgList * arg_list)
645 {
646     int ct = arg_list->useCt;
647     char const ** pnew_val = arg_list->apzArgs;
648 
649     while (ct-- > 0) {
650         tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++));
651         if (new_val->valType == OPARG_TYPE_HIERARCHY)
652             unload_arg_list(new_val->v.nestVal);
653         AGFREE(new_val);
654     }
655 
656     AGFREE(arg_list);
657 }
658 
659 /*=export_func  optionUnloadNested
660  *
661  * what:  Deallocate the memory for a nested value
662  * arg:   + tOptionValue const * + pOptVal + the hierarchical value +
663  *
664  * doc:
665  *  A nested value needs to be deallocated.  The pointer passed in should
666  *  have been gotten from a call to @code{configFileLoad()} (See
667  *  @pxref{libopts-configFileLoad}).
668 =*/
669 void
optionUnloadNested(tOptionValue const * opt_val)670 optionUnloadNested(tOptionValue const * opt_val)
671 {
672     if (opt_val == NULL) return;
673     if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
674         errno = EINVAL;
675         return;
676     }
677 
678     unload_arg_list(opt_val->v.nestVal);
679 
680     AGFREE(opt_val);
681 }
682 
683 /**
684  *  This is a _stable_ sort.  The entries are sorted alphabetically,
685  *  but within entries of the same name the ordering is unchanged.
686  *  Typically, we also hope the input is sorted.
687  */
688 static void
sort_list(tArgList * arg_list)689 sort_list(tArgList * arg_list)
690 {
691     int ix;
692     int lm = arg_list->useCt;
693 
694     /*
695      *  This loop iterates "useCt" - 1 times.
696      */
697     for (ix = 0; ++ix < lm;) {
698         int iy = ix-1;
699         tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]);
700         tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]);
701 
702         /*
703          *  For as long as the new entry precedes the "old" entry,
704          *  move the old pointer.  Stop before trying to extract the
705          *  "-1" entry.
706          */
707         while (strcmp(old_v->pzName, new_v->pzName) > 0) {
708             arg_list->apzArgs[iy+1] = VOIDP(old_v);
709             old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]);
710             if (iy < 0)
711                 break;
712         }
713 
714         /*
715          *  Always store the pointer.  Sometimes it is redundant,
716          *  but the redundancy is cheaper than a test and branch sequence.
717          */
718         arg_list->apzArgs[iy+1] = VOIDP(new_v);
719     }
720 }
721 
722 /*=
723  * private:
724  *
725  * what:  parse a hierarchical option argument
726  * arg:   + char const * + pzTxt  + the text to scan      +
727  * arg:   + char const * + pzName + the name for the text +
728  * arg:   + size_t       + nm_len + the length of "name"  +
729  *
730  * ret_type:  tOptionValue *
731  * ret_desc:  An allocated, compound value structure
732  *
733  * doc:
734  *  A block of text represents a series of values.  It may be an
735  *  entire configuration file, or it may be an argument to an
736  *  option that takes a hierarchical value.
737  *
738  *  If NULL is returned, errno will be set:
739  *  @itemize @bullet
740  *  @item
741  *  @code{EINVAL} the input text was NULL.
742  *  @item
743  *  @code{ENOMEM} the storage structures could not be allocated
744  *  @item
745  *  @code{ENOMSG} no configuration values were found
746  *  @end itemize
747 =*/
748 LOCAL tOptionValue *
optionLoadNested(char const * text,char const * name,size_t nm_len)749 optionLoadNested(char const * text, char const * name, size_t nm_len)
750 {
751     tOptionValue * res_val;
752 
753     /*
754      *  Make sure we have some data and we have space to put what we find.
755      */
756     if (text == NULL) {
757         errno = EINVAL;
758         return NULL;
759     }
760     text = SPN_WHITESPACE_CHARS(text);
761     if (*text == NUL) {
762         errno = ENOMSG;
763         return NULL;
764     }
765     res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
766     res_val->valType = OPARG_TYPE_HIERARCHY;
767     res_val->pzName  = (char *)(res_val + 1);
768     memcpy(res_val->pzName, name, nm_len);
769     res_val->pzName[nm_len] = NUL;
770 
771     {
772         tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
773 
774         res_val->v.nestVal = arg_list;
775         arg_list->useCt   = 0;
776         arg_list->allocCt = MIN_ARG_ALLOC_CT;
777     }
778 
779     /*
780      *  Scan until we hit a NUL.
781      */
782     do  {
783         text = SPN_WHITESPACE_CHARS(text);
784         if (IS_VAR_FIRST_CHAR(*text))
785             text = scan_name(text, res_val);
786 
787         else switch (*text) {
788         case NUL: goto scan_done;
789         case '<': text = scan_xml(text, res_val);
790                   if (text == NULL) goto woops;
791                   if (*text == ',') text++;
792 		  break;
793         case '#': text = strchr(text, NL);  break;
794         default:  goto woops;
795         }
796     } while (text != NULL); scan_done:;
797 
798     {
799         tArgList * al = res_val->v.nestVal;
800         if (al->useCt == 0) {
801             errno = ENOMSG;
802             goto woops;
803         }
804         if (al->useCt > 1)
805             sort_list(al);
806     }
807 
808     return res_val;
809 
810  woops:
811     AGFREE(res_val->v.nestVal);
812     AGFREE(res_val);
813     return NULL;
814 }
815 
816 /*=export_func  optionNestedVal
817  * private:
818  *
819  * what:  parse a hierarchical option argument
820  * arg:   + tOptions * + opts + program options descriptor +
821  * arg:   + tOptDesc * + od   + the descriptor for this arg +
822  *
823  * doc:
824  *  Nested value was found on the command line
825 =*/
826 void
optionNestedVal(tOptions * opts,tOptDesc * od)827 optionNestedVal(tOptions * opts, tOptDesc * od)
828 {
829     if (opts < OPTPROC_EMIT_LIMIT)
830         return;
831 
832     if (od->fOptState & OPTST_RESET) {
833         tArgList *    arg_list = od->optCookie;
834         int           ct;
835         char const ** av;
836 
837         if (arg_list == NULL)
838             return;
839         ct = arg_list->useCt;
840         av = arg_list->apzArgs;
841 
842         while (--ct >= 0) {
843             void * p = VOIDP(*(av++));
844             optionUnloadNested((tOptionValue const *)p);
845         }
846 
847         AGFREE(od->optCookie);
848 
849     } else {
850         tOptionValue * opt_val = optionLoadNested(
851             od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
852 
853         if (opt_val != NULL)
854             addArgListEntry(&(od->optCookie), VOIDP(opt_val));
855     }
856 }
857 
858 /**
859  * get_special_char
860  */
861 LOCAL int
get_special_char(char const ** ppz,int * ct)862 get_special_char(char const ** ppz, int * ct)
863 {
864     char const * pz = *ppz;
865     char *       rz;
866 
867     if (*ct < 3)
868         return '&';
869 
870     if (*pz == '#') {
871         int base = 10;
872         int retch;
873 
874         pz++;
875         if (*pz == 'x') {
876             base = 16;
877             pz++;
878         }
879         retch = (int)strtoul(pz, &rz, base);
880         pz = rz;
881         if (*pz != ';')
882             return '&';
883         base = (int)(++pz - *ppz);
884         if (base > *ct)
885             return '&';
886 
887         *ct -= base;
888         *ppz = pz;
889         return retch;
890     }
891 
892     {
893         int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
894         xml_xlate_t const * xlatp = xml_xlate;
895 
896         for (;;) {
897             if (  (*ct >= xlatp->xml_len)
898                && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) {
899                 *ppz += xlatp->xml_len;
900                 *ct  -= xlatp->xml_len;
901                 return xlatp->xml_ch;
902             }
903 
904             if (--ctr <= 0)
905                 break;
906             xlatp++;
907         }
908     }
909     return '&';
910 }
911 
912 /**
913  * emit_special_char
914  */
915 LOCAL void
emit_special_char(FILE * fp,int ch)916 emit_special_char(FILE * fp, int ch)
917 {
918     int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
919     xml_xlate_t const * xlatp = xml_xlate;
920 
921     putc('&', fp);
922     for (;;) {
923         if (ch == xlatp->xml_ch) {
924             fputs(xlatp->xml_txt, fp);
925             return;
926         }
927         if (--ctr <= 0)
928             break;
929         xlatp++;
930     }
931     fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
932 }
933 
934 /** @}
935  *
936  * Local Variables:
937  * mode: C
938  * c-file-style: "stroustrup"
939  * indent-tabs-mode: nil
940  * End:
941  * end of autoopts/nested.c */
942