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