GCC Code Coverage Report


Directory: src/
File: src/libcfgcli.c
Date: 2023-12-12 15:58:09
Exec Total Coverage
Lines: 445 812 54.8%
Functions: 14 21 66.7%
Branches: 336 758 44.3%

Line Branch Exec Source
1 /*******************************************************************************
2 * libcfgcli.c: this file is part of the libcfgcli library.
3
4 * libcfgcli: C library for parsing command line option and configuration files.
5
6 * Gitlab repository:
7 https://framagit.org/groolot-association/libcfgcli
8
9 * Copyright (c) 2019 Cheng Zhao <zhaocheng03@gmail.com>
10 * Copyright (c) 2023 Gregory David <dev@groolot.net>
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29
30 *******************************************************************************/
31
32 #include <stdlib.h>
33 #include <limits.h>
34 #include <stdint.h>
35 #include <ctype.h>
36 #include <string.h>
37 #include <strings.h>
38 #include "libcfgcli.h"
39
40 /*============================================================================*\
41 Definitions of macros
42 \*============================================================================*/
43
44 /* Settings on string allocation. */
45 #define CFGCLI_STR_INIT_SIZE 1024 /* initial size of dynamic string */
46 #define CFGCLI_STR_MAX_DOUBLE_SIZE 134217728 /* maximum string doubling size */
47 #define CFGCLI_NUM_MAX_SIZE(type) (CHAR_BIT * sizeof(type) / 3 + 2)
48
49 /* Settings on the source of the configurations. */
50 #define CFGCLI_SRC_NULL 0
51 #define CFGCLI_SRC_OF_OPT(x) (-x) /* -x for source being command line */
52 #define CFGCLI_SRC_VAL(x) ((x < 0) ? -(x) : x) /* abs(x) */
53
54 /* Definitions of error codes. */
55 #define CFGCLI_ERR_INIT (-1)
56 #define CFGCLI_ERR_MEMORY (-2)
57 #define CFGCLI_ERR_INPUT (-3)
58 #define CFGCLI_ERR_EXIST (-4)
59 #define CFGCLI_ERR_VALUE (-5)
60 #define CFGCLI_ERR_PARSE (-6)
61 #define CFGCLI_ERR_DTYPE (-7)
62 #define CFGCLI_ERR_CMD (-8)
63 #define CFGCLI_ERR_FILE (-9)
64 #define CFGCLI_ERR_UNKNOWN (-99)
65
66 #define CFGCLI_ERRNO(cfg) (((cfgcli_error_t *)cfg->error)->errno)
67 #define CFGCLI_IS_ERROR(cfg) (CFGCLI_ERRNO(cfg) != 0)
68
69 /* Check if a string is a valid command line option, or parser termination. */
70 #define CFGCLI_IS_OPT(a) ( \
71 a[0] == CFGCLI_CMD_FLAG && a[1] && \
72 ((isalpha(a[1]) && (!a[2] || a[2] == CFGCLI_CMD_ASSIGN)) || \
73 (a[1] == CFGCLI_CMD_FLAG && (!a[2] || \
74 (a[2] != CFGCLI_CMD_ASSIGN && isgraph(a[2]))))) \
75 )
76
77
78 /*============================================================================*\
79 Internal data structures
80 \*============================================================================*/
81
82 /* Data structure for storing verified configuration parameters. */
83 typedef struct {
84 cfgcli_dtype_t dtype; /* data type of the parameter */
85 int src; /* source of the value */
86 int opt; /* short command line option */
87 int narr; /* number of elements for the array */
88 size_t nlen; /* length of the parameter name */
89 size_t llen; /* length of the long option */
90 size_t vlen; /* length of the value */
91 size_t hlen; /* length of the help message */
92 char *name; /* name of the parameter */
93 char *lopt; /* long command line option */
94 char *value; /* value of the parameter */
95 void *var; /* variable for saving the retrieved value */
96 char *help; /* parameter help message */
97 } cfgcli_param_valid_t;
98
99 /* Data structure for storing verified command line functions. */
100 typedef struct {
101 int called; /* 1 if the function is already called */
102 int opt; /* short command line option */
103 size_t llen; /* length of the long option */
104 size_t hlen; /* length of the help message */
105 char *lopt; /* long command line option */
106 void (*func) (void *); /* pointer to the function */
107 void *args; /* pointer to the arguments */
108 char *help; /* parameter help message */
109 } cfgcli_func_valid_t;
110
111 /* Data structure for storing warning/error messages. */
112 typedef struct {
113 int errno; /* identifier of the warning/error */
114 int num; /* number of existing messages */
115 char *msg; /* warning and error messages */
116 size_t len; /* length of the existing messages */
117 size_t max; /* allocated space for the messages */
118 } cfgcli_error_t;
119
120 /* Data structure for storing an help line content. */
121 typedef struct {
122 cfgcli_dtype_t dtype; /* data type of the parameter */
123 int opt; /* short command line option */
124 char *lopt; /* long command line option */
125 char *name; /* name of the parameter */
126 char *help; /* parameter help message */
127 } cfgcli_help_line_t;
128
129 /* String parser states. */
130 typedef enum {
131 CFGCLI_PARSE_START, CFGCLI_PARSE_KEYWORD, CFGCLI_PARSE_EQUAL,
132 CFGCLI_PARSE_VALUE_START, CFGCLI_PARSE_VALUE,
133 CFGCLI_PARSE_QUOTE, CFGCLI_PARSE_QUOTE_END,
134 CFGCLI_PARSE_ARRAY_START, CFGCLI_PARSE_ARRAY_VALUE,
135 CFGCLI_PARSE_ARRAY_QUOTE, CFGCLI_PARSE_ARRAY_QUOTE_END,
136 CFGCLI_PARSE_ARRAY_NEWLINE, CFGCLI_PARSE_CLEAN,
137 CFGCLI_PARSE_ARRAY_END, CFGCLI_PARSE_ARRAY_DONE
138 } cfgcli_parse_state_t;
139
140 /* Return value for the parser status. */
141 typedef enum {
142 CFGCLI_PARSE_DONE,
143 CFGCLI_PARSE_PASS,
144 CFGCLI_PARSE_CONTINUE,
145 CFGCLI_PARSE_ERROR
146 } cfgcli_parse_return_t;
147
148
149 /*============================================================================*\
150 Functions for string manipulation
151 \*============================================================================*/
152
153 /******************************************************************************
154 Function `cfgcli_strnlen`:
155 Compute the length of a string by checking a limited number of characters.
156 Arguments:
157 * `src`: the input string;
158 * `max`: the maximum number of characters to be checked.
159 Return:
160 The length of the string, including the first '\0'; 0 if '\0' is not found.
161 ******************************************************************************/
162 static inline size_t cfgcli_strnlen(const char *src, const size_t max) {
163
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 for (size_t i = 0; i < max; i++) if (src[i] == '\0') return i + 1;
164 return 0;
165 }
166
167 /******************************************************************************
168 Function `cfgcli_msg`:
169 Append warning/error message to the error handler.
170 Arguments:
171 * `cfg`: entry for the configurations;
172 * `msg`: the null terminated warning/error message;
173 * `key`: the null terminated keyword for this message.
174 ******************************************************************************/
175 static void cfgcli_msg(cfgcli_t *cfg, const char *msg, const char *key) {
176 if (!msg || *msg == '\0') return;
177
178 cfgcli_error_t *err = (cfgcli_error_t *) cfg->error;
179 const size_t msglen = strlen(msg) + 1; /* suppose msg ends with '\0' */
180 size_t keylen, len;
181 char *tmp;
182
183 if (!key || *key == '\0') {
184 keylen = 0;
185 len = msglen; /* append only "msg" */
186 }
187 else {
188 keylen = strlen(key) + 1;
189 len = msglen + keylen + 1; /* append "msg: key" */
190 }
191
192 /* Double the allocated size if the space is not enough. */
193 len += err->len;
194 if (len > err->max) {
195 size_t max = 0;
196 if (err->max == 0) max = len;
197 else if (err->max >= CFGCLI_STR_MAX_DOUBLE_SIZE) {
198 if (SIZE_MAX - CFGCLI_STR_MAX_DOUBLE_SIZE >= err->max)
199 max = CFGCLI_STR_MAX_DOUBLE_SIZE + err->max;
200 }
201 else if (SIZE_MAX / 2 >= err->max) max = err->max << 1;
202 if (!max) {
203 err->errno = CFGCLI_ERR_MEMORY;
204 return;
205 }
206 if (len > max) max = len; /* the size is still not enough */
207
208 tmp = realloc(err->msg, max);
209 if (!tmp) {
210 err->errno = CFGCLI_ERR_MEMORY;
211 return;
212 }
213 err->msg = tmp;
214 err->max = max;
215 }
216
217 /* Record the message. */
218 tmp = err->msg + err->len;
219 memcpy(tmp, msg, msglen); /* '\0' is copied */
220 if (keylen) {
221 *(tmp + msglen - 1) = ':';
222 *(tmp + msglen) = ' ';
223 memcpy(tmp + msglen + 1, key, keylen);
224 }
225 err->len = len;
226 err->num += 1;
227 }
228
229
230 /*============================================================================*\
231 Functions for initialising parameters and functions
232 \*============================================================================*/
233
234 /******************************************************************************
235 Function `cfgcli_init`:
236 Initialise the entry for all parameters and command line functions.
237 Return:
238 The address of the structure.
239 ******************************************************************************/
240 1 cfgcli_t *cfgcli_init(void) {
241 1 cfgcli_t *cfg = calloc(1, sizeof(cfgcli_t));
242
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cfg) return NULL;
243
244 1 cfgcli_error_t *err = calloc(1, sizeof(cfgcli_error_t));
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!err) {
246 free(cfg);
247 return NULL;
248 }
249 1 err->msg = NULL;
250
251 1 cfg->params = cfg->funcs = NULL;
252 1 cfg->error = err;
253 1 return cfg;
254 }
255
256 /******************************************************************************
257 Function `cfgcli_print_help_line`:
258 Print short and long options with their dashes
259 Arguments:
260 * `buffer`: output buffer with options sets
261 * `help_line`: content to use for display
262 Return:
263 Output string length.
264 ******************************************************************************/
265 static size_t cfgcli_print_help_line(char *buffer, const cfgcli_help_line_t *help_line) {
266 size_t len = 0;
267 if (help_line->opt) {
268 len += sprintf(buffer, " -%c", help_line->opt);
269 if (help_line->lopt) {
270 len += sprintf(buffer + len, ",");
271 }
272 }
273 if (help_line->lopt) {
274 len += sprintf(buffer + len, " --%s", help_line->lopt);
275 }
276 if (help_line->dtype != CFGCLI_DTYPE_NULL && help_line->dtype != CFGCLI_DTYPE_BOOL) {
277 len += sprintf(buffer + len, " %s", help_line->name);
278 }
279 if (help_line->help) {
280 len += sprintf(buffer + len, "\n %s", help_line->help);
281 }
282 return len;
283 }
284
285 /******************************************************************************
286 Function `cfgcli_print_param`:
287 Wrapper to `cfgcli_print_help_line` for `cfgcli_param_valid_t` parameter
288 Arguments:
289 * `buffer`: output buffer with options sets
290 * `param`: parameter to display
291 Return:
292 Output string length.
293 ******************************************************************************/
294 static size_t cfgcli_print_param(char *buffer, const cfgcli_param_valid_t *param) {
295 cfgcli_help_line_t *help_content = malloc(sizeof(cfgcli_help_line_t));
296 help_content->dtype = param->dtype;
297 help_content->opt = param->opt;
298 help_content->lopt = param->lopt;
299 help_content->name = param->name;
300 help_content->help = param->help;
301 return cfgcli_print_help_line(buffer, help_content);
302 }
303
304 /******************************************************************************
305 Function `cfgcli_print_func`:
306 Wrapper to `cfgcli_print_help_line` for `cfgcli_func_valid_t` parameter
307 Arguments:
308 * `buffer`: output buffer with options sets
309 * `func`: function to display
310 Return:
311 Output string length.
312 ******************************************************************************/
313 static size_t cfgcli_print_func(char *buffer, const cfgcli_func_valid_t *func) {
314 cfgcli_help_line_t *help_content = malloc(sizeof(cfgcli_help_line_t));
315 help_content->dtype = CFGCLI_DTYPE_NULL;
316 help_content->opt = func->opt;
317 help_content->lopt = func->lopt;
318 help_content->name = NULL;
319 help_content->help = func->help;
320 return cfgcli_print_help_line(buffer, help_content);
321 }
322
323 /******************************************************************************
324 Function `cfgcli_print_help`:
325 Print help messages based on validated parameters
326 Arguments:
327 * `cfg`: entry for all configuration parameters;
328 ******************************************************************************/
329 void cfgcli_print_help(cfgcli_t *cfg) {
330 size_t used_len = 0;
331 int dash_size_overload = 13;
332 char buffer[CFGCLI_MAX_LOPT_LEN + CFGCLI_MAX_NAME_LEN + dash_size_overload + CFGCLI_MAX_HELP_LEN];
333 bzero(buffer, CFGCLI_MAX_LOPT_LEN + CFGCLI_MAX_NAME_LEN + dash_size_overload + CFGCLI_MAX_HELP_LEN);
334 if (cfg && !CFGCLI_IS_ERROR(cfg)) {
335 if (cfg->npar > 0) {
336 const cfgcli_param_valid_t *param = (cfgcli_param_valid_t *)cfg->params;
337 printf("Option%c:\n", cfg->npar > 1 ? 's' : '\0');
338 for (size_t i = 0; i < cfg->npar; ++i) {
339 used_len = cfgcli_print_param(buffer, &param[i]);
340 printf("%s\n", buffer);
341 bzero(buffer, used_len);
342 }
343 printf("\n");
344 }
345 else {
346 cfgcli_msg(cfg, "the parameter list is not set", NULL);
347 }
348 if (cfg->nfunc > 0) {
349 const cfgcli_func_valid_t *param = (cfgcli_func_valid_t *)cfg->funcs;
350 printf("Function%c:\n", cfg->nfunc > 1 ? 's' : '\0');
351 for (size_t i = 0; i < cfg->nfunc; ++i) {
352 used_len = cfgcli_print_func(buffer, &param[i]);
353 printf("%s\n", buffer);
354 bzero(buffer, used_len);
355 }
356 printf("\n");
357 }
358 else {
359 cfgcli_msg(cfg, "the function list is not set", NULL);
360 }
361 }
362 }
363
364 /******************************************************************************
365 Function `cfgcli_print_usage`:
366 Print usage messages based on validated parameters and provided progname
367 Arguments:
368 * `cfg`: entry for all configuration parameters;
369 * `progname`: program name to be displayed
370 ******************************************************************************/
371 void cfgcli_print_usage(cfgcli_t *cfg, char *progname) {
372 if (cfg && !CFGCLI_IS_ERROR(cfg)) {
373 char options[] = " [OPTIONS]";
374 char functions[] = " [FUNCTIONS]";
375 const char local_progname[] = "program";
376 if (!progname || *progname == '\0') {
377 progname = (char *)local_progname;
378 }
379 if (cfg->npar == 1) {
380 options[8] = ']'; options[9] = '\0'; } // Singularize
381 else if (cfg->npar == 0)
382 *options = '\0'; // Remove options
383 if (cfg->nfunc == 1) {
384 functions[10] = ']';
385 functions[11] = '\0';
386 } // Singularize
387 else if (cfg->nfunc == 0)
388 *functions = '\0'; // Remove functions
389 printf("Usage: %s%s%s\n", progname, options, functions);
390 }
391 }
392
393 /******************************************************************************
394 Function `cfgcli_set_params`:
395 Verify and register configuration parameters.
396 Arguments:
397 * `cfg`: entry for all configuration parameters;
398 * `param`: stucture for the input configuration parameters;
399 * `npar`: number of input configuration parameters.
400 Return:
401 Zero on success; non-zero on error.
402 ******************************************************************************/
403 1 int cfgcli_set_params(cfgcli_t *cfg, const cfgcli_param_t *param, const int npar) {
404 /* Validate arguments. */
405
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!cfg) return CFGCLI_ERR_INIT;
406
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
407
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!param || npar <= 0) {
408 cfgcli_msg(cfg, "the parameter list is not set", NULL);
409 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
410 }
411
412 /* Allocate memory for parameters. */
413 1 cfgcli_param_valid_t *vpar = realloc(cfg->params,
414 1 (npar + cfg->npar) * sizeof *vpar);
415
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!vpar) {
416 cfgcli_msg(cfg, "failed to allocate memory for parameters", NULL);
417 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
418 }
419 1 memset(vpar + cfg->npar, 0, npar * sizeof *vpar);
420 1 cfg->params = vpar;
421
422 /* Register parameters. */
423
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 times.
16 for (int i = 0; i < npar; i++) {
424 /* Reset the parameter holder. */
425 15 cfgcli_param_valid_t *par = (cfgcli_param_valid_t *) cfg->params + cfg->npar + i;
426 15 par->dtype = CFGCLI_DTYPE_NULL;
427 15 par->src = CFGCLI_SRC_NULL;
428 15 par->help = par->name = par->lopt = par->value = NULL;
429 15 par->var = NULL;
430
431 /* Create the string for the current index and short option. */
432 15 char tmp[CFGCLI_NUM_MAX_SIZE(int)];
433 15 sprintf(tmp, "%d", i);
434
435 /* Verify the name. */
436 15 char *str = param[i].name;
437
2/8
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
15 if (!str || (!isalpha(*str) && *str != '_' && *str != '-')) {
438 cfgcli_msg(cfg, "invalid parameter name in the list with index", tmp);
439 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
440 }
441 int j = 1;
442
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 15 times.
101 while (str[j] != '\0') {
443
3/6
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
86 if (!isalnum(str[j]) && str[j] != '_' && str[j] != '-') {
444 cfgcli_msg(cfg, "invalid parameter name in the list with index", tmp);
445 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
446 }
447
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 86 times.
86 if (++j >= CFGCLI_MAX_NAME_LEN) { /* no null termination */
448 cfgcli_msg(cfg, "invalid parameter name in the list with index", tmp);
449 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
450 }
451 }
452 15 par->name = str;
453 15 par->nlen = j + 1; /* length of name with the ending '\0' */
454
455 /* Verify the data type. */
456
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (CFGCLI_DTYPE_INVALID(param[i].dtype)) {
457 cfgcli_msg(cfg, "invalid data type for parameter", par->name);
458 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
459 }
460 15 par->dtype = param[i].dtype;
461
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (!param[i].var) {
462 cfgcli_msg(cfg, "variable unset for parameter", par->name);
463 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
464 }
465 15 par->var = param[i].var;
466
467 /* Verify command line options. */
468 15 char opt[3] = { 0 };
469
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (isalpha(param[i].opt)) {
470 15 par->opt = param[i].opt;
471 15 opt[0] = '-';
472 15 opt[1] = par->opt;
473 }
474 else if (param[i].opt) {
475 cfgcli_msg(cfg, "invalid short command line option for parameter",
476 par->name);
477 }
478
479 15 str = param[i].lopt;
480
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 if (str && str[j = 0] != '\0') {
481 75 do {
482
2/4
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 75 times.
75 if (!isgraph(str[j]) || str[j] == CFGCLI_CMD_ASSIGN) {
483 cfgcli_msg(cfg, "invalid long command line option for parameter",
484 par->name);
485 break;
486 }
487
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (++j >= CFGCLI_MAX_LOPT_LEN) { /* no null termination */
488 cfgcli_msg(cfg, "invalid long command line option for parameter",
489 par->name);
490 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
491 }
492 }
493
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 15 times.
75 while (str[j] != '\0');
494
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if (str[j] == '\0') {
495 15 par->lopt = str;
496 15 par->llen = j + 1; /* length of long option with the ending '\0' */
497 }
498 }
499
500 15 tmp[0] = par->opt;
501 15 tmp[1] = '\0';
502
503 /* Verify the help message. */
504 15 str = param[i].help;
505 15 j = 0;
506
2/2
✓ Branch 0 taken 388 times.
✓ Branch 1 taken 15 times.
403 while (str[j] != '\0') {
507
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 388 times.
388 if (++j >= CFGCLI_MAX_HELP_LEN) { /* no null termination */
508 cfgcli_msg(cfg, "invalid help (too long) for parameter", par->lopt ? par->lopt : opt);
509 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
510 }
511 }
512 15 par->help = str;
513 15 par->hlen = j + 1; /* length of help with the ending '\0' */
514
515 /* Check duplicates with the registered parameters. */
516
2/2
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 15 times.
120 for (j = 0; j < cfg->npar + i; j++) {
517
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (!strncmp(par->name, vpar[j].name, par->nlen)) {
518 cfgcli_msg(cfg, "duplicate parameter name", par->name);
519 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
520 }
521
2/4
✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 105 times.
105 if (par->opt && par->opt == vpar[j].opt) {
522 cfgcli_msg(cfg, "duplicate short command line option", tmp);
523 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
524 }
525
2/4
✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 105 times.
✗ Branch 3 not taken.
105 if (par->lopt && vpar[j].lopt &&
526
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 !strncmp(par->lopt, vpar[j].lopt, par->llen)) {
527 cfgcli_msg(cfg, "duplicate long command line option", par->lopt);
528 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
529 }
530 }
531
532 /* Check duplicates with the registered functions. */
533 15 cfgcli_func_valid_t *cfunc = (cfgcli_func_valid_t *) cfg->funcs;
534
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 for (j = 0; j < cfg->nfunc; j++) {
535 if (par->opt && par->opt == cfunc[j].opt) {
536 cfgcli_msg(cfg, "duplicate short command line option", tmp);
537 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
538 }
539 if (par->lopt && cfunc[j].lopt &&
540 !strncmp(par->lopt, cfunc[j].lopt, par->llen)) {
541 cfgcli_msg(cfg, "duplicate long command line option", par->lopt);
542 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
543 }
544 }
545 }
546
547 1 cfg->npar += npar;
548 1 return 0;
549 }
550
551 /******************************************************************************
552 Function `cfgcli_set_funcs`:
553 Verify and register command line functions.
554 Arguments:
555 * `cfg`: entry for all command line functions;
556 * `func`: stucture for the input functions;
557 * `nfunc`: number of input functions.
558 Return:
559 Zero on success; non-zero on error.
560 ******************************************************************************/
561 1 int cfgcli_set_funcs(cfgcli_t *cfg, const cfgcli_func_t *func, const int nfunc) {
562 /* Validate arguments. */
563
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!cfg) return CFGCLI_ERR_INIT;
564
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
565
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!func || nfunc <= 0) {
566 cfgcli_msg(cfg, "the function list is not set", NULL);
567 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
568 }
569
570 /* Allocate memory for command line functions. */
571 1 cfgcli_func_valid_t *vfunc = realloc(cfg->funcs,
572 1 (nfunc + cfg->nfunc) * sizeof *vfunc);
573
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!vfunc) {
574 cfgcli_msg(cfg, "failed to allocate memory for functions", NULL);
575 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
576 }
577 1 memset(vfunc + cfg->nfunc, 0, nfunc * sizeof *vfunc);
578 1 cfg->funcs = vfunc;
579
580 /* Register command line functions. */
581
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < nfunc; i++) {
582 /* Reset the command line function holder. */
583 2 cfgcli_func_valid_t *fun = (cfgcli_func_valid_t *) cfg->funcs + cfg->nfunc + i;
584 2 fun->lopt = NULL;
585 2 fun->func = NULL;
586 2 fun->args = NULL;
587 2 fun->help = NULL;
588
589 /* Create the string for the current index and short option. */
590 2 char tmp[CFGCLI_NUM_MAX_SIZE(int)];
591 2 sprintf(tmp, "%d", i);
592
593 /* Verify the command line options. */
594 2 char opt[3] = { 0 };
595
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (isalpha(func[i].opt)) {
596 1 fun->opt = func[i].opt;
597 1 opt[0] = '-';
598 1 opt[1] = (char)fun->opt;
599 }
600
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 else if (func[i].opt)
601 cfgcli_msg(cfg, "invalid short command line option for function index", tmp);
602
603 2 char *str = func[i].lopt;
604 2 int j = 0;
605
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (str && str[j] != '\0') {
606 11 do {
607
2/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
11 if (!isgraph(str[j]) || str[j] == CFGCLI_CMD_ASSIGN) {
608 cfgcli_msg(cfg, "invalid long command line option for function index",
609 tmp);
610 break;
611 }
612
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (++j >= CFGCLI_MAX_LOPT_LEN) { /* no null termination */
613 cfgcli_msg(cfg, "invalid long command line option for function index",
614 tmp);
615 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
616 }
617 }
618
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
11 while (str[j] != '\0');
619
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (str[j] == '\0') {
620 2 fun->lopt = str;
621 2 fun->llen = j + 1; /* length of long option with the ending '\0' */
622 }
623 }
624
625
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
2 if (!fun->opt && !fun->lopt) {
626 cfgcli_msg(cfg, "no valid command line option for function index", tmp);
627 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
628 }
629
630 /* Verify the function pointer. */
631
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!(fun->func = func[i].func)) {
632 cfgcli_msg(cfg, "function not set with index", tmp);
633 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
634 }
635 2 fun->args = func[i].args;
636
637 /* Verify the help message. */
638
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (func[i].help) {
639 29 str = func[i].help;
640 j = 0;
641
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 1 times.
29 while (str[j] != '\0') {
642
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if (++j >= CFGCLI_MAX_HELP_LEN) { /* no null termination */
643 cfgcli_msg(cfg, "invalid help (too long) for function", fun->lopt ? fun->lopt : opt);
644 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
645 }
646 }
647 1 fun->help = str;
648 1 fun->hlen = j + 1; /* length of help with the ending '\0' */
649 }
650
651 /* Check duplicates with the registered functions. */
652
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 for (j = 0; j < cfg->nfunc + i; j++) {
653 /* Function and arguments cannot both be identical. */
654
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (fun->func == vfunc[j].func && fun->args == vfunc[j].args) {
655 cfgcli_msg(cfg, "duplicate function with index", tmp);
656 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
657 }
658
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (fun->opt && fun->opt == vfunc[j].opt) {
659 tmp[0] = fun->opt;
660 tmp[1] = '\0';
661 cfgcli_msg(cfg, "duplicate short command line option", tmp);
662 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
663 }
664
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (fun->lopt && vfunc[j].lopt &&
665
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 !strncmp(fun->lopt, vfunc[j].lopt, fun->llen)) {
666 cfgcli_msg(cfg, "duplicate long command line option", fun->lopt);
667 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
668 }
669 }
670
671 /* Check duplicates with the registered parameters. */
672 2 cfgcli_param_valid_t *cpar = (cfgcli_param_valid_t *) cfg->params;
673
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 2 times.
32 for (j = 0; j < cfg->npar; j++) {
674
3/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
30 if (fun->opt && fun->opt == cpar[j].opt) {
675 tmp[0] = fun->opt;
676 tmp[1] = '\0';
677 cfgcli_msg(cfg, "duplicate short command line option", tmp);
678 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
679 }
680
2/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 if (fun->lopt && cpar[j].lopt &&
681
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 !strncmp(fun->lopt, cpar[j].lopt, fun->llen)) {
682 cfgcli_msg(cfg, "duplicate long command line option", fun->lopt);
683 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
684 }
685 }
686 }
687
688 1 cfg->nfunc += nfunc;
689 1 return 0;
690 }
691
692
693 /*============================================================================*\
694 Functions for parsing configurations represented by strings
695 \*============================================================================*/
696
697 /******************************************************************************
698 Function `cfgcli_parse_line`:
699 Read the configuration from a line of a configration file.
700 Arguments:
701 * `line`: the null terminated string line;
702 * `len`: length of the line, NOT including the first '\0';
703 * `key`: address of the retrieved keyword;
704 * `value`: address of the retrieved value;
705 * `state`: initial state for the parser.
706 Return:
707 Parser status.
708 ******************************************************************************/
709 22 static cfgcli_parse_return_t cfgcli_parse_line(char *line, const size_t len,
710 char **key, char **value, cfgcli_parse_state_t state) {
711
4/6
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
22 if (!line || *line == '\0' || len == 0) return CFGCLI_PARSE_PASS;
712 char quote = '\0'; /* handle quotation marks */
713 char *newline = NULL; /* handle line continuation */
714
2/2
✓ Branch 0 taken 351 times.
✓ Branch 1 taken 13 times.
364 for (size_t i = 0; i < len; i++) {
715 351 char c = line[i];
716
12/14
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
✓ Branch 3 taken 31 times.
✓ Branch 4 taken 28 times.
✓ Branch 5 taken 43 times.
✓ Branch 6 taken 33 times.
✓ Branch 7 taken 37 times.
✓ Branch 8 taken 6 times.
✓ Branch 9 taken 14 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 5 times.
✓ Branch 12 taken 3 times.
✓ Branch 13 taken 31 times.
351 switch (state) {
717 28 case CFGCLI_PARSE_START:
718
3/4
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
28 if (isalpha(c) || c == '_' || c == '-') {
719 14 *key = line + i;
720 14 state = CFGCLI_PARSE_KEYWORD;
721 }
722
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
14 else if (c == CFGCLI_SYM_COMMENT) return CFGCLI_PARSE_PASS;
723
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
724 break;
725 92 case CFGCLI_PARSE_KEYWORD:
726
3/4
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 78 times.
92 if (c == CFGCLI_SYM_EQUAL || isspace(c)) {
727 /* check if the keyword is too long */
728
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (line + i - *key >= CFGCLI_MAX_NAME_LEN) return CFGCLI_PARSE_ERROR;
729 14 line[i] = '\0'; /* terminate the keyword */
730 14 state = (c == CFGCLI_SYM_EQUAL) ?
731
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 CFGCLI_PARSE_VALUE_START : CFGCLI_PARSE_EQUAL;
732 }
733
3/4
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
78 else if (!isalnum(c) && c != '_' && c != '-') return CFGCLI_PARSE_ERROR;
734 break;
735 31 case CFGCLI_PARSE_EQUAL:
736
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 17 times.
31 if (c == CFGCLI_SYM_EQUAL) state = CFGCLI_PARSE_VALUE_START;
737
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
738 break;
739 28 case CFGCLI_PARSE_VALUE_START:
740
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 27 times.
28 if (c == '"' || c == '\'') { /* enter quotes */
741 1 quote = c;
742 1 *value = line + i;
743 1 state = CFGCLI_PARSE_QUOTE;
744 }
745
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 21 times.
27 else if (c == CFGCLI_SYM_ARRAY_START) { /* beginning of array */
746 6 *value = line + i;
747 6 state = CFGCLI_PARSE_ARRAY_START;
748 }
749
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 else if (c == CFGCLI_SYM_COMMENT) return CFGCLI_PARSE_PASS; /* no value */
750
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 14 times.
21 else if (isgraph(c)) { /* beginning of value */
751 7 *value = line + i;
752 7 state = CFGCLI_PARSE_VALUE;
753 }
754
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
755 break;
756 43 case CFGCLI_PARSE_ARRAY_START:
757
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 5 times.
43 if (c == CFGCLI_SYM_ARRAY_SEP || c == CFGCLI_SYM_ARRAY_END ||
758 c == CFGCLI_SYM_COMMENT) /* no value is found */
759 return CFGCLI_PARSE_ERROR;
760 else if (c == '"' || c == '\'') { /* enter quotes */
761 quote = c;
762 state = CFGCLI_PARSE_ARRAY_QUOTE;
763 }
764 else if (c == CFGCLI_SYM_NEWLINE) { /* line continuation */
765 newline = line + i;
766 state = CFGCLI_PARSE_ARRAY_NEWLINE;
767 }
768
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 11 times.
37 else if (isgraph(c)) state = CFGCLI_PARSE_ARRAY_VALUE;
769
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
770 break;
771 33 case CFGCLI_PARSE_VALUE:
772
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 31 times.
33 if (c == CFGCLI_SYM_COMMENT) {
773 2 line[i] = '\0'; /* terminate the value */
774 2 return CFGCLI_PARSE_DONE;
775 }
776
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 else if (!isprint(c)) return CFGCLI_PARSE_ERROR;
777 break;
778 37 case CFGCLI_PARSE_ARRAY_VALUE:
779
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 30 times.
37 if (c == CFGCLI_SYM_ARRAY_SEP) /* new array element */
780 state = CFGCLI_PARSE_ARRAY_START;
781
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 4 times.
30 else if (c == CFGCLI_SYM_ARRAY_END) /* end of array */
782 state = CFGCLI_PARSE_ARRAY_END;
783
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
26 else if (c == CFGCLI_SYM_COMMENT || !isprint(c)) return CFGCLI_PARSE_ERROR;
784 break;
785 6 case CFGCLI_PARSE_QUOTE:
786
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (c == quote) state = CFGCLI_PARSE_QUOTE_END;
787 break;
788 14 case CFGCLI_PARSE_ARRAY_QUOTE:
789
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 9 times.
14 if (c == quote) state = CFGCLI_PARSE_ARRAY_QUOTE_END;
790 break;
791 case CFGCLI_PARSE_QUOTE_END:
792 case CFGCLI_PARSE_ARRAY_END:
793 if (c == CFGCLI_SYM_COMMENT) {
794 line[i] = '\0';
795 return CFGCLI_PARSE_DONE;
796 }
797 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
798 break;
799 5 case CFGCLI_PARSE_ARRAY_QUOTE_END:
800
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (c == CFGCLI_SYM_ARRAY_SEP) state = CFGCLI_PARSE_ARRAY_START;
801
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 else if (c == CFGCLI_SYM_ARRAY_END) state = CFGCLI_PARSE_ARRAY_END;
802 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
803 break;
804 3 case CFGCLI_PARSE_ARRAY_NEWLINE: /* line continuation */
805
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (c == CFGCLI_SYM_COMMENT) { /* clear all characters */
806 1 state = CFGCLI_PARSE_CLEAN;
807 1 line[i] = ' ';
808 1 *newline = ' ';
809 }
810
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 else if (!isspace(c)) { /* not really for line continuation */
811 newline = NULL;
812 state = CFGCLI_PARSE_ARRAY_VALUE;
813 i--;
814 }
815 break;
816 31 case CFGCLI_PARSE_CLEAN:
817 31 line[i] = ' ';
818 31 break;
819 default:
820 return CFGCLI_PARSE_ERROR;
821 }
822 }
823
824 /* Check the final status. */
825
2/5
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
13 switch (state) {
826 case CFGCLI_PARSE_VALUE:
827 case CFGCLI_PARSE_QUOTE_END:
828 case CFGCLI_PARSE_ARRAY_END:
829 return CFGCLI_PARSE_DONE;
830 case CFGCLI_PARSE_START:
831 case CFGCLI_PARSE_VALUE_START:
832 return CFGCLI_PARSE_PASS;
833 case CFGCLI_PARSE_ARRAY_NEWLINE:
834 *newline = ' ';
835 #if __STDC_VERSION__ > 201112L
836 [[fallthrough]];
837 #endif
838 case CFGCLI_PARSE_CLEAN:
839 return CFGCLI_PARSE_CONTINUE;
840 default:
841 return CFGCLI_PARSE_ERROR;
842 }
843 }
844
845 /******************************************************************************
846 Function `cfgcli_parse_array`:
847 Split the string defined as array, and count the number of elements.
848 Arguments:
849 * `par`: address of the verified configuration parameter.
850 Return:
851 Zero on success; non-zero on error.
852 ******************************************************************************/
853 7 static int cfgcli_parse_array(cfgcli_param_valid_t *par) {
854 7 par->narr = 0; /* no need to check whether `par` is NULL */
855
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 if (!par->value || !par->vlen) return 0; /* empty string */
856
857 int n = 0;
858 char quote = '\0';
859 cfgcli_parse_state_t state = CFGCLI_PARSE_START;
860 char *start, *end;
861 start = end = NULL;
862
863
2/2
✓ Branch 0 taken 147 times.
✓ Branch 1 taken 6 times.
153 for (size_t i = 0; i < par->vlen; i++) {
864
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 if (state == CFGCLI_PARSE_ARRAY_DONE) break;
865 147 char c = par->value[i]; /* this is surely not '\0' */
866
867
6/7
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 78 times.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
147 switch (state) {
868 7 case CFGCLI_PARSE_START:
869
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 if (c == CFGCLI_SYM_ARRAY_START) {
870 state = CFGCLI_PARSE_ARRAY_START;
871 start = par->value + i; /* mark the array starting point */
872 }
873
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (!isspace(c)) { /* not an array */
874 1 par->narr = 1; /* try to parse as a single variable later */
875 1 return 0;
876 }
877 break;
878 78 case CFGCLI_PARSE_ARRAY_START:
879
2/3
✓ Branch 0 taken 73 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
78 if (c == CFGCLI_SYM_ARRAY_SEP || c == CFGCLI_SYM_ARRAY_END ||
880 c == CFGCLI_SYM_COMMENT) /* no value is found */
881 return CFGCLI_ERR_VALUE;
882 else if (c == '"' || c == '\'') { /* enter quotes */
883 quote = c;
884 state = CFGCLI_PARSE_ARRAY_QUOTE;
885 }
886
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 11 times.
73 else if (isgraph(c)) state = CFGCLI_PARSE_ARRAY_VALUE;
887
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 else if (!isspace(c)) return CFGCLI_ERR_VALUE;
888 break;
889 37 case CFGCLI_PARSE_ARRAY_VALUE:
890
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 30 times.
37 if (c == CFGCLI_SYM_ARRAY_SEP) { /* new array element */
891 7 n++;
892 7 state = CFGCLI_PARSE_ARRAY_START;
893 7 par->value[i] = '\0'; /* add separator for value parser */
894 }
895
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 4 times.
30 else if (c == CFGCLI_SYM_ARRAY_END) { /* end of array */
896 state = CFGCLI_PARSE_ARRAY_END;
897 end = par->value + i; /* mark the array ending point */
898 }
899
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 else if (c == CFGCLI_SYM_COMMENT || !isprint(c)) return CFGCLI_ERR_VALUE;
900 break;
901 14 case CFGCLI_PARSE_ARRAY_QUOTE:
902
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 9 times.
14 if (c == quote) {
903 5 quote = '\0';
904 5 state = CFGCLI_PARSE_ARRAY_QUOTE_END;
905 }
906 break;
907 5 case CFGCLI_PARSE_ARRAY_QUOTE_END:
908
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (c == CFGCLI_SYM_ARRAY_SEP) { /* new array element */
909 3 n++;
910 3 state = CFGCLI_PARSE_ARRAY_START;
911 3 par->value[i] = '\0'; /* add separator for value parser */
912 }
913
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 else if (c == CFGCLI_SYM_ARRAY_END) { /* end of array */
914 state = CFGCLI_PARSE_ARRAY_END;
915 end = par->value + i; /* mark the array ending point */
916 }
917 else if (!isspace(c)) return CFGCLI_ERR_VALUE;
918 break;
919 6 case CFGCLI_PARSE_ARRAY_END:
920
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (c == CFGCLI_SYM_COMMENT) {
921 state = CFGCLI_PARSE_ARRAY_DONE;
922 par->value[i] = '\0'; /* terminate earlier to skip comments */
923 }
924
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 else if (isgraph(c)) return CFGCLI_ERR_VALUE;
925 break;
926 default:
927 return CFGCLI_ERR_VALUE;
928 }
929 }
930
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (start)
931 6 par->value = start + 1; /* omit the starting '[' */
932
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (end)
933 6 *end = '\0'; /* remove the ending ']' */
934 6 par->narr = n + 1;
935 6 return 0;
936 }
937
938 /******************************************************************************
939 Function `cfgcli_get_value`:
940 Retrieve the parameter value and assign it to a variable.
941 Arguments:
942 * `var`: pointer to the variable to be assigned value;
943 * `str`: string storing the parameter value;
944 * `size`: length of `value`, including the ending '\0';
945 * `dtype`: data type;
946 * `src`: source of this value.
947 Return:
948 Zero on success; non-zero on error.
949 ******************************************************************************/
950 24 static int cfgcli_get_value(void *var, char *str, const size_t size,
951 const cfgcli_dtype_t dtype, int src) {
952 24 (void) src;
953
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (!str || !size) return 0;
954 char *value = str;
955 int n;
956
957 /* Validate the value. */
958
3/4
✓ Branch 0 taken 86 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 24 times.
86 while (*value && isspace(*value)) value++; /* omit whitespaces */
959
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (*value == '\0') return CFGCLI_ERR_VALUE; /* empty string */
960
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 18 times.
24 if (*value == '"' || *value == '\'') { /* remove quotes */
961 6 char quote = *value;
962 6 value++;
963
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 for (n = 0; value[n]; n++) { /* the string is surely terminated */
964
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
20 if (value[n] == quote) {
965 6 value[n] = '\0';
966 6 quote = 0; /* a flag indicating the other quote is found */
967 6 break;
968 }
969 }
970 /* empty string with quotes is valid for char or string type variable */
971
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
6 if (*value == '\0' && dtype != CFGCLI_DTYPE_CHAR && dtype != CFGCLI_DTYPE_STR)
972 return CFGCLI_ERR_VALUE;
973
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (quote) return CFGCLI_ERR_VALUE; /* open quotation marks */
974
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 for (++n; value[n]; n++) if (!isspace(value[n])) return CFGCLI_ERR_VALUE;
975 }
976 else { /* remove trailing whitespaces */
977 18 char *val = str + size - 2;
978
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 18 times.
31 while (isspace(*val)) {
979 13 *val = '\0';
980 13 val--;
981 }
982 }
983
984 /* Variable assignment. */
985 24 n = 0;
986
7/8
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
24 switch (dtype) {
987 5 case CFGCLI_DTYPE_BOOL:
988
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
5 if (!strncmp(value, "1", 2) || !strncmp(value, "T", 2) ||
989
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
4 !strncmp(value, "t", 2) || !strncmp(value, "true", 5) ||
990
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 !strncmp(value, "TRUE", 5) || !strncmp(value, "True", 5))
991 2 *((bool *) var) = true;
992
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
3 else if (!strncmp(value, "0", 2) || !strncmp(value, "F", 2) ||
993
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 !strncmp(value, "f", 2) || !strncmp(value, "false", 6) ||
994
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 !strncmp(value, "FALSE", 6) || !strncmp(value, "False", 6))
995 3 *((bool *) var) = false;
996 else return CFGCLI_ERR_PARSE;
997 break;
998 4 case CFGCLI_DTYPE_CHAR:
999 4 *((char *) var) = *value;
1000 4 n = 1;
1001 4 break;
1002 2 case CFGCLI_DTYPE_INT:
1003
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (sscanf(value, "%d%n", (int *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1004 break;
1005 2 case CFGCLI_DTYPE_LONG:
1006
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (sscanf(value, "%ld%n", (long *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1007 break;
1008 5 case CFGCLI_DTYPE_FLT:
1009
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (sscanf(value, "%f%n", (float *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1010 break;
1011 3 case CFGCLI_DTYPE_DBL:
1012
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (sscanf(value, "%lf%n", (double *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1013 break;
1014 3 case CFGCLI_DTYPE_STR:
1015 3 strcpy(*((char **) var), value); /* the usage of strcpy is safe here */
1016 3 break;
1017 default:
1018 return CFGCLI_ERR_DTYPE;
1019 }
1020
1021
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
24 if (n) { /* check remaining characters */
1022 16 value += n;
1023
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 while (*value != '\0') {
1024 if (!isspace(*value)) return CFGCLI_ERR_VALUE;
1025 value++;
1026 }
1027 }
1028
1029 return 0;
1030 }
1031
1032 /******************************************************************************
1033 Function `cfgcli_get_array`:
1034 Retrieve the parameter values and assign them to an array.
1035 Arguments:
1036 * `par`: address of the verified configuration parameter;
1037 * `src`: source of the value.
1038 Return:
1039 Zero on success; non-zero on error.
1040 ******************************************************************************/
1041 7 static int cfgcli_get_array(cfgcli_param_valid_t *par, int src) {
1042 7 size_t len;
1043 7 int i, err;
1044
1045 /* Split the value string for array elements. */
1046
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 if ((err = cfgcli_parse_array(par))) return err;
1047 7 char *value = par->value; /* array elements are separated by '\0' */
1048
1049 /* Allocate memory and assign values for arrays. */
1050
7/8
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
7 switch (par->dtype) {
1051 1 case CFGCLI_ARRAY_BOOL:
1052 1 *((bool **) par->var) = calloc(par->narr, sizeof(bool));
1053
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((bool **) par->var))) return CFGCLI_ERR_MEMORY;
1054 /* call the value assignment function for each segment */
1055
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (i = 0; i < par->narr; i++) {
1056 4 len = strlen(value) + 1; /* strlen is safe here */
1057
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ((err = cfgcli_get_value(*((bool **) par->var) + i, value, len,
1058 CFGCLI_DTYPE_BOOL, src))) return err;
1059 4 value += len;
1060 }
1061 7 break;
1062 1 case CFGCLI_ARRAY_CHAR:
1063 1 *((char **) par->var) = calloc(par->narr, sizeof(char));
1064
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((char **) par->var))) return CFGCLI_ERR_MEMORY;
1065
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 for (i = 0; i < par->narr; i++) {
1066 3 len = strlen(value) + 1;
1067
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((err = cfgcli_get_value(*((char **) par->var) + i, value, len,
1068 CFGCLI_DTYPE_CHAR, src))) return err;
1069 3 value += len;
1070 }
1071 break;
1072 1 case CFGCLI_ARRAY_INT:
1073 1 *((int **) par->var) = calloc(par->narr, sizeof(int));
1074
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((int **) par->var))) return CFGCLI_ERR_MEMORY;
1075
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (i = 0; i < par->narr; i++) {
1076 1 len = strlen(value) + 1;
1077
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((err = cfgcli_get_value(*((int **) par->var) + i, value, len,
1078 CFGCLI_DTYPE_INT, src))) return err;
1079 1 value += len;
1080 }
1081 break;
1082 1 case CFGCLI_ARRAY_LONG:
1083 1 *((long **) par->var) = calloc(par->narr, sizeof(long));
1084
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((long **) par->var))) return CFGCLI_ERR_MEMORY;
1085
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (i = 0; i < par->narr; i++) {
1086 1 len = strlen(value) + 1;
1087
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((err = cfgcli_get_value(*((long **) par->var) + i, value, len,
1088 CFGCLI_DTYPE_LONG, src))) return err;
1089 1 value += len;
1090 }
1091 break;
1092 1 case CFGCLI_ARRAY_FLT:
1093 1 *((float **) par->var) = calloc(par->narr, sizeof(float));
1094
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((float **) par->var))) return CFGCLI_ERR_MEMORY;
1095
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (i = 0; i < par->narr; i++) {
1096 4 len = strlen(value) + 1;
1097
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ((err = cfgcli_get_value(*((float **) par->var) + i, value, len,
1098 CFGCLI_DTYPE_FLT, src))) return err;
1099 4 value += len;
1100 }
1101 break;
1102 1 case CFGCLI_ARRAY_DBL:
1103 1 *((double **) par->var) = calloc(par->narr, sizeof(double));
1104
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((double **) par->var))) return CFGCLI_ERR_MEMORY;
1105
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (i = 0; i < par->narr; i++) {
1106 2 len = strlen(value) + 1;
1107
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((err = cfgcli_get_value(*((double **) par->var) + i, value, len,
1108 CFGCLI_DTYPE_DBL, src))) return err;
1109 2 value += len;
1110 }
1111 break;
1112 1 case CFGCLI_ARRAY_STR:
1113 1 *((char ***) par->var) = calloc(par->narr, sizeof(char *));
1114
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((char ***) par->var))) return CFGCLI_ERR_MEMORY;
1115 /* Allocate enough memory for the first element of the string array. */
1116 1 *(*((char ***) par->var)) = calloc(par->vlen, sizeof(char));
1117 1 char *tmp = *(*((char ***) par->var));
1118
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!tmp) return CFGCLI_ERR_MEMORY;
1119 /* The rest elements point to different positions of the space. */
1120
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (i = 0; i < par->narr; i++) {
1121 2 (*((char ***) par->var))[i] = tmp;
1122 2 len = strlen(value) + 1;
1123
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((err = cfgcli_get_value(&tmp, value, len, CFGCLI_DTYPE_STR, src)))
1124 return err;
1125 2 tmp += strlen(tmp) + 1; /* null termination ensured by calloc */
1126 2 value += len;
1127 }
1128 break;
1129 default:
1130 return CFGCLI_ERR_DTYPE; /* is CFGCLI_DTYPE_IS_ARRAY correct? */
1131 }
1132 7 return 0;
1133 }
1134
1135 /******************************************************************************
1136 Function `cfgcli_get`:
1137 Retrieve the parameter value and assign it to a variable.
1138 Arguments:
1139 * `cfg`: entry for all configurations;
1140 * `par`: address of the verified configuration parameter;
1141 * `src`: source of the value;
1142 Return:
1143 Zero on success; non-zero on error.
1144 ******************************************************************************/
1145 14 static int cfgcli_get(cfgcli_t *cfg, cfgcli_param_valid_t *par, int src) {
1146 14 int err = 0;
1147 /* Validate function arguments. */
1148
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
1149
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 if (!par->value || *par->value == '\0') return 0; /* value not set */
1150
1151 /* Deal with arrays and scalars separately. */
1152
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 if (CFGCLI_DTYPE_IS_ARRAY(par->dtype)) /* force preprocessing the value */
1153 7 err = cfgcli_get_array(par, src);
1154 else {
1155 /* Allocate memory only for string. */
1156
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 if (par->dtype == CFGCLI_DTYPE_STR) {
1157 1 *((char **) par->var) = calloc(par->vlen, sizeof(char));
1158
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!(*((char **) par->var))) err = CFGCLI_ERR_MEMORY;
1159 }
1160
1161 /* Assign values to the variable. */
1162 if (!err)
1163 7 err = cfgcli_get_value(par->var, par->value, par->vlen, par->dtype, src);
1164 }
1165
1166
1/6
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
14 switch (err) {
1167 case 0:
1168 return 0;
1169 case CFGCLI_ERR_MEMORY:
1170 cfgcli_msg(cfg, "failed to allocate memory for parameter", par->name);
1171 return CFGCLI_ERRNO(cfg) = err;
1172 case CFGCLI_ERR_VALUE:
1173 cfgcli_msg(cfg, "invalid value for parameter", par->name);
1174 return CFGCLI_ERRNO(cfg) = err;
1175 case CFGCLI_ERR_PARSE:
1176 cfgcli_msg(cfg, "failed to parse the value for parameter", par->name);
1177 return CFGCLI_ERRNO(cfg) = err;
1178 case CFGCLI_ERR_DTYPE:
1179 cfgcli_msg(cfg, "invalid data type for parameter", par->name);
1180 return CFGCLI_ERRNO(cfg) = err;
1181 default:
1182 cfgcli_msg(cfg, "unknown error occurred for parameter", par->name);
1183 return CFGCLI_ERRNO(cfg) = err;
1184 }
1185 }
1186
1187
1188 /*============================================================================*\
1189 High-level functions for reading configurations
1190 from command line options and text files
1191 \*============================================================================*/
1192
1193 /******************************************************************************
1194 Function `cfgcli_read_opts`:
1195 Parse command line options.
1196 Arguments:
1197 * `cfg`: entry for the configurations;
1198 * `argc`: number of arguments passed via command line;
1199 * `argv`: array of command line arguments;
1200 * `prior`: priority of values set via command line options;
1201 * `optidx`: position of the first unparsed argument.
1202 Return:
1203 Zero on success; non-zero on error.
1204 ******************************************************************************/
1205 1 int cfgcli_read_opts(cfgcli_t *cfg, const int argc, char *const *argv,
1206 const int prior, int *optidx) {
1207 /* Validate function arguments. */
1208
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!cfg) return CFGCLI_ERR_INIT;
1209
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
1210
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (cfg->npar <= 0 && cfg->nfunc <= 0) {
1211 cfgcli_msg(cfg, "no parameter or function has been registered", NULL);
1212 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INIT;
1213 }
1214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (prior <= CFGCLI_SRC_NULL) {
1215 cfgcli_msg(cfg, "invalid priority for command line options", NULL);
1216 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1217 }
1218
1219 1 *optidx = 0;
1220
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (argc <= 0 || !argv || !(*argv)) return 0;
1221 int i;
1222
1223 /* Start parsing command line options. */
1224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 for (i = 1; i < argc; i++) {
1225 char *arg = argv[i];
1226 if (!(CFGCLI_IS_OPT(arg))) { /* unrecognised option */
1227 cfgcli_msg(cfg, "unrecognised command line option", arg);
1228 continue;
1229 }
1230
1231 int j;
1232 char *optarg = NULL;
1233
1234 for (j = 0; j < CFGCLI_MAX_LOPT_LEN + 2; j++) /* check if '=' exists */
1235 if (arg[j] == '\0' || arg[j] == CFGCLI_CMD_ASSIGN) break;
1236 if (arg[j] == '\0') { /* '=' is not found */
1237 j = i + 1;
1238 if (j < argc && !(CFGCLI_IS_OPT(argv[j]))) optarg = argv[++i];
1239 }
1240 else if (arg[j] == CFGCLI_CMD_ASSIGN) { /* '=' is found */
1241 arg[j] = '\0';
1242 optarg = &arg[j + 1];
1243 }
1244 else {
1245 cfgcli_msg(cfg, "the command line option is too long", arg);
1246 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_CMD;
1247 }
1248
1249 cfgcli_param_valid_t *params = (cfgcli_param_valid_t *) cfg->params;
1250 cfgcli_func_valid_t *funcs = (cfgcli_func_valid_t *) cfg->funcs;
1251 enum { not_found, is_param, is_func } status;
1252 status = not_found;
1253
1254 if (arg[1] != CFGCLI_CMD_FLAG) { /* short option */
1255 for (j = 0; j < cfg->npar; j++) {
1256 if (arg[1] == params[j].opt) {
1257 status = is_param;
1258 break;
1259 }
1260 }
1261 if (status != is_param) {
1262 for (j = 0; j < cfg->nfunc; j++) {
1263 if (arg[1] == funcs[j].opt) {
1264 status = is_func;
1265 break;
1266 }
1267 }
1268 }
1269 }
1270 else if (arg[2] != '\0') { /* long option */
1271 for (j = 0; j < cfg->npar; j++) {
1272 if (params[j].lopt &&
1273 !strncmp(params[j].lopt, arg + 2, params[j].llen)) {
1274 status = is_param;
1275 break;
1276 }
1277 }
1278 if (status != is_param) {
1279 for (j = 0; j < cfg->nfunc; j++) {
1280 if (funcs[j].lopt &&
1281 !strncmp(funcs[j].lopt, arg + 2, funcs[j].llen)) {
1282 status = is_func;
1283 break;
1284 }
1285 }
1286 }
1287 }
1288 else { /* parser termination */
1289 *optidx = j; /* for arg = "--", j = i + 1 */
1290 break;
1291 }
1292
1293 if (status == is_func) { /* call the command line function */
1294 if (optarg) cfgcli_msg(cfg, "omitting command line argument", optarg);
1295 if (funcs[j].called)
1296 cfgcli_msg(cfg, "the function has already been called with option", arg);
1297 else {
1298 funcs[j].func(funcs[j].args); /* call the function */
1299 funcs[j].called = 1;
1300 }
1301 }
1302 else if (status == is_param) { /* assign parameter value */
1303 /* Priority check. */
1304 if (CFGCLI_SRC_VAL(params[j].src) > prior) continue;
1305 else if (CFGCLI_SRC_VAL(params[j].src) == prior) {
1306 cfgcli_msg(cfg, "omitting duplicate entry of parameter", params[j].name);
1307 continue;
1308 }
1309 /* Command line arguments can be omitted for bool type variables. */
1310 if (!optarg || *optarg == '\0') {
1311 if (params[j].dtype == CFGCLI_DTYPE_BOOL) {
1312 params[j].value = "T";
1313 params[j].vlen = 2;
1314 }
1315 else {
1316 cfgcli_msg(cfg, "argument not found for option", arg);
1317 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_CMD;
1318 }
1319 }
1320 else {
1321 params[j].value = optarg; /* args are surely null terminated */
1322 params[j].vlen = strlen(optarg) + 1; /* safe strlen */
1323 }
1324 /* Assign value to variable. */
1325 int err = cfgcli_get(cfg, params + j, CFGCLI_SRC_OF_OPT(prior));
1326 if (err) return err;
1327 params[j].src = CFGCLI_SRC_OF_OPT(prior);
1328 }
1329 else /* option not registered */
1330 cfgcli_msg(cfg, "unrecognised command line option", arg);
1331 }
1332
1333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (*optidx == 0) *optidx = i;
1334 return 0;
1335 }
1336
1337 /******************************************************************************
1338 Function `cfgcli_read_file`:
1339 Read configuration parameters from a file.
1340 Arguments:
1341 * `cfg`: entry for the configurations;
1342 * `fname`: name of the input file;
1343 * `prior`: priority of values read from this file.
1344 Return:
1345 Zero on success; non-zero on error.
1346 ******************************************************************************/
1347 1 int cfgcli_read_file(cfgcli_t *cfg, const char *fname, const int prior) {
1348 /* Validate function arguments. */
1349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cfg) return CFGCLI_ERR_INIT;
1350
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
1351
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (cfg->npar <= 0) {
1352 cfgcli_msg(cfg, "no parameter has been registered", NULL);
1353 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INIT;
1354 }
1355
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!fname || *fname == '\0') {
1356 cfgcli_msg(cfg, "the input configuration file is not set", NULL);
1357 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1358 }
1359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(cfgcli_strnlen(fname, CFGCLI_MAX_FILENAME_LEN))) {
1360 cfgcli_msg(cfg, "invalid filename of the configuration file", NULL);
1361 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1362 }
1363
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (prior <= CFGCLI_SRC_NULL) {
1364 cfgcli_msg(cfg, "invalid priority for configuration file", fname);
1365 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1366 }
1367
1368 1 FILE *fp = fopen(fname, "r");
1369
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!fp) {
1370 cfgcli_msg(cfg, "cannot open the configuration file", fname);
1371 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_FILE;
1372 }
1373
1374 /* Read file by chunk. */
1375 1 size_t clen = CFGCLI_STR_INIT_SIZE;
1376 1 char *chunk = calloc(clen, sizeof(char));
1377
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!chunk) {
1378 fclose(fp);
1379 cfgcli_msg(cfg, "failed to allocate memory for reading file", fname);
1380 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1381 }
1382
1383 1 size_t nline, nrest, nproc, cnt;
1384 1 char *key, *value;
1385 1 cfgcli_parse_state_t state = CFGCLI_PARSE_START;
1386 1 nline = nrest = nproc = 0;
1387 1 key = value = NULL;
1388 1 cfgcli_param_valid_t *params = (cfgcli_param_valid_t *) cfg->params;
1389
1390
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 while ((cnt = fread(chunk + nrest, sizeof(char), clen - nrest, fp))) {
1391
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 char *p = (state == CFGCLI_PARSE_ARRAY_START) ? chunk + nproc : chunk;
1392 1 char *end = chunk + nrest + cnt;
1393 1 char *endl;
1394
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (cnt < clen - nrest) *end++ = '\n'; /* terminate the last line */
1395
1396 /* Process lines in the chunk. */
1397
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 1 times.
23 while ((endl = memchr(p, '\n', end - p))) {
1398 22 *endl = '\0'; /* replace '\n' by '\0' for line parser */
1399 22 nline += 1;
1400
1401 /* Retrieve the keyword and value from the line. */
1402 22 char msg[CFGCLI_NUM_MAX_SIZE(size_t)];
1403 22 int j;
1404 22 cfgcli_parse_return_t status =
1405 22 cfgcli_parse_line(p, endl - p, &key, &value, state);
1406
1407
3/5
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
22 switch (status) {
1408 case CFGCLI_PARSE_DONE:
1409 /* search for the parameter given the name */
1410
1/2
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
119 for (j = 0; j < cfg->npar; j++)
1411
2/2
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 14 times.
119 if (!strncmp(key, params[j].name, params[j].nlen)) break;
1412
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (j == cfg->npar) /* parameter not found */
1413 cfgcli_msg(cfg, "unregistered parameter name", key);
1414 else {
1415 /* priority check */
1416
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (CFGCLI_SRC_VAL(params[j].src) < prior) {
1417 14 params[j].value = value;
1418 14 params[j].vlen = strlen(value) + 1;
1419 14 int err = cfgcli_get(cfg, params + j, prior);
1420
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (err) {
1421 free(chunk);
1422 fclose(fp);
1423 return err;
1424 }
1425 14 params[j].src = prior;
1426 }
1427 else if (CFGCLI_SRC_VAL(params[j].src) == prior)
1428 cfgcli_msg(cfg, "omitting duplicate entry of parameter", key);
1429 }
1430 /* reset states */
1431 14 key = value = NULL;
1432 14 state = CFGCLI_PARSE_START;
1433 14 break;
1434 1 case CFGCLI_PARSE_CONTINUE: /* line continuation */
1435 1 *endl = ' '; /* remove line break */
1436 1 state = CFGCLI_PARSE_ARRAY_START;
1437 1 break;
1438 case CFGCLI_PARSE_ERROR:
1439 sprintf(msg, "%zu", nline);
1440 cfgcli_msg(cfg, "invalid configuration entry at line", msg);
1441 #if __STDC_VERSION__ > 201112L
1442 [[fallthrough]];
1443 #endif
1444 case CFGCLI_PARSE_PASS:
1445 state = CFGCLI_PARSE_START;
1446 break;
1447 default:
1448 free(chunk);
1449 fclose(fp);
1450 sprintf(msg, "%d", status);
1451 cfgcli_msg(cfg, "unknown line parser status", msg);
1452 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_UNKNOWN;
1453 }
1454 22 p = endl + 1;
1455 }
1456
1457 /* The chunk cannot hold a full line. */
1458
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (p == chunk) {
1459 size_t new_len = 0;
1460 if (clen >= CFGCLI_STR_MAX_DOUBLE_SIZE) {
1461 if (SIZE_MAX - CFGCLI_STR_MAX_DOUBLE_SIZE >= clen)
1462 new_len = clen + CFGCLI_STR_MAX_DOUBLE_SIZE;
1463 }
1464 else if (SIZE_MAX / 2 >= clen) new_len = clen << 1;
1465 if (!new_len) { /* overflow occurred */
1466 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1467 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1468 }
1469 char *tmp = realloc(chunk, new_len);
1470 if (!tmp) {
1471 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1472 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1473 }
1474 chunk = tmp;
1475 clen = new_len;
1476 nrest += cnt;
1477 continue;
1478 }
1479
1480 /* Copy the remaining characters to the beginning of the chunk. */
1481
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (state == CFGCLI_PARSE_ARRAY_START) { /* copy also parsed part */
1482 if (!key) {
1483 free(chunk);
1484 fclose(fp);
1485 cfgcli_msg(cfg, "unknown parser interruption", NULL);
1486 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_UNKNOWN;
1487 }
1488 /* `key` is the starting point of this effective line */
1489 nrest = end - key;
1490 nproc = p - key;
1491 memmove(chunk, key, nrest);
1492 /* shift `key` and `value` */
1493 if (value) value -= key - chunk;
1494 key = chunk;
1495 }
1496 else { /* copy only from the current position */
1497 1 nrest = end - p;
1498 1 memmove(chunk, p, nrest);
1499 }
1500
1501 /* The chunk is full. */
1502
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (nrest == clen) {
1503 size_t new_len = 0;
1504 if (clen >= CFGCLI_STR_MAX_DOUBLE_SIZE) {
1505 if (SIZE_MAX - CFGCLI_STR_MAX_DOUBLE_SIZE >= clen)
1506 new_len = clen + CFGCLI_STR_MAX_DOUBLE_SIZE;
1507 }
1508 else if (SIZE_MAX / 2 >= clen) new_len = clen << 1;
1509 if (!new_len) {
1510 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1511 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1512 }
1513 size_t key_shift = key ? key - chunk : 0;
1514 size_t value_shift = value ? value - chunk : 0;
1515 char *tmp = realloc(chunk, new_len);
1516 if (!tmp) {
1517 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1518 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1519 }
1520 chunk = tmp;
1521 clen = new_len;
1522 if (key) key = chunk + key_shift;
1523 if (value) value = chunk + value_shift;
1524 }
1525 }
1526
1527
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!feof(fp)) {
1528 cfgcli_msg(cfg, "unexpected end of file", fname);
1529 free(chunk);
1530 fclose(fp);
1531 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_FILE;
1532 }
1533
1534 1 free(chunk);
1535 1 fclose(fp);
1536 1 return 0;
1537 }
1538
1539
1540 /*============================================================================*\
1541 Functions for checking the status of variables
1542 \*============================================================================*/
1543
1544 /******************************************************************************
1545 Function `cfgcli_is_set`:
1546 Check if a variable is set via the command line or files.
1547 Arguments:
1548 * `cfg`: entry of all configurations;
1549 * `var`: address of the variable.
1550 Return:
1551 True if the variable is set; false otherwise.
1552 ******************************************************************************/
1553 9 bool cfgcli_is_set(const cfgcli_t *cfg, const void *var) {
1554
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 if (!cfg || !var || !cfg->npar) return false;
1555
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 for (int i = 0; i < cfg->npar; i++) {
1556 37 cfgcli_param_valid_t *par = (cfgcli_param_valid_t *) cfg->params + i;
1557
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 28 times.
37 if (par->var == var) {
1558
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
9 if (par->src != CFGCLI_SRC_NULL) return true;
1559 break;
1560 }
1561 }
1562 return false;
1563 }
1564
1565 /******************************************************************************
1566 Function `cfgcli_get_size`:
1567 Return the number of elements for the parsed array.
1568 Arguments:
1569 * `cfg`: entry of all configurations;
1570 * `var`: address of the variable.
1571 Return:
1572 The number of array elements on success; 0 on error.
1573 ******************************************************************************/
1574 7 int cfgcli_get_size(const cfgcli_t *cfg, const void *var) {
1575
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 if (!cfg || !var || !cfg->npar) return 0;
1576
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 for (int i = 0; i < cfg->npar; i++) {
1577 84 cfgcli_param_valid_t *par = (cfgcli_param_valid_t *) cfg->params + i;
1578
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 77 times.
84 if (par->var == var) {
1579
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (par->src != CFGCLI_SRC_NULL) return par->narr;
1580 break;
1581 }
1582 }
1583 return 0;
1584 }
1585
1586
1587 /*============================================================================*\
1588 Functions for clean-up and error message handling
1589 \*============================================================================*/
1590
1591 /******************************************************************************
1592 Function `cfgcli_destroy`:
1593 Release memory allocated for the configuration parameters.
1594 Arguments:
1595 * `cfg`: pointer to the entry of all configurations.
1596 ******************************************************************************/
1597 1 void cfgcli_destroy(cfgcli_t *cfg) {
1598
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!cfg) return;
1599
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (cfg->npar) free(cfg->params);
1600
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (cfg->nfunc) free(cfg->funcs);
1601 1 cfgcli_error_t *err = cfg->error;
1602
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (err->max) free(err->msg);
1603 1 free(cfg->error);
1604 1 free(cfg);
1605 }
1606
1607 /******************************************************************************
1608 Function `cfgcli_perror`:
1609 Print the error message if there is an error.
1610 Arguments:
1611 * `cfg`: entry of all configurations;
1612 * `fp`: output file stream to write to;
1613 * `msg`: string to be printed before the error message.
1614 ******************************************************************************/
1615 void cfgcli_perror(const cfgcli_t *cfg, FILE *fp, const char *msg) {
1616 if (!cfg || !(CFGCLI_IS_ERROR(cfg))) return;
1617 const cfgcli_error_t *err = (cfgcli_error_t *) cfg->error;
1618 if (err->num <= 0 || !err->msg) return;
1619 const char *sep, *errmsg = err->msg;
1620 for (int i = 0; i < err->num - 1; i++) errmsg += strlen(errmsg) + 1;
1621
1622 if (!msg || *msg == '\0') msg = sep = "";
1623 else sep = " ";
1624 fprintf(fp, "%s%s%s.\n", msg, sep, errmsg);
1625 }
1626
1627 /******************************************************************************
1628 Function `cfgcli_pwarn`:
1629 Print the warning messages if there is any, and clean the warnings.
1630 Arguments:
1631 * `cfg`: entry of all configurations;
1632 * `fp`: output file stream to write to;
1633 * `msg`: string to be printed before the error message.
1634 ******************************************************************************/
1635 4 void cfgcli_pwarn(cfgcli_t *cfg, FILE *fp, const char *msg) {
1636 4 const char *sep;
1637
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (!msg || *msg == '\0') msg = sep = "";
1638 4 else sep = " ";
1639
1640
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!cfg) {
1641 fprintf(fp, "%s%sthe interface for configurations is not initialised.\n",
1642 msg, sep);
1643 return;
1644 }
1645 4 cfgcli_error_t *err = (cfgcli_error_t *) cfg->error;
1646
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 const int num = (CFGCLI_IS_ERROR(cfg)) ? err->num - 1 : err->num;
1647
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (num <= 0 || !err->msg) return;
1648
1649 char *errmsg = err->msg;
1650 for (int i = 0; i < num; i++) {
1651 fprintf(fp, "%s%s%s.\n", msg, sep, errmsg);
1652 errmsg += strlen(errmsg) + 1;
1653 }
1654
1655 /* Clean the warnings. */
1656 err->num -= num;
1657 err->len -= errmsg - err->msg;
1658 memmove(err->msg, errmsg, err->len);
1659 }
1660