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