Coverage Report

Created: 2023-05-19 06:16

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