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