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