Coverage Report

Created: 2026-02-26 06:20

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