Initial commit.
authorFredrik Tolf <fredrik@seatribe.se>
Mon, 13 Jun 2011 19:50:04 +0000 (21:50 +0200)
committerFredrik Tolf <fredrik@seatribe.se>
Mon, 13 Jun 2011 19:50:04 +0000 (21:50 +0200)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
dbsrc.c [new file with mode: 0644]
fssrc.c [new file with mode: 0644]
statdbput.c [new file with mode: 0644]
statserve.c [new file with mode: 0644]
statserve.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b3472df
--- /dev/null
@@ -0,0 +1,4 @@
+*~
+*.o
+/statdbput
+/statserve
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..cbf9f37
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+all: statserve statdbput
+
+CC =           gcc
+CFLAGS =       -g -Wall
+PREFIX =       /usr/local
+
+statserve: statserve.o dbsrc.o fssrc.o
+       $(CC) $(CFLAGS) -o $@ $^ -lht -ldb
+
+statdbput: statdbput.o
+       $(CC) $(CFLAGS) -o $@ $^ -lht -ldb
+
+%.o: %.c *.h
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+clean:
+       rm -f *.o statserve statdbput
+
+install: statserve statdbput
+       install statserve $(DESTDIR)$(PREFIX)/bin/; \
+       install statdbput $(DESTDIR)$(PREFIX)/bin/;
diff --git a/dbsrc.c b/dbsrc.c
new file mode 100644 (file)
index 0000000..fa9899c
--- /dev/null
+++ b/dbsrc.c
@@ -0,0 +1,150 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <db.h>
+#include <string.h>
+#include <ashd/utils.h>
+#include <ashd/log.h>
+#include <time.h>
+
+#include "statserve.h"
+
+struct dbsrc {
+    char *envnm;
+    DB_ENV *env;
+    DB *db;
+    time_t lastcp, lastar;
+};
+
+static struct fileinfo dbserve(struct source *src, char *nm)
+{
+    struct dbsrc *d = src->pdata;
+    int i, ret, ver;
+    DBT k, v;
+    size_t sz;
+    void *hp, *p;
+    struct fileinfo retf;
+    
+    memset(&k, 0, sizeof(k));
+    memset(&v, 0, sizeof(v));
+    k.size = strlen(k.data = nm);
+    v.flags = DB_DBT_MALLOC;
+    do {
+       ret = d->db->get(d->db, NULL, &k, &v, 0);
+    } while(ret == DB_LOCK_DEADLOCK);
+    if(ret == 0) {
+       hp = v.data;
+       sz = v.size;
+       if(sz < 1)
+           goto corrupt;
+       ver = *(uint8_t *)hp;
+       hp++; sz--;
+       if(ver == 1) {
+           if(sz < 8)
+               goto corrupt;
+           memset(&retf, 0, sizeof(retf));
+           for(i = 0, retf.mtime = 0; i < 8; i++) {
+               retf.mtime = (retf.mtime << 8) | (*(uint8_t *)hp);
+               hp++; sz--;
+           }
+           if(retf.mtime < 0)
+               goto corrupt;
+           if((p = memchr(hp, 0, sz)) == NULL)
+               goto corrupt;
+           p++;
+           if(p - hp > 64)
+               goto corrupt;
+           strcpy(retf.ctype, hp);
+           sz -= p - hp; hp = p;
+           retf.data = memcpy(smalloc(retf.sz = sz), hp, sz);
+           free(v.data);
+           return(retf);
+       } else {
+           goto corrupt;
+       }
+    } else if(ret == DB_NOTFOUND) {
+       return((struct fileinfo){});
+    } else {
+       flog(LOG_ERR, "could not read value of %s in %s: %s", nm, d->envnm, db_strerror(ret));
+       return((struct fileinfo){});
+    }
+    goto out;
+
+corrupt:
+    flog(LOG_ERR, "entry for %s in %s is corrupted", nm, d->envnm);
+    free(v.data);
+    return((struct fileinfo){});
+out:;
+}
+
+static void dbidle(struct source *src)
+{
+    struct dbsrc *d = src->pdata;
+    time_t now;
+    int ret;
+    char **files;
+    
+    now = time(NULL);
+    if(now - d->lastcp > 1800) {
+       d->lastcp = now;
+       if((ret = d->env->txn_checkpoint(d->env, 5000, 0, 0)) != 0) {
+           flog(LOG_ERR, "could not make db checkpoint in %s: %s", d->envnm, db_strerror(ret));
+       }
+    }
+    if(now - d->lastar > 7200) {
+       d->lastar = now;
+       files = NULL;
+       if((ret = d->env->log_archive(d->env, &files, DB_ARCH_REMOVE)) != 0) {
+           flog(LOG_ERR, "could not archive log files in %s: %s", d->envnm, db_strerror(ret));
+       }
+       if(files != NULL)
+           free(files);
+    }
+}
+
+static void enverror(const DB_ENV *env, const char *prefix, const char *msg)
+{
+    flog(LOG_ERR, "dbsource: environment error: %s", msg);
+}
+
+struct source *mkdbsrc(char *path, char *envpath)
+{
+    struct source *src;
+    struct dbsrc *d;
+    char *p;
+    int ret;
+    
+    omalloc(src);
+    src->serve = dbserve;
+    src->idle = dbidle;
+    src->pdata = omalloc(d);
+    if((ret = db_env_create(&d->env, 0)) != 0) {
+       flog(LOG_ERR, "could not create bdb environment: %s", db_strerror(ret));
+       exit(1);
+    }
+    if(envpath) {
+       d->envnm = sstrdup(envpath);
+    } else {
+       d->envnm = sstrdup(path);
+       if((p = strrchr(d->envnm, '/')) == NULL) {
+           free(d->envnm);
+           d->envnm = sstrdup(".");
+       } else {
+           *p = 0;
+       }
+    }
+    if((ret = d->env->open(d->env, d->envnm, DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN, 0666)) != 0) {
+       flog(LOG_ERR, "could not open bdb environment: %s", db_strerror(ret));
+       exit(1);
+    }
+    d->env->set_lk_detect(d->env, DB_LOCK_RANDOM);
+    d->env->set_errcall(d->env, enverror);
+    if((ret = db_create(&d->db, d->env, 0)) != 0) {
+       flog(LOG_ERR, "could not create bdb database: %s", db_strerror(ret));
+       exit(1);
+    }
+    if((ret = d->db->open(d->db, NULL, path, NULL, DB_HASH, DB_AUTO_COMMIT | DB_CREATE, 0666)) != 0) {
+       flog(LOG_ERR, "could not open bdb database: %s", db_strerror(ret));
+       exit(1);
+    }
+    return(src);
+}
diff --git a/fssrc.c b/fssrc.c
new file mode 100644 (file)
index 0000000..8a34ddb
--- /dev/null
+++ b/fssrc.c
@@ -0,0 +1,71 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ashd/utils.h>
+#include <ashd/mtio.h>
+#include <ashd/log.h>
+
+#include "statserve.h"
+
+struct fssrc {
+    char *path;
+};
+
+static struct fileinfo fsserve(struct source *src, char *nm)
+{
+    struct fssrc *d = src->pdata;
+    struct stat sb;
+    char *p;
+    struct charbuf rb;
+    int fd, ret;
+    
+    if((nm[0] == '.') || strchr(nm, '/'))
+       return((struct fileinfo){});
+    p = sprintf2("%s/%s", d->path, nm);
+    if(stat(p, &sb)) {
+       if(errno != ENOENT)
+           flog(LOG_WARNING, "fssrc: %s: %s", p, strerror(errno));
+       free(p);
+       return((struct fileinfo){});
+    }
+    if(!S_ISREG(sb.st_mode)) {
+       free(p);
+       return((struct fileinfo){});
+    }
+    if((fd = open(p, O_RDONLY)) < 0) {
+       flog(LOG_WARNING, "fssrc: %s: %s", p, strerror(errno));
+       free(p);
+       return((struct fileinfo){});
+    }
+    bufinit(rb);
+    sizebuf(rb, sb.st_size);
+    while(1) {
+       sizebuf(rb, 4096);
+       if((ret = read(fd, rb.b + rb.d, rb.s - rb.d)) < 0) {
+           flog(LOG_ERR, "fssrc: %s: %s", p, strerror(errno));
+           free(p);
+           buffree(rb);
+           return((struct fileinfo){});
+       }
+       if(ret == 0)
+           break;
+       rb.d += ret;
+    }
+    return((struct fileinfo){.mtime = sb.st_mtime, .sz = rb.d, .data = rb.b, .ctype = "image/png"});
+}
+
+struct source *mkfssrc(char *path)
+{
+    struct source *src;
+    struct fssrc *d;
+    
+    omalloc(src);
+    src->serve = fsserve;
+    src->pdata = omalloc(d);
+    d->path = sstrdup(path);
+    return(src);
+}
diff --git a/statdbput.c b/statdbput.c
new file mode 100644 (file)
index 0000000..8369efe
--- /dev/null
@@ -0,0 +1,258 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <db.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <ashd/utils.h>
+
+static DB_ENV *env;
+static DB *db;
+static DB_TXN *txn;
+static struct charvbuf files;
+static int verbose = 0;
+
+static void opendb(char *path)
+{
+    char *envpath, *p;
+    int ret;
+    
+    envpath = strdup(path);
+    if((p = strrchr(envpath, '/')) == NULL) {
+       free(envpath);
+       envpath = strdup(".");
+    } else {
+       *p = 0;
+    }
+    if((ret = db_env_create(&env, 0)) != 0) {
+       fprintf(stderr, "statdbput: could not create db environment handle: %s\n", db_strerror(ret));
+       exit(1);
+    }
+    if((ret = env->open(env, envpath, DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN, 0666)) != 0) {
+       fprintf(stderr, "statdbput: environment %s: %s\n", envpath, db_strerror(ret));
+       exit(1);
+    }
+    env->set_lk_detect(env, DB_LOCK_RANDOM);
+    if((ret = db_create(&db, env, 0)) != 0) {
+       fprintf(stderr, "statdbput: could not create db handle: %s\n", db_strerror(ret));
+       exit(1);
+    }
+    if((ret = db->open(db, NULL, path, NULL, DB_HASH, DB_AUTO_COMMIT | DB_CREATE, 0666)) != 0) {
+       fprintf(stderr, "statdbput: %s: %s\n", path, db_strerror(ret));
+       exit(1);
+    }
+}
+
+static void bufcatbe(struct charbuf *buf, intmax_t num, int nb)
+{
+    if(nb < 1)
+       return;
+    bufcatbe(buf, num >> 8, nb - 1);
+    bufadd(*buf, (uint8_t)(num & 0xff));
+}
+
+static int dofile2(int fd, char *name, time_t mtime, char *ctype)
+{
+    int ret;
+    DBT k, v;
+    struct charbuf buf;
+    
+    bufinit(buf);
+    bufadd(buf, 1);
+    bufcatbe(&buf, mtime, 8);
+    bufcatstr2(buf, ctype);
+    while(1) {
+       sizebuf(buf, buf.d + 4096);
+       if((ret = read(fd, buf.b + buf.d, buf.s - buf.d)) < 0) {
+           fprintf(stderr, "statdbput: %s: %s\n", name, strerror(errno));
+           buffree(buf);
+           return(1);
+       }
+       if(ret == 0)
+           break;
+       buf.d += ret;
+    }
+    k = (DBT){.data = name, .size = strlen(name)};
+    v = (DBT){.data = buf.b, .size = buf.d};
+    ret = db->put(db, txn, &k, &v, 0);
+    buffree(buf);
+    if(ret) {
+       if(ret == DB_LOCK_DEADLOCK)
+           return(2);
+       fprintf(stderr, "statdbput: %s: %s\n", name, db_strerror(ret));
+       return(1);
+    } else {
+       if(verbose)
+           fprintf(stderr, "put: %s\n", name);
+    }
+    return(0);
+}
+
+static int dofile(char *path, char *name, char *ctype)
+{
+    int fd, ret;
+    char *p;
+    struct stat sb;
+    
+    if((fd = open(path, O_RDONLY)) < 0) {
+       fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
+       return(1);
+    }
+    if(fstat(fd, &sb)) {
+       fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
+       close(fd);
+       return(1);
+    }
+    if(name == NULL) {
+       if((p = strrchr(path, '/')) != NULL)
+           name = p + 1;
+       else
+           name = path;
+    }
+    ret = dofile2(fd, name, sb.st_mtime, ctype);
+    close(fd);
+    if(!ret)
+       bufadd(files, sstrdup(path));
+    return(ret);
+}
+
+static int dodir(char *path, char *ctype)
+{
+    int rv, ret;
+    DIR *dir;
+    struct stat sb;
+    struct dirent *dent;
+    struct charbuf fnbuf;
+    
+    if((dir = opendir(path)) == NULL) {
+       fprintf(stderr, "statdbput: %s: %s\n", path, strerror(errno));
+       return(1);
+    }
+    rv = 0;
+    bufinit(fnbuf);
+    while((dent = readdir(dir)) != NULL) {
+       fnbuf.d = 0;
+       bprintf(&fnbuf, "%s/%s", path, dent->d_name);
+       bufadd(fnbuf, 0);
+       if(stat(fnbuf.b, &sb)) {
+           fprintf(stderr, "statdbput: %s: %s\n", fnbuf.b, strerror(errno));
+           rv = 1;
+           continue;
+       }
+       if(S_ISREG(sb.st_mode)) {
+           ret = dofile(fnbuf.b, NULL, ctype);
+           if(ret == 2)
+               return(2);
+           else if(ret == 1)
+               rv = 1;
+       }
+    }
+    buffree(fnbuf);
+    closedir(dir);
+    return(0);
+}
+
+static void usage(FILE *out)
+{
+    fprintf(out, "usage: statdbput [-hvD] [-n NAME] DB CONTENT-TYPE {FILE|-}...\n");
+    fprintf(out, "       statdbput [-hvD] [-d] DB CONTENT-TYPE DIR...\n");
+}
+
+int main(int argc, char **argv)
+{
+    int c, rv, ret;
+    int dm, ul, i, a;
+    char *name, *ctype, *dbpath;
+    
+    dm = ul = 0;
+    name = NULL;
+    while((c = getopt(argc, argv, "+hvDdn:")) >= 0) {
+       switch(c) {
+       case 'd':
+           dm = 1;
+           break;
+       case 'D':
+           ul = 1;
+           break;
+       case 'v':
+           verbose++;
+           break;
+       case 'n':
+           name = optarg;
+           break;
+       case 'h':
+           usage(stdout);
+           return(0);
+       default:
+           usage(stderr);
+           return(1);
+       }
+    }
+    if(optind > argc - 3) {
+       usage(stderr);
+       return(1);
+    }
+    dbpath = argv[optind++];
+    ctype = argv[optind++];
+    opendb(dbpath);
+    while(1) {
+       if((ret = env->txn_begin(env, NULL, &txn, 0)) != 0) {
+           fprintf(stderr, "statdbput: could not begin transaction in %s: %s\n", dbpath, db_strerror(ret));
+           return(1);
+       }
+       rv = 0;
+       a = optind;
+       if(dm) {
+           for(a = optind; a < argc; a++) {
+               ret = dodir(argv[a], ctype);
+               if(ret == 2)
+                   break;
+               else if(ret)
+                   rv = 1;
+           }
+       } else {
+           for(a = optind; a < argc; a++) {
+               if(!strcmp(argv[a], "-")) {
+                   if(name == NULL) {
+                       fprintf(stderr, "statdbput: must give -n when putting stdin\n");
+                       ret = 1;
+                   } else {
+                       ret = dofile2(0, name, time(NULL), ctype);
+                   }
+               } else {
+                   ret = dofile(argv[a], name, ctype);
+               }
+               if(ret == 2)
+                   break;
+               else if(ret)
+                   rv = 1;
+           }
+       }
+       if(ret == 2) {
+           if(verbose)
+               fprintf(stderr, "deadlocked, restarting and trying again\n");
+           for(i = 0; i < files.d; i++)
+               free(files.b[i]);
+           files.d = 0;
+           txn->abort(txn);
+           continue;
+       }
+       break;
+    }
+    if((ret = txn->commit(txn, 0)) != 0) {
+       fprintf(stderr, "statdbput: could not commit transaction in %s: %s\n", dbpath, db_strerror(ret));
+       return(1);
+    }
+    if(ul) {
+       for(i = 0; i < files.d; i++) {
+           if(verbose)
+               fprintf(stderr, "unlink %s\n", files.b[i]);
+           if(unlink(files.b[i]))
+               fprintf(stderr, "statdbput: unlink %s: %s\n", files.b[i], strerror(errno));
+       }
+    }
+    return(rv);
+}
diff --git a/statserve.c b/statserve.c
new file mode 100644 (file)
index 0000000..ed49388
--- /dev/null
@@ -0,0 +1,118 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ashd/req.h>
+#include <ashd/resp.h>
+#include <ashd/log.h>
+#include <ashd/mt.h>
+#include <ashd/mtio.h>
+
+#include "statserve.h"
+
+static struct source *sources = NULL;
+
+static struct source *parsesource(char *arg)
+{
+    if(arg[strlen(arg) - 1] == '/') {
+       return(mkfssrc(arg));
+    } else {
+       return(mkdbsrc(arg, NULL));
+    }
+}
+
+static void serve(struct muth *muth, va_list args)
+{
+    vavar(struct hthead *, req);
+    vavar(int, fd);
+    FILE *out;
+    struct source *src;
+    struct fileinfo f;
+    
+    out = NULL;
+    for(src = sources; src != NULL; src = src->next) {
+       f = src->serve(src, req->rest);
+       if(f.data != NULL)
+           break;
+    }
+    if(src == NULL) {
+       simpleerror(fd, 404, "Resource not found", "The resource %s was not found", htmlquote(req->rest));
+       goto out;
+    }
+    out = mtstdopen(fd, 1, 60, "r+");
+    fprintf(out, "HTTP/1.1 200 OK\n");
+    fprintf(out, "Content-Type: %s\n", f.ctype);
+    fprintf(out, "Content-Length: %zi\n", f.sz);
+    fprintf(out, "Last-Modified: %s\n", fmthttpdate(f.mtime));
+    fprintf(out, "\n");
+    fwrite(f.data, 1, f.sz, out);
+    free(f.data);
+    
+out:
+    if(out != NULL)
+       fclose(out);
+    else
+       close(fd);
+    freehthead(req);
+}
+
+static void usage(FILE *out)
+{
+    fprintf(out, "usage: statserve [-h] SOURCE...\n");
+}
+
+static void listenloop(struct muth *muth, va_list args)
+{
+    vavar(int, lfd);
+    int fd;
+    struct hthead *req;
+    struct source *src;
+    
+    while(1) {
+       block(0, EV_READ, 0);
+       if((fd = recvreq(lfd, &req)) < 0) {
+           if(errno != 0)
+               flog(LOG_ERR, "recvreq: %s", strerror(errno));
+           break;
+       }
+       mustart(serve, req, fd);
+       for(src = sources; src != NULL; src = src->next) {
+           if(src->idle)
+               src->idle(src);
+       }
+    }
+}
+
+int main(int argc, char **argv)
+{
+    int c;
+    struct source *last, *src;
+    
+    while((c = getopt(argc, argv, "+h")) >= 0) {
+       switch(c) {
+       case 'h':
+           usage(stdout);
+           return(0);
+       default:
+           usage(stderr);
+           return(1);
+       }
+    }
+    last = NULL;
+    while(optind < argc) {
+       src = parsesource(argv[optind++]);
+       if(!sources)
+           sources = src;
+       if(last)
+           last->next = src;
+       last = src;
+    }
+    if(!sources) {
+       usage(stderr);
+       return(1);
+    }
+    mustart(listenloop, 0);
+    ioloop();
+    return(0);
+}
diff --git a/statserve.h b/statserve.h
new file mode 100644 (file)
index 0000000..d092422
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef _STATSERVE_H
+#define _STATSERVE_H
+
+#include <time.h>
+#include <ashd/req.h>
+
+struct fileinfo {
+    time_t mtime;
+    size_t sz;
+    char ctype[64];
+    void *data;
+};
+
+struct source {
+    struct source *next;
+    struct fileinfo (*serve)(struct source *src, char *nm);
+    void (*idle)(struct source *src);
+    void *pdata;
+};
+
+struct source *mkdbsrc(char *path, char *envpath);
+struct source *mkfssrc(char *path);
+
+#endif