summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Bedo <cu@cua0.org>2012-11-19 11:15:39 +1100
committerJustin Bedo <cu@cua0.org>2012-11-19 11:15:39 +1100
commit13da42dedf0305f7f47dfc0f6d00ca5cb169493d (patch)
treeac0a2cc964f12b3516f6d688463eda692bed33d0
Initial importHEADmaster
-rw-r--r--flickrfs.c826
-rw-r--r--heap.c63
-rw-r--r--mheap.h17
-rw-r--r--mkfile12
-rw-r--r--string.c42
5 files changed, 960 insertions, 0 deletions
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<u.h>
+#include<libc.h>
+#include<mp.h>
+#include<libsec.h>
+#include<regexp.h>
+#include<bio.h>
+#include<auth.h>
+#include<fcall.h>
+#include<thread.h>
+#include<9p.h>
+#include<mheap.h>
+
+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; i<MD5dlen; i++)
+ sprint(buf+2*i, "%.2ux", p[i]);
+ return fmtstrcpy(fmt, buf);
+}
+
+
+/* Flickr API requests */
+typedef struct{
+ const char *name;
+ const char *value;
+} Parameter;
+
+typedef struct{
+ const char *url;
+ uint nparam;
+ Parameter params[16];
+} Request;
+
+Request fr;
+
+int
+pcmp(Parameter *a, Parameter *b)
+{
+ int c = strcmp(a->name, 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("<photo[ \t].*id=\"([^\"]+)\".*secret=\"([^\"]+)\".*server=\"([^\"]+)\".*farm=\"([^\"]+)\".*>")))
+ 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("<frob>(.*)</frob>")))
+ 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("<token>(.*)</token>")))
+ 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("<description>(.*)</description>")))
+ 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("<photoid>(.*)</photoid>")))
+ 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<u.h>
+#include<libc.h>
+#include<mheap.h>
+
+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 @@
+</$objtype/mkfile
+
+TARG=flickrfs
+
+OFILES=\
+ flickrfs.$O\
+ string.$O\
+ heap.$O\
+
+BIN=/$objtype/bin
+
+</sys/src/cmd/mkone
diff --git a/string.c b/string.c
new file mode 100644
index 0000000..d8a6429
--- /dev/null
+++ b/string.c
@@ -0,0 +1,42 @@
+#include<u.h>
+#include<libc.h>
+#include<mheap.h>
+
+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;
+}