]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/sshfs.c
libtags, zuke: add *.mod support (thanks kemal)
[plan9front.git] / sys / src / cmd / sshfs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <fcall.h>
4 #include <thread.h>
5 #include <9p.h>
6 #include <libsec.h>
7
8 int readonly;
9 int debug;
10 char *root = ".";
11 #define dprint(...) if(debug) fprint(2, __VA_ARGS__)
12 #pragma varargck        type    "Σ"    int
13
14 enum {
15         MAXPACK = 34000,
16         MAXWRITE = 32768,
17         MAXATTRIB = 64,
18         VERSION = 3,
19         MAXREQID = 32,
20         HASH = 64
21 };
22
23 enum {
24         SSH_FXP_INIT = 1,
25         SSH_FXP_VERSION = 2,
26         SSH_FXP_OPEN = 3,
27         SSH_FXP_CLOSE = 4,
28         SSH_FXP_READ = 5,
29         SSH_FXP_WRITE = 6,
30         SSH_FXP_LSTAT = 7,
31         SSH_FXP_FSTAT = 8,
32         SSH_FXP_SETSTAT = 9,
33         SSH_FXP_FSETSTAT = 10,
34         SSH_FXP_OPENDIR = 11,
35         SSH_FXP_READDIR = 12,
36         SSH_FXP_REMOVE = 13,
37         SSH_FXP_MKDIR = 14,
38         SSH_FXP_RMDIR = 15,
39         SSH_FXP_REALPATH = 16,
40         SSH_FXP_STAT = 17,
41         SSH_FXP_RENAME = 18,
42         SSH_FXP_READLINK = 19,
43         SSH_FXP_SYMLINK = 20,
44         SSH_FXP_STATUS = 101,
45         SSH_FXP_HANDLE = 102,
46         SSH_FXP_DATA = 103,
47         SSH_FXP_NAME = 104,
48         SSH_FXP_ATTRS = 105,
49         SSH_FXP_EXTENDED = 200,
50         SSH_FXP_EXTENDED_REPLY = 201,
51         
52         SSH_FXF_READ = 0x00000001,
53         SSH_FXF_WRITE = 0x00000002,
54         SSH_FXF_APPEND = 0x00000004,
55         SSH_FXF_CREAT = 0x00000008,
56         SSH_FXF_TRUNC = 0x00000010,
57         SSH_FXF_EXCL = 0x00000020,
58         SSH_FILEXFER_ATTR_SIZE = 0x00000001,
59         SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
60         SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
61         SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
62         SSH_FILEXFER_ATTR_EXTENDED = 0x80000000,
63
64         SSH_FX_OK = 0,
65         SSH_FX_EOF = 1,
66         SSH_FX_NO_SUCH_FILE = 2,
67         SSH_FX_PERMISSION_DENIED = 3,
68         SSH_FX_FAILURE = 4,
69         SSH_FX_BAD_MESSAGE = 5,
70         SSH_FX_NO_CONNECTION = 6,
71         SSH_FX_CONNECTION_LOST = 7,
72         SSH_FX_OP_UNSUPPORTED = 8,
73 };
74
75 char *errors[] = {
76         [SSH_FX_OK] "success",
77         [SSH_FX_EOF] "end of file",
78         [SSH_FX_NO_SUCH_FILE] "file does not exist",
79         [SSH_FX_PERMISSION_DENIED] "permission denied",
80         [SSH_FX_FAILURE] "failure",
81         [SSH_FX_BAD_MESSAGE] "bad message",
82         [SSH_FX_NO_CONNECTION] "no connection",
83         [SSH_FX_CONNECTION_LOST] "connection lost",
84         [SSH_FX_OP_UNSUPPORTED] "unsupported operation",
85 };
86
87 typedef struct SFid SFid;
88 typedef struct SReq SReq;
89 typedef struct IDEnt IDEnt;
90
91 struct SFid {
92         RWLock;
93         char *fn;
94         uchar *hand;
95         int handn;
96         Qid qid;
97         int dirreads;
98         Dir *dirent;
99         int ndirent, dirpos;
100         uchar direof;
101 };
102
103 struct SReq {
104         Req *req;
105         SFid *closefid;
106         SReq *next;
107 };
108
109 struct IDEnt {
110         char *name;
111         int id;
112         IDEnt *next;
113 };
114 IDEnt *uidtab[HASH], *gidtab[HASH];
115
116 int rdfd, wrfd;
117 SReq *sreqrd[MAXREQID];
118 QLock sreqidlock;
119 Rendez sreqidrend = {.l = &sreqidlock};
120
121 SReq *sreqwr, **sreqlast = &sreqwr;
122 QLock sreqwrlock;
123 Rendez writerend = {.l = &sreqwrlock};
124
125 #define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
126 #define GET4(p) ((u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24)
127
128 int
129 fxpfmt(Fmt *f)
130 {
131         int n;
132         
133         n = va_arg(f->args, int);
134         switch(n){
135         case SSH_FXP_INIT: fmtstrcpy(f, "SSH_FXP_INIT"); break;
136         case SSH_FXP_VERSION: fmtstrcpy(f, "SSH_FXP_VERSION"); break;
137         case SSH_FXP_OPEN: fmtstrcpy(f, "SSH_FXP_OPEN"); break;
138         case SSH_FXP_CLOSE: fmtstrcpy(f, "SSH_FXP_CLOSE"); break;
139         case SSH_FXP_READ: fmtstrcpy(f, "SSH_FXP_READ"); break;
140         case SSH_FXP_WRITE: fmtstrcpy(f, "SSH_FXP_WRITE"); break;
141         case SSH_FXP_LSTAT: fmtstrcpy(f, "SSH_FXP_LSTAT"); break;
142         case SSH_FXP_FSTAT: fmtstrcpy(f, "SSH_FXP_FSTAT"); break;
143         case SSH_FXP_SETSTAT: fmtstrcpy(f, "SSH_FXP_SETSTAT"); break;
144         case SSH_FXP_FSETSTAT: fmtstrcpy(f, "SSH_FXP_FSETSTAT"); break;
145         case SSH_FXP_OPENDIR: fmtstrcpy(f, "SSH_FXP_OPENDIR"); break;
146         case SSH_FXP_READDIR: fmtstrcpy(f, "SSH_FXP_READDIR"); break;
147         case SSH_FXP_REMOVE: fmtstrcpy(f, "SSH_FXP_REMOVE"); break;
148         case SSH_FXP_MKDIR: fmtstrcpy(f, "SSH_FXP_MKDIR"); break;
149         case SSH_FXP_RMDIR: fmtstrcpy(f, "SSH_FXP_RMDIR"); break;
150         case SSH_FXP_REALPATH: fmtstrcpy(f, "SSH_FXP_REALPATH"); break;
151         case SSH_FXP_STAT: fmtstrcpy(f, "SSH_FXP_STAT"); break;
152         case SSH_FXP_RENAME: fmtstrcpy(f, "SSH_FXP_RENAME"); break;
153         case SSH_FXP_READLINK: fmtstrcpy(f, "SSH_FXP_READLINK"); break;
154         case SSH_FXP_SYMLINK: fmtstrcpy(f, "SSH_FXP_SYMLINK"); break;
155         case SSH_FXP_STATUS: fmtstrcpy(f, "SSH_FXP_STATUS"); break;
156         case SSH_FXP_HANDLE: fmtstrcpy(f, "SSH_FXP_HANDLE"); break;
157         case SSH_FXP_DATA: fmtstrcpy(f, "SSH_FXP_DATA"); break;
158         case SSH_FXP_NAME: fmtstrcpy(f, "SSH_FXP_NAME"); break;
159         case SSH_FXP_ATTRS: fmtstrcpy(f, "SSH_FXP_ATTRS"); break;
160         case SSH_FXP_EXTENDED: fmtstrcpy(f, "SSH_FXP_EXTENDED"); break;
161         case SSH_FXP_EXTENDED_REPLY: fmtstrcpy(f, "SSH_FXP_EXTENDED_REPLY");
162         default: fmtprint(f, "%d", n);
163         }
164         return 0;
165 }
166
167 char *
168 idlookup(IDEnt **tab, int id)
169 {
170         IDEnt *p;
171         
172         for(p = tab[(ulong)id % HASH]; p != nil; p = p->next)
173                 if(p->id == id)
174                         return estrdup9p(p->name);
175         return smprint("%d", id);
176 }
177
178 int
179 namelookup(IDEnt **tab, char *name)
180 {
181         IDEnt *p;
182         int i;
183         char *q;
184         
185         for(i = 0; i < HASH; i++)
186                 for(p = tab[i]; p != nil; p = p->next)
187                         if(strcmp(p->name, name) == 0)
188                                 return p->id;
189         i = strtol(name, &q, 10);
190         if(*q == 0) return i;
191         werrstr("unknown %s '%s'", tab == uidtab ? "user" : "group", name);
192         return -1;
193 }
194
195 int
196 allocsreqid(SReq *r)
197 {
198         int i;
199
200         qlock(&sreqidlock);
201         for(;;){
202                 for(i = 0; i < MAXREQID; i++)
203                         if(sreqrd[i] == nil){
204                                 sreqrd[i] = r;
205                                 goto out;
206                         }
207                 rsleep(&sreqidrend);
208         }
209 out:
210         qunlock(&sreqidlock);
211         return i;
212 }
213
214 int
215 vpack(uchar *p, int n, char *fmt, va_list a)
216 {
217         uchar *p0 = p, *e = p+n;
218         SReq *sr = nil;
219         u32int u;
220         u64int v;
221         void *s;
222         int c;
223
224         for(;;){
225                 switch(c = *fmt++){
226                 case '\0':
227                         if(sr != nil){
228                                 u = allocsreqid(sr);
229                                 PUT4(p0+1, u);
230                         }
231                         return p - p0;
232                 case '_':
233                         if(++p > e) goto err;
234                         break;
235                 case '.':
236                         *va_arg(a, void**) = p;
237                         break;
238                 case 'b':
239                         if(p >= e) goto err;
240                         *p++ = va_arg(a, int);
241                         break;
242                 case '[':
243                 case 's':
244                         s = va_arg(a, void*);
245                         u = va_arg(a, int);
246                         if(c == 's'){
247                                 if(p+4 > e) goto err;
248                                 PUT4(p, u), p += 4;
249                         }
250                         if(u > e-p) goto err;
251                         memmove(p, s, u);
252                         p += u;
253                         break;
254                 case 'q':
255                         p += 4;
256                         if(p != p0+5 || p > e) goto err;
257                         sr = va_arg(a, SReq*);
258                         break;
259                 case 'u':
260                         u = va_arg(a, int);
261                         if(p+4 > e) goto err;
262                         PUT4(p, u), p += 4;
263                         break;
264                 case 'v':
265                         v = va_arg(a, vlong);
266                         if(p+8 > e) goto err;
267                         u = v>>32; PUT4(p, u), p += 4;
268                         u = v; PUT4(p, u), p += 4;
269                         break;
270                 }
271         }
272 err:
273         return -1;
274 }
275
276 int
277 vunpack(uchar *p, int n, char *fmt, va_list a)
278 {
279         uchar *p0 = p, *e = p+n;
280         u32int u;
281         u64int v;
282         void *s;
283
284         for(;;){
285                 switch(*fmt++){
286                 case '\0':
287                         return p - p0;
288                 case '_':
289                         if(++p > e) goto err;
290                         break;
291                 case '.':
292                         *va_arg(a, void**) = p;
293                         break;
294                 case 'b':
295                         if(p >= e) goto err;
296                         *va_arg(a, int*) = *p++;
297                         break;
298                 case 's':
299                         if(p+4 > e) goto err;
300                         u = GET4(p), p += 4;
301                         if(u > e-p) goto err;
302                         *va_arg(a, void**) = p;
303                         *va_arg(a, int*) = u;
304                         p += u;
305                         break;
306                 case '[':
307                         s = va_arg(a, void*);
308                         u = va_arg(a, int);
309                         if(u > e-p) goto err;
310                         memmove(s, p, u);
311                         p += u;
312                         break;
313                 case 'u':
314                         if(p+4 > e) goto err;
315                         u = GET4(p);
316                         *va_arg(a, int*) = u;
317                         p += 4;
318                         break;
319                 case 'v':
320                         if(p+8 > e) goto err;
321                         v = (u64int)GET4(p) << 32;
322                         v |= (u32int)GET4(p+4);
323                         *va_arg(a, vlong*) = v;
324                         p += 8;
325                         break;
326                 }
327         }
328 err:
329         return -1;
330 }
331
332 int
333 pack(uchar *p, int n, char *fmt, ...)
334 {
335         va_list a;
336         va_start(a, fmt);
337         n = vpack(p, n, fmt, a);
338         va_end(a);
339         return n;
340 }
341 int
342 unpack(uchar *p, int n, char *fmt, ...)
343 {
344         va_list a;
345         va_start(a, fmt);
346         n = vunpack(p, n, fmt, a);
347         va_end(a);
348         return n;
349 }
350
351 void
352 sendpkt(char *fmt, ...)
353 {
354         static uchar buf[MAXPACK];
355         int n;
356         va_list a;
357
358         va_start(a, fmt);
359         n = vpack(buf+4, sizeof(buf)-4, fmt, a);
360         va_end(a);
361         if(n < 0) {
362                 sysfatal("sendpkt: message too big");
363                 return;
364         }
365         PUT4(buf, n);
366         n += 4;
367
368         dprint("SFTP --> %Σ\n", (int)buf[4]);
369         if(write(wrfd, buf, n) != n)
370                 sysfatal("write: %r");
371 }
372
373 static uchar rxpkt[MAXPACK];
374 static int rxlen;
375
376 int
377 recvpkt(void)
378 {
379         static uchar rxbuf[MAXPACK];
380         static int rxfill;
381         int rc;
382         
383         while(rxfill < 4 || rxfill < (rxlen = GET4(rxbuf) + 4) && rxlen <= MAXPACK){
384                 rc = read(rdfd, rxbuf + rxfill, MAXPACK - rxfill);
385                 if(rc < 0) sysfatal("read: %r");
386                 if(rc == 0) sysfatal("read: eof");
387                 rxfill += rc;
388         }
389         if(rxlen > MAXPACK) sysfatal("received garbage");
390         memmove(rxpkt, rxbuf + 4, rxlen - 4);
391         memmove(rxbuf, rxbuf + rxlen, rxfill - rxlen);
392         rxfill -= rxlen;
393         rxlen -= 4;
394         dprint("SFTP <-- %Σ\n", (int)rxpkt[0]);
395         return rxpkt[0];
396 }
397
398 void
399 freedir1(Dir *d)
400 {
401         free(d->name);
402         free(d->uid);
403         free(d->gid);
404         free(d->muid);
405 }
406
407 void
408 freedir(SFid *s)
409 {
410         int i;
411
412         for(i = 0; i < s->ndirent; i++)
413                 freedir1(&s->dirent[i]);
414         free(s->dirent);
415         s->dirent = nil;
416         s->ndirent = 0;
417         s->dirpos = 0;
418 }
419
420 void
421 putsfid(SFid *s)
422 {
423         if(s == nil) return;
424
425         wlock(s);
426         wunlock(s);
427
428         free(s->fn);
429         free(s->hand);
430         freedir(s);
431         free(s);
432 }
433
434 void
435 putsreq(SReq *s)
436 {
437         free(s);
438 }
439
440 void
441 submitsreq(SReq *s)
442 {
443         qlock(&sreqwrlock);
444         *sreqlast = s;
445         sreqlast = &s->next;
446         rwakeup(&writerend);
447         qunlock(&sreqwrlock);
448 }
449
450 void
451 submitreq(Req *r)
452 {
453         SReq *s;
454         
455         s = emalloc9p(sizeof(SReq));
456         s->req = r;
457         submitsreq(s);
458 }
459
460 char *
461 pathcat(char *p, char *c)
462 {
463         return cleanname(smprint("%s/%s", p, c));
464 }
465
466 char *
467 parentdir(char *p)
468 {
469         return pathcat(p, "..");
470 }
471
472 char *
473 finalelem(char *p)
474 {
475         char *q;
476         
477         q = strrchr(p, '/');
478         if(q == nil) return estrdup9p(p);
479         return estrdup9p(q+1);
480 }
481
482 u64int
483 qidcalc(char *c)
484 {
485         uchar dig[SHA1dlen];
486
487         sha1((uchar *) c, strlen(c), dig, nil);
488         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;
489 }
490
491 void
492 walkprocess(Req *r, char *e)
493 {
494         char *p;
495         SFid *sf;
496         
497         sf = r->newfid->aux;
498         if(e != nil){
499                 r->ofcall.nwqid--;
500                 if(r->ofcall.nwqid == 0){
501                         respond(r, e);
502                         return;
503                 }
504                 p = r->aux;
505                 r->aux = parentdir(p);
506                 free(p);
507                 submitreq(r);
508         }else{
509                 assert(r->ofcall.nwqid > 0);
510                 wlock(sf);
511                 free(sf->fn);
512                 sf->fn = r->aux;
513                 r->aux = nil;
514                 sf->qid = r->ofcall.wqid[r->ofcall.nwqid - 1];
515                 wunlock(sf);
516                 respond(r, nil);
517         }
518 }
519
520 int
521 attrib2dir(uchar *p0, uchar *ep, Dir *d)
522 {
523         uchar *p;
524         int i, rc, extn, extvn;
525         u32int flags, uid, gid, perm, next;
526         uchar *exts,  *extvs;
527         
528         p = p0;
529         if(p + 4 > ep) return -1;
530         flags = GET4(p), p += 4;
531         if((flags & SSH_FILEXFER_ATTR_SIZE) != 0){
532                 rc = unpack(p, ep - p, "v", &d->length); if(rc < 0) return -1; p += rc;
533         }
534         if((flags & SSH_FILEXFER_ATTR_UIDGID) != 0){
535                 rc = unpack(p, ep - p, "uu", &uid, &gid); if(rc < 0) return -1; p += rc;
536                 d->uid = idlookup(uidtab, uid);
537                 d->gid = idlookup(gidtab, gid);
538         }else{
539                 d->uid = estrdup9p("sshfs");
540                 d->gid = estrdup9p("sshfs");
541         }
542         d->muid = estrdup9p(d->uid);
543         if((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0){
544                 rc = unpack(p, ep - p, "u", &perm); if(rc < 0) return -1; p += rc;
545                 d->mode = perm & 0777;
546                 if((perm & 0170000) == 0040000) d->mode |= DMDIR;
547         }
548         d->qid.type = d->mode >> 24;
549         if((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0){
550                 rc = unpack(p, ep - p, "uu", &d->atime, &d->mtime); if(rc < 0) return -1; p += rc;
551                 d->qid.vers = d->mtime;
552         }
553         if((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0){
554                 rc = unpack(p, ep - p, "u", &next); if(rc < 0) return -1; p += rc;
555                 for(i = 0; i < next; i++){
556                         rc = unpack(p, ep - p, "ss", &exts, &extn, &extvs, &extvn); if(rc < 0) return -1; p += rc;
557                         exts[extn] = extvs[extvn] = 0;
558                 }
559         }
560         return p - p0;
561 }
562
563 int
564 dir2attrib(Dir *d, uchar **rp)
565 {
566         int rc;
567         uchar *r, *p, *e;
568         u32int fl;
569         int uid, gid;
570
571         werrstr("phase error");
572         *rp = r = emalloc9p(MAXATTRIB);
573         e = r + MAXATTRIB;
574         fl = 0;
575         p = r + 4;
576         if(d->length != (uvlong)-1){
577                 fl |= SSH_FILEXFER_ATTR_SIZE;
578                 rc = pack(p, e - p, "v", d->length); if(rc < 0) return -1; p += rc;
579         }
580         if(d->uid != nil && *d->uid != 0 || d->gid != nil && *d->gid != 0){
581                 /* FIXME: sending -1 for "don't change" works with openssh, but violates the spec */
582                 if(d->uid != nil && *d->uid != 0){
583                         uid = namelookup(uidtab, d->uid);
584                         if(uid == -1)
585                                 return -1;
586                 }else
587                         uid = -1;
588                 if(d->gid != nil && *d->gid != 0){
589                         gid = namelookup(gidtab, d->gid);
590                         if(gid == -1)
591                                 return -1;
592                 }else
593                         gid = -1;
594                 fl |= SSH_FILEXFER_ATTR_UIDGID;
595                 rc = pack(p, e - p, "uu", uid, gid); if(rc < 0) return -1; p += rc;
596         }
597         if(d->mode != (ulong)-1){
598                 fl |= SSH_FILEXFER_ATTR_PERMISSIONS;
599                 rc = pack(p, e - p, "u", d->mode); if(rc < 0) return -1; p += rc;
600         }
601         if(d->atime != (ulong)-1 || d->mtime != (ulong)-1){
602                 /* FIXME: see above */
603                 fl |= SSH_FILEXFER_ATTR_ACMODTIME;
604                 rc = pack(p, e - p, "uu", d->atime, d->mtime); if(rc < 0) return -1; p += rc;
605         }
606         PUT4(r, fl);
607         return p - r;
608 }
609
610 int
611 attrfixupqid(Qid *qid)
612 {
613         u32int flags;
614         uchar *p;
615
616         if(unpack(rxpkt, rxlen, "_____u", &flags) < 0) return -1;
617         p = rxpkt + 9;
618         if(flags & SSH_FILEXFER_ATTR_SIZE) p += 8;
619         if(flags & SSH_FILEXFER_ATTR_UIDGID) p += 8;
620         if(flags & SSH_FILEXFER_ATTR_PERMISSIONS){
621                 if(p + 4 > rxpkt + rxlen) return -1;
622                 if((GET4(p) & 0170000) != 0040000) qid->type = 0;
623                 else qid->type = QTDIR;
624                 p += 4;
625         }
626         if(flags & SSH_FILEXFER_ATTR_ACMODTIME){
627                 if(p + 8 > rxpkt + rxlen) return -1;
628                 p += 4;
629                 qid->vers = GET4(p);    /* mtime for qid.vers */
630         }
631         return 0;
632 }
633
634 int
635 parsedir(SFid *sf)
636 {
637         int i, rc;
638         Dir *d;
639         u32int c;
640         uchar *p, *ep;
641         char *fn, *ln;
642         int fns, lns;
643         char *s;
644
645         if(unpack(rxpkt, rxlen, "_____u", &c) < 0) return -1;
646         wlock(sf);
647         freedir(sf);
648         sf->dirent = emalloc9p(c * sizeof(Dir));
649         d = sf->dirent;
650         p = rxpkt + 9;
651         ep = rxpkt + rxlen;
652         for(i = 0; i < c; i++){
653                 memset(d, 0, sizeof(Dir));
654                 rc = unpack(p, ep - p, "ss", &fn, &fns, &ln, &lns); if(rc < 0) goto err; p += rc;
655                 rc = attrib2dir(p, ep, d); if(rc < 0) goto err; p += rc;
656                 if(fn[0] == '.' && (fns == 1 || fns == 2 && fn[1] == '.')){
657                         freedir1(d);
658                         continue;
659                 }
660                 d->name = emalloc9p(fns + 1);
661                 memcpy(d->name, fn, fns);
662                 d->name[fns] = 0;
663                 s = pathcat(sf->fn, d->name);
664                 d->qid.path = qidcalc(s);
665                 free(s);
666                 sf->ndirent++;
667                 d++;
668         }
669         wunlock(sf);
670         return 0;
671 err:
672         freedir1(d);
673         wunlock(sf);
674         return -1;
675 }
676
677
678 void
679 readprocess(Req *r)
680 {
681         int i;
682         uchar *p, *ep;
683         uint rv;
684         SFid *sf;
685         
686         sf = r->fid->aux;
687         wlock(sf);
688         if(sf->direof){
689                 wunlock(sf);
690                 respond(r, nil);
691                 return;
692         }
693         i = sf->dirpos;
694         p = (uchar*)r->ofcall.data + r->ofcall.count;
695         ep = (uchar*)r->ofcall.data + r->ifcall.count;
696         rv = ep - p;
697         while(p < ep){
698                 if(i >= sf->ndirent)
699                         break;
700                 rv = convD2M(&sf->dirent[i], p, ep-p);
701                 if(rv <= BIT16SZ)
702                         break;
703                 p += rv;
704                 i++;
705         }
706         sf->dirpos = i;
707         if(i >= sf->ndirent)
708                 freedir(sf);
709         wunlock(sf);
710         r->ofcall.count = p - (uchar*)r->ofcall.data;
711         if(rv <= BIT16SZ)
712                 respond(r, nil);
713         else
714                 submitreq(r);
715 }
716
717 void
718 sshfsread(Req *r)
719 {
720         if((r->fid->qid.type & QTDIR) == 0){
721                 submitreq(r);
722                 return;
723         }
724         if(r->ifcall.offset == 0){
725                 SFid *sf = r->fid->aux;
726                 wlock(sf);
727                 freedir(sf);
728                 if(sf->dirreads > 0){
729                         wunlock(sf);
730                         r->aux = (void*)-1;
731                         submitreq(r);
732                         return;
733                 }
734                 wunlock(sf);
735         }
736         readprocess(r);
737 }
738
739 void
740 sshfsattach(Req *r)
741 {
742         SFid *sf;
743
744         if(r->aux == nil){
745                 sf = emalloc9p(sizeof(SFid));
746                 if(r->ifcall.aname != nil)
747                         switch(*r->ifcall.aname){
748                         case '~':
749                                 switch(r->ifcall.aname[1]){
750                                 case 0: sf->fn = estrdup9p("."); break;
751                                 case '/': sf->fn = estrdup9p(r->ifcall.aname + 2); break;
752                                 default:
753                                         free(sf);
754                                         respond(r, "invalid attach name");
755                                         return;
756                                 }
757                                 break;
758                         case '/':
759                                 sf->fn = estrdup9p(r->ifcall.aname);
760                                 break;
761                         case 0:
762                                 sf->fn = estrdup9p(root);
763                                 break;
764                         default:
765                                 sf->fn = pathcat(root, r->ifcall.aname);
766                         }
767                 else
768                         sf->fn = estrdup9p(root);
769                 r->fid->aux = sf;
770                 submitreq(r);
771         }else{
772                 sf = r->fid->aux;
773                 sf->qid = (Qid){qidcalc(sf->fn), 0, QTDIR};
774                 r->ofcall.qid = sf->qid;
775                 r->fid->qid = sf->qid;
776                 respond(r, nil);
777         }
778 }
779
780 void
781 sendproc(void *)
782 {
783         SReq *r;
784         SFid *sf;
785         int x, y;
786         char *s, *t;
787
788         threadsetname("send");
789
790         for(;;){
791                 qlock(&sreqwrlock);
792                 while(sreqwr == nil)
793                         rsleep(&writerend);
794                 r = sreqwr;
795                 sreqwr = r->next;
796                 if(sreqwr == nil) sreqlast = &sreqwr;
797                 qunlock(&sreqwrlock);
798
799                 sf = r->closefid;
800                 if(sf != nil){
801                         rlock(sf);
802                         sendpkt("bqs", SSH_FXP_CLOSE, r, sf->hand, sf->handn);
803                         runlock(sf);
804                         continue;
805                 }
806                 if(r->req == nil)
807                         sysfatal("nil request in queue");
808
809                 sf = r->req->fid->aux;
810                 switch(r->req->ifcall.type){
811                 case Tattach:
812                         rlock(sf);
813                         sendpkt("bqs", SSH_FXP_STAT, r, sf->fn, strlen(sf->fn));
814                         runlock(sf);
815                         break;
816                 case Twalk:
817                         sendpkt("bqs", SSH_FXP_STAT, r, r->req->aux, strlen(r->req->aux));
818                         break;
819                 case Topen:
820                         if((r->req->ofcall.qid.type & QTDIR) != 0){
821                                 rlock(sf);
822                                 sendpkt("bqs", SSH_FXP_OPENDIR, r, sf->fn, strlen(sf->fn));
823                                 runlock(sf);
824                         }else{
825                                 x = r->req->ifcall.mode;
826                                 y = 0;
827                                 switch(x & 3){
828                                 case OREAD: y = SSH_FXF_READ; break;
829                                 case OWRITE: y = SSH_FXF_WRITE; break;
830                                 case ORDWR: y = SSH_FXF_READ | SSH_FXF_WRITE; break;
831                                 }
832                                 if(readonly && (y & SSH_FXF_WRITE) != 0){
833                                         respond(r->req, "mounted read-only");
834                                         putsreq(r);
835                                         break;
836                                 }
837                                 if((x & OTRUNC) != 0)
838                                         y |= SSH_FXF_TRUNC;
839                                 rlock(sf);
840                                 sendpkt("bqsuu", SSH_FXP_OPEN, r, sf->fn, strlen(sf->fn), y, 0);
841                                 runlock(sf);
842                         }
843                         break;
844                 case Tcreate:
845                         rlock(sf);
846                         s = pathcat(sf->fn, r->req->ifcall.name);
847                         runlock(sf);
848                         if((r->req->ifcall.perm & DMDIR) != 0){
849                                 if(r->req->aux == nil){
850                                         r->req->aux = (void*)-1;
851                                         sendpkt("bqsuu", SSH_FXP_MKDIR, r, s, strlen(s),
852                                                 SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
853                                 }else{
854                                         r->req->aux = (void*)-2;
855                                         sendpkt("bqs", SSH_FXP_OPENDIR, r, s, strlen(s));
856                                 }
857                                 free(s);
858                                 break;
859                         }
860                         x = r->req->ifcall.mode;
861                         y = SSH_FXF_CREAT | SSH_FXF_EXCL;
862                         switch(x & 3){
863                         case OREAD: y |= SSH_FXF_READ; break;
864                         case OWRITE: y |= SSH_FXF_WRITE; break;
865                         case ORDWR: y |= SSH_FXF_READ | SSH_FXF_WRITE; break;
866                         }
867                         sendpkt("bqsuuu", SSH_FXP_OPEN, r, s, strlen(s), y,
868                                 SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
869                         free(s);
870                         break;
871                 case Tread:
872                         if((r->req->fid->qid.type & QTDIR) != 0){
873                                 wlock(sf);
874                                 if(r->req->aux == (void*)-1){
875                                         sendpkt("bqs", SSH_FXP_CLOSE, r, sf->hand, sf->handn);
876                                         free(sf->hand);
877                                         sf->hand = nil;
878                                         sf->handn = 0;
879                                         sf->direof = 0;
880                                         sf->dirreads = 0;
881                                 }else if(r->req->aux == (void*)-2){
882                                         sendpkt("bqs", SSH_FXP_OPENDIR, r, sf->fn, strlen(sf->fn));
883                                 }else{
884                                         sf->dirreads++;
885                                         sendpkt("bqs", SSH_FXP_READDIR, r, sf->hand, sf->handn);
886                                 }
887                                 wunlock(sf);
888                         }else{
889                                 rlock(sf);
890                                 sendpkt("bqsvuu", SSH_FXP_READ, r, sf->hand, sf->handn,
891                                         r->req->ifcall.offset, r->req->ifcall.count);
892                                 runlock(sf);
893                         }
894                         break;
895                 case Twrite:
896                         x = r->req->ifcall.count - r->req->ofcall.count;
897                         if(x >= MAXWRITE) x = MAXWRITE;
898                         r->req->ofcall.offset = x;
899                         rlock(sf);
900                         sendpkt("bqsvs", SSH_FXP_WRITE, r, sf->hand, sf->handn,
901                                 r->req->ifcall.offset + r->req->ofcall.count,
902                                 r->req->ifcall.data + r->req->ofcall.count,
903                                 x);
904                         runlock(sf);
905                         break;
906                 case Tstat:
907                         rlock(sf);
908                         r->req->d.name = finalelem(sf->fn);
909                         r->req->d.qid = sf->qid;
910                         if(sf->handn > 0 && (sf->qid.type & QTDIR) == 0)
911                                 sendpkt("bqs", SSH_FXP_FSTAT, r, sf->hand, sf->handn);
912                         else
913                                 sendpkt("bqs", SSH_FXP_STAT, r, sf->fn, strlen(sf->fn));
914                         runlock(sf);
915                         break;
916                 case Twstat:
917                         if(r->req->aux == (void*)-1){
918                                 rlock(sf);
919                                 s = parentdir(sf->fn);
920                                 t = pathcat(s, r->req->d.name);
921                                 r->req->aux = t;
922                                 sendpkt("bqss", SSH_FXP_RENAME, r, sf->fn, strlen(sf->fn), t, strlen(t));
923                                 runlock(sf);
924                                 free(s);
925                                 break;
926                         }
927                         x = dir2attrib(&r->req->d, (uchar **) &s);
928                         if(x < 0){
929                                 responderror(r->req);
930                                 putsreq(r);
931                                 free(s);
932                                 break;
933                         }
934                         rlock(sf);
935                         if(sf->handn > 0)
936                                 sendpkt("bqs[", SSH_FXP_FSETSTAT, r, sf->hand, sf->handn, s, x);
937                         else
938                                 sendpkt("bqs[", SSH_FXP_SETSTAT, r, sf->fn, strlen(sf->fn), s, x);
939                         runlock(sf);
940                         free(s);
941                         break;
942                 case Tremove:
943                         rlock(sf);
944                         if((sf->qid.type & QTDIR) != 0)
945                                 sendpkt("bqs", SSH_FXP_RMDIR, r, sf->fn, strlen(sf->fn));
946                         else
947                                 sendpkt("bqs", SSH_FXP_REMOVE, r, sf->fn, strlen(sf->fn));
948                         runlock(sf);
949                         break;
950                 default:
951                         fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
952                         respond(r->req, "phase error");
953                         putsreq(r);
954                 }
955         }
956 }
957
958 void
959 recvproc(void *)
960 {
961         static char ebuf[256];
962
963         SReq *r;
964         SFid *sf;
965         int t, id;
966         u32int code;
967         char *msg, *lang, *hand, *s;
968         int msgn, langn, handn;
969         int okresp;
970         char *e;
971         
972         threadsetname("recv");
973         
974         for(;;){
975                 e = "phase error";
976                 switch(t = recvpkt()){
977                 case SSH_FXP_STATUS:
978                 case SSH_FXP_HANDLE:
979                 case SSH_FXP_DATA:
980                 case SSH_FXP_NAME:
981                 case SSH_FXP_ATTRS:
982                         break;
983                 default:
984                         fprint(2, "sshfs: received unexpected packet of type %Σ\n", t);
985                         continue;
986                 }
987                 id = GET4(rxpkt + 1);
988                 if(id >= MAXREQID){
989                         fprint(2, "sshfs: received %Σ response with id out of range, %d > %d\n", t, id, MAXREQID);
990                         continue;
991                 }
992                 qlock(&sreqidlock);
993                 r = sreqrd[id];
994                 if(r != nil){
995                         sreqrd[id] = nil;
996                         rwakeup(&sreqidrend);
997                 }
998                 qunlock(&sreqidlock);
999                 if(r == nil){
1000                         fprint(2, "sshfs: received %Σ response to non-existent request (req id = %d)\n", t, id);
1001                         continue;
1002                 }
1003                 if(r->closefid != nil){
1004                         putsfid(r->closefid);
1005                         putsreq(r);
1006                         continue;
1007                 }
1008                 if(r->req == nil)
1009                         sysfatal("recvproc: r->req == nil");
1010
1011                 sf = r->req->fid->aux;
1012                 okresp = rxlen >= 9 && t == SSH_FXP_STATUS && GET4(rxpkt+5) == SSH_FX_OK;
1013                 switch(r->req->ifcall.type){
1014                 case Tattach:
1015                         if(t != SSH_FXP_ATTRS) goto common;
1016                         if(attrfixupqid(&r->req->ofcall.qid) < 0)
1017                                 goto garbage;
1018                         r->req->aux = (void*)-1;
1019                         if((r->req->ofcall.qid.type & QTDIR) == 0)
1020                                 respond(r->req, "not a directory");
1021                         else
1022                                 sshfsattach(r->req);
1023                         break;
1024                 case Twalk:
1025                         if(t != SSH_FXP_ATTRS) goto common;
1026                         if(r->req->ofcall.nwqid <= 0
1027                         || attrfixupqid(&r->req->ofcall.wqid[r->req->ofcall.nwqid - 1]) < 0)
1028                                 goto garbage;
1029                         walkprocess(r->req, nil);
1030                         break;
1031                 case Tcreate:
1032                         if(okresp && r->req->aux == (void*)-1){
1033                                 submitreq(r->req);
1034                                 break;
1035                         }
1036                         /* wet floor */
1037                 case Topen: opendir:
1038                         if(t != SSH_FXP_HANDLE) goto common;
1039                         if(unpack(rxpkt, rxlen, "_____s", &hand, &handn) < 0) goto garbage;
1040                         wlock(sf);
1041                         sf->handn = handn;
1042                         sf->hand = emalloc9p(sf->handn);
1043                         memcpy(sf->hand, hand, sf->handn);
1044                         if(r->req->ifcall.type == Tcreate){
1045                                 s = sf->fn;
1046                                 sf->fn = pathcat(s, r->req->ifcall.name);
1047                                 free(s);
1048                                 sf->qid = (Qid){qidcalc(sf->fn), 0, (r->req->ifcall.perm & DMDIR) != 0 ? QTDIR : 0};
1049                                 r->req->ofcall.qid = sf->qid;
1050                                 r->req->fid->qid = sf->qid;
1051                         }
1052                         wunlock(sf);
1053                         if(r->req->ifcall.type == Tread){
1054                                 r->req->aux = nil;
1055                                 readprocess(r->req);
1056                         }else
1057                                 respond(r->req, nil);
1058                         break;
1059                 case Tread:
1060                         if((r->req->fid->qid.type & QTDIR) != 0){
1061                                 if(r->req->aux == (void*)-1){
1062                                         if(t != SSH_FXP_STATUS) goto common;
1063                                         /* reopen even if close failed */
1064                                         r->req->aux = (void*)-2;
1065                                         submitreq(r->req);
1066                                 }else if(r->req->aux == (void*)-2)
1067                                         goto opendir;
1068                                 else{
1069                                         if(t != SSH_FXP_NAME) goto common;
1070                                         if(parsedir(sf) < 0) goto garbage;
1071                                         readprocess(r->req);
1072                                 }
1073                                 break;
1074                         }
1075                         if(t != SSH_FXP_DATA) goto common;
1076                         if(unpack(rxpkt, rxlen, "_____s", &msg, &msgn) < 0) goto garbage;
1077                         if(msgn > r->req->ifcall.count) msgn = r->req->ifcall.count;
1078                         r->req->ofcall.count = msgn;
1079                         memcpy(r->req->ofcall.data, msg, msgn);
1080                         respond(r->req, nil);
1081                         break;
1082                 case Twrite:
1083                         if(t != SSH_FXP_STATUS) goto common;
1084                         if(okresp){
1085                                 r->req->ofcall.count += r->req->ofcall.offset;
1086                                 if(r->req->ofcall.count == r->req->ifcall.count)
1087                                         respond(r->req, nil);
1088                                 else
1089                                         submitreq(r->req);
1090                                 break;
1091                         }
1092                         if(r->req->ofcall.count == 0) goto common;
1093                         respond(r->req, nil);
1094                         break;
1095                 case Tstat:
1096                         if(t != SSH_FXP_ATTRS) goto common;
1097                         if(attrib2dir(rxpkt + 5, rxpkt + rxlen, &r->req->d) < 0) goto garbage;
1098                         respond(r->req, nil);
1099                         break;
1100                 case Twstat:
1101                         if(!okresp) goto common;
1102                         if(!r->req->d.name[0]){
1103                                 respond(r->req, nil);
1104                                 break;
1105                         }
1106                         if(r->req->aux == nil){
1107                                 r->req->aux = (void*)-1;
1108                                 submitreq(r->req);
1109                         }else{
1110                                 wlock(sf);
1111                                 free(sf->fn);
1112                                 sf->fn = r->req->aux;
1113                                 wunlock(sf);
1114                                 respond(r->req, nil);
1115                         }
1116                         break;
1117                 case Tremove:
1118                         goto common;
1119                 default:
1120                         fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
1121                         respond(r->req, "phase error");
1122                 }
1123                 putsreq(r);
1124                 continue;
1125                 
1126         common:
1127                 switch(t){
1128                 case SSH_FXP_STATUS:
1129                         if(unpack(rxpkt, rxlen, "_____uss", &code, &msg, &msgn, &lang, &langn) < 0){
1130         garbage:
1131                                 fprint(2, "sshfs: garbled packet in response to 9p request %F\n", &r->req->ifcall);
1132                                 break;
1133                         }
1134                         if(code == SSH_FX_OK)
1135                                 e = nil;
1136                         else if(code == SSH_FX_EOF && r->req->ifcall.type == Tread){
1137                                 if((r->req->fid->qid.type & QTDIR) != 0){
1138                                         wlock(sf);
1139                                         sf->direof = 1;
1140                                         wunlock(sf);
1141                                         readprocess(r->req);
1142                                         putsreq(r);
1143                                         continue;
1144                                 }
1145                                 r->req->ofcall.count = 0;
1146                                 e = nil;
1147                         }else if(msgn > 0){
1148                                 e = msg;
1149                                 e[msgn] = 0;
1150                         }else if(code < nelem(errors))
1151                                 e = errors[code];
1152                         else{
1153                                 snprint(ebuf, sizeof(ebuf), "error code %d", code);
1154                                 e = ebuf;
1155                         }
1156                         break;
1157                 default:
1158                         fprint(2, "sshfs: received unexpected packet %Σ for 9p request %F\n", t, &r->req->ifcall);
1159                 }
1160                 if(r->req->ifcall.type == Twalk)
1161                         walkprocess(r->req, e);
1162                 else
1163                         respond(r->req, e);
1164                 putsreq(r);
1165                 continue;
1166         }
1167 }
1168
1169 void
1170 sshfswalk(Req *r)
1171 {
1172         SFid *s, *t;
1173         char *p, *q;
1174         int i;
1175
1176         if(r->fid != r->newfid){
1177                 r->newfid->qid = r->fid->qid;
1178                 s = r->fid->aux;
1179                 t = emalloc9p(sizeof(SFid));
1180                 t->fn = estrdup9p(s->fn);
1181                 t->qid = s->qid;
1182                 r->newfid->aux = t;
1183         }else
1184                 t = r->fid->aux;
1185         if(r->ifcall.nwname == 0){
1186                 respond(r, nil);
1187                 return;
1188         }
1189         p = estrdup9p(t->fn);
1190         for(i = 0; i < r->ifcall.nwname; i++){
1191                 q = pathcat(p, r->ifcall.wname[i]);
1192                 free(p);
1193                 p = q;
1194                 r->ofcall.wqid[i] = (Qid){qidcalc(p), 0, QTDIR};
1195         }
1196         r->ofcall.nwqid = r->ifcall.nwname;
1197         r->aux = p;
1198         submitreq(r);
1199 }
1200
1201 void
1202 sshfsdestroyfid(Fid *f)
1203 {
1204         SFid *sf;
1205         SReq *sr;
1206
1207         sf = f->aux;
1208         if(sf == nil)
1209                 return;
1210         if(sf->hand != nil){
1211                 sr = emalloc9p(sizeof(SReq));
1212                 sr->closefid = sf;
1213                 submitsreq(sr);
1214         }else
1215                 putsfid(sf);
1216 }
1217
1218 void
1219 sshfsdestroyreq(Req *r)
1220 {
1221         if(r->ifcall.type == Twalk)
1222                 free(r->aux);
1223 }
1224
1225 void
1226 sshfsstart(Srv *)
1227 {
1228         proccreate(sendproc, nil, mainstacksize);
1229         proccreate(recvproc, nil, mainstacksize);
1230 }
1231
1232 void
1233 sshfsend(Srv *)
1234 {
1235         dprint("sshfs: ending\n");
1236         threadexitsall(nil);
1237 }
1238
1239 Srv sshfssrv = {
1240         .start sshfsstart,
1241         .attach sshfsattach,
1242         .walk sshfswalk,
1243         .open submitreq,
1244         .create submitreq,
1245         .read sshfsread,
1246         .write submitreq,
1247         .stat submitreq,
1248         .wstat submitreq,
1249         .remove submitreq,
1250         .destroyfid sshfsdestroyfid,
1251         .destroyreq sshfsdestroyreq,
1252         .end sshfsend,
1253 };
1254
1255 char *
1256 readfile(char *fn)
1257 {
1258         char *hand, *dat;
1259         int handn, datn;
1260         u32int code;
1261         char *p;
1262         int off;
1263         
1264         if(fn == nil) return nil;
1265         sendpkt("busuu", SSH_FXP_OPEN, 0, fn, strlen(fn), SSH_FXF_READ, 0);
1266         if(recvpkt() != SSH_FXP_HANDLE) return nil;
1267         if(unpack(rxpkt, rxlen, "_____s", &dat, &handn) < 0) return nil;
1268         hand = emalloc9p(handn);
1269         memcpy(hand, dat, handn);
1270         off = 0;
1271         p = nil;
1272         for(;;){
1273                 sendpkt("busvu", SSH_FXP_READ, 0, hand, handn, (uvlong)off, MAXWRITE);
1274                 switch(recvpkt()){
1275                 case SSH_FXP_STATUS:
1276                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto err;
1277                         if(code == SSH_FX_EOF) goto out;
1278                 default:
1279                         goto err;
1280                 case SSH_FXP_DATA:
1281                         if(unpack(rxpkt, rxlen, "_____s", &dat, &datn) < 0) goto err;
1282                         break;
1283                 }
1284                 p = erealloc9p(p, off + datn + 1);
1285                 memcpy(p + off, dat, datn);
1286                 off += datn;
1287                 p[off] = 0;
1288         }
1289 err:
1290         p = nil;
1291 out:
1292         sendpkt("bus", SSH_FXP_CLOSE, 0, hand, handn);
1293         free(hand);
1294         recvpkt();
1295         return p;
1296 }
1297
1298 void
1299 passwdparse(IDEnt **tab, char *s)
1300 {
1301         IDEnt *e, **b;
1302         char *p, *n;
1303         int id;
1304
1305         if(s == nil)
1306                 return;
1307         for(p = s;;){
1308                 n = p;
1309                 p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
1310                 *p = 0;
1311                 p = strpbrk(p+1, ":\n");
1312                 p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
1313                 id = strtol(p+1, &p, 10);
1314                 p = strchr(p, '\n');
1315                 if(p == nil) break;
1316                 p++;
1317                 e = emalloc9p(sizeof(IDEnt));
1318                 e->name = estrdup9p(n);
1319                 e->id = id;
1320                 b = &tab[((ulong)e->id) % HASH];
1321                 e->next = *b;
1322                 *b = e;
1323         }
1324         free(s);
1325 }
1326
1327 int pfd[2];
1328 int sshargc;
1329 char **sshargv;
1330
1331 void
1332 startssh(void *)
1333 {
1334         char *f;
1335
1336         close(pfd[0]);
1337         dup(pfd[1], 0);
1338         dup(pfd[1], 1);
1339         close(pfd[1]);
1340         if(strncmp(sshargv[0], "./", 2) != 0)
1341                 f = smprint("/bin/%s", sshargv[0]);
1342         else
1343                 f = sshargv[0];
1344         procexec(nil, f, sshargv);
1345         sysfatal("exec: %r");
1346 }
1347
1348 void
1349 usage(void)
1350 {
1351         static char *common = "[-abdRUGM] [-s service] [-m mtpt] [-r root] [-u uidfile] [-g gidfile]";
1352         fprint(2, "usage: %s %s [-- ssh-options] [user@]host\n", argv0, common);
1353         fprint(2, "       %s %s -c cmdline\n", argv0, common);
1354         fprint(2, "       %s %s -p\n", argv0, common);
1355         threadexits("usage");
1356 }
1357
1358 void
1359 threadmain(int argc, char **argv)
1360 {
1361         u32int x;
1362         static int pflag, cflag;
1363         static char *svc, *mtpt;
1364         static int mflag;
1365         static char *uidfile, *gidfile;
1366         
1367         fmtinstall(L'Σ', fxpfmt);
1368         
1369         mtpt = "/n/ssh";
1370         uidfile = "/etc/passwd";
1371         gidfile = "/etc/group";
1372         ARGBEGIN{
1373         case 'R': readonly++; break;
1374         case 'd': debug++; chatty9p++; break;
1375         case 'p': pflag++; break;
1376         case 'c': cflag++; break;
1377         case 's': svc = EARGF(usage()); break;
1378         case 'a': mflag |= MAFTER; break;
1379         case 'b': mflag |= MBEFORE; break;
1380         case 'm': mtpt = EARGF(usage()); break;
1381         case 'M': mtpt = nil; break;
1382         case 'u': uidfile = EARGF(usage()); break;
1383         case 'U': uidfile = nil; break;
1384         case 'g': gidfile = EARGF(usage()); break;
1385         case 'G': gidfile = nil; break;
1386         case 'r': root = EARGF(usage()); break;
1387         default: usage();
1388         }ARGEND;
1389         
1390         if(readonly){
1391                 sshfssrv.create = nil;
1392                 sshfssrv.write = nil;
1393                 sshfssrv.wstat = nil;
1394                 sshfssrv.remove = nil;
1395         }
1396         
1397         if(pflag){
1398                 rdfd = 0;
1399                 wrfd = 1;
1400         }else{
1401                 if(argc == 0) usage();
1402                 if(cflag){
1403                         sshargc = argc;
1404                         sshargv = argv;
1405                 }else{
1406                         sshargc = argc + 2;
1407                         sshargv = emalloc9p(sizeof(char *) * (sshargc + 1));
1408                         sshargv[0] = "ssh";
1409                         memcpy(sshargv + 1, argv, argc * sizeof(char *));
1410                         sshargv[sshargc - 1] = "#sftp";
1411                 }
1412                 pipe(pfd);
1413                 rdfd = wrfd = pfd[0];
1414                 procrfork(startssh, nil, mainstacksize, RFFDG|RFNOTEG|RFNAMEG);
1415                 close(pfd[1]);
1416         }
1417
1418         sendpkt("bu", SSH_FXP_INIT, VERSION);
1419         if(recvpkt() != SSH_FXP_VERSION || unpack(rxpkt, rxlen, "_u", &x) < 0) sysfatal("received garbage");
1420         if(x != VERSION) sysfatal("server replied with incompatible version %d", x);
1421         
1422         passwdparse(uidtab, readfile(uidfile));
1423         passwdparse(gidtab, readfile(gidfile));
1424         
1425         threadpostmountsrv(&sshfssrv, svc, mtpt, MCREATE | mflag);
1426
1427         threadexits(nil);
1428 }