plan9fox/sys/src/cmd/sshfs.c
Benjamin Riefenstahl 108d74cb0a cmd/sshfs.c (recvproc): prefer error codes over error strings
Strings for existing codes in the most used server (OpenSSH) just
repeat the error code name.  OTOH we like to have wording of the
strings under our control as much as possible, so we can easier find
and process them.  Error strings are still usefull as fallback for
compatibility with future versions of the server.
2022-01-07 10:37:02 +00:00

1430 lines
30 KiB
C

#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <libsec.h>
int readonly;
int debug;
char *root = ".";
#define dprint(...) if(debug) fprint(2, __VA_ARGS__)
#pragma varargck type "Σ" int
enum {
MAXPACK = 34000,
MAXWRITE = 32768,
MAXATTRIB = 64,
VERSION = 3,
MAXREQID = 32,
HASH = 64
};
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;
typedef struct IDEnt IDEnt;
struct SFid {
RWLock;
char *fn;
uchar *hand;
int handn;
Qid qid;
int dirreads;
Dir *dirent;
int ndirent, dirpos;
uchar direof;
};
struct SReq {
Req *req;
SFid *closefid;
SReq *next;
};
struct IDEnt {
char *name;
int id;
IDEnt *next;
};
IDEnt *uidtab[HASH], *gidtab[HASH];
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;
}
char *
idlookup(IDEnt **tab, int id)
{
IDEnt *p;
for(p = tab[(ulong)id % HASH]; p != nil; p = p->next)
if(p->id == id)
return estrdup9p(p->name);
return smprint("%d", id);
}
int
namelookup(IDEnt **tab, char *name)
{
IDEnt *p;
int i;
char *q;
for(i = 0; i < HASH; i++)
for(p = tab[i]; p != nil; p = p->next)
if(strcmp(p->name, name) == 0)
return p->id;
i = strtol(name, &q, 10);
if(*q == 0) return i;
werrstr("unknown %s '%s'", tab == uidtab ? "user" : "group", name);
return -1;
}
int
allocsreqid(SReq *r)
{
int i;
qlock(&sreqidlock);
for(;;){
for(i = 0; i < MAXREQID; i++)
if(sreqrd[i] == nil){
sreqrd[i] = r;
goto out;
}
rsleep(&sreqidrend);
}
out:
qunlock(&sreqidlock);
return i;
}
int
vpack(uchar *p, int n, char *fmt, va_list a)
{
uchar *p0 = p, *e = p+n;
SReq *sr = nil;
u32int u;
u64int v;
void *s;
int c;
for(;;){
switch(c = *fmt++){
case '\0':
if(sr != nil){
u = allocsreqid(sr);
PUT4(p0+1, u);
}
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 'q':
p += 4;
if(p != p0+5 || p > e) goto err;
sr = va_arg(a, SReq*);
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
freedir1(Dir *d)
{
free(d->name);
free(d->uid);
free(d->gid);
free(d->muid);
}
void
freedir(SFid *s)
{
int i;
for(i = 0; i < s->ndirent; i++)
freedir1(&s->dirent[i]);
free(s->dirent);
s->dirent = nil;
s->ndirent = 0;
s->dirpos = 0;
}
void
putsfid(SFid *s)
{
if(s == nil) return;
wlock(s);
wunlock(s);
free(s->fn);
free(s->hand);
freedir(s);
free(s);
}
void
putsreq(SReq *s)
{
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->req = r;
submitsreq(s);
}
char *
pathcat(char *p, char *c)
{
return cleanname(smprint("%s/%s", p, c));
}
char *
parentdir(char *p)
{
return pathcat(p, "..");
}
char *
finalelem(char *p)
{
char *q;
q = strrchr(p, '/');
if(q == nil) return estrdup9p(p);
return estrdup9p(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, 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);
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 = idlookup(uidtab, uid);
d->gid = idlookup(gidtab, gid);
}else{
d->uid = estrdup9p("sshfs");
d->gid = estrdup9p("sshfs");
}
d->muid = estrdup9p(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 & 0170000) == 0040000) 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;
d->qid.vers = d->mtime;
}
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;
u32int fl;
int uid, gid;
werrstr("phase error");
*rp = 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 = namelookup(uidtab, d->uid);
if(uid == -1)
return -1;
}else
uid = -1;
if(d->gid != nil && *d->gid != 0){
gid = namelookup(gidtab, d->gid);
if(gid == -1)
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);
return p - r;
}
int
attrfixupqid(Qid *qid)
{
u32int flags;
uchar *p;
if(unpack(rxpkt, rxlen, "_____u", &flags) < 0) return -1;
p = rxpkt + 9;
if(flags & SSH_FILEXFER_ATTR_SIZE) p += 8;
if(flags & SSH_FILEXFER_ATTR_UIDGID) p += 8;
if(flags & SSH_FILEXFER_ATTR_PERMISSIONS){
if(p + 4 > rxpkt + rxlen) return -1;
if((GET4(p) & 0170000) != 0040000) qid->type = 0;
else qid->type = QTDIR;
p += 4;
}
if(flags & SSH_FILEXFER_ATTR_ACMODTIME){
if(p + 8 > rxpkt + rxlen) return -1;
p += 4;
qid->vers = GET4(p); /* mtime for qid.vers */
}
return 0;
}
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);
freedir(sf);
sf->dirent = emalloc9p(c * sizeof(Dir));
d = sf->dirent;
p = rxpkt + 9;
ep = rxpkt + rxlen;
for(i = 0; i < c; i++){
memset(d, 0, sizeof(Dir));
rc = unpack(p, ep - p, "ss", &fn, &fns, &ln, &lns); if(rc < 0) goto err; p += rc;
rc = attrib2dir(p, ep, d); if(rc < 0) goto err; p += rc;
if(fn[0] == '.' && (fns == 1 || fns == 2 && fn[1] == '.')){
freedir1(d);
continue;
}
d->name = emalloc9p(fns + 1);
memcpy(d->name, fn, fns);
d->name[fns] = 0;
s = pathcat(sf->fn, d->name);
d->qid.path = qidcalc(s);
free(s);
sf->ndirent++;
d++;
}
wunlock(sf);
return 0;
err:
freedir1(d);
wunlock(sf);
return -1;
}
void
readprocess(Req *r)
{
int i;
uchar *p, *ep;
uint rv;
SFid *sf;
sf = r->fid->aux;
wlock(sf);
if(sf->direof){
wunlock(sf);
respond(r, nil);
return;
}
i = sf->dirpos;
p = (uchar*)r->ofcall.data + r->ofcall.count;
ep = (uchar*)r->ofcall.data + r->ifcall.count;
rv = ep - p;
while(p < ep){
if(i >= sf->ndirent)
break;
rv = convD2M(&sf->dirent[i], p, ep-p);
if(rv <= BIT16SZ)
break;
p += rv;
i++;
}
sf->dirpos = i;
if(i >= sf->ndirent)
freedir(sf);
wunlock(sf);
r->ofcall.count = p - (uchar*)r->ofcall.data;
if(rv <= BIT16SZ)
respond(r, nil);
else
submitreq(r);
}
void
sshfsread(Req *r)
{
if((r->fid->qid.type & QTDIR) == 0){
submitreq(r);
return;
}
if(r->ifcall.offset == 0){
SFid *sf = r->fid->aux;
wlock(sf);
freedir(sf);
if(sf->dirreads > 0){
wunlock(sf);
r->aux = (void*)-1;
submitreq(r);
return;
}
wunlock(sf);
}
readprocess(r);
}
void
sshfsattach(Req *r)
{
SFid *sf;
if(r->aux == nil){
sf = emalloc9p(sizeof(SFid));
if(r->ifcall.aname != nil)
switch(*r->ifcall.aname){
case '~':
switch(r->ifcall.aname[1]){
case 0: sf->fn = estrdup9p("."); break;
case '/': sf->fn = estrdup9p(r->ifcall.aname + 2); break;
default:
free(sf);
respond(r, "invalid attach name");
return;
}
break;
case '/':
sf->fn = estrdup9p(r->ifcall.aname);
break;
case 0:
sf->fn = estrdup9p(root);
break;
default:
sf->fn = pathcat(root, r->ifcall.aname);
}
else
sf->fn = estrdup9p(root);
r->fid->aux = sf;
submitreq(r);
}else{
sf = r->fid->aux;
sf->qid = (Qid){qidcalc(sf->fn), 0, QTDIR};
r->ofcall.qid = sf->qid;
r->fid->qid = sf->qid;
respond(r, nil);
}
}
void
sendproc(void *)
{
SReq *r;
SFid *sf;
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);
sf = r->closefid;
if(sf != nil){
rlock(sf);
sendpkt("bqs", SSH_FXP_CLOSE, r, sf->hand, sf->handn);
runlock(sf);
continue;
}
if(r->req == nil)
sysfatal("nil request in queue");
sf = r->req->fid->aux;
switch(r->req->ifcall.type){
case Tattach:
rlock(sf);
sendpkt("bqs", SSH_FXP_STAT, r, sf->fn, strlen(sf->fn));
runlock(sf);
break;
case Twalk:
sendpkt("bqs", SSH_FXP_STAT, r, r->req->aux, strlen(r->req->aux));
break;
case Topen:
if((r->req->ofcall.qid.type & QTDIR) != 0){
rlock(sf);
sendpkt("bqs", SSH_FXP_OPENDIR, r, sf->fn, strlen(sf->fn));
runlock(sf);
}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");
putsreq(r);
break;
}
if((x & OTRUNC) != 0)
y |= SSH_FXF_TRUNC;
rlock(sf);
sendpkt("bqsuu", SSH_FXP_OPEN, r, 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){
r->req->aux = (void*)-1;
sendpkt("bqsuu", SSH_FXP_MKDIR, r, s, strlen(s),
SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
}else{
r->req->aux = (void*)-2;
sendpkt("bqs", SSH_FXP_OPENDIR, r, s, strlen(s));
}
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("bqsuuu", SSH_FXP_OPEN, r, s, strlen(s), y,
SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
free(s);
break;
case Tread:
if((r->req->fid->qid.type & QTDIR) != 0){
wlock(sf);
if(r->req->aux == (void*)-1){
sendpkt("bqs", SSH_FXP_CLOSE, r, sf->hand, sf->handn);
free(sf->hand);
sf->hand = nil;
sf->handn = 0;
sf->direof = 0;
sf->dirreads = 0;
}else if(r->req->aux == (void*)-2){
sendpkt("bqs", SSH_FXP_OPENDIR, r, sf->fn, strlen(sf->fn));
}else{
sf->dirreads++;
sendpkt("bqs", SSH_FXP_READDIR, r, sf->hand, sf->handn);
}
wunlock(sf);
}else{
rlock(sf);
sendpkt("bqsvuu", SSH_FXP_READ, r, 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;
r->req->ofcall.offset = x;
rlock(sf);
sendpkt("bqsvs", SSH_FXP_WRITE, r, sf->hand, sf->handn,
r->req->ifcall.offset + r->req->ofcall.count,
r->req->ifcall.data + r->req->ofcall.count,
x);
runlock(sf);
break;
case Tstat:
rlock(sf);
r->req->d.name = finalelem(sf->fn);
r->req->d.qid = sf->qid;
if(sf->handn > 0 && (sf->qid.type & QTDIR) == 0)
sendpkt("bqs", SSH_FXP_FSTAT, r, sf->hand, sf->handn);
else
sendpkt("bqs", SSH_FXP_STAT, r, 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);
r->req->aux = t;
sendpkt("bqss", SSH_FXP_RENAME, r, sf->fn, strlen(sf->fn), t, strlen(t));
runlock(sf);
free(s);
break;
}
x = dir2attrib(&r->req->d, (uchar **) &s);
if(x < 0){
responderror(r->req);
putsreq(r);
free(s);
break;
}
rlock(sf);
if(sf->handn > 0)
sendpkt("bqs[", SSH_FXP_FSETSTAT, r, sf->hand, sf->handn, s, x);
else
sendpkt("bqs[", SSH_FXP_SETSTAT, r, sf->fn, strlen(sf->fn), s, x);
runlock(sf);
free(s);
break;
case Tremove:
rlock(sf);
if((sf->qid.type & QTDIR) != 0)
sendpkt("bqs", SSH_FXP_RMDIR, r, sf->fn, strlen(sf->fn));
else
sendpkt("bqs", SSH_FXP_REMOVE, r, 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, *s;
int msgn, langn, handn;
int okresp;
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", t, 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", t, id);
continue;
}
if(r->closefid != nil){
putsfid(r->closefid);
putsreq(r);
continue;
}
if(r->req == nil)
sysfatal("recvproc: r->req == nil");
sf = r->req->fid->aux;
okresp = rxlen >= 9 && t == SSH_FXP_STATUS && GET4(rxpkt+5) == SSH_FX_OK;
switch(r->req->ifcall.type){
case Tattach:
if(t != SSH_FXP_ATTRS) goto common;
if(attrfixupqid(&r->req->ofcall.qid) < 0)
goto garbage;
r->req->aux = (void*)-1;
if((r->req->ofcall.qid.type & QTDIR) == 0)
respond(r->req, "not a directory");
else
sshfsattach(r->req);
break;
case Twalk:
if(t != SSH_FXP_ATTRS) goto common;
if(r->req->ofcall.nwqid <= 0
|| attrfixupqid(&r->req->ofcall.wqid[r->req->ofcall.nwqid - 1]) < 0)
goto garbage;
walkprocess(r->req, nil);
break;
case Tcreate:
if(okresp && r->req->aux == (void*)-1){
submitreq(r->req);
break;
}
/* wet floor */
case Topen: opendir:
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);
if(r->req->ifcall.type == Tcreate){
s = sf->fn;
sf->fn = pathcat(s, r->req->ifcall.name);
free(s);
sf->qid = (Qid){qidcalc(sf->fn), 0, (r->req->ifcall.perm & DMDIR) != 0 ? QTDIR : 0};
r->req->ofcall.qid = sf->qid;
r->req->fid->qid = sf->qid;
}
wunlock(sf);
if(r->req->ifcall.type == Tread){
r->req->aux = nil;
readprocess(r->req);
}else
respond(r->req, nil);
break;
case Tread:
if((r->req->fid->qid.type & QTDIR) != 0){
if(r->req->aux == (void*)-1){
if(t != SSH_FXP_STATUS) goto common;
/* reopen even if close failed */
r->req->aux = (void*)-2;
submitreq(r->req);
}else if(r->req->aux == (void*)-2)
goto opendir;
else{
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(okresp){
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(!okresp) goto common;
if(!r->req->d.name[0]){
respond(r->req, nil);
break;
}
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;
/* prefer our well-defined error strings to arbitrary
* strings from the server */
}else if(code < nelem(errors))
e = errors[code];
else if(msgn > 0){
e = msg;
e[msgn] = 0;
}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, 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 = estrdup9p(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 = estrdup9p(t->fn);
for(i = 0; i < r->ifcall.nwname; i++){
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->closefid = sf;
submitsreq(sr);
}else
putsfid(sf);
}
void
sshfsdestroyreq(Req *r)
{
if(r->ifcall.type == Twalk)
free(r->aux);
}
void
sshfsstart(Srv *)
{
proccreate(sendproc, nil, mainstacksize);
proccreate(recvproc, nil, mainstacksize);
}
void
sshfsend(Srv *)
{
dprint("sshfs: ending\n");
threadexitsall(nil);
}
Srv sshfssrv = {
.start sshfsstart,
.attach sshfsattach,
.walk sshfswalk,
.open submitreq,
.create submitreq,
.read sshfsread,
.write submitreq,
.stat submitreq,
.wstat submitreq,
.remove submitreq,
.destroyfid sshfsdestroyfid,
.destroyreq sshfsdestroyreq,
.end sshfsend,
};
char *
readfile(char *fn)
{
char *hand, *dat;
int handn, datn;
u32int code;
char *p;
int off;
if(fn == nil) return nil;
sendpkt("busuu", SSH_FXP_OPEN, 0, fn, strlen(fn), SSH_FXF_READ, 0);
if(recvpkt() != SSH_FXP_HANDLE) return nil;
if(unpack(rxpkt, rxlen, "_____s", &dat, &handn) < 0) return nil;
hand = emalloc9p(handn);
memcpy(hand, dat, handn);
off = 0;
p = nil;
for(;;){
sendpkt("busvu", SSH_FXP_READ, 0, hand, handn, (uvlong)off, MAXWRITE);
switch(recvpkt()){
case SSH_FXP_STATUS:
if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto err;
if(code == SSH_FX_EOF) goto out;
default:
goto err;
case SSH_FXP_DATA:
if(unpack(rxpkt, rxlen, "_____s", &dat, &datn) < 0) goto err;
break;
}
p = erealloc9p(p, off + datn + 1);
memcpy(p + off, dat, datn);
off += datn;
p[off] = 0;
}
err:
p = nil;
out:
sendpkt("bus", SSH_FXP_CLOSE, 0, hand, handn);
free(hand);
recvpkt();
return p;
}
void
passwdparse(IDEnt **tab, char *s)
{
IDEnt *e, **b;
char *p, *n;
int id;
if(s == nil)
return;
for(p = s;;){
n = p;
p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
*p = 0;
p = strpbrk(p+1, ":\n");
p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
id = strtol(p+1, &p, 10);
p = strchr(p, '\n');
if(p == nil) break;
p++;
e = emalloc9p(sizeof(IDEnt));
e->name = estrdup9p(n);
e->id = id;
b = &tab[((ulong)e->id) % HASH];
e->next = *b;
*b = e;
}
free(s);
}
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)
{
static char *common = "[-abdRUGM] [-s service] [-m mtpt] [-r root] [-u uidfile] [-g gidfile]";
fprint(2, "usage: %s %s [-- ssh-options] [user@]host\n", argv0, common);
fprint(2, " %s %s -c cmdline\n", argv0, common);
fprint(2, " %s %s -p\n", argv0, common);
threadexits("usage");
}
void
threadmain(int argc, char **argv)
{
u32int x;
static int pflag, cflag;
static char *svc, *mtpt;
static int mflag;
static char *uidfile, *gidfile;
fmtinstall(L'Σ', fxpfmt);
mtpt = "/n/ssh";
uidfile = "/etc/passwd";
gidfile = "/etc/group";
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;
case 'M': mtpt = nil; break;
case 'u': uidfile = EARGF(usage()); break;
case 'U': uidfile = nil; break;
case 'g': gidfile = EARGF(usage()); break;
case 'G': gidfile = nil; break;
case 'r': root = 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|RFNAMEG);
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);
passwdparse(uidtab, readfile(uidfile));
passwdparse(gidtab, readfile(gidfile));
threadpostmountsrv(&sshfssrv, svc, mtpt, MCREATE | mflag);
threadexits(nil);
}