1 #include <u.h> 2 #include <libc.h> 3 #include <auth.h> 4 #include <fcall.h> 5 #include <String.h> 6 #include "ftpfs.h" 7 8 /* an active fid */ 9 typedef struct Fid Fid; 10 struct Fid 11 { 12 int fid; 13 Node *node; /* path to remote file */ 14 int busy; 15 Fid *next; 16 int open; 17 }; 18 19 Fid *fids; /* linked list of fids */ 20 char errstring[128]; /* error to return */ 21 int mfd; /* fd for 9fs */ 22 int messagesize = 4*1024*IOHDRSZ; 23 uchar mdata[8*1024*IOHDRSZ]; 24 uchar mbuf[8*1024*IOHDRSZ]; 25 Fcall rhdr; 26 Fcall thdr; 27 int debug; 28 int usenlst; 29 int usetls; 30 char *ext; 31 int quiet; 32 int kapid = -1; 33 int dying; /* set when any process decides to die */ 34 int dokeepalive; 35 36 char *rflush(Fid*), *rnop(Fid*), *rversion(Fid*), 37 *rattach(Fid*), *rclone(Fid*), *rwalk(Fid*), 38 *rclwalk(Fid*), *ropen(Fid*), *rcreate(Fid*), 39 *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), 40 *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*), 41 *rauth(Fid*);; 42 void mountinit(char*); 43 void io(void); 44 int readpdir(Node*); 45 46 char *(*fcalls[])(Fid*) = { 47 [Tflush] rflush, 48 [Tversion] rversion, 49 [Tattach] rattach, 50 [Tauth] rauth, 51 [Twalk] rwalk, 52 [Topen] ropen, 53 [Tcreate] rcreate, 54 [Tread] rread, 55 [Twrite] rwrite, 56 [Tclunk] rclunk, 57 [Tremove] rremove, 58 [Tstat] rstat, 59 [Twstat] rwstat, 60 }; 61 62 /* these names are matched as prefixes, so VMS must precede VM */ 63 OS oslist[] = { 64 { Plan9, "Plan 9", }, 65 { Plan9, "Plan9", }, 66 { Plan9, "UNIX Type: L8 Version: Plan 9", }, 67 { Unix, "SUN", }, 68 { Unix, "UNIX", }, 69 { VMS, "VMS", }, 70 { VM, "VM", }, 71 { Tops, "TOPS", }, 72 { MVS, "MVS", }, 73 { NetWare, "NetWare", }, 74 { NetWare, "NETWARE", }, 75 { OS½, "OS/2", }, 76 { TSO, "TSO", }, 77 { NT, "Windows_NT", }, /* DOS like interface */ 78 { NT, "WINDOWS_NT", }, /* Unix like interface */ 79 { Unknown, 0 }, 80 }; 81 82 char *nouid = "?uid?"; 83 84 #define S2P(x) (((ulong)(x)) & 0xffffff) 85 86 char *nosuchfile = "file does not exist"; 87 char *keyspec = ""; 88 89 void 90 usage(void) 91 { 92 fprint(2, "ftpfs [-/dqnt] [-a passwd] [-m mountpoint] [-e ext] [-k keyspec] [-o os] [-r root] [net!]address\n"); 93 exits("usage"); 94 } 95 96 void 97 main(int argc, char *argv[]) 98 { 99 char *mountroot = 0; 100 char *mountpoint = "/n/ftp"; 101 char *cpassword = 0; 102 char *cp; 103 int p[2]; 104 OS *o; 105 106 defos = Unix; 107 user = strdup(getuser()); 108 usetls = 0; 109 110 ARGBEGIN { 111 case '/': 112 mountroot = "/"; 113 break; 114 case 'a': 115 cpassword = ARGF(); 116 break; 117 case 'd': 118 debug = 1; 119 break; 120 case 'k': 121 keyspec = EARGF(usage()); 122 break; 123 case 'K': 124 dokeepalive = 1; 125 break; 126 case 'm': 127 mountpoint = ARGF(); 128 break; 129 case 'n': 130 usenlst = 1; 131 break; 132 case 'e': 133 ext = ARGF(); 134 break; 135 case 't': 136 usetls = 1; 137 break; 138 case 'o': 139 cp = ARGF(); 140 for(o = oslist; o->os != Unknown; o++) 141 if(strncmp(cp, o->name, strlen(o->name)) == 0){ 142 defos = o->os; 143 break; 144 } 145 break; 146 case 'r': 147 mountroot = ARGF(); 148 break; 149 case 'q': 150 quiet = 1; 151 break; 152 } ARGEND 153 if(argc != 1) 154 usage(); 155 156 /* get a pipe to mount and run 9fs on */ 157 if(pipe(p) < 0) 158 fatal("pipe failed: %r"); 159 mfd = p[0]; 160 161 /* initial handshakes with remote side */ 162 hello(*argv); 163 if(cpassword == 0) 164 rlogin(*argv, keyspec); 165 else 166 clogin("anonymous", cpassword); 167 preamble(mountroot); 168 169 /* start the 9fs protocol */ 170 switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){ 171 case -1: 172 fatal("fork: %r"); 173 case 0: 174 /* seal off standard input/output */ 175 close(0); 176 open("/dev/null", OREAD); 177 close(1); 178 open("/dev/null", OWRITE); 179 180 close(p[1]); 181 fmtinstall('F', fcallfmt); /* debugging */ 182 fmtinstall('D', dirfmt); /* expected by %F */ 183 fmtinstall('M', dirmodefmt); /* expected by %F */ 184 io(); 185 quit(); 186 break; 187 default: 188 close(p[0]); 189 if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0) 190 fatal("mount failed: %r"); 191 } 192 exits(0); 193 } 194 195 /* 196 * lookup an fid. if not found, create a new one. 197 */ 198 Fid * 199 newfid(int fid) 200 { 201 Fid *f, *ff; 202 203 ff = 0; 204 for(f = fids; f; f = f->next){ 205 if(f->fid == fid){ 206 if(f->busy) 207 return f; 208 else{ 209 ff = f; 210 break; 211 } 212 } else if(!ff && !f->busy) 213 ff = f; 214 } 215 if(ff == 0){ 216 ff = mallocz(sizeof(*f), 1); 217 ff->next = fids; 218 fids = ff; 219 } 220 ff->node = nil; 221 ff->fid = fid; 222 return ff; 223 } 224 225 /* 226 * a process that sends keep alive messages to 227 * keep the server from shutting down the connection 228 */ 229 int 230 kaproc(void) 231 { 232 int pid; 233 234 if(!dokeepalive) 235 return -1; 236 237 switch(pid = rfork(RFPROC|RFMEM)){ 238 case -1: 239 return -1; 240 case 0: 241 break; 242 default: 243 return pid; 244 } 245 246 while(!dying){ 247 sleep(5000); 248 nop(); 249 } 250 251 _exits(0); 252 return -1; 253 } 254 255 void 256 io(void) 257 { 258 char *err, buf[ERRMAX]; 259 int n; 260 261 kapid = kaproc(); 262 263 while(!dying){ 264 n = read9pmsg(mfd, mdata, messagesize); 265 if(n <= 0){ 266 errstr(buf, sizeof buf); 267 if(buf[0]=='\0' || strstr(buf, "hungup")) 268 exits(""); 269 fatal("mount read: %s\n", buf); 270 } 271 if(convM2S(mdata, n, &thdr) == 0) 272 continue; 273 274 if(debug) 275 fprint(2, "<-%F\n", &thdr);/**/ 276 277 if(!fcalls[thdr.type]) 278 err = "bad fcall type"; 279 else 280 err = (*fcalls[thdr.type])(newfid(thdr.fid)); 281 if(err){ 282 rhdr.type = Rerror; 283 rhdr.ename = err; 284 }else{ 285 rhdr.type = thdr.type + 1; 286 rhdr.fid = thdr.fid; 287 } 288 rhdr.tag = thdr.tag; 289 if(debug) 290 fprint(2, "->%F\n", &rhdr);/**/ 291 n = convS2M(&rhdr, mdata, messagesize); 292 if(write(mfd, mdata, n) != n) 293 fatal("mount write"); 294 } 295 } 296 297 char* 298 rnop(Fid *f) 299 { 300 USED(f); 301 return 0; 302 } 303 304 char* 305 rversion(Fid*) 306 { 307 if(thdr.msize > sizeof(mdata)) 308 rhdr.msize = messagesize; 309 else 310 rhdr.msize = thdr.msize; 311 messagesize = thdr.msize; 312 313 if(strncmp(thdr.version, "9P2000", 6) != 0) 314 return "unknown 9P version"; 315 rhdr.version = "9P2000"; 316 return nil; 317 } 318 319 char* 320 rflush(Fid*) 321 { 322 return 0; 323 } 324 325 char* 326 rauth(Fid*) 327 { 328 return "auth unimplemented"; 329 } 330 331 char* 332 rattach(Fid *f) 333 { 334 f->busy = 1; 335 f->node = remroot; 336 rhdr.qid = f->node->d->qid; 337 return 0; 338 } 339 340 char* 341 rwalk(Fid *f) 342 { 343 Node *np; 344 Fid *nf; 345 char **elems; 346 int i, nelems; 347 char *err; 348 Node *node; 349 350 /* clone fid */ 351 nf = nil; 352 if(thdr.newfid != thdr.fid){ 353 nf = newfid(thdr.newfid); 354 if(nf->busy) 355 return "newfid in use"; 356 nf->busy = 1; 357 nf->node = f->node; 358 f = nf; 359 } 360 361 err = nil; 362 elems = thdr.wname; 363 nelems = thdr.nwname; 364 node = f->node; 365 rhdr.nwqid = 0; 366 if(nelems > 0){ 367 /* walk fid */ 368 for(i=0; i<nelems && i<MAXWELEM; i++){ 369 if((node->d->qid.type & QTDIR) == 0){ 370 err = "not a directory"; 371 break; 372 } 373 if(strcmp(elems[i], ".") == 0){ 374 Found: 375 rhdr.wqid[i] = node->d->qid; 376 rhdr.nwqid++; 377 continue; 378 } 379 if(strcmp(elems[i], "..") == 0){ 380 node = node->parent; 381 goto Found; 382 } 383 if(strcmp(elems[i], ".flush.ftpfs") == 0){ 384 /* hack to flush the cache */ 385 invalidate(node); 386 readdir(node); 387 goto Found; 388 } 389 390 /* some top level names are special */ 391 if((os == Tops || os == VM || os == VMS) && node == remroot){ 392 np = newtopsdir(elems[i]); 393 if(np){ 394 node = np; 395 goto Found; 396 } else { 397 err = nosuchfile; 398 break; 399 } 400 } 401 402 /* everything else */ 403 node = extendpath(node, s_copy(elems[i])); 404 if(ISCACHED(node->parent)){ 405 /* the cache of the parent is good, believe it */ 406 if(!ISVALID(node)){ 407 err = nosuchfile; 408 break; 409 } 410 if(node->parent->chdirunknown || (node->d->mode & DMSYML)) 411 fixsymbolic(node); 412 } else if(!ISVALID(node)){ 413 /* this isn't a valid node, try cd'ing */ 414 if(changedir(node) == 0){ 415 node->d->qid.type = QTDIR; 416 node->d->mode |= DMDIR; 417 }else{ 418 node->d->qid.type = QTFILE; 419 node->d->mode &= ~DMDIR; 420 } 421 } 422 goto Found; 423 } 424 if(i == 0 && err == 0) 425 err = "file does not exist"; 426 } 427 428 /* clunk a newly cloned fid if the walk didn't succeed */ 429 if(nf != nil && (err != nil || rhdr.nwqid<nelems)){ 430 nf->busy = 0; 431 nf->fid = 0; 432 } 433 434 /* if it all worked, point the fid to the enw node */ 435 if(err == nil) 436 f->node = node; 437 438 return err; 439 } 440 441 char * 442 ropen(Fid *f) 443 { 444 int mode; 445 446 mode = thdr.mode; 447 if(f->node->d->qid.type & QTDIR) 448 if(mode != OREAD) 449 return "permission denied"; 450 451 if(mode & OTRUNC){ 452 uncache(f->node); 453 uncache(f->node->parent); 454 filedirty(f->node); 455 } else { 456 /* read the remote file or directory */ 457 if(!ISCACHED(f->node)){ 458 filefree(f->node); 459 if(f->node->d->qid.type & QTDIR){ 460 invalidate(f->node); 461 if(readdir(f->node) < 0) 462 return nosuchfile; 463 } else { 464 if(readfile(f->node) < 0) 465 return errstring; 466 } 467 CACHED(f->node); 468 } 469 } 470 471 rhdr.qid = f->node->d->qid; 472 f->open = 1; 473 f->node->opens++; 474 return 0; 475 } 476 477 char* 478 rcreate(Fid *f) 479 { 480 char *name; 481 482 if((f->node->d->qid.type&QTDIR) == 0) 483 return "not a directory"; 484 485 name = thdr.name; 486 f->node = extendpath(f->node, s_copy(name)); 487 uncache(f->node); 488 if(thdr.perm & DMDIR){ 489 if(createdir(f->node) < 0) 490 return "permission denied"; 491 } else 492 filedirty(f->node); 493 invalidate(f->node->parent); 494 uncache(f->node->parent); 495 496 rhdr.qid = f->node->d->qid; 497 f->open = 1; 498 f->node->opens++; 499 return 0; 500 } 501 502 char* 503 rread(Fid *f) 504 { 505 long off; 506 int n, cnt, rv; 507 Node *np; 508 509 rhdr.count = 0; 510 off = thdr.offset; 511 cnt = thdr.count; 512 if(cnt > messagesize-IOHDRSZ) 513 cnt = messagesize-IOHDRSZ; 514 515 if(f->node->d->qid.type & QTDIR){ 516 rv = 0; 517 for(np = f->node->children; np != nil; np = np->sibs){ 518 if(!ISVALID(np)) 519 continue; 520 if(off <= BIT16SZ) 521 break; 522 n = convD2M(np->d, mbuf, messagesize-IOHDRSZ); 523 off -= n; 524 } 525 for(; rv < cnt && np != nil; np = np->sibs){ 526 if(!ISVALID(np)) 527 continue; 528 if(np->d->mode & DMSYML) 529 fixsymbolic(np); 530 n = convD2M(np->d, mbuf + rv, cnt-rv); 531 if(n <= BIT16SZ) 532 break; 533 rv += n; 534 } 535 } else { 536 /* reread file if it's fallen out of the cache */ 537 if(!ISCACHED(f->node)) 538 if(readfile(f->node) < 0) 539 return errstring; 540 CACHED(f->node); 541 rv = fileread(f->node, (char*)mbuf, off, cnt); 542 if(rv < 0) 543 return errstring; 544 } 545 546 rhdr.data = (char*)mbuf; 547 rhdr.count = rv; 548 return 0; 549 } 550 551 char* 552 rwrite(Fid *f) 553 { 554 long off; 555 int cnt; 556 557 if(f->node->d->qid.type & QTDIR) 558 return "directories are not writable"; 559 560 rhdr.count = 0; 561 off = thdr.offset; 562 cnt = thdr.count; 563 cnt = filewrite(f->node, thdr.data, off, cnt); 564 if(cnt < 0) 565 return errstring; 566 filedirty(f->node); 567 rhdr.count = cnt; 568 return 0; 569 } 570 571 char * 572 rclunk(Fid *f) 573 { 574 if(fileisdirty(f->node)){ 575 if(createfile(f->node) < 0) 576 fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name); 577 fileclean(f->node); 578 uncache(f->node); 579 } 580 if(f->open){ 581 f->open = 0; 582 f->node->opens--; 583 } 584 f->busy = 0; 585 return 0; 586 } 587 588 /* 589 * remove is an implicit clunk 590 */ 591 char * 592 rremove(Fid *f) 593 { 594 char *e; 595 e = nil; 596 if(QTDIR & f->node->d->qid.type){ 597 if(removedir(f->node) < 0) 598 e = errstring; 599 } else { 600 if(removefile(f->node) < 0) 601 e = errstring; 602 } 603 uncache(f->node->parent); 604 uncache(f->node); 605 if(e == nil) 606 INVALID(f->node); 607 f->busy = 0; 608 return e; 609 } 610 611 char * 612 rstat(Fid *f) 613 { 614 Node *p; 615 616 p = f->node->parent; 617 if(!ISCACHED(p)){ 618 invalidate(p); 619 readdir(p); 620 CACHED(p); 621 } 622 if(!ISVALID(f->node)) 623 return nosuchfile; 624 if(p->chdirunknown) 625 fixsymbolic(f->node); 626 rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ); 627 rhdr.stat = mbuf; 628 return 0; 629 } 630 631 char * 632 rwstat(Fid *f) 633 { 634 USED(f); 635 return "wstat not implemented"; 636 } 637 638 /* 639 * print message and die 640 */ 641 void 642 fatal(char *fmt, ...) 643 { 644 va_list arg; 645 char buf[8*1024]; 646 647 dying = 1; 648 649 va_start(arg, fmt); 650 vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg); 651 va_end(arg); 652 653 fprint(2, "ftpfs: %s\n", buf); 654 if(kapid > 0) 655 postnote(PNGROUP, kapid, "die"); 656 exits(buf); 657 } 658 659 /* 660 * like strncpy but make sure there's a terminating null 661 */ 662 void* 663 safecpy(void *to, void *from, int n) 664 { 665 char *a = ((char*)to) + n - 1; 666 667 strncpy(to, from, n); 668 *a = 0; 669 return to; 670 } 671 672 /* 673 * set the error string 674 */ 675 int 676 seterr(char *fmt, ...) 677 { 678 va_list arg; 679 680 va_start(arg, fmt); 681 vsnprint(errstring, sizeof errstring, fmt, arg); 682 va_end(arg); 683 return -1; 684 } 685 686 /* 687 * create a new node 688 */ 689 Node* 690 newnode(Node *parent, String *name) 691 { 692 Node *np; 693 static ulong path; 694 Dir d; 695 696 np = mallocz(sizeof(Node), 1); 697 if(np == 0) 698 fatal("out of memory"); 699 700 np->children = 0; 701 if(parent){ 702 np->parent = parent; 703 np->sibs = parent->children; 704 parent->children = np; 705 np->depth = parent->depth + 1; 706 d.dev = 0; /* not stat'd */ 707 } else { 708 /* the root node */ 709 np->parent = np; 710 np->sibs = 0; 711 np->depth = 0; 712 d.dev = 1; 713 } 714 np->remname = name; 715 d.name = s_to_c(name); 716 d.atime = time(0); 717 d.mtime = d.atime; 718 d.uid = nouid; 719 d.gid = nouid; 720 d.muid = nouid; 721 np->fp = 0; 722 d.qid.path = ++path; 723 d.qid.vers = 0; 724 d.qid.type = QTFILE; 725 d.type = 0; 726 np->d = reallocdir(&d, 0); 727 728 return np; 729 } 730 731 /* 732 * walk one down the local mirror of the remote directory tree 733 */ 734 Node* 735 extendpath(Node *parent, String *elem) 736 { 737 Node *np; 738 739 for(np = parent->children; np; np = np->sibs) 740 if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){ 741 s_free(elem); 742 return np; 743 } 744 745 return newnode(parent, elem); 746 } 747 748 /* 749 * flush the cached file, write it back if it's dirty 750 */ 751 void 752 uncache(Node *np) 753 { 754 if(fileisdirty(np)) 755 createfile(np); 756 filefree(np); 757 UNCACHED(np); 758 } 759 760 /* 761 * invalidate all children of a node 762 */ 763 void 764 invalidate(Node *node) 765 { 766 Node *np; 767 768 if(node->opens) 769 return; /* don't invalidate something that's open */ 770 771 uncachedir(node, 0); 772 773 /* invalidate children */ 774 for(np = node->children; np; np = np->sibs){ 775 if(np->opens) 776 continue; /* don't invalidate something that's open */ 777 UNCACHED(np); 778 invalidate(np); 779 np->d->dev = 0; 780 } 781 } 782 783 /* 784 * make a top level tops-20 directory. They are automaticly valid. 785 */ 786 Node* 787 newtopsdir(char *name) 788 { 789 Node *np; 790 791 np = extendpath(remroot, s_copy(name)); 792 if(!ISVALID(np)){ 793 np->d->qid.type = QTDIR; 794 np->d->atime = time(0); 795 np->d->mtime = np->d->atime; 796 np->d->uid = "?uid?"; 797 np->d->gid = "?uid?"; 798 np->d->muid = "?uid?"; 799 np->d->mode = DMDIR|0777; 800 np->d->length = 0; 801 np->d = reallocdir(np->d, 1); 802 803 if(changedir(np) >= 0) 804 VALID(np); 805 } 806 return np; 807 } 808 809 /* 810 * figure out if a symbolic link is to a directory or a file 811 */ 812 void 813 fixsymbolic(Node *node) 814 { 815 if(changedir(node) == 0){ 816 node->d->mode |= DMDIR; 817 node->d->qid.type = QTDIR; 818 } else 819 node->d->qid.type = QTFILE; 820 node->d->mode &= ~DMSYML; 821 } 822