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, ¶m[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, ¶m[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 |