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 #if !defined(_WIN32) && !defined(__CELLOS_LV2__) && !defined(_XBOX)
30 #include <sys/param.h> /* PATH_MAX */
31 #elif defined(_WIN32) && !defined(_XBOX)
32 #define WIN32_LEAN_AND_MEAN
33 #include <windows.h>
34 #elif defined(_XBOX)
35 #include <xtl.h>
36 #endif
37 #ifdef ORBIS
38 #include <sys/fcntl.h>
39 #include <orbisFile.h>
40 #endif
41 #include <retro_miscellaneous.h>
42 #include <compat/strl.h>
43 #include <compat/posix_string.h>
44 #include <compat/fopen_utf8.h>
45 #include <compat/msvc.h>
46 #include <file/config_file.h>
47 #include <file/file_path.h>
48 #include <string/stdstring.h>
49 #include <streams/file_stream.h>
50
51 #define MAX_INCLUDE_DEPTH 16
52
53 struct config_include_list
54 {
55 char *path;
56 struct config_include_list *next;
57 };
58
59 /* Forward declaration */
60 static bool config_file_parse_line(config_file_t *conf,
61 struct config_entry_list *list, char *line, config_file_cb_t *cb);
62
config_file_sort_compare_func(struct config_entry_list * a,struct config_entry_list * b)63 static int config_file_sort_compare_func(struct config_entry_list *a,
64 struct config_entry_list *b)
65 {
66 if (a && b)
67 {
68 if (a->key)
69 {
70 if (b->key)
71 return strcasecmp(a->key, b->key);
72 return 1;
73 }
74 else if (b->key)
75 return -1;
76 }
77
78 return 0;
79 }
80
81 /* https://stackoverflow.com/questions/7685/merge-sort-a-linked-list */
config_file_merge_sort_linked_list(struct config_entry_list * list,int (* compare)(struct config_entry_list * one,struct config_entry_list * two))82 static struct config_entry_list* config_file_merge_sort_linked_list(
83 struct config_entry_list *list, int (*compare)(
84 struct config_entry_list *one,struct config_entry_list *two))
85 {
86 struct config_entry_list
87 *right = list,
88 *temp = list,
89 *last = list,
90 *result = 0,
91 *next = 0,
92 *tail = 0;
93
94 /* Trivial case. */
95 if (!list || !list->next)
96 return list;
97
98 /* Find halfway through the list (by running two pointers,
99 * one at twice the speed of the other). */
100 while (temp && temp->next)
101 {
102 last = right;
103 right = right->next;
104 temp = temp->next->next;
105 }
106
107 /* Break the list in two. (prev pointers are broken here,
108 * but we fix later) */
109 last->next = 0;
110
111 /* Recurse on the two smaller lists: */
112 list = config_file_merge_sort_linked_list(list, compare);
113 right = config_file_merge_sort_linked_list(right, compare);
114
115 /* Merge: */
116 while (list || right)
117 {
118 /* Take from empty lists, or compare: */
119 if (!right)
120 {
121 next = list;
122 list = list->next;
123 }
124 else if (!list)
125 {
126 next = right;
127 right = right->next;
128 }
129 else if (compare(list, right) < 0)
130 {
131 next = list;
132 list = list->next;
133 }
134 else
135 {
136 next = right;
137 right = right->next;
138 }
139
140 if (!result)
141 result = next;
142 else
143 tail->next = next;
144
145 tail = next;
146 }
147
148 return result;
149 }
150
151 /* Searches input string for a comment ('#') entry
152 * > If first character is '#', then entire line is
153 * a comment and may correspond to a directive
154 * (command action - e.g. include sub-config file).
155 * In this case, 'str' is set to NUL and the comment
156 * itself (everything after the '#' character) is
157 * returned
158 * > If a '#' character is found inside a string literal
159 * value, then it does not correspond to a comment and
160 * is ignored. In this case, 'str' is left untouched
161 * and NULL is returned
162 * > If a '#' character is found anywhere else, then the
163 * comment text is a suffix of the input string and
164 * has no programmatic value. In this case, the comment
165 * is removed from the end of 'str' and NULL is returned */
config_file_strip_comment(char * str)166 static char *config_file_strip_comment(char *str)
167 {
168 /* Search for a comment (#) character */
169 char *comment = strchr(str, '#');
170
171 if (comment)
172 {
173 char *literal_start = NULL;
174
175 /* Check whether entire line is a comment
176 * > First character == '#' */
177 if (str == comment)
178 {
179 /* Set 'str' to NUL and return comment
180 * for processing at a higher level */
181 *str = '\0';
182 return ++comment;
183 }
184
185 /* Comment character occurs at an offset:
186 * Search for the start of a string literal value */
187 literal_start = strchr(str, '\"');
188
189 /* Check whether string literal start occurs
190 * *before* the comment character */
191 if (literal_start && (literal_start < comment))
192 {
193 /* Search for the end of the string literal
194 * value */
195 char *literal_end = strchr(literal_start + 1, '\"');
196
197 /* Check whether string literal end occurs
198 * *after* the comment character
199 * > If this is the case, ignore the comment
200 * > Leave 'str' untouched and return NULL */
201 if (literal_end && (literal_end > comment))
202 return NULL;
203 }
204
205 /* If we reach this point, then a comment
206 * exists outside of a string literal
207 * > Trim the entire comment from the end
208 * of 'str' */
209 *comment = '\0';
210 }
211
212 return NULL;
213 }
214
config_file_extract_value(char * line,bool is_value)215 static char *config_file_extract_value(char *line, bool is_value)
216 {
217 size_t idx = 0;
218 char *value = NULL;
219
220 if (is_value)
221 {
222 while (ISSPACE((int)*line))
223 line++;
224
225 /* If we don't have an equal sign here,
226 * we've got an invalid string. */
227 if (*line != '=')
228 return NULL;
229
230 line++;
231 }
232
233 while (ISSPACE((int)*line))
234 line++;
235
236 /* Note: From this point on, an empty value
237 * string is valid - and in this case, strdup("")
238 * will be returned
239 * > If we instead return NULL, the the entry
240 * is ignored completely - which means we cannot
241 * track *changes* in entry value */
242
243 /* If first character is ("), we have a full string
244 * literal */
245 if (*line == '"')
246 {
247 /* Skip to next character */
248 line++;
249
250 /* If this a ("), then value string is empty */
251 if (*line == '"')
252 return strdup("");
253
254 /* Find the next (") character */
255 while (line[idx] && (line[idx] != '\"'))
256 idx++;
257
258 line[idx] = '\0';
259 value = line;
260 }
261 /* This is not a string literal - just read
262 * until the next space is found
263 * > Note: Skip this if line is empty */
264 else if (*line != '\0')
265 {
266 /* Find next space character */
267 while (line[idx] && isgraph((int)line[idx]))
268 idx++;
269
270 line[idx] = '\0';
271 value = line;
272 }
273
274 if (value && *value)
275 return strdup(value);
276
277 return strdup("");
278 }
279
280 /* Move semantics? */
config_file_add_child_list(config_file_t * parent,config_file_t * child)281 static void config_file_add_child_list(config_file_t *parent, config_file_t *child)
282 {
283 struct config_entry_list *list = child->entries;
284 if (parent->entries)
285 {
286 struct config_entry_list *head = parent->entries;
287 while (head->next)
288 head = head->next;
289
290 /* set list readonly */
291 while (list)
292 {
293 list->readonly = true;
294 list = list->next;
295 }
296 head->next = child->entries;
297 }
298 else
299 {
300 /* set list readonly */
301 while (list)
302 {
303 list->readonly = true;
304 list = list->next;
305 }
306 parent->entries = child->entries;
307 }
308
309 child->entries = NULL;
310
311 /* Rebase tail. */
312 if (parent->entries)
313 {
314 struct config_entry_list *head =
315 (struct config_entry_list*)parent->entries;
316
317 while (head->next)
318 head = head->next;
319 parent->tail = head;
320 }
321 else
322 parent->tail = NULL;
323 }
324
config_file_get_realpath(char * s,size_t len,char * path,const char * config_path)325 static void config_file_get_realpath(char *s, size_t len,
326 char *path, const char *config_path)
327 {
328 #ifdef _WIN32
329 if (!string_is_empty(config_path))
330 fill_pathname_resolve_relative(s, config_path,
331 path, len);
332 #else
333 #ifndef __CELLOS_LV2__
334 if (*path == '~')
335 {
336 const char *home = getenv("HOME");
337 if (home)
338 {
339 strlcpy(s, home, len);
340 strlcat(s, path + 1, len);
341 }
342 else
343 strlcpy(s, path + 1, len);
344 }
345 else
346 #endif
347 if (!string_is_empty(config_path))
348 fill_pathname_resolve_relative(s, config_path, path, len);
349 #endif
350 }
351
config_file_add_sub_conf(config_file_t * conf,char * path,char * real_path,size_t len,config_file_cb_t * cb)352 static void config_file_add_sub_conf(config_file_t *conf, char *path,
353 char *real_path, size_t len, config_file_cb_t *cb)
354 {
355 struct config_include_list *head = conf->includes;
356 struct config_include_list *node = (struct config_include_list*)
357 malloc(sizeof(*node));
358
359 if (node)
360 {
361 node->next = NULL;
362 /* Add include list */
363 node->path = strdup(path);
364
365 if (head)
366 {
367 while (head->next)
368 head = head->next;
369
370 head->next = node;
371 }
372 else
373 conf->includes = node;
374 }
375
376 config_file_get_realpath(real_path, len, path,
377 conf->path);
378 }
379
config_file_load_internal(struct config_file * conf,const char * path,unsigned depth,config_file_cb_t * cb)380 static int config_file_load_internal(
381 struct config_file *conf,
382 const char *path, unsigned depth, config_file_cb_t *cb)
383 {
384 RFILE *file = NULL;
385 char *new_path = strdup(path);
386 if (!new_path)
387 return 1;
388
389 conf->path = new_path;
390 conf->include_depth = depth;
391 file = filestream_open(path,
392 RETRO_VFS_FILE_ACCESS_READ,
393 RETRO_VFS_FILE_ACCESS_HINT_NONE);
394
395 if (!file)
396 {
397 free(conf->path);
398 return 1;
399 }
400
401 while (!filestream_eof(file))
402 {
403 char *line = NULL;
404 struct config_entry_list *list = (struct config_entry_list*)
405 malloc(sizeof(*list));
406
407 if (!list)
408 {
409 filestream_close(file);
410 return -1;
411 }
412
413 list->readonly = false;
414 list->key = NULL;
415 list->value = NULL;
416 list->next = NULL;
417
418 line = filestream_getline(file);
419
420 if (!line)
421 {
422 free(list);
423 continue;
424 }
425
426 if (
427 !string_is_empty(line)
428 && config_file_parse_line(conf, list, line, cb))
429 {
430 if (conf->entries)
431 conf->tail->next = list;
432 else
433 conf->entries = list;
434
435 conf->tail = list;
436
437 if (cb && list->key && list->value)
438 cb->config_file_new_entry_cb(list->key, list->value) ;
439 }
440
441 free(line);
442
443 if (list != conf->tail)
444 free(list);
445 }
446
447 filestream_close(file);
448
449 return 0;
450 }
451
config_file_parse_line(config_file_t * conf,struct config_entry_list * list,char * line,config_file_cb_t * cb)452 static bool config_file_parse_line(config_file_t *conf,
453 struct config_entry_list *list, char *line, config_file_cb_t *cb)
454 {
455 size_t cur_size = 32;
456 size_t idx = 0;
457 char *key = NULL;
458 char *key_tmp = NULL;
459 /* Remove any comment text */
460 char *comment = config_file_strip_comment(line);
461
462 /* Check whether entire line is a comment */
463 if (comment)
464 {
465 config_file_t sub_conf;
466 char real_path[PATH_MAX_LENGTH];
467 char *path = NULL;
468 char *include_line = NULL;
469
470 /* Starting a line with an 'include' directive
471 * appends a sub-config file
472 * > All other comments are ignored */
473 if (!string_starts_with_size(comment, "include ",
474 STRLEN_CONST("include ")))
475 return false;
476
477 include_line = comment + STRLEN_CONST("include ");
478
479 if (string_is_empty(include_line))
480 return false;
481
482 path = config_file_extract_value(include_line, false);
483
484 if (!path)
485 return false;
486
487 if ( string_is_empty(path)
488 || conf->include_depth >= MAX_INCLUDE_DEPTH)
489 {
490 free(path);
491 return false;
492 }
493
494 real_path[0] = '\0';
495 config_file_add_sub_conf(conf, path,
496 real_path, sizeof(real_path), cb);
497
498 config_file_initialize(&sub_conf);
499
500 switch (config_file_load_internal(&sub_conf, real_path,
501 conf->include_depth + 1, cb))
502 {
503 case 0:
504 /* Pilfer internal list. */
505 config_file_add_child_list(conf, &sub_conf);
506 /* fall-through to deinitialize */
507 case -1:
508 config_file_deinitialize(&sub_conf);
509 break;
510 case 1:
511 default:
512 break;
513 }
514
515 free(path);
516 return true;
517 }
518
519 /* Skip to first non-space character */
520 while (ISSPACE((int)*line))
521 line++;
522
523 /* Allocate storage for key */
524 key = (char*)malloc(cur_size + 1);
525 if (!key)
526 return false;
527
528 /* Copy line contents into key until we
529 * reach the next space character */
530 while (isgraph((int)*line))
531 {
532 /* If current key storage is too small,
533 * double its size */
534 if (idx == cur_size)
535 {
536 cur_size *= 2;
537 key_tmp = (char*)realloc(key, cur_size + 1);
538
539 if (!key_tmp)
540 {
541 free(key);
542 return false;
543 }
544
545 key = key_tmp;
546 }
547
548 key[idx++] = *line++;
549 }
550 key[idx] = '\0';
551
552 /* Add key and value entries to list */
553 list->key = key;
554 list->value = config_file_extract_value(line, true);
555
556 /* An entry without a value is invalid */
557 if (!list->value)
558 {
559 list->key = NULL;
560 free(key);
561 return false;
562 }
563
564 return true;
565 }
566
config_file_from_string_internal(struct config_file * conf,char * from_string,const char * path)567 static int config_file_from_string_internal(
568 struct config_file *conf,
569 char *from_string,
570 const char *path)
571 {
572 char *lines = from_string;
573 char *save_ptr = NULL;
574 char *line = NULL;
575
576 if (!string_is_empty(path))
577 conf->path = strdup(path);
578 if (string_is_empty(lines))
579 return 0;
580
581 /* Get first line of config file */
582 line = strtok_r(lines, "\n", &save_ptr);
583
584 while (line)
585 {
586 struct config_entry_list *list = (struct config_entry_list*)
587 malloc(sizeof(*list));
588
589 if (!list)
590 return -1;
591
592 list->readonly = false;
593 list->key = NULL;
594 list->value = NULL;
595 list->next = NULL;
596
597 /* Parse current line */
598 if (
599 !string_is_empty(line)
600 && config_file_parse_line(conf, list, line, NULL))
601 {
602 if (conf->entries)
603 conf->tail->next = list;
604 else
605 conf->entries = list;
606
607 conf->tail = list;
608 }
609
610 if (list != conf->tail)
611 free(list);
612
613 /* Get next line of config file */
614 line = strtok_r(NULL, "\n", &save_ptr);
615 }
616
617 return 0;
618 }
619
620
config_file_deinitialize(config_file_t * conf)621 bool config_file_deinitialize(config_file_t *conf)
622 {
623 struct config_include_list *inc_tmp = NULL;
624 struct config_entry_list *tmp = NULL;
625 if (!conf)
626 return false;
627
628 tmp = conf->entries;
629 while (tmp)
630 {
631 struct config_entry_list *hold = NULL;
632 if (tmp->key)
633 free(tmp->key);
634 if (tmp->value)
635 free(tmp->value);
636
637 tmp->value = NULL;
638 tmp->key = NULL;
639
640 hold = tmp;
641 tmp = tmp->next;
642
643 if (hold)
644 free(hold);
645 }
646
647 inc_tmp = (struct config_include_list*)conf->includes;
648 while (inc_tmp)
649 {
650 struct config_include_list *hold = NULL;
651 if (inc_tmp->path)
652 free(inc_tmp->path);
653 hold = (struct config_include_list*)inc_tmp;
654 inc_tmp = inc_tmp->next;
655 if (hold)
656 free(hold);
657 }
658
659 if (conf->path)
660 free(conf->path);
661 return true;
662 }
663
config_file_free(config_file_t * conf)664 void config_file_free(config_file_t *conf)
665 {
666 if (!config_file_deinitialize(conf))
667 return;
668 free(conf);
669 }
670
config_append_file(config_file_t * conf,const char * path)671 bool config_append_file(config_file_t *conf, const char *path)
672 {
673 config_file_t *new_conf = config_file_new_from_path_to_string(path);
674 if (!new_conf)
675 return false;
676
677 if (new_conf->tail)
678 {
679 new_conf->tail->next = conf->entries;
680 conf->entries = new_conf->entries; /* Pilfer. */
681 new_conf->entries = NULL;
682 }
683
684 config_file_free(new_conf);
685 return true;
686 }
687
config_file_new_from_string(char * from_string,const char * path)688 config_file_t *config_file_new_from_string(char *from_string,
689 const char *path)
690 {
691 struct config_file *conf = config_file_new_alloc();
692
693 if (!conf)
694 return NULL;
695 if (config_file_from_string_internal(conf, from_string, path) == -1)
696 {
697 config_file_free(conf);
698 return NULL;
699 }
700 return conf;
701 }
702
config_file_new_from_path_to_string(const char * path)703 config_file_t *config_file_new_from_path_to_string(const char *path)
704 {
705 int64_t length = 0;
706 uint8_t *ret_buf = NULL;
707 config_file_t *conf = NULL;
708
709 if (path_is_valid(path))
710 {
711 if (filestream_read_file(path, (void**)&ret_buf, &length))
712 {
713 /* Note: 'ret_buf' is not used outside this
714 * function - we do not care that it will be
715 * modified by config_file_new_from_string() */
716 if (length >= 0)
717 conf = config_file_new_from_string((char*)ret_buf, path);
718
719 if ((void*)ret_buf)
720 free((void*)ret_buf);
721 }
722 }
723
724 return conf;
725 }
726
config_file_new_with_callback(const char * path,config_file_cb_t * cb)727 config_file_t *config_file_new_with_callback(
728 const char *path, config_file_cb_t *cb)
729 {
730 int ret = 0;
731 struct config_file *conf = config_file_new_alloc();
732 if (!path || !*path)
733 return conf;
734 ret = config_file_load_internal(conf, path, 0, cb);
735 if (ret == -1)
736 {
737 config_file_free(conf);
738 return NULL;
739 }
740 else if (ret == 1)
741 {
742 free(conf);
743 return NULL;
744 }
745 return conf;
746 }
747
config_file_new(const char * path)748 config_file_t *config_file_new(const char *path)
749 {
750 int ret = 0;
751 struct config_file *conf = config_file_new_alloc();
752 if (!path || !*path)
753 return conf;
754 ret = config_file_load_internal(conf, path, 0, NULL);
755 if (ret == -1)
756 {
757 config_file_free(conf);
758 return NULL;
759 }
760 else if (ret == 1)
761 {
762 free(conf);
763 return NULL;
764 }
765 return conf;
766 }
767
config_file_initialize(struct config_file * conf)768 void config_file_initialize(struct config_file *conf)
769 {
770 if (!conf)
771 return;
772
773 conf->path = NULL;
774 conf->entries = NULL;
775 conf->tail = NULL;
776 conf->last = NULL;
777 conf->includes = NULL;
778 conf->include_depth = 0;
779 conf->guaranteed_no_duplicates = false;
780 conf->modified = false;
781 }
782
config_file_new_alloc(void)783 config_file_t *config_file_new_alloc(void)
784 {
785 struct config_file *conf = (struct config_file*)malloc(sizeof(*conf));
786 if (!conf)
787 return NULL;
788 config_file_initialize(conf);
789 return conf;
790 }
791
config_get_entry_internal(const config_file_t * conf,const char * key,struct config_entry_list ** prev)792 static struct config_entry_list *config_get_entry_internal(
793 const config_file_t *conf,
794 const char *key, struct config_entry_list **prev)
795 {
796 struct config_entry_list *entry = NULL;
797 struct config_entry_list *previous = prev ? *prev : NULL;
798
799 for (entry = conf->entries; entry; entry = entry->next)
800 {
801 if (string_is_equal(key, entry->key))
802 return entry;
803
804 previous = entry;
805 }
806
807 if (prev)
808 *prev = previous;
809
810 return NULL;
811 }
812
config_get_entry(const config_file_t * conf,const char * key)813 struct config_entry_list *config_get_entry(
814 const config_file_t *conf, const char *key)
815 {
816 struct config_entry_list *entry = NULL;
817 for (entry = conf->entries; entry; entry = entry->next)
818 {
819 if (string_is_equal(key, entry->key))
820 return entry;
821 }
822 return NULL;
823 }
824
825
config_get_double(config_file_t * conf,const char * key,double * in)826 bool config_get_double(config_file_t *conf, const char *key, double *in)
827 {
828 const struct config_entry_list *entry = config_get_entry(conf, key);
829
830 if (!entry)
831 return false;
832
833 *in = strtod(entry->value, NULL);
834 return true;
835 }
836
config_get_float(config_file_t * conf,const char * key,float * in)837 bool config_get_float(config_file_t *conf, const char *key, float *in)
838 {
839 const struct config_entry_list *entry = config_get_entry(conf, key);
840
841 if (!entry)
842 return false;
843
844 /* strtof() is C99/POSIX. Just use the more portable kind. */
845 *in = (float)strtod(entry->value, NULL);
846 return true;
847 }
848
config_get_int(config_file_t * conf,const char * key,int * in)849 bool config_get_int(config_file_t *conf, const char *key, int *in)
850 {
851 const struct config_entry_list *entry = config_get_entry(conf, key);
852 errno = 0;
853
854 if (entry)
855 {
856 int val = (int)strtol(entry->value, NULL, 0);
857
858 if (errno == 0)
859 {
860 *in = val;
861 return true;
862 }
863 }
864
865 return false;
866 }
867
config_get_size_t(config_file_t * conf,const char * key,size_t * in)868 bool config_get_size_t(config_file_t *conf, const char *key, size_t *in)
869 {
870 const struct config_entry_list *entry = config_get_entry(conf, key);
871 errno = 0;
872
873 if (entry)
874 {
875 size_t val = 0;
876 if (sscanf(entry->value, "%" PRI_SIZET, &val) == 1)
877 {
878 *in = val;
879 return true;
880 }
881 }
882
883 return false;
884 }
885
886 #if defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
config_get_uint64(config_file_t * conf,const char * key,uint64_t * in)887 bool config_get_uint64(config_file_t *conf, const char *key, uint64_t *in)
888 {
889 const struct config_entry_list *entry = config_get_entry(conf, key);
890 errno = 0;
891
892 if (entry)
893 {
894 uint64_t val = strtoull(entry->value, NULL, 0);
895
896 if (errno == 0)
897 {
898 *in = val;
899 return true;
900 }
901 }
902 return false;
903 }
904 #endif
905
config_get_uint(config_file_t * conf,const char * key,unsigned * in)906 bool config_get_uint(config_file_t *conf, const char *key, unsigned *in)
907 {
908 const struct config_entry_list *entry = config_get_entry(conf, key);
909 errno = 0;
910
911 if (entry)
912 {
913 unsigned val = (unsigned)strtoul(entry->value, NULL, 0);
914
915 if (errno == 0)
916 {
917 *in = val;
918 return true;
919 }
920 }
921
922 return false;
923 }
924
config_get_hex(config_file_t * conf,const char * key,unsigned * in)925 bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
926 {
927 const struct config_entry_list *entry = config_get_entry(conf, key);
928 errno = 0;
929
930 if (entry)
931 {
932 unsigned val = (unsigned)strtoul(entry->value, NULL, 16);
933
934 if (errno == 0)
935 {
936 *in = val;
937 return true;
938 }
939 }
940
941 return false;
942 }
943
config_get_char(config_file_t * conf,const char * key,char * in)944 bool config_get_char(config_file_t *conf, const char *key, char *in)
945 {
946 const struct config_entry_list *entry = config_get_entry(conf, key);
947
948 if (entry)
949 {
950 if (entry->value[0] && entry->value[1])
951 return false;
952
953 *in = *entry->value;
954 return true;
955 }
956
957 return false;
958 }
959
config_get_string(config_file_t * conf,const char * key,char ** str)960 bool config_get_string(config_file_t *conf, const char *key, char **str)
961 {
962 const struct config_entry_list *entry = config_get_entry(conf, key);
963
964 if (!entry || !entry->value)
965 return false;
966
967 *str = strdup(entry->value);
968 return true;
969 }
970
config_get_config_path(config_file_t * conf,char * s,size_t len)971 bool config_get_config_path(config_file_t *conf, char *s, size_t len)
972 {
973 if (!conf)
974 return false;
975 return strlcpy(s, conf->path, len);
976 }
977
config_get_array(config_file_t * conf,const char * key,char * buf,size_t size)978 bool config_get_array(config_file_t *conf, const char *key,
979 char *buf, size_t size)
980 {
981 const struct config_entry_list *entry = config_get_entry(conf, key);
982 if (entry)
983 return strlcpy(buf, entry->value, size) < size;
984 return false;
985 }
986
config_get_path(config_file_t * conf,const char * key,char * buf,size_t size)987 bool config_get_path(config_file_t *conf, const char *key,
988 char *buf, size_t size)
989 {
990 #if defined(RARCH_CONSOLE) || !defined(RARCH_INTERNAL)
991 if (config_get_array(conf, key, buf, size))
992 return true;
993 #else
994 const struct config_entry_list *entry = config_get_entry(conf, key);
995
996 if (entry)
997 {
998 fill_pathname_expand_special(buf, entry->value, size);
999 return true;
1000 }
1001 #endif
1002 return false;
1003 }
1004
config_get_bool(config_file_t * conf,const char * key,bool * in)1005 bool config_get_bool(config_file_t *conf, const char *key, bool *in)
1006 {
1007 const struct config_entry_list *entry = config_get_entry(conf, key);
1008
1009 if (!entry)
1010 return false;
1011
1012 if (
1013 (
1014 entry->value[0] == '1'
1015 && entry->value[1] == '\0'
1016 )
1017 || string_is_equal(entry->value, "true")
1018 )
1019 *in = true;
1020 else if (
1021 (
1022 entry->value[0] == '0'
1023 && entry->value[1] == '\0'
1024 )
1025 || string_is_equal(entry->value, "false")
1026 )
1027 *in = false;
1028 else
1029 return false;
1030
1031 return true;
1032 }
1033
config_set_string(config_file_t * conf,const char * key,const char * val)1034 void config_set_string(config_file_t *conf, const char *key, const char *val)
1035 {
1036 struct config_entry_list *last = NULL;
1037 struct config_entry_list *entry = NULL;
1038
1039 if (!conf || !key || !val)
1040 return;
1041
1042 last = conf->entries;
1043
1044 if (conf->guaranteed_no_duplicates)
1045 {
1046 if (conf->last)
1047 last = conf->last;
1048 }
1049 else
1050 {
1051 entry = config_get_entry_internal(
1052 conf, key, &last);
1053 if (entry)
1054 {
1055 /* An entry corresponding to 'key' already exists
1056 * > Check if it's read only */
1057 if (entry->readonly)
1058 return;
1059
1060 /* Check whether value is currently set */
1061 if (entry->value)
1062 {
1063 /* Do nothing if value is unchanged */
1064 if (string_is_equal(entry->value, val))
1065 return;
1066
1067 /* Value is to be updated
1068 * > Free existing */
1069 free(entry->value);
1070 }
1071
1072 /* Update value */
1073 entry->value = strdup(val);
1074 conf->modified = true;
1075 return;
1076 }
1077 }
1078
1079 /* Entry corresponding to 'key' does not exist
1080 * > Create new entry */
1081 entry = (struct config_entry_list*)malloc(sizeof(*entry));
1082 if (!entry)
1083 return;
1084
1085 entry->readonly = false;
1086 entry->key = strdup(key);
1087 entry->value = strdup(val);
1088 entry->next = NULL;
1089 conf->modified = true;
1090
1091 if (last)
1092 last->next = entry;
1093 else
1094 conf->entries = entry;
1095
1096 conf->last = entry;
1097 }
1098
config_unset(config_file_t * conf,const char * key)1099 void config_unset(config_file_t *conf, const char *key)
1100 {
1101 struct config_entry_list *last = NULL;
1102 struct config_entry_list *entry = NULL;
1103
1104 if (!conf || !key)
1105 return;
1106
1107 last = conf->entries;
1108 entry = config_get_entry_internal(conf, key, &last);
1109
1110 if (!entry)
1111 return;
1112
1113 if (entry->key)
1114 free(entry->key);
1115
1116 if (entry->value)
1117 free(entry->value);
1118
1119 entry->key = NULL;
1120 entry->value = NULL;
1121 conf->modified = true;
1122 }
1123
config_set_path(config_file_t * conf,const char * entry,const char * val)1124 void config_set_path(config_file_t *conf, const char *entry, const char *val)
1125 {
1126 #if defined(RARCH_CONSOLE) || !defined(RARCH_INTERNAL)
1127 config_set_string(conf, entry, val);
1128 #else
1129 char buf[PATH_MAX_LENGTH];
1130
1131 buf[0] = '\0';
1132 fill_pathname_abbreviate_special(buf, val, sizeof(buf));
1133 config_set_string(conf, entry, buf);
1134 #endif
1135 }
1136
config_set_double(config_file_t * conf,const char * key,double val)1137 void config_set_double(config_file_t *conf, const char *key, double val)
1138 {
1139 char buf[320];
1140 #ifdef __cplusplus
1141 snprintf(buf, sizeof(buf), "%f", (float)val);
1142 #elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
1143 snprintf(buf, sizeof(buf), "%lf", val);
1144 #else
1145 snprintf(buf, sizeof(buf), "%f", (float)val);
1146 #endif
1147 config_set_string(conf, key, buf);
1148 }
1149
config_set_float(config_file_t * conf,const char * key,float val)1150 void config_set_float(config_file_t *conf, const char *key, float val)
1151 {
1152 char buf[64];
1153 snprintf(buf, sizeof(buf), "%f", val);
1154 config_set_string(conf, key, buf);
1155 }
1156
config_set_int(config_file_t * conf,const char * key,int val)1157 void config_set_int(config_file_t *conf, const char *key, int val)
1158 {
1159 char buf[16];
1160 snprintf(buf, sizeof(buf), "%d", val);
1161 config_set_string(conf, key, buf);
1162 }
1163
config_set_uint(config_file_t * conf,const char * key,unsigned int val)1164 void config_set_uint(config_file_t *conf, const char *key, unsigned int val)
1165 {
1166 char buf[16];
1167 snprintf(buf, sizeof(buf), "%u", val);
1168 config_set_string(conf, key, buf);
1169 }
1170
config_set_hex(config_file_t * conf,const char * key,unsigned val)1171 void config_set_hex(config_file_t *conf, const char *key, unsigned val)
1172 {
1173 char buf[16];
1174 snprintf(buf, sizeof(buf), "%x", val);
1175 config_set_string(conf, key, buf);
1176 }
1177
config_set_uint64(config_file_t * conf,const char * key,uint64_t val)1178 void config_set_uint64(config_file_t *conf, const char *key, uint64_t val)
1179 {
1180 char buf[32];
1181 snprintf(buf, sizeof(buf), "%" PRIu64, val);
1182 config_set_string(conf, key, buf);
1183 }
1184
config_set_char(config_file_t * conf,const char * key,char val)1185 void config_set_char(config_file_t *conf, const char *key, char val)
1186 {
1187 char buf[2];
1188 snprintf(buf, sizeof(buf), "%c", val);
1189 config_set_string(conf, key, buf);
1190 }
1191
config_set_bool(config_file_t * conf,const char * key,bool val)1192 void config_set_bool(config_file_t *conf, const char *key, bool val)
1193 {
1194 config_set_string(conf, key, val ? "true" : "false");
1195 }
1196
config_file_write(config_file_t * conf,const char * path,bool sort)1197 bool config_file_write(config_file_t *conf, const char *path, bool sort)
1198 {
1199 if (!conf)
1200 return false;
1201
1202 if (!conf->modified)
1203 return true;
1204
1205 if (!string_is_empty(path))
1206 {
1207 #ifdef ORBIS
1208 int fd = orbisOpen(path,O_RDWR|O_CREAT,0644);
1209 if (fd < 0)
1210 return false;
1211 config_file_dump_orbis(conf,fd);
1212 orbisClose(fd);
1213 #else
1214 void* buf = NULL;
1215 FILE *file = (FILE*)fopen_utf8(path, "wb");
1216 if (!file)
1217 return false;
1218
1219 /* TODO: this is only useful for a few platforms, find which and add ifdef */
1220 #if !defined(PSP)
1221 buf = calloc(1, 0x4000);
1222 setvbuf(file, (char*)buf, _IOFBF, 0x4000);
1223 #endif
1224
1225 config_file_dump(conf, file, sort);
1226
1227 if (file != stdout)
1228 fclose(file);
1229 if (buf)
1230 free(buf);
1231 #endif
1232
1233 /* Only update modified flag if config file
1234 * is actually written to disk */
1235 conf->modified = false;
1236 }
1237 else
1238 config_file_dump(conf, stdout, sort);
1239
1240 return true;
1241 }
1242
1243 #ifdef ORBIS
config_file_dump_orbis(config_file_t * conf,int fd)1244 void config_file_dump_orbis(config_file_t *conf, int fd)
1245 {
1246 struct config_entry_list *list = NULL;
1247 struct config_include_list *includes = conf->includes;
1248 while (includes)
1249 {
1250 char cad[256];
1251 snprintf(cad, sizeof(cad),
1252 "#include %s\n", includes->path);
1253 orbisWrite(fd, cad, strlen(cad));
1254 includes = includes->next;
1255 }
1256
1257 list = config_file_merge_sort_linked_list(
1258 (struct config_entry_list*)conf->entries,
1259 config_file_sort_compare_func);
1260 conf->entries = list;
1261
1262 while (list)
1263 {
1264 if (!list->readonly && list->key)
1265 {
1266 char newlist[256];
1267 snprintf(newlist, sizeof(newlist),
1268 "%s = %s\n", list->key, list->value);
1269 orbisWrite(fd, newlist, strlen(newlist));
1270 }
1271 list = list->next;
1272 }
1273 }
1274 #endif
1275
config_file_dump(config_file_t * conf,FILE * file,bool sort)1276 void config_file_dump(config_file_t *conf, FILE *file, bool sort)
1277 {
1278 struct config_entry_list *list = NULL;
1279 struct config_include_list *includes = conf->includes;
1280
1281 while (includes)
1282 {
1283 fprintf(file, "#include \"%s\"\n", includes->path);
1284 includes = includes->next;
1285 }
1286
1287 if (sort)
1288 list = config_file_merge_sort_linked_list(
1289 (struct config_entry_list*)conf->entries,
1290 config_file_sort_compare_func);
1291 else
1292 list = (struct config_entry_list*)conf->entries;
1293
1294 conf->entries = list;
1295
1296 while (list)
1297 {
1298 if (!list->readonly && list->key)
1299 fprintf(file, "%s = \"%s\"\n", list->key, list->value);
1300 list = list->next;
1301 }
1302 }
1303
config_entry_exists(config_file_t * conf,const char * entry)1304 bool config_entry_exists(config_file_t *conf, const char *entry)
1305 {
1306 struct config_entry_list *list = conf->entries;
1307
1308 while (list)
1309 {
1310 if (string_is_equal(entry, list->key))
1311 return true;
1312 list = list->next;
1313 }
1314
1315 return false;
1316 }
1317
config_get_entry_list_head(config_file_t * conf,struct config_file_entry * entry)1318 bool config_get_entry_list_head(config_file_t *conf,
1319 struct config_file_entry *entry)
1320 {
1321 const struct config_entry_list *head = conf->entries;
1322
1323 if (!head)
1324 return false;
1325
1326 entry->key = head->key;
1327 entry->value = head->value;
1328 entry->next = head->next;
1329 return true;
1330 }
1331
config_get_entry_list_next(struct config_file_entry * entry)1332 bool config_get_entry_list_next(struct config_file_entry *entry)
1333 {
1334 const struct config_entry_list *next = entry->next;
1335
1336 if (!next)
1337 return false;
1338
1339 entry->key = next->key;
1340 entry->value = next->value;
1341 entry->next = next->next;
1342 return true;
1343 }
1344
config_file_exists(const char * path)1345 bool config_file_exists(const char *path)
1346 {
1347 config_file_t conf;
1348 config_file_initialize(&conf);
1349 if (config_file_load_internal(&conf, path, 0, NULL) == 1)
1350 return false;
1351
1352 config_file_deinitialize(&conf);
1353 return true;
1354 }
1355