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/load.c
Line
Count
Source
1
2
/**
3
 *  \file load.c
4
 *
5
 *  This file contains the routines that deal with processing text strings
6
 *  for options, either from a NUL-terminated string passed in or from an
7
 *  rc/ini file.
8
 *
9
 * @addtogroup autoopts
10
 * @{
11
 */
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
static bool
35
get_realpath(char * buf, size_t b_sz)
36
0
{
37
0
#if defined(HAVE_CANONICALIZE_FILE_NAME)
38
0
    {
39
0
        size_t name_len;
40
41
0
        char * pz = canonicalize_file_name(buf);
42
0
        if (pz == NULL)
43
0
            return false;
44
45
0
        name_len = strlen(pz);
46
0
        if (name_len >= (size_t)b_sz) {
47
0
            free(pz);
48
0
            return false;
49
0
        }
50
51
0
        memcpy(buf, pz, name_len + 1);
52
0
        free(pz);
53
0
    }
54
55
#elif defined(HAVE_REALPATH)
56
    {
57
        size_t name_len;
58
        char z[PATH_MAX+1];
59
60
        if (realpath(buf, z) == NULL)
61
            return false;
62
63
        name_len = strlen(z);
64
        if (name_len >= b_sz)
65
            return false;
66
67
        memcpy(buf, z, name_len + 1);
68
    }
69
#endif
70
0
    return true;
71
0
}
72
73
/*=export_func  optionMakePath
74
 * private:
75
 *
76
 * what:  translate and construct a path
77
 * arg:   + char *       + p_buf     + The result buffer +
78
 * arg:   + int          + b_sz      + The size of this buffer +
79
 * arg:   + char const * + fname     + The input name +
80
 * arg:   + char const * + prg_path  + The full path of the current program +
81
 *
82
 * ret-type: bool
83
 * ret-desc: true if the name was handled, otherwise false.
84
 *           If the name does not start with ``$'', then it is handled
85
 *           simply by copying the input name to the output buffer and
86
 *           resolving the name with either
87
 *           @code{canonicalize_file_name(3GLIBC)} or @code{realpath(3C)}.
88
 *
89
 * doc:
90
 *
91
 *  This routine will copy the @code{pzName} input name into the
92
 *  @code{pzBuf} output buffer, not exceeding @code{bufSize} bytes.  If the
93
 *  first character of the input name is a @code{'$'} character, then there
94
 *  is special handling:
95
 *  @*
96
 *  @code{$$} is replaced with the directory name of the @code{pzProgPath},
97
 *  searching @code{$PATH} if necessary.
98
 *  @*
99
 *  @code{$@} is replaced with the AutoGen package data installation directory
100
 *  (aka @code{pkgdatadir}).
101
 *  @*
102
 *  @code{$NAME} is replaced by the contents of the @code{NAME} environment
103
 *  variable.  If not found, the search fails.
104
 *
105
 *  Please note: both @code{$$} and @code{$NAME} must be at the start of the
106
 *     @code{pzName} string and must either be the entire string or be followed
107
 *     by the @code{'/'} (backslash on windows) character.
108
 *
109
 * err:  @code{false} is returned if:
110
 *       @*
111
 *       @bullet{} The input name exceeds @code{bufSize} bytes.
112
 *       @*
113
 *       @bullet{} @code{$$}, @code{$@@} or @code{$NAME} is not the full string
114
 *                 and the next character is not '/'.
115
 *       @*
116
 *       @bullet{} libopts was built without PKGDATADIR defined and @code{$@@}
117
 *                 was specified.
118
 *       @*
119
 *       @bullet{} @code{NAME} is not a known environment variable
120
 *       @*
121
 *       @bullet{} @code{canonicalize_file_name} or @code{realpath} return
122
 *                 errors (cannot resolve the resulting path).
123
=*/
124
bool
125
optionMakePath(char * p_buf, int b_sz, char const * fname, char const * prg_path)
126
0
{
127
0
    {
128
0
        size_t len = strlen(fname);
129
130
0
        if (((size_t)b_sz <= len) || (len == 0))
131
0
            return false;
132
0
    }
133
134
    /*
135
     *  IF not an environment variable, just copy the data
136
     */
137
0
    if (*fname != '$') {
138
0
        char   const * src = fname;
139
0
        char * dst = p_buf;
140
0
        int    ct  = b_sz;
141
142
0
        for (;;) {
143
0
            if ( (*(dst++) = *(src++)) == NUL)
144
0
                break;
145
0
            if (--ct <= 0)
146
0
                return false;
147
0
        }
148
0
    }
149
150
    /*
151
     *  IF the name starts with "$$", then it must be "$$" or
152
     *  it must start with "$$/".  In either event, replace the "$$"
153
     *  with the path to the executable and append a "/" character.
154
     */
155
0
    else switch (fname[1]) {
156
0
    case NUL:
157
0
        return false;
158
159
0
    case '$':
160
0
        if (! add_prog_path(p_buf, b_sz, fname, prg_path))
161
0
            return false;
162
0
        break;
163
164
0
    case '@':
165
0
        if (program_pkgdatadir[0] == NUL)
166
0
            return false;
167
168
0
        if (snprintf(p_buf, (size_t)b_sz, "%s%s",
169
0
                     program_pkgdatadir, fname + 2) >= b_sz)
170
0
            return false;
171
0
        break;
172
173
0
    default:
174
0
        if (! add_env_val(p_buf, b_sz, fname))
175
0
            return false;
176
0
    }
177
178
0
    return get_realpath(p_buf, b_sz);
179
0
}
180
181
/**
182
 * convert a leading "$$" into a path to the executable.
183
 */
184
static bool
185
add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path)
186
0
{
187
0
    char const *   path;
188
0
    char const *   pz;
189
0
    int     skip = 2;
190
0
    size_t  fname_len;
191
0
    size_t  dir_len;  //!< length of the directory portion of the path to the exe
192
193
0
    switch (fname[2]) {
194
0
    case DIRCH:
195
0
        skip = 3;
196
0
    case NUL:
197
0
        break;
198
0
    default:
199
0
        return false;
200
0
    }
201
202
    /*
203
     *  See if the path is included in the program name.
204
     *  If it is, we're done.  Otherwise, we have to hunt
205
     *  for the program using "pathfind".
206
     */
207
0
    if (strchr(prg_path, DIRCH) != NULL)
208
0
        path = prg_path;
209
0
    else {
210
0
        path = pathfind(getenv("PATH"), (char *)prg_path, "rx");
211
212
0
        if (path == NULL)
213
0
            return false;
214
0
    }
215
216
0
    pz = strrchr(path, DIRCH);
217
218
    /*
219
     *  IF we cannot find a directory name separator,
220
     *  THEN we do not have a path name to our executable file.
221
     */
222
0
    if (pz == NULL)
223
0
        return false;
224
225
0
    fname    += skip;
226
0
    fname_len = strlen(fname) + 1; // + NUL byte
227
0
    dir_len   = (pz - path) + 1;   // + dir sep character
228
229
    /*
230
     *  Concatenate the file name to the end of the executable path.
231
     *  The result may be either a file or a directory.
232
     */
233
0
    if (dir_len + fname_len > (unsigned)b_sz)
234
0
        return false;
235
236
0
    memcpy(buf, path, dir_len);
237
0
    memcpy(buf + dir_len, fname, fname_len);
238
239
    /*
240
     *  If the "path" path was gotten from "pathfind()", then it was
241
     *  allocated and we need to deallocate it.
242
     */
243
0
    if (path != prg_path)
244
0
        AGFREE(path);
245
0
    return true;
246
0
}
247
248
/**
249
 * Add an environment variable value.
250
 */
251
static bool
252
add_env_val(char * buf, int buf_sz, char const * name)
253
0
{
254
0
    char * dir_part = buf;
255
256
0
    for (;;) {
257
0
        int ch = (int)*++name;
258
0
        if (! IS_VALUE_NAME_CHAR(ch))
259
0
            break;
260
0
        *(dir_part++) = (char)ch;
261
0
    }
262
263
0
    if (dir_part == buf)
264
0
        return false;
265
266
0
    *dir_part = NUL;
267
268
0
    dir_part = getenv(buf);
269
270
    /*
271
     *  Environment value not found -- skip the home list entry
272
     */
273
0
    if (dir_part == NULL)
274
0
        return false;
275
276
0
    {
277
0
        size_t dir_len = strlen(dir_part);
278
0
        size_t nm_len  = strlen(name) + 1;
279
        
280
0
        if (dir_len + nm_len >= (unsigned)buf_sz)
281
0
            return false;
282
0
        memcpy(buf, dir_part, dir_len);
283
0
        memcpy(buf + dir_len, name, nm_len);
284
0
    }
285
286
0
    return true;
287
0
}
288
289
/**
290
 * Trim leading and trailing white space.
291
 * If we are cooking the text and the text is quoted, then "cook"
292
 * the string.  To cook, the string must be quoted.
293
 *
294
 * @param[in,out] txt  the input and output string
295
 * @param[in]     mode the handling mode (cooking method)
296
 */
297
static void
298
munge_str(char * txt, tOptionLoadMode mode)
299
0
{
300
0
    char * end;
301
302
0
    if (mode == OPTION_LOAD_KEEP)
303
0
        return;
304
305
0
    if (IS_WHITESPACE_CHAR(*txt)) {
306
0
        char * src = SPN_WHITESPACE_CHARS(txt+1);
307
0
        size_t l   = strlen(src) + 1;
308
0
        memmove(txt, src, l);
309
0
        end = txt + l - 1;
310
311
0
    } else
312
0
        end = txt + strlen(txt);
313
314
0
    end  = SPN_WHITESPACE_BACK(txt, end);
315
0
    *end = NUL;
316
317
0
    if (mode == OPTION_LOAD_UNCOOKED)
318
0
        return;
319
320
0
    switch (*txt) {
321
0
    default: return;
322
0
    case '"':
323
0
    case '\'': break;
324
0
    }
325
326
0
    switch (end[-1]) {
327
0
    default: return;
328
0
    case '"':
329
0
    case '\'': break;
330
0
    }
331
332
0
    (void)ao_string_cook(txt, NULL);
333
0
}
334
335
static char *
336
assemble_arg_val(char * txt, tOptionLoadMode mode)
337
0
{
338
0
    char * end = strpbrk(txt, ARG_BREAK_STR);
339
0
    int    space_break;
340
341
    /*
342
     *  Not having an argument to a configurable name is okay.
343
     */
344
0
    if (end == NULL)
345
0
        return txt + strlen(txt);
346
347
    /*
348
     *  If we are keeping all whitespace, then the  modevalue starts with the
349
     *  character that follows the end of the configurable name, regardless
350
     *  of which character caused it.
351
     */
352
0
    if (mode == OPTION_LOAD_KEEP) {
353
0
        *(end++) = NUL;
354
0
        return end;
355
0
    }
356
357
    /*
358
     *  If the name ended on a white space character, remember that
359
     *  because we'll have to skip over an immediately following ':' or '='
360
     *  (and the white space following *that*).
361
     */
362
0
    space_break = IS_WHITESPACE_CHAR(*end);
363
0
    *(end++) = NUL;
364
365
0
    end = SPN_WHITESPACE_CHARS(end);
366
0
    if (space_break && ((*end == ':') || (*end == '=')))
367
0
        end = SPN_WHITESPACE_CHARS(end+1);
368
369
0
    return end;
370
0
}
371
372
static char *
373
trim_quotes(char * arg)
374
0
{
375
0
    switch (*arg) {
376
0
    case '"':
377
0
    case '\'':
378
0
        ao_string_cook(arg, NULL);
379
0
    }
380
0
    return arg;
381
0
}
382
383
/**
384
 * See if the option is to be processed in the current scan direction
385
 * (-1 or +1).
386
 */
387
static bool
388
direction_ok(opt_state_mask_t f, int dir)
389
0
{
390
0
    if (dir == 0)
391
0
        return true;
392
393
0
    switch (f & (OPTST_IMM|OPTST_DISABLE_IMM)) {
394
0
    case 0:
395
        /*
396
         *  The selected option has no immediate action.
397
         *  THEREFORE, if the direction is PRESETTING
398
         *  THEN we skip this option.
399
         */
400
0
        if (PRESETTING(dir))
401
0
            return false;
402
0
        break;
403
404
0
    case OPTST_IMM:
405
0
        if (PRESETTING(dir)) {
406
            /*
407
             *  We are in the presetting direction with an option we handle
408
             *  immediately for enablement, but normally for disablement.
409
             *  Therefore, skip if disabled.
410
             */
411
0
            if ((f & OPTST_DISABLED) == 0)
412
0
                return false;
413
0
        } else {
414
            /*
415
             *  We are in the processing direction with an option we handle
416
             *  immediately for enablement, but normally for disablement.
417
             *  Therefore, skip if NOT disabled.
418
             */
419
0
            if ((f & OPTST_DISABLED) != 0)
420
0
                return false;
421
0
        }
422
0
        break;
423
424
0
    case OPTST_DISABLE_IMM:
425
0
        if (PRESETTING(dir)) {
426
            /*
427
             *  We are in the presetting direction with an option we handle
428
             *  immediately for disablement, but normally for handling.
429
             *  Therefore, skip if NOT disabled.
430
             */
431
0
            if ((f & OPTST_DISABLED) != 0)
432
0
                return false;
433
0
        } else {
434
            /*
435
             *  We are in the processing direction with an option we handle
436
             *  immediately for disablement, but normally for handling.
437
             *  Therefore, skip if disabled.
438
             */
439
0
            if ((f & OPTST_DISABLED) == 0)
440
0
                return false;
441
0
        }
442
0
        break;
443
444
0
    case OPTST_IMM|OPTST_DISABLE_IMM:
445
        /*
446
         *  The selected option is always for immediate action.
447
         *  THEREFORE, if the direction is PROCESSING
448
         *  THEN we skip this option.
449
         */
450
0
        if (PROCESSING(dir))
451
0
            return false;
452
0
        break;
453
0
    }
454
0
    return true;
455
0
}
456
457
/**
458
 *  Load an option from a block of text.  The text must start with the
459
 *  configurable/option name and be followed by its associated value.
460
 *  That value may be processed in any of several ways.  See "tOptionLoadMode"
461
 *  in autoopts.h.
462
 *
463
 * @param[in,out] opts       program options descriptor
464
 * @param[in,out] opt_state  option processing state
465
 * @param[in,out] line       source line with long option name in it
466
 * @param[in]     direction  current processing direction (preset or not)
467
 * @param[in]     load_mode  option loading mode (OPTION_LOAD_*)
468
 */
469
static void
470
load_opt_line(tOptions * opts, tOptState * opt_state, char * line,
471
              tDirection direction, tOptionLoadMode load_mode )
472
0
{
473
    /*
474
     * When parsing a stored line, we only look at the characters after
475
     * a hyphen.  Long names must always be at least two characters and
476
     * short options are always exactly one character long.
477
     */
478
0
    line = SPN_LOAD_LINE_SKIP_CHARS(line);
479
480
0
    {
481
0
        char * arg = assemble_arg_val(line, load_mode);
482
483
0
        if (IS_OPTION_NAME_CHAR(line[1])) {
484
485
0
            if (! SUCCESSFUL(opt_find_long(opts, line, opt_state)))
486
0
                return;
487
488
0
        } else if (! SUCCESSFUL(opt_find_short(opts, *line, opt_state)))
489
0
            return;
490
491
0
        if ((! CALLED(direction)) && (opt_state->flags & OPTST_NO_INIT))
492
0
            return;
493
494
0
        opt_state->pzOptArg = trim_quotes(arg);
495
0
    }
496
497
0
    if (! direction_ok(opt_state->flags, direction))
498
0
        return;
499
500
    /*
501
     *  Fix up the args.
502
     */
503
0
    if (OPTST_GET_ARGTYPE(opt_state->pOD->fOptState) == OPARG_TYPE_NONE) {
504
0
        if (*opt_state->pzOptArg != NUL)
505
0
            return;
506
0
        opt_state->pzOptArg = NULL;
507
508
0
    } else if (opt_state->pOD->fOptState & OPTST_ARG_OPTIONAL) {
509
0
        if (*opt_state->pzOptArg == NUL)
510
0
             opt_state->pzOptArg = NULL;
511
0
        else {
512
0
            AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg");
513
0
            opt_state->flags |= OPTST_ALLOC_ARG;
514
0
        }
515
516
0
    } else {
517
0
        if (*opt_state->pzOptArg == NUL)
518
0
             opt_state->pzOptArg = zNil;
519
0
        else {
520
0
            AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg");
521
0
            opt_state->flags |= OPTST_ALLOC_ARG;
522
0
        }
523
0
    }
524
525
0
    {
526
0
        tOptionLoadMode sv = option_load_mode;
527
0
        option_load_mode = load_mode;
528
0
        handle_opt(opts, opt_state);
529
0
        option_load_mode = sv;
530
0
    }
531
0
}
532
533
/*=export_func  optionLoadLine
534
 *
535
 * what:  process a string for an option name and value
536
 *
537
 * arg:   tOptions *,   opts,  program options descriptor
538
 * arg:   char const *, line,  NUL-terminated text
539
 *
540
 * doc:
541
 *
542
 *  This is a client program callable routine for setting options from, for
543
 *  example, the contents of a file that they read in.  Only one option may
544
 *  appear in the text.  It will be treated as a normal (non-preset) option.
545
 *
546
 *  When passed a pointer to the option struct and a string, it will find
547
 *  the option named by the first token on the string and set the option
548
 *  argument to the remainder of the string.  The caller must NUL terminate
549
 *  the string.  The caller need not skip over any introductory hyphens.
550
 *  Any embedded new lines will be included in the option
551
 *  argument.  If the input looks like one or more quoted strings, then the
552
 *  input will be "cooked".  The "cooking" is identical to the string
553
 *  formation used in AutoGen definition files (@pxref{basic expression}),
554
 *  except that you may not use backquotes.
555
 *
556
 * err:   Invalid options are silently ignored.  Invalid option arguments
557
 *        will cause a warning to print, but the function should return.
558
=*/
559
void
560
optionLoadLine(tOptions * opts, char const * line)
561
0
{
562
0
    tOptState st = OPTSTATE_INITIALIZER(SET);
563
0
    char *    pz;
564
0
    proc_state_mask_t sv_flags = opts->fOptSet;
565
0
    opts->fOptSet &= ~OPTPROC_ERRSTOP;
566
0
    AGDUPSTR(pz, line, "opt line");
567
0
    load_opt_line(opts, &st, pz, DIRECTION_CALLED, OPTION_LOAD_COOKED);
568
0
    AGFREE(pz);
569
0
    opts->fOptSet = sv_flags;
570
0
}
571
/** @}
572
 *
573
 * Local Variables:
574
 * mode: C
575
 * c-file-style: "stroustrup"
576
 * indent-tabs-mode: nil
577
 * End:
578
 * end of autoopts/load.c */