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