Merge branch 'next'
[nagios-reports-module.git] / cfgfile.c
blob87ca3d9c0bd4cc20b04a1322c084f9acba00caf7
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <stdarg.h>
5 #include <sys/types.h>
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <errno.h>
11 #include "cfgfile.h"
13 static struct cfg_comp *parse_file(const char *path, struct cfg_comp *parent, unsigned line);
15 /* read a file and return it in a buffer. Size is stored in *len.
16 * If there are errors, return NULL and set *len to -errno */
17 static char *cfg_read_file(const char *path, unsigned *len)
19 int fd, rd = 0, total = 0;
20 struct stat st;
21 char *buf = NULL;
23 if (access(path, R_OK) < 0) {
24 *len = -errno;
25 fprintf(stderr, "Failed to access '%s': %s\n", path, strerror(errno));
26 return NULL;
29 /* open, stat, malloc, read. caller handles errors (errno will be set) */
30 fd = open(path, O_RDONLY);
31 if (fd < 0) {
32 *len = -errno;
33 fprintf(stderr, "Failed to open '%s': %s\n", path, strerror(errno));
34 return NULL;
37 if (fstat(fd, &st) < 0) {
38 *len = -errno;
39 fprintf(stderr, "Failed to stat '%s': %s\n", path, strerror(errno));
40 close(fd);
41 return NULL;
44 /* make room for a forced newline and null-termination */
45 buf = malloc(st.st_size + 3);
46 if (!buf) {
47 *len = -errno;
48 fprintf(stderr, "Failed to allocate %lu bytes of memory for '%s'\n",
49 st.st_size, path);
50 close(fd);
51 return NULL;
54 do {
55 rd = read(fd, buf + rd, st.st_size - rd);
56 total += rd;
57 } while (total < st.st_size && rd > 0);
59 /* preserve errno, so close() doesn't alter it */
60 *len = errno;
61 close(fd);
63 if (rd < 0 || total != st.st_size) {
64 fprintf(stderr, "Reading from '%s' failed: %s\n", path, strerror(*len));
65 free(buf);
66 return NULL;
69 /* force newline+nul at EOF */
70 buf[st.st_size] = '\n';
71 buf[st.st_size + 1] = '\0';
72 *len = st.st_size;
74 return buf;
77 static struct cfg_comp *start_compound(const char *name, struct cfg_comp *cur, unsigned line)
79 struct cfg_comp *comp = calloc(1, sizeof(struct cfg_comp));
81 if (comp) {
82 comp->start = line;
83 comp->name = name;
84 comp->parent = cur;
87 if (cur) {
88 cur->nested++;
89 cur->nest = realloc(cur->nest, sizeof(struct cfg_comp *) * cur->nested);
90 cur->nest[cur->nested - 1] = comp;
93 return comp;
96 static struct cfg_comp *close_compound(struct cfg_comp *comp, unsigned line)
98 if (comp) {
99 comp->end = line;
100 return comp->parent;
103 return NULL;
106 static void add_var(struct cfg_comp *comp, struct cfg_var *v)
108 if (comp->vars >= comp->vlist_len) {
109 comp->vlist_len += 5;
110 comp->vlist = realloc(comp->vlist, sizeof(struct cfg_var *) * comp->vlist_len);
112 if (v->value) {
113 int vlen = strlen(v->value) - 1;
114 while (ISSPACE(v->value[vlen]))
115 v->value[vlen--] = 0;
118 comp->vlist[comp->vars] = malloc(sizeof(struct cfg_var));
119 memcpy(comp->vlist[comp->vars++], v, sizeof(struct cfg_var));
122 static inline char *end_of_line(char *s)
124 char last = 0;
126 for (; *s; s++) {
127 if (*s == '\n')
128 return s;
129 if (last != '\\') {
130 if (*s == ';') {
131 *s = '\0';
133 else {
134 if (*s == '{' || *s == '}')
135 return s;
138 last = *s;
141 return NULL;
144 static struct cfg_comp *parse_file(const char *path, struct cfg_comp *parent, unsigned line)
146 unsigned compound_depth = 0, buflen, i, lnum = 0;
147 char *buf;
148 struct cfg_var v;
149 struct cfg_comp *comp;
150 char end = '\n'; /* count like cat -n */
152 if (!(comp = start_compound(path, parent, 0)))
153 return NULL;
155 if (!(buf = cfg_read_file(path, &buflen))) {
156 free(comp);
157 return NULL;
160 comp->buf = buf; /* save a pointer to free() later */
161 comp->start = comp->end = line;
163 memset(&v, 0, sizeof(v));
164 for (i = 0; i < buflen; i++) {
165 char *next, *lstart, *lend;
167 if (end == '\n')
168 lnum++;
170 /* skipe whitespace */
171 while(ISSPACE(buf[i]))
172 i++;
174 /* skip comments */
175 if (buf[i] == '#') {
176 while(buf[i] != '\n')
177 i++;
179 end = '\n';
180 continue;
183 /* hop empty lines */
184 if (buf[i] == '\n') {
185 v.key = v.value = NULL;
186 end = '\n';
187 continue;
190 next = lend = end_of_line(&buf[i]);
191 end = *lend;
192 lstart = &buf[i];
194 /* nul-terminate and strip space from end of line */
195 *lend-- = '\0';
196 while(ISSPACE(*lend))
197 lend--;
199 /* check for start of compound */
200 if (end == '{') {
201 v.key = v.value = NULL;
202 compound_depth++;
203 comp = start_compound(lstart, comp, lnum);
204 i = next - buf;
205 continue;
208 if (!v.key) {
209 char *p = lstart + 1;
211 v.line = lnum;
212 v.key = lstart;
214 while (p < lend && !ISSPACE(*p) && *p != '=')
215 p++;
217 if (ISSPACE(*p) || *p == '=') {
218 v.key_len = p - &buf[i];
219 while(p < lend && (ISSPACE(*p) || *p == '='))
220 *p++ = '\0';
222 if (*p && p <= lend)
223 v.value = p;
227 if (v.key && *v.key) {
228 if (v.value)
229 v.value_len = 1 + lend - v.value;
230 add_var(comp, &v);
231 memset(&v, 0, sizeof(v));
234 if (end == '}') {
235 comp = close_compound(comp, lnum);
236 compound_depth--;
239 i = next - buf;
242 return comp;
245 static void cfg_print_error(struct cfg_comp *comp, struct cfg_var *v,
246 const char *fmt, va_list ap)
248 struct cfg_comp *c;
250 fprintf(stderr, "*** Configuration error\n");
251 if (v)
252 fprintf(stderr, " on line %d, near '%s' = '%s'\n",
253 v->line, v->key, v->value);
255 if (!comp->buf)
256 fprintf(stderr, " in compound '%s' starting on line %d\n", comp->name, comp->start);
258 fprintf(stderr, " in file ");
259 for (c = comp; c; c = c->parent) {
260 if (c->buf)
261 fprintf(stderr, "'%s'", c->name);
264 fprintf(stderr, "----\n");
265 vfprintf(stderr, fmt, ap);
266 if (fmt[strlen(fmt) - 1] != '\n')
267 fputc('\n', stderr);
268 fprintf(stderr, "----\n");
271 /** public functions **/
273 /* this is significantly faster than doing strdup(), since
274 * it can copy word-wise rather than byte-wise */
275 char *cfg_copy_value(struct cfg_var *v)
277 char *ptr;
279 if ((ptr = calloc(v->value_len + 1, 1)))
280 return memcpy(ptr, v->value, v->value_len);
282 return NULL;
285 void cfg_warn(struct cfg_comp *comp, struct cfg_var *v, const char *fmt, ...)
287 va_list ap;
289 va_start(ap, fmt);
290 cfg_print_error(comp, v, fmt, ap);
291 va_end(ap);
294 void cfg_error(struct cfg_comp *comp, struct cfg_var *v, const char *fmt, ...)
296 va_list ap;
298 va_start(ap, fmt);
299 cfg_print_error(comp, v, fmt, ap);
300 va_end(ap);
302 exit (1);
305 /* releases a compound and all its nested compounds recursively
306 * Note that comp->name is never free()'d since it either
307 * points to somewhere in comp->buf or is obtained from the caller
308 * and may point to the stack of some other function */
309 void cfg_destroy_compound(struct cfg_comp *comp)
311 unsigned i;
313 if (!comp)
314 return;
316 /* free() children so this can be entered anywhere in the middle */
317 for (i = 0; i < comp->nested; i++) {
318 cfg_destroy_compound(comp->nest[i]);
319 free(comp->nest[i]);
322 for (i = 0; i < comp->vars; i++)
323 free(comp->vlist[i]);
325 if (comp->vlist)
326 free(comp->vlist);
328 if (comp->buf)
329 free(comp->buf);
331 if (comp->nest)
332 free(comp->nest);
334 if (!comp->parent)
335 free(comp);
336 else {
337 /* If there is a parent we'll likely enter this compound again.
338 * If so, it mustn't try to free anything or read from any list,
339 * so zero the entire compound, but preserve the parent pointer. */
340 struct cfg_comp *parent = comp->parent;
341 memset(comp, 0, sizeof(struct cfg_comp));
342 comp->parent = parent;
346 struct cfg_comp *cfg_parse_file(const char *path)
348 struct cfg_comp *comp = parse_file(path, NULL, 0);
350 /* this is the public API, so make sure all compounds are closed */
351 if (comp && comp->parent) {
352 cfg_error(comp, NULL, "Unclosed compound (there may be more)\n");
353 return NULL;
356 return comp;