Plan 9 from Bell Labs’s /usr/web/sources/patch/sorry/htfilefs/htfilefs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ndb.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <mp.h>
#include <libsec.h>

char *mtpt;
char *host;
char *file;
char *port;
char *url;
char *get;
char *user;
char *netstk;
vlong size;
int usetls;
int blocks = 32;
int debug;

void
usage(void)
{
	fprint(2, "usage: htfilefs [-9d] [-b count] [-x netstk] [-m mntpt] [-s serv] URL\n");
	exits("usage");
}

enum
{
	Qroot,
	Qfile,
};

#define PATH(type, n)		((type)|((n)<<8))
#define TYPE(path)			((int)(path) & 0xFF)
#define NUM(path)			((uint)(path)>>8)

Channel *fsreqchan;
Channel *fsreqwaitchan;
Channel *fsblockchan;
Channel *fsblockwaitchan;
ulong time0;

typedef struct Block Block;
struct Block
{
	char *d;
	vlong off;
	vlong len;
	Block *link;
};

typedef struct Client Client;
struct Client
{
	int ref;
	int nb;
	Block *brq;
	Block **ebrq;
	Block *bq;
	Block **ebq;
	Req *rq;
	Req **erq;
};

Client client;

void
queuereq(Client *c, Req *r)
{

	if(c->rq==nil)
		c->erq = &c->rq;
	*c->erq = r;
	r->aux = nil;
	c->erq = (Req**)&r->aux;
}

void
queueblock(Client *c, Block *b)
{

	if(c->brq == nil)
		c->ebrq = &c->brq;
	*c->ebrq = b;
	b->link = nil;
	c->ebrq = (Block**)&b->link;
}

void
demiseblock(Client *c)
{
	Block *b;

	if(debug)
		print("Demising block.\n");

	b = c->bq->link;
	free(c->bq);
	c->bq = b;
	c->nb--;

	return;
}

void
addblock(Client *c, Block *b)
{

	if(debug)
		print("adding: %d %d %lld %lld\n", c->nb, (int)b, b->len, b->off);
	if(c->nb == blocks)
		demiseblock(c);
	if(c->bq == nil)
		c->ebq = &c->bq;
	*c->ebq = b;
	b->link = nil;
	c->ebq = (Block**)&b->link;
	c->nb++;
}

Block*
findblock(Client *c, vlong off)
{
	Block *l;

	l = c->bq;
	while(l) {
		if(off >= l->off && off < l->off + l->len) {
			if(debug)
				print("found: %lld -> %d %lld %lld\n", off, (int)l, l->off, l->len);
			return l;
		}
		l = l->link;
	}
	return nil;
}

Req*
findreq(Client *c, Req *r)
{
	Req **l;

	l = &c->rq;
	while(*l) {
		if(*l == r) {
			*l = r->aux;
			if(*l == nil)
				c->erq = l;
			return r;
		}
		l = (Req **)&(*l)->aux;
	}
	return nil;
}

void
matchblocks(Client *c)
{
	Req *r;
	Block *b, *br;
	vlong n, m;

	if(c->rq == nil)
		return;
	br = nil;
	r = c->rq;
	b = findblock(c, r->ifcall.offset);
	if(b != nil) {
		c->rq = r->aux;

		m = r->ifcall.offset - b->off;
		n = r->ifcall.count;
		if(n >= (b->len - m)) {
			n = b->len - m;
			if(b->off + b->len < size) {
				br = emalloc9p(sizeof(Block));
				br->d = nil;
				br->off = b->off + b->len;
				if(br->off + b->len < size) {
					br->len = b->len;
				} else {
					br->len = size - br->off;
				}
			}
		}
		if(debug)
			print("Giving back: %lld %lld -> %lld %lld\n", b->off, b->len, b->off + m, n);
		memmove(r->ofcall.data, b->d + m, n);
		r->ofcall.count = n;
		respond(r, nil);
	} else {
		if(r->ifcall.offset >= size) {
			c->rq = r->aux;
			respond(r, nil);
		}
		br = emalloc9p(sizeof(Block));
		br->d = nil;
		br->off = r->ifcall.offset - (r->ifcall.offset % 65536);
		if(br->off < size - 65536) {
			br->len = 65536;
		} else {
			br->len = size - br->off;
		}
	}

	if(br != nil) {
		sendp(fsblockchan, br);
		recvp(fsblockwaitchan);
	}
}

void
hangupclient(Client *c)
{
	Req *r, *next;
	Block *b, *bnext;

	if(--c->ref)
		return;

	b = c->brq;
	while(b) {
		bnext = b->link;
		if(b->d != nil)
			free(b->d);
		free(b);
		b = bnext;
	}
	c->brq = nil;

	b = c->bq;
	while(b) {
		bnext = b->link;
		if(b->d != nil)
			free(b->d);
		free(b);
		b = bnext;
	}
	c->bq = nil;

	r = c->rq;
	while(r) {
		next = r->aux;
		respond(r, "We are out of here.");
		r = next;
	}
	c->rq = nil;

	return;
}

char *
read_line(int s)
{
	char *ret;
	int l;

	ret = nil;
	l = 0;

	while((ret = realloc(ret, ++l)) != nil && read(s, &ret[l - 1], 1) > 0 && l < 1024) {
		if(l > 1) {
			if(ret[l - 1] == '\n') {
				ret[l - 1] = '\0';
				if(ret[l - 2] == '\r')
					ret[l - 2] = '\0';
				return ret;
			}
		}
	}

	if(ret != nil)
		free(ret);
	return nil;
}

int
do_tls(int fd)
{
	TLSconn conn;
	int ret;

	ret = tlsClient(fd, &conn);
	if(ret < 0)
		sysfatal("Unable to do TLS");

	if(conn.cert != nil)
		free(conn.cert);

	return ret;
}

char *
read_httphdr(int s, vlong *size)
{
	char *ret, *stat;

	stat = nil;
	while((ret = read_line(s)) != nil && ret[0] != '\0') {
		if(stat == nil)
			stat = estrdup9p(ret);
		if(!strncmp(ret, "Content-Length: ", 16) && size != nil)
			*size = atoll(ret + 16);
		free(ret);
	}

	return stat;
}

char *
get_range(vlong min, vlong len)
{
	char *ret, *b;
	int fd, userange;

	userange = 1;
	if(len < 65536 && min == 0) {
		userange = 0;
	} else {
		min -= 65536 - len;
	}

	fd = dial(netmkaddr(host, netstk, port), 0, 0, 0);
	if(fd < 0)
		return nil;
	
	if(usetls)
		fd = do_tls(fd);

	if(userange) {
		ret = smprint("Range: bytes=%lld-%lld\r\n", min, min + 65536);
	} else {
		ret = estrdup9p("");
	}
	fprint(fd, "GET /%s HTTP/1.1\r\nHost: %s\r\nAccept-Encoding:\r\n%s\r\n",
				get, host, ret);
	free(ret);
	ret = read_httphdr(fd, nil);
	if(!strstr(ret, "206 Partial Content") && userange) {
		free(ret);
		close(fd);
		return nil;
	}
	if(!strstr(ret, "200 OK") && !userange) {
		free(ret);
		close(fd);
		return nil;
	}
	free(ret);

	ret = emalloc9p(len);
	if(readn(fd, ret, len) != len) {
		free(ret);
		close(fd);
		return nil;
	}
	close(fd);

	if(len < 65536 && userange) {
		b = emalloc9p(len);
		memmove(b, ret + (65536 - len), len);
		free(ret);
		ret = b;
	}

	return ret;
}

void
htfilereadproc(void *a)
{
	Block *p;
	Client *c;

	p = nil;
	c = a;
	while(1) {
nothingtodo:
		matchblocks(c);
		sendp(fsblockchan, p);
		p = recvp(fsblockwaitchan);
		if(p == nil) {
			sleep(1000);
			goto nothingtodo;
		}
		p->d = get_range(p->off, p->len);
		if(p->d == nil)
			sysfatal("We got a nil pointer from get_range.");
	}
}

typedef struct Tab Tab;
struct Tab
{
	char *name;
	ulong mode;
};

Tab tab[] =
{
	"/",		DMDIR|0555,
	nil,		0444,
};

static void
fillstat(Dir *d, uvlong path)
{
	Tab *t;

	memset(d, 0, sizeof(*d));
	d->uid = estrdup9p(user);
	d->gid = estrdup9p(user);
	d->qid.path = path;
	d->atime = d->mtime = time0;
	t = &tab[TYPE(path)];
	d->name = estrdup9p(t->name);
	d->length = size;
	d->qid.type = t->mode>>24;
	d->mode = t->mode;
}

static void
fsattach(Req *r)
{

	if(r->ifcall.aname && r->ifcall.aname[0]) {
		respond(r, "invalid attach specifier");
		return;
	}
	r->fid->qid.path = PATH(Qroot, 0);
	r->fid->qid.type = QTDIR;
	r->fid->qid.vers = 0;
	r->ofcall.qid = r->fid->qid;
	respond(r, nil);
}

static void
fsstat(Req *r)
{

	fillstat(&r->d, r->fid->qid.path);
	respond(r, nil);
}

static int
rootgen(int i, Dir *d, void*)
{

	i += Qroot + 1;
	if(i <= Qfile) {
		fillstat(d, i);
		return 0;
	}
	return -1;
}

static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
	int i;
	ulong path;

	path = fid->qid.path;
	if(!(fid->qid.type & QTDIR))
		return "walk in non-directory";

	if(strcmp(name, "..") == 0) {
		switch(TYPE(path)) {
			case Qroot:
				return nil;
			default:
				return "bug in fswalk1";
		}
	}

	i = TYPE(path) + 1;
	while(i < nelem(tab)) {
		if(strcmp(name, tab[i].name) == 0) {
			qid->path = PATH(i, NUM(path));
			qid->type = tab[i].mode>>24;
			return nil;
		}
		if(tab[i].mode & DMDIR)
			break;
		i++;
	}
	return "directory entry not found";
}

vlong
get_filesize(void)
{
	vlong ret;
	int fd;
	char *b;

	fd = dial(netmkaddr(host, netstk, port), 0, 0, 0);
	if(fd < 0)
		return -1;

	if(usetls)
		fd = do_tls(fd);

	fprint(fd, "HEAD /%s HTTP/1.1\r\nHost: %s\r\nAccept-Encoding:\r\n\r\n", get, host);
	b = read_httphdr(fd, &ret);
	if(!strstr(b, "200 OK"))
		ret = -1;
	free(b);
	close(fd);

	return ret;
}

static void
fileread(Req *r, Client *c)
{

	queuereq(c, r);
	matchblocks(c);
}

static void
fsread(Req *r)
{
	char e[ERRMAX];
	ulong path;

	path = r->fid->qid.path;
	switch(TYPE(path)) {
		case Qroot:
			dirread9p(r, rootgen, nil);
			respond(r, nil);
			break;
		case Qfile:
			fileread(r, &client);
			break;
		default:
			snprint(e, sizeof e, "bug in fsread path=%lux", path);
			respond(r, e);
			break;
	}
}

static void
fsopen(Req *r)
{
	static int need[4] = { 4, 2, 6, 1 };
	ulong path;
	int n;
	Tab *t;

	path = r->fid->qid.path;
	t = &tab[TYPE(path)];
	n = need[r->ifcall.mode&3];
	if((n&t->mode) != n) {
		respond(r, "permission denied");
		return;
	}

	respond(r, nil);
}

static void
fsflush(Req *r)
{

	if(findreq(&client, r->oldreq))
		respond(r->oldreq, "interrupted");
	respond(r, nil);
}

void
fsnetproc(void *)
{
	Alt a[3];
	Req *r;
	Block *b;

	threadsetname("fsthread");

	a[0].op = CHANRCV;
	a[0].c = fsblockchan;
	a[0].v = &b;
	a[1].op = CHANRCV;
	a[1].c = fsreqchan;
	a[1].v = &r;
	a[2].op = CHANEND;

	while(1) {
		switch(alt(a)) {
			case 0:
				if(b != nil && b->d == nil) {
					queueblock(&client, b);
					sendp(fsblockwaitchan, 0);
				} else {
					if(b != nil) {
						if(b != client.brq)
							sysfatal("Did not ask the first queue entry?");
						addblock(&client, b);
						client.brq = client.brq->link;
					}
					sendp(fsblockwaitchan, client.brq);
				}
				break;
			case 1:
				switch(r->ifcall.type) {
					case Tattach:
						fsattach(r);
						break;
					case Topen:
						fsopen(r);
						break;
					case Tstat:
						fsstat(r);
						break;
					case Tflush:
						fsflush(r);
						break;
					default:
						respond(r, "bug in fsthread");
						break;
				}
				sendp(fsreqwaitchan, 0);
				break;
		}
	}
}

static void
fssend(Req *r)
{

	sendp(fsreqchan, r);
	recvp(fsreqwaitchan);
}

static void
fsdestroyfid(Fid *)
{

	return;
}

void
takedown(Srv *)
{

	hangupclient(&client);
	threadexitsall("done");
}

Srv fs = 
{
.attach=		fssend,
.destroyfid=	fsdestroyfid,
.walk1=		fswalk1,
.open=		fssend,
.read=		fsread,
.stat=		fssend,
.flush=		fssend,
.end=		takedown,
};

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

	srvname = "htfile";
	mntpt = nil;

	ARGBEGIN {
		case 'D':
			chatty9p++;
			break;
		case 'd':
			debug++;
			break;
		case 's':
			srvname = EARGF(usage());
			break;
		case 'm':
			mntpt = EARGF(usage());
			break;
		case 'b':
			blocks = atoi(EARGF(usage()));
			break;
		case 'x':
			netstk = EARGF(usage());
			break;
		default:
			usage();
	} ARGEND;

	if(argc < 1)
		usage();
	if(blocks <= 0)
		blocks = 32;

	time0 = time(0);
	host = url = estrdup9p(argv[0]);

	if(!strncmp(url, "https://", 8)) {
		host += 8;
		usetls = 1;
	} else if(!strncmp(url, "http://", 7)) {
		host += 7;
	} else {
		free(url);
		sysfatal("Only http and https are supported or bad syntax.");
	}
	port = strchr(host, ':');
	get = strchr(host, '/');
	if(get != nil)
		*get++ = '\0';
	if(port != nil && get != nil) {
		if(port < get) {
			*port++ = '\0';
			port = estrdup9p(port);
		} else {
			port = estrdup9p(usetls ? "443" : "80");
		}
	}
	if(port == nil && get != nil)
		port = estrdup9p(usetls ? "443" : "80");
	if(port != nil && get == nil) {
		*port++ = '\0';
		port = estrdup9p(port);
		get = estrdup9p("/");
	}
	if(port == nil && get == nil) {
		port = estrdup9p(usetls ? "443" : "80");
		get = estrdup9p("/");
	}

	file = strrchr(get, '/');
	if(file != nil) {
		file++;
	} else {
		if(strcmp(get, "/")) {
			file = get;
		} else {
			file = "index";
		}
	}

	tab[Qfile].name = file;
	user = getuser();
	size = get_filesize();
	if(size < 0)
		sysfatal("Could not get the filesize: %r");

	fsreqchan = chancreate(sizeof(Req *), 0);
	fsreqwaitchan = chancreate(sizeof(void *), 0);
	fsblockchan = chancreate(sizeof(Block *), 0);
	fsblockwaitchan = chancreate(sizeof(Block *), 0);

	procrfork(fsnetproc, nil, 8192, RFNAMEG | RFNOTEG);
	procrfork(htfilereadproc, &client, 8192, RFNAMEG | RFNOTEG);

	threadpostmountsrv(&fs, srvname, mntpt, MREPL);
	exits(0);
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.