lib: Preserve error properly across flog.
[ashd.git] / lib / cf.c
CommitLineData
06c1a718
FT
1/*
2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include <stdlib.h>
20#include <stdio.h>
21#include <unistd.h>
22#include <string.h>
23#include <ctype.h>
24#include <glob.h>
dd281054 25#include <libgen.h>
3d74fc07 26#include <sys/socket.h>
06c1a718
FT
27#include <errno.h>
28
29#ifdef HAVE_CONFIG_H
30#include <config.h>
31#endif
32#include <utils.h>
33#include <cf.h>
34#include <mt.h>
35#include <proc.h>
36#include <log.h>
37
38#define CH_SOCKET 0
39#define CH_FORK 1
40
1924fe8c
FT
41struct stdchild {
42 int type;
43 char **argv;
44 int fd;
45};
46
dd281054 47static int parsefile(struct cfstate *s, FILE *in);
1924fe8c
FT
48static void stdmerge(struct child *old, struct child *new);
49static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata);
50static void stddestroy(struct child *ch);
dd281054
FT
51
52static int doinclude(struct cfstate *s, char *spec)
06c1a718 53{
dd281054
FT
54 int rv, i;
55 FILE *inc;
06c1a718 56 glob_t globm;
dd281054
FT
57 char *fbk, *dir, *fspec;
58
59 rv = 0;
60 fbk = s->file;
61 if(spec[0] == '/') {
62 fspec = spec;
63 } else {
64 dir = sstrdup(fbk);
65 fspec = sprintf3("%s/%s", dirname(dir), spec);
66 free(dir);
67 }
68 if(glob(fspec, 0, NULL, &globm))
69 return(0);
70 for(i = 0; i < globm.gl_pathc; i++) {
71 if((inc = fopen(globm.gl_pathv[i], "r")) != NULL) {
72 s->file = globm.gl_pathv[i];
73 if(parsefile(s, inc)) {
74 fclose(inc);
75 rv = 1;
76 goto out;
77 }
78 fclose(inc);
79 inc = NULL;
80 }
81 }
82
83out:
84 globfree(&globm);
85 s->file = fbk;
86 return(rv);
87}
88
89static int parsefile(struct cfstate *s, FILE *in)
90{
91 int i;
06c1a718
FT
92 char line[1024];
93 int eof, argc;
94 int ind, indst[80], indl;
dd281054 95 char *p, **w;
06c1a718
FT
96
97 s->lno = 0;
98 indst[indl = 0] = 0;
99 eof = 0;
100 while(1) {
101 if(fgets(line, sizeof(line), in) == NULL) {
102 eof = 1;
103 line[0] = 0;
104 }
105 s->lno++;
c3b91092
FT
106 if(line[0]) {
107 for(p = line + strlen(line) - 1; p >= line; p--) {
108 if(isspace(*p))
109 *p = 0;
110 else
111 break;
112 }
06c1a718
FT
113 }
114 for(ind = 0, p = line; *p; p++) {
115 if(*p == ' ') {
116 ind++;
117 } else if(*p == '\t') {
118 ind = ind - (ind % 8) + 8;
119 } else {
120 break;
121 }
122 }
123 if(!eof && (!*p || (*p == '#')))
124 continue;
125
126 reindent:
127 if(ind > indst[indl]) {
128 indst[++indl] = ind;
129 if(!s->expstart) {
130 s->res = tokenize("start");
131 if(yield())
132 return(1);
133 } else {
134 s->expstart = 0;
135 }
136 } else {
137 if(s->expstart) {
138 s->res = tokenize("end");
139 if(yield())
140 return(1);
141 s->expstart = 0;
142 }
143 while(ind < indst[indl]) {
144 indl--;
145 s->res = tokenize("end");
146 if(yield())
147 return(1);
148 }
149 if(ind > indst[indl]) {
150 flog(LOG_WARNING, "%s:%i: unexpected indentation level", s->file, s->lno);
151 goto reindent;
152 }
153 }
154
155 if(eof)
156 return(0);
157
158 argc = calen(w = tokenize(line));
159 if(argc < 1) {
160 /* Shouldn't happen, but... */
161 freeca(w);
162 continue;
163 }
164
165 if(indl == 0) {
166 if(!strcmp(w[0], "include")) {
06c1a718 167 for(i = 1; i < argc; i++) {
dd281054
FT
168 if(doinclude(s, w[i])) {
169 freeca(w);
170 return(1);
06c1a718
FT
171 }
172 }
173 freeca(w);
06c1a718
FT
174 continue;
175 }
176 }
177
178 if(!strcmp(w[0], "start") ||
179 !strcmp(w[0], "end") ||
180 !strcmp(w[0], "eof")) {
181 flog(LOG_WARNING, "%s:%i: illegal directive: %s", s->file, s->lno, w[0]);
182 } else {
183 s->res = w;
184 if(yield())
185 return(1);
186 }
187 }
188}
189
190static void parsefn(struct muth *mt, va_list args)
191{
192 vavar(struct cfstate *, s);
193 vavar(FILE *, in);
194 vavar(char *, file);
195
196 s->file = sstrdup(file);
197 if(parsefile(s, in))
198 goto out;
199 do {
200 s->res = tokenize("eof");
201 } while(!yield());
202
203out:
204 free(s->file);
205}
206
207char **getcfline(struct cfstate *s)
208{
209 freeca(s->argv);
210 if(s->res == NULL)
211 resume(s->pf, 0);
212 s->argc = calen(s->argv = s->res);
213 s->res = NULL;
214 return(s->argv);
215}
216
217struct cfstate *mkcfparser(FILE *in, char *name)
218{
219 struct cfstate *s;
220
221 omalloc(s);
222 s->pf = mustart(parsefn, s, in, name);
223 return(s);
224}
225
226void freecfparser(struct cfstate *s)
227{
228 resume(s->pf, -1);
229 freeca(s->argv);
230 freeca(s->res);
231 free(s);
232}
233
0fc6fd13
FT
234char *findstdconf(char *name)
235{
236 char *path, *p, *p2, *t;
237
238 if((path = getenv("PATH")) == NULL)
239 return(NULL);
240 path = sstrdup(path);
241 for(p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) {
242 if((p2 = strrchr(p, '/')) == NULL)
243 continue;
244 *p2 = 0;
13975be5 245 if(!access(t = sprintf2("%s/etc/%s", p, name), R_OK)) {
0fc6fd13 246 free(path);
13975be5 247 return(t);
0fc6fd13 248 }
13975be5 249 free(t);
0fc6fd13
FT
250 }
251 free(path);
252 return(NULL);
253}
254
1924fe8c 255struct child *newchild(char *name, struct chandler *iface, void *pdata)
06c1a718
FT
256{
257 struct child *ch;
258
259 omalloc(ch);
260 ch->name = sstrdup(name);
1924fe8c
FT
261 ch->iface = iface;
262 ch->pdata = pdata;
06c1a718
FT
263 return(ch);
264}
265
266void freechild(struct child *ch)
267{
1924fe8c
FT
268 if(ch->iface->destroy != NULL)
269 ch->iface->destroy(ch);
06c1a718
FT
270 if(ch->name != NULL)
271 free(ch->name);
06c1a718
FT
272 free(ch);
273}
274
1924fe8c
FT
275void mergechildren(struct child *dst, struct child *src)
276{
277 struct child *ch1, *ch2;
278
279 for(ch1 = dst; ch1 != NULL; ch1 = ch1->next) {
280 for(ch2 = src; ch2 != NULL; ch2 = ch2->next) {
281 if(ch1->iface->merge && !strcmp(ch1->name, ch2->name)) {
282 ch1->iface->merge(ch1, ch2);
283 break;
284 }
285 }
286 }
287}
288
06c1a718
FT
289void skipcfblock(struct cfstate *s)
290{
291 char **w;
292
293 while(1) {
294 w = getcfline(s);
295 if(!strcmp(w[0], "end") || !strcmp(w[0], "eof"))
296 return;
297 }
298}
299
1924fe8c
FT
300static struct chandler stdhandler = {
301 .handle = stdhandle,
302 .merge = stdmerge,
303 .destroy = stddestroy,
304};
305
306static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
307{
308 struct stdchild *i = ch->pdata;
45b19d96 309 int serr;
1924fe8c
FT
310
311 if(i->type == CH_SOCKET) {
312 if(i->fd < 0)
313 i->fd = stdmkchild(i->argv, chinit, idata);
3d74fc07 314 if(sendreq2(i->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
45b19d96
FT
315 serr = errno;
316 if((serr == EPIPE) || (serr == ECONNRESET)) {
1924fe8c
FT
317 /* Assume that the child has crashed and restart it. */
318 close(i->fd);
319 i->fd = stdmkchild(i->argv, chinit, idata);
3d74fc07 320 if(!sendreq2(i->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
1924fe8c
FT
321 return(0);
322 }
45b19d96
FT
323 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
324 if(serr != EAGAIN) {
fc35a3ef
FT
325 close(i->fd);
326 i->fd = -1;
327 }
1924fe8c
FT
328 return(-1);
329 }
330 } else if(i->type == CH_FORK) {
331 if(stdforkserve(i->argv, req, fd, chinit, idata) < 0)
332 return(-1);
333 }
334 return(0);
335}
336
337static void stdmerge(struct child *dst, struct child *src)
338{
339 struct stdchild *od, *nd;
340
341 if(src->iface == &stdhandler) {
342 nd = dst->pdata;
343 od = src->pdata;
344 nd->fd = od->fd;
345 od->fd = -1;
346 }
347}
348
349static void stddestroy(struct child *ch)
350{
351 struct stdchild *d = ch->pdata;
352
353 if(d->fd >= 0)
354 close(d->fd);
355 if(d->argv)
356 freeca(d->argv);
357 free(d);
358}
359
06c1a718
FT
360struct child *parsechild(struct cfstate *s)
361{
362 struct child *ch;
1924fe8c 363 struct stdchild *d;
06c1a718
FT
364 int i;
365 int sl;
366
367 sl = s->lno;
368 if(!strcmp(s->argv[0], "child")) {
369 s->expstart = 1;
370 if(s->argc < 2) {
371 flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
372 skipcfblock(s);
373 return(NULL);
374 }
1924fe8c
FT
375 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
376 d->type = CH_SOCKET;
06c1a718
FT
377 } else if(!strcmp(s->argv[0], "fchild")) {
378 s->expstart = 1;
379 if(s->argc < 2) {
380 flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
381 skipcfblock(s);
382 return(NULL);
383 }
1924fe8c
FT
384 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
385 d->type = CH_FORK;
06c1a718
FT
386 } else {
387 return(NULL);
388 }
1924fe8c 389 d->fd = -1;
06c1a718
FT
390
391 while(1) {
392 getcfline(s);
393 if(!strcmp(s->argv[0], "exec")) {
394 if(s->argc < 2) {
395 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
396 continue;
397 }
1924fe8c 398 d->argv = szmalloc(sizeof(*d->argv) * s->argc);
06c1a718 399 for(i = 0; i < s->argc - 1; i++)
1924fe8c 400 d->argv[i] = sstrdup(s->argv[i + 1]);
06c1a718
FT
401 } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
402 break;
403 } else {
404 flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
405 }
406 }
1924fe8c 407 if(d->argv == NULL) {
06c1a718
FT
408 flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
409 freechild(ch);
410 return(NULL);
411 }
412 return(ch);
413}
414
6a7a868e 415int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
06c1a718 416{
1924fe8c 417 return(ch->iface->handle(ch, req, fd, chinit, idata));
06c1a718 418}