]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/sshfs.c
sshfs: check correctly for directory bits; calculate parent directory correctly
[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         else strcpy(q, ".");
454         return q;
455 }
456
457 char *
458 finalelem(char *p)
459 {
460         char *q;
461         
462         q = strrchr(p, '/');
463         if(q == nil) return strdup(p);
464         return strdup(q+1);
465 }
466
467 u64int
468 qidcalc(char *c)
469 {
470         uchar dig[SHA1dlen];
471
472         sha1((uchar *) c, strlen(c), dig, nil);
473         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;
474 }
475
476 void
477 walkprocess(Req *r, int isdir, char *e)
478 {
479         char *p;
480         SFid *sf;
481         
482         sf = r->newfid->aux;
483         if(e != nil){
484                 r->ofcall.nwqid--;
485                 if(r->ofcall.nwqid == 0){
486                         respond(r, e);
487                         return;
488                 }
489                 p = r->aux;
490                 r->aux = parentdir(p);
491                 free(p);
492                 submitreq(r);
493         }else{
494                 assert(r->ofcall.nwqid > 0);
495                 if(!isdir)
496                         r->ofcall.wqid[r->ofcall.nwqid - 1].type = 0;
497                 wlock(sf);
498                 free(sf->fn);
499                 sf->fn = r->aux;
500                 r->aux = nil;
501                 sf->qid = r->ofcall.wqid[r->ofcall.nwqid - 1];
502                 wunlock(sf);
503                 respond(r, nil);
504         }
505 }
506
507 int
508 attrib2dir(uchar *p0, uchar *ep, Dir *d)
509 {
510         uchar *p;
511         int i, rc, extn, extvn;
512         u32int flags, uid, gid, perm, next;
513         uchar *exts,  *extvs;
514         
515         p = p0;
516         if(p + 4 > ep) return -1;
517         flags = GET4(p), p += 4;
518         if((flags & SSH_FILEXFER_ATTR_SIZE) != 0){
519                 rc = unpack(p, ep - p, "v", &d->length); if(rc < 0) return -1; p += rc;
520         }
521         if((flags & SSH_FILEXFER_ATTR_UIDGID) != 0){
522                 rc = unpack(p, ep - p, "uu", &uid, &gid); if(rc < 0) return -1; p += rc;
523                 d->uid = idlookup(uidtab, uid);
524                 d->gid = idlookup(gidtab, gid);
525         }else{
526                 d->uid = strdup("sshfs");
527                 d->gid = strdup("sshfs");
528         }
529         d->muid = strdup(d->uid);
530         if((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0){
531                 rc = unpack(p, ep - p, "u", &perm); if(rc < 0) return -1; p += rc;
532                 d->mode = perm & 0777;
533                 if((perm & 0170000) == 0040000) d->mode |= DMDIR;
534         }
535         d->qid.type = d->mode >> 24;
536         if((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0){
537                 rc = unpack(p, ep - p, "uu", &d->atime, &d->mtime); if(rc < 0) return -1; p += rc;
538         }
539         if((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0){
540                 rc = unpack(p, ep - p, "u", &next); if(rc < 0) return -1; p += rc;
541                 for(i = 0; i < next; i++){
542                         rc = unpack(p, ep - p, "ss", &exts, &extn, &extvs, &extvn); if(rc < 0) return -1; p += rc;
543                         exts[extn] = extvs[extvn] = 0;
544                 }
545         }
546         return p - p0;
547 }
548
549 int
550 dir2attrib(Dir *d, uchar **rp)
551 {
552         int rc;
553         uchar *r, *p, *e;
554         u32int fl;
555         int uid, gid;
556         
557         werrstr("phase error");
558         r = emalloc9p(MAXATTRIB);
559         e = r + MAXATTRIB;
560         fl = 0;
561         p = r + 4;
562         if(d->length != (uvlong)-1){
563                 fl |= SSH_FILEXFER_ATTR_SIZE;
564                 rc = pack(p, e - p, "v", d->length); if(rc < 0) return -1; p += rc;
565         }
566         if(d->uid != nil && *d->uid != 0 || d->gid != nil && *d->gid != 0){
567                 /* FIXME: sending -1 for "don't change" works with openssh, but violates the spec */
568                 if(d->uid != nil && *d->uid != 0){
569                         uid = namelookup(uidtab, d->uid);
570                         if(uid == -1)
571                                 return -1;
572                 }else
573                         uid = -1;
574                 if(d->gid != nil && *d->gid != 0){
575                         gid = namelookup(gidtab, d->gid);
576                         if(gid == -1)
577                                 return -1;
578                 }else
579                         gid = -1;
580                 fl |= SSH_FILEXFER_ATTR_UIDGID;
581                 rc = pack(p, e - p, "uu", uid, gid); if(rc < 0) return -1; p += rc;
582         }
583         if(d->mode != (ulong)-1){
584                 fl |= SSH_FILEXFER_ATTR_PERMISSIONS;
585                 rc = pack(p, e - p, "u", d->mode); if(rc < 0) return -1; p += rc;
586         }
587         if(d->atime != (ulong)-1 || d->mtime != (ulong)-1){
588                 /* FIXME: see above */
589                 fl |= SSH_FILEXFER_ATTR_ACMODTIME;
590                 rc = pack(p, e - p, "uu", d->atime, d->mtime); if(rc < 0) return -1; p += rc;
591         }
592         PUT4(r, fl);
593         *rp = r;
594         return p - r;
595 }
596
597 int
598 attribisdir(char *fn)
599 {
600         u32int code;
601         uchar *p;
602         
603         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) return -1;
604         if((code & 4) == 0){
605                 fprint(2, "sshfs: can't determine if %s is a directory\n", fn);
606                 return 1;
607         }
608         p = rxpkt + 9;
609         if(code & 1) p += 8;
610         if(code & 2) p += 8;
611         if(p + 4 > rxpkt + rxlen) return -1;
612         return (GET4(p) & 0170000) == 0040000;
613 }
614
615 int
616 parsedir(SFid *sf)
617 {
618         int i, rc;
619         Dir *d;
620         u32int c;
621         uchar *p, *ep;
622         char *fn, *ln;
623         int fns, lns;
624         char *s;
625
626         if(unpack(rxpkt, rxlen, "_____u", &c) < 0) return -1;
627         wlock(sf);
628         freedir(sf);
629         sf->dirent = emalloc9p(c * sizeof(Dir));
630         d = sf->dirent;
631         p = rxpkt + 9;
632         ep = rxpkt + rxlen;
633         for(i = 0; i < c; i++){
634                 rc = unpack(p, ep - p, "ss", &fn, &fns, &ln, &lns); if(rc < 0) goto err; p += rc;
635                 memset(d, 0, sizeof(Dir));
636                 rc = attrib2dir(p, ep, d); if(rc < 0) goto err; p += rc;
637                 if(fn[0] == '.' && (fns == 1 || fns == 2 && fn[1] == '.')){
638                         free(d->uid);
639                         free(d->gid);
640                         free(d->muid);
641                         continue;
642                 }
643                 d->name = emalloc9p(fns + 1);
644                 memcpy(d->name, fn, fns);
645                 s = pathcat(sf->fn, d->name);
646                 d->qid.path = qidcalc(s);
647                 free(s);
648                 sf->ndirent++;
649                 d++;
650         }
651         wunlock(sf);
652         return 0;
653 err:
654         wunlock(sf);
655         return -1;
656 }
657
658
659 void
660 readprocess(Req *r)
661 {
662         int i;
663         uchar *p, *ep;
664         uint rv;
665         SFid *sf;
666         
667         sf = r->fid->aux;
668         wlock(sf);
669         if(sf->direof){
670                 wunlock(sf);
671                 respond(r, nil);
672                 return;
673         }
674         i = sf->dirpos;
675         p = (uchar*)r->ofcall.data + r->ofcall.count;
676         ep = (uchar*)r->ofcall.data + r->ifcall.count;
677         rv = ep - p;
678         while(p < ep){
679                 if(i >= sf->ndirent)
680                         break;
681                 rv = convD2M(&sf->dirent[i], p, ep-p);
682                 if(rv <= BIT16SZ)
683                         break;
684                 p += rv;
685                 i++;
686         }
687         sf->dirpos = i;
688         if(i >= sf->ndirent)
689                 freedir(sf);
690         wunlock(sf);
691         r->ofcall.count = p - (uchar*)r->ofcall.data;
692         if(rv <= BIT16SZ)
693                 respond(r, nil);
694         else
695                 submitreq(r);
696 }
697
698 void
699 sshfsread(Req *r)
700 {
701         SFid *sf;
702
703         if((r->fid->qid.type & QTDIR) == 0){
704                 submitreq(r);
705                 return;
706         }
707         sf = r->fid->aux;       
708         if(r->ifcall.offset == 0){
709                 wlock(sf);
710                 freedir(sf);
711                 if(sf->dirreads > 0){
712                         r->aux = (void*)-1;
713                         submitreq(r);
714                         wunlock(sf);
715                         return;
716                 }
717                 wunlock(sf);
718         }
719         readprocess(r);
720 }
721
722 void
723 sshfsattach(Req *r)
724 {
725         SFid *sf;
726
727         if(r->ifcall.aname != nil && *r->ifcall.aname != 0 && r->aux == nil){
728                 submitreq(r);
729                 return;
730         }
731         sf = emalloc9p(sizeof(SFid));
732         if(r->ifcall.aname != nil && *r->ifcall.aname != 0)
733                 sf->fn = strdup(r->ifcall.aname);
734         else
735                 sf->fn = strdup(".");
736         sf->qid = (Qid){qidcalc(sf->fn), 0, QTDIR};
737         r->ofcall.qid = sf->qid;
738         r->fid->qid = sf->qid;
739         r->fid->aux = sf;
740         respond(r, nil);
741 }
742
743 void
744 sendproc(void *)
745 {
746         SReq *r;
747         SFid *sf;
748         int i;
749         int x, y;
750         char *s, *t;
751
752         threadsetname("send");
753
754         for(;;){
755                 qlock(&sreqwrlock);
756                 while(sreqwr == nil)
757                         rsleep(&writerend);
758                 r = sreqwr;
759                 sreqwr = r->next;
760                 if(sreqwr == nil) sreqlast = &sreqwr;
761                 qunlock(&sreqwrlock);
762                 
763                 qlock(&sreqidlock);
764         idagain:
765                 for(i = 0; i < MAXREQID; i++)
766                         if(sreqrd[i] == nil){
767                                 sreqrd[i] = r;
768                                 r->reqid = i;
769                                 break;
770                         }
771                 if(i == MAXREQID){
772                         rsleep(&sreqidrend);
773                         goto idagain;
774                 }
775                 qunlock(&sreqidlock);
776
777                 if(r->closefid != nil){
778                         sendpkt("bus", SSH_FXP_CLOSE, r->reqid, r->closefid->hand, r->closefid->handn);
779                         continue;
780                 }
781                 if(r->req == nil)
782                         sysfatal("nil request in queue");
783
784                 sf = r->req->fid != nil ? r->req->fid->aux : nil;
785                 switch(r->req->ifcall.type){
786                 case Tattach:
787                         sendpkt("bus", SSH_FXP_STAT, r->reqid, r->req->ifcall.aname, strlen(r->req->ifcall.aname));
788                         break;
789                 case Twalk:
790                         sendpkt("bus", SSH_FXP_STAT, r->reqid, r->req->aux, strlen(r->req->aux));
791                         break;
792                 case Topen:
793                         rlock(sf);
794                         if((r->req->ofcall.qid.type & QTDIR) != 0)
795                                 sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, sf->fn, strlen(sf->fn));
796                         else{
797                                 x = r->req->ifcall.mode;
798                                 y = 0;
799                                 switch(x & 3){
800                                 case OREAD: y = SSH_FXF_READ; break;
801                                 case OWRITE: y = SSH_FXF_WRITE; break;
802                                 case ORDWR: y = SSH_FXF_READ | SSH_FXF_WRITE; break;
803                                 }
804                                 if(readonly && (y & SSH_FXF_WRITE) != 0){
805                                         respond(r->req, "mounted read-only");
806                                         runlock(sf);
807                                         putsreq(r);
808                                         break;
809                                 }
810                                 if((x & OTRUNC) != 0)
811                                         y |= SSH_FXF_TRUNC;
812                                 sendpkt("busuu", SSH_FXP_OPEN, r->reqid, sf->fn, strlen(sf->fn), y, 0);
813                         }
814                         runlock(sf);
815                         break;
816                 case Tcreate:
817                         rlock(sf);
818                         s = pathcat(sf->fn, r->req->ifcall.name);
819                         runlock(sf);
820                         if((r->req->ifcall.perm & DMDIR) != 0){
821                                 if(r->req->aux == nil){
822                                         sendpkt("busuu", SSH_FXP_MKDIR, r->reqid, s, strlen(s),
823                                                 SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
824                                         r->req->aux = (void*)-1;
825                                 }else{
826                                         sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, s, strlen(s));
827                                         r->req->aux = (void*)-2;
828                                 }
829                                 free(s);
830                                 break;
831                         }
832                         x = r->req->ifcall.mode;
833                         y = SSH_FXF_CREAT | SSH_FXF_EXCL;
834                         switch(x & 3){
835                         case OREAD: y |= SSH_FXF_READ; break;
836                         case OWRITE: y |= SSH_FXF_WRITE; break;
837                         case ORDWR: y |= SSH_FXF_READ | SSH_FXF_WRITE; break;
838                         }
839                         sendpkt("busuuu", SSH_FXP_OPEN, r->reqid, s, strlen(s), y,
840                                 SSH_FILEXFER_ATTR_PERMISSIONS, r->req->ifcall.perm & 0777);
841                         free(s);
842                         break;
843                 case Tread:
844                         if((r->req->fid->qid.type & QTDIR) != 0){
845                                 wlock(sf);
846                                 if(r->req->aux == (void*)-1){
847                                         sendpkt("bus", SSH_FXP_CLOSE, r->reqid, sf->hand, sf->handn);
848                                         free(sf->hand);
849                                         sf->hand = nil;
850                                         sf->handn = 0;
851                                         sf->direof = 0;
852                                         sf->dirreads = 0;
853                                 }else if(r->req->aux == (void*)-2){
854                                         sendpkt("bus", SSH_FXP_OPENDIR, r->reqid, sf->fn, strlen(sf->fn));
855                                 }else{
856                                         sendpkt("bus", SSH_FXP_READDIR, r->reqid, sf->hand, sf->handn);
857                                         sf->dirreads++;
858                                 }
859                                 wunlock(sf);
860                         }else{
861                                 rlock(sf);
862                                 sendpkt("busvuu", SSH_FXP_READ, r->reqid, sf->hand, sf->handn,
863                                         r->req->ifcall.offset, r->req->ifcall.count);
864                                 runlock(sf);
865                         }
866                         break;
867                 case Twrite:
868                         x = r->req->ifcall.count - r->req->ofcall.count;
869                         if(x >= MAXWRITE) x = MAXWRITE;
870                         rlock(sf);
871                         sendpkt("busvs", SSH_FXP_WRITE, r->reqid, sf->hand, sf->handn,
872                                 r->req->ifcall.offset + r->req->ofcall.count,
873                                 r->req->ifcall.data + r->req->ofcall.count,
874                                 x);
875                         runlock(sf);
876                         r->req->ofcall.offset = x;
877                         break;
878                 case Tstat:
879                         rlock(sf);
880                         r->req->d.name = finalelem(sf->fn);
881                         r->req->d.qid = sf->qid;
882                         if(sf->handn > 0 && (sf->qid.type & QTDIR) == 0)
883                                 sendpkt("bus", SSH_FXP_FSTAT, r->reqid, sf->hand, sf->handn);
884                         else
885                                 sendpkt("bus", SSH_FXP_STAT, r->reqid, sf->fn, strlen(sf->fn));
886                         runlock(sf);
887                         break;
888                 case Twstat:
889                         if(r->req->aux == (void *) -1){
890                                 rlock(sf);
891                                 s = parentdir(sf->fn);
892                                 t = pathcat(s, r->req->d.name);
893                                 sendpkt("buss", SSH_FXP_RENAME, r->reqid, sf->fn, strlen(sf->fn), t, strlen(t));
894                                 free(s);
895                                 r->req->aux = t;
896                                 runlock(sf);
897                                 break;
898                         }
899                         x = dir2attrib(&r->req->d, (uchar **) &s);
900                         if(x < 0){
901                                 responderror(r->req);
902                                 putsreq(r);
903                                 break;
904                         }
905                         rlock(sf);
906                         if(sf->handn > 0)
907                                 sendpkt("bus[", SSH_FXP_FSETSTAT, r->reqid, sf->hand, sf->handn, s, x);
908                         else
909                                 sendpkt("bus[", SSH_FXP_SETSTAT, r->reqid, sf->fn, strlen(sf->fn), s, x);
910                         runlock(sf);
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, rc;
936         u32int code;
937         char *msg, *lang, *hand;
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                         rc = attribisdir(r->req->ifcall.aname);
987                         r->req->aux = (void*)-1;
988                         if(rc < 0)
989                                 goto garbage;
990                         if(rc == 0)
991                                 respond(r->req, "not a directory");
992                         else
993                                 sshfsattach(r->req);
994                         break;
995                 case Twalk:
996                         if(t != SSH_FXP_ATTRS) goto common;
997                         rc = attribisdir(((SFid*)r->req->fid)->fn);
998                         if(rc < 0) goto garbage;
999                         walkprocess(r->req, rc, 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                         wunlock(sf);
1015                         if(r->req->ifcall.type == Tread){
1016                                 r->req->aux = nil;
1017                                 readprocess(r->req);
1018                         }else
1019                                 respond(r->req, nil);
1020                         break;
1021                 case Tread:
1022                         if((r->req->fid->qid.type & QTDIR) != 0){
1023                                 if(r->req->aux == (void*)-1){
1024                                         if(t != SSH_FXP_STATUS) goto common;
1025                                         /* reopen even if close failed */
1026                                         r->req->aux = (void*)-2;
1027                                         submitreq(r->req);
1028                                 }else if(r->req->aux == (void*)-2)
1029                                         goto opendir;
1030                                 else{
1031                                         if(t != SSH_FXP_NAME) goto common;
1032                                         if(parsedir(sf) < 0) goto garbage;
1033                                         readprocess(r->req);
1034                                 }
1035                                 break;
1036                         }
1037                         if(t != SSH_FXP_DATA) goto common;
1038                         if(unpack(rxpkt, rxlen, "_____s", &msg, &msgn) < 0) goto garbage;
1039                         if(msgn > r->req->ifcall.count) msgn = r->req->ifcall.count;
1040                         r->req->ofcall.count = msgn;
1041                         memcpy(r->req->ofcall.data, msg, msgn);
1042                         respond(r->req, nil);
1043                         break;
1044                 case Twrite:
1045                         if(t != SSH_FXP_STATUS) goto common;
1046                         if(okresp){
1047                                 r->req->ofcall.count += r->req->ofcall.offset;
1048                                 if(r->req->ofcall.count == r->req->ifcall.count)
1049                                         respond(r->req, nil);
1050                                 else
1051                                         submitreq(r->req);
1052                                 break;
1053                         }
1054                         if(r->req->ofcall.count == 0) goto common;
1055                         respond(r->req, nil);
1056                         break;
1057                 case Tstat:
1058                         if(t != SSH_FXP_ATTRS) goto common;
1059                         if(attrib2dir(rxpkt + 5, rxpkt + rxlen, &r->req->d) < 0) goto garbage;
1060                         respond(r->req, nil);
1061                         break;
1062                 case Twstat:
1063                         if(!okresp) goto common;
1064                         if(r->req->aux == nil){
1065                                 r->req->aux = (void *) -1;
1066                                 submitreq(r->req);
1067                         }else{
1068                                 wlock(sf);
1069                                 free(sf->fn);
1070                                 sf->fn = r->req->aux;
1071                                 wunlock(sf);
1072                                 respond(r->req, nil);
1073                         }
1074                         break;
1075                 case Tremove:
1076                         goto common;
1077                 default:
1078                         fprint(2, "sendproc: unimplemented 9p request %F in queue\n", &r->req->ifcall);
1079                         respond(r->req, "phase error");
1080                 }
1081                 putsreq(r);
1082                 continue;
1083                 
1084         common:
1085                 switch(t){
1086                 case SSH_FXP_STATUS:
1087                         if(unpack(rxpkt, rxlen, "_____uss", &code, &msg, &msgn, &lang, &langn) < 0){
1088         garbage:
1089                                 fprint(2, "sshfs: garbled packet in response to 9p request %F\n", &r->req->ifcall);
1090                                 break;
1091                         }
1092                         if(code == SSH_FX_OK)
1093                                 e = nil;
1094                         else if(code == SSH_FX_EOF && r->req->ifcall.type == Tread){
1095                                 if((r->req->fid->qid.type & QTDIR) != 0){
1096                                         wlock(sf);
1097                                         sf->direof = 1;
1098                                         wunlock(sf);
1099                                         readprocess(r->req);
1100                                         putsreq(r);
1101                                         continue;
1102                                 }
1103                                 r->req->ofcall.count = 0;
1104                                 e = nil;
1105                         }else if(msgn > 0){
1106                                 e = msg;
1107                                 e[msgn] = 0;
1108                         }else if(code < nelem(errors))
1109                                 e = errors[code];
1110                         else{
1111                                 snprint(ebuf, sizeof(ebuf), "error code %d", code);
1112                                 e = ebuf;
1113                         }
1114                         break;
1115                 default:
1116                         fprint(2, "sshfs: received unexpected packet %Σ for 9p request %F\n", t, &r->req->ifcall);
1117                 }
1118                 if(r->req->ifcall.type == Twalk)
1119                         walkprocess(r->req, 0, e);
1120                 else
1121                         respond(r->req, e);
1122                 putsreq(r);
1123                 continue;
1124         }
1125 }
1126
1127 void
1128 sshfswalk(Req *r)
1129 {
1130         SFid *s, *t;
1131         char *p, *q;
1132         int i;
1133
1134         if(r->fid != r->newfid){
1135                 r->newfid->qid = r->fid->qid;
1136                 s = r->fid->aux;
1137                 t = emalloc9p(sizeof(SFid));
1138                 t->fn = strdup(s->fn);
1139                 t->qid = s->qid;
1140                 r->newfid->aux = t;
1141         }else
1142                 t = r->fid->aux;
1143         if(r->ifcall.nwname == 0){
1144                 respond(r, nil);
1145                 return;
1146         }
1147         p = strdup(t->fn);
1148         for(i = 0; i < r->ifcall.nwname; i++){
1149                 if(strcmp(r->ifcall.wname[i], "..") == 0)
1150                         q = parentdir(p);
1151                 else
1152                         q = pathcat(p, r->ifcall.wname[i]);
1153                 free(p);
1154                 p = q;
1155                 r->ofcall.wqid[i] = (Qid){qidcalc(p), 0, QTDIR};
1156         }
1157         r->ofcall.nwqid = r->ifcall.nwname;
1158         r->aux = p;
1159         submitreq(r);
1160 }
1161
1162 void
1163 sshfsdestroyfid(Fid *f)
1164 {
1165         SFid *sf;
1166         SReq *sr;
1167
1168         sf = f->aux;
1169         if(sf == nil)
1170                 return;
1171         if(sf->hand != nil){
1172                 sr = emalloc9p(sizeof(SReq));
1173                 sr->reqid = -1;
1174                 sr->closefid = sf;
1175                 submitsreq(sr);
1176         }else
1177                 putsfid(sf);
1178 }
1179
1180 void
1181 sshfsdestroyreq(Req *r)
1182 {
1183         if(r->ifcall.type == Twalk)
1184                 free(r->aux);
1185 }
1186
1187 void
1188 sshfsend(Srv *)
1189 {
1190         dprint("sshfs: ending\n");
1191         threadexitsall(nil);
1192 }
1193
1194 Srv sshfssrv = {
1195         .attach sshfsattach,
1196         .walk sshfswalk,
1197         .open submitreq,
1198         .create submitreq,
1199         .read sshfsread,
1200         .write submitreq,
1201         .stat submitreq,
1202         .wstat submitreq,
1203         .remove submitreq,
1204         .destroyfid sshfsdestroyfid,
1205         .destroyreq sshfsdestroyreq,
1206         .end sshfsend
1207 };
1208
1209 char *
1210 readfile(char *fn)
1211 {
1212         char *hand, *dat;
1213         int handn, datn;
1214         u32int code;
1215         char *p;
1216         int off;
1217         
1218         if(fn == nil) return nil;
1219         sendpkt("busuu", SSH_FXP_OPEN, 0, fn, strlen(fn), SSH_FXF_READ, 0);
1220         if(recvpkt() != SSH_FXP_HANDLE) return nil;
1221         if(unpack(rxpkt, rxlen, "_____s", &dat, &handn) < 0) return nil;
1222         hand = emalloc9p(handn);
1223         memcpy(hand, dat, handn);
1224         off = 0;
1225         p = nil;
1226         for(;;){
1227                 sendpkt("busvu", SSH_FXP_READ, 0, hand, handn, (uvlong)off, MAXWRITE);
1228                 switch(recvpkt()){
1229                 case SSH_FXP_STATUS:
1230                         if(unpack(rxpkt, rxlen, "_____u", &code) < 0) goto err;
1231                         if(code == SSH_FX_EOF) goto out;
1232                 default:
1233                         goto err;
1234                 case SSH_FXP_DATA:
1235                         if(unpack(rxpkt, rxlen, "_____s", &dat, &datn) < 0) goto err;
1236                         break;
1237                 }
1238                 p = erealloc9p(p, off + datn + 1);
1239                 memcpy(p + off, dat, datn);
1240                 off += datn;
1241                 p[off] = 0;
1242         }
1243 err:
1244         p = nil;
1245 out:
1246         sendpkt("bus", SSH_FXP_CLOSE, 0, hand, handn);
1247         free(hand);
1248         recvpkt();
1249         return p;
1250 }
1251
1252 void
1253 passwdparse(IDEnt **tab, char *s)
1254 {
1255         char *p;
1256         char *n;
1257         int id;
1258         IDEnt *e, **b;
1259
1260         p = s;
1261         for(;;){
1262                 n = p;
1263                 p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
1264                 *p = 0;
1265                 p = strpbrk(p+1, ":\n");
1266                 p = strpbrk(p, ":\n"); if(p == nil) break; if(*p != ':'){ p++; continue; }
1267                 id = strtol(p+1, &p, 10);
1268                 p = strchr(p, '\n');
1269                 if(p == nil) break;
1270                 p++;
1271                 e = emalloc9p(sizeof(IDEnt));
1272                 e->name = strdup(n);
1273                 e->id = id;
1274                 b = &tab[((ulong)e->id) % HASH];
1275                 e->next = *b;
1276                 *b = e;
1277         }
1278         free(s);
1279 }
1280
1281 int pfd[2];
1282 int sshargc;
1283 char **sshargv;
1284
1285 void
1286 startssh(void *)
1287 {
1288         char *f;
1289
1290         close(pfd[0]);
1291         dup(pfd[1], 0);
1292         dup(pfd[1], 1);
1293         close(pfd[1]);
1294         if(strncmp(sshargv[0], "./", 2) != 0)
1295                 f = smprint("/bin/%s", sshargv[0]);
1296         else
1297                 f = sshargv[0];
1298         procexec(nil, f, sshargv);
1299         sysfatal("exec: %r");
1300 }
1301
1302 void
1303 usage(void)
1304 {
1305         static char *common = "[-abdRUG] [-s service] [-m mtpt] [-u uidfile] [-g gidfile]";
1306         fprint(2, "usage: %s %s [-- ssh-options] host\n", argv0, common);
1307         fprint(2, "       %s %s -c cmdline\n", argv0, common);
1308         fprint(2, "       %s %s -p\n", argv0, common);
1309         exits("usage");
1310 }
1311
1312 void
1313 threadmain(int argc, char **argv)
1314 {
1315         u32int x;
1316         static int pflag, cflag;
1317         static char *svc, *mtpt;
1318         static int mflag;
1319         static char *uidfile, *gidfile;
1320         
1321         fmtinstall(L'Σ', fxpfmt);
1322         
1323         mtpt = "/n/ssh";
1324         uidfile = "/etc/passwd";
1325         gidfile = "/etc/group";
1326         ARGBEGIN{
1327         case 'R': readonly++; break;
1328         case 'd': debug++; chatty9p++; break;
1329         case 'p': pflag++; break;
1330         case 'c': cflag++; break;
1331         case 's': svc = EARGF(usage()); break;
1332         case 'a': mflag |= MAFTER; break;
1333         case 'b': mflag |= MBEFORE; break;
1334         case 'm': mtpt = EARGF(usage()); break;
1335         case 'u': uidfile = EARGF(usage()); break;
1336         case 'U': uidfile = nil; break;
1337         case 'g': gidfile = EARGF(usage()); break;
1338         case 'G': gidfile = nil; break;
1339         default: usage();
1340         }ARGEND;
1341         
1342         if(readonly){
1343                 sshfssrv.create = nil;
1344                 sshfssrv.write = nil;
1345                 sshfssrv.wstat = nil;
1346                 sshfssrv.remove = nil;
1347         }
1348         
1349         if(pflag){
1350                 rdfd = 0;
1351                 wrfd = 1;
1352         }else{
1353                 if(argc == 0) usage();
1354                 if(cflag){
1355                         sshargc = argc;
1356                         sshargv = argv;
1357                 }else{
1358                         sshargc = argc + 2;
1359                         sshargv = emalloc9p(sizeof(char *) * (sshargc + 1));
1360                         sshargv[0] = "ssh";
1361                         memcpy(sshargv + 1, argv, argc * sizeof(char *));
1362                         sshargv[sshargc - 1] = "#sftp";
1363                 }
1364                 pipe(pfd);
1365                 rdfd = wrfd = pfd[0];
1366                 procrfork(startssh, nil, mainstacksize, RFFDG|RFNOTEG);
1367                 close(pfd[1]);
1368         }
1369
1370         sendpkt("bu", SSH_FXP_INIT, VERSION);
1371         if(recvpkt() != SSH_FXP_VERSION || unpack(rxpkt, rxlen, "_u", &x) < 0) sysfatal("received garbage");
1372         if(x != VERSION) sysfatal("server replied with incompatible version %d", x);
1373         
1374         passwdparse(uidtab, readfile(uidfile));
1375         passwdparse(gidtab, readfile(gidfile));
1376         
1377         procrfork(sendproc, 0, mainstacksize, RFNOTEG);
1378         procrfork(recvproc, 0, mainstacksize, RFNOTEG);
1379         threadpostmountsrv(&sshfssrv, svc, mtpt, MCREATE | mflag);
1380 }