/* Copyright © 2008 Fazlul Shahriar*/ #include "fxp.h" enum{ MaxPktLen = 34000, FMsgid = 0x99999999, }; enum{ /* Fxp.type */ SSH_FXP_INIT = 1, SSH_FXP_VERSION = 2, /* reply */ 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, /* reply */ SSH_FXP_HANDLE = 102, /* reply */ SSH_FXP_DATA = 103, /* reply */ SSH_FXP_NAME = 104, /* reply */ SSH_FXP_ATTRS = 105, /* reply */ SSH_FXP_EXTENDED = 200, SSH_FXP_EXTENDED_REPLY = 201, /* Fxp.status */ 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, /* Fxp.pflags */ SSH_FXF_READ = 0x00000001, SSH_FXF_WRITE = 0x00000002, SSH_FXF_APPEND = 0x00000004, SSH_FXF_CREAT = 0x00000008, SSH_FXF_TRUNC = 0x00000010, SSH_FXF_EXCL = 0x00000020, /* FAttrs.flags */ 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, }; typedef struct FAttrs FAttrs; typedef struct FName FName; typedef struct Fxp Fxp; /* File Attributes */ struct FAttrs{ u32int flags; u64int size; u32int uid; u32int gid; u32int perm; u32int atime; u32int mtime; /*u32int nextended; */ }; struct Fxp{ uchar type; u32int id; /* except Init and Version */ union{ struct{ /* Init,Version */ u32int version; }; struct{ /* Open/Read/Write/Data */ String filename; /* Remove */ u32int pflags; String handle; /* Handle,Close,Readdir,Fstat */ u64int offset; u32int len; String data; /* Rmdir,Opendir,Realpath,Stat,Lstat,Readlink */ String path; FAttrs attrs; /* Fsetstat,Mkdir,Setstat,Attrs */ }; struct{ /* Rename */ String oldpath; String newpath; }; struct{ /* Symlink */ String linkpath; String targetpath; }; struct{ /* Status */ u32int status; String errmsg; String lang; }; struct{ /* Name */ u32int count; String *filenames; String *longnames; FAttrs *attrsv; }; }; }; enum{ STACK = 8192, }; static int debug = 0; typedef struct Conn Conn; struct Conn{ char *host; char sshver; char *serverpath; Map *map; uchar buf[MaxPktLen]; Channel *rdchan; Channel *wrchan; int sshfd; int fd[2]; }; static Conn conn; static int put8(u64int v, uchar *a) { a[0] = v>>56; a[1] = v>>48; a[2] = v>>40; a[3] = v>>32; a[4] = v>>24; a[5] = v>>16; a[6] = v>>8; a[7] = v; return 8; } static int put4(u32int v, uchar *a) { a[0] = v>>24; a[1] = v>>16; a[2] = v>>8; a[3] = v; return 4; } static int put1(uchar v, uchar *a) { a[0] = v; return 1; } static int putstring(String *s, uchar *buf) { put4(s->len, buf); memmove(buf+4, s->s, s->len); return s->len+4; } static int putattrs(FAttrs *a, uchar *buf) { uchar *bp; bp = buf; bp += put4(a->flags, bp); if(a->flags & SSH_FILEXFER_ATTR_SIZE) bp += put8(a->size, bp); if(a->flags & SSH_FILEXFER_ATTR_UIDGID){ bp += put4(a->uid, bp); bp += put4(a->gid, bp); } if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) bp += put4(a->perm, bp); if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){ bp += put4(a->atime, bp); bp += put4(a->mtime, bp); } return bp-buf; } static int fxpattrslen(FAttrs *a) { int n; n = 4; n += (a->flags & SSH_FILEXFER_ATTR_SIZE) ? 8 : 0; n += (a->flags & SSH_FILEXFER_ATTR_UIDGID) ? 8 : 0; n += (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) ? 4 : 0; n += (a->flags & SSH_FILEXFER_ATTR_ACMODTIME) ? 8 : 0; return n; } static int fxpencode(Fxp *m, uchar *buf, int bufsz) { uchar *bp; if(bufsz < MaxPktLen) sysfatal("buffer size (%d) too small", bufsz); bp = buf; switch(m->type){ default: sysfatal("encoding msg of unknown type %d", m->type); break; case SSH_FXP_INIT: bp += put4(1+4, bp); bp += put1(m->type, bp); bp += put4(m->version, bp); break; case SSH_FXP_RMDIR: case SSH_FXP_OPENDIR: case SSH_FXP_REALPATH: case SSH_FXP_STAT: case SSH_FXP_LSTAT: case SSH_FXP_READLINK: bp += put4(1+4+4+m->path.len, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->path, bp); break; case SSH_FXP_READDIR: case SSH_FXP_CLOSE: case SSH_FXP_FSTAT: bp += put4(1+4+4+m->handle.len, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->handle, bp); break; case SSH_FXP_OPEN: bp += put4(1+4+4+m->filename.len+4+fxpattrslen(&m->attrs), bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->filename, bp); bp += put4(m->pflags, bp); bp += putattrs(&m->attrs, bp); break; case SSH_FXP_READ: bp += put4(1+4+4+m->handle.len+8+4, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->handle, bp); bp += put8(m->offset, bp); bp += put4(m->len, bp); break; case SSH_FXP_WRITE: bp += put4(1+4+4+m->handle.len+8+4+m->data.len, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->handle, bp); bp += put8(m->offset, bp); bp += putstring(&m->data, bp); break; case SSH_FXP_REMOVE: bp += put4(1+4+4+m->filename.len, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->filename, bp); break; case SSH_FXP_RENAME: bp += put4(1+4+4+m->oldpath.len+4+m->newpath.len, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->oldpath, bp); bp += putstring(&m->newpath, bp); break; case SSH_FXP_MKDIR: case SSH_FXP_SETSTAT: bp += put4(1+4+4+m->path.len+fxpattrslen(&m->attrs), bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->path, bp); bp += putattrs(&m->attrs, bp); break; case SSH_FXP_FSETSTAT: bp += put4(1+4+4+m->handle.len+fxpattrslen(&m->attrs), bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->handle, bp); bp += putattrs(&m->attrs, bp); break; case SSH_FXP_SYMLINK: bp += put4(1+4+4+m->linkpath.len+4+m->targetpath.len, bp); bp += put1(m->type, bp); bp += put4(m->id, bp); bp += putstring(&m->linkpath, bp); bp += putstring(&m->targetpath, bp); break; } return bp-buf; } static int get8(u64int *x, uchar *a) { u64int v; v = (uvlong)a[0]<<56; v |= (uvlong)a[1]<<48; v |= (uvlong)a[2]<<40; v |= (uvlong)a[3]<<32; v |= a[4]<<24; v |= a[5]<<16; v |= a[6]<<8; v |= a[7]<<0; *x = v; return 8; } static int get4(u32int *x, uchar *a) { *x = (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0); return 4; } static int get1(uchar *x, uchar *a) { *x = a[0]; return 1; } static int getstring(String *s, uchar *buf) { uchar *bp; bp = buf; bp += get4(&s->len, bp); s->s = emalloc9p(s->len+1); memmove(s->s, bp, s->len); bp += s->len; s->s[s->len] = 0; return bp-buf; } static int getattrs(FAttrs *a, uchar *buf) { uchar *bp; bp = buf; bp += get4(&a->flags, bp); if(a->flags & SSH_FILEXFER_ATTR_SIZE) bp += get8(&a->size, bp); if(a->flags & SSH_FILEXFER_ATTR_UIDGID){ bp += get4(&a->uid, bp); bp += get4(&a->gid, bp); } if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) bp += get4(&a->perm, bp); if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){ bp += get4(&a->atime, bp); bp += get4(&a->mtime, bp); } return bp-buf; } static Fxp* fxpdecode(uchar *buf) { Fxp *m; int i; uchar *bp; bp = buf; bp += 4; /* ignore length */ m = emalloc9p(sizeof(*m)); bp += get1(&m->type, bp); switch(m->type){ default: sysfatal("bad msg type %d when decoding", m->type); break; case SSH_FXP_VERSION: bp += get4(&m->version, bp); break; case SSH_FXP_HANDLE: bp += get4(&m->id, bp); bp += getstring(&m->handle, bp); break; case SSH_FXP_DATA: bp += get4(&m->id, bp); bp += getstring(&m->data, bp); break; case SSH_FXP_STATUS: bp += get4(&m->id, bp); bp += get4(&m->status, bp); bp += getstring(&m->errmsg, bp); bp += getstring(&m->lang, bp); break; case SSH_FXP_NAME: bp += get4(&m->id, bp); bp += get4(&m->count, bp); m->filenames = emalloc9p(m->count*sizeof(*m->filenames)); m->longnames = emalloc9p(m->count*sizeof(*m->longnames)); m->attrsv = emalloc9p(m->count*sizeof(*m->attrsv)); for(i = 0; i < m->count; i++){ bp += getstring(&m->filenames[i], bp); bp += getstring(&m->longnames[i], bp); bp += getattrs(&m->attrsv[i], bp); } break; case SSH_FXP_ATTRS: bp += get4(&m->id, bp); bp += getattrs(&m->attrs, bp); break; } USED(bp); return m; } static void sshproc(void*) { int *p; threadsetname("sshproc"); p = conn.fd; close(p[0]); dup(p[1], 0); dup(p[1], 1); close(p[1]); switch(conn.sshver){ case '1': procexecl(nil, "/bin/ssh1", "ssh1", "-P", "-m", "-I", "-f", conn.host, conn.serverpath, nil); break; case '2': procexecl(nil, "/bin/ssh", "ssh", "-m", "-i", "-C", "-s", "sftp", conn.host, nil); break; case 'o': procexecl(nil, "/bin/openssh/ssh", "ssh", "-x", "-a", "-oClearAllForwardings=yes", "-2", conn.host, "-s", "sftp", nil); break; } sysfatal("exec ssh: %r"); } static void serverproc(void*) { uchar *buf; Fxp *req, *reply; int fd; uint n; threadsetname("serverproc"); fd = conn.sshfd; for(;;){ req = recvp(conn.rdchan); if(req == nil) threadexits(nil); reply = nil; n = fxpencode(req, conn.buf, sizeof conn.buf); buf = conn.buf; if(debug) hexdump("sending: ", buf, n); if(write(fd, buf, n) != n) goto sendreply; if(readn(fd, buf, 4) != 4) goto sendreply; get4(&n, buf); if(n >= sizeof(conn.buf)) sysfatal("reply silly big (%d > %d)\n", n, sizeof(conn.buf)); if(debug) fprint(2, "response length: %d\n", n); if(readn(fd, buf+4, n) != n) goto sendreply; if(debug) hexdump("response: ", buf, n+4); reply = fxpdecode(buf); if(req->type != SSH_FXP_INIT) if(req->id != reply->id) sysfatal("reply id doesn't match request id"); sendreply: sendp(conn.wrchan, reply); } } static Fxp* fxpgetreply(Fxp *msg) { Fxp *r; sendp(conn.rdchan, msg); r = recvp(conn.wrchan); if(r == nil) /* probably ssh exited */ threadexitsall(nil); return r; } static void stringinit(String *s, char *t) { s->s = (uchar*)estrdup9p(t); s->len = strlen(t); } static void stringcpy(String *dst, String *src) { dst->len = src->len; dst->s = emalloc9p(src->len+1); memmove(dst->s, src->s, src->len); dst->s[dst->len] = 0; } static String* stringdup(String *s) { String *q; q = emalloc9p(sizeof *q); q->len = s->len; q->s = emalloc9p(s->len+1); memmove(q->s, s->s, s->len); q->s[q->len] = 0; return q; } static void freestring(String *s) { if(s){ free(s->s); free(s); } } struct{ u32int status; char *str; int iserror; } errtab[] = { SSH_FX_OK, "", 0, SSH_FX_EOF, "", 0, SSH_FX_NO_SUCH_FILE, Enofile, 1, SSH_FX_PERMISSION_DENIED, Eperm, 1, SSH_FX_FAILURE, Efail, 1, SSH_FX_BAD_MESSAGE, Emsg, 1, SSH_FX_NO_CONNECTION, Enocn, 1, SSH_FX_CONNECTION_LOST, Elostcn, 1, SSH_FX_OP_UNSUPPORTED, Eunsup, 1, }; static char* errlookup(u32int status, int *err) { int i; for(i = 0; i < nelem(errtab); i++) if(errtab[i].status == status){ if(err) *err = errtab[i].iserror; return errtab[i].str; } if(err) *err = 1; return Ebotch; } static int fxpwerrstr(u32int status) { int i; for(i = 0; i < nelem(errtab); i++) if(errtab[i].status == status){ werrstr(errtab[i].str); return errtab[i].iserror; } werrstr("%s", Ebotch); return 1; } static void freefxp(Fxp *m) { int i; if(m == nil) return; switch(m->type){ default: fprint(2, "how to free msg of type %d?\n", m->type); break; case SSH_FXP_INIT: case SSH_FXP_VERSION: case SSH_FXP_ATTRS: break; case SSH_FXP_OPEN: case SSH_FXP_REMOVE: free(m->filename.s); break; case SSH_FXP_CLOSE: case SSH_FXP_READ: case SSH_FXP_READDIR: case SSH_FXP_FSTAT: case SSH_FXP_FSETSTAT: case SSH_FXP_HANDLE: free(m->handle.s); break; case SSH_FXP_WRITE: free(m->handle.s); free(m->data.s); break; case SSH_FXP_RENAME: free(m->oldpath.s); free(m->newpath.s); break; case SSH_FXP_MKDIR: case SSH_FXP_RMDIR: case SSH_FXP_OPENDIR: case SSH_FXP_STAT: case SSH_FXP_LSTAT: case SSH_FXP_SETSTAT: case SSH_FXP_READLINK: case SSH_FXP_REALPATH: free(m->path.s); break; case SSH_FXP_SYMLINK: free(m->linkpath.s); free(m->targetpath.s); break; case SSH_FXP_STATUS: free(m->errmsg.s); free(m->lang.s); break; case SSH_FXP_DATA: free(m->data.s); break; case SSH_FXP_NAME: for(i = 0; i < m->count; i++){ free(m->filenames[i].s); free(m->longnames[i].s); } free(m->filenames); free(m->longnames); free(m->attrsv); break; } free(m); } static FHandle* replyhandle(Fxp *m) { Fxp *r; FHandle *h; r = fxpgetreply(m); freefxp(m); switch(r->type){ default: werrstr("%s", Ebotch); freefxp(r); return nil; case SSH_FXP_STATUS: fxpwerrstr(r->status); freefxp(r); return nil; case SSH_FXP_HANDLE: h = stringdup(&r->handle); freefxp(r); return h; } } static int replystatus(Fxp *r) { int err; switch(r->type){ default: werrstr("%s", Ebotch); freefxp(r); return 1; case SSH_FXP_STATUS: err = fxpwerrstr(r->status); freefxp(r); return err; } } static Fxp* newfxp(uchar type) { Fxp *m; m = emalloc9p(sizeof *m); m->type = type; m->id = FMsgid; return m; } int fxpremove(char *file) { Fxp *m, *r; m = newfxp(SSH_FXP_REMOVE); stringinit(&m->filename, file); r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : 0; } int fxprmdir(char *path) { Fxp *m, *r; m = newfxp(SSH_FXP_RMDIR); stringinit(&m->path, path); r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : 0; } int fxpmkdir(char *path, ulong perm) { Fxp *m, *r; m = newfxp(SSH_FXP_MKDIR); stringinit(&m->path, path); m->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; m->attrs.perm = perm&0777; r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : 0; } long fxpwrite(FHandle *h, void *buf, long nbuf, vlong off) { Fxp *m, *r; m = newfxp(SSH_FXP_WRITE); stringcpy(&m->handle, h); m->offset = off; m->data.len = nbuf; m->data.s = emalloc9p(nbuf+1); memmove(m->data.s, buf, nbuf); m->data.s[m->data.len] = 0; r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : nbuf; } int fxpread(FHandle *h, void *buf, long nbuf, vlong off) { Fxp *m, *r; int n; m = newfxp(SSH_FXP_READ); stringcpy(&m->handle, h); m->offset = off; m->len = nbuf; r = fxpgetreply(m); freefxp(m); if(r->type == SSH_FXP_DATA){ n = r->data.len; memmove(buf, r->data.s, n); freefxp(r); return n; } return replystatus(r) ? -1 : 0; } FHandle* fxpcreate(char *file, int omode, ulong perm) { int mode; Fxp *m; mode = SSH_FXF_CREAT | SSH_FXF_TRUNC; if(omode&OREAD) mode |= SSH_FXF_READ; if(omode&OWRITE) mode |= SSH_FXF_WRITE; if(omode&ORDWR) mode |= SSH_FXF_READ | SSH_FXF_WRITE; m = newfxp(SSH_FXP_OPEN); stringinit(&m->filename, file); m->pflags = mode; m->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; m->attrs.perm = perm&0777; return replyhandle(m); } FHandle* fxpopen(char *file, int omode) { Fxp *m; int mode; /* * We can't do any permission checks for OEXEC or * ORCLOSE, as we don't know the UID of the user or what * group(s) the user belongs to. */ mode = 0; if(omode&OREAD) mode |= SSH_FXF_READ; if(omode&OWRITE) mode |= SSH_FXF_WRITE; if(omode&ORDWR) mode |= SSH_FXF_READ | SSH_FXF_WRITE; /* * If file doesn't exists, there should be no Topen, * as Twalk will fail. So, the CREAT flag is here only to * statisfy sftp protocol. */ if(omode&OTRUNC) mode |= SSH_FXF_TRUNC | SSH_FXF_CREAT; m = newfxp(SSH_FXP_OPEN); stringinit(&m->filename, file); m->pflags = mode; m->attrs.flags = 0; return replyhandle(m); } void freedir(Dir *d) { if(d){ free(d->uid); free(d->gid); free(d->muid); free(d->name); free(d); } } enum { UIDSz = 20, ATDIR = 0040000, }; static void attrs2dir(Dir *d, FAttrs *a, char *name) { /* caller should fill in Qid properly later */ d->qid = (Qid){0, 0, QTFILE}; d->mode = 0; d->atime = 0; d->mtime = 0; d->length = 0; d->muid = estrdup9p("unknown"); d->name = estrdup9p(name); if(conn.map == nil){ d->uid = estrdup9p("unknown"); d->gid = estrdup9p("unknown"); }else{ d->uid = uidtostr(conn.map, a->uid); d->gid = gidtostr(conn.map, a->gid); } if(a->flags & SSH_FILEXFER_ATTR_SIZE) d->length = a->size; if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS){ d->mode = a->perm&0777; if(a->perm & ATDIR){ d->mode |= DMDIR; d->qid.type = QTDIR; } } if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){ d->atime = a->atime; d->mtime = a->mtime; } } static char* basename(char *p) { char *s; s = strrchr(p, '/'); if(s == nil) s = p; else s++; return estrdup9p(s); } Dir* fxpstat1(char *name, char **err) { Fxp *m, *r; Dir *d; m = newfxp(SSH_FXP_STAT); stringinit(&m->path, name); r = fxpgetreply(m); freefxp(m); /* walk1 kludge. We can't use responderror there */ if(err) *err = errlookup(r->status, nil); if(r->type == SSH_FXP_ATTRS){ d = emalloc9p(sizeof *d); name = basename(name); attrs2dir(d, &r->attrs, name); free(name); freefxp(r); return d; } return replystatus(r) ? nil : nil; } Dir* fxpstat(char *name) { return fxpstat1(name, nil); } static void dir2attrs(FAttrs *a, Dir *d, Dir *od) { a->flags = 0; if(d->length != ~0){ a->flags |= SSH_FILEXFER_ATTR_SIZE; a->size = d->length; } if(d->mode != ~0){ a->flags |= SSH_FILEXFER_ATTR_PERMISSIONS; a->perm = d->mode&0777; } d->atime = od->atime; d->mtime = od->mtime; if(d->atime != ~0) a->atime = d->atime; if(d->mtime != ~0) a->mtime = d->mtime; if(d->atime != ~0 || d->mtime != ~0) a->flags |= SSH_FILEXFER_ATTR_ACMODTIME; } int fxpsetstat(char *name, Dir *d) { Fxp *m, *r; Dir *od; if((od = fxpstat(name)) == nil) return -1; m = newfxp(SSH_FXP_SETSTAT); stringinit(&m->path, name); dir2attrs(&m->attrs, d, od); r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : 0; } int fxprename(char *old, char *new) { Fxp *m, *r; m = newfxp(SSH_FXP_RENAME); stringinit(&m->oldpath, old); stringinit(&m->newpath, new); r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : 0; } long fxpreaddir(FHandle *h, Dir ***buf) { Fxp *m, *r; int i, j; char *name; Dir **ds; FAttrs *a; m = newfxp(SSH_FXP_READDIR); stringcpy(&m->handle, h); r = fxpgetreply(m); freefxp(m); if(r->type == SSH_FXP_NAME){ ds = emalloc9p(r->count*sizeof(Dir*)); j = 0; for(i = 0; i < r->count; i++){ name = (char*)r->filenames[i].s; a = &r->attrsv[i]; if(strcmp(name, ".") != 0 && strcmp(name, "..") != 0){ ds[j] = emalloc9p(sizeof(Dir)); attrs2dir(ds[j], a, name); j++; } } *buf = ds; freefxp(r); return j; } return replystatus(r) ? -1 : 0; } int fxpclose(FHandle *h) { Fxp *m, *r; m = newfxp(SSH_FXP_CLOSE); stringcpy(&m->handle, h); freestring(h); r = fxpgetreply(m); freefxp(m); return replystatus(r) ? -1 : 0; } FHandle* fxpopendir(char *path) { Fxp *m; m = newfxp(SSH_FXP_OPENDIR); stringinit(&m->path, path); return replyhandle(m); } static int fxpversion(int ver) { Fxp *m, *r; int n; m = newfxp(SSH_FXP_INIT); m->version = ver; r = fxpgetreply(m); freefxp(m); if(r->type != SSH_FXP_VERSION){ freefxp(r); return -1; } n = r->version; freefxp(r); return n; } int fxpinit(char *host, char ver, char *path) { int *p, n; p = conn.fd; if(pipe(p) < 0) sysfatal("pipe: %r"); conn.host = estrdup9p(host); conn.sshver = ver; conn.serverpath = estrdup9p(path); procrfork(sshproc, nil, STACK, RFFDG); close(p[1]); conn.sshfd = p[0]; conn.rdchan = chancreate(sizeof(Fxp*), 0); conn.wrchan = chancreate(sizeof(Fxp*), 0); proccreate(serverproc, nil, STACK); if((n = fxpversion(3)) != 3){ werrstr("got protocol version %d; want 3", n); return -1; } return 0; } void fxpreadmap(char *pfile, char *gfile) { conn.map = readmap(pfile, gfile); } void fxpterm(void) { close(conn.sshfd); sendp(conn.rdchan, nil); /* terminate serverproc */ chanfree(conn.rdchan); chanfree(conn.wrchan); free(conn.host); free(conn.serverpath); if(conn.map) closemap(conn.map); }