From 13da42dedf0305f7f47dfc0f6d00ca5cb169493d Mon Sep 17 00:00:00 2001 From: Justin Bedo Date: Mon, 19 Nov 2012 11:15:39 +1100 Subject: Initial import --- flickrfs.c | 826 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ heap.c | 63 +++++ mheap.h | 17 ++ mkfile | 12 + string.c | 42 ++++ 5 files changed, 960 insertions(+) create mode 100644 flickrfs.c create mode 100644 heap.c create mode 100644 mheap.h create mode 100644 mkfile create mode 100644 string.c diff --git a/flickrfs.c b/flickrfs.c new file mode 100644 index 0000000..056bdcb --- /dev/null +++ b/flickrfs.c @@ -0,0 +1,826 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include<9p.h> +#include + +const char KEY[] = "a1b693d302635eb916d330aebd0bd5c8"; +const char SECRET[] = "36eb40a37bf10381"; +const char BOUNDARY[] = "thisismyboundarytherearemanylikeitbutthisoneismine"; +char *token; + +enum{ + HEAP = 4972, /* Memory heap size */ +}; + +/* Webfs */ +char *webmtpt = "/mnt/web"; +int ctlfd, conn; + +int +webclone(int *c) +{ + char buf[16]; + int n, fd; + + hpush(); + + if((fd = open(hprint("%s/clone", webmtpt), ORDWR)) < 0) + sysfatal("couldn't open %s: %r", buf); + if((n = read(fd, buf, sizeof buf-1)) < 0) + sysfatal("reading clone: %r"); + if(n == 0) + sysfatal("short read on clone"); + buf[n] = '\0'; + *c = atoi(buf); + + hpop(); + + return fd; +} + +/* Formatters for URL and MD5 digest encoding */ +#define ALPHANUM(x) ((x) >= 'a' && (x) <= 'z' || \ + (x) >= 'A' && (x) <= 'Z' || \ + (x) >= '0' && (x) <= '9' || \ + (x) == '_' || (x) == '.' || (x) == '-') + +#pragma varargck type "U" char* +static int +urlfmt(Fmt *fmt) +{ + const char *buf; + char *p; + + for(p = va_arg(fmt->args, char*), buf = ""; *p; p++) + if(ALPHANUM(*p)) + buf = hprint("%s%c", buf, *p); + else + buf = hprint("%s%%%X", buf, (uchar)*p); + + return fmtstrcpy(fmt, buf); +} + +#pragma varargck type "M" uchar* +static int +digestfmt(Fmt *fmt) +{ + char buf[MD5dlen*2+1]; + uchar *p; + int i; + + p = va_arg(fmt->args, uchar*); + for(i=0; iname, b->name); + if(c != 0) return c; + + return strcmp(a->value, b->value); +} + +void +sortreq(Request *r) +{ + qsort(r->params, r->nparam, sizeof(Parameter), (int(*)(void *, void *))pcmp); +} + +void +add(Request *r, char *name, char *value) +{ + r->params[r->nparam].name = hstrdup(name); + r->params[r->nparam++].value = hstrdup(value); +} + +void +reset(Request *r) +{ + r->nparam = 0; + r->url = "http://flickr.com/services/rest/"; +} + +void +sign(Request *r) +{ + uchar digest[MD5dlen]; + const char *buffer; + uint i; + + sortreq(r); + buffer = hstrdup(SECRET); + for(i = 0; i < r->nparam; i++) + buffer = hprint("%s%s%s", buffer, r->params[i].name, r->params[i].value); + + md5((uchar *)buffer, strlen(buffer), digest, nil); + add(r, "api_sig", hprint("%M", digest)); +} + +void +auth(Request *r) +{ + add(r, "auth_token", token); + add(r, "api_key", KEY); + sign(r); +} + +/* Flickr unique photo ids */ +typedef struct{ + char *id; /* if nil then it's a un-uploaded buffer not a reference */ + union{ + struct{char *farm, *secret, *server;}; + struct{uchar *buf; ulong sz;}; + }; +} Pid; + +/* Makes a get via webfs given a request */ +Biobuf * +get(Request *r) +{ + const char *buf; + int i, n; + Biobuf *fp; + + + /* Compile url */ + buf = hprint("url %s", r->url); + for(i = 0; i < r->nparam; i++) + buf = hprint("%s%c%U=%U", buf, i == 0 ? '?':'&', + r->params[i].name, r->params[i].value); + + if(write(ctlfd, buf, n = strlen(buf)) != n) + sysfatal("get: write: %r"); + + /* Response */ + if((fp = Bopen(hprint("%s/%d/body", webmtpt, conn), OREAD)) == nil) + sysfatal("get: couldn't open body: %r"); + + return fp; +} + +/* Posts a photo to flickr */ +Biobuf * +post(Request *r, Pid *image) +{ + const char *url, *ctype; + int i, n; + Biobuf *fp; + + /* our own webfs connection */ + int myconn, myctl; + myctl = webclone(&myconn); + + /* Compile url */ + url = hprint("url %s", r->url); + if(write(myctl, url, n = strlen(url)) != n) + sysfatal("post: write: %r"); + ctype = hprint("contenttype multipart/form-data; boundary=%s", BOUNDARY); + if(write(myctl, ctype, n = strlen(ctype)) != n) + sysfatal("post: write: %r"); + + /* Open postbody */ + if((fp = Bopen(hprint("%s/%d/postbody", webmtpt, myconn), OWRITE)) == nil) + sysfatal("post: opening postbody: %r"); + + /* Post parameters */ + for(i = 0; i < r->nparam; i++){ + Bprint(fp, "--%s\r\n", BOUNDARY); + Bprint(fp, "Content-disposition: form-data; name=\"%s\"\r\n\r\n", r->params[i].name); + Bprint(fp, "%s\r\n", r->params[i].value); + } + + /* Now the image itself */ + Bprint(fp, "--%s\r\n", BOUNDARY); + Bprint(fp, "Content-disposition: form-data; name=\"photo\"; filename=\"photo.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"); + Bwrite(fp, image->buf, image->sz); + Bprint(fp, "\r\n--%s\r\n", BOUNDARY); + Bterm(fp); + + /* Response */ + if((fp = Bopen(hprint("%s/%d/body", webmtpt, myconn), OREAD)) == nil) + sysfatal("post: opening body: %r"); + + close(myctl); + return fp; +} + +/* Dumps a request to stdout instead of webfs */ +int +dump(Request *r) +{ + uint i; + + print("%s", r->url); + for(i = 0; i < r->nparam; i++) + print("%c%s=%s", i == 0?'?':'&', r->params[i].name, + r->params[i].value); + print("\n"); + + return 0; +} + +/* XML shallow parsing */ +struct{ + const char *frob; + const char *token; + const char *pages; + const char *desc; + const char *id; + const char *title; + const char *farm; + const char *secret; + const char *server; +} Parsed; + +typedef void (*parser)(char *); +void +parse(Biobuf *body, uint n, ...) +{ + char *line; + uint i; + parser p; + va_list parsers; + + memset(&Parsed, 0, sizeof Parsed); + while(line = Brdstr(body, '\n', 1)){ + va_start(parsers, n); + for(i = 0; i < n; i++){ + p = va_arg(parsers, parser); + p(line); + } + va_end(parsers); + free(line); + } + + Bterm(body); +} + +int +parseregex(char *line, Reprog *rx, uint ndest, ...) +{ + Resub match[8]; + va_list dests; + const char **dest; + char tmp; + uint i; + + ndest++; + assert(ndest < 8); + + match[0].sp = match[0].ep = 0; + if(regexec(rx, line, match, ndest) != 1) + return -1; + + va_start(dests, ndest); + for(i = 1; i < ndest; i++){ + dest = va_arg(dests, const char**); + tmp = *match[i].ep; + *match[i].ep = '\0'; + *dest = hstrdup(match[i].sp); + *match[i].ep = tmp; + } + va_end(dests); + + + return 0; +} + +void +parsephoto(char *line) +{ + static Reprog *rx = nil; + static Reprog *trx = nil; + + if(rx == nil && !(rx = regcomp(""))) + sysfatal("parsephoto: couldn't compile rx"); + if(trx == nil && !(trx = regcomp("title=\"([^\"]+)\".*/>"))) + sysfatal("parsephoto: couldn't compile trx"); + + if(!parseregex(line, rx, 4, &Parsed.id, &Parsed.secret, + &Parsed.server, &Parsed.farm)) + parseregex(line, trx, 1, &Parsed.title); +} + +void +parsefrob(char *line) +{ + static Reprog *rx = nil; + + if(rx == nil && !(rx = regcomp("(.*)"))) + sysfatal("parsefrob: couldn't compile rx"); + + parseregex(line, rx, 1, &Parsed.frob); +} + +void +parsetoken(char *line) +{ + static Reprog *rx = nil; + + if(rx == nil && !(rx = regcomp("(.*)"))) + sysfatal("parsetoken: couldn't compile rx"); + + parseregex(line, rx, 1, &Parsed.token); +} + +void +parsedesc(char *line) +{ + static Reprog *rx = nil; + + if(rx == nil && !(rx = regcomp("(.*)"))) + sysfatal("parsedesc: couldn't compile rx"); + + parseregex(line, rx, 1, &Parsed.desc); +} + +void +parseid(char *line) +{ + static Reprog *rx = nil; + + if(rx == nil && !(rx = regcomp("(.*)"))) + sysfatal("getfrob: couldn't compile rx"); + + parseregex(line, rx, 1, &Parsed.id); +} + +void +parsepages(char *line) +{ + static Reprog *rx = nil; + + if(rx == nil && !(rx = regcomp("pages=\"([^\"]+)\""))) + sysfatal("parsepages: couldn't compile rx"); + + parseregex(line, rx, 1, &Parsed.pages); + +} + +/* Cache for reading images */ +struct{ + char *url; + ulong sz; + ulong n; + uchar *data; +} Filecache; + +uchar * +cache(char *url, long *n) +{ + Biobuf *fp; + long r; + + /* If already cached */ + if(Filecache.url && !strcmp(url, Filecache.url) && Filecache.n > 0){ + *n = Filecache.n; + return Filecache.data; + } + + /* Load file from flickr */ + Filecache.n = *n = 0; + if(Filecache.url) + free(Filecache.url); + Filecache.url = estrdup9p(url); + reset(&fr); + fr.url = hstrdup(url); + if((fp = get(&fr)) == nil){ + return nil; + } + + do{ + if(Filecache.sz <= Filecache.n){ + Filecache.sz = (Filecache.sz + 1) << 1; + Filecache.data = erealloc9p(Filecache.data, sizeof(*Filecache.data) * Filecache.sz); + } + r = Bread(fp, Filecache.data + Filecache.n, + Filecache.sz - Filecache.n); + Filecache.n += r; + }while(r > 0); + Bterm(fp); + + *n = Filecache.n; + return Filecache.data; +} + +/* 9p */ +void +fsread(Req *r) +{ + Pid *p; + const char *url; + void *c; + long n; + + p = (Pid*)r->fid->file->aux; + if(!p){ + respond(r, "empty aux"); + return; + } + + if(p->id == nil){ + respond(r, "no associated id"); + return; + } + + hpush(); + url = hprint("http://farm%s.staticflickr.com/%s/%s_%s_b.jpg", + p->farm, p->server, p->id, p->secret); + c = cache(url, &n); + hpop(); + + if(n == 0){ + respond(r, "cache error"); + return; + } + readbuf(r, c, n); + respond(r, nil); +} + +void +fswstat(Req *r) +{ + char *p, *q; + Pid *aux; + + aux = (Pid*)r->fid->file->aux; + hpush(); + + /* Name changes */ + if(r->d.name && r->d.name[0]){ + /* Check extension */ + p = strrchr(r->d.name, '.'); + q = strrchr(r->fid->file->Dir.name, '.'); + if(p == nil || strcmp(p, q)){ + respond(r, "cannot change extension"); + hpop(); + return; + } + *p = '\0'; + + /* Get description */ + reset(&fr); + add(&fr, "method", "flickr.photos.getInfo"); + add(&fr, "photo_id", aux->id); + auth(&fr); + parse(get(&fr), 1, parsedesc); + + /* Update flickr */ + reset(&fr); + add(&fr, "method", "flickr.photos.setMeta"); + add(&fr, "photo_id", aux->id); + add(&fr, "title", r->d.name); + if(Parsed.desc) + add(&fr, "description", Parsed.desc); + auth(&fr); + parse(get(&fr), 0); + + /* Success */ + *p = '.'; + free(r->fid->file->Dir.name); + r->fid->file->Dir.name = estrdup9p(r->d.name); + } + + hpop(); + respond(r, nil); +} + +void +fsremove(Req *r) +{ + Pid *aux; + + if(r->fid->file == nil || r->fid->file->aux == nil){ + respond(r, nil); + return; + } + hpush(); + aux = (Pid*)r->fid->file->aux; + reset(&fr); + add(&fr, "method", "flickr.photos.delete"); + add(&fr, "photo_id", aux->id); + auth(&fr); + parse(get(&fr), 0); + hpop(); + respond(r, nil); +} + +void +fscreate(Req *r) +{ + Pid *aux; + char *p; + File *f; + + + p = strrchr(r->ifcall.name, '.'); + if(p == nil || strcmp(p, ".jpg")) + respond(r, "invalid filename"); + + if((f = createfile(r->fid->file, r->ifcall.name, nil, 0666, nil)) == nil){ + respond(r, "couldn't create file"); + return; + } + + aux = emalloc9p(sizeof(*aux)); + aux->id = nil; + aux->buf = nil; + aux->sz = 0; + f->aux = aux; + r->fid->file = f; + r->ofcall.qid = f->qid; + + respond(r, nil); +} + +void +fswrite(Req *r) +{ + Pid *aux; + vlong offset; + long count; + + aux = (Pid*) r->fid->file->aux; + if(aux->id){ + respond(r, "replacing files not supported"); + return; + } + + offset = r->ifcall.offset; + count = r->ifcall.count; + if(offset+count >= aux->sz){ + aux->buf = erealloc9p(aux->buf, offset+count+1); + aux->sz = offset+count; + } + + memmove(aux->buf+offset, r->ifcall.data, count); + r->ofcall.count = count; + + respond(r, nil); +} + +void +fsdestroyfid(Fid *fid) +{ + Pid *aux; + char *p; + + + if(fid->file == nil) + return; + + aux = (Pid*)fid->file->aux; + if(aux == nil) + return; + + hpush(); + + if(aux->id == nil){ + /* Upload buffer to flickr */ + reset(&fr); + fr.url = "http://api.flickr.com/services/upload/"; + p = strrchr(fid->file->name, '.'); + *p = '\0'; + add(&fr, "title", fid->file->name); + *p = '.'; + auth(&fr); + + /* Parse response */ + parse(post(&fr, aux), 1, parseid); + if(Parsed.id == nil) + sysfatal("fsdestroyfid: bad response"); + //fprint(2, "got id: %s", Parsed.id); + free(aux->buf); + + /* Query image to find farm/server/secret */ + reset(&fr); + add(&fr, "method", "flickr.photos.getInfo"); + add(&fr, "photo_id", Parsed.id); + auth(&fr); + parse(get(&fr), 1, parsephoto); + + if(Parsed.id == nil) + sysfatal("fsdestroyfid: getinfo failed"); + aux->id = estrdup9p(Parsed.id); + aux->farm = estrdup9p(Parsed.farm); + aux->server = estrdup9p(Parsed.server); + aux->secret = estrdup9p(Parsed.secret); + } + hpop(); +} + +Srv fs = { + .destroyfid= fsdestroyfid, + .read= fsread, + .write= fswrite, + .wstat= fswstat, + .remove= fsremove, + .create= fscreate, +}; + + +void +fsdestroyfile(File *f) +{ + Pid *aux; + aux = (Pid*)f->aux; + if(aux != nil){ + if(aux->id){ + free(aux->secret); + free(aux->farm); + free(aux->id); + free(aux->server); + free(aux); + }else + if(aux->sz > 0) + free(aux->buf); + } +} + +/* Flickr searching to build file tree */ +void +parsesearch(char *line) +{ + File *f; + Pid *aux; + const char *fn; + + hpush(); + + Parsed.title = nil; + parsephoto(line); + + if(Parsed.title){ + aux = emalloc9p(sizeof(*aux)); + memset(aux, 0, sizeof(*aux)); + fn = hprint("%s.jpg", Parsed.title); + f = createfile(fs.tree->root, fn, nil, 0666, aux); + if(f == nil){ + fprint(2, "cannot create file: %s\n", fn); + free(aux); + hpop(); + return; + } + aux->id = estrdup9p(Parsed.id); + aux->farm = estrdup9p(Parsed.farm); + aux->secret = estrdup9p(Parsed.secret); + aux->server = estrdup9p(Parsed.server); + closefile(f); + } + + hpop(); +} + +void +searchflickr(void) +{ + uint page = 1; + + + do{ + hpush(); + reset(&fr); + add(&fr, "method", "flickr.photos.search"); + add(&fr, "user_id", "me"); + add(&fr, "per_page", "500"); + add(&fr, "page", hprint("%d", page)); + auth(&fr); + parse(get(&fr), 2, parsesearch, parsepages); + if(Parsed.pages == nil){ + fprint(2, "searchflickr: warning couldn't parse pages\n"); + break; + } + hpop(); + }while(++page < atoi(Parsed.pages)); + +} + + +void +usage(void) +{ + fprint(2, "%s: [-D] [-w webfs mtpt] [-s srvname] [-m mtpt]\n", argv0); + exits("usage"); +} + + +void +main(int argc, char *argv[]) +{ + UserPasswd *up; + char buf; + char *mtpt; + char *srvname; + Qid q; + + memset(&Filecache, 0, sizeof Filecache); + mtpt = "/n/flickr"; + srvname = nil; + hinit(HEAP); + + //extern int hdebug = 1; + + ARGBEGIN{ + case 'D': + chatty9p++; + break; + case 'w': + webmtpt = EARGF(usage()); + break; + case 'm': + mtpt = EARGF(usage()); + break; + case 's': + srvname = EARGF(usage()); + break; + case 'h': + default: + usage(); + }ARGEND; + + if(argc) + usage(); + + fmtinstall('M', digestfmt); + fmtinstall('U', urlfmt); + + ctlfd = webclone(&conn); + + /* Try finding a token */ + up = auth_getuserpasswd(nil, "proto=pass role=client server=flickr.com user=%s", KEY); + + /* No token */ + if(up == nil){ + /* Get a frob */ + reset(&fr); + add(&fr, "method", "flickr.auth.getFrob"); + add(&fr, "api_key", KEY); + sign(&fr); + parse(get(&fr), 1, parsefrob); + if(Parsed.frob == nil) + sysfatal("couldn't parse frob"); + + /* Authentication url */ + reset(&fr); + fr.url = "http://flickr.com/services/auth/"; + add(&fr, "api_key", KEY); + add(&fr, "perms", "delete"); + add(&fr, "frob", Parsed.frob); + sign(&fr); + print("Authenticate then press enter: "); + dump(&fr); + read(0, &buf, 1); + + /* Fetch token */ + reset(&fr); + fr.url = "http://flickr.com/services/rest/"; + add(&fr, "method", "flickr.auth.getToken"); + add(&fr, "api_key", KEY); + add(&fr, "frob", Parsed.frob); + sign(&fr); + parse(get(&fr), 1, parsetoken); + if(Parsed.token == nil) + sysfatal("couldn't parse token"); + print("key proto=pass role=client server=flickr.com user=%s !password=%s\n", KEY, Parsed.token); + + exits(0); + } + + /* We got a token */ + token = up->passwd; + + /* Populate our tree */ + fs.tree = alloctree(nil, nil, DMDIR|0777, fsdestroyfile); + q = fs.tree->root->qid; + searchflickr(); + + /* Mount ourselves */ + postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE); + + exits(0); +} diff --git a/heap.c b/heap.c new file mode 100644 index 0000000..0789abc --- /dev/null +++ b/heap.c @@ -0,0 +1,63 @@ +#include +#include +#include + +static void *pool = nil; +static void *ptop; +static ulong psz = 0; +static ulong csz = 0; + +static void *stack[128]; // Stack depth is 128 maximum +static void **stop = stack; + +int hdebug = 0; + +void +hinit(ulong sz) +{ + if(pool) + free(pool); + pool = malloc(sz); + if(pool == nil) + sysfatal("hinit: %r"); + + psz = sz; + csz = 0; + ptop = pool; +} + +void * +halloc(ulong sz) +{ + void *ptr; + + /* Check bounds */ + if(sz + csz > psz) + sysfatal("halloc: heap exhausted"); + + ptr = ptop; + ptop = sz + (uchar*)ptop; + csz += sz; + + if(hdebug) + fprint(2, "halloc: %uld\n", csz); + + return ptr; +} + +/* Stack management */ + +ulong +hpush(void) +{ + *stop = ptop; + return (ulong)*stop++; +} + +ulong +hpop(void) +{ + ptop = *--stop; + csz = ((uchar *)ptop - (uchar*)pool); + return (ulong)ptop; +} diff --git a/mheap.h b/mheap.h new file mode 100644 index 0000000..2a0d54f --- /dev/null +++ b/mheap.h @@ -0,0 +1,17 @@ +#pragma lib "libmheap.a" + +void hinit(ulong); + +/* Heap markers */ +ulong hpush(void); +ulong hpop(void); + + +/* Allocators */ + +void *halloc(ulong); +//void *hrealloc(void *, ulong); + +/* String functions */ +const char *hprint(char *, ...); +const char *hstrdup(char *); diff --git a/mkfile b/mkfile new file mode 100644 index 0000000..8d49093 --- /dev/null +++ b/mkfile @@ -0,0 +1,12 @@ + +#include +#include + +const char * +hprint(char *fmt, ...) +{ + char *buf, *hbuf; + int len, n; + va_list args; + + /* Initial buffer */ + len = 512; + buf = nil; + + do{ + len <<= 1; + buf = realloc(buf, len); + if(buf == nil) + sysfatal("hsprint: %r"); + + va_start(args, fmt); + n = vsnprint(buf, len, fmt, args) + 1; + va_end(args); + }while(n == len); + + hbuf = halloc(n); + memmove(hbuf, buf, n); + free(buf); + + return hbuf; +} + +const char * +hstrdup(char *s) +{ + char *u; + + u = halloc(strlen(s) + 1); + memmove(u, s, strlen(s) + 1); + return u; +} -- cgit v1.2.3