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