shithub: riscv

Download patch

ref: 562fd5b134dc3a9e22e32e53f81858954efd4bf1
parent: 2a920e73611b7f37dd277cf5f0abaa6d5958e6c6
author: aiju <devnull@localhost>
date: Fri Apr 28 11:41:48 EDT 2017

add sshfs

--- /dev/null
+++ b/sys/src/cmd/sshfs.c
@@ -1,0 +1,1204 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <libsec.h>
+
+int readonly;
+int debug;
+#define dprint(...) if(debug) fprint(2, __VA_ARGS__)
+#pragma	varargck	type	"Σ"	int
+
+enum {
+	MAXPACK = 34000,
+	MAXWRITE = 32768,
+	MAXATTRIB = 64,
+	VERSION = 3,
+	MAXREQID = 32
+};
+
+enum {
+	SSH_FXP_INIT = 1,
+	SSH_FXP_VERSION = 2,
+	SSH_FXP_OPEN = 3,
+	SSH_FXP_CLOSE = 4,
+	SSH_FXP_READ = 5,
+	SSH_FXP_WRITE = 6,
+	SSH_FXP_LSTAT = 7,
+	SSH_FXP_FSTAT = 8,
+	SSH_FXP_SETSTAT = 9,
+	SSH_FXP_FSETSTAT = 10,
+	SSH_FXP_OPENDIR = 11,
+	SSH_FXP_READDIR = 12,
+	SSH_FXP_REMOVE = 13,
+	SSH_FXP_MKDIR = 14,
+	SSH_FXP_RMDIR = 15,
+	SSH_FXP_REALPATH = 16,
+	SSH_FXP_STAT = 17,
+	SSH_FXP_RENAME = 18,
+	SSH_FXP_READLINK = 19,
+	SSH_FXP_SYMLINK = 20,
+	SSH_FXP_STATUS = 101,
+	SSH_FXP_HANDLE = 102,
+	SSH_FXP_DATA = 103,
+	SSH_FXP_NAME = 104,
+	SSH_FXP_ATTRS = 105,
+	SSH_FXP_EXTENDED = 200,
+	SSH_FXP_EXTENDED_REPLY = 201,
+	
+	SSH_FXF_READ = 0x00000001,
+	SSH_FXF_WRITE = 0x00000002,
+	SSH_FXF_APPEND = 0x00000004,
+	SSH_FXF_CREAT = 0x00000008,
+	SSH_FXF_TRUNC = 0x00000010,
+	SSH_FXF_EXCL = 0x00000020,
+	SSH_FILEXFER_ATTR_SIZE = 0x00000001,
+	SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
+	SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
+	SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
+	SSH_FILEXFER_ATTR_EXTENDED = 0x80000000,
+
+	SSH_FX_OK = 0,
+	SSH_FX_EOF = 1,
+	SSH_FX_NO_SUCH_FILE = 2,
+	SSH_FX_PERMISSION_DENIED = 3,
+	SSH_FX_FAILURE = 4,
+	SSH_FX_BAD_MESSAGE = 5,
+	SSH_FX_NO_CONNECTION = 6,
+	SSH_FX_CONNECTION_LOST = 7,
+	SSH_FX_OP_UNSUPPORTED = 8,
+};
+
+char *errors[] = {
+	[SSH_FX_OK] "success",
+	[SSH_FX_EOF] "end of file",
+	[SSH_FX_NO_SUCH_FILE] "file does not exist",
+	[SSH_FX_PERMISSION_DENIED] "permission denied",
+	[SSH_FX_FAILURE] "failure",
+	[SSH_FX_BAD_MESSAGE] "bad message",
+	[SSH_FX_NO_CONNECTION] "no connection",
+	[SSH_FX_CONNECTION_LOST] "connection lost",
+	[SSH_FX_OP_UNSUPPORTED] "unsupported operation",
+};
+
+typedef struct SFid SFid;
+typedef struct SReq SReq;
+
+struct SFid {
+	RWLock;
+	char *fn;
+	uchar *hand;
+	int handn;
+	Qid qid;
+	Dir *dirent;
+	int ndirent;
+	uchar direof;
+};
+
+struct SReq {
+	Req *req;
+	SFid *closefid;
+	int reqid;
+	SReq *next;
+};
+
+int rdfd, wrfd;
+SReq *sreqrd[MAXREQID];
+QLock sreqidlock;
+Rendez sreqidrend = {.l = &sreqidlock};
+
+SReq *sreqwr, **sreqlast = &sreqwr;
+QLock sreqwrlock;
+Rendez writerend = {.l = &sreqwrlock};
+
+#define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
+#define GET4(p)	((u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24)
+
+int
+fxpfmt(Fmt *f)
+{
+	int n;
+	
+	n = va_arg(f->args, int);
+	switch(n){
+	case SSH_FXP_INIT: fmtstrcpy(f, "SSH_FXP_INIT"); break;
+	case SSH_FXP_VERSION: fmtstrcpy(f, "SSH_FXP_VERSION"); break;
+	case SSH_FXP_OPEN: fmtstrcpy(f, "SSH_FXP_OPEN"); break;
+	case SSH_FXP_CLOSE: fmtstrcpy(f, "SSH_FXP_CLOSE"); break;
+	case SSH_FXP_READ: fmtstrcpy(f, "SSH_FXP_READ"); break;
+	case SSH_FXP_WRITE: fmtstrcpy(f, "SSH_FXP_WRITE"); break;
+	case SSH_FXP_LSTAT: fmtstrcpy(f, "SSH_FXP_LSTAT"); break;
+	case SSH_FXP_FSTAT: fmtstrcpy(f, "SSH_FXP_FSTAT"); break;
+	case SSH_FXP_SETSTAT: fmtstrcpy(f, "SSH_FXP_SETSTAT"); break;
+	case SSH_FXP_FSETSTAT: fmtstrcpy(f, "SSH_FXP_FSETSTAT"); break;
+	case SSH_FXP_OPENDIR: fmtstrcpy(f, "SSH_FXP_OPENDIR"); break;
+	case SSH_FXP_READDIR: fmtstrcpy(f, "SSH_FXP_READDIR"); break;
+	case SSH_FXP_REMOVE: fmtstrcpy(f, "SSH_FXP_REMOVE"); break;
+	case SSH_FXP_MKDIR: fmtstrcpy(f, "SSH_FXP_MKDIR"); break;
+	case SSH_FXP_RMDIR: fmtstrcpy(f, "SSH_FXP_RMDIR"); break;
+	case SSH_FXP_REALPATH: fmtstrcpy(f, "SSH_FXP_REALPATH"); break;
+	case SSH_FXP_STAT: fmtstrcpy(f, "SSH_FXP_STAT"); break;
+	case SSH_FXP_RENAME: fmtstrcpy(f, "SSH_FXP_RENAME"); break;
+	case SSH_FXP_READLINK: fmtstrcpy(f, "SSH_FXP_READLINK"); break;
+	case SSH_FXP_SYMLINK: fmtstrcpy(f, "SSH_FXP_SYMLINK"); break;
+	case SSH_FXP_STATUS: fmtstrcpy(f, "SSH_FXP_STATUS"); break;
+	case SSH_FXP_HANDLE: fmtstrcpy(f, "SSH_FXP_HANDLE"); break;
+	case SSH_FXP_DATA: fmtstrcpy(f, "SSH_FXP_DATA"); break;
+	case SSH_FXP_NAME: fmtstrcpy(f, "SSH_FXP_NAME"); break;
+	case SSH_FXP_ATTRS: fmtstrcpy(f, "SSH_FXP_ATTRS"); break;
+	case SSH_FXP_EXTENDED: fmtstrcpy(f, "SSH_FXP_EXTENDED"); break;
+	case SSH_FXP_EXTENDED_REPLY: fmtstrcpy(f, "SSH_FXP_EXTENDED_REPLY");
+	default: fmtprint(f, "%d", n);
+	}
+	return 0;
+}
+
+int
+vpack(uchar *p, int n, char *fmt, va_list a)
+{
+	uchar *p0 = p, *e = p+n;
+	u32int u;
+	u64int v;
+	void *s;
+	int c;
+
+	for(;;){
+		switch(c = *fmt++){
+		case '\0':
+			return p - p0;
+		case '_':
+			if(++p > e) goto err;
+			break;
+		case '.':
+			*va_arg(a, void**) = p;
+			break;
+		case 'b':
+			if(p >= e) goto err;
+			*p++ = va_arg(a, int);
+			break;
+		case '[':
+		case 's':
+			s = va_arg(a, void*);
+			u = va_arg(a, int);
+			if(c == 's'){
+				if(p+4 > e) goto err;
+				PUT4(p, u), p += 4;
+			}
+			if(u > e-p) goto err;
+			memmove(p, s, u);
+			p += u;
+			break;
+		case 'u':
+			u = va_arg(a, int);
+			if(p+4 > e) goto err;
+			PUT4(p, u), p += 4;
+			break;
+		case 'v':
+			v = va_arg(a, vlong);
+			if(p+8 > e) goto err;
+			u = v>>32; PUT4(p, u), p += 4;
+			u = v; PUT4(p, u), p += 4;
+			break;
+		}
+	}
+err:
+	return -1;
+}
+
+int
+vunpack(uchar *p, int n, char *fmt, va_list a)
+{
+	uchar *p0 = p, *e = p+n;
+	u32int u;
+	u64int v;
+	void *s;
+
+	for(;;){
+		switch(*fmt++){
+		case '\0':
+			return p - p0;
+		case '_':
+			if(++p > e) goto err;
+			break;
+		case '.':
+			*va_arg(a, void**) = p;
+			break;
+		case 'b':
+			if(p >= e) goto err;
+			*va_arg(a, int*) = *p++;
+			break;
+		case 's':
+			if(p+4 > e) goto err;
+			u = GET4(p), p += 4;
+			if(u > e-p) goto err;
+			*va_arg(a, void**) = p;
+			*va_arg(a, int*) = u;
+			p += u;
+			break;
+		case '[':
+			s = va_arg(a, void*);
+			u = va_arg(a, int);
+			if(u > e-p) goto err;
+			memmove(s, p, u);
+			p += u;
+			break;
+		case 'u':
+			if(p+4 > e) goto err;
+			u = GET4(p);
+			*va_arg(a, int*) = u;
+			p += 4;
+			break;
+		case 'v':
+			if(p+8 > e) goto err;
+			v = (u64int)GET4(p) << 32;
+			v |= (u32int)GET4(p+4);
+			*va_arg(a, vlong*) = v;
+			p += 8;
+			break;
+		}
+	}
+err:
+	return -1;
+}
+
+int
+pack(uchar *p, int n, char *fmt, ...)
+{
+	va_list a;
+	va_start(a, fmt);
+	n = vpack(p, n, fmt, a);
+	va_end(a);
+	return n;
+}
+int
+unpack(uchar *p, int n, char *fmt, ...)
+{
+	va_list a;
+	va_start(a, fmt);
+	n = vunpack(p, n, fmt, a);
+	va_end(a);
+	return n;
+}
+
+void
+sendpkt(char *fmt, ...)
+{
+	static uchar buf[MAXPACK];
+	int n;
+	va_list a;
+
+	va_start(a, fmt);
+	n = vpack(buf+4, sizeof(buf)-4, fmt, a);
+	va_end(a);
+	if(n < 0) {
+		sysfatal("sendpkt: message too big");
+		return;
+	}
+	PUT4(buf, n);
+	n += 4;
+
+	dprint("SFTP --> %Σ\n", (int)buf[4]);
+	if(write(wrfd, buf, n) != n)
+		sysfatal("write: %r");
+}
+
+static uchar rxpkt[MAXPACK];
+static int rxlen;
+
+int
+recvpkt(void)
+{
+	static uchar rxbuf[MAXPACK];
+	static int rxfill;
+	int rc;
+	
+	while(rxfill < 4 || rxfill < (rxlen = GET4(rxbuf) + 4) && rxlen <= MAXPACK){
+		rc = read(rdfd, rxbuf + rxfill, MAXPACK - rxfill);
+		if(rc < 0) sysfatal("read: %r");
+		if(rc == 0) sysfatal("read: eof");
+		rxfill += rc;
+	}
+	if(rxlen > MAXPACK) sysfatal("received garbage");
+	memmove(rxpkt, rxbuf + 4, rxlen - 4);
+	memmove(rxbuf, rxbuf + rxlen, rxfill - rxlen);
+	rxfill -= rxlen;
+	rxlen -= 4;
+	dprint("SFTP <-- %Σ\n", (int)rxpkt[0]);
+	return rxpkt[0];
+}
+
+void
+putsfid(SFid *s)
+{
+	int i;
+	Dir *d;
+
+	if(s == nil) return;
+	free(s->fn);
+	free(s->hand);
+	for(i = 0; i < s->ndirent; i++){
+		d = &s->dirent[i];
+		free(d->name);
+		free(d->uid);
+		free(d->gid);
+		free(d->muid);
+	}
+	free(s->dirent);
+	free(s);
+}
+
+void
+putsreq(SReq *s)
+{
+	if(s == nil) return;
+	if(s->reqid != -1){
+		qlock(&sreqidlock);
+		sreqrd[s->reqid] = nil;
+		rwakeup(&sreqidrend);
+		qunlock(&sreqidlock);
+	}
+	putsfid(s->closefid);
+	free(s);
+}
+
+void
+submitsreq(SReq *s)
+{
+	qlock(&sreqwrlock);
+	*sreqlast = s;
+	sreqlast = &s->next;
+	rwakeup(&writerend);
+	qunlock(&sreqwrlock);
+}
+
+
+void
+submitreq(Req *r)
+{
+	SReq *s;
+	
+	s = emalloc9p(sizeof(SReq));
+	s->reqid = -1;
+	s->req = r;
+	submitsreq(s);
+}
+
+char *
+pathcat(char *p, char *c)
+{
+	if(strcmp(p, ".") == 0)
+		return strdup(c);
+	return smprint("%s/%s", p, c);
+}
+
+char *
+parentdir(char *p)
+{
+	char *q, *r;
+	
+	if(strcmp(p, ".") == 0) return strdup(".");
+	if(strcmp(p, "/") == 0) return strdup("/");
+	q = strdup(p);
+	r = strrchr(q, '/');
+	if(r != nil) *r = 0;
+	return q;
+}
+
+char *
+finalelem(char *p)
+{
+	char *q;
+	
+	q = strrchr(p, '/');
+	if(q == nil) return strdup(p);
+	return strdup(q+1);
+}
+
+u64int
+qidcalc(char *c)
+{
+	uchar dig[SHA1dlen];
+
+	sha1((uchar *) c, strlen(c), dig, nil);
+	return dig[0] | dig[1] << 8 | dig[2] << 16 | dig[3] << 24 | (uvlong)dig[4] << 32 | (uvlong)dig[5] << 40 | (uvlong)dig[6] << 48 | (uvlong)dig[7] << 56;
+}
+
+void
+walkprocess(Req *r, int isdir, char *e)
+{
+	char *p;
+	SFid *sf;
+	
+	sf = r->newfid->aux;
+	if(e != nil){
+		r->ofcall.nwqid--;
+		if(r->ofcall.nwqid == 0){
+			respond(r, e);
+			return;
+		}
+		p = r->aux;
+		r->aux = parentdir(p);
+		free(p);
+		submitreq(r);
+	}else{
+		assert(r->ofcall.nwqid > 0);
+		if(!isdir)
+			r->ofcall.wqid[r->ofcall.nwqid - 1].type = 0;
+		wlock(sf);
+		free(sf->fn);
+		sf->fn = r->aux;
+		r->aux = nil;
+		sf->qid = r->ofcall.wqid[r->ofcall.nwqid - 1];
+		wunlock(sf);
+		respond(r, nil);
+	}
+}
+
+int
+attrib2dir(uchar *p0, uchar *ep, Dir *d)
+{
+	uchar *p;
+	int i, rc, extn, extvn;
+	u32int flags, uid, gid, perm, next;
+	uchar *exts,  *extvs;
+	
+	p = p0;
+	if(p + 4 > ep) return -1;
+	flags = GET4(p), p += 4;
+	if((flags & SSH_FILEXFER_ATTR_SIZE) != 0){
+		rc = unpack(p, ep - p, "v", &d->length); if(rc < 0) return -1; p += rc;
+	}
+	if((flags & SSH_FILEXFER_ATTR_UIDGID) != 0){
+		rc = unpack(p, ep - p, "uu", &uid, &gid); if(rc < 0) return -1; p += rc;
+		d->uid = smprint("%d", uid);
+		d->gid = smprint("%d", gid);
+	}else{
+		d->uid = strdup("sshfs");
+		d->gid = strdup("sshfs");
+	}
+	d->muid = strdup(d->uid);
+	if((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0){
+		rc = unpack(p, ep - p, "u", &perm); if(rc < 0) return -1; p += rc;
+		d->mode = perm & 0777;
+		if((perm & 0040000) != 0) d->mode |= DMDIR;
+	}
+	d->qid.type = d->mode >> 24;
+	if((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0){
+		rc = unpack(p, ep - p, "uu", &d->atime, &d->mtime); if(rc < 0) return -1; p += rc;
+	}
+	if((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0){
+		rc = unpack(p, ep - p, "u", &next); if(rc < 0) return -1; p += rc;
+		for(i = 0; i < next; i++){
+			rc = unpack(p, ep - p, "ss", &exts, &extn, &extvs, &extvn); if(rc < 0) return -1; p += rc;
+			exts[extn] = extvs[extvn] = 0;
+		}
+	}
+	return p - p0;
+}
+
+int
+dir2attrib(Dir *d, uchar **rp)
+{
+	int rc;
+	uchar *r, *p, *e;
+	char *pp;
+	u32int fl;
+	int uid, gid;
+	
+	werrstr("phase error");
+	r = emalloc9p(MAXATTRIB);
+	e = r + MAXATTRIB;
+	fl = 0;
+	p = r + 4;
+	if(d->length != (uvlong)-1){
+		fl |= SSH_FILEXFER_ATTR_SIZE;
+		rc = pack(p, e - p, "v", d->length); if(rc < 0) return -1; p += rc;
+	}
+	if(d->uid != nil && *d->uid != 0 || d->gid != nil && *d->gid != 0){
+		/* FIXME: sending -1 for "don't change" works with openssh, but violates the spec */
+		if(d->uid != nil && *d->uid != 0){
+			uid = strtol(d->uid, &pp, 10);
+			if(*pp != 0){
+				werrstr("uid not a number");
+				return -1;
+			}
+		}else
+			uid = -1;
+		if(d->gid != nil && *d->gid != 0){
+			gid = strtol(d->gid, &pp, 10);
+			if(*pp != 0){
+				werrstr("gid not a number");
+				return -1;
+			}
+		}else
+			gid = -1;
+		fl |= SSH_FILEXFER_ATTR_UIDGID;
+		rc = pack(p, e - p, "uu", uid, gid); if(rc < 0) return -1; p += rc;
+	}
+	if(d->mode != (ulong)-1){
+		fl |= SSH_FILEXFER_ATTR_PERMISSIONS;
+		rc = pack(p, e - p, "u", d->mode); if(rc < 0) return -1; p += rc;
+	}
+	if(d->atime != (ulong)-1 || d->mtime != (ulong)-1){
+		/* FIXME: see above */
+		fl |= SSH_FILEXFER_ATTR_ACMODTIME;
+		rc = pack(p, e - p, "uu", d->atime, d->mtime); if(rc < 0) return -1; p += rc;
+	}
+	PUT4(r, fl);
+	*rp = r;
+	return p - r;
+}
+
+int
+parsedir(SFid *sf)
+{
+	int i, rc;
+	Dir *d;
+	u32int c;
+	uchar *p, *ep;
+	char *fn, *ln;
+	int fns, lns;
+	char *s;
+
+	if(unpack(rxpkt, rxlen, "_____u", &c) < 0) return -1;
+	wlock(sf);
+	sf->dirent = erealloc9p(sf->dirent, (sf->ndirent + c) * sizeof(Dir));
+	d = sf->dirent + sf->ndirent;
+	p = rxpkt + 9;
+	ep = rxpkt + rxlen;
+	for(i = 0; i < c; i++){
+		rc = unpack(p, ep - p, "ss", &fn, &fns, &ln, &lns); if(rc < 0) goto err; p += rc;
+		memset(d, 0, sizeof(Dir));
+		rc = attrib2dir(p, ep, d); if(rc < 0) goto err; p += rc;
+		if(fn[0] == '.' && (fns == 1 || fns == 2 && fn[1] == '.')){
+			free(d->uid);
+			free(d->gid);
+			free(d->muid);
+			continue;
+		}
+		d->name = emalloc9p(fns + 1);
+		memcpy(d->name, fn, fns);
+		s = pathcat(sf->fn, d->name);
+		d->qid.path = qidcalc(s);
+		free(s);
+		sf->ndirent++;
+		d++;
+	}
+	wunlock(sf);
+	return 0;
+err:
+	wunlock(sf);
+	return -1;
+}
+
+void
+readprocess(Req *r)
+{
+	int start;
+	uchar *p, *ep;
+	uint rv;
+	SFid *sf;
+
+	if((r->fid->qid.type & QTDIR) == 0){
+		submitreq(r);
+		return;
+	}
+
+	if(r->ifcall.offset == 0)
+		start = 0;
+	else
+		start = r->fid->dirindex;
+
+	p = (uchar*)r->ofcall.data;
+	ep = p+r->ifcall.count;
+	sf = r->fid->aux;
+
+	rlock(sf);
+	while(p < ep){
+		if(start >= sf->ndirent)
+			if(sf->direof)
+				break;
+			else{
+				runlock(sf);
+				submitreq(r);
+				return;
+			}
+		rv = convD2M(&sf->dirent[start], p, ep-p);
+		if(rv <= BIT16SZ)
+			break;
+		p += rv;
+		start++;
+	}
+	runlock(sf);
+	r->fid->dirindex = start;
+	r->ofcall.count = p - (uchar*)r->ofcall.data;
+	respond(r, nil);
+}
+
+void
+sshfsattach(Req *r)
+{
+	SFid *sf;
+
+	if(r->ifcall.aname != nil && *r->ifcall.aname != 0 && r->aux == nil){
+		submitreq(r);
+		return;
+	}
+	sf = emalloc9p(sizeof(SFid));
+	if(r->ifcall.aname != nil && *r->ifcall.aname != 0)
+		sf->fn = strdup(r->ifcall.aname);
+	else
+		sf->fn = strdup(".");
+	sf->qid = (Qid){qidcalc(sf->fn), 0, QTDIR};
+	r->ofcall.qid = sf->qid;
+	r->fid->qid = sf->qid;
+	r->fid->aux = sf;
+	respond(r, nil);
+}
+
+void
+sendproc(void *)
+{
+	SReq *r;
+	SFid *sf;
+	int i;
+	int x, y;
+	char *s, *t;
+
+	threadsetname("send");
+
+	for(;;){
+		qlock(&sreqwrlock);
+		while(sreqwr == nil)
+			rsleep(&writerend);
+		r = sreqwr;
+		sreqwr = r->next;
+		if(sreqwr == nil) sreqlast = &sreqwr;
+		qunlock(&sreqwrlock);
+		
+		qlock(&sreqidlock);
+	idagain:
+		for(i = 0; i < MAXREQID; i++)
+			if(sreqrd[i] == nil){
+				sreqrd[i] = r;
+				r->reqid = i;
+				break;
+			}
+		if(i == MAXREQID){
+			rsleep(&sreqidrend);
+			goto idagain;
+		}
+		qunlock(&sreqidlock);
+
+		if(r->closefid != nil){
+			sendpkt("bus", SSH_FXP_CLOSE, r->reqid, r->closefid->hand, r->closefid->handn);
+			continue;
+		}
+		if(r->req == nil)
+			sysfatal("nil request in queue");
+
+		sf = r->req->fid != nil ? r->req->fid->aux : nil;
+		switch(r->req->ifcall.type){
+		case Tattach:
+			sendpkt("bus", SSH_FXP_STAT, r->reqid, r->req->ifcall.aname, strlen(r->req->ifcall.aname));
+			break;
+		case Twalk:
+			sendpkt("bus", SSH_FXP_STAT, r->reqid, r->req->aux, strlen(r->req->aux));
+			break;
+		case Topen:
+			rlock(sf);
+			if((r->req->ofcall.qid.type & QTDIR) != 0)
+				sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, sf->fn, strlen(sf->fn));
+			else{
+				x = r->req->ifcall.mode;
+				y = 0;
+				switch(x & 3){
+				case OREAD: y = SSH_FXF_READ; break;
+				case OWRITE: y = SSH_FXF_WRITE; break;
+				case ORDWR: y = SSH_FXF_READ | SSH_FXF_WRITE; break;
+				}
+				if(readonly && (y & SSH_FXF_WRITE) != 0){
+					respond(r->req, "mounted read-only");
+					runlock(sf);
+					putsreq(r);
+					break;
+				}
+				if((x & OTRUNC) != 0)
+					y |= SSH_FXF_TRUNC;
+				sendpkt("busuu", SSH_FXP_OPEN, r->reqid, sf->fn, strlen(sf->fn), y, 0);
+			}
+			runlock(sf);
+			break;
+		case Tcreate:
+			rlock(sf);
+			s = pathcat(sf->fn, r->req->ifcall.name);
+			runlock(sf);
+			if((r->req->ifcall.perm & DMDIR) != 0){
+				if(r->req->aux == nil){
+					sendpkt("busuu", SSH_FXP_MKDIR, r->reqid, s, strlen(s),
+						SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
+					r->req->aux = (void*)-1;
+				}else{
+					sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, s, strlen(s));
+					r->req->aux = (void*)-2;
+				}
+				free(s);
+				break;
+			}
+			x = r->req->ifcall.mode;
+			y = SSH_FXF_CREAT | SSH_FXF_EXCL;
+			switch(x & 3){
+			case OREAD: y |= SSH_FXF_READ; break;
+			case OWRITE: y |= SSH_FXF_WRITE; break;
+			case ORDWR: y |= SSH_FXF_READ | SSH_FXF_WRITE; break;
+			}
+			sendpkt("busuuu", SSH_FXP_OPEN, r->reqid, s, strlen(s), y,
+				SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
+			free(s);
+			break;
+		case Tread:
+			rlock(sf);
+			if((r->req->fid->qid.type & QTDIR) != 0)
+				sendpkt("bus", SSH_FXP_READDIR, r->reqid, sf->hand, sf->handn);
+			else
+				sendpkt("busvuu", SSH_FXP_READ, r->reqid, sf->hand, sf->handn,
+					r->req->ifcall.offset, r->req->ifcall.count);
+			runlock(sf);
+			break;
+		case Twrite:
+			x = r->req->ifcall.count - r->req->ofcall.count;
+			if(x >= MAXWRITE) x = MAXWRITE;
+			rlock(sf);
+			sendpkt("busvs", SSH_FXP_WRITE, r->reqid, sf->hand, sf->handn,
+				r->req->ifcall.offset + r->req->ofcall.count,
+				r->req->ifcall.data + r->req->ofcall.count,
+				x);
+			runlock(sf);
+			r->req->ofcall.offset = x;
+			break;
+		case Tstat:
+			rlock(sf);
+			r->req->d.name = finalelem(sf->fn);
+			r->req->d.qid = sf->qid;
+			if(sf->handn > 0)
+				sendpkt("bus", SSH_FXP_FSTAT, r->reqid, sf->hand, sf->handn);
+			else
+				sendpkt("bus", SSH_FXP_STAT, r->reqid, sf->fn, strlen(sf->fn));
+			runlock(sf);
+			break;
+		case Twstat:
+			if(r->req->aux == (void *) -1){
+				rlock(sf);
+				s = parentdir(sf->fn);
+				t = pathcat(s, r->req->d.name);
+				sendpkt("buss", SSH_FXP_RENAME, r->reqid, sf->fn, strlen(sf->fn), t, strlen(t));
+				free(s);
+				r->req->aux = t;
+				runlock(sf);
+				break;
+			}
+			x = dir2attrib(&r->req->d, (uchar **) &s);
+			if(x < 0){
+				responderror(r->req);
+				putsreq(r);
+				break;
+			}
+			rlock(sf);
+			if(sf->handn > 0)
+				sendpkt("bus[", SSH_FXP_FSETSTAT, r->reqid, sf->hand, sf->handn, s, x);
+			else
+				sendpkt("bus[", SSH_FXP_SETSTAT, r->reqid, sf->fn, strlen(sf->fn), s, x);
+			runlock(sf);
+			break;
+		case Tremove:
+			rlock(sf);
+			if((sf->qid.type & QTDIR) != 0)
+				sendpkt("bus", SSH_FXP_RMDIR, r->reqid, sf->fn, strlen(sf->fn));
+			else
+				sendpkt("bus", SSH_FXP_REMOVE, r->reqid, sf->fn, strlen(sf->fn));
+			runlock(sf);
+			break;
+		default:
+			fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
+			respond(r->req, "phase error");
+			putsreq(r);
+		}
+	}
+}
+
+void
+recvproc(void *)
+{
+	static char ebuf[256];
+
+	SReq *r;
+	SFid *sf;
+	int t, id;
+	u32int code;
+	char *msg, *lang, *hand;
+	int msgn, langn, handn;
+	uchar *p;
+	char *e;
+	
+	threadsetname("recv");
+	
+	for(;;){
+		e = "phase error";
+		switch(t = recvpkt()){
+		case SSH_FXP_STATUS:
+		case SSH_FXP_HANDLE:
+		case SSH_FXP_DATA:
+		case SSH_FXP_NAME:
+		case SSH_FXP_ATTRS:
+			break;
+		default:
+			fprint(2, "sshfs: received unexpected packet of type %Σ\n", t);
+			continue;
+		}
+		id = GET4(rxpkt + 1);
+		if(id >= MAXREQID){
+			fprint(2, "sshfs: received response with id out of range, %d > %d\n", id, MAXREQID);
+			continue;
+		}
+		qlock(&sreqidlock);
+		r = sreqrd[id];
+		if(r != nil){
+			sreqrd[id] = nil;
+			rwakeup(&sreqidrend);
+		}
+		qunlock(&sreqidlock);
+		if(r == nil){
+			fprint(2, "sshfs: received response to non-existent request (req id = %d)\n", id);
+			continue;
+		}
+		if(r->closefid != nil){
+			putsreq(r);
+			continue;
+		}
+		if(r->req == nil)
+			sysfatal("recvproc: r->req == nil");
+
+		sf = r->req->fid != nil ? r->req->fid->aux : nil;
+		switch(r->req->ifcall.type){
+		case Tattach:
+			if(t != SSH_FXP_ATTRS) goto common;
+			if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
+			r->req->aux = (void*)-1;
+			if((code & 4) == 0){
+				fprint(2, "sshfs: can't determine if %s is a directory\n", r->req->ifcall.aname);
+				sshfsattach(r->req);
+				break;
+			}
+			p = rxpkt + 9;
+			if(code & 1) p += 8;
+			if(code & 2) p += 8;
+			if(p + 4 > rxpkt + rxlen) goto garbage;
+			if((GET4(p) & 0040000) == 0)
+				respond(r->req, "not a directory");
+			else
+				sshfsattach(r->req);
+			break;
+		case Twalk:
+			if(t != SSH_FXP_ATTRS) goto common;
+			if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
+			if((code & 4) == 0){
+				fprint(2, "sshfs: can't determine if %s is a directory\n", ((SFid*)r->req->fid)->fn);
+				walkprocess(r->req, 0, nil);
+				break;
+			}
+			p = rxpkt + 9;
+			if(code & 1) p += 8;
+			if(code & 2) p += 8;
+			if(p + 4 > rxpkt + rxlen) goto garbage;
+			walkprocess(r->req, GET4(p) & 0040000, nil);
+			break;
+		case Tcreate:
+			if(t == SSH_FXP_STATUS && r->req->aux == (void*)-1){
+				if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
+				if(code != SSH_FX_OK) goto common;
+				submitreq(r->req);
+				break;
+			}
+			/* wet floor */
+		case Topen:
+			if(t != SSH_FXP_HANDLE) goto common;
+			if(unpack(rxpkt, rxlen, "_____s", &hand, &handn) < 0) goto garbage;
+			wlock(sf);
+			sf->handn = handn;
+			sf->hand = emalloc9p(sf->handn);
+			memcpy(sf->hand, hand, sf->handn);
+			wunlock(sf);
+			respond(r->req, nil);
+			break;
+		case Tread:
+			if((r->req->fid->qid.type & QTDIR) != 0){
+				if(t != SSH_FXP_NAME) goto common;
+				if(parsedir(sf) < 0) goto garbage;
+				readprocess(r->req);
+				break;
+			}
+			if(t != SSH_FXP_DATA) goto common;
+			if(unpack(rxpkt, rxlen, "_____s", &msg, &msgn) < 0) goto garbage;
+			if(msgn > r->req->ifcall.count) msgn = r->req->ifcall.count;
+			r->req->ofcall.count = msgn;
+			memcpy(r->req->ofcall.data, msg, msgn);
+			respond(r->req, nil);
+			break;
+		case Twrite:
+			if(t != SSH_FXP_STATUS) goto common;
+			if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
+			if(code == SSH_FX_OK){
+				r->req->ofcall.count += r->req->ofcall.offset;
+				if(r->req->ofcall.count == r->req->ifcall.count)
+					respond(r->req, nil);
+				else
+					submitreq(r->req);
+				break;
+			}
+			if(r->req->ofcall.count == 0) goto common;
+			respond(r->req, nil);
+			break;
+		case Tstat:
+			if(t != SSH_FXP_ATTRS) goto common;
+			if(attrib2dir(rxpkt + 5, rxpkt + rxlen, &r->req->d) < 0) goto garbage;
+			respond(r->req, nil);
+			break;
+		case Twstat:
+			if(t != SSH_FXP_STATUS || r->req->d.name == nil || *r->req->d.name == 0) goto common;
+			if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto garbage;
+			if(code != SSH_FX_OK) goto common;
+			if(r->req->aux == nil){
+				r->req->aux = (void *) -1;
+				submitreq(r->req);
+			}else{
+				wlock(sf);
+				free(sf->fn);
+				sf->fn = r->req->aux;
+				wunlock(sf);
+				respond(r->req, nil);
+			}
+			break;
+		case Tremove:
+			goto common;
+		default:
+			fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
+			respond(r->req, "phase error");
+		}
+		putsreq(r);
+		continue;
+		
+	common:
+		switch(t){
+		case SSH_FXP_STATUS:
+			if(unpack(rxpkt, rxlen, "_____uss", &code, &msg, &msgn, &lang, &langn) < 0){
+	garbage:
+				fprint(2, "sshfs: garbled packet in response to 9p request %F\n", &r->req->ifcall);
+				break;
+			}
+			if(code == SSH_FX_OK)
+				e = nil;
+			else if(code == SSH_FX_EOF && r->req->ifcall.type == Tread){
+				if((r->req->fid->qid.type & QTDIR) != 0){
+					wlock(sf);
+					sf->direof = 1;
+					wunlock(sf);
+					readprocess(r->req);
+					putsreq(r);
+					continue;
+				}
+				r->req->ofcall.count = 0;
+				e = nil;
+			}else if(msgn > 0){
+				e = msg;
+				e[msgn] = 0;
+			}else if(code < nelem(errors))
+				e = errors[code];
+			else{
+				snprint(ebuf, sizeof(ebuf), "error code %d", code);
+				e = ebuf;
+			}
+			break;
+		default:
+			fprint(2, "sshfs: received unexpected packet %Σ for 9p request %F\n", t, &r->req->ifcall);
+		}
+		if(r->req->ifcall.type == Twalk)
+			walkprocess(r->req, 0, e);
+		else
+			respond(r->req, e);
+		putsreq(r);
+		continue;
+	}
+}
+
+void
+sshfswalk(Req *r)
+{
+	SFid *s, *t;
+	char *p, *q;
+	int i;
+
+	if(r->fid != r->newfid){
+		r->newfid->qid = r->fid->qid;
+		s = r->fid->aux;
+		t = emalloc9p(sizeof(SFid));
+		t->fn = strdup(s->fn);
+		t->qid = s->qid;
+		r->newfid->aux = t;
+	}else
+		t = r->fid->aux;
+	if(r->ifcall.nwname == 0){
+		respond(r, nil);
+		return;
+	}
+	p = strdup(t->fn);
+	for(i = 0; i < r->ifcall.nwname; i++){
+		if(strcmp(r->ifcall.wname[i], "..") == 0)
+			q = parentdir(p);
+		else
+			q = pathcat(p, r->ifcall.wname[i]);
+		free(p);
+		p = q;
+		r->ofcall.wqid[i] = (Qid){qidcalc(p), 0, QTDIR};
+	}
+	r->ofcall.nwqid = r->ifcall.nwname;
+	r->aux = p;
+	submitreq(r);
+}
+
+void
+sshfsdestroyfid(Fid *f)
+{
+	SFid *sf;
+	SReq *sr;
+
+	sf = f->aux;
+	if(sf == nil)
+		return;
+	if(sf->hand != nil){
+		sr = emalloc9p(sizeof(SReq));
+		sr->reqid = -1;
+		sr->closefid = sf;
+		submitsreq(sr);
+	}else
+		putsfid(sf);
+}
+
+void
+sshfsdestroyreq(Req *r)
+{
+	if(r->ifcall.type == Twalk)
+		free(r->aux);
+}
+
+void
+sshfsend(Srv *)
+{
+	dprint("sshfs: ending\n");
+	threadexitsall(nil);
+}
+
+Srv sshfssrv = {
+	.attach sshfsattach,
+	.walk sshfswalk,
+	.open submitreq,
+	.create submitreq,
+	.read readprocess,
+	.write submitreq,
+	.stat submitreq,
+	.wstat submitreq,
+	.remove submitreq,
+	.destroyfid sshfsdestroyfid,
+	.destroyreq sshfsdestroyreq,
+	.end sshfsend
+};
+
+int pfd[2];
+int sshargc;
+char **sshargv;
+
+void
+startssh(void *)
+{
+	char *f;
+
+	close(pfd[0]);
+	dup(pfd[1], 0);
+	dup(pfd[1], 1);
+	close(pfd[1]);
+	if(strncmp(sshargv[0], "./", 2) != 0)
+		f = smprint("/bin/%s", sshargv[0]);
+	else
+		f = sshargv[0];
+	procexec(nil, f, sshargv);
+	sysfatal("exec: %r");
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-abdR] [-s service] [-m mtpt] [-- ssh options] host\n", argv0);
+	fprint(2, "       %s [-abdR] [-s service] [-m mtpt] -c cmdline\n", argv0);
+	fprint(2, "       %s [-abdR] [-s service] [-m mtpt] -p\n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	u32int x;
+	static int pflag, cflag;
+	static char *svc, *mtpt;
+	static int mflag;
+	
+	fmtinstall(L'Σ', fxpfmt);
+	
+	mtpt = "/n/ssh";
+	ARGBEGIN{
+	case 'R': readonly++; break;
+	case 'd': debug++; chatty9p++; break;
+	case 'p': pflag++; break;
+	case 'c': cflag++; break;
+	case 's': svc = EARGF(usage()); break;
+	case 'a': mflag |= MAFTER; break;
+	case 'b': mflag |= MBEFORE; break;
+	case 'm': mtpt = EARGF(usage()); break;
+	default: usage();
+	}ARGEND;
+	
+	if(readonly){
+		sshfssrv.create = nil;
+		sshfssrv.write = nil;
+		sshfssrv.wstat = nil;
+		sshfssrv.remove = nil;
+	}
+	
+	if(pflag){
+		rdfd = 0;
+		wrfd = 1;
+	}else{
+		if(argc == 0) usage();
+		if(cflag){
+			sshargc = argc;
+			sshargv = argv;
+		}else{
+			sshargc = argc + 2;
+			sshargv = emalloc9p(sizeof(char *) * (sshargc + 1));
+			sshargv[0] = "ssh";
+			memcpy(sshargv + 1, argv, argc * sizeof(char *));
+			sshargv[sshargc - 1] = "#sftp";
+		}
+		pipe(pfd);
+		rdfd = wrfd = pfd[0];
+		procrfork(startssh, nil, mainstacksize, RFFDG|RFNOTEG);
+		close(pfd[1]);
+	}
+
+	sendpkt("bu", SSH_FXP_INIT, VERSION);
+	if(recvpkt() != SSH_FXP_VERSION || unpack(rxpkt, rxlen, "_u", &x) < 0) sysfatal("received garbage");
+	if(x != VERSION) sysfatal("server replied with incompatible version %d", x);
+	
+	procrfork(sendproc, 0, mainstacksize, RFNOTEG);
+	procrfork(recvproc, 0, mainstacksize, RFNOTEG);
+	threadpostmountsrv(&sshfssrv, svc, mtpt, MCREATE | mflag);
+}