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