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