1 /* XQF - Quake server browser and launcher
2 * Copyright (C) 1998-2000 Roman Pozlevich <roma@botik.ru>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
17 */
18
19 /*
20 * Simple 'ini'-file management.
21 * Function calls should be compatible with GNOME ones.
22 */
23
24 #include <sys/types.h>
25 #include <stdio.h> /* FILE */
26 #include <string.h> /* strchr, strcmp, strlen */
27 #include <stdlib.h> /* strtol, strtod */
28 #include <unistd.h> /* unlink */
29
30 #include <glib.h>
31
32 #include "utils.h"
33 #include "debug.h"
34 #include "config.h"
35
36
37 static GSList* directories = NULL;
38 static GList *files = NULL;
39 static GList *prefix_stack = NULL;
40
41 struct config_file {
42 char *filename;
43 GList *sections;
44 unsigned dirty : 1;
45 unsigned read_only : 1;
46 };
47
48 struct config_section {
49 char *name;
50 GList *keys;
51 };
52
53 struct config_key {
54 char *name;
55 char *value;
56 };
57
58 /* never used
59 #ifdef DEBUG
60 static void dump_base (void) {
61 GList *fptr, *sptr, *kptr;
62 struct config_file *file;
63 struct config_section *section;
64 struct config_key *key;
65
66 for (fptr = files; fptr; fptr = fptr->next) {
67 file = (struct config_file *) fptr->data;
68 fprintf (stderr, "---- %s\n", file->filename);
69
70 for (sptr = file->sections; sptr; sptr = sptr->next) {
71 section = (struct config_section *) sptr->data;
72 fprintf (stderr, "[%s]\n", section->name);
73
74 for (kptr = section->keys; kptr; kptr = kptr->next) {
75 key = (struct config_key *) kptr->data;
76 fprintf (stderr, "%s=%s\n", key->name, key->value);
77 }
78 }
79 }
80 fprintf (stderr, "----\n");
81 }
82 #endif
83 */
84
85 /**
86 * lookup keyname in secname in filename (secname and keyname can be NULL).
87 * When create is set, appropriate entries will be created if they don't
88 * already exist. When file1, section1 and key1 are not NULL (all three are
89 * independent), pointers to the found/created structs are stored there.
90 **/
find_key(const char * filename,const char * secname,const char * keyname,int create,struct config_file ** file1,struct config_section ** section1,struct config_key ** key1)91 static void find_key (const char *filename,
92 const char *secname,
93 const char *keyname,
94 int create,
95 struct config_file **file1,
96 struct config_section **section1,
97 struct config_key **key1) {
98
99 struct config_file *file = NULL;
100 struct config_section *section = NULL;
101 struct config_key *key = NULL;
102 GList *list;
103
104 if (file1) *file1 = NULL;
105 if (section1) *section1 = NULL;
106 if (key1) *key1 = NULL;
107
108 // look if file is already known
109 for (list = files; list; list = list->next) {
110 file = (struct config_file *) list->data;
111 if (strcmp (filename, file->filename) == 0)
112 break;
113 }
114
115 // otherwise create one
116 if (!list) {
117
118 if (!create)
119 return;
120
121 file = g_malloc0 (sizeof (struct config_file));
122 file->filename = g_strdup (filename);
123 files = g_list_append (files, file);
124 }
125
126 if (file1)
127 *file1 = file;
128
129 if (secname) {
130 // see if section is alreay known in file
131 for (list = file->sections; list; list = list->next) {
132 section = (struct config_section *) list->data;
133 if (g_ascii_strcasecmp (secname, section->name) == 0)
134 break;
135 }
136
137 // otherwise create one
138 if (!list) {
139
140 if (!create)
141 return;
142
143 section = g_malloc0 (sizeof (struct config_section));
144 section->name = g_strdup (secname);
145 file->sections = g_list_append (file->sections, section);
146 }
147
148 if (section1)
149 *section1 = section;
150 }
151
152 if (secname && keyname) {
153 // see if key is already in section
154 for (list = section->keys; list; list = list->next) {
155 key = (struct config_key *) list->data;
156 if (g_ascii_strcasecmp (keyname, key->name) == 0)
157 break;
158 }
159
160 // otherwise create one
161 if (!list) {
162
163 if (!create)
164 return;
165
166 key = g_malloc0 (sizeof (struct config_key));
167 key->name = g_strdup (keyname);
168 section->keys = g_list_append (section->keys, key);
169 }
170
171 if (key1)
172 *key1 = key;
173 }
174 }
175
176 /**
177 * lookup keyname in section, if not found create a new config_key.
178 * returns pointer to found or created key
179 */
key_in_section(struct config_section * section,char * keyname)180 static struct config_key *key_in_section (struct config_section *section,
181 char *keyname) {
182 struct config_key *key;
183 GList *list;
184
185 for (list = section->keys; list; list = list->next) {
186 key = (struct config_key *) list->data;
187 if (g_ascii_strcasecmp (keyname, key->name) == 0)
188 break;
189 }
190
191 if (!list) {
192 key = g_malloc0 (sizeof (struct config_key));
193 key->name = g_strdup (keyname);
194 section->keys = g_list_append (section->keys, key);
195 }
196
197 return key;
198 }
199
200
201 #define BUF_SIZE (1024*8)
202
203 struct state_s
204 {
205 const char* filename;
206 char *secname;
207 struct config_section *section;
208 void* data;
209 };
210
parse_line_file(char * buf,struct state_s * state)211 static gboolean parse_line_file(char* buf, struct state_s* state) {
212 struct config_key *key = NULL;
213 char *p;
214 unsigned len;
215
216 len = strlen(buf);
217
218 if (len <= 1)
219 return TRUE;
220
221 if (buf[len - 1] == '\n') {
222 buf[len - 1] = '\0';
223 len--;
224 }
225
226 if (buf[len - 1] == '\r') {
227 buf[len - 1] = '\0';
228 len--;
229 }
230
231 if (*buf == '[') {
232 p = strchr (buf + 1, ']');
233 if (p) {
234 *p = '\0';
235
236 if (state->secname)
237 g_free (state->secname);
238
239 state->secname = g_strdup (buf + 1);
240 state->section = NULL;
241 }
242 }
243 else {
244 p = strchr (buf, '=');
245 if (p) {
246 *p++ = '\0';
247
248 if (!state->section && !state->secname)
249 return TRUE;
250
251 if (!state->section)
252 find_key (state->filename, state->secname, buf, TRUE, NULL, &state->section, &key);
253 else
254 key = key_in_section (state->section, buf);
255
256 if (key->value)
257 g_free (key->value);
258
259 key->value = g_strdup (p);
260 }
261 }
262
263 return TRUE;
264 }
265
266 #define BEGIN_XQF_INFO "### BEGIN XQF INFO"
267 #define END_XQF_INFO "### END XQF INFO"
parse_line_script(char * buf,struct state_s * state)268 static gboolean parse_line_script(char* buf, struct state_s* state) {
269 switch(GPOINTER_TO_INT(state->data)) {
270 case 0:
271 if (!strncmp(buf, BEGIN_XQF_INFO, strlen(BEGIN_XQF_INFO)))
272 state->data = GINT_TO_POINTER(1);
273 return TRUE;
274
275 case 1:
276 if (!strncmp(buf, "# ", 2))
277 return parse_line_file(buf+2, state);
278 else if (!strncmp(buf, END_XQF_INFO, strlen(END_XQF_INFO)))
279 return FALSE;
280 }
281 return FALSE;
282 }
283 #undef BEGIN_XQF_INFO
284 #undef END_XQF_INFO
285
load_file(const char * filename)286 static void load_file (const char *filename) {
287 FILE *f = NULL;
288 char buf[BUF_SIZE];
289 char *fn;
290 struct state_s state = {0};
291 GSList* l;
292 gboolean (*parse_line)(char* buf, struct state_s* state);
293 struct config_file* file = NULL;
294
295 debug (4, "%s", filename);
296
297 for (l = directories; l && !f; l = g_slist_next(l)) {
298 fn = file_in_dir (l->data, filename);
299 f = fopen (fn, "r");
300 if (f) debug(4, "loaded %s", fn);
301 g_free (fn);
302 }
303
304 /* Create empty file record and thus cache lookups of non-existent files.
305 * Don't mark it 'dirty' -- nothing was changed really.
306 */
307
308 find_key (filename, NULL, NULL, TRUE, &file, NULL, NULL);
309
310 if (!f)
311 return;
312
313 state.filename = filename;
314
315 // XXX:
316 if (!strncmp(filename, "scripts/", strlen("scripts/"))) {
317 parse_line = parse_line_script;
318 file->read_only = 1;
319 }
320 else
321 parse_line = parse_line_file;
322
323 while (fgets (buf, BUF_SIZE, f))
324 parse_line(buf, &state);
325
326 g_free(state.secname);
327 fclose (f);
328 }
329
330
unlink_file(const char * filename)331 static void unlink_file (const char *filename) {
332 char *fn;
333
334 fn = file_in_dir (directories->data, filename);
335 #ifdef DEBUG
336 fprintf (stderr, "config.c: unlink_file (%s)\n", fn);
337 #endif
338 unlink (fn);
339 g_free (fn);
340 }
341
342
path_concat(const char * str1,const char * str2)343 static char *path_concat (const char *str1, const char *str2) {
344 if (str1[strlen (str1) - 1] != '/')
345 return g_strconcat (str1, "/", str2, NULL);
346 else
347 return g_strconcat (str1, str2, NULL);
348 }
349
350
parse_path(const char * path,int create,char ** defval,struct config_file ** file1,struct config_section ** section1)351 static struct config_key *parse_path (const char *path,
352 int create,
353 char **defval,
354 struct config_file **file1,
355 struct config_section **section1) {
356 const char *filename;
357 char *secname = NULL;
358 char *keyname = NULL;
359 char *def;
360 char *buf = NULL;
361 char *ptr = NULL;
362 struct config_file *file;
363 struct config_key *key = NULL;
364
365 if (defval) {
366 *defval = NULL;
367 def = strchr (path, '=');
368 if (def)
369 *defval = def + 1;
370 }
371
372 if (*path != '/') {
373 if (prefix_stack)
374 buf = path_concat ((const char *) prefix_stack->data, path);
375 else
376 return NULL;
377
378 if (*buf != '/') {
379 g_free (buf);
380 return NULL;
381 }
382 }
383 else {
384 buf = g_strdup (path);
385 }
386
387 def = strchr (buf, '=');
388 if (def)
389 *def = '\0';
390
391 filename = ptr = &buf[1];
392
393 // XXX:
394 if (!strncmp(buf, "/scripts", strlen("/scripts"))) {
395 ptr = strchr (ptr, '/');
396 if (ptr)
397 ++ptr;
398 }
399
400 secname = strchr (ptr, '/');
401 if (secname) {
402 *secname++ = '\0';
403
404 keyname = strchr (secname, '/');
405 if (keyname)
406 *keyname++ = '\0';
407 }
408
409 /* Ensure that the file is loaded */
410
411 find_key (filename, NULL, NULL, FALSE, &file, NULL, NULL);
412 if (!file)
413 load_file (filename);
414
415 /* Actually look up the key */
416
417 find_key (filename, secname, keyname, create, file1, section1, &key);
418
419 if (buf)
420 g_free (buf);
421
422 return key;
423 }
424
425
string_escape(const char * s)426 static char *string_escape (const char *s) {
427 const char *c;
428 char *p;
429 char *res;
430 int n = 0;
431
432 if (!s)
433 return NULL;
434
435 for (c = s; *c; c++) {
436 if (*c == '\n' || *c == '\r' || *c == '\\') {
437 n++;
438 }
439 n++;
440 }
441
442 p = res = g_malloc (n + 1);
443
444 do {
445 switch (*s) {
446
447 case '\n':
448 *p++ = '\\';
449 *p++ = 'n';
450 break;
451
452 case '\r':
453 *p++ = '\\';
454 *p++ = 'r';
455 break;
456
457 case '\\':
458 *p++ = '\\';
459 *p++ = '\\';
460 break;
461
462 default:
463 *p++ = *s;
464 break;
465
466 }
467 } while (*s++);
468
469 return res;
470 }
471
472
string_unescape(const char * s)473 static char *string_unescape (const char *s) {
474 const char *c;
475 char *p;
476 char *res;
477 int n = 0;
478
479 if (!s)
480 return NULL;
481
482 for (c = s; *c; c++) {
483 if (*c == '\\' && (c[1] == 'n' || c[1] == 'r' || c[1] == '\\'))
484 c++;
485 n++;
486 }
487
488 p = res = g_malloc (n + 1);
489
490 do {
491
492 if (*s != '\\') {
493 *p++ = *s;
494 }
495 else {
496 s++;
497
498 switch (*s) {
499
500 case 'n':
501 *p++ = '\n';
502 break;
503
504 case 'r':
505 *p++ = '\r';
506 break;
507
508 case '\\':
509 *p++ = '\\';
510 break;
511
512 default:
513 *p++ = '\\';
514 *p++ = *s;
515 break;
516
517 }
518 }
519
520 } while (*s++);
521
522 return res;
523 }
524
525
config_get_raw_with_default(const char * path,int * def)526 static char *config_get_raw_with_default (const char *path, int *def) {
527 struct config_file *file;
528 struct config_key *key;
529 char *val;
530
531 key = parse_path (path, FALSE, &val, &file, NULL);
532 if (key) {
533 if (def) *def = FALSE;
534 val = key->value;
535 }
536 else {
537 if (def) *def = TRUE;
538 }
539 return val;
540 }
541
542
config_get_int_with_default(const char * path,int * def)543 int config_get_int_with_default (const char *path, int *def) {
544 char *val;
545
546 val = config_get_raw_with_default (path, def);
547 return (val)? strtol (val, NULL, 10) : 0;
548 }
549
550
config_get_float_with_default(const char * path,int * def)551 double config_get_float_with_default (const char *path, int *def) {
552 char *val;
553
554 val = config_get_raw_with_default (path, def);
555 return (val)? strtod (val, NULL) : 0.0;
556 }
557
558
config_get_bool_with_default(const char * path,int * def)559 int config_get_bool_with_default (const char *path, int *def) {
560 char *val;
561
562 val = config_get_raw_with_default (path, def);
563
564 /* Gnome returns FALSE as default value */
565
566 if (val && !g_ascii_strcasecmp (val, "true"))
567 return TRUE;
568 else
569 return FALSE;
570 }
571
572
config_get_string_with_default(const char * path,int * def)573 char *config_get_string_with_default (const char *path, int *def) {
574 char *val;
575
576 val = config_get_raw_with_default (path, def);
577 return (val)? string_unescape (val) : NULL;
578 }
579
580
config_set_raw(const char * path,char * raw_value)581 static void config_set_raw (const char *path, char *raw_value) {
582 struct config_key *key;
583 struct config_file *file;
584
585 key = parse_path (path, TRUE, NULL, &file, NULL);
586 if (key) {
587 if (key->value)
588 g_free (key->value);
589 key->value = raw_value;
590 if (file)
591 file->dirty = TRUE;
592 }
593 }
594
595
config_set_int(const char * path,int i)596 void config_set_int (const char *path, int i) {
597 config_set_raw (path, g_strdup_printf ("%d", i));
598 }
599
600
config_set_float(const char * path,double f)601 void config_set_float (const char *path, double f) {
602 config_set_raw (path, g_strdup_printf ("%g", f));
603 }
604
605
config_set_bool(const char * path,gboolean b)606 void config_set_bool (const char *path, gboolean b) {
607 config_set_raw (path, g_strdup ((b)? "true" : "false"));
608 }
609
610
config_set_string(const char * path,const char * str)611 void config_set_string (const char *path, const char *str) {
612 config_set_raw (path, string_escape (str));
613 }
614
615
config_init_iterator(const char * path)616 config_key_iterator* config_init_iterator (const char *path) {
617 struct config_section *section;
618
619 parse_path (path, FALSE, NULL, NULL, §ion);
620 return (section)? (config_key_iterator*)section->keys : NULL;
621 }
622
623
config_iterator_next(config_key_iterator * iterator,char ** key,char ** val)624 config_key_iterator* config_iterator_next (config_key_iterator* iterator, char **key, char **val) {
625 struct config_key *cfg_key;
626
627 if (!iterator)
628 return NULL;
629
630 cfg_key = ((GList *) iterator)->data;
631
632 if (key) *key = g_strdup (cfg_key->name);
633 if (val) *val = g_strdup (cfg_key->value);
634
635 return (config_key_iterator*)((GList *) iterator)->next;
636 }
637
config_init_section_iterator(const char * path)638 config_section_iterator* config_init_section_iterator (const char *path) {
639 struct config_file *file;
640
641 parse_path (path, FALSE, NULL, &file, NULL);
642 return (file)? (config_section_iterator*)file->sections : NULL;
643 }
644
645
config_section_iterator_next(config_section_iterator * iterator,char ** section)646 config_section_iterator* config_section_iterator_next (config_section_iterator* iterator, char **section) {
647 struct config_section *cfg_sect;
648
649 if (!iterator)
650 return NULL;
651
652 cfg_sect = ((GList *) iterator)->data;
653
654 if (section) *section = g_strdup (cfg_sect->name);
655
656 return (config_section_iterator*)((GList *) iterator)->next;
657 }
658
659
660
dump_file(struct config_file * file)661 static void dump_file (struct config_file *file) {
662 GList *sptr, *kptr;
663 struct config_section *section;
664 struct config_key *key;
665 FILE *f;
666 char *fn;
667
668 if (!file->dirty)
669 return;
670
671 if (file->read_only)
672 return;
673
674 if (!file->sections) {
675 unlink_file (file->filename);
676 return;
677 }
678
679 fn = file_in_dir (directories->data, file->filename);
680 f = fopen (fn, "w");
681 g_free (fn);
682
683 if (f) {
684 for (sptr = file->sections; sptr; sptr = sptr->next) {
685 section = (struct config_section *) sptr->data;
686 fprintf (f, "[%s]\n", section->name);
687
688 for (kptr = section->keys; kptr; kptr = kptr->next) {
689 key = (struct config_key *) kptr->data;
690 fprintf (f, "%s=%s\n", key->name, key->value);
691 }
692
693 fprintf (f, "\n");
694 }
695 fclose (f);
696 }
697 }
698
699
config_sync(void)700 void config_sync (void) {
701 GList *list;
702 struct config_file *file;
703
704 for (list = files; list; list = list->next) {
705 file = (struct config_file *) list->data;
706 dump_file (file);
707 }
708 /* config_drop_all (); */
709 }
710
711
drop_file(struct config_file * file)712 static void drop_file (struct config_file *file) {
713 GList *sptr, *kptr;
714 struct config_section *section;
715 struct config_key *key;
716
717 #ifdef DEBUG
718 fprintf (stderr, "config.c: drop_file (%s)\n", file->filename);
719 #endif
720
721 for (sptr = file->sections; sptr; sptr = sptr->next) {
722 section = (struct config_section *) sptr->data;
723
724 for (kptr = section->keys; kptr; kptr = kptr->next) {
725 key = (struct config_key *) kptr->data;
726 g_free (key->name);
727 g_free (key->value);
728 g_free(key);
729 key=NULL;
730 }
731
732 g_list_free (section->keys);
733 g_free (section->name);
734 g_free(section);
735 section=NULL;
736 }
737
738 g_list_free (file->sections);
739 g_free (file->filename);
740
741 files = g_list_remove (files, file);
742
743 g_free(file);
744 }
745
746
drop_section(struct config_file * file,struct config_section * section)747 static void drop_section (struct config_file *file,
748 struct config_section *section) {
749 GList *kptr;
750 struct config_key *key;
751
752 for (kptr = section->keys; kptr; kptr = kptr->next) {
753 key = (struct config_key *) kptr->data;
754 g_free (key->name);
755 g_free (key->value);
756 g_free (key);
757 }
758 g_list_free (section->keys);
759 g_free (section->name);
760 file->sections = g_list_remove (file->sections, section);
761
762 g_free(section);
763
764 file->dirty = TRUE;
765 }
766
767
drop_key(struct config_file * file,struct config_section * section,struct config_key * key)768 static void drop_key (struct config_file *file,
769 struct config_section *section,
770 struct config_key *key) {
771 g_free (key->name);
772 g_free (key->value);
773 section->keys = g_list_remove (section->keys, key);
774 g_free(key);
775
776 file->dirty = TRUE;
777
778 if (section->keys == NULL)
779 drop_section (file, section);
780 }
781
782
config_drop_all(void)783 void config_drop_all (void) {
784 while (files) {
785 drop_file ((struct config_file *) files->data);
786 }
787 }
788
789
config_drop_file(const char * path)790 void config_drop_file (const char *path) {
791 struct config_file *file = NULL;
792
793 parse_path (path, FALSE, NULL, &file, NULL);
794 if (file)
795 drop_file (file);
796 }
797
798
config_clean_key(const char * path)799 void config_clean_key (const char *path) {
800 struct config_file *file;
801 struct config_section *section;
802 struct config_key *key;
803
804 key = parse_path (path, FALSE, NULL, &file, §ion);
805 if (key)
806 drop_key (file, section, key);
807 }
808
809
config_clean_section(const char * path)810 void config_clean_section (const char *path) {
811 struct config_file *file;
812 struct config_section *section = NULL;
813
814 parse_path (path, FALSE, NULL, &file, §ion);
815 if (section)
816 drop_section (file, section);
817 }
818
819
config_clean_file(const char * path)820 void config_clean_file (const char *path) {
821 struct config_file *file = NULL;
822 char *filename;
823
824 parse_path (path, FALSE, NULL, &file, NULL);
825 if (file) {
826 filename = g_strdup (file->filename);
827
828 drop_file (file);
829
830 /* Re-create file record and mark it as 'dirty' */
831
832 find_key (filename, NULL, NULL, TRUE, &file, NULL, NULL);
833 file->dirty = TRUE;
834
835 g_free (filename);
836 }
837 }
838
839
config_push_prefix(const char * prefix)840 void config_push_prefix (const char *prefix) {
841 if (prefix && *prefix)
842 prefix_stack = g_list_prepend (prefix_stack, g_strdup (prefix));
843 }
844
845
config_pop_prefix(void)846 void config_pop_prefix (void) {
847 void *data;
848
849 if (prefix_stack) {
850 data = prefix_stack->data;
851 prefix_stack = g_list_remove (prefix_stack, data);
852 g_free (data);
853 }
854 }
855
856
config_add_dir(const char * dir)857 void config_add_dir (const char *dir) {
858 directories = g_slist_prepend(directories,g_strdup (dir));
859 }
860
861