shithub: s3

ref: 67323b833fec711c372f907ead4a47967cb878cf
dir: /fs.c/

View raw version
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <mp.h>
#include <libsec.h>
#include <bio.h>
#include "s3.h"
#include "xml.h"
#include "cmd.h"

typedef struct Xfid Xfid;
typedef struct Tab Tab;
typedef struct Child Child;

struct Child {
	char name[128];
	uchar type;
};

struct Xfid {
	char path[512];
	int nc;
	Child *c;
};

struct Tab {
	char key[512];
	uvlong val;
	Tab *next;
};

Tab table[256];
uvlong nextqid;
S3 s3;

static ulong
shash(char *s)
{
	ulong hash;

	hash = 7;
	for(; *s; s++)
		hash = hash*31  + *s;
	return hash;
}

static uvlong
findoradd(char *s)
{
	Tab *e, **ep;

	e = &table[shash(s)%nelem(table)];
	ep = &e;
	if(e->key == nil)
		goto Add;
	for(; e != nil; e = e->next){
		ep = &e->next;
		if(strcmp(e->key, s) == 0)
			return e->val;
	}
	*ep = mallocz(sizeof *e, 1);
Add:
	snprint((*ep)->key, sizeof (*ep)->key, "%s", s);
	(*ep)->val = nextqid++;
	return (*ep)->val;
}

static void
fsattach(Req *r)
{
	Xfid *x;

	r->fid->aux = mallocz(sizeof(Xfid), 1);
	x = r->fid->aux;
	x->path[0] = '/', x->path[1] = '\0';
	r->fid->qid = (Qid){findoradd(x->path), 0, QTDIR};
	r->ofcall.qid = r->fid->qid;
	respond(r, nil);
}

static int
yoinkfiles(Xelem *x, char *dst[], int ndst, int trim)
{
	int n;
	Xelem *key;
	Xelem *i;

	/* Sanity check */
	for(i = x; i != nil; i = i->next){
		if(strcmp(i->n, "ListBucketResult") == 0)
			goto Valid;
	}
	werrstr("xml result did not include ListBucketResult");
	return -1;

Valid:
	x = xmlget(x, "Contents", nil);
	if(x == nil)
		return 0;

	for(n = 0; x != nil && ndst > 0; x = x->next, ndst--){
		key = xmlget(x, "Key", nil);
		if(key == nil) 
			continue;
		dst[n++] = strdup(key->v + trim);
	}
	return n;
}

static int
s3ls(char *url, char *dst[], int ndst, int trim)
{
	char buf[2048];
	Hcon con;
	int n;
	long count;
	Xelem *raw;
	Bstr *b;

	if(s3get(&s3, &con, url) < 0){
		werrstr("file does not exist: %r");
		return -1;
	}
	count = read(con.body, buf, sizeof buf);
	if(count <= 0){
		werrstr("read error on list body");
		return -1;
	}
	b = Bstropen(buf, count);
	raw = xmlread(b, 0);
	if(raw == nil){
		werrstr("s3 did not give us valid xml");
		return -1;
	}
	Bterm(b);
	n = yoinkfiles(raw, dst, ndst, trim);
	xmlfree(raw);

	return n;
}

static void
packwalk(Req *r, uchar qtype, char *prefix, char *path)
{
	char key[512];
	char *p;
	int i;
	int skip;

	for(skip = 0, p = prefix+1; p = strchr(p, '/'); p++)
		skip++;
	for(i = 0, p = path; p = strchr(p, '/'); p++){
		if(skip--)
			continue;
		snprint(key, sizeof key, "%s%.*s", prefix, (int)(p-path)+1, path);
		r->ofcall.wqid[i++] = (Qid){findoradd(key), 0, QTDIR};
	}
	if(qtype == QTDIR)
		snprint(key, sizeof key, "%s%s/", prefix, path);
	else
		snprint(key, sizeof key, "%s%s", prefix, path);
	r->ofcall.wqid[i++] = (Qid){findoradd(key), 0, qtype};
	r->ofcall.nwqid = i;
}

static void
fswalk(Req *r)
{
	char path[512], url[512];
	char *files[128];
	char *p, *e;
	int i, n, len;
	Xfid *x, *dst;

	e = path + sizeof path;
	p = path;
	x = r->fid->aux;

	if(r->ifcall.newfid != r->ifcall.fid){
		r->newfid->aux = mallocz(sizeof(Xfid), 1);
		dst = r->newfid->aux;
		memcpy(dst->path, x->path, sizeof x->path);
	} else
		dst = x;
	if(r->ifcall.nwname == 0)
		goto Done;
	//Our paths start at /, s3's do not
	snprint(path, sizeof path, "%s", dst->path+1);
	for(i = 0; i < r->ifcall.nwname-1; i++)
		p = seprint(p, e, "%s%s", r->ifcall.wname[i], "/");
	snprint(url, sizeof url, "?list-type=2&prefix=%U", path);
	n = s3ls(url, files, nelem(files), strlen(path));
	if(n < 0){
		responderror(r);
		return;
	}
	if(n == 0){
		respond(r, "no Key's in result");
		return;
	}

	/*
	 * s3 doesn't really have dirs and files,
	 * it just has files which can have / in the name.
	 * Common uses still use / as a path seperator, so we can do that.
	 * However, all we get back from a list are keys, so we need to fabricate
	 * directories on the fly.
	 */
	p = r->ifcall.wname[r->ifcall.nwname-1];
	len = strlen(p);
	for(i = 0; i < n; i++){
		if(strcmp(files[i], p) == 0){
			//Exact match, a file.
			packwalk(r, QTFILE, x->path, path);
			snprint(dst->path, sizeof dst->path, "/%s%s", path, files[i]);
			goto Done;
		}
		if(strncmp(files[i], p, len) == 0 && files[i][len] == '/'){
			//There are keys under, we're a directory
			packwalk(r, QTDIR, x->path, path);
			snprint(dst->path, sizeof dst->path, "/%s%.*s/", path, len, files[i]);
			goto Done;
		}
	}
	// No results
	respond(r, "file does not exist");
	return;

Done:
	respond(r, nil);
}

static void
fsopen(Req *r)
{
	respond(r, nil);
}

static void
fsclose(Req *r)
{
	Xfid *x;

	x = r->fid->aux;
	if(x->c){
		//Only cache list results until file is closed
		free(x->c);
		x->c = nil;
	}
	respond(r, nil);
}

static void
filldir(Dir *d, uchar type, char *path)
{
	char *p;

	d->qid = (Qid){findoradd(path), 0, type};
	d->mode = 0555;
	d->atime = 0;
	d->mtime = 0;
	d->length = 0;
	if(path[0] == '/' && path[1] == '\0')
		d->name = estrdup9p("/");
	else {
		p = strrchr(path, '/');
		if(type == QTDIR){
			d->mode |= DMDIR;
			p--;
			while(*p != '/')
				p--;
			d->name = estrdup9p(p+1);
			p = strchr(d->name, '/');
			*p = '\0';
			
		} else
			d->name = estrdup9p(p+1);
	}
	d->uid = estrdup9p("sys");
	d->gid = estrdup9p("sys");
	d->muid = estrdup9p("sys");
}

static int
dirgen(int n, Dir *d, void *aux)
{
	Xfid *x;

	x = aux;
	if(n >= x->nc)
		return -1;
	filldir(d, x->c[n].type, x->c[n].name);
	return 0;
}

static void
fsread(Req *r)
{
	Hcon con;
	int i, ret;
	long n, wr;
	char *files[128];
	char url[512];
	Xfid *x;
	char *p;
	uchar type;
	char err[ERRMAX];

	x = r->fid->aux;
	switch(r->fid->qid.type){
	case QTFILE:
		wr = 0;
		ret = s3getrange(&s3, &con, x->path+1, r->ifcall.offset, r->ifcall.count);
		if(ret < 0){
			rerrstr(err, ERRMAX);
			if(strstr(err, "416 Range Not Satisfiable") == err)
				goto Doneread;
			responderror(r);
			return;
		}
		for(;;){
			n = read(con.body, r->ofcall.data+wr, r->ifcall.count-wr);
			if(n == 0)
				break;
			if(n < 0){
				respond(r, "read error: %r");
				return;
			}
			wr += n;
		}
Doneread:
		r->ofcall.count = wr;
		respond(r, nil);
		return;
	case QTDIR:
		if(x->c == nil){
			snprint(url, sizeof url, "?list-type=2&prefix=%U", x->path+1);
			n = s3ls(url, files, nelem(files), strlen(x->path+1));
			if(n < 0){
				responderror(r);
				return;
			}
			x->c = mallocz(sizeof *x->c * n, 1);
			x->nc = 0;
			for(i = 0; i < n; i++){
				p = strchr(files[i], '/');
				if(p){
					type = QTDIR;
					*p = '\0';
				} else
					type = QTFILE;
				snprint(x->c[x->nc].name, 128, "%s%s%s", x->path, files[i], type == QTDIR ? "/" : "");
				x->c[x->nc++].type = type;
			}
		}
		dirread9p(r, dirgen, x);
		respond(r, nil);
		return;
	}
}

static void
fsstat(Req *r)
{
	Xfid *x;

	x = r->fid->aux;
	filldir(&r->d, r->fid->qid.type, x->path);
	respond(r, nil);
}

static void
fsdestroy(Fid *fid)
{
	Xfid *x;

	if(fid->aux){
		x = fid->aux;
		free(x->c);
	}
}

Srv fs = {
	.attach=fsattach,
	.walk=fswalk,
	.read=fsread,
	.stat=fsstat,
	.destroyfid=fsdestroy,
};

_Noreturn void
usage(void)
{
	fprint(2, "Usage: %s [-Dabr] [-m mntpt] [-s srv] bucket\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	int mflag;
	char *mntpt, *srvname;

	s3fmtinstall();
	tmfmtinstall();
	fmtinstall('H', encodefmt);
	parseargs(&s3, argc, argv);

	mflag = MREPL;
	mntpt = srvname = nil;
	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 'a':
		mflag = MAFTER;
		break;
	case 'b':
		mflag = MBEFORE;
		break;
	case 'm':
		mntpt = EARGF(usage());
		break;
	case 'r':
		mflag = MREPL;
		break;
	case 's':
		srvname = EARGF(usage());
		break;
	}ARGEND
	if(argc == 0)
		usage();

	s3.bucket = argv[0];
	if(mntpt == nil && srvname == nil)
		mntpt = smprint("/n/%s", argv[0]);
	postmountsrv(&fs, srvname, mntpt, mflag);
	exits(nil);
}