1 /* Copyright (C) 2010-2020 The RetroArch team
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (config_file.c).
5 * ---------------------------------------------------------------------------------------
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:
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
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>
40 #define MAX_INCLUDE_DEPTH 16
42 struct config_include_list
45 struct config_include_list *next;
48 /* Forward declaration */
49 static bool config_file_parse_line(config_file_t *conf,
50 struct config_entry_list *list, char *line, config_file_cb_t *cb);
52 static int config_file_sort_compare_func(struct config_entry_list *a,
53 struct config_entry_list *b)
60 return strcasecmp(a->key, b->key);
70 /* https://stackoverflow.com/questions/7685/merge-sort-a-linked-list */
71 static struct config_entry_list* config_file_merge_sort_linked_list(
72 struct config_entry_list *list, int (*compare)(
73 struct config_entry_list *one,struct config_entry_list *two))
75 struct config_entry_list
84 if (!list || !list->next)
87 /* Find halfway through the list (by running two pointers,
88 * one at twice the speed of the other). */
89 while (temp && temp->next)
93 temp = temp->next->next;
96 /* Break the list in two. (prev pointers are broken here,
97 * but we fix later) */
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);
105 while (list || right)
107 /* Take from empty lists, or compare: */
118 else if (compare(list, right) < 0)
141 * config_file_strip_comment:
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
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
159 static char *config_file_strip_comment(char *str)
161 /* Search for a comment (#) character */
162 char *comment = strchr(str, '#');
166 char *literal_start = NULL;
168 /* Check whether entire line is a comment
169 * > First character == '#' */
172 /* Set 'str' to NUL and return comment
173 * for processing at a higher level */
178 /* Comment character occurs at an offset:
179 * Search for the start of a string literal value */
180 literal_start = strchr(str, '\"');
182 /* Check whether string literal start occurs
183 * *before* the comment character */
184 if (literal_start && (literal_start < comment))
186 /* Search for the end of the string literal
188 char *literal_end = strchr(literal_start + 1, '\"');
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))
198 /* If we reach this point, then a comment
199 * exists outside of a string literal
200 * > Trim the entire comment from the end
208 static char *config_file_extract_value(char *line)
211 while (ISSPACE((int)*line))
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 */
221 /* If first character is ("), we have a full string
227 /* Skip to next character */
230 /* If this a ("), then value string is empty */
233 /* Find the next (") character */
234 while (line[idx] && (line[idx] != '\"'))
238 if ((value = line) && *value)
239 return strdup(value);
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')
249 /* Find next space character */
250 while (line[idx] && isgraph((int)line[idx]))
254 if ((value = line) && *value)
255 return strdup(value);
258 /* Note 2: This is an unrolled strldup call
259 * to avoid an unnecessary dependency -
260 * call is strldup("", sizeof(""))
262 dst = (char*)malloc(sizeof(char) * 2);
267 /* Move semantics? */
268 static void config_file_add_child_list(config_file_t *parent,
269 config_file_t *child)
271 struct config_entry_list *list = child->entries;
272 bool merge_hash_map = false;
276 struct config_entry_list *head = parent->entries;
280 /* set list readonly */
283 list->readonly = true;
286 head->next = child->entries;
288 merge_hash_map = true;
292 /* set list readonly */
295 list->readonly = true;
298 parent->entries = child->entries;
304 struct config_entry_list *head =
305 (struct config_entry_list*)parent->entries;
314 /* Update hash map */
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++)
325 uint32_t child_hash = RHMAP_KEY(child->entries_map, i);
326 const char *child_key = RHMAP_KEY_STR(child->entries_map, i);
330 !RHMAP_HAS_FULL(parent->entries_map, child_hash, child_key))
332 struct config_entry_list *entry = child->entries_map[i];
335 RHMAP_SET_FULL(parent->entries_map, child_hash, child_key, entry);
339 /* Child entries map is no longer required,
341 RHMAP_FREE(child->entries_map);
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;
352 child->entries = NULL;
355 static void config_file_get_realpath(char *s, size_t len,
356 char *path, const char *config_path)
359 if (!string_is_empty(config_path))
360 fill_pathname_resolve_relative(s, config_path,
363 #if !defined(__PSL1GHT__) && !defined(__PS3__)
366 const char *home = getenv("HOME");
369 strlcpy(s, home, len);
370 strlcat(s, path + 1, len);
373 strlcpy(s, path + 1, len);
377 if (!string_is_empty(config_path))
378 fill_pathname_resolve_relative(s, config_path, path, len);
382 static void config_file_add_sub_conf(config_file_t *conf, char *path,
383 char *real_path, size_t len, config_file_cb_t *cb)
385 struct config_include_list *head = conf->includes;
386 struct config_include_list *node = (struct config_include_list*)
387 malloc(sizeof(*node));
392 /* Add include list */
393 node->path = strdup(path);
403 conf->includes = node;
406 config_file_get_realpath(real_path, len, path,
410 void config_file_add_reference(config_file_t *conf, char *path)
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);
420 static int config_file_load_internal(
421 struct config_file *conf,
422 const char *path, unsigned depth, config_file_cb_t *cb)
425 char *new_path = strdup(path);
429 conf->path = new_path;
430 conf->include_depth = depth;
432 if (!(file = filestream_open(path,
433 RETRO_VFS_FILE_ACCESS_READ,
434 RETRO_VFS_FILE_ACCESS_HINT_NONE)))
440 while (!filestream_eof(file))
443 struct config_entry_list *list = (struct config_entry_list*)
444 malloc(sizeof(*list));
448 filestream_close(file);
452 list->readonly = false;
457 line = filestream_getline(file);
466 !string_is_empty(line)
467 && config_file_parse_line(conf, list, line, cb))
470 conf->tail->next = list;
472 conf->entries = list;
478 /* Only add entry to the map if an entry
479 * with the specified value does not
481 uint32_t hash = rhmap_hash_string(list->key);
483 if (!RHMAP_HAS_FULL(conf->entries_map, hash, list->key))
485 RHMAP_SET_FULL(conf->entries_map, hash, list->key, list);
487 if (cb && list->value)
488 cb->config_file_new_entry_cb(list->key, list->value);
495 if (list != conf->tail)
499 filestream_close(file);
504 static bool config_file_parse_line(config_file_t *conf,
505 struct config_entry_list *list, char *line, config_file_cb_t *cb)
507 size_t cur_size = 32;
510 char *key_tmp = NULL;
511 /* Remove any comment text */
512 char *comment = config_file_strip_comment(line);
514 /* Check whether entire line is a comment */
518 bool include_found = string_starts_with_size(comment,
520 STRLEN_CONST("include "));
521 bool reference_found = string_starts_with_size(comment,
523 STRLEN_CONST("reference "));
525 /* All comments except those starting with the include or
526 * reference directive are ignored */
527 if (!include_found && !reference_found)
530 /* Starting a line with an 'include' directive
531 * appends a sub-config file */
534 config_file_t sub_conf;
535 char real_path[PATH_MAX_LENGTH];
536 char *include_line = comment + STRLEN_CONST("include ");
538 if (string_is_empty(include_line))
541 if (!(path = config_file_extract_value(include_line)))
544 if ( string_is_empty(path)
545 || conf->include_depth >= MAX_INCLUDE_DEPTH)
551 config_file_add_sub_conf(conf, path,
552 real_path, sizeof(real_path), cb);
554 config_file_initialize(&sub_conf);
556 switch (config_file_load_internal(&sub_conf, real_path,
557 conf->include_depth + 1, cb))
560 /* Pilfer internal list. */
561 config_file_add_child_list(conf, &sub_conf);
562 /* fall-through to deinitialize */
564 config_file_deinitialize(&sub_conf);
572 /* Starting a line with an 'reference' directive
573 * sets the reference path */
576 char *reference_line = comment + STRLEN_CONST("reference ");
578 if (string_is_empty(reference_line))
581 if (!(path = config_file_extract_value(reference_line)))
584 config_file_add_reference(conf, path);
594 /* Skip to first non-space character */
595 while (ISSPACE((int)*line))
598 /* Allocate storage for key */
599 if (!(key = (char*)malloc(cur_size + 1)))
602 /* Copy line contents into key until we
603 * reach the next space character */
604 while (isgraph((int)*line))
606 /* If current key storage is too small,
611 if (!(key_tmp = (char*)realloc(key, cur_size + 1)))
620 key[idx++] = *line++;
624 /* Add key and value entries to list */
627 /* An entry without a value is invalid */
628 while (ISSPACE((int)*line))
631 /* If we don't have an equal sign here,
632 * we've got an invalid string. */
641 if (!(list->value = config_file_extract_value(line)))
652 static int config_file_from_string_internal(
653 struct config_file *conf,
657 char *lines = from_string;
658 char *save_ptr = NULL;
661 if (!string_is_empty(path))
662 conf->path = strdup(path);
663 if (string_is_empty(lines))
666 /* Get first line of config file */
667 line = strtok_r(lines, "\n", &save_ptr);
671 struct config_entry_list *list = (struct config_entry_list*)
672 malloc(sizeof(*list));
677 list->readonly = false;
682 /* Parse current line */
684 !string_is_empty(line)
685 && config_file_parse_line(conf, list, line, NULL))
688 conf->tail->next = list;
690 conf->entries = list;
696 /* Only add entry to the map if an entry
697 * with the specified value does not
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);
705 if (list != conf->tail)
708 /* Get next line of config file */
709 line = strtok_r(NULL, "\n", &save_ptr);
716 bool config_file_deinitialize(config_file_t *conf)
718 struct config_include_list *inc_tmp = NULL;
719 struct config_entry_list *tmp = NULL;
727 struct config_entry_list *hold = NULL;
743 inc_tmp = (struct config_include_list*)conf->includes;
746 struct config_include_list *hold = NULL;
749 hold = (struct config_include_list*)inc_tmp;
750 inc_tmp = inc_tmp->next;
755 path_linked_list_free(conf->references);
760 RHMAP_FREE(conf->entries_map);
770 void config_file_free(config_file_t *conf)
772 if (config_file_deinitialize(conf))
777 * config_append_file:
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.
782 bool config_append_file(config_file_t *conf, const char *path)
785 config_file_t *new_conf = config_file_new_from_path_to_string(path);
790 /* Update hash map */
791 for (i = 0, cap = RHMAP_CAP(new_conf->entries_map); i != cap; i++)
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);
796 if (new_hash && new_key)
798 struct config_entry_list *entry = new_conf->entries_map[i];
801 RHMAP_SET_FULL(conf->entries_map, new_hash, new_key, entry);
807 new_conf->tail->next = conf->entries;
808 conf->entries = new_conf->entries; /* Pilfer. */
809 new_conf->entries = NULL;
812 config_file_free(new_conf);
817 * config_file_new_from_string:
819 * Load a config file from a string.
821 * NOTE: This will modify @from_string.
822 * Pass a copy of source string if original
823 * contents must be preserved
825 config_file_t *config_file_new_from_string(char *from_string,
828 struct config_file *conf = config_file_new_alloc();
830 && config_file_from_string_internal(
831 conf, from_string, path) != -1)
834 config_file_free(conf);
838 config_file_t *config_file_new_from_path_to_string(const char *path)
840 if (path_is_valid(path))
842 uint8_t *ret_buf = NULL;
844 if (filestream_read_file(path, (void**)&ret_buf, &length))
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() */
851 conf = config_file_new_from_string((char*)ret_buf, path);
854 free((void*)ret_buf);
864 * config_file_new_with_callback:
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.
870 * @return Returns NULL if file doesn't exist.
872 config_file_t *config_file_new_with_callback(
873 const char *path, config_file_cb_t *cb)
876 struct config_file *conf = config_file_new_alloc();
879 if ((ret = config_file_load_internal(conf, path, 0, cb)) == -1)
881 config_file_free(conf);
895 * Loads a config file.
896 * If @path is NULL, will create an empty config file.
898 * @return Returns NULL if file doesn't exist.
900 config_file_t *config_file_new(const char *path)
903 struct config_file *conf = config_file_new_alloc();
906 if ((ret = config_file_load_internal(conf, path, 0, NULL)) == -1)
908 config_file_free(conf);
920 * config_file_initialize:
924 void config_file_initialize(struct config_file *conf)
930 conf->entries_map = NULL;
931 conf->entries = NULL;
934 conf->references = NULL;
935 conf->includes = NULL;
936 conf->include_depth = 0;
937 conf->guaranteed_no_duplicates = false;
938 conf->modified = false;
941 config_file_t *config_file_new_alloc(void)
943 struct config_file *conf = (struct config_file*)malloc(sizeof(*conf));
946 config_file_initialize(conf);
951 * config_get_entry_internal:
955 static struct config_entry_list *config_get_entry_internal(
956 const config_file_t *conf,
957 const char *key, struct config_entry_list **prev)
959 struct config_entry_list *entry = RHMAP_GET_STR(conf->entries_map, key);
966 struct config_entry_list *previous = *prev;
967 for (entry = conf->entries; entry; entry = entry->next)
976 struct config_entry_list *config_get_entry(
977 const config_file_t *conf, const char *key)
979 return RHMAP_GET_STR(conf->entries_map, key);
985 * Extracts a double from config file.
987 * @return true if found, otherwise false.
989 bool config_get_double(config_file_t *conf, const char *key, double *in)
991 const struct config_entry_list *entry = config_get_entry(conf, key);
996 *in = strtod(entry->value, NULL);
1003 * Extracts a float from config file.
1005 * @return true if found, otherwise false.
1007 bool config_get_float(config_file_t *conf, const char *key, float *in)
1009 const struct config_entry_list *entry = config_get_entry(conf, key);
1014 /* strtof() is C99/POSIX. Just use the more portable kind. */
1015 *in = (float)strtod(entry->value, NULL);
1019 bool config_get_int(config_file_t *conf, const char *key, int *in)
1021 const struct config_entry_list *entry = config_get_entry(conf, key);
1026 int val = (int)strtol(entry->value, NULL, 0);
1038 bool config_get_size_t(config_file_t *conf, const char *key, size_t *in)
1040 const struct config_entry_list *entry = config_get_entry(conf, key);
1046 if (sscanf(entry->value, "%" PRI_SIZET, &val) == 1)
1056 #if defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
1057 bool config_get_uint64(config_file_t *conf, const char *key, uint64_t *in)
1059 const struct config_entry_list *entry = config_get_entry(conf, key);
1064 uint64_t val = strtoull(entry->value, NULL, 0);
1076 bool config_get_uint(config_file_t *conf, const char *key, unsigned *in)
1078 const struct config_entry_list *entry = config_get_entry(conf, key);
1083 unsigned val = (unsigned)strtoul(entry->value, NULL, 0);
1095 bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
1097 const struct config_entry_list *entry = config_get_entry(conf, key);
1102 unsigned val = (unsigned)strtoul(entry->value, NULL, 16);
1117 * Extracts a single char from config file.
1118 * If value consists of several chars, this is an error.
1120 * @return true if found, otherwise false.
1122 bool config_get_char(config_file_t *conf, const char *key, char *in)
1124 const struct config_entry_list *entry = config_get_entry(conf, key);
1128 if (entry->value[0] && entry->value[1])
1131 *in = *entry->value;
1139 * config_get_string:
1141 * Extracts an allocated string in *in. This must be free()-d if
1142 * this function succeeds.
1144 * @return true if found, otherwise false.
1146 bool config_get_string(config_file_t *conf, const char *key, char **str)
1148 const struct config_entry_list *entry = config_get_entry(conf, key);
1150 if (!entry || !entry->value)
1153 *str = strdup(entry->value);
1158 * config_get_config_path:
1160 * Extracts a string to a preallocated buffer.
1161 * Avoid memory allocation.
1163 bool config_get_config_path(config_file_t *conf, char *s, size_t len)
1166 return strlcpy(s, conf->path, len);
1170 bool config_get_array(config_file_t *conf, const char *key,
1171 char *buf, size_t size)
1173 const struct config_entry_list *entry = config_get_entry(conf, key);
1175 return strlcpy(buf, entry->value, size) < size;
1179 bool config_get_path(config_file_t *conf, const char *key,
1180 char *buf, size_t size)
1182 #if defined(RARCH_CONSOLE) || !defined(RARCH_INTERNAL)
1183 if (config_get_array(conf, key, buf, size))
1186 const struct config_entry_list *entry = config_get_entry(conf, key);
1189 fill_pathname_expand_special(buf, entry->value, size);
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.
1203 * @return true if preconditions are true, otherwise false.
1205 bool config_get_bool(config_file_t *conf, const char *key, bool *in)
1207 const struct config_entry_list *entry = config_get_entry(conf, key);
1214 entry->value[0] == '1'
1215 && entry->value[1] == '\0'
1217 || string_is_equal(entry->value, "true")
1222 entry->value[0] == '0'
1223 && entry->value[1] == '\0'
1225 || string_is_equal(entry->value, "false")
1234 void config_set_string(config_file_t *conf, const char *key, const char *val)
1236 struct config_entry_list *last = NULL;
1237 struct config_entry_list *entry = NULL;
1239 if (!conf || !key || !val)
1242 last = conf->entries;
1244 if (conf->guaranteed_no_duplicates)
1251 if ((entry = config_get_entry_internal(conf, key, &last)))
1253 /* An entry corresponding to 'key' already exists
1254 * > Check whether value is currently set */
1257 /* Do nothing if value is unchanged */
1258 if (string_is_equal(entry->value, val))
1261 /* Value is to be updated
1262 * > Free existing */
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;
1276 /* Entry corresponding to 'key' does not exist
1277 * > Create new entry */
1278 if (!(entry = (struct config_entry_list*)malloc(sizeof(*entry))))
1281 entry->readonly = false;
1282 entry->key = strdup(key);
1283 entry->value = strdup(val);
1285 conf->modified = true;
1290 conf->entries = entry;
1294 RHMAP_SET_STR(conf->entries_map, entry->key, entry);
1297 void config_unset(config_file_t *conf, const char *key)
1299 struct config_entry_list *last = NULL;
1300 struct config_entry_list *entry = NULL;
1305 last = conf->entries;
1307 if (!(entry = config_get_entry_internal(conf, key, &last)))
1310 (void)RHMAP_DEL_STR(conf->entries_map, entry->key);
1319 entry->value = NULL;
1320 conf->modified = true;
1323 void config_set_path(config_file_t *conf, const char *entry, const char *val)
1325 #if defined(RARCH_CONSOLE) || !defined(RARCH_INTERNAL)
1326 config_set_string(conf, entry, val);
1328 char buf[PATH_MAX_LENGTH];
1329 fill_pathname_abbreviate_special(buf, val, sizeof(buf));
1330 config_set_string(conf, entry, buf);
1334 void config_set_double(config_file_t *conf, const char *key, double val)
1338 snprintf(buf, sizeof(buf), "%f", (float)val);
1339 #elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
1340 snprintf(buf, sizeof(buf), "%lf", val);
1342 snprintf(buf, sizeof(buf), "%f", (float)val);
1344 config_set_string(conf, key, buf);
1347 void config_set_float(config_file_t *conf, const char *key, float val)
1350 snprintf(buf, sizeof(buf), "%f", val);
1351 config_set_string(conf, key, buf);
1354 void config_set_int(config_file_t *conf, const char *key, int val)
1357 snprintf(buf, sizeof(buf), "%d", val);
1358 config_set_string(conf, key, buf);
1361 void config_set_uint(config_file_t *conf, const char *key, unsigned int val)
1364 snprintf(buf, sizeof(buf), "%u", val);
1365 config_set_string(conf, key, buf);
1368 void config_set_hex(config_file_t *conf, const char *key, unsigned val)
1371 snprintf(buf, sizeof(buf), "%x", val);
1372 config_set_string(conf, key, buf);
1375 void config_set_uint64(config_file_t *conf, const char *key, uint64_t val)
1378 snprintf(buf, sizeof(buf), "%" PRIu64, val);
1379 config_set_string(conf, key, buf);
1382 void config_set_char(config_file_t *conf, const char *key, char val)
1385 snprintf(buf, sizeof(buf), "%c", val);
1386 config_set_string(conf, key, buf);
1392 * TODO/FIXME - could be turned into a trivial macro or removed
1394 void config_set_bool(config_file_t *conf, const char *key, bool val)
1396 config_set_string(conf, key, val ? "true" : "false");
1400 * config_file_write:
1402 * Write the current config to a file.
1404 bool config_file_write(config_file_t *conf, const char *path, bool sort)
1409 if (!conf->modified)
1412 if (!string_is_empty(path))
1415 FILE *file = (FILE*)fopen_utf8(path, "wb");
1419 buf = calloc(1, 0x4000);
1420 setvbuf(file, (char*)buf, _IOFBF, 0x4000);
1422 config_file_dump(conf, file, sort);
1429 /* Only update modified flag if config file
1430 * is actually written to disk */
1431 conf->modified = false;
1434 config_file_dump(conf, stdout, sort);
1442 * Dump the current config to an already opened file.
1443 * Does not close the file.
1445 void config_file_dump(config_file_t *conf, FILE *file, bool sort)
1447 struct config_entry_list *list = NULL;
1448 struct config_include_list *includes = conf->includes;
1449 struct path_linked_list *ref_tmp = conf->references;
1453 pathname_make_slashes_portable(ref_tmp->path);
1454 fprintf(file, "#reference \"%s\"\n", ref_tmp->path);
1455 ref_tmp = ref_tmp->next;
1459 list = config_file_merge_sort_linked_list(
1460 (struct config_entry_list*)conf->entries,
1461 config_file_sort_compare_func);
1463 list = (struct config_entry_list*)conf->entries;
1465 conf->entries = list;
1469 if (!list->readonly && list->key)
1470 fprintf(file, "%s = \"%s\"\n", list->key, list->value);
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 */
1482 fprintf(file, "#include \"%s\"\n", includes->path);
1483 includes = includes->next;
1488 * config_get_entry_list_head:
1492 bool config_get_entry_list_head(config_file_t *conf,
1493 struct config_file_entry *entry)
1495 const struct config_entry_list *head = conf->entries;
1500 entry->key = head->key;
1501 entry->value = head->value;
1502 entry->next = head->next;
1507 * config_get_entry_list_next:
1511 bool config_get_entry_list_next(struct config_file_entry *entry)
1513 const struct config_entry_list *next = entry->next;
1518 entry->key = next->key;
1519 entry->value = next->value;
1520 entry->next = next->next;