Add independent config file parsing functions
[browser-switch] / configfile.c
1 /*
2  * configfile.c -- config file functions for browser-switchboard
3  *
4  * Copyright (C) 2009 Steven Luo
5  * Derived from a Python implementation by Jason Simpson and Steven Luo
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20  * USA.
21  */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <regex.h>
28
29 #include "configfile.h"
30
31 static regex_t re_ignore, re_config1, re_config2;
32 static int re_init = 0;
33
34 /* Open config file for reading */
35 FILE *open_config_file(void) {
36         char *homedir, *configfile;
37         size_t len;
38         FILE *fp;
39
40         /* Put together the path to the config file */
41         if (!(homedir = getenv("HOME")))
42                 homedir = DEFAULT_HOMEDIR;
43         len = strlen(homedir) + strlen(CONFIGFILE_LOC) + 1;
44         if (!(configfile = calloc(len, sizeof(char))))
45                 return NULL;
46         snprintf(configfile, len, "%s%s", homedir, CONFIGFILE_LOC);
47
48         /* Try to open the config file */
49         if (!(fp = fopen(configfile, "r"))) {
50                 /* Try the legacy config file location before giving up
51                    XXX we assume here that CONFIGFILE_LOC_OLD is shorter
52                    than CONFIGFILE_LOC! */
53                 snprintf(configfile, len, "%s%s", homedir, CONFIGFILE_LOC_OLD);
54                 fp = fopen(configfile, "r");
55         }
56
57         free(configfile);
58         return fp;
59 }
60
61 /* Initialize the config file parser
62    Returns 1 if initialization successful, 0 otherwise */
63 int parse_config_file_begin(void) {
64         /* Just return if parser's already been initialized */
65         if (re_init)
66                 return 1;
67
68         /* compile regex matching blank lines or comments */
69         if (regcomp(&re_ignore, REGEX_IGNORE, REGEX_IGNORE_FLAGS))
70                 return 0;
71         /* compile regex matching foo = "bar", with arbitrary whitespace at
72            beginning and end of line and surrounding the = */
73         if (regcomp(&re_config1, REGEX_CONFIG1, REGEX_CONFIG1_FLAGS)) {
74                 regfree(&re_ignore);
75                 return 0;
76         }
77         /* compile regex matching foo = bar, with arbitrary whitespace at
78            beginning of line and surrounding the = */
79         if (regcomp(&re_config2, REGEX_CONFIG2, REGEX_CONFIG2_FLAGS)) {
80                 regfree(&re_ignore);
81                 regfree(&re_config1);
82                 return 0;
83         }
84
85         re_init = 1;
86         return 1;
87 }
88
89 /* End config file parsing and free the resources */
90 void parse_config_file_end(void) {
91         /* Just return if parser's not active */
92         if (!re_init)
93                 return;
94
95         regfree(&re_ignore);
96         regfree(&re_config1);
97         regfree(&re_config2);
98 }
99
100 /* Read the next line from a config file and store it into a swb_config_line,
101    parsing it into key and value if possible
102    Caller is responsible for freeing the strings in the swb_config_line if
103    call returns successfully
104    Returns 0 on success (whether line parsed or not), 1 on EOF, -1 on error */
105 int parse_config_file_line(FILE *fp, struct swb_config_line *line) {
106         regmatch_t substrs[3];
107         size_t len;
108         char *tmp;
109
110         if (!re_init || !fp || !line)
111                 return -1;
112
113         line->parsed = 0;
114         line->key = NULL;
115         line->value = NULL;
116
117         if (!(line->key = calloc(MAXLINE, sizeof(char))))
118                 return -1;
119
120         /* Read in the next line of the config file
121            XXX doesn't deal with lines longer than MAXLINE */
122         if (!fgets(line->key, MAXLINE, fp)) {
123                 free(line->key);
124                 line->key = NULL;
125                 if (feof(fp))
126                         return 1;
127                 else
128                         return -1;
129         }
130         printf("%s", line->key);
131
132         /* no need to parse blank lines and comments */
133         if (!regexec(&re_ignore, line->key, 0, NULL, 0))
134                 goto finish;
135
136         /* Find the substrings corresponding to the key and value
137            If the line doesn't match our idea of a config file entry,
138            don't parse it */
139         if (regexec(&re_config1, line->key, 3, substrs, 0) &&
140             regexec(&re_config2, line->key, 3, substrs, 0))
141                 goto finish;
142         if (substrs[1].rm_so == -1 || substrs[2].rm_so == -1)
143                 goto finish;
144
145         /* copy the config value into a new string */
146         len = substrs[2].rm_eo - substrs[2].rm_so;
147         if (!(line->value = calloc(len+1, sizeof(char)))) {
148                 free(line->key);
149                 line->key = NULL;
150                 return -1;
151         }
152         strncpy(line->value, line->key+substrs[2].rm_so, len);
153         /* calloc() zeroes the memory, so string is automatically
154            null terminated */
155
156         /* make key point to a null-terminated string holding the 
157            config key */
158         len = substrs[1].rm_eo - substrs[1].rm_so;
159         memmove(line->key, line->key+substrs[1].rm_so, len);
160         line->key[len] = '\0';
161
162         /* done parsing the line */
163         line->parsed = 1;
164         printf("%s: '%s'\n", line->key, line->value);
165
166 finish:
167         if (!line->parsed) {
168                 /* Toss a trailing newline, if present */
169                 len = strlen(line->key);
170                 if (line->key[len-1] == '\n')
171                         line->key[len-1] = '\0';
172         }
173         /* Try to shrink the allocation for key, to save space if someone
174            wants to keep it around */
175         len = strlen(line->key);
176         if ((tmp = realloc(line->key, len+1)))
177                 line->key = tmp;
178         return 0;
179 }