lib: Fixed minor bug in expandargs.
[ashd.git] / lib / cf.c
index 82e5fba..b809d33 100644 (file)
--- a/lib/cf.c
+++ b/lib/cf.c
@@ -22,6 +22,9 @@
 #include <string.h>
 #include <ctype.h>
 #include <glob.h>
+#include <libgen.h>
+#include <sys/socket.h>
+#include <time.h>
 #include <errno.h>
 
 #ifdef HAVE_CONFIG_H
 #define CH_SOCKET 0
 #define CH_FORK 1
 
-static int parsefile(struct cfstate *s, FILE *in)
+struct stdchild {
+    int type;
+    char **argv;
+    char **envp;
+    int fd;
+    int agains;
+    time_t lastrep;
+};
+
+static int parsefile(struct cfstate *s, FILE *in);
+static void stdmerge(struct child *old, struct child *new);
+static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata);
+static void stddestroy(struct child *ch);
+
+static int doinclude(struct cfstate *s, char *spec)
 {
-    int i, o, ret;
+    int rv, i;
+    FILE *inc;
     glob_t globm;
+    char *fbk, *dir, *fspec;
+
+    rv = 0;
+    fbk = s->file;
+    if(spec[0] == '/') {
+       fspec = spec;
+    } else {
+       dir = sstrdup(fbk);
+       fspec = sprintf3("%s/%s", dirname(dir), spec);
+       free(dir);
+    }
+    if(glob(fspec, 0, NULL, &globm))
+       return(0);
+    for(i = 0; i < globm.gl_pathc; i++) {
+       if((inc = fopen(globm.gl_pathv[i], "r")) != NULL) {
+           s->file = globm.gl_pathv[i];
+           if(parsefile(s, inc)) {
+               fclose(inc);
+               rv = 1;
+               goto out;
+           }
+           fclose(inc);
+           inc = NULL;
+       }
+    }
+    
+out:
+    globfree(&globm);
+    s->file = fbk;
+    return(rv);
+}
+
+static int parsefile(struct cfstate *s, FILE *in)
+{
+    int i;
     char line[1024];
     int eof, argc;
     int ind, indst[80], indl;
-    char *p, **w, *fbk;
-    FILE *inc;
+    char *p, **w;
     
     s->lno = 0;
     indst[indl = 0] = 0;
@@ -55,11 +107,13 @@ static int parsefile(struct cfstate *s, FILE *in)
            line[0] = 0;
        }
        s->lno++;
-       for(p = line + strlen(line) - 1; p >= line; p--) {
-           if(isspace(*p))
-               *p = 0;
-           else
-               break;
+       if(line[0]) {
+           for(p = line + strlen(line) - 1; p >= line; p--) {
+               if(isspace(*p))
+                   *p = 0;
+               else
+                   break;
+           }
        }
        for(ind = 0, p = line; *p; p++) {
            if(*p == ' ') {
@@ -114,26 +168,13 @@ static int parsefile(struct cfstate *s, FILE *in)
        
        if(indl == 0) {
            if(!strcmp(w[0], "include")) {
-               fbk = s->file;
                for(i = 1; i < argc; i++) {
-                   if((ret = glob(w[i], 0, NULL, &globm)) == 0) {
-                       for(o = 0; o < globm.gl_pathc; o++) {
-                           if((inc = fopen(globm.gl_pathv[o], "r")) != NULL) {
-                               s->file = globm.gl_pathv[o];
-                               if(parsefile(s, inc)) {
-                                   fclose(inc);
-                                   globfree(&globm);
-                                   freeca(w);
-                                   return(1);
-                               }
-                               fclose(inc);
-                           }
-                       }
-                       globfree(&globm);
+                   if(doinclude(s, w[i])) {
+                       freeca(w);
+                       return(1);
                    }
                }
                freeca(w);
-               s->file = fbk;
                continue;
            }
        }
@@ -194,28 +235,66 @@ void freecfparser(struct cfstate *s)
     free(s);
 }
 
-static struct child *newchild(char *name, int type)
+char *findstdconf(char *name)
+{
+    char *home, *path, *p, *p2, *t;
+    
+    if((home = getenv("HOME")) != NULL) {
+       if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
+           return(t);
+       free(t);
+    }
+    if((path = getenv("PATH")) != NULL) {
+       path = sstrdup(path);
+       for(p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) {
+           if((p2 = strrchr(p, '/')) == NULL)
+               continue;
+           *p2 = 0;
+           if(!access(t = sprintf2("%s/etc/%s", p, name), R_OK)) {
+               free(path);
+               return(t);
+           }
+           free(t);
+       }
+       free(path);
+    }
+    return(NULL);
+}
+
+struct child *newchild(char *name, struct chandler *iface, void *pdata)
 {
     struct child *ch;
     
     omalloc(ch);
     ch->name = sstrdup(name);
-    ch->type = type;
-    ch->fd = -1;
+    ch->iface = iface;
+    ch->pdata = pdata;
     return(ch);
 }
 
 void freechild(struct child *ch)
 {
-    if(ch->fd != -1)
-       close(ch->fd);
+    if(ch->iface->destroy != NULL)
+       ch->iface->destroy(ch);
     if(ch->name != NULL)
        free(ch->name);
-    if(ch->argv != NULL)
-       freeca(ch->argv);
     free(ch);
 }
 
+void mergechildren(struct child *dst, struct child *src)
+{
+    struct child *ch1, *ch2;
+    
+    for(ch1 = dst; ch1 != NULL; ch1 = ch1->next) {
+       for(ch2 = src; ch2 != NULL; ch2 = ch2->next) {
+           if(ch1->iface->merge && !strcmp(ch1->name, ch2->name)) {
+               ch1->iface->merge(ch1, ch2);
+               break;
+           }
+       }
+    }
+}
+
 void skipcfblock(struct cfstate *s)
 {
     char **w;
@@ -227,9 +306,160 @@ void skipcfblock(struct cfstate *s)
     }
 }
 
+static struct chandler stdhandler = {
+    .handle = stdhandle,
+    .merge = stdmerge,
+    .destroy = stddestroy,
+};
+
+static char **expandargs(struct stdchild *sd)
+{
+    int i;
+    char **ret, *p, *p2, *p3, *np, *env;
+    struct charbuf exp;
+    
+    ret = szmalloc(sizeof(*ret) * (calen(sd->argv) + 1));
+    bufinit(exp);
+    for(i = 0; sd->argv[i] != NULL; i++) {
+       if((p = strchr(sd->argv[i], '$')) == NULL) {
+           ret[i] = sstrdup(sd->argv[i]);
+       } else {
+           exp.d = 0;
+           for(p2 = sd->argv[i]; p != NULL; p2 = np, p = strchr(np, '$')) {
+               bufcat(exp, p2, p - p2);
+               if(p[1] == '{') {
+                   if((p3 = strchr((p += 2), '}')) == NULL) {
+                       np = p;
+                       break;
+                   }
+                   np = p3 + 1;
+               } else {
+                   for(p3 = ++p; *p3; p3++) {
+                       if(!(((*p3 >= 'a') && (*p3 <= 'z')) ||
+                            ((*p3 >= 'A') && (*p3 <= 'Z')) ||
+                            ((*p3 >= '0') && (*p3 <= '9')) ||
+                            (*p3 == '_'))) {
+                           break;
+                       }
+                   }
+                   np = p3;
+               }
+               char temp[(p3 - p) + 1];
+               memcpy(temp, p, p3 - p);
+               temp[p3 - p] = 0;
+               if((env = getenv(temp)) != NULL)
+                   bufcatstr(exp, env);
+           }
+           bufcatstr2(exp, np);
+           ret[i] = sstrdup(exp.b);
+       }
+    }
+    ret[i] = NULL;
+    buffree(exp);
+    return(ret);
+}
+
+struct sidata {
+    struct stdchild *sd;
+    void (*sinit)(void *);
+    void *sdata;
+};
+
+static void stdinit(void *data)
+{
+    struct sidata *d = data;
+    int i;
+       
+    for(i = 0; d->sd->envp[i]; i += 2)
+       putenv(sprintf2("%s=%s", d->sd->envp[i], d->sd->envp[i + 1]));
+    if(d->sinit != NULL)
+       d->sinit(d->sdata);
+}
+
+static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *sdata)
+{
+    struct stdchild *sd = ch->pdata;
+    int serr;
+    char **args;
+    struct sidata idat;
+    
+    if(sd->type == CH_SOCKET) {
+       idat = (struct sidata) {.sd = sd, .sinit = chinit, .sdata = sdata};
+       if(sd->fd < 0) {
+           args = expandargs(sd);
+           sd->fd = stdmkchild(args, stdinit, &idat);
+           freeca(args);
+       }
+       if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
+           serr = errno;
+           if((serr == EPIPE) || (serr == ECONNRESET)) {
+               /* Assume that the child has crashed and restart it. */
+               close(sd->fd);
+               args = expandargs(sd);
+               sd->fd = stdmkchild(args, stdinit, &idat);
+               freeca(args);
+               if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
+                   goto ok;
+               serr = errno;
+           }
+           if(serr == EAGAIN) {
+               if(sd->agains++ == 0) {
+                   flog(LOG_WARNING, "request to child %s denied due to buffer overload", ch->name);
+                   sd->lastrep = time(NULL);
+               }
+           } else {
+               flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
+               close(sd->fd);
+               sd->fd = -1;
+           }
+           return(-1);
+       }
+    ok:
+       if((sd->agains > 0) && ((time(NULL) - sd->lastrep) > 10)) {
+           flog(LOG_WARNING, "%i requests to child %s were denied due to buffer overload", sd->agains, ch->name);
+           sd->agains = 0;
+       }
+    } else if(sd->type == CH_FORK) {
+       args = expandargs(sd);
+       if(stdforkserve(args, req, fd, chinit, sdata) < 0) {
+           freeca(args);
+           return(-1);
+       }
+       freeca(args);
+    }
+    return(0);
+}
+
+static void stdmerge(struct child *dst, struct child *src)
+{
+    struct stdchild *od, *nd;
+    
+    if(src->iface == &stdhandler) {
+       nd = dst->pdata;
+       od = src->pdata;
+       nd->fd = od->fd;
+       od->fd = -1;
+    }
+}
+
+static void stddestroy(struct child *ch)
+{
+    struct stdchild *d = ch->pdata;
+    
+    if(d->fd >= 0)
+       close(d->fd);
+    if(d->argv)
+       freeca(d->argv);
+    if(d->envp)
+       freeca(d->envp);
+    free(d);
+}
+
 struct child *parsechild(struct cfstate *s)
 {
     struct child *ch;
+    struct stdchild *d;
+    struct charvbuf envbuf;
     int i;
     int sl;
     
@@ -241,7 +471,8 @@ struct child *parsechild(struct cfstate *s)
            skipcfblock(s);
            return(NULL);
        }
-       ch = newchild(s->argv[1], CH_SOCKET);
+       ch = newchild(s->argv[1], &stdhandler, omalloc(d));
+       d->type = CH_SOCKET;
     } else if(!strcmp(s->argv[0], "fchild")) {
        s->expstart = 1;
        if(s->argc < 2) {
@@ -249,11 +480,14 @@ struct child *parsechild(struct cfstate *s)
            skipcfblock(s);
            return(NULL);
        }
-       ch = newchild(s->argv[1], CH_FORK);
+       ch = newchild(s->argv[1], &stdhandler, omalloc(d));
+       d->type = CH_FORK;
     } else {
        return(NULL);
     }
+    d->fd = -1;
     
+    bufinit(envbuf);
     while(1) {
        getcfline(s);
        if(!strcmp(s->argv[0], "exec")) {
@@ -261,16 +495,25 @@ struct child *parsechild(struct cfstate *s)
                flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
                continue;
            }
-           ch->argv = szmalloc(sizeof(*ch->argv) * s->argc);
+           d->argv = szmalloc(sizeof(*d->argv) * s->argc);
            for(i = 0; i < s->argc - 1; i++)
-               ch->argv[i] = sstrdup(s->argv[i + 1]);
+               d->argv[i] = sstrdup(s->argv[i + 1]);
+       } else if(!strcmp(s->argv[0], "env")) {
+           if(s->argc < 3) {
+               flog(LOG_WARNING, "%s:%i: too few parameters to `env'", s->file, s->lno);
+               continue;
+           }
+           bufadd(envbuf, sstrdup(s->argv[1]));
+           bufadd(envbuf, sstrdup(s->argv[2]));
        } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
            break;
        } else {
            flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
        }
     }
-    if(ch->argv == NULL) {
+    bufadd(envbuf, NULL);
+    d->envp = envbuf.b;
+    if(d->argv == NULL) {
        flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
        freechild(ch);
        return(NULL);
@@ -278,27 +521,7 @@ struct child *parsechild(struct cfstate *s)
     return(ch);
 }
 
-int childhandle(struct child *ch, struct hthead *req, int fd)
+int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
 {
-    if(ch->type == CH_SOCKET) {
-       if(ch->fd < 0)
-           ch->fd = stdmkchild(ch->argv);
-       if(sendreq(ch->fd, req, fd)) {
-           if(errno == EPIPE) {
-               /* Assume that the child has crashed and restart it. */
-               close(ch->fd);
-               ch->fd = stdmkchild(ch->argv);
-               if(!sendreq(ch->fd, req, fd))
-                   return(0);
-           }
-           flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(errno));
-           close(ch->fd);
-           ch->fd = -1;
-           return(-1);
-       }
-    } else if(ch->type == CH_FORK) {
-       if(stdforkserve(ch->argv, req, fd) < 0)
-           return(-1);
-    }
-    return(0);
+    return(ch->iface->handle(ch, req, fd, chinit, idata));
 }