libretro: adjust psxclock description
[pcsx_rearmed.git] / deps / libretro-common / file / config_file.c
CommitLineData
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
42struct config_include_list
43{
44 char *path;
45 struct config_include_list *next;
46};
47
48/* Forward declaration */
49static bool config_file_parse_line(config_file_t *conf,
50 struct config_entry_list *list, char *line, config_file_cb_t *cb);
51
52static 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 */
71static 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 **/
159static 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
208static 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? */
268static 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
355static 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
382static 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
410void 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
420static 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
504static 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
646error:
647 list->key = NULL;
648 free(key);
649 return false;
650}
651
652static 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
716bool 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 **/
770void 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 **/
782bool 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 **/
825config_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
838config_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 **/
872config_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 **/
900config_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 **/
924void 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
941config_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 **/
955static 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
976struct 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 **/
989bool 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 **/
1007bool 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
1019bool 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
1038bool 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
1057bool 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
1076bool 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
1095bool 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 **/
1122bool 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 **/
1146bool 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 **/
1163bool 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
1170bool 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
1179bool 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 **/
1205bool 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
1234void 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
1297void 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
1323void 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
1334void 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
1347void 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
1354void 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
1361void 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
1368void 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
1375void 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
1382void 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 **/
1394void 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 **/
1404bool 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 **/
1445void 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 **/
1492bool 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 **/
1511bool 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}