Added a generic config parser/child handler and used it for dirplex and patplex.
[ashd.git] / lib / cf.c
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 <errno.h>
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30 #include <utils.h>
31 #include <cf.h>
32 #include <mt.h>
33 #include <proc.h>
34 #include <log.h>
35
36 #define CH_SOCKET 0
37 #define CH_FORK 1
38
39 static int parsefile(struct cfstate *s, FILE *in)
40 {
41     int i, o, ret;
42     glob_t globm;
43     char line[1024];
44     int eof, argc;
45     int ind, indst[80], indl;
46     char *p, **w, *fbk;
47     FILE *inc;
48     
49     s->lno = 0;
50     indst[indl = 0] = 0;
51     eof = 0;
52     while(1) {
53         if(fgets(line, sizeof(line), in) == NULL) {
54             eof = 1;
55             line[0] = 0;
56         }
57         s->lno++;
58         for(p = line + strlen(line) - 1; p >= line; p--) {
59             if(isspace(*p))
60                 *p = 0;
61             else
62                 break;
63         }
64         for(ind = 0, p = line; *p; p++) {
65             if(*p == ' ') {
66                 ind++;
67             } else if(*p == '\t') {
68                 ind = ind - (ind % 8) + 8;
69             } else {
70                 break;
71             }
72         }
73         if(!eof && (!*p || (*p == '#')))
74             continue;
75         
76     reindent:
77         if(ind > indst[indl]) {
78             indst[++indl] = ind;
79             if(!s->expstart) {
80                 s->res = tokenize("start");
81                 if(yield())
82                     return(1);
83             } else {
84                 s->expstart = 0;
85             }
86         } else {
87             if(s->expstart) {
88                 s->res = tokenize("end");
89                 if(yield())
90                     return(1);
91                 s->expstart = 0;
92             }
93             while(ind < indst[indl]) {
94                 indl--;
95                 s->res = tokenize("end");
96                 if(yield())
97                     return(1);
98             }
99             if(ind > indst[indl]) {
100                 flog(LOG_WARNING, "%s:%i: unexpected indentation level", s->file, s->lno);
101                 goto reindent;
102             }
103         }
104         
105         if(eof)
106             return(0);
107         
108         argc = calen(w = tokenize(line));
109         if(argc < 1) {
110             /* Shouldn't happen, but... */
111             freeca(w);
112             continue;
113         }
114         
115         if(indl == 0) {
116             if(!strcmp(w[0], "include")) {
117                 fbk = s->file;
118                 for(i = 1; i < argc; i++) {
119                     if((ret = glob(w[i], 0, NULL, &globm)) == 0) {
120                         for(o = 0; o < globm.gl_pathc; o++) {
121                             if((inc = fopen(globm.gl_pathv[o], "r")) != NULL) {
122                                 s->file = globm.gl_pathv[o];
123                                 if(parsefile(s, inc)) {
124                                     fclose(inc);
125                                     globfree(&globm);
126                                     freeca(w);
127                                     return(1);
128                                 }
129                                 fclose(inc);
130                             }
131                         }
132                         globfree(&globm);
133                     }
134                 }
135                 freeca(w);
136                 s->file = fbk;
137                 continue;
138             }
139         }
140         
141         if(!strcmp(w[0], "start") ||
142            !strcmp(w[0], "end") || 
143            !strcmp(w[0], "eof")) {
144             flog(LOG_WARNING, "%s:%i: illegal directive: %s", s->file, s->lno, w[0]);
145         } else {
146             s->res = w;
147             if(yield())
148                 return(1);
149         }
150     }
151 }
152
153 static void parsefn(struct muth *mt, va_list args)
154 {
155     vavar(struct cfstate *, s);
156     vavar(FILE *, in);
157     vavar(char *, file);
158     
159     s->file = sstrdup(file);
160     if(parsefile(s, in))
161         goto out;
162     do {
163         s->res = tokenize("eof");
164     } while(!yield());
165     
166 out:
167     free(s->file);
168 }
169
170 char **getcfline(struct cfstate *s)
171 {
172     freeca(s->argv);
173     if(s->res == NULL)
174         resume(s->pf, 0);
175     s->argc = calen(s->argv = s->res);
176     s->res = NULL;
177     return(s->argv);
178 }
179
180 struct cfstate *mkcfparser(FILE *in, char *name)
181 {
182     struct cfstate *s;
183     
184     omalloc(s);
185     s->pf = mustart(parsefn, s, in, name);
186     return(s);
187 }
188
189 void freecfparser(struct cfstate *s)
190 {
191     resume(s->pf, -1);
192     freeca(s->argv);
193     freeca(s->res);
194     free(s);
195 }
196
197 static struct child *newchild(char *name, int type)
198 {
199     struct child *ch;
200     
201     omalloc(ch);
202     ch->name = sstrdup(name);
203     ch->type = type;
204     ch->fd = -1;
205     return(ch);
206 }
207
208 void freechild(struct child *ch)
209 {
210     if(ch->fd != -1)
211         close(ch->fd);
212     if(ch->name != NULL)
213         free(ch->name);
214     if(ch->argv != NULL)
215         freeca(ch->argv);
216     free(ch);
217 }
218
219 void skipcfblock(struct cfstate *s)
220 {
221     char **w;
222     
223     while(1) {
224         w = getcfline(s);
225         if(!strcmp(w[0], "end") || !strcmp(w[0], "eof"))
226             return;
227     }
228 }
229
230 struct child *parsechild(struct cfstate *s)
231 {
232     struct child *ch;
233     int i;
234     int sl;
235     
236     sl = s->lno;
237     if(!strcmp(s->argv[0], "child")) {
238         s->expstart = 1;
239         if(s->argc < 2) {
240             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
241             skipcfblock(s);
242             return(NULL);
243         }
244         ch = newchild(s->argv[1], CH_SOCKET);
245     } else if(!strcmp(s->argv[0], "fchild")) {
246         s->expstart = 1;
247         if(s->argc < 2) {
248             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
249             skipcfblock(s);
250             return(NULL);
251         }
252         ch = newchild(s->argv[1], CH_FORK);
253     } else {
254         return(NULL);
255     }
256     
257     while(1) {
258         getcfline(s);
259         if(!strcmp(s->argv[0], "exec")) {
260             if(s->argc < 2) {
261                 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
262                 continue;
263             }
264             ch->argv = szmalloc(sizeof(*ch->argv) * s->argc);
265             for(i = 0; i < s->argc - 1; i++)
266                 ch->argv[i] = sstrdup(s->argv[i + 1]);
267         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
268             break;
269         } else {
270             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
271         }
272     }
273     if(ch->argv == NULL) {
274         flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
275         freechild(ch);
276         return(NULL);
277     }
278     return(ch);
279 }
280
281 int childhandle(struct child *ch, struct hthead *req, int fd)
282 {
283     if(ch->type == CH_SOCKET) {
284         if(ch->fd < 0)
285             ch->fd = stdmkchild(ch->argv);
286         if(sendreq(ch->fd, req, fd)) {
287             if(errno == EPIPE) {
288                 /* Assume that the child has crashed and restart it. */
289                 close(ch->fd);
290                 ch->fd = stdmkchild(ch->argv);
291                 if(!sendreq(ch->fd, req, fd))
292                     return(-1);
293             }
294             flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(errno));
295             close(ch->fd);
296             ch->fd = -1;
297             return(-1);
298         }
299     } else if(ch->type == CH_FORK) {
300         if(stdforkserve(ch->argv, req, fd) < 0)
301             return(-1);
302     }
303     return(0);
304 }