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, buf[ERRMAX]; 251 int n; 252 253 kapid = kaproc(); 254 255 while(!dying){ 256 n = read9pmsg(mfd, mdata, messagesize); 257 if(n <= 0){ 258 errstr(buf, sizeof buf); 259 if(buf[0]=='\0' || strstr(buf, "hungup")) 260 exits(""); 261 fatal("mount read: %s\n", buf); 262 } 263 if(convM2S(mdata, n, &thdr) == 0) 264 continue; 265 266 if(debug) 267 fprint(2, "<-%F\n", &thdr);/**/ 268 269 if(!fcalls[thdr.type]) 270 err = "bad fcall type"; 271 else 272 err = (*fcalls[thdr.type])(newfid(thdr.fid)); 273 if(err){ 274 rhdr.type = Rerror; 275 rhdr.ename = err; 276 }else{ 277 rhdr.type = thdr.type + 1; 278 rhdr.fid = thdr.fid; 279 } 280 rhdr.tag = thdr.tag; 281 if(debug) 282 fprint(2, "->%F\n", &rhdr);/**/ 283 n = convS2M(&rhdr, mdata, messagesize); 284 if(write(mfd, mdata, n) != n) 285 fatal("mount write"); 286 } 287 } 288 289 char* 290 rnop(Fid *f) 291 { 292 USED(f); 293 return 0; 294 } 295 296 char* 297 rversion(Fid*) 298 { 299 if(thdr.msize > sizeof(mdata)) 300 rhdr.msize = messagesize; 301 else 302 rhdr.msize = thdr.msize; 303 messagesize = thdr.msize; 304 305 if(strncmp(thdr.version, "9P2000", 6) != 0) 306 return "unknown 9P version"; 307 rhdr.version = "9P2000"; 308 return nil; 309 } 310 311 char* 312 rflush(Fid*) 313 { 314 return 0; 315 } 316 317 char* 318 rauth(Fid*) 319 { 320 return "auth unimplemented"; 321 } 322 323 char* 324 rattach(Fid *f) 325 { 326 f->busy = 1; 327 f->node = remroot; 328 rhdr.qid = f->node->d->qid; 329 return 0; 330 } 331 332 char* 333 rwalk(Fid *f) 334 { 335 Node *np; 336 Fid *nf; 337 char **elems; 338 int i, nelems; 339 char *err; 340 Node *node; 341 342 /* clone fid */ 343 nf = nil; 344 if(thdr.newfid != thdr.fid){ 345 nf = newfid(thdr.newfid); 346 if(nf->busy) 347 return "newfid in use"; 348 nf->busy = 1; 349 nf->node = f->node; 350 f = nf; 351 } 352 353 err = nil; 354 elems = thdr.wname; 355 nelems = thdr.nwname; 356 node = f->node; 357 rhdr.nwqid = 0; 358 if(nelems > 0){ 359 /* walk fid */ 360 for(i=0; i<nelems && i<MAXWELEM; i++){ 361 if((node->d->qid.type & QTDIR) == 0){ 362 err = "not a directory"; 363 break; 364 } 365 if(strcmp(elems[i], ".") == 0){ 366 Found: 367 rhdr.wqid[i] = node->d->qid; 368 rhdr.nwqid++; 369 continue; 370 } 371 if(strcmp(elems[i], "..") == 0){ 372 node = node->parent; 373 goto Found; 374 } 375 if(strcmp(elems[i], ".flush.ftpfs") == 0){ 376 /* hack to flush the cache */ 377 invalidate(node); 378 readdir(node); 379 goto Found; 380 } 381 382 /* some top level names are special */ 383 if((os == Tops || os == VM || os == VMS) && node == remroot){ 384 np = newtopsdir(elems[i]); 385 if(np){ 386 node = np; 387 goto Found; 388 } else { 389 err = nosuchfile; 390 break; 391 } 392 } 393 394 /* everything else */ 395 node = extendpath(node, s_copy(elems[i])); 396 if(ISCACHED(node->parent)){ 397 /* the cache of the parent is good, believe it */ 398 if(!ISVALID(node)){ 399 err = nosuchfile; 400 break; 401 } 402 if(node->parent->chdirunknown || (node->d->mode & DMSYML)) 403 fixsymbolic(node); 404 } else if(!ISVALID(node)){ 405 /* this isn't a valid node, try cd'ing */ 406 if(changedir(node) == 0){ 407 node->d->qid.type = QTDIR; 408 node->d->mode |= DMDIR; 409 }else{ 410 node->d->qid.type = QTFILE; 411 node->d->mode &= ~DMDIR; 412 } 413 } 414 goto Found; 415 } 416 if(i == 0 && err == 0) 417 err = "file does not exist"; 418 } 419 420 /* clunk a newly cloned fid if the walk didn't succeed */ 421 if(nf != nil && (err != nil || rhdr.nwqid<nelems)){ 422 nf->busy = 0; 423 nf->fid = 0; 424 } 425 426 /* if it all worked, point the fid to the enw node */ 427 if(err == nil) 428 f->node = node; 429 430 return err; 431 } 432 433 char * 434 ropen(Fid *f) 435 { 436 int mode; 437 438 mode = thdr.mode; 439 if(f->node->d->qid.type & QTDIR) 440 if(mode != OREAD) 441 return "permission denied"; 442 443 if(mode & OTRUNC){ 444 uncache(f->node); 445 uncache(f->node->parent); 446 filedirty(f->node); 447 } else { 448 /* read the remote file or directory */ 449 if(!ISCACHED(f->node)){ 450 filefree(f->node); 451 if(f->node->d->qid.type & QTDIR){ 452 invalidate(f->node); 453 if(readdir(f->node) < 0) 454 return nosuchfile; 455 } else { 456 if(readfile(f->node) < 0) 457 return errstring; 458 } 459 CACHED(f->node); 460 } 461 } 462 463 rhdr.qid = f->node->d->qid; 464 f->open = 1; 465 f->node->opens++; 466 return 0; 467 } 468 469 char* 470 rcreate(Fid *f) 471 { 472 char *name; 473 474 if((f->node->d->qid.type&QTDIR) == 0) 475 return "not a directory"; 476 477 name = thdr.name; 478 f->node = extendpath(f->node, s_copy(name)); 479 uncache(f->node); 480 if(thdr.perm & DMDIR){ 481 if(createdir(f->node) < 0) 482 return "permission denied"; 483 } else 484 filedirty(f->node); 485 invalidate(f->node->parent); 486 uncache(f->node->parent); 487 488 rhdr.qid = f->node->d->qid; 489 f->open = 1; 490 f->node->opens++; 491 return 0; 492 } 493 494 char* 495 rread(Fid *f) 496 { 497 long off; 498 int n, cnt, rv; 499 Node *np; 500 501 rhdr.count = 0; 502 off = thdr.offset; 503 cnt = thdr.count; 504 if(cnt > messagesize-IOHDRSZ) 505 cnt = messagesize-IOHDRSZ; 506 507 if(f->node->d->qid.type & QTDIR){ 508 rv = 0; 509 for(np = f->node->children; np != nil; np = np->sibs){ 510 if(!ISVALID(np)) 511 continue; 512 if(off <= BIT16SZ) 513 break; 514 n = convD2M(np->d, mbuf, messagesize-IOHDRSZ); 515 off -= n; 516 } 517 for(; rv < cnt && np != nil; np = np->sibs){ 518 if(!ISVALID(np)) 519 continue; 520 if(np->d->mode & DMSYML) 521 fixsymbolic(np); 522 n = convD2M(np->d, mbuf + rv, cnt-rv); 523 if(n <= BIT16SZ) 524 break; 525 rv += n; 526 } 527 } else { 528 /* reread file if it's fallen out of the cache */ 529 if(!ISCACHED(f->node)) 530 if(readfile(f->node) < 0) 531 return errstring; 532 CACHED(f->node); 533 rv = fileread(f->node, (char*)mbuf, off, cnt); 534 if(rv < 0) 535 return errstring; 536 } 537 538 rhdr.data = (char*)mbuf; 539 rhdr.count = rv; 540 return 0; 541 } 542 543 char* 544 rwrite(Fid *f) 545 { 546 long off; 547 int cnt; 548 549 if(f->node->d->qid.type & QTDIR) 550 return "directories are not writable"; 551 552 rhdr.count = 0; 553 off = thdr.offset; 554 cnt = thdr.count; 555 cnt = filewrite(f->node, thdr.data, off, cnt); 556 if(cnt < 0) 557 return errstring; 558 filedirty(f->node); 559 rhdr.count = cnt; 560 return 0; 561 } 562 563 char * 564 rclunk(Fid *f) 565 { 566 if(fileisdirty(f->node)){ 567 if(createfile(f->node) < 0) 568 fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name); 569 fileclean(f->node); 570 uncache(f->node); 571 } 572 if(f->open){ 573 f->open = 0; 574 f->node->opens--; 575 } 576 f->busy = 0; 577 return 0; 578 } 579 580 /* 581 * remove is an implicit clunk 582 */ 583 char * 584 rremove(Fid *f) 585 { 586 char *e; 587 e = nil; 588 if(QTDIR & f->node->d->qid.type){ 589 if(removedir(f->node) < 0) 590 e = errstring; 591 } else { 592 if(removefile(f->node) < 0) 593 e = errstring; 594 } 595 uncache(f->node->parent); 596 uncache(f->node); 597 if(e == nil) 598 INVALID(f->node); 599 f->busy = 0; 600 return e; 601 } 602 603 char * 604 rstat(Fid *f) 605 { 606 Node *p; 607 608 p = f->node->parent; 609 if(!ISCACHED(p)){ 610 invalidate(p); 611 readdir(p); 612 CACHED(p); 613 } 614 if(!ISVALID(f->node)) 615 return nosuchfile; 616 if(p->chdirunknown) 617 fixsymbolic(f->node); 618 rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ); 619 rhdr.stat = mbuf; 620 return 0; 621 } 622 623 char * 624 rwstat(Fid *f) 625 { 626 USED(f); 627 return "wstat not implemented"; 628 } 629 630 /* 631 * print message and die 632 */ 633 void 634 fatal(char *fmt, ...) 635 { 636 va_list arg; 637 char buf[8*1024]; 638 639 dying = 1; 640 641 va_start(arg, fmt); 642 vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg); 643 va_end(arg); 644 645 fprint(2, "ftpfs: %s\n", buf); 646 if(kapid > 0) 647 postnote(PNGROUP, kapid, "die"); 648 exits(buf); 649 } 650 651 /* 652 * like strncpy but make sure there's a terminating null 653 */ 654 void* 655 safecpy(void *to, void *from, int n) 656 { 657 char *a = ((char*)to) + n - 1; 658 659 strncpy(to, from, n); 660 *a = 0; 661 return to; 662 } 663 664 /* 665 * set the error string 666 */ 667 int 668 seterr(char *fmt, ...) 669 { 670 va_list arg; 671 672 va_start(arg, fmt); 673 vsnprint(errstring, sizeof errstring, fmt, arg); 674 va_end(arg); 675 return -1; 676 } 677 678 /* 679 * create a new node 680 */ 681 Node* 682 newnode(Node *parent, String *name) 683 { 684 Node *np; 685 static ulong path; 686 Dir d; 687 688 np = mallocz(sizeof(Node), 1); 689 if(np == 0) 690 fatal("out of memory"); 691 692 np->children = 0; 693 if(parent){ 694 np->parent = parent; 695 np->sibs = parent->children; 696 parent->children = np; 697 np->depth = parent->depth + 1; 698 d.dev = 0; /* not stat'd */ 699 } else { 700 /* the root node */ 701 np->parent = np; 702 np->sibs = 0; 703 np->depth = 0; 704 d.dev = 1; 705 } 706 np->remname = name; 707 d.name = s_to_c(name); 708 d.atime = time(0); 709 d.mtime = d.atime; 710 d.uid = nouid; 711 d.gid = nouid; 712 d.muid = nouid; 713 np->fp = 0; 714 d.qid.path = ++path; 715 d.qid.vers = 0; 716 d.qid.type = QTFILE; 717 d.type = 0; 718 np->d = reallocdir(&d, 0); 719 720 return np; 721 } 722 723 /* 724 * walk one down the local mirror of the remote directory tree 725 */ 726 Node* 727 extendpath(Node *parent, String *elem) 728 { 729 Node *np; 730 731 for(np = parent->children; np; np = np->sibs) 732 if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){ 733 s_free(elem); 734 return np; 735 } 736 737 return newnode(parent, elem); 738 } 739 740 /* 741 * flush the cached file, write it back if it's dirty 742 */ 743 void 744 uncache(Node *np) 745 { 746 if(fileisdirty(np)) 747 createfile(np); 748 filefree(np); 749 UNCACHED(np); 750 } 751 752 /* 753 * invalidate all children of a node 754 */ 755 void 756 invalidate(Node *node) 757 { 758 Node *np; 759 760 if(node->opens) 761 return; /* don't invalidate something that's open */ 762 763 uncachedir(node, 0); 764 765 /* invalidate children */ 766 for(np = node->children; np; np = np->sibs){ 767 if(np->opens) 768 continue; /* don't invalidate something that's open */ 769 UNCACHED(np); 770 invalidate(np); 771 np->d->dev = 0; 772 } 773 } 774 775 /* 776 * make a top level tops-20 directory. They are automaticly valid. 777 */ 778 Node* 779 newtopsdir(char *name) 780 { 781 Node *np; 782 783 np = extendpath(remroot, s_copy(name)); 784 if(!ISVALID(np)){ 785 np->d->qid.type = QTDIR; 786 np->d->atime = time(0); 787 np->d->mtime = np->d->atime; 788 np->d->uid = "?uid?"; 789 np->d->gid = "?uid?"; 790 np->d->muid = "?uid?"; 791 np->d->mode = DMDIR|0777; 792 np->d->length = 0; 793 np->d = reallocdir(np->d, 1); 794 795 if(changedir(np) >= 0) 796 VALID(np); 797 } 798 return np; 799 } 800 801 /* 802 * figure out if a symbolic link is to a directory or a file 803 */ 804 void 805 fixsymbolic(Node *node) 806 { 807 if(changedir(node) == 0){ 808 node->d->mode |= DMDIR; 809 node->d->qid.type = QTDIR; 810 } else 811 node->d->qid.type = QTFILE; 812 node->d->mode &= ~DMSYML; 813 } 814