<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/bedo/flickrfs.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/bedo/flickrfs.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
#include&lt;u.h&gt;
#include&lt;libc.h&gt;
#include&lt;mp.h&gt;
#include&lt;libsec.h&gt;
#include&lt;regexp.h&gt;
#include&lt;bio.h&gt;
#include&lt;auth.h&gt;
#include&lt;fcall.h&gt;
#include&lt;thread.h&gt;
#include&lt;9p.h&gt;

const char KEY[] = "a1b693d302635eb916d330aebd0bd5c8";
const char SECRET[] = "36eb40a37bf10381";
const char BOUNDARY[] = "thisismyboundarytherearemanylikeitbutthisoneismine";
char *token;

/* Webfs */
char *webmtpt = "/mnt/web";
int ctlfd, conn;

int
webclone(int *c)
{
	char buf[128];
	int n, fd;
	
	snprint(buf, sizeof buf, "%s/clone", webmtpt);
	if((fd = open(buf, ORDWR)) &lt; 0)
		sysfatal("couldn't open %s: %r", buf);
	if((n = read(fd, buf, sizeof buf-1)) &lt; 0)
		sysfatal("reading clone: %r");
	if(n == 0)
		sysfatal("short read on clone");
	buf[n] = '\0';
	*c = atoi(buf);
	
	return fd;
}

/* Formatters for URL and MD5 digest encoding */
#define ALPHANUM(x) ((x) &gt;= 'a' &amp;&amp; (x) &lt;= 'z' || \
	(x) &gt;= 'A' &amp;&amp; (x) &lt;= 'Z' || \
	(x) &gt;= '0' &amp;&amp; (x) &lt;= '9' || \
	(x) == '_' || (x) == '.' || (x) == '-')
#pragma varargck type "U" char*

static int
urlfmt(Fmt *fmt)
{
	char buf[1024];
	char *p, *q;
	
	
	for(p = va_arg(fmt-&gt;args, char*), q = buf; *p; p++)
		if(ALPHANUM(*p))
			*q++ = *p;
		else
			q += sprint(q, "%%%X", (uchar)*p);
	*q = '\0';
	
	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-&gt;args, uchar*);
	for(i=0; i&lt;MD5dlen; i++)
		sprint(buf+2*i, "%.2ux", p[i]);
	return fmtstrcpy(fmt, buf);
}


/* Flickr API requests */
typedef struct{
	char *name;
	char *value;
} Parameter;

typedef struct{
	char url[256];
	uint nparam;
	Parameter params[16];
} Request;

Request fr;

int
pcmp(Parameter *a, Parameter *b)
{
	int c = strcmp(a-&gt;name, b-&gt;name);
	if(c != 0) return c;
	
	return strcmp(a-&gt;value, b-&gt;value);
}

void
sortreq(Request *r)
{
	qsort(r-&gt;params, r-&gt;nparam, sizeof(Parameter), (int(*)(void *, void *))pcmp);
}

void
add(Request *r, char *name, char *value)
{
	r-&gt;params[r-&gt;nparam].name = estrdup9p(name);
	r-&gt;params[r-&gt;nparam++].value = estrdup9p(value);
}

void
reset(Request *r)
{
	uint i;
	
	for(i = 0; i &lt; r-&gt;nparam; i++){
		free(r-&gt;params[i].name);
		free(r-&gt;params[i].value);
	}
	r-&gt;nparam = 0;
	strcpy(r-&gt;url, "http://flickr.com/services/rest/");
}

void
sign(Request *r)
{
	uchar digest[MD5dlen];
	char buffer[1024];
	uint len, i;
	
	sortreq(r);
	len = snprint(buffer, sizeof(buffer), "%s", SECRET);
	for(i = 0; i &lt; r-&gt;nparam; i++)
		len += snprint(buffer + len, sizeof(buffer) - len,
			"%s%s", r-&gt;params[i].name, r-&gt;params[i].value);
	
	md5((uchar *)buffer, strlen(buffer), digest, nil);
	snprint(buffer, sizeof buffer, "%M", digest);
	add(r, "api_sig", buffer);
}

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 unuploaded 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)
{
	char buf[2056], *ptr;
	int i, n;
	Biobuf *fp;
	
	
	/* Compile url */
	ptr = buf + snprint(buf, sizeof buf, "url %s", r-&gt;url);
	for(i = 0; i &lt; r-&gt;nparam; i++)
		ptr += snprint(ptr, sizeof buf + buf - ptr, "%c%U=%U", i == 0 ? '?':'&amp;',
			r-&gt;params[i].name, r-&gt;params[i].value);
	
	if(write(ctlfd, buf, n = strlen(buf)) != n)
		sysfatal("get: write: %r");
		
	/* Response */
	snprint(buf, sizeof buf, "%s/%d/body", webmtpt, conn);
	if((fp = Bopen(buf, OREAD)) == nil)
		sysfatal("get: couldn't open body: %r");
		
	return fp;
}

/* Posts a photo to flickr */
Biobuf *
post(Request *r, Pid *image)
{
	char buf[2056];
	int i, n;
	Biobuf *fp;
	
	/* our own webfs connection */
	int myconn, myctl;
	myctl = webclone(&amp;myconn);
	
	/* Compile url */
	snprint(buf, sizeof buf, "url %s", r-&gt;url);
	if(write(myctl, buf, n = strlen(buf)) != n)
		sysfatal("post: write: %r");
	snprint(buf, sizeof buf, "contenttype multipart/form-data; boundary=%s", BOUNDARY);
	if(write(myctl, buf, n = strlen(buf)) != n)
		sysfatal("post: write: %r");
	
	/* Open postbody */
	snprint(buf, sizeof buf, "%s/%d/postbody", webmtpt, myconn);
	if((fp = Bopen(buf, OWRITE)) == nil)
		sysfatal("post: opening postbody: %r");
	
	/* Post parameters */
	for(i = 0; i &lt; r-&gt;nparam; i++){
		Bprint(fp, "--%s\r\n", BOUNDARY);
		Bprint(fp, "Content-disposition: form-data; name=\"%s\"\r\n\r\n", r-&gt;params[i].name);
		Bprint(fp, "%s\r\n", r-&gt;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-&gt;buf, image-&gt;sz);
	Bprint(fp, "\r\n--%s\r\n", BOUNDARY);
	Bterm(fp);
	
	/* Response */
	snprint(buf, sizeof buf, "%s/%d/body", webmtpt, myconn);
	if((fp = Bopen(buf, 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-&gt;url);
	for(i = 0; i &lt; r-&gt;nparam; i++)
		print("%c%s=%s", i == 0?'?':'&amp;', r-&gt;params[i].name,
			r-&gt;params[i].value);
	print("\n");
	
	return 0;
}

/* XML shallow parsing */
struct{
	char frob[128];
	char token[128];
	char pages[16];
	char desc[1024];
	char id[32];
	char title[1024];
	char farm[128];
	char secret[128];
	char server[128];
} Parsed;

typedef void (*parser)(char *);
void
parse(Biobuf *body, uint n, ...)
{
	char *line;
	uint i;
	parser p;
	va_list parsers;
	
	memset(&amp;Parsed, 0, sizeof Parsed);
	while(line = Brdstr(body, '\n', 1)){
		/*if(n == 0)
			fprint(2, "fparse: %s\n", line);*/
		va_start(parsers, n);
		for(i = 0; i &lt; 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;
	va_list dests;
	char *dest;
	uint i;
	
	ndest++;
	match = emalloc9p(sizeof(*match) * ndest);
	
	match[0].sp = match[0].ep = 0;
	if(regexec(rx, line, match, ndest) != 1)
		return -1;
	
	va_start(dests, ndest);
	for(i = 1; i &lt; ndest; i++){
		dest = va_arg(dests, char*);
		strncpy(dest, match[i].sp, match[i].ep - match[i].sp);
		dest[match[i].ep - match[i].sp] = '\0';
	}
	va_end(dests);
	
	free(match);
	
	return 0;
}

void
parsephoto(char *line)
{
	static Reprog *rx = nil;
	static Reprog *trx = nil;

	if(rx == nil &amp;&amp; !(rx = regcomp("&lt;photo[ \t].*id=\"([^\"]+)\".*secret=\"([^\"]+)\".*server=\"([^\"]+)\".*farm=\"([^\"]+)\".*&gt;")))
		sysfatal("parsephoto: couldn't compile rx");
	if(trx == nil &amp;&amp; !(trx = regcomp("title=\"([^\"]+)\".*/&gt;")))
		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 &amp;&amp; !(rx = regcomp("&lt;frob&gt;(.*)&lt;/frob&gt;")))
		sysfatal("getfrob: couldn't compile rx");
		
	parseregex(line, rx, 1, Parsed.frob);
}

void
parsetoken(char *line)
{
	static Reprog *rx = nil;
	
	if(rx == nil &amp;&amp; !(rx = regcomp("&lt;token&gt;(.*)&lt;/token&gt;")))
		sysfatal("getfrob: couldn't compile rx");
		
	parseregex(line, rx, 1, Parsed.token);
}

void
parsedesc(char *line)
{
	static Reprog *rx = nil;
	
	if(rx == nil &amp;&amp; !(rx = regcomp("&lt;description&gt;(.*)&lt;/description&gt;")))
		sysfatal("getfrob: couldn't compile rx");
		
	parseregex(line, rx, 1, Parsed.desc);
}

void
parseid(char *line)
{
	static Reprog *rx = nil;
	
	if(rx == nil &amp;&amp; !(rx = regcomp("&lt;photoid&gt;(.*)&lt;/photoid&gt;")))
		sysfatal("getfrob: couldn't compile rx");
		
	parseregex(line, rx, 1, Parsed.id);
}

void
parsepages(char *line)
{
	static Reprog *rx = nil;
	
	if(rx == nil &amp;&amp; !(rx = regcomp("pages=\"([^\"]+)\"")))
		sysfatal("parsesearch: couldn't compile rx");
		
	parseregex(line, rx, 1, Parsed.pages);
		
}

/* Cache for reading images */
struct{
	char url[1024];
	ulong sz;
	ulong n;
	uchar *data;
} Filecache;

uchar *
cache(char *url, long *n)
{
	Biobuf *fp;
	long r;
	
	/* If already cached */
	if(!strncmp(url, Filecache.url, sizeof Filecache.url) &amp;&amp; Filecache.n &gt; 0){
		*n = Filecache.n;
		return Filecache.data;
	}
	
	/* Load file from flickr */
	Filecache.n = *n = 0;
	strncpy(Filecache.url, url, sizeof Filecache.url);
	reset(&amp;fr);
	strncpy(fr.url, url, sizeof fr.url);
	if((fp = get(&amp;fr)) == nil)
		return nil;
	do{
		if(Filecache.sz &lt;= Filecache.n){
			Filecache.sz = (Filecache.sz + 1) &lt;&lt; 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 &gt; 0);
	Bterm(fp);
	
	*n = Filecache.n;
	return Filecache.data;
}

/* 9p */
void
fsread(Req *r)
{
	Pid *p;
	char buf[1024];
	void *c;
	long n;
	
	p = (Pid*)r-&gt;fid-&gt;file-&gt;aux;
	if(!p){
		respond(r, "empty aux");
		return;
	}
		
	if(p-&gt;id == nil){
		respond(r, "no associated id");
		return;
	}
	
	snprint(buf, sizeof buf, "http://farm%s.staticflickr.com/%s/%s_%s_b.jpg", p-&gt;farm, p-&gt;server, p-&gt;id,  p-&gt;secret);
	c = cache(buf, &amp;n);
	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-&gt;fid-&gt;file-&gt;aux;
	
	/* Name changes */
	if(r-&gt;d.name &amp;&amp; r-&gt;d.name[0]){
		/* Check extension */
		p = strrchr(r-&gt;d.name, '.');
		q = strrchr(r-&gt;fid-&gt;file-&gt;Dir.name, '.');
		if(p == nil || strcmp(p, q)){
			respond(r, "cannot change extension");
			return;
		}
		*p = '\0';
		
		/* Get description */
		reset(&amp;fr);
		add(&amp;fr, "method", "flickr.photos.getInfo");
		add(&amp;fr, "photo_id", aux-&gt;id);
		auth(&amp;fr);
		parse(get(&amp;fr), 1, parsedesc);
		
		/* Update flickr */
		reset(&amp;fr);
		add(&amp;fr, "method", "flickr.photos.setMeta");
		add(&amp;fr, "photo_id", aux-&gt;id);
		add(&amp;fr, "title", r-&gt;d.name);
		add(&amp;fr, "description", Parsed.desc);
		auth(&amp;fr);
		parse(get(&amp;fr), 0);
		
		/* Success */
		*p = '.';
		free(r-&gt;fid-&gt;file-&gt;Dir.name);
		r-&gt;fid-&gt;file-&gt;Dir.name = estrdup9p(r-&gt;d.name);
	}

	respond(r, nil);
}

void
fsremove(Req *r)
{
	Pid *aux;
	
	if(r-&gt;fid-&gt;file == nil || r-&gt;fid-&gt;file-&gt;aux == nil){
		respond(r, nil);
		return;
	}
	aux = (Pid*)r-&gt;fid-&gt;file-&gt;aux;
	reset(&amp;fr);
	add(&amp;fr, "method", "flickr.photos.delete");
	add(&amp;fr, "photo_id", aux-&gt;id);
	auth(&amp;fr);
	parse(get(&amp;fr), 0);
	respond(r, nil);
}

void
fscreate(Req *r)
{
	Pid *aux;
	char *p;
	File *f;
	
	
	p = strrchr(r-&gt;ifcall.name, '.');
	if(p == nil || strcmp(p, ".jpg"))
		respond(r, "invalid filename");
	
	if((f = createfile(r-&gt;fid-&gt;file, r-&gt;ifcall.name, nil, 0666, nil)) == nil){
		respond(r, "couldn't create file");
		return;
	}
		
	aux = emalloc9p(sizeof(*aux));
	aux-&gt;id = nil;
	aux-&gt;buf = nil;
	aux-&gt;sz = 0;
	f-&gt;aux = aux;
	r-&gt;fid-&gt;file = f;
	r-&gt;ofcall.qid = f-&gt;qid;
	
	respond(r, nil);
}

void
fswrite(Req *r)
{
	Pid *aux;
	vlong offset;
	long count;
	
	aux = (Pid*) r-&gt;fid-&gt;file-&gt;aux;
	if(aux-&gt;id){
		respond(r, "replacing files not supported");
		return;
	}
	
	offset = r-&gt;ifcall.offset;
	count = r-&gt;ifcall.count;
	if(offset+count &gt;= aux-&gt;sz){
		aux-&gt;buf = erealloc9p(aux-&gt;buf, offset+count+1);
		aux-&gt;sz = offset+count;
	}
		
	memmove(aux-&gt;buf+offset, r-&gt;ifcall.data, count);
	r-&gt;ofcall.count = count;
	
	respond(r, nil);
}

void
fsdestroyfid(Fid *fid)
{
	Pid *aux;
	char *p;
	
	if(fid-&gt;file == nil)
		return;

	aux = (Pid*)fid-&gt;file-&gt;aux;
	if(aux == nil)
		return;
		
	if(aux-&gt;id == nil){
		/* Upload buffer to flickr */
		reset(&amp;fr);
		strcpy(fr.url, "http://api.flickr.com/services/upload/");
		p = strrchr(fid-&gt;file-&gt;name, '.');
		*p = '\0';
		add(&amp;fr, "title", fid-&gt;file-&gt;name);
		*p = '.';
		auth(&amp;fr);
			
		/* Parse response */
		parse(post(&amp;fr, aux), 1, parseid);
		if(Parsed.id[0] == '\0')
			sysfatal("fsdestroyfid: bad response");
		//fprint(2, "got id: %s", Parsed.id);
		free(aux-&gt;buf);
		
		/* Query image to find farm/server/secret */
		reset(&amp;fr);
		add(&amp;fr, "method", "flickr.photos.getInfo");
		add(&amp;fr, "photo_id", Parsed.id);
		auth(&amp;fr);
		parse(get(&amp;fr), 1, parsephoto);
		
		if(Parsed.id[0] == '\0')
			sysfatal("fsdestroyfid: getinfo failed");
		aux-&gt;id = estrdup9p(Parsed.id);
		aux-&gt;farm = estrdup9p(Parsed.farm);
		aux-&gt;server = estrdup9p(Parsed.server);
		aux-&gt;secret = estrdup9p(Parsed.secret);
	}
}

Srv fs = {
	.destroyfid=	fsdestroyfid,
	.read=	fsread,
	.write=	fswrite,
	.wstat=	fswstat,
	.remove=	fsremove,
	.create=	fscreate,
};


void
fsdestroyfile(File *f)
{
	Pid *aux;
	aux = (Pid*)f-&gt;aux;
	if(aux != nil){
		if(aux-&gt;id){
			free(aux-&gt;secret);
			free(aux-&gt;farm);
			free(aux-&gt;id);
			free(aux-&gt;server);
			free(aux);
		}else
			if(aux-&gt;sz &gt; 0)
				free(aux-&gt;buf);
	}
}

/* Flickr searching to build file tree */
void
parsesearch(char *line)
{
	char fn[1024];
	File *f;
	Pid *aux;
	
	Parsed.title[0] = '\0';
	parsephoto(line);
	
	if(Parsed.title[0]){
		aux = emalloc9p(sizeof(*aux));
		memset(aux, 0, sizeof(*aux));
		snprint(fn, sizeof fn, "%s.jpg", Parsed.title);
		f = createfile(fs.tree-&gt;root, fn, nil, 0666, aux);
		if(f == nil){
			fprint(2, "cannot create file: %s\n", fn);
			free(aux);
			return;
		}
		aux-&gt;id = estrdup9p(Parsed.id);
		aux-&gt;farm = estrdup9p(Parsed.farm);
		aux-&gt;secret = estrdup9p(Parsed.secret);
		aux-&gt;server = estrdup9p(Parsed.server);
		closefile(f);
	}
		
}

void
searchflickr(void)
{
	uint page = 1;
	char buf[16];
	
	do{
		reset(&amp;fr);
		add(&amp;fr, "method", "flickr.photos.search");
		add(&amp;fr, "user_id", "me");
		add(&amp;fr, "per_page", "500");
		snprint(buf, sizeof buf, "%d", page);
		add(&amp;fr, "page", buf);
		auth(&amp;fr);
		parse(get(&amp;fr), 2, parsesearch, parsepages);
		if(Parsed.pages[0] == '\0'){
			fprint(2, "searchflickr: warning couldn't parse pages\n");
			break;
		}
		page++;
	}while(page &lt; 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[128];
	char *mtpt;
	char *srvname;
	Qid q;
	
	memset(&amp;Filecache, 0, sizeof Filecache);
	mtpt = "/n/flickr";
	srvname = nil;
	
	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(&amp;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(&amp;fr);
		add(&amp;fr, "method", "flickr.auth.getFrob");
		add(&amp;fr, "api_key", KEY);
		sign(&amp;fr);
		parse(get(&amp;fr), 1, parsefrob);
		if(Parsed.frob == nil)
			sysfatal("couldn't parse frob");
		
		/* Authentication url */
		reset(&amp;fr);
		strcpy(fr.url, "http://flickr.com/services/auth/");
		add(&amp;fr, "api_key", KEY);
		add(&amp;fr, "perms", "delete");
		add(&amp;fr, "frob", Parsed.frob);
		sign(&amp;fr);
		print("Authenticate then press enter: ");
		dump(&amp;fr);
		read(0, buf, 1);
		
		/* Fetch token */
		reset(&amp;fr);
		strcpy(fr.url, "http://flickr.com/services/rest/");
		add(&amp;fr, "method", "flickr.auth.getToken");
		add(&amp;fr, "api_key", KEY);
		add(&amp;fr, "frob", Parsed.frob);
		sign(&amp;fr);
		parse(get(&amp;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-&gt;passwd;
	
	/* Populate our tree */
	fs.tree = alloctree(nil, nil, DMDIR|0777, fsdestroyfile);
	q = fs.tree-&gt;root-&gt;qid;
	searchflickr();
	
	/* Mount ourselves */
	postmountsrv(&amp;fs, srvname, mtpt, MREPL|MCREATE);

	exits(0);
}
<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:webmaster@plan9.bell-labs.com">webmaster@plan9.bell-labs.com</a>.</span>
</font></center>
</body>
</html>