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