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