showlog: Add option to hide log rotation
[nagios-reports-module.git] / cfgfile.c
blob1978b19aa0af1d5fca8f72e279244dbe6cdf1784
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);
113 comp->vlist[comp->vars] = malloc(sizeof(struct cfg_var));
114 memcpy(comp->vlist[comp->vars++], v, sizeof(struct cfg_var));
117 static inline char *end_of_line(char *s)
119 char last = 0;
121 for (; *s; s++) {
122 if (*s == '\n')
123 return s;
124 if (last != '\\') {
125 if (*s == ';') {
126 *s = '\0';
128 else {
129 if (*s == '{' || *s == '}')
130 return s;
133 last = *s;
136 return NULL;
139 static struct cfg_comp *parse_file(const char *path, struct cfg_comp *parent, unsigned line)
141 unsigned compound_depth = 0, buflen, i, lnum = 0;
142 char *buf;
143 struct cfg_var v;
144 struct cfg_comp *comp;
145 char end = '\n'; /* count like cat -n */
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 = comp->end = line;
158 memset(&v, 0, sizeof(v));
159 for (i = 0; i < buflen; i++) {
160 char *next, *lstart, *lend;
162 if (end == '\n')
163 lnum++;
165 /* skipe whitespace */
166 while(ISSPACE(buf[i]))
167 i++;
169 /* skip comments */
170 if (buf[i] == '#') {
171 while(buf[i] != '\n')
172 i++;
174 end = '\n';
175 continue;
178 /* hop empty lines */
179 if (buf[i] == '\n') {
180 v.key = v.value = NULL;
181 end = '\n';
182 continue;
185 next = lend = end_of_line(&buf[i]);
186 end = *lend;
187 lstart = &buf[i];
189 /* nul-terminate and strip space from end of line */
190 *lend-- = '\0';
191 while(ISSPACE(*lend))
192 lend--;
194 /* check for start of compound */
195 if (end == '{') {
196 v.key = v.value = NULL;
197 compound_depth++;
198 comp = start_compound(lstart, comp, lnum);
199 i = next - buf;
200 continue;
203 if (!v.key) {
204 char *p = lstart + 1;
206 v.line = lnum;
207 v.key = lstart;
209 while (p < lend && !ISSPACE(*p) && *p != '=')
210 p++;
212 if (ISSPACE(*p) || *p == '=') {
213 v.key_len = p - &buf[i];
214 while(p < lend && (ISSPACE(*p) || *p == '='))
215 *p++ = '\0';
217 if (*p && p <= lend)
218 v.value = p;
222 if (v.key && *v.key) {
223 if (v.value)
224 v.value_len = 1 + lend - v.value;
225 add_var(comp, &v);
226 memset(&v, 0, sizeof(v));
229 if (end == '}') {
230 comp = close_compound(comp, lnum);
231 compound_depth--;
234 i = next - buf;
237 return comp;
240 static void cfg_print_error(struct cfg_comp *comp, struct cfg_var *v,
241 const char *fmt, va_list ap)
243 struct cfg_comp *c;
245 fprintf(stderr, "*** Configuration error\n");
246 if (v)
247 fprintf(stderr, " on line %d, near '%s' = '%s'\n",
248 v->line, v->key, v->value);
250 if (!comp->buf)
251 fprintf(stderr, " in compound '%s' starting on line %d\n", comp->name, comp->start);
253 fprintf(stderr, " in file ");
254 for (c = comp; c; c = c->parent) {
255 if (c->buf)
256 fprintf(stderr, "'%s'", c->name);
259 fprintf(stderr, "----\n");
260 vfprintf(stderr, fmt, ap);
261 if (fmt[strlen(fmt) - 1] != '\n')
262 fputc('\n', stderr);
263 fprintf(stderr, "----\n");
266 /** public functions **/
268 /* this is significantly faster than doing strdup(), since
269 * it can copy word-wise rather than byte-wise */
270 char *cfg_copy_value(struct cfg_var *v)
272 char *ptr;
274 if ((ptr = calloc(v->value_len + 1, 1)))
275 return memcpy(ptr, v->value, v->value_len);
277 return NULL;
280 void cfg_warn(struct cfg_comp *comp, struct cfg_var *v, const char *fmt, ...)
282 va_list ap;
284 va_start(ap, fmt);
285 cfg_print_error(comp, v, fmt, ap);
286 va_end(ap);
289 void cfg_error(struct cfg_comp *comp, struct cfg_var *v, const char *fmt, ...)
291 va_list ap;
293 va_start(ap, fmt);
294 cfg_print_error(comp, v, fmt, ap);
295 va_end(ap);
297 exit (1);
300 /* releases a compound and all its nested compounds recursively
301 * Note that comp->name is never free()'d since it either
302 * points to somewhere in comp->buf or is obtained from the caller
303 * and may point to the stack of some other function */
304 void cfg_destroy_compound(struct cfg_comp *comp)
306 unsigned i;
308 if (!comp)
309 return;
311 /* free() children so this can be entered anywhere in the middle */
312 for (i = 0; i < comp->nested; i++) {
313 cfg_destroy_compound(comp->nest[i]);
314 free(comp->nest[i]);
317 for (i = 0; i < comp->vars; i++)
318 free(comp->vlist[i]);
320 if (comp->vlist)
321 free(comp->vlist);
323 if (comp->buf)
324 free(comp->buf);
326 if (comp->nest)
327 free(comp->nest);
329 if (!comp->parent)
330 free(comp);
331 else {
332 /* If there is a parent we'll likely enter this compound again.
333 * If so, it mustn't try to free anything or read from any list,
334 * so zero the entire compound, but preserve the parent pointer. */
335 struct cfg_comp *parent = comp->parent;
336 memset(comp, 0, sizeof(struct cfg_comp));
337 comp->parent = parent;
341 struct cfg_comp *cfg_parse_file(const char *path)
343 struct cfg_comp *comp = parse_file(path, NULL, 0);
345 /* this is the public API, so make sure all compounds are closed */
346 if (comp && comp->parent) {
347 cfg_error(comp, NULL, "Unclosed compound (there may be more)\n");
348 return NULL;
351 return comp;