From: Fredrik Tolf Date: Mon, 13 Jun 2011 19:50:04 +0000 (+0200) Subject: Initial commit. X-Git-Url: http://dolda2000.com/gitweb/?p=statserve.git;a=commitdiff_plain;h=34d725a595138f81ba84d67d7b84766c3a270d9f Initial commit. --- 34d725a595138f81ba84d67d7b84766c3a270d9f diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3472df --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.o +/statdbput +/statserve diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..fa9899c --- /dev/null +++ b/dbsrc.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..8a34ddb --- /dev/null +++ b/fssrc.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..8369efe --- /dev/null +++ b/statdbput.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 0000000..ed49388 --- /dev/null +++ b/statserve.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..d092422 --- /dev/null +++ b/statserve.h @@ -0,0 +1,24 @@ +#ifndef _STATSERVE_H +#define _STATSERVE_H + +#include +#include + +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