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