diff config.c @ 430:7f84090ab1cd

Add config file parser and default config file
author Mike Pavone <pavone@retrodev.com>
date Wed, 10 Jul 2013 09:38:05 -0700
parents
children 440efd7d27a9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/config.c	Wed Jul 10 09:38:05 2013 -0700
@@ -0,0 +1,229 @@
+#include "tern.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+char * alloc_concat(char * first, char * second)
+{
+	int flen = strlen(first);
+	int slen = strlen(second);
+	char * ret = malloc(flen + slen + 1);
+	memcpy(ret, first, flen);
+	memcpy(ret+flen, second, slen+1);
+	return ret;
+}
+
+char * alloc_concat_m(int num_parts, char ** parts)
+{
+	int total = 0;
+	for (int i = 0; i < num_parts; i++) {
+		total += strlen(parts[i]);
+	}
+	char * ret = malloc(total + 1);
+	*ret = 0;
+	for (int i = 0; i < num_parts; i++) {
+		strcat(ret, parts[i]);
+	}
+	return ret;
+}
+
+long file_size(FILE * f)
+{
+	fseek(f, 0, SEEK_END);
+	long fsize = ftell(f);
+	fseek(f, 0, SEEK_SET);
+	return fsize;
+}
+
+char * strip_ws(char * text)
+{
+	while (*text && (!isprint(*text) || isblank(*text)))
+	{
+		text++;
+	}
+	char * ret = text;
+	text = ret + strlen(ret) - 1;
+	while (text > ret && (!isprint(*text) || isblank(*text)))
+	{
+		*text = 0;
+		text--;
+	}
+	return ret;
+}
+
+char * split_keyval(char * text)
+{
+	while (*text && !isblank(*text))
+	{
+		text++;
+	}
+	if (!*text) {
+		return text;
+	}
+	*text = 0;
+	return text+1;
+}
+
+#define MAX_NEST 30 //way more than I'll ever need
+
+tern_node * parse_config(char * config_data)
+{
+	char *state, *curline;
+	char *prefix = NULL;
+	int nest_level = 0;
+	char * prefix_parts[MAX_NEST];
+	int line = 1;
+	tern_node * head = NULL;
+	while ((curline = strtok_r(config_data, "\n", &state)))
+	{
+		config_data = NULL;
+		curline = strip_ws(curline);
+		int len = strlen(curline);
+		if (!len) {
+			continue;
+		}
+		if (curline[0] == '#') {
+			continue;
+		}
+		if (curline[0] == '}') {
+			if (!nest_level) {
+				fprintf(stderr, "unexpected } on line %d\n", line);
+				exit(1);
+			}
+			if (prefix) {
+				free(prefix);
+				prefix = NULL;
+			}
+			nest_level--;
+			curline = strip_ws(curline+1);
+		}
+		char * end = curline + len - 1;
+		if (*end == '{') {
+			*end = 0;
+			curline = strip_ws(curline);
+			prefix_parts[nest_level++] = curline;
+			if (prefix) {
+				free(prefix);
+				prefix = NULL;
+			}
+		} else {
+			if (nest_level && !prefix) {
+				prefix = alloc_concat_m(nest_level, prefix_parts);
+			}
+			char * val = strip_ws(split_keyval(curline));
+			char * key = curline;
+			if (*key) {
+				if (prefix) {
+					key = alloc_concat(prefix, key);
+				}
+				head = tern_insert_ptr(head, key, val);
+				if (prefix) {
+					free(key);
+				}
+			}
+		}
+	}
+	if (prefix) {
+		free(prefix);
+	}
+	return head;
+}
+
+tern_node * parse_config_file(char * config_path)
+{
+	tern_node * ret = NULL;
+	FILE * config_file = fopen(config_path, "r");
+	if (!config_file) {
+		goto open_fail;
+	}
+	long config_size = file_size(config_file);
+	if (!config_size) {
+		goto config_empty;
+	}
+	char * config_data = malloc(config_size);
+	if (fread(config_data, 1, config_size, config_file) != config_size) {
+		goto config_read_fail;
+	}
+	ret = parse_config(config_data);
+config_read_fail:
+	free(config_data);
+config_empty:
+	fclose(config_file);
+open_fail:
+	return ret;
+}
+
+char * readlink_alloc(char * path)
+{
+	char * linktext = NULL;
+	ssize_t linksize = 512;
+	ssize_t cursize = 0;
+	do {
+		if (linksize > cursize) {
+			cursize = linksize;
+			if (linktext) {
+				free(linktext);
+			}
+		}
+		linktext = malloc(cursize);
+		linksize = readlink(path, linktext, cursize-1);
+		if (linksize == -1) {
+			perror("readlink");
+			free(linktext);
+			linktext = NULL;
+		}
+	} while (linksize > cursize);
+	return linktext;
+}
+
+tern_node * load_config(char * expath)
+{
+	char * linktext;
+	char * home = getenv("HOME");
+	if (!home) {
+		goto load_in_app_dir;
+	}
+	char * path = alloc_concat(home, "/.config/blastem/blastem.cfg");
+	tern_node * ret = parse_config_file(path);
+	if (ret) {
+		goto success;
+	}
+	free(path);
+load_in_app_dir:
+	
+	linktext = readlink_alloc("/proc/self/exe");
+	if (!linktext) {
+		goto link_prob;
+	}
+	char * cur;
+	int linksize = strlen(linktext);
+	for(cur = linktext + linksize - 1; cur != linktext; cur--)
+	{
+		if (*cur == '/') {
+			*cur = 0;
+			break;
+		}
+	}
+	if (cur == linktext) {
+		goto link_prob;
+	}
+	path = alloc_concat(linktext, "/default.cfg");
+	ret = parse_config_file(path);
+success:
+	return ret;
+link_prob:
+	if (linktext) {
+		free(linktext);
+	}
+no_proc:
+	//TODO: Fall back to using expath if /proc is not available
+	fputs("Failed to find a config file in ~/.config/blastem/blastem.cfg or in the blastem executable directory\n", stderr);
+	exit(1);
+}
+