Commit | Line | Data |
---|---|---|
3719602c PC |
1 | /* Copyright (C) 2010-2020 The RetroArch team |
2 | * | |
3 | * --------------------------------------------------------------------------------------- | |
4 | * The following license statement only applies to this file (config_file.c). | |
5 | * --------------------------------------------------------------------------------------- | |
6 | * | |
7 | * Permission is hereby granted, free of charge, | |
8 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), | |
9 | * to deal in the Software without restriction, including without limitation the rights to | |
10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, | |
11 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | */ | |
22 | ||
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | #include <stdio.h> | |
26 | #include <ctype.h> | |
27 | #include <errno.h> | |
28 | ||
29 | #include <retro_miscellaneous.h> | |
30 | #include <compat/strl.h> | |
31 | #include <compat/posix_string.h> | |
32 | #include <compat/fopen_utf8.h> | |
33 | #include <compat/msvc.h> | |
34 | #include <file/config_file.h> | |
35 | #include <file/file_path.h> | |
36 | #include <string/stdstring.h> | |
37 | #include <streams/file_stream.h> | |
38 | #include <array/rhmap.h> | |
39 | ||
40 | #define MAX_INCLUDE_DEPTH 16 | |
41 | ||
42 | struct config_include_list | |
43 | { | |
44 | char *path; | |
45 | struct config_include_list *next; | |
46 | }; | |
47 | ||
48 | /* Forward declaration */ | |
49 | static bool config_file_parse_line(config_file_t *conf, | |
50 | struct config_entry_list *list, char *line, config_file_cb_t *cb); | |
51 | ||
52 | static int config_file_sort_compare_func(struct config_entry_list *a, | |
53 | struct config_entry_list *b) | |
54 | { | |
55 | if (a && b) | |
56 | { | |
57 | if (a->key) | |
58 | { | |
59 | if (b->key) | |
60 | return strcasecmp(a->key, b->key); | |
61 | return 1; | |
62 | } | |
63 | else if (b->key) | |
64 | return -1; | |
65 | } | |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
70 | /* https://stackoverflow.com/questions/7685/merge-sort-a-linked-list */ | |
71 | static struct config_entry_list* config_file_merge_sort_linked_list( | |
72 | struct config_entry_list *list, int (*compare)( | |
73 | struct config_entry_list *one,struct config_entry_list *two)) | |
74 | { | |
75 | struct config_entry_list | |
76 | *right = list, | |
77 | *temp = list, | |
78 | *last = list, | |
79 | *result = 0, | |
80 | *next = 0, | |
81 | *tail = 0; | |
82 | ||
83 | /* Trivial case. */ | |
84 | if (!list || !list->next) | |
85 | return list; | |
86 | ||
87 | /* Find halfway through the list (by running two pointers, | |
88 | * one at twice the speed of the other). */ | |
89 | while (temp && temp->next) | |
90 | { | |
91 | last = right; | |
92 | right = right->next; | |
93 | temp = temp->next->next; | |
94 | } | |
95 | ||
96 | /* Break the list in two. (prev pointers are broken here, | |
97 | * but we fix later) */ | |
98 | last->next = 0; | |
99 | ||
100 | /* Recurse on the two smaller lists: */ | |
101 | list = config_file_merge_sort_linked_list(list, compare); | |
102 | right = config_file_merge_sort_linked_list(right, compare); | |
103 | ||
104 | /* Merge: */ | |
105 | while (list || right) | |
106 | { | |
107 | /* Take from empty lists, or compare: */ | |
108 | if (!right) | |
109 | { | |
110 | next = list; | |
111 | list = list->next; | |
112 | } | |
113 | else if (!list) | |
114 | { | |
115 | next = right; | |
116 | right = right->next; | |
117 | } | |
118 | else if (compare(list, right) < 0) | |
119 | { | |
120 | next = list; | |
121 | list = list->next; | |
122 | } | |
123 | else | |
124 | { | |
125 | next = right; | |
126 | right = right->next; | |
127 | } | |
128 | ||
129 | if (!result) | |
130 | result = next; | |
131 | else | |
132 | tail->next = next; | |
133 | ||
134 | tail = next; | |
135 | } | |
136 | ||
137 | return result; | |
138 | } | |
139 | ||
140 | /** | |
141 | * config_file_strip_comment: | |
142 | * | |
143 | * Searches input string for a comment ('#') entry | |
144 | * > If first character is '#', then entire line is | |
145 | * a comment and may correspond to a directive | |
146 | * (command action - e.g. include sub-config file). | |
147 | * In this case, 'str' is set to NUL and the comment | |
148 | * itself (everything after the '#' character) is | |
149 | * returned | |
150 | * > If a '#' character is found inside a string literal | |
151 | * value, then it does not correspond to a comment and | |
152 | * is ignored. In this case, 'str' is left untouched | |
153 | * and NULL is returned | |
154 | * > If a '#' character is found anywhere else, then the | |
155 | * comment text is a suffix of the input string and | |
156 | * has no programmatic value. In this case, the comment | |
157 | * is removed from the end of 'str' and NULL is returned | |
158 | **/ | |
159 | static char *config_file_strip_comment(char *str) | |
160 | { | |
161 | /* Search for a comment (#) character */ | |
162 | char *comment = strchr(str, '#'); | |
163 | ||
164 | if (comment) | |
165 | { | |
166 | char *literal_start = NULL; | |
167 | ||
168 | /* Check whether entire line is a comment | |
169 | * > First character == '#' */ | |
170 | if (str == comment) | |
171 | { | |
172 | /* Set 'str' to NUL and return comment | |
173 | * for processing at a higher level */ | |
174 | *str = '\0'; | |
175 | return ++comment; | |
176 | } | |
177 | ||
178 | /* Comment character occurs at an offset: | |
179 | * Search for the start of a string literal value */ | |
180 | literal_start = strchr(str, '\"'); | |
181 | ||
182 | /* Check whether string literal start occurs | |
183 | * *before* the comment character */ | |
184 | if (literal_start && (literal_start < comment)) | |
185 | { | |
186 | /* Search for the end of the string literal | |
187 | * value */ | |
188 | char *literal_end = strchr(literal_start + 1, '\"'); | |
189 | ||
190 | /* Check whether string literal end occurs | |
191 | * *after* the comment character | |
192 | * > If this is the case, ignore the comment | |
193 | * > Leave 'str' untouched and return NULL */ | |
194 | if (literal_end && (literal_end > comment)) | |
195 | return NULL; | |
196 | } | |
197 | ||
198 | /* If we reach this point, then a comment | |
199 | * exists outside of a string literal | |
200 | * > Trim the entire comment from the end | |
201 | * of 'str' */ | |
202 | *comment = '\0'; | |
203 | } | |
204 | ||
205 | return NULL; | |
206 | } | |
207 | ||
208 | static char *config_file_extract_value(char *line) | |
209 | { | |
210 | char *dst = NULL; | |
211 | while (ISSPACE((int)*line)) | |
212 | line++; | |
213 | ||
214 | /* Note: From this point on, an empty value | |
215 | * string is valid - and in this case, strldup("", sizeof("")) | |
216 | * will be returned (see Note 2) | |
217 | * > If we instead return NULL, the the entry | |
218 | * is ignored completely - which means we cannot | |
219 | * track *changes* in entry value */ | |
220 | ||
221 | /* If first character is ("), we have a full string | |
222 | * literal */ | |
223 | if (*line == '"') | |
224 | { | |
225 | size_t idx = 0; | |
226 | char *value = NULL; | |
227 | /* Skip to next character */ | |
228 | line++; | |
229 | ||
230 | /* If this a ("), then value string is empty */ | |
231 | if (*line != '"') | |
232 | { | |
233 | /* Find the next (") character */ | |
234 | while (line[idx] && (line[idx] != '\"')) | |
235 | idx++; | |
236 | ||
237 | line[idx] = '\0'; | |
238 | if ((value = line) && *value) | |
239 | return strdup(value); | |
240 | } | |
241 | } | |
242 | /* This is not a string literal - just read | |
243 | * until the next space is found | |
244 | * > Note: Skip this if line is empty */ | |
245 | else if (*line != '\0') | |
246 | { | |
247 | size_t idx = 0; | |
248 | char *value = NULL; | |
249 | /* Find next space character */ | |
250 | while (line[idx] && isgraph((int)line[idx])) | |
251 | idx++; | |
252 | ||
253 | line[idx] = '\0'; | |
254 | if ((value = line) && *value) | |
255 | return strdup(value); | |
256 | } | |
257 | ||
258 | /* Note 2: This is an unrolled strldup call | |
259 | * to avoid an unnecessary dependency - | |
260 | * call is strldup("", sizeof("")) | |
261 | **/ | |
262 | dst = (char*)malloc(sizeof(char) * 2); | |
263 | strlcpy(dst, "", 1); | |
264 | return dst; | |
265 | } | |
266 | ||
267 | /* Move semantics? */ | |
268 | static void config_file_add_child_list(config_file_t *parent, | |
269 | config_file_t *child) | |
270 | { | |
271 | struct config_entry_list *list = child->entries; | |
272 | bool merge_hash_map = false; | |
273 | ||
274 | if (parent->entries) | |
275 | { | |
276 | struct config_entry_list *head = parent->entries; | |
277 | while (head->next) | |
278 | head = head->next; | |
279 | ||
280 | /* set list readonly */ | |
281 | while (list) | |
282 | { | |
283 | list->readonly = true; | |
284 | list = list->next; | |
285 | } | |
286 | head->next = child->entries; | |
287 | ||
288 | merge_hash_map = true; | |
289 | } | |
290 | else | |
291 | { | |
292 | /* set list readonly */ | |
293 | while (list) | |
294 | { | |
295 | list->readonly = true; | |
296 | list = list->next; | |
297 | } | |
298 | parent->entries = child->entries; | |
299 | } | |
300 | ||
301 | /* Rebase tail. */ | |
302 | if (parent->entries) | |
303 | { | |
304 | struct config_entry_list *head = | |
305 | (struct config_entry_list*)parent->entries; | |
306 | ||
307 | while (head->next) | |
308 | head = head->next; | |
309 | parent->tail = head; | |
310 | } | |
311 | else | |
312 | parent->tail = NULL; | |
313 | ||
314 | /* Update hash map */ | |
315 | if (merge_hash_map) | |
316 | { | |
317 | size_t i; | |
318 | size_t cap; | |
319 | ||
320 | /* We are merging two lists - if any child entry | |
321 | * (key) is not present in the parent list, add it | |
322 | * to the parent hash map */ | |
323 | for (i = 0, cap = RHMAP_CAP(child->entries_map); i != cap; i++) | |
324 | { | |
325 | uint32_t child_hash = RHMAP_KEY(child->entries_map, i); | |
326 | const char *child_key = RHMAP_KEY_STR(child->entries_map, i); | |
327 | ||
328 | if (child_hash && | |
329 | child_key && | |
330 | !RHMAP_HAS_FULL(parent->entries_map, child_hash, child_key)) | |
331 | { | |
332 | struct config_entry_list *entry = child->entries_map[i]; | |
333 | ||
334 | if (entry) | |
335 | RHMAP_SET_FULL(parent->entries_map, child_hash, child_key, entry); | |
336 | } | |
337 | } | |
338 | ||
339 | /* Child entries map is no longer required, | |
340 | * so free it now */ | |
341 | RHMAP_FREE(child->entries_map); | |
342 | } | |
343 | else | |
344 | { | |
345 | /* If parent list was originally empty, | |
346 | * take map from child list */ | |
347 | RHMAP_FREE(parent->entries_map); | |
348 | parent->entries_map = child->entries_map; | |
349 | child->entries_map = NULL; | |
350 | } | |
351 | ||
352 | child->entries = NULL; | |
353 | } | |
354 | ||
355 | static void config_file_get_realpath(char *s, size_t len, | |
356 | char *path, const char *config_path) | |
357 | { | |
358 | #ifdef _WIN32 | |
359 | if (!string_is_empty(config_path)) | |
360 | fill_pathname_resolve_relative(s, config_path, | |
361 | path, len); | |
362 | #else | |
363 | #if !defined(__PSL1GHT__) && !defined(__PS3__) | |
364 | if (*path == '~') | |
365 | { | |
366 | const char *home = getenv("HOME"); | |
367 | if (home) | |
368 | { | |
369 | strlcpy(s, home, len); | |
370 | strlcat(s, path + 1, len); | |
371 | } | |
372 | else | |
373 | strlcpy(s, path + 1, len); | |
374 | } | |
375 | else | |
376 | #endif | |
377 | if (!string_is_empty(config_path)) | |
378 | fill_pathname_resolve_relative(s, config_path, path, len); | |
379 | #endif | |
380 | } | |
381 | ||
382 | static void config_file_add_sub_conf(config_file_t *conf, char *path, | |
383 | char *real_path, size_t len, config_file_cb_t *cb) | |
384 | { | |
385 | struct config_include_list *head = conf->includes; | |
386 | struct config_include_list *node = (struct config_include_list*) | |
387 | malloc(sizeof(*node)); | |
388 | ||
389 | if (node) | |
390 | { | |
391 | node->next = NULL; | |
392 | /* Add include list */ | |
393 | node->path = strdup(path); | |
394 | ||
395 | if (head) | |
396 | { | |
397 | while (head->next) | |
398 | head = head->next; | |
399 | ||
400 | head->next = node; | |
401 | } | |
402 | else | |
403 | conf->includes = node; | |
404 | } | |
405 | ||
406 | config_file_get_realpath(real_path, len, path, | |
407 | conf->path); | |
408 | } | |
409 | ||
410 | void config_file_add_reference(config_file_t *conf, char *path) | |
411 | { | |
412 | /* It is expected that the conf has it's path already set */ | |
413 | char short_path[PATH_MAX_LENGTH]; | |
414 | if (!conf->references) | |
415 | conf->references = path_linked_list_new(); | |
416 | fill_pathname_abbreviated_or_relative(short_path, conf->path, path, sizeof(short_path)); | |
417 | path_linked_list_add_path(conf->references, short_path); | |
418 | } | |
419 | ||
420 | static int config_file_load_internal( | |
421 | struct config_file *conf, | |
422 | const char *path, unsigned depth, config_file_cb_t *cb) | |
423 | { | |
424 | RFILE *file = NULL; | |
425 | char *new_path = strdup(path); | |
426 | if (!new_path) | |
427 | return 1; | |
428 | ||
429 | conf->path = new_path; | |
430 | conf->include_depth = depth; | |
431 | ||
432 | if (!(file = filestream_open(path, | |
433 | RETRO_VFS_FILE_ACCESS_READ, | |
434 | RETRO_VFS_FILE_ACCESS_HINT_NONE))) | |
435 | { | |
436 | free(conf->path); | |
437 | return 1; | |
438 | } | |
439 | ||
440 | while (!filestream_eof(file)) | |
441 | { | |
442 | char *line = NULL; | |
443 | struct config_entry_list *list = (struct config_entry_list*) | |
444 | malloc(sizeof(*list)); | |
445 | ||
446 | if (!list) | |
447 | { | |
448 | filestream_close(file); | |
449 | return -1; | |
450 | } | |
451 | ||
452 | list->readonly = false; | |
453 | list->key = NULL; | |
454 | list->value = NULL; | |
455 | list->next = NULL; | |
456 | ||
457 | line = filestream_getline(file); | |
458 | ||
459 | if (!line) | |
460 | { | |
461 | free(list); | |
462 | continue; | |
463 | } | |
464 | ||
465 | if ( | |
466 | !string_is_empty(line) | |
467 | && config_file_parse_line(conf, list, line, cb)) | |
468 | { | |
469 | if (conf->entries) | |
470 | conf->tail->next = list; | |
471 | else | |
472 | conf->entries = list; | |
473 | ||
474 | conf->tail = list; | |
475 | ||
476 | if (list->key) | |
477 | { | |
478 | /* Only add entry to the map if an entry | |
479 | * with the specified value does not | |
480 | * already exist */ | |
481 | uint32_t hash = rhmap_hash_string(list->key); | |
482 | ||
483 | if (!RHMAP_HAS_FULL(conf->entries_map, hash, list->key)) | |
484 | { | |
485 | RHMAP_SET_FULL(conf->entries_map, hash, list->key, list); | |
486 | ||
487 | if (cb && list->value) | |
488 | cb->config_file_new_entry_cb(list->key, list->value); | |
489 | } | |
490 | } | |
491 | } | |
492 | ||
493 | free(line); | |
494 | ||
495 | if (list != conf->tail) | |
496 | free(list); | |
497 | } | |
498 | ||
499 | filestream_close(file); | |
500 | ||
501 | return 0; | |
502 | } | |
503 | ||
504 | static bool config_file_parse_line(config_file_t *conf, | |
505 | struct config_entry_list *list, char *line, config_file_cb_t *cb) | |
506 | { | |
507 | size_t cur_size = 32; | |
508 | size_t idx = 0; | |
509 | char *key = NULL; | |
510 | char *key_tmp = NULL; | |
511 | /* Remove any comment text */ | |
512 | char *comment = config_file_strip_comment(line); | |
513 | ||
514 | /* Check whether entire line is a comment */ | |
515 | if (comment) | |
516 | { | |
517 | char *path = NULL; | |
518 | bool include_found = string_starts_with_size(comment, | |
519 | "include ", | |
520 | STRLEN_CONST("include ")); | |
521 | bool reference_found = string_starts_with_size(comment, | |
522 | "reference ", | |
523 | STRLEN_CONST("reference ")); | |
524 | ||
525 | /* All comments except those starting with the include or | |
526 | * reference directive are ignored */ | |
527 | if (!include_found && !reference_found) | |
528 | return false; | |
529 | ||
530 | /* Starting a line with an 'include' directive | |
531 | * appends a sub-config file */ | |
532 | if (include_found) | |
533 | { | |
534 | config_file_t sub_conf; | |
535 | char real_path[PATH_MAX_LENGTH]; | |
536 | char *include_line = comment + STRLEN_CONST("include "); | |
537 | ||
538 | if (string_is_empty(include_line)) | |
539 | return false; | |
540 | ||
541 | if (!(path = config_file_extract_value(include_line))) | |
542 | return false; | |
543 | ||
544 | if ( string_is_empty(path) | |
545 | || conf->include_depth >= MAX_INCLUDE_DEPTH) | |
546 | { | |
547 | free(path); | |
548 | return false; | |
549 | } | |
550 | ||
551 | config_file_add_sub_conf(conf, path, | |
552 | real_path, sizeof(real_path), cb); | |
553 | ||
554 | config_file_initialize(&sub_conf); | |
555 | ||
556 | switch (config_file_load_internal(&sub_conf, real_path, | |
557 | conf->include_depth + 1, cb)) | |
558 | { | |
559 | case 0: | |
560 | /* Pilfer internal list. */ | |
561 | config_file_add_child_list(conf, &sub_conf); | |
562 | /* fall-through to deinitialize */ | |
563 | case -1: | |
564 | config_file_deinitialize(&sub_conf); | |
565 | break; | |
566 | case 1: | |
567 | default: | |
568 | break; | |
569 | } | |
570 | } | |
571 | ||
572 | /* Starting a line with an 'reference' directive | |
573 | * sets the reference path */ | |
574 | if (reference_found) | |
575 | { | |
576 | char *reference_line = comment + STRLEN_CONST("reference "); | |
577 | ||
578 | if (string_is_empty(reference_line)) | |
579 | return false; | |
580 | ||
581 | if (!(path = config_file_extract_value(reference_line))) | |
582 | return false; | |
583 | ||
584 | config_file_add_reference(conf, path); | |
585 | ||
586 | if (!path) | |
587 | return false; | |
588 | } | |
589 | ||
590 | free(path); | |
591 | return true; | |
592 | } | |
593 | ||
594 | /* Skip to first non-space character */ | |
595 | while (ISSPACE((int)*line)) | |
596 | line++; | |
597 | ||
598 | /* Allocate storage for key */ | |
599 | if (!(key = (char*)malloc(cur_size + 1))) | |
600 | return false; | |
601 | ||
602 | /* Copy line contents into key until we | |
603 | * reach the next space character */ | |
604 | while (isgraph((int)*line)) | |
605 | { | |
606 | /* If current key storage is too small, | |
607 | * double its size */ | |
608 | if (idx == cur_size) | |
609 | { | |
610 | cur_size *= 2; | |
611 | if (!(key_tmp = (char*)realloc(key, cur_size + 1))) | |
612 | { | |
613 | free(key); | |
614 | return false; | |
615 | } | |
616 | ||
617 | key = key_tmp; | |
618 | } | |
619 | ||
620 | key[idx++] = *line++; | |
621 | } | |
622 | key[idx] = '\0'; | |
623 | ||
624 | /* Add key and value entries to list */ | |
625 | list->key = key; | |
626 | ||
627 | /* An entry without a value is invalid */ | |
628 | while (ISSPACE((int)*line)) | |
629 | line++; | |
630 | ||
631 | /* If we don't have an equal sign here, | |
632 | * we've got an invalid string. */ | |
633 | if (*line != '=') | |
634 | { | |
635 | list->value = NULL; | |
636 | goto error; | |
637 | } | |
638 | ||
639 | line++; | |
640 | ||
641 | if (!(list->value = config_file_extract_value(line))) | |
642 | goto error; | |
643 | ||
644 | return true; | |
645 | ||
646 | error: | |
647 | list->key = NULL; | |
648 | free(key); | |
649 | return false; | |
650 | } | |
651 | ||
652 | static int config_file_from_string_internal( | |
653 | struct config_file *conf, | |
654 | char *from_string, | |
655 | const char *path) | |
656 | { | |
657 | char *lines = from_string; | |
658 | char *save_ptr = NULL; | |
659 | char *line = NULL; | |
660 | ||
661 | if (!string_is_empty(path)) | |
662 | conf->path = strdup(path); | |
663 | if (string_is_empty(lines)) | |
664 | return 0; | |
665 | ||
666 | /* Get first line of config file */ | |
667 | line = strtok_r(lines, "\n", &save_ptr); | |
668 | ||
669 | while (line) | |
670 | { | |
671 | struct config_entry_list *list = (struct config_entry_list*) | |
672 | malloc(sizeof(*list)); | |
673 | ||
674 | if (!list) | |
675 | return -1; | |
676 | ||
677 | list->readonly = false; | |
678 | list->key = NULL; | |
679 | list->value = NULL; | |
680 | list->next = NULL; | |
681 | ||
682 | /* Parse current line */ | |
683 | if ( | |
684 | !string_is_empty(line) | |
685 | && config_file_parse_line(conf, list, line, NULL)) | |
686 | { | |
687 | if (conf->entries) | |
688 | conf->tail->next = list; | |
689 | else | |
690 | conf->entries = list; | |
691 | ||
692 | conf->tail = list; | |
693 | ||
694 | if (list->key) | |
695 | { | |
696 | /* Only add entry to the map if an entry | |
697 | * with the specified value does not | |
698 | * already exist */ | |
699 | uint32_t hash = rhmap_hash_string(list->key); | |
700 | if (!RHMAP_HAS_FULL(conf->entries_map, hash, list->key)) | |
701 | RHMAP_SET_FULL(conf->entries_map, hash, list->key, list); | |
702 | } | |
703 | } | |
704 | ||
705 | if (list != conf->tail) | |
706 | free(list); | |
707 | ||
708 | /* Get next line of config file */ | |
709 | line = strtok_r(NULL, "\n", &save_ptr); | |
710 | } | |
711 | ||
712 | return 0; | |
713 | } | |
714 | ||
715 | ||
716 | bool config_file_deinitialize(config_file_t *conf) | |
717 | { | |
718 | struct config_include_list *inc_tmp = NULL; | |
719 | struct config_entry_list *tmp = NULL; | |
720 | ||
721 | if (!conf) | |
722 | return false; | |
723 | ||
724 | tmp = conf->entries; | |
725 | while (tmp) | |
726 | { | |
727 | struct config_entry_list *hold = NULL; | |
728 | if (tmp->key) | |
729 | free(tmp->key); | |
730 | if (tmp->value) | |
731 | free(tmp->value); | |
732 | ||
733 | tmp->value = NULL; | |
734 | tmp->key = NULL; | |
735 | ||
736 | hold = tmp; | |
737 | tmp = tmp->next; | |
738 | ||
739 | if (hold) | |
740 | free(hold); | |
741 | } | |
742 | ||
743 | inc_tmp = (struct config_include_list*)conf->includes; | |
744 | while (inc_tmp) | |
745 | { | |
746 | struct config_include_list *hold = NULL; | |
747 | if (inc_tmp->path) | |
748 | free(inc_tmp->path); | |
749 | hold = (struct config_include_list*)inc_tmp; | |
750 | inc_tmp = inc_tmp->next; | |
751 | if (hold) | |
752 | free(hold); | |
753 | } | |
754 | ||
755 | path_linked_list_free(conf->references); | |
756 | ||
757 | if (conf->path) | |
758 | free(conf->path); | |
759 | ||
760 | RHMAP_FREE(conf->entries_map); | |
761 | ||
762 | return true; | |
763 | } | |
764 | ||
765 | /** | |
766 | * config_file_free: | |
767 | * | |
768 | * Frees config file. | |
769 | **/ | |
770 | void config_file_free(config_file_t *conf) | |
771 | { | |
772 | if (config_file_deinitialize(conf)) | |
773 | free(conf); | |
774 | } | |
775 | ||
776 | /** | |
777 | * config_append_file: | |
778 | * | |
779 | * Loads a new config, and appends its data to @conf. | |
780 | * The key-value pairs of the new config file takes priority over the old. | |
781 | **/ | |
782 | bool config_append_file(config_file_t *conf, const char *path) | |
783 | { | |
784 | size_t i, cap; | |
785 | config_file_t *new_conf = config_file_new_from_path_to_string(path); | |
786 | ||
787 | if (!new_conf) | |
788 | return false; | |
789 | ||
790 | /* Update hash map */ | |
791 | for (i = 0, cap = RHMAP_CAP(new_conf->entries_map); i != cap; i++) | |
792 | { | |
793 | uint32_t new_hash = RHMAP_KEY(new_conf->entries_map, i); | |
794 | const char *new_key = RHMAP_KEY_STR(new_conf->entries_map, i); | |
795 | ||
796 | if (new_hash && new_key) | |
797 | { | |
798 | struct config_entry_list *entry = new_conf->entries_map[i]; | |
799 | ||
800 | if (entry) | |
801 | RHMAP_SET_FULL(conf->entries_map, new_hash, new_key, entry); | |
802 | } | |
803 | } | |
804 | ||
805 | if (new_conf->tail) | |
806 | { | |
807 | new_conf->tail->next = conf->entries; | |
808 | conf->entries = new_conf->entries; /* Pilfer. */ | |
809 | new_conf->entries = NULL; | |
810 | } | |
811 | ||
812 | config_file_free(new_conf); | |
813 | return true; | |
814 | } | |
815 | ||
816 | /** | |
817 | * config_file_new_from_string: | |
818 | * | |
819 | * Load a config file from a string. | |
820 | * | |
821 | * NOTE: This will modify @from_string. | |
822 | * Pass a copy of source string if original | |
823 | * contents must be preserved | |
824 | **/ | |
825 | config_file_t *config_file_new_from_string(char *from_string, | |
826 | const char *path) | |
827 | { | |
828 | struct config_file *conf = config_file_new_alloc(); | |
829 | if ( conf | |
830 | && config_file_from_string_internal( | |
831 | conf, from_string, path) != -1) | |
832 | return conf; | |
833 | if (conf) | |
834 | config_file_free(conf); | |
835 | return NULL; | |
836 | } | |
837 | ||
838 | config_file_t *config_file_new_from_path_to_string(const char *path) | |
839 | { | |
840 | if (path_is_valid(path)) | |
841 | { | |
842 | uint8_t *ret_buf = NULL; | |
843 | int64_t length = 0; | |
844 | if (filestream_read_file(path, (void**)&ret_buf, &length)) | |
845 | { | |
846 | config_file_t *conf = NULL; | |
847 | /* Note: 'ret_buf' is not used outside this | |
848 | * function - we do not care that it will be | |
849 | * modified by config_file_new_from_string() */ | |
850 | if (length >= 0) | |
851 | conf = config_file_new_from_string((char*)ret_buf, path); | |
852 | ||
853 | if ((void*)ret_buf) | |
854 | free((void*)ret_buf); | |
855 | ||
856 | return conf; | |
857 | } | |
858 | } | |
859 | ||
860 | return NULL; | |
861 | } | |
862 | ||
863 | /** | |
864 | * config_file_new_with_callback: | |
865 | * | |
866 | * Loads a config file. | |
867 | * If @path is NULL, will create an empty config file. | |
868 | * Includes cb callbacks to run custom code during config file processing. | |
869 | * | |
870 | * @return Returns NULL if file doesn't exist. | |
871 | **/ | |
872 | config_file_t *config_file_new_with_callback( | |
873 | const char *path, config_file_cb_t *cb) | |
874 | { | |
875 | int ret = 0; | |
876 | struct config_file *conf = config_file_new_alloc(); | |
877 | if (!path || !*path) | |
878 | return conf; | |
879 | if ((ret = config_file_load_internal(conf, path, 0, cb)) == -1) | |
880 | { | |
881 | config_file_free(conf); | |
882 | return NULL; | |
883 | } | |
884 | else if (ret == 1) | |
885 | { | |
886 | free(conf); | |
887 | return NULL; | |
888 | } | |
889 | return conf; | |
890 | } | |
891 | ||
892 | /** | |
893 | * config_file_new: | |
894 | * | |
895 | * Loads a config file. | |
896 | * If @path is NULL, will create an empty config file. | |
897 | * | |
898 | * @return Returns NULL if file doesn't exist. | |
899 | **/ | |
900 | config_file_t *config_file_new(const char *path) | |
901 | { | |
902 | int ret = 0; | |
903 | struct config_file *conf = config_file_new_alloc(); | |
904 | if (!path || !*path) | |
905 | return conf; | |
906 | if ((ret = config_file_load_internal(conf, path, 0, NULL)) == -1) | |
907 | { | |
908 | config_file_free(conf); | |
909 | return NULL; | |
910 | } | |
911 | else if (ret == 1) | |
912 | { | |
913 | free(conf); | |
914 | return NULL; | |
915 | } | |
916 | return conf; | |
917 | } | |
918 | ||
919 | /** | |
920 | * config_file_initialize: | |
921 | * | |
922 | * Leaf function. | |
923 | **/ | |
924 | void config_file_initialize(struct config_file *conf) | |
925 | { | |
926 | if (!conf) | |
927 | return; | |
928 | ||
929 | conf->path = NULL; | |
930 | conf->entries_map = NULL; | |
931 | conf->entries = NULL; | |
932 | conf->tail = NULL; | |
933 | conf->last = NULL; | |
934 | conf->references = NULL; | |
935 | conf->includes = NULL; | |
936 | conf->include_depth = 0; | |
937 | conf->guaranteed_no_duplicates = false; | |
938 | conf->modified = false; | |
939 | } | |
940 | ||
941 | config_file_t *config_file_new_alloc(void) | |
942 | { | |
943 | struct config_file *conf = (struct config_file*)malloc(sizeof(*conf)); | |
944 | if (!conf) | |
945 | return NULL; | |
946 | config_file_initialize(conf); | |
947 | return conf; | |
948 | } | |
949 | ||
950 | /** | |
951 | * config_get_entry_internal: | |
952 | * | |
953 | * Leaf function. | |
954 | **/ | |
955 | static struct config_entry_list *config_get_entry_internal( | |
956 | const config_file_t *conf, | |
957 | const char *key, struct config_entry_list **prev) | |
958 | { | |
959 | struct config_entry_list *entry = RHMAP_GET_STR(conf->entries_map, key); | |
960 | ||
961 | if (entry) | |
962 | return entry; | |
963 | ||
964 | if (prev) | |
965 | { | |
966 | struct config_entry_list *previous = *prev; | |
967 | for (entry = conf->entries; entry; entry = entry->next) | |
968 | previous = entry; | |
969 | ||
970 | *prev = previous; | |
971 | } | |
972 | ||
973 | return NULL; | |
974 | } | |
975 | ||
976 | struct config_entry_list *config_get_entry( | |
977 | const config_file_t *conf, const char *key) | |
978 | { | |
979 | return RHMAP_GET_STR(conf->entries_map, key); | |
980 | } | |
981 | ||
982 | /** | |
983 | * config_get_double: | |
984 | * | |
985 | * Extracts a double from config file. | |
986 | * | |
987 | * @return true if found, otherwise false. | |
988 | **/ | |
989 | bool config_get_double(config_file_t *conf, const char *key, double *in) | |
990 | { | |
991 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
992 | ||
993 | if (!entry) | |
994 | return false; | |
995 | ||
996 | *in = strtod(entry->value, NULL); | |
997 | return true; | |
998 | } | |
999 | ||
1000 | /** | |
1001 | * config_get_float: | |
1002 | * | |
1003 | * Extracts a float from config file. | |
1004 | * | |
1005 | * @return true if found, otherwise false. | |
1006 | **/ | |
1007 | bool config_get_float(config_file_t *conf, const char *key, float *in) | |
1008 | { | |
1009 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1010 | ||
1011 | if (!entry) | |
1012 | return false; | |
1013 | ||
1014 | /* strtof() is C99/POSIX. Just use the more portable kind. */ | |
1015 | *in = (float)strtod(entry->value, NULL); | |
1016 | return true; | |
1017 | } | |
1018 | ||
1019 | bool config_get_int(config_file_t *conf, const char *key, int *in) | |
1020 | { | |
1021 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1022 | errno = 0; | |
1023 | ||
1024 | if (entry) | |
1025 | { | |
1026 | int val = (int)strtol(entry->value, NULL, 0); | |
1027 | ||
1028 | if (errno == 0) | |
1029 | { | |
1030 | *in = val; | |
1031 | return true; | |
1032 | } | |
1033 | } | |
1034 | ||
1035 | return false; | |
1036 | } | |
1037 | ||
1038 | bool config_get_size_t(config_file_t *conf, const char *key, size_t *in) | |
1039 | { | |
1040 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1041 | errno = 0; | |
1042 | ||
1043 | if (entry) | |
1044 | { | |
1045 | size_t val = 0; | |
1046 | if (sscanf(entry->value, "%" PRI_SIZET, &val) == 1) | |
1047 | { | |
1048 | *in = val; | |
1049 | return true; | |
1050 | } | |
1051 | } | |
1052 | ||
1053 | return false; | |
1054 | } | |
1055 | ||
1056 | #if defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L | |
1057 | bool config_get_uint64(config_file_t *conf, const char *key, uint64_t *in) | |
1058 | { | |
1059 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1060 | errno = 0; | |
1061 | ||
1062 | if (entry) | |
1063 | { | |
1064 | uint64_t val = strtoull(entry->value, NULL, 0); | |
1065 | ||
1066 | if (errno == 0) | |
1067 | { | |
1068 | *in = val; | |
1069 | return true; | |
1070 | } | |
1071 | } | |
1072 | return false; | |
1073 | } | |
1074 | #endif | |
1075 | ||
1076 | bool config_get_uint(config_file_t *conf, const char *key, unsigned *in) | |
1077 | { | |
1078 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1079 | errno = 0; | |
1080 | ||
1081 | if (entry) | |
1082 | { | |
1083 | unsigned val = (unsigned)strtoul(entry->value, NULL, 0); | |
1084 | ||
1085 | if (errno == 0) | |
1086 | { | |
1087 | *in = val; | |
1088 | return true; | |
1089 | } | |
1090 | } | |
1091 | ||
1092 | return false; | |
1093 | } | |
1094 | ||
1095 | bool config_get_hex(config_file_t *conf, const char *key, unsigned *in) | |
1096 | { | |
1097 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1098 | errno = 0; | |
1099 | ||
1100 | if (entry) | |
1101 | { | |
1102 | unsigned val = (unsigned)strtoul(entry->value, NULL, 16); | |
1103 | ||
1104 | if (errno == 0) | |
1105 | { | |
1106 | *in = val; | |
1107 | return true; | |
1108 | } | |
1109 | } | |
1110 | ||
1111 | return false; | |
1112 | } | |
1113 | ||
1114 | /** | |
1115 | * config_get_char: | |
1116 | * | |
1117 | * Extracts a single char from config file. | |
1118 | * If value consists of several chars, this is an error. | |
1119 | * | |
1120 | * @return true if found, otherwise false. | |
1121 | **/ | |
1122 | bool config_get_char(config_file_t *conf, const char *key, char *in) | |
1123 | { | |
1124 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1125 | ||
1126 | if (entry) | |
1127 | { | |
1128 | if (entry->value[0] && entry->value[1]) | |
1129 | return false; | |
1130 | ||
1131 | *in = *entry->value; | |
1132 | return true; | |
1133 | } | |
1134 | ||
1135 | return false; | |
1136 | } | |
1137 | ||
1138 | /** | |
1139 | * config_get_string: | |
1140 | * | |
1141 | * Extracts an allocated string in *in. This must be free()-d if | |
1142 | * this function succeeds. | |
1143 | * | |
1144 | * @return true if found, otherwise false. | |
1145 | **/ | |
1146 | bool config_get_string(config_file_t *conf, const char *key, char **str) | |
1147 | { | |
1148 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1149 | ||
1150 | if (!entry || !entry->value) | |
1151 | return false; | |
1152 | ||
1153 | *str = strdup(entry->value); | |
1154 | return true; | |
1155 | } | |
1156 | ||
1157 | /** | |
1158 | * config_get_config_path: | |
1159 | * | |
1160 | * Extracts a string to a preallocated buffer. | |
1161 | * Avoid memory allocation. | |
1162 | **/ | |
1163 | bool config_get_config_path(config_file_t *conf, char *s, size_t len) | |
1164 | { | |
1165 | if (conf) | |
1166 | return strlcpy(s, conf->path, len); | |
1167 | return false; | |
1168 | } | |
1169 | ||
1170 | bool config_get_array(config_file_t *conf, const char *key, | |
1171 | char *buf, size_t size) | |
1172 | { | |
1173 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1174 | if (entry) | |
1175 | return strlcpy(buf, entry->value, size) < size; | |
1176 | return false; | |
1177 | } | |
1178 | ||
1179 | bool config_get_path(config_file_t *conf, const char *key, | |
1180 | char *buf, size_t size) | |
1181 | { | |
1182 | #if defined(RARCH_CONSOLE) || !defined(RARCH_INTERNAL) | |
1183 | if (config_get_array(conf, key, buf, size)) | |
1184 | return true; | |
1185 | #else | |
1186 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1187 | if (entry) | |
1188 | { | |
1189 | fill_pathname_expand_special(buf, entry->value, size); | |
1190 | return true; | |
1191 | } | |
1192 | #endif | |
1193 | return false; | |
1194 | } | |
1195 | ||
1196 | /** | |
1197 | * config_get_bool: | |
1198 | * | |
1199 | * Extracts a boolean from config. | |
1200 | * Valid boolean true are "true" and "1". Valid false are "false" and "0". | |
1201 | * Other values will be treated as an error. | |
1202 | * | |
1203 | * @return true if preconditions are true, otherwise false. | |
1204 | **/ | |
1205 | bool config_get_bool(config_file_t *conf, const char *key, bool *in) | |
1206 | { | |
1207 | const struct config_entry_list *entry = config_get_entry(conf, key); | |
1208 | ||
1209 | if (!entry) | |
1210 | return false; | |
1211 | ||
1212 | if ( | |
1213 | ( | |
1214 | entry->value[0] == '1' | |
1215 | && entry->value[1] == '\0' | |
1216 | ) | |
1217 | || string_is_equal(entry->value, "true") | |
1218 | ) | |
1219 | *in = true; | |
1220 | else if ( | |
1221 | ( | |
1222 | entry->value[0] == '0' | |
1223 | && entry->value[1] == '\0' | |
1224 | ) | |
1225 | || string_is_equal(entry->value, "false") | |
1226 | ) | |
1227 | *in = false; | |
1228 | else | |
1229 | return false; | |
1230 | ||
1231 | return true; | |
1232 | } | |
1233 | ||
1234 | void config_set_string(config_file_t *conf, const char *key, const char *val) | |
1235 | { | |
1236 | struct config_entry_list *last = NULL; | |
1237 | struct config_entry_list *entry = NULL; | |
1238 | ||
1239 | if (!conf || !key || !val) | |
1240 | return; | |
1241 | ||
1242 | last = conf->entries; | |
1243 | ||
1244 | if (conf->guaranteed_no_duplicates) | |
1245 | { | |
1246 | if (conf->last) | |
1247 | last = conf->last; | |
1248 | } | |
1249 | else | |
1250 | { | |
1251 | if ((entry = config_get_entry_internal(conf, key, &last))) | |
1252 | { | |
1253 | /* An entry corresponding to 'key' already exists | |
1254 | * > Check whether value is currently set */ | |
1255 | if (entry->value) | |
1256 | { | |
1257 | /* Do nothing if value is unchanged */ | |
1258 | if (string_is_equal(entry->value, val)) | |
1259 | return; | |
1260 | ||
1261 | /* Value is to be updated | |
1262 | * > Free existing */ | |
1263 | free(entry->value); | |
1264 | } | |
1265 | ||
1266 | /* Update value | |
1267 | * > Note that once a value is set, it | |
1268 | * is no longer considered 'read only' */ | |
1269 | entry->value = strdup(val); | |
1270 | entry->readonly = false; | |
1271 | conf->modified = true; | |
1272 | return; | |
1273 | } | |
1274 | } | |
1275 | ||
1276 | /* Entry corresponding to 'key' does not exist | |
1277 | * > Create new entry */ | |
1278 | if (!(entry = (struct config_entry_list*)malloc(sizeof(*entry)))) | |
1279 | return; | |
1280 | ||
1281 | entry->readonly = false; | |
1282 | entry->key = strdup(key); | |
1283 | entry->value = strdup(val); | |
1284 | entry->next = NULL; | |
1285 | conf->modified = true; | |
1286 | ||
1287 | if (last) | |
1288 | last->next = entry; | |
1289 | else | |
1290 | conf->entries = entry; | |
1291 | ||
1292 | conf->last = entry; | |
1293 | ||
1294 | RHMAP_SET_STR(conf->entries_map, entry->key, entry); | |
1295 | } | |
1296 | ||
1297 | void config_unset(config_file_t *conf, const char *key) | |
1298 | { | |
1299 | struct config_entry_list *last = NULL; | |
1300 | struct config_entry_list *entry = NULL; | |
1301 | ||
1302 | if (!conf || !key) | |
1303 | return; | |
1304 | ||
1305 | last = conf->entries; | |
1306 | ||
1307 | if (!(entry = config_get_entry_internal(conf, key, &last))) | |
1308 | return; | |
1309 | ||
1310 | (void)RHMAP_DEL_STR(conf->entries_map, entry->key); | |
1311 | ||
1312 | if (entry->key) | |
1313 | free(entry->key); | |
1314 | ||
1315 | if (entry->value) | |
1316 | free(entry->value); | |
1317 | ||
1318 | entry->key = NULL; | |
1319 | entry->value = NULL; | |
1320 | conf->modified = true; | |
1321 | } | |
1322 | ||
1323 | void config_set_path(config_file_t *conf, const char *entry, const char *val) | |
1324 | { | |
1325 | #if defined(RARCH_CONSOLE) || !defined(RARCH_INTERNAL) | |
1326 | config_set_string(conf, entry, val); | |
1327 | #else | |
1328 | char buf[PATH_MAX_LENGTH]; | |
1329 | fill_pathname_abbreviate_special(buf, val, sizeof(buf)); | |
1330 | config_set_string(conf, entry, buf); | |
1331 | #endif | |
1332 | } | |
1333 | ||
1334 | void config_set_double(config_file_t *conf, const char *key, double val) | |
1335 | { | |
1336 | char buf[320]; | |
1337 | #ifdef __cplusplus | |
1338 | snprintf(buf, sizeof(buf), "%f", (float)val); | |
1339 | #elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L | |
1340 | snprintf(buf, sizeof(buf), "%lf", val); | |
1341 | #else | |
1342 | snprintf(buf, sizeof(buf), "%f", (float)val); | |
1343 | #endif | |
1344 | config_set_string(conf, key, buf); | |
1345 | } | |
1346 | ||
1347 | void config_set_float(config_file_t *conf, const char *key, float val) | |
1348 | { | |
1349 | char buf[64]; | |
1350 | snprintf(buf, sizeof(buf), "%f", val); | |
1351 | config_set_string(conf, key, buf); | |
1352 | } | |
1353 | ||
1354 | void config_set_int(config_file_t *conf, const char *key, int val) | |
1355 | { | |
1356 | char buf[16]; | |
1357 | snprintf(buf, sizeof(buf), "%d", val); | |
1358 | config_set_string(conf, key, buf); | |
1359 | } | |
1360 | ||
1361 | void config_set_uint(config_file_t *conf, const char *key, unsigned int val) | |
1362 | { | |
1363 | char buf[16]; | |
1364 | snprintf(buf, sizeof(buf), "%u", val); | |
1365 | config_set_string(conf, key, buf); | |
1366 | } | |
1367 | ||
1368 | void config_set_hex(config_file_t *conf, const char *key, unsigned val) | |
1369 | { | |
1370 | char buf[16]; | |
1371 | snprintf(buf, sizeof(buf), "%x", val); | |
1372 | config_set_string(conf, key, buf); | |
1373 | } | |
1374 | ||
1375 | void config_set_uint64(config_file_t *conf, const char *key, uint64_t val) | |
1376 | { | |
1377 | char buf[32]; | |
1378 | snprintf(buf, sizeof(buf), "%" PRIu64, val); | |
1379 | config_set_string(conf, key, buf); | |
1380 | } | |
1381 | ||
1382 | void config_set_char(config_file_t *conf, const char *key, char val) | |
1383 | { | |
1384 | char buf[2]; | |
1385 | snprintf(buf, sizeof(buf), "%c", val); | |
1386 | config_set_string(conf, key, buf); | |
1387 | } | |
1388 | ||
1389 | /** | |
1390 | * config_set_bool: | |
1391 | ||
1392 | * TODO/FIXME - could be turned into a trivial macro or removed | |
1393 | **/ | |
1394 | void config_set_bool(config_file_t *conf, const char *key, bool val) | |
1395 | { | |
1396 | config_set_string(conf, key, val ? "true" : "false"); | |
1397 | } | |
1398 | ||
1399 | /** | |
1400 | * config_file_write: | |
1401 | * | |
1402 | * Write the current config to a file. | |
1403 | **/ | |
1404 | bool config_file_write(config_file_t *conf, const char *path, bool sort) | |
1405 | { | |
1406 | if (!conf) | |
1407 | return false; | |
1408 | ||
1409 | if (!conf->modified) | |
1410 | return true; | |
1411 | ||
1412 | if (!string_is_empty(path)) | |
1413 | { | |
1414 | void* buf = NULL; | |
1415 | FILE *file = (FILE*)fopen_utf8(path, "wb"); | |
1416 | if (!file) | |
1417 | return false; | |
1418 | ||
1419 | buf = calloc(1, 0x4000); | |
1420 | setvbuf(file, (char*)buf, _IOFBF, 0x4000); | |
1421 | ||
1422 | config_file_dump(conf, file, sort); | |
1423 | ||
1424 | if (file != stdout) | |
1425 | fclose(file); | |
1426 | if (buf) | |
1427 | free(buf); | |
1428 | ||
1429 | /* Only update modified flag if config file | |
1430 | * is actually written to disk */ | |
1431 | conf->modified = false; | |
1432 | } | |
1433 | else | |
1434 | config_file_dump(conf, stdout, sort); | |
1435 | ||
1436 | return true; | |
1437 | } | |
1438 | ||
1439 | /** | |
1440 | * config_file_dump: | |
1441 | * | |
1442 | * Dump the current config to an already opened file. | |
1443 | * Does not close the file. | |
1444 | **/ | |
1445 | void config_file_dump(config_file_t *conf, FILE *file, bool sort) | |
1446 | { | |
1447 | struct config_entry_list *list = NULL; | |
1448 | struct config_include_list *includes = conf->includes; | |
1449 | struct path_linked_list *ref_tmp = conf->references; | |
1450 | ||
1451 | while (ref_tmp) | |
1452 | { | |
1453 | pathname_make_slashes_portable(ref_tmp->path); | |
1454 | fprintf(file, "#reference \"%s\"\n", ref_tmp->path); | |
1455 | ref_tmp = ref_tmp->next; | |
1456 | } | |
1457 | ||
1458 | if (sort) | |
1459 | list = config_file_merge_sort_linked_list( | |
1460 | (struct config_entry_list*)conf->entries, | |
1461 | config_file_sort_compare_func); | |
1462 | else | |
1463 | list = (struct config_entry_list*)conf->entries; | |
1464 | ||
1465 | conf->entries = list; | |
1466 | ||
1467 | while (list) | |
1468 | { | |
1469 | if (!list->readonly && list->key) | |
1470 | fprintf(file, "%s = \"%s\"\n", list->key, list->value); | |
1471 | list = list->next; | |
1472 | } | |
1473 | ||
1474 | /* Config files are read from the top down - if | |
1475 | * duplicate entries are found then the topmost | |
1476 | * one in the list takes precedence. This means | |
1477 | * '#include' directives must go *after* individual | |
1478 | * config entries, otherwise they will override | |
1479 | * any custom-set values */ | |
1480 | while (includes) | |
1481 | { | |
1482 | fprintf(file, "#include \"%s\"\n", includes->path); | |
1483 | includes = includes->next; | |
1484 | } | |
1485 | } | |
1486 | ||
1487 | /** | |
1488 | * config_get_entry_list_head: | |
1489 | * | |
1490 | * Leaf function. | |
1491 | **/ | |
1492 | bool config_get_entry_list_head(config_file_t *conf, | |
1493 | struct config_file_entry *entry) | |
1494 | { | |
1495 | const struct config_entry_list *head = conf->entries; | |
1496 | ||
1497 | if (!head) | |
1498 | return false; | |
1499 | ||
1500 | entry->key = head->key; | |
1501 | entry->value = head->value; | |
1502 | entry->next = head->next; | |
1503 | return true; | |
1504 | } | |
1505 | ||
1506 | /** | |
1507 | * config_get_entry_list_next: | |
1508 | * | |
1509 | * Leaf function. | |
1510 | **/ | |
1511 | bool config_get_entry_list_next(struct config_file_entry *entry) | |
1512 | { | |
1513 | const struct config_entry_list *next = entry->next; | |
1514 | ||
1515 | if (!next) | |
1516 | return false; | |
1517 | ||
1518 | entry->key = next->key; | |
1519 | entry->value = next->value; | |
1520 | entry->next = next->next; | |
1521 | return true; | |
1522 | } |