1 /*
2 * Schism Tracker - a cross-platform Impulse Tracker clone
3 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5 * copyright (c) 2009 Storlek & Mrs. Brisby
6 * copyright (c) 2010-2012 Storlek
7 * URL: http://schismtracker.org/
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "headers.h"
25
26 #include "slurp.h"
27 #include "util.h"
28 #include "config-parser.h"
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <string.h>
37 #include <errno.h>
38
39 /* --------------------------------------------------------------------------------------------------------- */
40 /* some utilities for reading the config structure in memory */
41
_get_section(cfg_file_t * cfg,const char * section_name,int add)42 static struct cfg_section *_get_section(cfg_file_t *cfg, const char *section_name, int add)
43 {
44 struct cfg_section *section = cfg->sections, *prev = NULL;
45
46 if (section_name == NULL)
47 return NULL;
48
49 while (section) {
50 if (strcasecmp(section_name, section->name) == 0)
51 return section;
52 prev = section;
53 section = section->next;
54 }
55 if (add) {
56 section = mem_calloc(1, sizeof(struct cfg_section));
57 section->name = str_dup(section_name);
58 if (prev) {
59 section->next = prev->next;
60 prev->next = section;
61 } else {
62 cfg->sections = section;
63 }
64 }
65 return section;
66 }
67
_get_key(struct cfg_section * section,const char * key_name,int add)68 static struct cfg_key *_get_key(struct cfg_section *section, const char *key_name, int add)
69 {
70 struct cfg_key *key = section->keys, *prev = NULL;
71
72 if (key_name == NULL)
73 return NULL;
74
75 while (key) {
76 if (strcasecmp(key_name, key->name) == 0)
77 return key;
78 prev = key;
79 key = key->next;
80 }
81 if (add) {
82 key = mem_calloc(1, sizeof(struct cfg_key));
83 key->name = str_dup(key_name);
84 if (prev) {
85 key->next = prev->next;
86 prev->next = key;
87 } else {
88 section->keys = key;
89 }
90 }
91 return key;
92 }
93
94 /* --------------------------------------------------------------------------------------------------------- */
95 /* configuration file parser */
96
97 /* skip past any comments and save them. return: length of comments */
_parse_comments(const char * s,char ** comments)98 static size_t _parse_comments(const char *s, char **comments)
99 {
100 const char *ptr = s, *prev;
101 char *new_comments, *tmp;
102 size_t len;
103
104 do {
105 prev = ptr;
106 ptr += strspn(ptr, " \t\r\n");
107 if (*ptr == '#' || *ptr == ';')
108 ptr += strcspn(ptr, "\r\n");
109 } while (*ptr && ptr != prev);
110 len = ptr - s;
111 if (len) {
112 /* save the comments */
113 new_comments = strn_dup(s, len);
114 if (*comments) {
115 /* already have some comments -- add to them */
116 if (asprintf(&tmp, "%s%s", *comments, new_comments) == -1) {
117 perror("asprintf");
118 exit(255);
119 }
120 if (!tmp) {
121 perror("asprintf");
122 exit(255);
123 }
124 free(*comments);
125 free(new_comments);
126 *comments = tmp;
127 } else {
128 *comments = new_comments;
129 }
130 }
131 return len;
132 }
133
134 /* parse a [section] line. return: 1 if all's well, 0 if it didn't work */
_parse_section(cfg_file_t * cfg,char * line,struct cfg_section ** cur_section,char * comments)135 static int _parse_section(cfg_file_t *cfg, char *line, struct cfg_section **cur_section, char *comments)
136 {
137 char *tmp;
138
139 if (line[0] != '[' || line[strlen(line) - 1] != ']')
140 return 0;
141
142 memmove(line, line + 1, strlen(line));
143 line[strlen(line) - 1] = 0;
144 *cur_section = _get_section(cfg, line, 1);
145 (*cur_section)->omit = 0;
146 if (comments) {
147 if ((*cur_section)->comments) {
148 /* glue them together */
149 if (asprintf(&tmp, "%s\n%s", comments, (*cur_section)->comments) == -1) {
150 perror("asprintf");
151 exit(255);
152 }
153 if (!tmp) {
154 perror("asprintf");
155 exit(255);
156 }
157 free((*cur_section)->comments);
158 free(comments);
159 (*cur_section)->comments = tmp;
160 } else {
161 (*cur_section)->comments = comments;
162 }
163 }
164
165 return 1;
166 }
167
168 /* parse a line as a key=value pair, and add it to the configuration. */
_parse_keyval(cfg_file_t * cfg,char * line,struct cfg_section * cur_section,char * comments)169 static int _parse_keyval(cfg_file_t *cfg, char *line, struct cfg_section *cur_section, char *comments)
170 {
171 struct cfg_key *key;
172 char *k, *v, *tmp;
173
174 if (!strchr(line, '=')) {
175 fprintf(stderr, "%s: malformed line \"%s\"; ignoring\n", cfg->filename, line);
176 return 0;
177 }
178 if (cur_section == NULL) {
179 fprintf(stderr, "%s: missing section for line \"%s\"\n", cfg->filename, line);
180 return 0;
181 }
182
183 str_break(line, '=', &k, &v);
184 trim_string(k);
185 trim_string(v);
186
187 key = _get_key(cur_section, k, 1);
188 if (key->value) {
189 fprintf(stderr, "%s: duplicate key \"%s\" in section \"%s\"; overwriting\n",
190 cfg->filename, k, cur_section->name);
191 free(key->value);
192 }
193 key->value = str_unescape(v);
194
195 free(k);
196 free(v);
197
198 if (comments) {
199 if (key->comments) {
200 /* glue them together */
201 if (asprintf(&tmp, "%s\n%s", comments, key->comments) == -1) {
202 perror("asprintf");
203 exit(255);
204 }
205 if (!tmp) {
206 perror("asprintf");
207 exit(255);
208 }
209 free(key->comments);
210 free(comments);
211 key->comments = tmp;
212 } else {
213 key->comments = comments;
214 }
215 }
216
217 return 1;
218 }
219
220 /* --------------------------------------------------------------------------------------------------------- */
221 /* memory mismanagement */
222
_free_key(struct cfg_key * key)223 static struct cfg_key *_free_key(struct cfg_key *key)
224 {
225 struct cfg_key *next_key = key->next;
226
227 free(key->name);
228 free(key->value);
229 if (key->comments)
230 free(key->comments);
231 free(key);
232 return next_key;
233 }
234
_free_section(struct cfg_section * section)235 static struct cfg_section *_free_section(struct cfg_section *section)
236 {
237 struct cfg_section *next_section = section->next;
238 struct cfg_key *key = section->keys;
239
240 free(section->name);
241 if (section->comments)
242 free(section->comments);
243 while (key)
244 key = _free_key(key);
245 free(section);
246 return next_section;
247 }
248
249 /* --------------------------------------------------------------------------------------------------------- */
250 /* public functions */
251
cfg_read(cfg_file_t * cfg)252 int cfg_read(cfg_file_t *cfg)
253 {
254 struct stat buf;
255 slurp_t *t;
256 struct cfg_section *cur_section = NULL;
257 const char *pos; /* current position in the buffer */
258 size_t len; /* how far away the end of the token is from the start */
259 char *comments = NULL, *tmp;
260
261 /* have to do our own stat, because we're going to fiddle with the size. (this is to be sure the
262 buffer ends with a '\0', which makes it much easier to handle with normal string operations) */
263 if (stat(cfg->filename, &buf) < 0)
264 return -1;
265 if (S_ISDIR(buf.st_mode)) {
266 errno = EISDIR;
267 return -1;
268 }
269 if (buf.st_size <= 0)
270 return -1;
271 buf.st_size++;
272 t = slurp(cfg->filename, &buf, 0);
273 if (!t)
274 return -1;
275
276 pos = (const char *)t->data;
277 do {
278 pos += _parse_comments(pos, &comments);
279
280 /* look for the end of the line or the next comment, whichever comes first. note that a
281 comment in the middle of a line ends up on the next line when the file is rewritten.
282 semicolon-comments are only handled at the start of lines. */
283 len = strcspn(pos, "#\r\n");
284 if (len) {
285 char *line;
286 line = strn_dup(pos, len);
287 trim_string(line);
288 if (_parse_section(cfg, line, &cur_section, comments)
289 || _parse_keyval(cfg, line, cur_section, comments)) {
290 comments = NULL;
291 } else {
292 /* broken line: add it as a comment. */
293 if (comments) {
294 if (asprintf(&tmp, "%s# %s\n", comments, line) == -1) {
295 perror("asprintf");
296 exit(255);
297 }
298 if (!tmp) {
299 perror("asprintf");
300 exit(255);
301 }
302 free(comments);
303 comments = tmp;
304 } else {
305 if (asprintf(&comments, "# %s\n", line) == -1) {
306 perror("asprintf");
307 exit(255);
308 }
309 if (!comments) {
310 perror("asprintf");
311 exit(255);
312 }
313 }
314 }
315 free(line);
316 }
317 pos += len;
318
319 /* skip the newline */
320 if (*pos == '\r')
321 pos++;
322 if (*pos == '\n')
323 pos++;
324 } while (*pos);
325 cfg->eof_comments = comments;
326
327 cfg->dirty = 0;
328
329 unslurp(t);
330
331 return 0;
332 }
333
cfg_write(cfg_file_t * cfg)334 int cfg_write(cfg_file_t *cfg)
335 {
336 struct cfg_section *section;
337 struct cfg_key *key;
338 FILE *fp;
339
340 if (!cfg->filename) {
341 /* FIXME | don't print a message here! this should be considered library code.
342 * FIXME | instead, this should give a more useful indicator of what happened. */
343 fprintf(stderr, "bbq, cfg_write called with no filename\n");
344 return -1;
345 }
346
347 if (!cfg->dirty)
348 return 0;
349 cfg->dirty = 0;
350
351 make_backup_file(cfg->filename, 0);
352
353 fp = fopen(cfg->filename, "wb");
354 if (!fp) {
355 /* FIXME: don't print a message here! */
356 perror(cfg->filename);
357 return -1;
358 }
359
360 /* I should be checking a lot more return values, but ... meh */
361
362 for (section = cfg->sections; section; section = section->next) {
363 if (section->comments)
364 fprintf(fp, "%s", section->comments);
365 if (section->omit) fputc('#', fp);
366 fprintf(fp, "[%s]\n", section->name);
367 for (key = section->keys; key; key = key->next) {
368 /* NOTE: key names are intentionally not escaped in any way;
369 * it is up to the program to choose names that aren't stupid.
370 * (cfg_delete_key uses this to comment out a key name) */
371 if (key->comments)
372 fprintf(fp, "%s", key->comments);
373 if (section->omit) fputc('#', fp);
374 /* TODO | if no keys in a section have defined values,
375 * TODO | comment out the section header as well. (this
376 * TODO | might be difficult since it's already been
377 * TODO | written to the file) */
378 if (key->value) {
379 char *tmp = str_escape(key->value, 1);
380 fprintf(fp, "%s=%s\n", key->name, tmp);
381 free(tmp);
382 } else {
383 fprintf(fp, "# %s=(undefined)\n", key->name);
384 }
385 }
386 }
387 if (cfg->eof_comments)
388 fprintf(fp, "%s", cfg->eof_comments);
389
390 fclose(fp);
391
392 return 0;
393 }
394
cfg_get_string(cfg_file_t * cfg,const char * section_name,const char * key_name,char * value,int len,const char * def)395 const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name,
396 char *value, int len, const char *def)
397 {
398 struct cfg_section *section;
399 struct cfg_key *key;
400 const char *r = def;
401
402 section = _get_section(cfg, section_name, 0);
403 if (section) {
404 key = _get_key(section, key_name, 0);
405 if (key && key->value)
406 r = key->value;
407 }
408 if (value && r) {
409 //copy up to len chars [0..len-1]
410 strncpy(value, r, len);
411 value[len] = 0;
412 }
413 return r;
414 }
415
cfg_get_number(cfg_file_t * cfg,const char * section_name,const char * key_name,int def)416 int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def)
417 {
418 struct cfg_section *section;
419 struct cfg_key *key;
420 char *e;
421 long r = def;
422
423 section = _get_section(cfg, section_name, 0);
424 if (section) {
425 key = _get_key(section, key_name, 0);
426 if (key && key->value && key->value[0]) {
427 r = strtol(key->value, &e, 10);
428 if (e == key->value) {
429 /* Not a number */
430 r = def;
431 } else if (*e) {
432 /* Junk at the end of the string. I'm accepting the number here, but it
433 would also be acceptable to treat it as junk and return the default. */
434 /* r = def; */
435 }
436 }
437 }
438 return r;
439 }
440
cfg_set_string(cfg_file_t * cfg,const char * section_name,const char * key_name,const char * value)441 void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value)
442 {
443 struct cfg_section *section;
444 struct cfg_key *key;
445
446 if (section_name == NULL || key_name == NULL)
447 return;
448 section = _get_section(cfg, section_name, 1);
449 section->omit = 0;
450
451 key = _get_key(section, key_name, 1);
452 if (key->value)
453 free(key->value);
454 if (value)
455 key->value = str_dup(value);
456 else
457 key->value = NULL;
458 cfg->dirty = 1;
459 }
460
cfg_set_number(cfg_file_t * cfg,const char * section_name,const char * key_name,int value)461 void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value)
462 {
463 struct cfg_section *section;
464 struct cfg_key *key;
465
466 if (section_name == NULL || key_name == NULL)
467 return;
468 section = _get_section(cfg, section_name, 1);
469 section->omit = 0;
470
471 key = _get_key(section, key_name, 1);
472 if (key->value)
473 free(key->value);
474 if (asprintf(&key->value, "%d", value) == -1) {
475 perror("asprintf");
476 exit(255);
477 }
478 if (!key->value) {
479 perror("asprintf");
480 exit(255);
481 }
482 cfg->dirty = 1;
483 }
484
cfg_delete_key(cfg_file_t * cfg,const char * section_name,const char * key_name)485 void cfg_delete_key(cfg_file_t *cfg, const char *section_name, const char *key_name)
486 {
487 struct cfg_section *section;
488 struct cfg_key *key;
489 char *newname;
490
491 if (section_name == NULL || key_name == NULL)
492 return;
493 section = _get_section(cfg, section_name, 1);
494 key = _get_key(section, key_name, 0);
495 if (key == NULL || key->name[0] == '#')
496 return;
497 newname = mem_alloc(strlen(key->name) + 2);
498 newname[0] = '#';
499 strcpy(newname + 1, key->name);
500 free(key->name);
501 key->name = newname;
502 }
503
cfg_init(cfg_file_t * cfg,const char * filename)504 int cfg_init(cfg_file_t *cfg, const char *filename)
505 {
506 memset(cfg, 0, sizeof(*cfg));
507 cfg->filename = str_dup(filename);
508 cfg->sections = NULL;
509
510 return cfg_read(cfg);
511 }
512
cfg_free(cfg_file_t * cfg)513 void cfg_free(cfg_file_t *cfg)
514 {
515 struct cfg_section *section = cfg->sections;
516
517 free(cfg->filename);
518 if (cfg->eof_comments)
519 free(cfg->eof_comments);
520 while (section)
521 section = _free_section(section);
522 }
523
524 /* --------------------------------------------------------------------------------------------------------- */
525
526 #ifdef TEST
main(int argc,char ** argv)527 int main(int argc, char **argv)
528 {
529 cfg_file_t cfg;
530 char buf[64];
531
532 cfg_init(&cfg, "config");
533
534 /*
535 - test these functions with defined and undefined section/key names
536 - test all functions with completely broken values
537 (e.g. NULL shouldn't break it, it should give up, maybe print a warning,
538 and for the get functions, return the default value)
539
540 const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name,
541 char *value, int len, const char *def);
542 int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def);
543 void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value);
544 void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value);
545 */
546 /*
547 [ducks]
548 color = brown
549 count = 7
550 weight = 64 lb.
551 */
552 printf("testing cfg_get_ functions...\n");
553 printf("defined values\n");
554 printf("ducks:color = %s\n", cfg_get_string(&cfg, "ducks", "color", NULL, 0, "abcd"));
555 printf("ducks:count = %d\n", cfg_get_number(&cfg, "ducks", "count", 1234));
556 printf("ducks:weight = %d\n", cfg_get_number(&cfg, "ducks", "weight", 1234));
557 printf("\n");
558 printf("undefined values in a defined section\n");
559 printf("ducks:sauce = %s\n", cfg_get_string(&cfg, "ducks", "sauce", NULL, 0, "soy"));
560 printf("ducks:feathers = %d\n", cfg_get_number(&cfg, "ducks", "feathers", 94995));
561 printf("\n");
562 printf("undefined section\n");
563 printf("barbecue:weather = %s\n", cfg_get_string(&cfg, "barbecue", "weather", NULL, 0, "elf"));
564 printf("barbecue:dismal = %d\n", cfg_get_number(&cfg, "barbecue", "dismal", 758));
565 printf("\n");
566 printf("obviously broken values\n");
567 printf("string with null section: %s\n", cfg_get_string(&cfg, NULL, "shouldn't crash", NULL, 0, "ok"));
568 printf("string with null key: %s\n", cfg_get_string(&cfg, "shouldn't crash", NULL, NULL, 0, "ok"));
569 printf("number with null section: %d\n", cfg_get_number(&cfg, NULL, "shouldn't crash", 1));
570 printf("number with null key: %d\n", cfg_get_number(&cfg, "shouldn't crash", NULL, 1));
571 printf("string with null default value: %s\n", cfg_get_string(&cfg, "doesn't", "exist", NULL, 0, NULL));
572 strcpy(buf, "didn't change");
573 printf("null default value, with value return parameter set: %s\n",
574 cfg_get_string(&cfg, "still", "nonexistent", buf, 64, NULL));
575 printf("... and the buffer it returned: %s\n", buf);
576 strcpy(buf, "didn't change");
577 printf("null default value on defined key with return parameter: %s\n",
578 cfg_get_string(&cfg, "ducks", "weight", buf, 64, NULL));
579 printf("... and the buffer it returned: %s\n", buf);
580 printf("\n");
581 printf("string boundary tests\n");
582 cfg_set_string(&cfg, "test", "test", "abcdefghijklmnopqrstuvwxyz???broken");
583 cfg_get_string(&cfg, "test", "test", buf, 26, "wtf");
584 printf("26 characters using defined value: %s\n", buf);
585 cfg_get_string(&cfg, "fake section", "fake key", buf, 10, "1234567890???broken");
586 printf("10 characters using default value: %s\n", buf);
587 cfg_get_string(&cfg, "fake section", "fake key", buf, 0, "huh?");
588 printf("zero-length buffer (this should be an empty string) \"%s\"\n", buf);
589 printf("\n");
590 printf("testing cfg_set_ functions...\n");
591 printf("string in new section\n");
592 cfg_set_string(&cfg, "toast", "is", "tasty");
593 printf("string with new key in existing section\n");
594 cfg_set_string(&cfg, "toast", "tastes", "good");
595 printf("number in new section\n");
596 cfg_set_number(&cfg, "cowboy", "hats", 3);
597 printf("number with new key in existing section\n");
598 cfg_set_number(&cfg, "cowboy", "boots", 4);
599 printf("string with null section\n");
600 cfg_set_string(&cfg, NULL, "shouldn't", "crash");
601 printf("string with null key\n");
602 cfg_set_string(&cfg, "shouldn't", NULL, "crash");
603 printf("string with null value\n");
604 cfg_set_string(&cfg, "shouldn't", "crash", NULL);
605 printf("re-reading that null string should return default value: %s\n",
606 cfg_get_string(&cfg, "shouldn't", "crash", NULL, 0, "it does"));
607 printf("number with null section\n");
608 cfg_set_number(&cfg, NULL, "don't segfault", 42);
609 printf("number with null key\n");
610 cfg_set_number(&cfg, "don't segfault", NULL, 42);
611
612 cfg_dump(&cfg);
613 cfg_free(&cfg);
614
615 return 0;
616 }
617 #endif /* TEST */
618