db_updater: Put parentheses back
[merlin.git] / cfgfile.c
blobc19b89ca714e6f50b2f4c3fd78965aaccfae488f
1 #define _GNU_SOURCE 1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <stdarg.h>
6 #include <sys/types.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <errno.h>
12 #include "cfgfile.h"
14 /* have a care with this one. It can't be used when (c) has side-effects */
15 #undef ISSPACE
16 #define ISSPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r')
18 /* read a file and return it in a buffer. Size is stored in *len.
19 * If there are errors, return NULL and set *len to -errno */
20 static char *cfg_read_file(const char *path, unsigned *len)
22 int fd, rd = 0, total = 0;
23 struct stat st;
24 char *buf = NULL;
26 /* open, stat, malloc, read. caller handles errors (errno will be set) */
27 fd = open(path, O_RDONLY);
28 if (fd < 0) {
29 *len = -errno;
30 fprintf(stderr, "Failed to open '%s': %s\n", path, strerror(errno));
31 return NULL;
34 if (fstat(fd, &st) < 0) {
35 *len = -errno;
36 fprintf(stderr, "Failed to stat '%s': %s\n", path, strerror(errno));
37 close(fd);
38 return NULL;
41 /* make room for a forced newline and null-termination */
42 buf = malloc(st.st_size + 3);
43 if (!buf) {
44 *len = -errno;
45 fprintf(stderr, "Failed to allocate %lld bytes of memory for '%s'\n",
46 (long long)st.st_size, path);
47 close(fd);
48 return NULL;
51 do {
52 rd = read(fd, buf + rd, st.st_size - rd);
53 total += rd;
54 } while (total < st.st_size && rd > 0);
56 /* preserve errno, so close() doesn't alter it */
57 *len = errno;
58 close(fd);
60 if (rd < 0 || total != st.st_size) {
61 fprintf(stderr, "Reading from '%s' failed: %s\n", path, strerror(*len));
62 free(buf);
63 return NULL;
66 /* force newline+nul at EOF */
67 buf[st.st_size] = '\n';
68 buf[st.st_size + 1] = '\0';
69 *len = st.st_size;
71 return buf;
74 static struct cfg_comp *start_compound(const char *name, struct cfg_comp *cur, unsigned line)
76 struct cfg_comp *comp = calloc(1, sizeof(struct cfg_comp));
78 if (comp) {
79 int namelen = strlen(name);
80 comp->start = line;
81 comp->name = strdup(name);
82 while (ISSPACE(comp->name[namelen - 1])) {
83 comp->name[--namelen] = 0;
85 comp->parent = cur;
88 if (cur) {
89 cur->nested++;
90 cur->nest = realloc(cur->nest, sizeof(struct cfg_comp *) * cur->nested);
91 cur->nest[cur->nested - 1] = comp;
94 return comp;
97 static struct cfg_comp *close_compound(struct cfg_comp *comp, unsigned line)
99 if (comp) {
100 if (!comp->parent) {
101 cfg_error(comp, NULL, "Compound closed on line %d was never opened\n", line);
103 return comp->parent;
106 return NULL;
109 static void add_var(struct cfg_comp *comp, struct cfg_var *v)
111 if (!comp)
112 cfg_error(NULL, v, "Adding variable to NULL compound. Weird that...\n");
113 if (comp->vars >= comp->vlist_len) {
114 comp->vlist_len += 5;
115 comp->vlist = realloc(comp->vlist, sizeof(struct cfg_var *) * comp->vlist_len);
117 if (v->value) {
118 int vlen = strlen(v->value) - 1;
119 while (ISSPACE(v->value[vlen]))
120 v->value[vlen--] = 0;
123 comp->vlist[comp->vars] = malloc(sizeof(struct cfg_var));
124 memcpy(comp->vlist[comp->vars++], v, sizeof(struct cfg_var));
127 #ifndef __GLIBC__
128 static inline char *strchrnul(const char *s, int c)
130 int i = 0, last = 0;
132 for (i = 0;; i++) {
133 if (s[i] == 0 || s[i] == c)
134 break;
136 return &s[i];
138 #endif
140 static struct cfg_comp *parse_file(const char *path, struct cfg_comp *parent, unsigned line)
142 unsigned compound_depth = 0, buflen, i, lnum = 0;
143 char *buf;
144 struct cfg_var v;
145 struct cfg_comp *comp;
147 if (!(comp = start_compound(path, parent, 0)))
148 return NULL;
150 if (!(buf = cfg_read_file(path, &buflen))) {
151 free(comp);
152 return NULL;
155 comp->buf = buf; /* save a pointer to free() later */
156 comp->start = line;
158 memset(&v, 0, sizeof(v));
159 for (i = 0; i < buflen; i++) {
160 char *next, *lstart, *lend;
161 lnum++;
163 /* skip whitespace */
164 while (ISSPACE(buf[i]))
165 i++;
167 /* skip empty lines */
168 if (buf[i] == '\n') {
169 v.key = v.value = NULL;
170 continue;
173 /* skip comments */
174 if (buf[i] == '#') {
175 i++;
176 while(buf[i] != '\n')
177 i++;
179 continue;
182 /* check for compound closure */
183 if (buf[i] == '}') {
184 v.key = v.value = NULL;
185 i++;
186 comp = close_compound(comp, lnum);
187 continue;
190 /* we have a real line, so set the starting point */
191 lstart = &buf[i];
193 /* locate next newline */
194 next = lend = strchrnul(&buf[i], '\n');
195 while ((ISSPACE(*lend) || *lend == '\n') && lend > lstart)
196 *lend-- = 0;
198 /* check for start of compound */
199 if (*lend == '{') {
200 *lend-- = 0;
202 /* nul-terminate and strip space from end of line */
203 while(ISSPACE(*lend) && lend > lstart)
204 *lend-- = 0;
206 v.key = v.value = NULL;
207 compound_depth++;
208 comp = start_compound(lstart, comp, lnum);
209 i = next - buf;
210 continue;
211 } else if (*lend == ';' && lend[-1] != '\\') {
212 *lend-- = 0;
213 while (ISSPACE(*lend) && lend > lstart)
214 *lend-- = 0;
217 if (!v.key) {
218 char *p = lstart + 1;
219 char *split = NULL;
221 v.line = lnum;
222 v.key = lstart;
224 while (p < lend && !ISSPACE(*p) && *p != '=')
225 p++;
227 split = p;
229 if (ISSPACE(*p) || *p == '=') {
230 v.key_len = p - &buf[i];
231 while(p <= lend && (ISSPACE(*p) || *p == '='))
232 *p++ = '\0';
234 if (*p && p <= lend && p > split)
235 v.value = p;
239 if (v.key && *v.key) {
240 if (v.value)
241 v.value_len = 1 + lend - v.value;
242 add_var(comp, &v);
243 memset(&v, 0, sizeof(v));
246 i = next - buf;
249 return comp;
252 static void cfg_print_error(struct cfg_comp *comp, struct cfg_var *v,
253 const char *fmt, va_list ap)
255 struct cfg_comp *c;
257 fprintf(stderr, "*** Configuration error\n");
258 if (v)
259 fprintf(stderr, " on line %d, near '%s' = '%s'\n",
260 v->line, v->key, v->value);
262 if (!comp->buf)
263 fprintf(stderr, " in compound '%s' starting on line %d\n", comp->name, comp->start);
265 for (c = comp; c; c = c->parent) {
266 if (!c->buf)
267 continue;
268 fprintf(stderr, " in file '%s'\n", c->name);
271 fprintf(stderr, "----\n");
272 vfprintf(stderr, fmt, ap);
273 if (fmt[strlen(fmt) - 1] != '\n')
274 fputc('\n', stderr);
275 fprintf(stderr, "----\n");
278 /** public functions **/
279 void cfg_warn(struct cfg_comp *comp, struct cfg_var *v, const char *fmt, ...)
281 va_list ap;
283 va_start(ap, fmt);
284 cfg_print_error(comp, v, fmt, ap);
285 va_end(ap);
288 void cfg_error(struct cfg_comp *comp, struct cfg_var *v, const char *fmt, ...)
290 va_list ap;
292 va_start(ap, fmt);
293 cfg_print_error(comp, v, fmt, ap);
294 va_end(ap);
296 exit (1);
299 /* releases a compound and all its nested compounds recursively
300 * Note that comp->name is never free()'d since it either
301 * points to somewhere in comp->buf or is obtained from the caller
302 * and may point to the stack of some other function */
303 void cfg_destroy_compound(struct cfg_comp *comp)
305 unsigned i;
307 if (!comp)
308 return;
310 /* free() children so this can be entered anywhere in the middle */
311 for (i = 0; i < comp->nested; i++) {
312 cfg_destroy_compound(comp->nest[i]);
315 for (i = 0; i < comp->vars; i++)
316 free(comp->vlist[i]);
318 if (comp->vlist)
319 free(comp->vlist);
321 if (comp->buf)
322 free(comp->buf);
324 if (comp->nest)
325 free(comp->nest);
327 if (comp->name)
328 free(comp->name);
330 free(comp);
333 struct cfg_comp *cfg_parse_file(const char *path)
335 struct cfg_comp *comp;
337 if (path == NULL)
338 return NULL;
339 comp = parse_file(path, NULL, 0);
341 /* this is the public API, so make sure all compounds are closed */
342 if (comp && comp->parent) {
343 cfg_error(comp, NULL, "Unclosed compound (there may be more)\n");
344 return NULL;
347 return comp;