GCC Code Coverage Report


Directory: src/
File: src/libcfgcli.c
Date: 2023-04-16 22:42:50
Exec Total Coverage
Lines: 530 895 59.2%
Functions: 15 22 68.2%
Branches: 390 819 47.6%

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 1 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 3 cfgcli_t *cfgcli_init(void) {
241 3 cfgcli_t *cfg = calloc(1, sizeof(cfgcli_t));
242
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!cfg) return NULL;
243
244 3 cfgcli_error_t *err = calloc(1, sizeof(cfgcli_error_t));
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!err) {
246 free(cfg);
247 return NULL;
248 }
249 3 err->msg = NULL;
250
251 3 cfg->params = cfg->funcs = NULL;
252 3 cfg->error = err;
253 3 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 unsigned int cfgcli_print_help_line(char *buffer, const cfgcli_help_line_t *help_line) {
266 unsigned int 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 unsigned int 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 unsigned int 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 uint8_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 }
357 else {
358 cfgcli_msg(cfg, "the function list is not set", NULL);
359 }
360 }
361 }
362
363 /******************************************************************************
364 Function `cfgcli_print_usage`:
365 Print usage messages based on validated parameters and provided progname
366 Arguments:
367 * `cfg`: entry for all configuration parameters;
368 * `progname`: program name to be displayed
369 ******************************************************************************/
370 void cfgcli_print_usage(cfgcli_t *cfg, char *progname) {
371 if (cfg && !CFGCLI_IS_ERROR(cfg)) {
372 char options[] = " [OPTIONS]";
373 char functions[] = " [FUNCTIONS]";
374 const char local_progname[] = "program";
375 if (!progname || *progname == '\0') {
376 progname = (char *)local_progname;
377 }
378 if (cfg->npar == 1) {
379 options[8] = ']'; options[9] = '\0'; } // Singularize
380 else if (cfg->npar == 0)
381 *options = '\0'; // Remove options
382 if (cfg->nfunc == 1) {
383 functions[10] = ']';
384 functions[11] = '\0';
385 } // Singularize
386 else if (cfg->nfunc == 0)
387 *functions = '\0'; // Remove functions
388 printf("Usage: %s%s%s\n", progname, options, functions);
389 }
390 }
391
392 /******************************************************************************
393 Function `cfgcli_set_params`:
394 Verify and register configuration parameters.
395 Arguments:
396 * `cfg`: entry for all configuration parameters;
397 * `param`: stucture for the input configuration parameters;
398 * `npar`: number of input configuration parameters.
399 Return:
400 Zero on success; non-zero on error.
401 ******************************************************************************/
402 2 int cfgcli_set_params(cfgcli_t *cfg, const cfgcli_param_t *param, const int npar) {
403 /* Validate arguments. */
404
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!cfg) return CFGCLI_ERR_INIT;
405
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
406
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!param || npar <= 0) {
407 cfgcli_msg(cfg, "the parameter list is not set", NULL);
408 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
409 }
410
411 /* Allocate memory for parameters. */
412 2 cfgcli_param_valid_t *vpar = realloc(cfg->params,
413 2 (npar + cfg->npar) * sizeof *vpar);
414
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!vpar) {
415 cfgcli_msg(cfg, "failed to allocate memory for parameters", NULL);
416 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
417 }
418 2 memset(vpar + cfg->npar, 0, npar * sizeof *vpar);
419 2 cfg->params = vpar;
420
421 /* Register parameters. */
422
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
18 for (int i = 0; i < npar; i++) {
423 /* Reset the parameter holder. */
424 16 cfgcli_param_valid_t *par = (cfgcli_param_valid_t *) cfg->params + cfg->npar + i;
425 16 par->dtype = CFGCLI_DTYPE_NULL;
426 16 par->src = CFGCLI_SRC_NULL;
427 16 par->help = par->name = par->lopt = par->value = NULL;
428 16 par->var = NULL;
429
430 /* Create the string for the current index and short option. */
431 char tmp[CFGCLI_NUM_MAX_SIZE(int)];
432 16 sprintf(tmp, "%d", i);
433
434 /* Verify the name. */
435 16 char *str = param[i].name;
436
2/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
16 if (!str || (!isalpha(*str) && *str != '_' && *str != '-')) {
437 cfgcli_msg(cfg, "invalid parameter name in the list with index", tmp);
438 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
439 }
440 16 int j = 1;
441
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 16 times.
115 while (str[j] != '\0') {
442
5/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 89 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
99 if (!isalnum(str[j]) && str[j] != '_' && str[j] != '-') {
443 cfgcli_msg(cfg, "invalid parameter name in the list with index", tmp);
444 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
445 }
446
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if (++j >= CFGCLI_MAX_NAME_LEN) { /* no null termination */
447 cfgcli_msg(cfg, "invalid parameter name in the list with index", tmp);
448 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
449 }
450 }
451 16 par->name = str;
452 16 par->nlen = j + 1; /* length of name with the ending '\0' */
453
454 /* Verify the data type. */
455
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
16 if (CFGCLI_DTYPE_INVALID(param[i].dtype)) {
456 cfgcli_msg(cfg, "invalid data type for parameter", par->name);
457 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
458 }
459 16 par->dtype = param[i].dtype;
460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (!param[i].var) {
461 cfgcli_msg(cfg, "variable unset for parameter", par->name);
462 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
463 }
464 16 par->var = param[i].var;
465
466 /* Verify command line options. */
467 16 char opt[3] = { 0 };
468
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (isalpha(param[i].opt)) {
469 16 par->opt = param[i].opt;
470 16 opt[0] = '-';
471 16 opt[1] = par->opt;
472 }
473 else if (param[i].opt) {
474 cfgcli_msg(cfg, "invalid short command line option for parameter",
475 par->name);
476 }
477
478 16 str = param[i].lopt;
479
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 if (str && str[j = 0] != '\0') {
480 do {
481
2/4
✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 89 times.
89 if (!isgraph(str[j]) || str[j] == CFGCLI_CMD_ASSIGN) {
482 cfgcli_msg(cfg, "invalid long command line option for parameter",
483 par->name);
484 break;
485 }
486
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
89 if (++j >= CFGCLI_MAX_LOPT_LEN) { /* no null termination */
487 cfgcli_msg(cfg, "invalid long command line option for parameter",
488 par->name);
489 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
490 }
491 }
492
2/2
✓ Branch 0 taken 73 times.
✓ Branch 1 taken 16 times.
89 while (str[j] != '\0');
493
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (str[j] == '\0') {
494 16 par->lopt = str;
495 16 par->llen = j + 1; /* length of long option with the ending '\0' */
496 }
497 }
498
499 16 tmp[0] = par->opt;
500 16 tmp[1] = '\0';
501
502 /* Verify the help message. */
503 16 str = param[i].help;
504 16 j = 0;
505
2/2
✓ Branch 0 taken 403 times.
✓ Branch 1 taken 16 times.
419 while (str[j] != '\0') {
506
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 403 times.
403 if (++j >= CFGCLI_MAX_HELP_LEN) { /* no null termination */
507 cfgcli_msg(cfg, "invalid help (too long) for parameter", par->lopt ? par->lopt : opt);
508 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
509 }
510 }
511 16 par->help = str;
512 16 par->hlen = j + 1; /* length of help with the ending '\0' */
513
514 /* Check duplicates with the registered parameters. */
515
2/2
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 16 times.
121 for (j = 0; j < cfg->npar + i; j++) {
516
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (!strncmp(par->name, vpar[j].name, par->nlen)) {
517 cfgcli_msg(cfg, "duplicate parameter name", par->name);
518 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
519 }
520
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) {
521 cfgcli_msg(cfg, "duplicate short command line option", tmp);
522 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
523 }
524
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 &&
525
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 !strncmp(par->lopt, vpar[j].lopt, par->llen)) {
526 cfgcli_msg(cfg, "duplicate long command line option", par->lopt);
527 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
528 }
529 }
530
531 /* Check duplicates with the registered functions. */
532 16 cfgcli_func_valid_t *cfunc = (cfgcli_func_valid_t *) cfg->funcs;
533
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 for (j = 0; j < cfg->nfunc; j++) {
534 if (par->opt && par->opt == cfunc[j].opt) {
535 cfgcli_msg(cfg, "duplicate short command line option", tmp);
536 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
537 }
538 if (par->lopt && cfunc[j].lopt &&
539 !strncmp(par->lopt, cfunc[j].lopt, par->llen)) {
540 cfgcli_msg(cfg, "duplicate long command line option", par->lopt);
541 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
542 }
543 }
544 }
545
546 2 cfg->npar += npar;
547 2 return 0;
548 }
549
550 /******************************************************************************
551 Function `cfgcli_set_funcs`:
552 Verify and register command line functions.
553 Arguments:
554 * `cfg`: entry for all command line functions;
555 * `func`: stucture for the input functions;
556 * `nfunc`: number of input functions.
557 Return:
558 Zero on success; non-zero on error.
559 ******************************************************************************/
560 1 int cfgcli_set_funcs(cfgcli_t *cfg, const cfgcli_func_t *func, const int nfunc) {
561 /* Validate arguments. */
562
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cfg) return CFGCLI_ERR_INIT;
563
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
564
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!func || nfunc <= 0) {
565 cfgcli_msg(cfg, "the function list is not set", NULL);
566 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
567 }
568
569 /* Allocate memory for command line functions. */
570 1 cfgcli_func_valid_t *vfunc = realloc(cfg->funcs,
571 1 (nfunc + cfg->nfunc) * sizeof *vfunc);
572
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!vfunc) {
573 cfgcli_msg(cfg, "failed to allocate memory for functions", NULL);
574 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
575 }
576 1 memset(vfunc + cfg->nfunc, 0, nfunc * sizeof *vfunc);
577 1 cfg->funcs = vfunc;
578
579 /* Register command line functions. */
580
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (int i = 0; i < nfunc; i++) {
581 /* Reset the command line function holder. */
582 2 cfgcli_func_valid_t *fun = (cfgcli_func_valid_t *) cfg->funcs + cfg->nfunc + i;
583 2 fun->lopt = NULL;
584 2 fun->func = NULL;
585 2 fun->args = NULL;
586 2 fun->help = NULL;
587
588 /* Create the string for the current index and short option. */
589 char tmp[CFGCLI_NUM_MAX_SIZE(int)];
590 2 sprintf(tmp, "%d", i);
591
592 /* Verify the command line options. */
593 2 char opt[3] = { 0 };
594
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (isalpha(func[i].opt)) {
595 1 fun->opt = func[i].opt;
596 1 opt[0] = '-';
597 1 opt[1] = (char)fun->opt;
598 }
599
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 else if (func[i].opt)
600 cfgcli_msg(cfg, "invalid short command line option for function index", tmp);
601
602 2 char *str = func[i].lopt;
603 2 int j = 0;
604
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') {
605 do {
606
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) {
607 cfgcli_msg(cfg, "invalid long command line option for function index",
608 tmp);
609 break;
610 }
611
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (++j >= CFGCLI_MAX_LOPT_LEN) { /* no null termination */
612 cfgcli_msg(cfg, "invalid long command line option for function index",
613 tmp);
614 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
615 }
616 }
617
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
11 while (str[j] != '\0');
618
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (str[j] == '\0') {
619 2 fun->lopt = str;
620 2 fun->llen = j + 1; /* length of long option with the ending '\0' */
621 }
622 }
623
624
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) {
625 cfgcli_msg(cfg, "no valid command line option for function index", tmp);
626 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
627 }
628
629 /* Verify the function pointer. */
630
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!(fun->func = func[i].func)) {
631 cfgcli_msg(cfg, "function not set with index", tmp);
632 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
633 }
634 2 fun->args = func[i].args;
635
636 /* Verify the help message. */
637
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (func[i].help) {
638 1 str = func[i].help;
639 1 j = 0;
640
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 1 times.
29 while (str[j] != '\0') {
641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if (++j >= CFGCLI_MAX_HELP_LEN) { /* no null termination */
642 cfgcli_msg(cfg, "invalid help (too long) for function", fun->lopt ? fun->lopt : opt);
643 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
644 }
645 }
646 1 fun->help = str;
647 1 fun->hlen = j + 1; /* length of help with the ending '\0' */
648 }
649
650 /* Check duplicates with the registered functions. */
651
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 for (j = 0; j < cfg->nfunc + i; j++) {
652 /* Function and arguments cannot both be identical. */
653
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) {
654 cfgcli_msg(cfg, "duplicate function with index", tmp);
655 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
656 }
657
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) {
658 tmp[0] = fun->opt;
659 tmp[1] = '\0';
660 cfgcli_msg(cfg, "duplicate short command line option", tmp);
661 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
662 }
663
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 &&
664
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 !strncmp(fun->lopt, vfunc[j].lopt, fun->llen)) {
665 cfgcli_msg(cfg, "duplicate long command line option", fun->lopt);
666 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
667 }
668 }
669
670 /* Check duplicates with the registered parameters. */
671 2 cfgcli_param_valid_t *cpar = (cfgcli_param_valid_t *) cfg->params;
672
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 2 times.
32 for (j = 0; j < cfg->npar; j++) {
673
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) {
674 tmp[0] = fun->opt;
675 tmp[1] = '\0';
676 cfgcli_msg(cfg, "duplicate short command line option", tmp);
677 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
678 }
679
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 &&
680
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 !strncmp(fun->lopt, cpar[j].lopt, fun->llen)) {
681 cfgcli_msg(cfg, "duplicate long command line option", fun->lopt);
682 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_EXIST;
683 }
684 }
685 }
686
687 1 cfg->nfunc += nfunc;
688 1 return 0;
689 }
690
691
692 /*============================================================================*\
693 Functions for parsing configurations represented by strings
694 \*============================================================================*/
695
696 /******************************************************************************
697 Function `cfgcli_parse_line`:
698 Read the configuration from a line of a configration file.
699 Arguments:
700 * `line`: the null terminated string line;
701 * `len`: length of the line, NOT including the first '\0';
702 * `key`: address of the retrieved keyword;
703 * `value`: address of the retrieved value;
704 * `state`: initial state for the parser.
705 Return:
706 Parser status.
707 ******************************************************************************/
708 22 static cfgcli_parse_return_t cfgcli_parse_line(char *line, const size_t len,
709 char **key, char **value, cfgcli_parse_state_t state) {
710
4/6
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 19 times.
22 if (!line || *line == '\0' || len == 0) return CFGCLI_PARSE_PASS;
711 19 char quote = '\0'; /* handle quotation marks */
712 19 char *newline = NULL; /* handle line continuation */
713
2/2
✓ Branch 0 taken 351 times.
✓ Branch 1 taken 13 times.
364 for (size_t i = 0; i < len; i++) {
714 351 char c = line[i];
715
12/14
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 92 times.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 28 times.
✓ Branch 4 taken 43 times.
✓ Branch 5 taken 33 times.
✓ Branch 6 taken 37 times.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 14 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 31 times.
✗ Branch 13 not taken.
351 switch (state) {
716 28 case CFGCLI_PARSE_START:
717
4/6
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
28 if (isalpha(c) || c == '_' || c == '-') {
718 14 *key = line + i;
719 14 state = CFGCLI_PARSE_KEYWORD;
720 }
721
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14 else if (c == CFGCLI_SYM_COMMENT) return CFGCLI_PARSE_PASS;
722
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
723 24 break;
724 92 case CFGCLI_PARSE_KEYWORD:
725
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)) {
726 /* check if the keyword is too long */
727
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (line + i - *key >= CFGCLI_MAX_NAME_LEN) return CFGCLI_PARSE_ERROR;
728 14 line[i] = '\0'; /* terminate the keyword */
729 14 state = (c == CFGCLI_SYM_EQUAL) ?
730
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 CFGCLI_PARSE_VALUE_START : CFGCLI_PARSE_EQUAL;
731 }
732
3/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 71 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
78 else if (!isalnum(c) && c != '_' && c != '-') return CFGCLI_PARSE_ERROR;
733 92 break;
734 31 case CFGCLI_PARSE_EQUAL:
735
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 17 times.
31 if (c == CFGCLI_SYM_EQUAL) state = CFGCLI_PARSE_VALUE_START;
736
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
737 31 break;
738 28 case CFGCLI_PARSE_VALUE_START:
739
3/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
28 if (c == '"' || c == '\'') { /* enter quotes */
740 1 quote = c;
741 1 *value = line + i;
742 1 state = CFGCLI_PARSE_QUOTE;
743 }
744
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 21 times.
27 else if (c == CFGCLI_SYM_ARRAY_START) { /* beginning of array */
745 6 *value = line + i;
746 6 state = CFGCLI_PARSE_ARRAY_START;
747 }
748
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 else if (c == CFGCLI_SYM_COMMENT) return CFGCLI_PARSE_PASS; /* no value */
749
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 14 times.
21 else if (isgraph(c)) { /* beginning of value */
750 7 *value = line + i;
751 7 state = CFGCLI_PARSE_VALUE;
752 }
753
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
754 28 break;
755 43 case CFGCLI_PARSE_ARRAY_START:
756
3/6
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 43 times.
43 if (c == CFGCLI_SYM_ARRAY_SEP || c == CFGCLI_SYM_ARRAY_END ||
757 c == CFGCLI_SYM_COMMENT) /* no value is found */
758 return CFGCLI_PARSE_ERROR;
759
4/4
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 38 times.
43 else if (c == '"' || c == '\'') { /* enter quotes */
760 5 quote = c;
761 5 state = CFGCLI_PARSE_ARRAY_QUOTE;
762 }
763
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 37 times.
38 else if (c == CFGCLI_SYM_NEWLINE) { /* line continuation */
764 1 newline = line + i;
765 1 state = CFGCLI_PARSE_ARRAY_NEWLINE;
766 }
767
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 26 times.
37 else if (isgraph(c)) state = CFGCLI_PARSE_ARRAY_VALUE;
768
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
769 43 break;
770 33 case CFGCLI_PARSE_VALUE:
771
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 31 times.
33 if (c == CFGCLI_SYM_COMMENT) {
772 2 line[i] = '\0'; /* terminate the value */
773 2 return CFGCLI_PARSE_DONE;
774 }
775
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 else if (!isprint(c)) return CFGCLI_PARSE_ERROR;
776 31 break;
777 37 case CFGCLI_PARSE_ARRAY_VALUE:
778
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 30 times.
37 if (c == CFGCLI_SYM_ARRAY_SEP) /* new array element */
779 7 state = CFGCLI_PARSE_ARRAY_START;
780
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 26 times.
30 else if (c == CFGCLI_SYM_ARRAY_END) /* end of array */
781 4 state = CFGCLI_PARSE_ARRAY_END;
782
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
26 else if (c == CFGCLI_SYM_COMMENT || !isprint(c)) return CFGCLI_PARSE_ERROR;
783 37 break;
784 6 case CFGCLI_PARSE_QUOTE:
785
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (c == quote) state = CFGCLI_PARSE_QUOTE_END;
786 6 break;
787 14 case CFGCLI_PARSE_ARRAY_QUOTE:
788
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 9 times.
14 if (c == quote) state = CFGCLI_PARSE_ARRAY_QUOTE_END;
789 14 break;
790 case CFGCLI_PARSE_QUOTE_END:
791 case CFGCLI_PARSE_ARRAY_END:
792 if (c == CFGCLI_SYM_COMMENT) {
793 line[i] = '\0';
794 return CFGCLI_PARSE_DONE;
795 }
796 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
797 break;
798 5 case CFGCLI_PARSE_ARRAY_QUOTE_END:
799
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (c == CFGCLI_SYM_ARRAY_SEP) state = CFGCLI_PARSE_ARRAY_START;
800
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 else if (c == CFGCLI_SYM_ARRAY_END) state = CFGCLI_PARSE_ARRAY_END;
801 else if (!isspace(c)) return CFGCLI_PARSE_ERROR;
802 5 break;
803 3 case CFGCLI_PARSE_ARRAY_NEWLINE: /* line continuation */
804
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (c == CFGCLI_SYM_COMMENT) { /* clear all characters */
805 1 state = CFGCLI_PARSE_CLEAN;
806 1 line[i] = ' ';
807 1 *newline = ' ';
808 }
809
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 else if (!isspace(c)) { /* not really for line continuation */
810 newline = NULL;
811 state = CFGCLI_PARSE_ARRAY_VALUE;
812 i--;
813 }
814 3 break;
815 31 case CFGCLI_PARSE_CLEAN:
816 31 line[i] = ' ';
817 31 break;
818 default:
819 return CFGCLI_PARSE_ERROR;
820 }
821 }
822
823 /* Check the final status. */
824
2/5
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
13 switch (state) {
825 12 case CFGCLI_PARSE_VALUE:
826 case CFGCLI_PARSE_QUOTE_END:
827 case CFGCLI_PARSE_ARRAY_END:
828 12 return CFGCLI_PARSE_DONE;
829 case CFGCLI_PARSE_START:
830 case CFGCLI_PARSE_VALUE_START:
831 return CFGCLI_PARSE_PASS;
832 case CFGCLI_PARSE_ARRAY_NEWLINE:
833 *newline = ' ';
834 #if __STDC_VERSION__ > 201112L
835 [[fallthrough]];
836 #endif
837 1 case CFGCLI_PARSE_CLEAN:
838 1 return CFGCLI_PARSE_CONTINUE;
839 default:
840 return CFGCLI_PARSE_ERROR;
841 }
842 }
843
844 /******************************************************************************
845 Function `cfgcli_parse_array`:
846 Split the string defined as array, and count the number of elements.
847 Arguments:
848 * `par`: address of the verified configuration parameter.
849 Return:
850 Zero on success; non-zero on error.
851 ******************************************************************************/
852 7 static int cfgcli_parse_array(cfgcli_param_valid_t *par) {
853 7 par->narr = 0; /* no need to check whether `par` is NULL */
854
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 */
855
856 7 int n = 0;
857 7 char quote = '\0';
858 7 cfgcli_parse_state_t state = CFGCLI_PARSE_START;
859 char *start, *end;
860 7 start = end = NULL;
861
862
2/2
✓ Branch 0 taken 147 times.
✓ Branch 1 taken 6 times.
153 for (size_t i = 0; i < par->vlen; i++) {
863
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 147 times.
147 if (state == CFGCLI_PARSE_ARRAY_DONE) break;
864 147 char c = par->value[i]; /* this is surely not '\0' */
865
866
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) {
867 7 case CFGCLI_PARSE_START:
868
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 if (c == CFGCLI_SYM_ARRAY_START) {
869 6 state = CFGCLI_PARSE_ARRAY_START;
870 6 start = par->value + i; /* mark the array starting point */
871 }
872
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (!isspace(c)) { /* not an array */
873 1 par->narr = 1; /* try to parse as a single variable later */
874 1 return 0;
875 }
876 6 break;
877 78 case CFGCLI_PARSE_ARRAY_START:
878
3/6
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 78 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 78 times.
78 if (c == CFGCLI_SYM_ARRAY_SEP || c == CFGCLI_SYM_ARRAY_END ||
879 c == CFGCLI_SYM_COMMENT) /* no value is found */
880 return CFGCLI_ERR_VALUE;
881
4/4
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 73 times.
78 else if (c == '"' || c == '\'') { /* enter quotes */
882 5 quote = c;
883 5 state = CFGCLI_PARSE_ARRAY_QUOTE;
884 }
885
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 62 times.
73 else if (isgraph(c)) state = CFGCLI_PARSE_ARRAY_VALUE;
886
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 else if (!isspace(c)) return CFGCLI_ERR_VALUE;
887 78 break;
888 37 case CFGCLI_PARSE_ARRAY_VALUE:
889
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 30 times.
37 if (c == CFGCLI_SYM_ARRAY_SEP) { /* new array element */
890 7 n++;
891 7 state = CFGCLI_PARSE_ARRAY_START;
892 7 par->value[i] = '\0'; /* add separator for value parser */
893 }
894
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 26 times.
30 else if (c == CFGCLI_SYM_ARRAY_END) { /* end of array */
895 4 state = CFGCLI_PARSE_ARRAY_END;
896 4 end = par->value + i; /* mark the array ending point */
897 }
898
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
26 else if (c == CFGCLI_SYM_COMMENT || !isprint(c)) return CFGCLI_ERR_VALUE;
899 37 break;
900 14 case CFGCLI_PARSE_ARRAY_QUOTE:
901
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 9 times.
14 if (c == quote) {
902 5 quote = '\0';
903 5 state = CFGCLI_PARSE_ARRAY_QUOTE_END;
904 }
905 14 break;
906 5 case CFGCLI_PARSE_ARRAY_QUOTE_END:
907
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (c == CFGCLI_SYM_ARRAY_SEP) { /* new array element */
908 3 n++;
909 3 state = CFGCLI_PARSE_ARRAY_START;
910 3 par->value[i] = '\0'; /* add separator for value parser */
911 }
912
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 else if (c == CFGCLI_SYM_ARRAY_END) { /* end of array */
913 2 state = CFGCLI_PARSE_ARRAY_END;
914 2 end = par->value + i; /* mark the array ending point */
915 }
916 else if (!isspace(c)) return CFGCLI_ERR_VALUE;
917 5 break;
918 6 case CFGCLI_PARSE_ARRAY_END:
919
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (c == CFGCLI_SYM_COMMENT) {
920 state = CFGCLI_PARSE_ARRAY_DONE;
921 par->value[i] = '\0'; /* terminate earlier to skip comments */
922 }
923
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 else if (isgraph(c)) return CFGCLI_ERR_VALUE;
924 6 break;
925 default:
926 return CFGCLI_ERR_VALUE;
927 }
928 }
929
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (start)
930 6 par->value = start + 1; /* omit the starting '[' */
931
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (end)
932 6 *end = '\0'; /* remove the ending ']' */
933 6 par->narr = n + 1;
934 6 return 0;
935 }
936
937 /******************************************************************************
938 Function `cfgcli_get_value`:
939 Retrieve the parameter value and assign it to a variable.
940 Arguments:
941 * `var`: pointer to the variable to be assigned value;
942 * `str`: string storing the parameter value;
943 * `size`: length of `value`, including the ending '\0';
944 * `dtype`: data type;
945 * `src`: source of this value.
946 Return:
947 Zero on success; non-zero on error.
948 ******************************************************************************/
949 25 static int cfgcli_get_value(void *var, char *str, const size_t size,
950 const cfgcli_dtype_t dtype, int src) {
951 (void) src;
952
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
25 if (!str || !size) return 0;
953 25 char *value = str;
954 int n;
955
956 /* Validate the value. */
957
3/4
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 25 times.
87 while (*value && isspace(*value)) value++; /* omit whitespaces */
958
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (*value == '\0') return CFGCLI_ERR_VALUE; /* empty string */
959
4/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 19 times.
31 if (*value == '"' || *value == '\'') { /* remove quotes */
960 6 char quote = *value;
961 6 value++;
962
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 for (n = 0; value[n]; n++) { /* the string is surely terminated */
963
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
20 if (value[n] == quote) {
964 6 value[n] = '\0';
965 6 quote = 0; /* a flag indicating the other quote is found */
966 6 break;
967 }
968 }
969 /* empty string with quotes is valid for char or string type variable */
970
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 if (*value == '\0' && dtype != CFGCLI_DTYPE_CHAR && dtype != CFGCLI_DTYPE_STR)
971 return CFGCLI_ERR_VALUE;
972
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (quote) return CFGCLI_ERR_VALUE; /* open quotation marks */
973
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;
974 }
975 else { /* remove trailing whitespaces */
976 19 char *val = str + size - 2;
977
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 19 times.
32 while (isspace(*val)) {
978 13 *val = '\0';
979 13 val--;
980 }
981 }
982
983 /* Variable assignment. */
984 25 n = 0;
985
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 4 times.
✗ Branch 7 not taken.
25 switch (dtype) {
986 5 case CFGCLI_DTYPE_BOOL:
987
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) ||
988
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) ||
989
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))
990 2 *((bool *) var) = true;
991
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) ||
992
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) ||
993
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))
994 3 *((bool *) var) = false;
995 else return CFGCLI_ERR_PARSE;
996 5 break;
997 4 case CFGCLI_DTYPE_CHAR:
998 4 *((char *) var) = *value;
999 4 n = 1;
1000 4 break;
1001 2 case CFGCLI_DTYPE_INT:
1002
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (sscanf(value, "%d%n", (int *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1003 2 break;
1004 2 case CFGCLI_DTYPE_LONG:
1005
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (sscanf(value, "%ld%n", (long *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1006 2 break;
1007 5 case CFGCLI_DTYPE_FLT:
1008
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (sscanf(value, "%f%n", (float *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1009 5 break;
1010 3 case CFGCLI_DTYPE_DBL:
1011
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sscanf(value, "%lf%n", (double *) var, &n) != 1) return CFGCLI_ERR_PARSE;
1012 3 break;
1013 4 case CFGCLI_DTYPE_STR:
1014 4 strcpy(*((char **) var), value); /* the usage of strcpy is safe here */
1015 4 break;
1016 default:
1017 return CFGCLI_ERR_DTYPE;
1018 }
1019
1020
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 9 times.
25 if (n) { /* check remaining characters */
1021 16 value += n;
1022
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 while (*value != '\0') {
1023 if (!isspace(*value)) return CFGCLI_ERR_VALUE;
1024 value++;
1025 }
1026 }
1027
1028 25 return 0;
1029 }
1030
1031 /******************************************************************************
1032 Function `cfgcli_get_array`:
1033 Retrieve the parameter values and assign them to an array.
1034 Arguments:
1035 * `par`: address of the verified configuration parameter;
1036 * `src`: source of the value.
1037 Return:
1038 Zero on success; non-zero on error.
1039 ******************************************************************************/
1040 7 static int cfgcli_get_array(cfgcli_param_valid_t *par, int src) {
1041 size_t len;
1042 int i, err;
1043
1044 /* Split the value string for array elements. */
1045
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 if ((err = cfgcli_parse_array(par))) return err;
1046 7 char *value = par->value; /* array elements are separated by '\0' */
1047
1048 /* Allocate memory and assign values for arrays. */
1049
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) {
1050 1 case CFGCLI_ARRAY_BOOL:
1051 1 *((bool **) par->var) = calloc(par->narr, sizeof(bool));
1052
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((bool **) par->var))) return CFGCLI_ERR_MEMORY;
1053 /* call the value assignment function for each segment */
1054
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (i = 0; i < par->narr; i++) {
1055 4 len = strlen(value) + 1; /* strlen is safe here */
1056
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ((err = cfgcli_get_value(*((bool **) par->var) + i, value, len,
1057 CFGCLI_DTYPE_BOOL, src))) return err;
1058 4 value += len;
1059 }
1060 1 break;
1061 1 case CFGCLI_ARRAY_CHAR:
1062 1 *((char **) par->var) = calloc(par->narr, sizeof(char));
1063
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((char **) par->var))) return CFGCLI_ERR_MEMORY;
1064
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 for (i = 0; i < par->narr; i++) {
1065 3 len = strlen(value) + 1;
1066
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((err = cfgcli_get_value(*((char **) par->var) + i, value, len,
1067 CFGCLI_DTYPE_CHAR, src))) return err;
1068 3 value += len;
1069 }
1070 1 break;
1071 1 case CFGCLI_ARRAY_INT:
1072 1 *((int **) par->var) = calloc(par->narr, sizeof(int));
1073
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((int **) par->var))) return CFGCLI_ERR_MEMORY;
1074
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (i = 0; i < par->narr; i++) {
1075 1 len = strlen(value) + 1;
1076
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((err = cfgcli_get_value(*((int **) par->var) + i, value, len,
1077 CFGCLI_DTYPE_INT, src))) return err;
1078 1 value += len;
1079 }
1080 1 break;
1081 1 case CFGCLI_ARRAY_LONG:
1082 1 *((long **) par->var) = calloc(par->narr, sizeof(long));
1083
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((long **) par->var))) return CFGCLI_ERR_MEMORY;
1084
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (i = 0; i < par->narr; i++) {
1085 1 len = strlen(value) + 1;
1086
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((err = cfgcli_get_value(*((long **) par->var) + i, value, len,
1087 CFGCLI_DTYPE_LONG, src))) return err;
1088 1 value += len;
1089 }
1090 1 break;
1091 1 case CFGCLI_ARRAY_FLT:
1092 1 *((float **) par->var) = calloc(par->narr, sizeof(float));
1093
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((float **) par->var))) return CFGCLI_ERR_MEMORY;
1094
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (i = 0; i < par->narr; i++) {
1095 4 len = strlen(value) + 1;
1096
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ((err = cfgcli_get_value(*((float **) par->var) + i, value, len,
1097 CFGCLI_DTYPE_FLT, src))) return err;
1098 4 value += len;
1099 }
1100 1 break;
1101 1 case CFGCLI_ARRAY_DBL:
1102 1 *((double **) par->var) = calloc(par->narr, sizeof(double));
1103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((double **) par->var))) return CFGCLI_ERR_MEMORY;
1104
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (i = 0; i < par->narr; i++) {
1105 2 len = strlen(value) + 1;
1106
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((err = cfgcli_get_value(*((double **) par->var) + i, value, len,
1107 CFGCLI_DTYPE_DBL, src))) return err;
1108 2 value += len;
1109 }
1110 1 break;
1111 1 case CFGCLI_ARRAY_STR:
1112 1 *((char ***) par->var) = calloc(par->narr, sizeof(char *));
1113
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(*((char ***) par->var))) return CFGCLI_ERR_MEMORY;
1114 /* Allocate enough memory for the first element of the string array. */
1115 1 *(*((char ***) par->var)) = calloc(par->vlen, sizeof(char));
1116 1 char *tmp = *(*((char ***) par->var));
1117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!tmp) return CFGCLI_ERR_MEMORY;
1118 /* The rest elements point to different positions of the space. */
1119
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (i = 0; i < par->narr; i++) {
1120 2 (*((char ***) par->var))[i] = tmp;
1121 2 len = strlen(value) + 1;
1122
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((err = cfgcli_get_value(&tmp, value, len, CFGCLI_DTYPE_STR, src)))
1123 return err;
1124 2 tmp += strlen(tmp) + 1; /* null termination ensured by calloc */
1125 2 value += len;
1126 }
1127 1 break;
1128 default:
1129 return CFGCLI_ERR_DTYPE; /* is CFGCLI_DTYPE_IS_ARRAY correct? */
1130 }
1131 7 return 0;
1132 }
1133
1134 /******************************************************************************
1135 Function `cfgcli_get`:
1136 Retrieve the parameter value and assign it to a variable.
1137 Arguments:
1138 * `cfg`: entry for all configurations;
1139 * `par`: address of the verified configuration parameter;
1140 * `src`: source of the value;
1141 Return:
1142 Zero on success; non-zero on error.
1143 ******************************************************************************/
1144 15 static int cfgcli_get(cfgcli_t *cfg, cfgcli_param_valid_t *par, int src) {
1145 15 int err = 0;
1146 /* Validate function arguments. */
1147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
1148
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
15 if (!par->value || *par->value == '\0') return 0; /* value not set */
1149
1150 /* Deal with arrays and scalars separately. */
1151
3/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
15 if (CFGCLI_DTYPE_IS_ARRAY(par->dtype)) /* force preprocessing the value */
1152 7 err = cfgcli_get_array(par, src);
1153 else {
1154 /* Allocate memory only for string. */
1155
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (par->dtype == CFGCLI_DTYPE_STR) {
1156 2 *((char **) par->var) = calloc(par->vlen, sizeof(char));
1157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!(*((char **) par->var))) err = CFGCLI_ERR_MEMORY;
1158 }
1159
1160 /* Assign values to the variable. */
1161
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (!err)
1162 8 err = cfgcli_get_value(par->var, par->value, par->vlen, par->dtype, src);
1163 }
1164
1165
1/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
15 switch (err) {
1166 15 case 0:
1167 15 return 0;
1168 case CFGCLI_ERR_MEMORY:
1169 cfgcli_msg(cfg, "failed to allocate memory for parameter", par->name);
1170 return CFGCLI_ERRNO(cfg) = err;
1171 case CFGCLI_ERR_VALUE:
1172 cfgcli_msg(cfg, "invalid value for parameter", par->name);
1173 return CFGCLI_ERRNO(cfg) = err;
1174 case CFGCLI_ERR_PARSE:
1175 cfgcli_msg(cfg, "failed to parse the value for parameter", par->name);
1176 return CFGCLI_ERRNO(cfg) = err;
1177 case CFGCLI_ERR_DTYPE:
1178 cfgcli_msg(cfg, "invalid data type for parameter", par->name);
1179 return CFGCLI_ERRNO(cfg) = err;
1180 default:
1181 cfgcli_msg(cfg, "unknown error occurred for parameter", par->name);
1182 return CFGCLI_ERRNO(cfg) = err;
1183 }
1184 }
1185
1186
1187 /*============================================================================*\
1188 High-level functions for reading configurations
1189 from command line options and text files
1190 \*============================================================================*/
1191
1192 /******************************************************************************
1193 Function `cfgcli_read_opts`:
1194 Parse command line options.
1195 Arguments:
1196 * `cfg`: entry for the configurations;
1197 * `argc`: number of arguments passed via command line;
1198 * `argv`: array of command line arguments;
1199 * `prior`: priority of values set via command line options;
1200 * `optidx`: position of the first unparsed argument.
1201 Return:
1202 Zero on success; non-zero on error.
1203 ******************************************************************************/
1204 2 int cfgcli_read_opts(cfgcli_t *cfg, const int argc, char *const *argv,
1205 const int prior, int *optidx) {
1206 /* Validate function arguments. */
1207
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!cfg) return CFGCLI_ERR_INIT;
1208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
1209
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (cfg->npar <= 0 && cfg->nfunc <= 0) {
1210 cfgcli_msg(cfg, "no parameter or function has been registered", NULL);
1211 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INIT;
1212 }
1213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (prior <= CFGCLI_SRC_NULL) {
1214 cfgcli_msg(cfg, "invalid priority for command line options", NULL);
1215 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1216 }
1217
1218 2 *optidx = 0;
1219
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
2 if (argc <= 0 || !argv || !(*argv)) return 0;
1220 int i;
1221
1222 /* Start parsing command line options. */
1223
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 for (i = 1; i < argc; i++) {
1224 1 char *arg = argv[i];
1225
7/18
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✓ Branch 17 taken 1 times.
1 if (!(CFGCLI_IS_OPT(arg))) { /* unrecognised option */
1226 cfgcli_msg(cfg, "unrecognised command line option", arg);
1227 continue;
1228 }
1229
1230 int j;
1231 1 char *optarg = NULL;
1232
1233
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 for (j = 0; j < CFGCLI_MAX_LOPT_LEN + 2; j++) /* check if '=' exists */
1234
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
17 if (arg[j] == '\0' || arg[j] == CFGCLI_CMD_ASSIGN) break;
1235
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (arg[j] == '\0') { /* '=' is not found */
1236 1 j = i + 1;
1237
2/20
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
1 if (j < argc && !(CFGCLI_IS_OPT(argv[j]))) optarg = argv[++i];
1238 }
1239 else if (arg[j] == CFGCLI_CMD_ASSIGN) { /* '=' is found */
1240 arg[j] = '\0';
1241 optarg = &arg[j + 1];
1242 }
1243 else {
1244 cfgcli_msg(cfg, "the command line option is too long", arg);
1245 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_CMD;
1246 }
1247
1248 1 cfgcli_param_valid_t *params = (cfgcli_param_valid_t *) cfg->params;
1249 1 cfgcli_func_valid_t *funcs = (cfgcli_func_valid_t *) cfg->funcs;
1250 enum { not_found, is_param, is_func } status;
1251 1 status = not_found;
1252
1253
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (arg[1] != CFGCLI_CMD_FLAG) { /* short option */
1254 for (j = 0; j < cfg->npar; j++) {
1255 if (arg[1] == params[j].opt) {
1256 status = is_param;
1257 break;
1258 }
1259 }
1260 if (status != is_param) {
1261 for (j = 0; j < cfg->nfunc; j++) {
1262 if (arg[1] == funcs[j].opt) {
1263 status = is_func;
1264 break;
1265 }
1266 }
1267 }
1268 }
1269
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (arg[2] != '\0') { /* long option */
1270
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (j = 0; j < cfg->npar; j++) {
1271
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (params[j].lopt &&
1272
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 !strncmp(params[j].lopt, arg + 2, params[j].llen)) {
1273 1 status = is_param;
1274 1 break;
1275 }
1276 }
1277
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (status != is_param) {
1278 for (j = 0; j < cfg->nfunc; j++) {
1279 if (funcs[j].lopt &&
1280 !strncmp(funcs[j].lopt, arg + 2, funcs[j].llen)) {
1281 status = is_func;
1282 break;
1283 }
1284 }
1285 }
1286 }
1287 else { /* parser termination */
1288 *optidx = j; /* for arg = "--", j = i + 1 */
1289 break;
1290 }
1291
1292
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (status == is_func) { /* call the command line function */
1293 if (optarg) cfgcli_msg(cfg, "omitting command line argument", optarg);
1294 if (funcs[j].called)
1295 cfgcli_msg(cfg, "the function has already been called with option", arg);
1296 else {
1297 funcs[j].func(funcs[j].args); /* call the function */
1298 funcs[j].called = 1;
1299 }
1300 }
1301
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (status == is_param) { /* assign parameter value */
1302 /* Priority check. */
1303
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (CFGCLI_SRC_VAL(params[j].src) > prior) continue;
1304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 else if (CFGCLI_SRC_VAL(params[j].src) == prior) {
1305 cfgcli_msg(cfg, "omitting duplicate entry of parameter", params[j].name);
1306 continue;
1307 }
1308 /* Command line arguments can be omitted for bool type variables. */
1309
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!optarg || *optarg == '\0') {
1310 if (params[j].dtype == CFGCLI_DTYPE_BOOL) {
1311 params[j].value = "T";
1312 params[j].vlen = 2;
1313 }
1314 else {
1315 cfgcli_msg(cfg, "argument not found for option", arg);
1316 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_CMD;
1317 }
1318 }
1319 else {
1320 1 params[j].value = optarg; /* args are surely null terminated */
1321 1 params[j].vlen = strlen(optarg) + 1; /* safe strlen */
1322 }
1323 /* Assign value to variable. */
1324 1 int err = cfgcli_get(cfg, params + j, CFGCLI_SRC_OF_OPT(prior));
1325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (err) return err;
1326 1 params[j].src = CFGCLI_SRC_OF_OPT(prior);
1327 }
1328 else /* option not registered */
1329 cfgcli_msg(cfg, "unrecognised command line option", arg);
1330 }
1331
1332
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (*optidx == 0) *optidx = i;
1333 2 return 0;
1334 }
1335
1336 /******************************************************************************
1337 Function `cfgcli_read_file`:
1338 Read configuration parameters from a file.
1339 Arguments:
1340 * `cfg`: entry for the configurations;
1341 * `fname`: name of the input file;
1342 * `prior`: priority of values read from this file.
1343 Return:
1344 Zero on success; non-zero on error.
1345 ******************************************************************************/
1346 1 int cfgcli_read_file(cfgcli_t *cfg, const char *fname, const int prior) {
1347 /* Validate function arguments. */
1348
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cfg) return CFGCLI_ERR_INIT;
1349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (CFGCLI_IS_ERROR(cfg)) return CFGCLI_ERRNO(cfg);
1350
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (cfg->npar <= 0) {
1351 cfgcli_msg(cfg, "no parameter has been registered", NULL);
1352 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INIT;
1353 }
1354
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') {
1355 cfgcli_msg(cfg, "the input configuration file is not set", NULL);
1356 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1357 }
1358
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!(cfgcli_strnlen(fname, CFGCLI_MAX_FILENAME_LEN))) {
1359 cfgcli_msg(cfg, "invalid filename of the configuration file", NULL);
1360 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1361 }
1362
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (prior <= CFGCLI_SRC_NULL) {
1363 cfgcli_msg(cfg, "invalid priority for configuration file", fname);
1364 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_INPUT;
1365 }
1366
1367 1 FILE *fp = fopen(fname, "r");
1368
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!fp) {
1369 cfgcli_msg(cfg, "cannot open the configuration file", fname);
1370 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_FILE;
1371 }
1372
1373 /* Read file by chunk. */
1374 1 size_t clen = CFGCLI_STR_INIT_SIZE;
1375 1 char *chunk = calloc(clen, sizeof(char));
1376
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!chunk) {
1377 fclose(fp);
1378 cfgcli_msg(cfg, "failed to allocate memory for reading file", fname);
1379 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1380 }
1381
1382 size_t nline, nrest, nproc, cnt;
1383 char *key, *value;
1384 1 cfgcli_parse_state_t state = CFGCLI_PARSE_START;
1385 1 nline = nrest = nproc = 0;
1386 1 key = value = NULL;
1387 1 cfgcli_param_valid_t *params = (cfgcli_param_valid_t *) cfg->params;
1388
1389
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 while ((cnt = fread(chunk + nrest, sizeof(char), clen - nrest, fp))) {
1390
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 char *p = (state == CFGCLI_PARSE_ARRAY_START) ? chunk + nproc : chunk;
1391 1 char *end = chunk + nrest + cnt;
1392 char *endl;
1393
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (cnt < clen - nrest) *end++ = '\n'; /* terminate the last line */
1394
1395 /* Process lines in the chunk. */
1396
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 1 times.
23 while ((endl = memchr(p, '\n', end - p))) {
1397 22 *endl = '\0'; /* replace '\n' by '\0' for line parser */
1398 22 nline += 1;
1399
1400 /* Retrieve the keyword and value from the line. */
1401 char msg[CFGCLI_NUM_MAX_SIZE(size_t)];
1402 int j;
1403 cfgcli_parse_return_t status =
1404 22 cfgcli_parse_line(p, endl - p, &key, &value, state);
1405
1406
3/5
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
22 switch (status) {
1407 14 case CFGCLI_PARSE_DONE:
1408 /* search for the parameter given the name */
1409
1/2
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
119 for (j = 0; j < cfg->npar; j++)
1410
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 105 times.
119 if (!strncmp(key, params[j].name, params[j].nlen)) break;
1411
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (j == cfg->npar) /* parameter not found */
1412 cfgcli_msg(cfg, "unregistered parameter name", key);
1413 else {
1414 /* priority check */
1415
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (CFGCLI_SRC_VAL(params[j].src) < prior) {
1416 14 params[j].value = value;
1417 14 params[j].vlen = strlen(value) + 1;
1418 14 int err = cfgcli_get(cfg, params + j, prior);
1419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (err) {
1420 free(chunk);
1421 fclose(fp);
1422 return err;
1423 }
1424 14 params[j].src = prior;
1425 }
1426 else if (CFGCLI_SRC_VAL(params[j].src) == prior)
1427 cfgcli_msg(cfg, "omitting duplicate entry of parameter", key);
1428 }
1429 /* reset states */
1430 14 key = value = NULL;
1431 14 state = CFGCLI_PARSE_START;
1432 14 break;
1433 1 case CFGCLI_PARSE_CONTINUE: /* line continuation */
1434 1 *endl = ' '; /* remove line break */
1435 1 state = CFGCLI_PARSE_ARRAY_START;
1436 1 break;
1437 case CFGCLI_PARSE_ERROR:
1438 sprintf(msg, "%zu", nline);
1439 cfgcli_msg(cfg, "invalid configuration entry at line", msg);
1440 #if __STDC_VERSION__ > 201112L
1441 [[fallthrough]];
1442 #endif
1443 7 case CFGCLI_PARSE_PASS:
1444 7 state = CFGCLI_PARSE_START;
1445 7 break;
1446 default:
1447 free(chunk);
1448 fclose(fp);
1449 sprintf(msg, "%d", status);
1450 cfgcli_msg(cfg, "unknown line parser status", msg);
1451 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_UNKNOWN;
1452 }
1453 22 p = endl + 1;
1454 }
1455
1456 /* The chunk cannot hold a full line. */
1457
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (p == chunk) {
1458 size_t new_len = 0;
1459 if (clen >= CFGCLI_STR_MAX_DOUBLE_SIZE) {
1460 if (SIZE_MAX - CFGCLI_STR_MAX_DOUBLE_SIZE >= clen)
1461 new_len = clen + CFGCLI_STR_MAX_DOUBLE_SIZE;
1462 }
1463 else if (SIZE_MAX / 2 >= clen) new_len = clen << 1;
1464 if (!new_len) { /* overflow occurred */
1465 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1466 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1467 }
1468 char *tmp = realloc(chunk, new_len);
1469 if (!tmp) {
1470 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1471 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1472 }
1473 chunk = tmp;
1474 clen = new_len;
1475 nrest += cnt;
1476 continue;
1477 }
1478
1479 /* Copy the remaining characters to the beginning of the chunk. */
1480
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (state == CFGCLI_PARSE_ARRAY_START) { /* copy also parsed part */
1481 if (!key) {
1482 free(chunk);
1483 fclose(fp);
1484 cfgcli_msg(cfg, "unknown parser interruption", NULL);
1485 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_UNKNOWN;
1486 }
1487 /* `key` is the starting point of this effective line */
1488 nrest = end - key;
1489 nproc = p - key;
1490 memmove(chunk, key, nrest);
1491 /* shift `key` and `value` */
1492 if (value) value -= key - chunk;
1493 key = chunk;
1494 }
1495 else { /* copy only from the current position */
1496 1 nrest = end - p;
1497 1 memmove(chunk, p, nrest);
1498 }
1499
1500 /* The chunk is full. */
1501
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (nrest == clen) {
1502 size_t new_len = 0;
1503 if (clen >= CFGCLI_STR_MAX_DOUBLE_SIZE) {
1504 if (SIZE_MAX - CFGCLI_STR_MAX_DOUBLE_SIZE >= clen)
1505 new_len = clen + CFGCLI_STR_MAX_DOUBLE_SIZE;
1506 }
1507 else if (SIZE_MAX / 2 >= clen) new_len = clen << 1;
1508 if (!new_len) {
1509 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1510 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1511 }
1512 size_t key_shift = key ? key - chunk : 0;
1513 size_t value_shift = value ? value - chunk : 0;
1514 char *tmp = realloc(chunk, new_len);
1515 if (!tmp) {
1516 cfgcli_msg(cfg, "failed to allocate memory for reading the file", fname);
1517 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_MEMORY;
1518 }
1519 chunk = tmp;
1520 clen = new_len;
1521 if (key) key = chunk + key_shift;
1522 if (value) value = chunk + value_shift;
1523 }
1524 }
1525
1526
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!feof(fp)) {
1527 cfgcli_msg(cfg, "unexpected end of file", fname);
1528 free(chunk);
1529 fclose(fp);
1530 return CFGCLI_ERRNO(cfg) = CFGCLI_ERR_FILE;
1531 }
1532
1533 1 free(chunk);
1534 1 fclose(fp);
1535 1 return 0;
1536 }
1537
1538
1539 /*============================================================================*\
1540 Functions for checking the status of variables
1541 \*============================================================================*/
1542
1543 /******************************************************************************
1544 Function `cfgcli_is_set`:
1545 Check if a variable is set via the command line or files.
1546 Arguments:
1547 * `cfg`: entry of all configurations;
1548 * `var`: address of the variable.
1549 Return:
1550 True if the variable is set; false otherwise.
1551 ******************************************************************************/
1552 11 bool cfgcli_is_set(const cfgcli_t *cfg, const void *var) {
1553
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
11 if (!cfg || !var || !cfg->npar) return false;
1554
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 for (int i = 0; i < cfg->npar; i++) {
1555 39 cfgcli_param_valid_t *par = (cfgcli_param_valid_t *) cfg->params + i;
1556
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 28 times.
39 if (par->var == var) {
1557
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
11 if (par->src != CFGCLI_SRC_NULL) return true;
1558 3 break;
1559 }
1560 }
1561 3 return false;
1562 }
1563
1564 /******************************************************************************
1565 Function `cfgcli_get_size`:
1566 Return the number of elements for the parsed array.
1567 Arguments:
1568 * `cfg`: entry of all configurations;
1569 * `var`: address of the variable.
1570 Return:
1571 The number of array elements on success; 0 on error.
1572 ******************************************************************************/
1573 7 int cfgcli_get_size(const cfgcli_t *cfg, const void *var) {
1574
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
7 if (!cfg || !var || !cfg->npar) return 0;
1575
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 for (int i = 0; i < cfg->npar; i++) {
1576 84 cfgcli_param_valid_t *par = (cfgcli_param_valid_t *) cfg->params + i;
1577
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 77 times.
84 if (par->var == var) {
1578
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (par->src != CFGCLI_SRC_NULL) return par->narr;
1579 break;
1580 }
1581 }
1582 return 0;
1583 }
1584
1585
1586 /*============================================================================*\
1587 Functions for clean-up and error message handling
1588 \*============================================================================*/
1589
1590 /******************************************************************************
1591 Function `cfgcli_destroy`:
1592 Release memory allocated for the configuration parameters.
1593 Arguments:
1594 * `cfg`: pointer to the entry of all configurations.
1595 ******************************************************************************/
1596 1 void cfgcli_destroy(cfgcli_t *cfg) {
1597
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cfg) return;
1598
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (cfg->npar) free(cfg->params);
1599
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (cfg->nfunc) free(cfg->funcs);
1600 1 cfgcli_error_t *err = cfg->error;
1601
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (err->max) free(err->msg);
1602 1 free(cfg->error);
1603 1 free(cfg);
1604 }
1605
1606 /******************************************************************************
1607 Function `cfgcli_perror`:
1608 Print the error message if there is an error.
1609 Arguments:
1610 * `cfg`: entry of all configurations;
1611 * `fp`: output file stream to write to;
1612 * `msg`: string to be printed before the error message.
1613 ******************************************************************************/
1614 void cfgcli_perror(const cfgcli_t *cfg, FILE *fp, const char *msg) {
1615 if (!cfg || !(CFGCLI_IS_ERROR(cfg))) return;
1616 const cfgcli_error_t *err = (cfgcli_error_t *) cfg->error;
1617 if (err->num <= 0 || !err->msg) return;
1618 const char *sep, *errmsg = err->msg;
1619 for (int i = 0; i < err->num - 1; i++) errmsg += strlen(errmsg) + 1;
1620
1621 if (!msg || *msg == '\0') msg = sep = "";
1622 else sep = " ";
1623 fprintf(fp, "%s%s%s.\n", msg, sep, errmsg);
1624 }
1625
1626 /******************************************************************************
1627 Function `cfgcli_pwarn`:
1628 Print the warning messages if there is any, and clean the warnings.
1629 Arguments:
1630 * `cfg`: entry of all configurations;
1631 * `fp`: output file stream to write to;
1632 * `msg`: string to be printed before the error message.
1633 ******************************************************************************/
1634 4 void cfgcli_pwarn(cfgcli_t *cfg, FILE *fp, const char *msg) {
1635 const char *sep;
1636
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 = "";
1637 4 else sep = " ";
1638
1639
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!cfg) {
1640 fprintf(fp, "%s%sthe interface for configurations is not initialised.\n",
1641 msg, sep);
1642 return;
1643 }
1644 4 cfgcli_error_t *err = (cfgcli_error_t *) cfg->error;
1645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 const int num = (CFGCLI_IS_ERROR(cfg)) ? err->num - 1 : err->num;
1646
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;
1647
1648 char *errmsg = err->msg;
1649 for (int i = 0; i < num; i++) {
1650 fprintf(fp, "%s%s%s.\n", msg, sep, errmsg);
1651 errmsg += strlen(errmsg) + 1;
1652 }
1653
1654 /* Clean the warnings. */
1655 err->num -= num;
1656 err->len -= errmsg - err->msg;
1657 memmove(err->msg, errmsg, err->len);
1658 }
1659