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