]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* SPDX-License-Identifier: BSD-3-Clause |
2 | * Copyright(c) 2010-2014 Intel Corporation | |
3 | */ | |
4 | ||
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <string.h> | |
8 | #include <ctype.h> | |
9 | #include <errno.h> | |
9f95a23c | 10 | #include <rte_string_fns.h> |
11fdf7f2 | 11 | #include <rte_common.h> |
f67539c2 | 12 | #include <rte_log.h> |
11fdf7f2 TL |
13 | |
14 | #include "rte_cfgfile.h" | |
15 | ||
16 | struct rte_cfgfile_section { | |
17 | char name[CFG_NAME_LEN]; | |
18 | int num_entries; | |
19 | int allocated_entries; | |
20 | struct rte_cfgfile_entry *entries; | |
21 | }; | |
22 | ||
23 | struct rte_cfgfile { | |
24 | int flags; | |
25 | int num_sections; | |
26 | int allocated_sections; | |
27 | struct rte_cfgfile_section *sections; | |
28 | }; | |
29 | ||
f67539c2 TL |
30 | static int cfgfile_logtype; |
31 | ||
32 | #define CFG_LOG(level, fmt, args...) \ | |
33 | rte_log(RTE_LOG_ ## level, cfgfile_logtype, "%s(): " fmt "\n", \ | |
34 | __func__, ## args) | |
35 | ||
11fdf7f2 TL |
36 | /** when we resize a file structure, how many extra entries |
37 | * for new sections do we add in */ | |
38 | #define CFG_ALLOC_SECTION_BATCH 8 | |
39 | /** when we resize a section structure, how many extra entries | |
40 | * for new entries do we add in */ | |
41 | #define CFG_ALLOC_ENTRY_BATCH 16 | |
42 | ||
43 | /** | |
44 | * Default cfgfile load parameters. | |
45 | */ | |
46 | static const struct rte_cfgfile_parameters default_cfgfile_params = { | |
47 | .comment_character = CFG_DEFAULT_COMMENT_CHARACTER, | |
48 | }; | |
49 | ||
50 | /** | |
51 | * Defines the list of acceptable comment characters supported by this | |
52 | * library. | |
53 | */ | |
54 | static const char valid_comment_chars[] = { | |
55 | '!', | |
56 | '#', | |
57 | '%', | |
58 | ';', | |
59 | '@' | |
60 | }; | |
61 | ||
62 | static unsigned | |
63 | _strip(char *str, unsigned len) | |
64 | { | |
65 | int newlen = len; | |
66 | if (len == 0) | |
67 | return 0; | |
68 | ||
69 | if (isspace(str[len-1])) { | |
70 | /* strip trailing whitespace */ | |
71 | while (newlen > 0 && isspace(str[newlen - 1])) | |
72 | str[--newlen] = '\0'; | |
73 | } | |
74 | ||
75 | if (isspace(str[0])) { | |
76 | /* strip leading whitespace */ | |
77 | int i, start = 1; | |
78 | while (isspace(str[start]) && start < newlen) | |
79 | start++ | |
80 | ; /* do nothing */ | |
81 | newlen -= start; | |
82 | for (i = 0; i < newlen; i++) | |
83 | str[i] = str[i+start]; | |
84 | str[i] = '\0'; | |
85 | } | |
86 | return newlen; | |
87 | } | |
88 | ||
89 | static struct rte_cfgfile_section * | |
90 | _get_section(struct rte_cfgfile *cfg, const char *sectionname) | |
91 | { | |
92 | int i; | |
93 | ||
94 | for (i = 0; i < cfg->num_sections; i++) { | |
95 | if (strncmp(cfg->sections[i].name, sectionname, | |
96 | sizeof(cfg->sections[0].name)) == 0) | |
97 | return &cfg->sections[i]; | |
98 | } | |
99 | return NULL; | |
100 | } | |
101 | ||
102 | static int | |
103 | _add_entry(struct rte_cfgfile_section *section, const char *entryname, | |
104 | const char *entryvalue) | |
105 | { | |
106 | /* resize entry structure if we don't have room for more entries */ | |
107 | if (section->num_entries == section->allocated_entries) { | |
108 | struct rte_cfgfile_entry *n_entries = realloc( | |
109 | section->entries, | |
110 | sizeof(struct rte_cfgfile_entry) * | |
111 | ((section->allocated_entries) + | |
112 | CFG_ALLOC_ENTRY_BATCH)); | |
113 | ||
114 | if (n_entries == NULL) | |
115 | return -ENOMEM; | |
116 | ||
117 | section->entries = n_entries; | |
118 | section->allocated_entries += CFG_ALLOC_ENTRY_BATCH; | |
119 | } | |
120 | /* fill up entry fields with key name and value */ | |
121 | struct rte_cfgfile_entry *curr_entry = | |
122 | §ion->entries[section->num_entries]; | |
123 | ||
9f95a23c TL |
124 | strlcpy(curr_entry->name, entryname, sizeof(curr_entry->name)); |
125 | strlcpy(curr_entry->value, entryvalue, sizeof(curr_entry->value)); | |
11fdf7f2 TL |
126 | section->num_entries++; |
127 | ||
128 | return 0; | |
129 | } | |
130 | ||
131 | static int | |
132 | rte_cfgfile_check_params(const struct rte_cfgfile_parameters *params) | |
133 | { | |
134 | unsigned int valid_comment; | |
135 | unsigned int i; | |
136 | ||
137 | if (!params) { | |
f67539c2 | 138 | CFG_LOG(ERR, "missing cfgfile parameters\n"); |
11fdf7f2 TL |
139 | return -EINVAL; |
140 | } | |
141 | ||
142 | valid_comment = 0; | |
143 | for (i = 0; i < RTE_DIM(valid_comment_chars); i++) { | |
144 | if (params->comment_character == valid_comment_chars[i]) { | |
145 | valid_comment = 1; | |
146 | break; | |
147 | } | |
148 | } | |
149 | ||
150 | if (valid_comment == 0) { | |
f67539c2 | 151 | CFG_LOG(ERR, "invalid comment characters %c\n", |
11fdf7f2 TL |
152 | params->comment_character); |
153 | return -ENOTSUP; | |
154 | } | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | struct rte_cfgfile * | |
160 | rte_cfgfile_load(const char *filename, int flags) | |
161 | { | |
162 | return rte_cfgfile_load_with_params(filename, flags, | |
163 | &default_cfgfile_params); | |
164 | } | |
165 | ||
166 | struct rte_cfgfile * | |
167 | rte_cfgfile_load_with_params(const char *filename, int flags, | |
168 | const struct rte_cfgfile_parameters *params) | |
169 | { | |
f67539c2 | 170 | char buffer[CFG_NAME_LEN + CFG_VALUE_LEN + 4]; |
11fdf7f2 | 171 | int lineno = 0; |
f67539c2 | 172 | struct rte_cfgfile *cfg; |
11fdf7f2 TL |
173 | |
174 | if (rte_cfgfile_check_params(params)) | |
175 | return NULL; | |
176 | ||
177 | FILE *f = fopen(filename, "r"); | |
178 | if (f == NULL) | |
179 | return NULL; | |
180 | ||
181 | cfg = rte_cfgfile_create(flags); | |
182 | ||
183 | while (fgets(buffer, sizeof(buffer), f) != NULL) { | |
f67539c2 | 184 | char *pos; |
11fdf7f2 TL |
185 | size_t len = strnlen(buffer, sizeof(buffer)); |
186 | lineno++; | |
187 | if ((len >= sizeof(buffer) - 1) && (buffer[len-1] != '\n')) { | |
f67539c2 | 188 | CFG_LOG(ERR, " line %d - no \\n found on string. " |
11fdf7f2 TL |
189 | "Check if line too long\n", lineno); |
190 | goto error1; | |
191 | } | |
192 | /* skip parsing if comment character found */ | |
193 | pos = memchr(buffer, params->comment_character, len); | |
194 | if (pos != NULL && (*(pos-1) != '\\')) { | |
195 | *pos = '\0'; | |
196 | len = pos - buffer; | |
197 | } | |
198 | ||
199 | len = _strip(buffer, len); | |
200 | /* skip lines without useful content */ | |
201 | if (buffer[0] != '[' && memchr(buffer, '=', len) == NULL) | |
202 | continue; | |
203 | ||
204 | if (buffer[0] == '[') { | |
205 | /* section heading line */ | |
206 | char *end = memchr(buffer, ']', len); | |
207 | if (end == NULL) { | |
f67539c2 TL |
208 | CFG_LOG(ERR, |
209 | "line %d - no terminating ']' character found\n", | |
210 | lineno); | |
11fdf7f2 TL |
211 | goto error1; |
212 | } | |
213 | *end = '\0'; | |
214 | _strip(&buffer[1], end - &buffer[1]); | |
215 | ||
216 | rte_cfgfile_add_section(cfg, &buffer[1]); | |
217 | } else { | |
218 | /* key and value line */ | |
219 | char *split[2] = {NULL}; | |
220 | ||
221 | split[0] = buffer; | |
222 | split[1] = memchr(buffer, '=', len); | |
223 | if (split[1] == NULL) { | |
f67539c2 TL |
224 | CFG_LOG(ERR, |
225 | "line %d - no '=' character found\n", | |
226 | lineno); | |
11fdf7f2 TL |
227 | goto error1; |
228 | } | |
229 | *split[1] = '\0'; | |
230 | split[1]++; | |
231 | ||
232 | _strip(split[0], strlen(split[0])); | |
233 | _strip(split[1], strlen(split[1])); | |
234 | char *end = memchr(split[1], '\\', strlen(split[1])); | |
235 | ||
9f95a23c | 236 | size_t split_len = strlen(split[1]) + 1; |
11fdf7f2 TL |
237 | while (end != NULL) { |
238 | if (*(end+1) == params->comment_character) { | |
239 | *end = '\0'; | |
9f95a23c | 240 | strlcat(split[1], end+1, split_len); |
11fdf7f2 TL |
241 | } else |
242 | end++; | |
243 | end = memchr(end, '\\', strlen(end)); | |
244 | } | |
245 | ||
246 | if (!(flags & CFG_FLAG_EMPTY_VALUES) && | |
247 | (*split[1] == '\0')) { | |
f67539c2 TL |
248 | CFG_LOG(ERR, |
249 | "line %d - cannot use empty values\n", | |
250 | lineno); | |
11fdf7f2 TL |
251 | goto error1; |
252 | } | |
253 | ||
254 | if (cfg->num_sections == 0) | |
255 | goto error1; | |
256 | ||
257 | _add_entry(&cfg->sections[cfg->num_sections - 1], | |
258 | split[0], split[1]); | |
259 | } | |
260 | } | |
261 | fclose(f); | |
262 | return cfg; | |
263 | error1: | |
264 | rte_cfgfile_close(cfg); | |
265 | fclose(f); | |
266 | return NULL; | |
267 | } | |
268 | ||
269 | struct rte_cfgfile * | |
270 | rte_cfgfile_create(int flags) | |
271 | { | |
272 | int i; | |
f67539c2 | 273 | struct rte_cfgfile *cfg; |
11fdf7f2 TL |
274 | |
275 | cfg = malloc(sizeof(*cfg)); | |
276 | ||
277 | if (cfg == NULL) | |
278 | return NULL; | |
279 | ||
280 | cfg->flags = flags; | |
281 | cfg->num_sections = 0; | |
282 | ||
283 | /* allocate first batch of sections and entries */ | |
f67539c2 TL |
284 | cfg->sections = calloc(CFG_ALLOC_SECTION_BATCH, |
285 | sizeof(struct rte_cfgfile_section)); | |
11fdf7f2 TL |
286 | if (cfg->sections == NULL) |
287 | goto error1; | |
288 | ||
289 | cfg->allocated_sections = CFG_ALLOC_SECTION_BATCH; | |
290 | ||
291 | for (i = 0; i < CFG_ALLOC_SECTION_BATCH; i++) { | |
f67539c2 TL |
292 | cfg->sections[i].entries = calloc(CFG_ALLOC_ENTRY_BATCH, |
293 | sizeof(struct rte_cfgfile_entry)); | |
11fdf7f2 TL |
294 | |
295 | if (cfg->sections[i].entries == NULL) | |
296 | goto error1; | |
297 | ||
298 | cfg->sections[i].num_entries = 0; | |
299 | cfg->sections[i].allocated_entries = CFG_ALLOC_ENTRY_BATCH; | |
300 | } | |
301 | ||
302 | if (flags & CFG_FLAG_GLOBAL_SECTION) | |
303 | rte_cfgfile_add_section(cfg, "GLOBAL"); | |
304 | ||
305 | return cfg; | |
306 | error1: | |
307 | if (cfg->sections != NULL) { | |
308 | for (i = 0; i < cfg->allocated_sections; i++) { | |
309 | if (cfg->sections[i].entries != NULL) { | |
310 | free(cfg->sections[i].entries); | |
311 | cfg->sections[i].entries = NULL; | |
312 | } | |
313 | } | |
314 | free(cfg->sections); | |
315 | cfg->sections = NULL; | |
316 | } | |
317 | free(cfg); | |
318 | return NULL; | |
319 | } | |
320 | ||
321 | int | |
322 | rte_cfgfile_add_section(struct rte_cfgfile *cfg, const char *sectionname) | |
323 | { | |
324 | int i; | |
325 | ||
326 | if (cfg == NULL) | |
327 | return -EINVAL; | |
328 | ||
329 | if (sectionname == NULL) | |
330 | return -EINVAL; | |
331 | ||
332 | /* resize overall struct if we don't have room for more sections */ | |
333 | if (cfg->num_sections == cfg->allocated_sections) { | |
334 | ||
335 | struct rte_cfgfile_section *n_sections = | |
336 | realloc(cfg->sections, | |
337 | sizeof(struct rte_cfgfile_section) * | |
338 | ((cfg->allocated_sections) + | |
339 | CFG_ALLOC_SECTION_BATCH)); | |
340 | ||
341 | if (n_sections == NULL) | |
342 | return -ENOMEM; | |
343 | ||
344 | for (i = 0; i < CFG_ALLOC_SECTION_BATCH; i++) { | |
345 | n_sections[i + cfg->allocated_sections].num_entries = 0; | |
346 | n_sections[i + | |
347 | cfg->allocated_sections].allocated_entries = 0; | |
348 | n_sections[i + cfg->allocated_sections].entries = NULL; | |
349 | } | |
350 | cfg->sections = n_sections; | |
351 | cfg->allocated_sections += CFG_ALLOC_SECTION_BATCH; | |
352 | } | |
353 | ||
9f95a23c TL |
354 | strlcpy(cfg->sections[cfg->num_sections].name, sectionname, |
355 | sizeof(cfg->sections[0].name)); | |
11fdf7f2 TL |
356 | cfg->sections[cfg->num_sections].num_entries = 0; |
357 | cfg->num_sections++; | |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | int rte_cfgfile_add_entry(struct rte_cfgfile *cfg, | |
363 | const char *sectionname, const char *entryname, | |
364 | const char *entryvalue) | |
365 | { | |
366 | int ret; | |
367 | ||
368 | if ((cfg == NULL) || (sectionname == NULL) || (entryname == NULL) | |
369 | || (entryvalue == NULL)) | |
370 | return -EINVAL; | |
371 | ||
372 | if (rte_cfgfile_has_entry(cfg, sectionname, entryname) != 0) | |
373 | return -EEXIST; | |
374 | ||
375 | /* search for section pointer by sectionname */ | |
376 | struct rte_cfgfile_section *curr_section = _get_section(cfg, | |
377 | sectionname); | |
378 | if (curr_section == NULL) | |
379 | return -EINVAL; | |
380 | ||
381 | ret = _add_entry(curr_section, entryname, entryvalue); | |
382 | ||
383 | return ret; | |
384 | } | |
385 | ||
386 | int rte_cfgfile_set_entry(struct rte_cfgfile *cfg, const char *sectionname, | |
387 | const char *entryname, const char *entryvalue) | |
388 | { | |
389 | int i; | |
390 | ||
391 | if ((cfg == NULL) || (sectionname == NULL) || (entryname == NULL)) | |
392 | return -EINVAL; | |
393 | ||
394 | /* search for section pointer by sectionname */ | |
395 | struct rte_cfgfile_section *curr_section = _get_section(cfg, | |
396 | sectionname); | |
397 | if (curr_section == NULL) | |
398 | return -EINVAL; | |
399 | ||
400 | if (entryvalue == NULL) | |
401 | entryvalue = ""; | |
402 | ||
403 | for (i = 0; i < curr_section->num_entries; i++) | |
404 | if (!strcmp(curr_section->entries[i].name, entryname)) { | |
9f95a23c TL |
405 | strlcpy(curr_section->entries[i].value, entryvalue, |
406 | sizeof(curr_section->entries[i].value)); | |
11fdf7f2 TL |
407 | return 0; |
408 | } | |
f67539c2 TL |
409 | |
410 | CFG_LOG(ERR, "entry name doesn't exist\n"); | |
11fdf7f2 TL |
411 | return -EINVAL; |
412 | } | |
413 | ||
414 | int rte_cfgfile_save(struct rte_cfgfile *cfg, const char *filename) | |
415 | { | |
416 | int i, j; | |
417 | ||
418 | if ((cfg == NULL) || (filename == NULL)) | |
419 | return -EINVAL; | |
420 | ||
421 | FILE *f = fopen(filename, "w"); | |
422 | ||
423 | if (f == NULL) | |
424 | return -EINVAL; | |
425 | ||
426 | for (i = 0; i < cfg->num_sections; i++) { | |
427 | fprintf(f, "[%s]\n", cfg->sections[i].name); | |
428 | ||
429 | for (j = 0; j < cfg->sections[i].num_entries; j++) { | |
430 | fprintf(f, "%s=%s\n", | |
431 | cfg->sections[i].entries[j].name, | |
432 | cfg->sections[i].entries[j].value); | |
433 | } | |
434 | } | |
435 | return fclose(f); | |
436 | } | |
437 | ||
438 | int rte_cfgfile_close(struct rte_cfgfile *cfg) | |
439 | { | |
440 | int i; | |
441 | ||
442 | if (cfg == NULL) | |
443 | return -1; | |
444 | ||
445 | if (cfg->sections != NULL) { | |
446 | for (i = 0; i < cfg->allocated_sections; i++) { | |
447 | if (cfg->sections[i].entries != NULL) { | |
448 | free(cfg->sections[i].entries); | |
449 | cfg->sections[i].entries = NULL; | |
450 | } | |
451 | } | |
452 | free(cfg->sections); | |
453 | cfg->sections = NULL; | |
454 | } | |
455 | free(cfg); | |
456 | cfg = NULL; | |
457 | ||
458 | return 0; | |
459 | } | |
460 | ||
461 | int | |
462 | rte_cfgfile_num_sections(struct rte_cfgfile *cfg, const char *sectionname, | |
463 | size_t length) | |
464 | { | |
465 | int i; | |
466 | int num_sections = 0; | |
467 | for (i = 0; i < cfg->num_sections; i++) { | |
468 | if (strncmp(cfg->sections[i].name, sectionname, length) == 0) | |
469 | num_sections++; | |
470 | } | |
471 | return num_sections; | |
472 | } | |
473 | ||
474 | int | |
475 | rte_cfgfile_sections(struct rte_cfgfile *cfg, char *sections[], | |
476 | int max_sections) | |
477 | { | |
478 | int i; | |
479 | ||
480 | for (i = 0; i < cfg->num_sections && i < max_sections; i++) | |
9f95a23c | 481 | strlcpy(sections[i], cfg->sections[i].name, CFG_NAME_LEN); |
11fdf7f2 TL |
482 | |
483 | return i; | |
484 | } | |
485 | ||
486 | int | |
487 | rte_cfgfile_has_section(struct rte_cfgfile *cfg, const char *sectionname) | |
488 | { | |
489 | return _get_section(cfg, sectionname) != NULL; | |
490 | } | |
491 | ||
492 | int | |
493 | rte_cfgfile_section_num_entries(struct rte_cfgfile *cfg, | |
494 | const char *sectionname) | |
495 | { | |
496 | const struct rte_cfgfile_section *s = _get_section(cfg, sectionname); | |
497 | if (s == NULL) | |
498 | return -1; | |
499 | return s->num_entries; | |
500 | } | |
501 | ||
502 | int | |
503 | rte_cfgfile_section_num_entries_by_index(struct rte_cfgfile *cfg, | |
504 | char *sectionname, int index) | |
505 | { | |
506 | if (index < 0 || index >= cfg->num_sections) | |
507 | return -1; | |
508 | ||
509 | const struct rte_cfgfile_section *sect = &(cfg->sections[index]); | |
510 | ||
9f95a23c | 511 | strlcpy(sectionname, sect->name, CFG_NAME_LEN); |
11fdf7f2 TL |
512 | return sect->num_entries; |
513 | } | |
514 | int | |
515 | rte_cfgfile_section_entries(struct rte_cfgfile *cfg, const char *sectionname, | |
516 | struct rte_cfgfile_entry *entries, int max_entries) | |
517 | { | |
518 | int i; | |
519 | const struct rte_cfgfile_section *sect = _get_section(cfg, sectionname); | |
520 | if (sect == NULL) | |
521 | return -1; | |
522 | for (i = 0; i < max_entries && i < sect->num_entries; i++) | |
523 | entries[i] = sect->entries[i]; | |
524 | return i; | |
525 | } | |
526 | ||
527 | int | |
528 | rte_cfgfile_section_entries_by_index(struct rte_cfgfile *cfg, int index, | |
529 | char *sectionname, | |
530 | struct rte_cfgfile_entry *entries, int max_entries) | |
531 | { | |
532 | int i; | |
533 | const struct rte_cfgfile_section *sect; | |
534 | ||
535 | if (index < 0 || index >= cfg->num_sections) | |
536 | return -1; | |
537 | sect = &cfg->sections[index]; | |
9f95a23c | 538 | strlcpy(sectionname, sect->name, CFG_NAME_LEN); |
11fdf7f2 TL |
539 | for (i = 0; i < max_entries && i < sect->num_entries; i++) |
540 | entries[i] = sect->entries[i]; | |
541 | return i; | |
542 | } | |
543 | ||
544 | const char * | |
545 | rte_cfgfile_get_entry(struct rte_cfgfile *cfg, const char *sectionname, | |
546 | const char *entryname) | |
547 | { | |
548 | int i; | |
549 | const struct rte_cfgfile_section *sect = _get_section(cfg, sectionname); | |
550 | if (sect == NULL) | |
551 | return NULL; | |
552 | for (i = 0; i < sect->num_entries; i++) | |
553 | if (strncmp(sect->entries[i].name, entryname, CFG_NAME_LEN) | |
554 | == 0) | |
555 | return sect->entries[i].value; | |
556 | return NULL; | |
557 | } | |
558 | ||
559 | int | |
560 | rte_cfgfile_has_entry(struct rte_cfgfile *cfg, const char *sectionname, | |
561 | const char *entryname) | |
562 | { | |
563 | return rte_cfgfile_get_entry(cfg, sectionname, entryname) != NULL; | |
564 | } | |
f67539c2 TL |
565 | |
566 | RTE_INIT(cfgfile_init) | |
567 | { | |
568 | cfgfile_logtype = rte_log_register("lib.cfgfile"); | |
569 | if (cfgfile_logtype >= 0) | |
570 | rte_log_set_level(cfgfile_logtype, RTE_LOG_INFO); | |
571 | } |