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