1 #include <u.h> 2 #include <libc.h> 3 #include <auth.h> 4 #include <fcall.h> 5 6 /* 7 * Rather than reading /adm/users, which is a lot of work for 8 * a toy program, we assume all groups have the form 9 * NNN:user:user: 10 * meaning that each user is the leader of his own group. 11 */ 12 13 enum 14 { 15 OPERM = 0x3, /* mask of all permission types in open mode */ 16 Nram = 2048, 17 Maxsize = 768*1024*1024, 18 Maxfdata = 8192, 19 }; 20 21 typedef struct Fid Fid; 22 typedef struct Ram Ram; 23 24 struct Fid 25 { 26 short busy; 27 short open; 28 short rclose; 29 int fid; 30 Fid *next; 31 char *user; 32 Ram *ram; 33 }; 34 35 struct Ram 36 { 37 short busy; 38 short open; 39 long parent; /* index in Ram array */ 40 Qid qid; 41 long perm; 42 char *name; 43 ulong atime; 44 ulong mtime; 45 char *user; 46 char *group; 47 char *muid; 48 char *data; 49 long ndata; 50 }; 51 52 enum 53 { 54 Pexec = 1, 55 Pwrite = 2, 56 Pread = 4, 57 Pother = 1, 58 Pgroup = 8, 59 Powner = 64, 60 }; 61 62 ulong path; /* incremented for each new file */ 63 Fid *fids; 64 Ram ram[Nram]; 65 int nram; 66 int mfd[2]; 67 char *user; 68 uchar mdata[IOHDRSZ+Maxfdata]; 69 uchar rdata[Maxfdata]; /* buffer for data in reply */ 70 uchar statbuf[STATMAX]; 71 Fcall thdr; 72 Fcall rhdr; 73 int messagesize = sizeof mdata; 74 75 Fid * newfid(int); 76 uint ramstat(Ram*, uchar*, uint); 77 void error(char*); 78 void io(void); 79 void *erealloc(void*, ulong); 80 void *emalloc(ulong); 81 char *estrdup(char*); 82 void usage(void); 83 int perm(Fid*, Ram*, int); 84 85 char *rflush(Fid*), *rversion(Fid*), *rauth(Fid*), 86 *rattach(Fid*), *rwalk(Fid*), 87 *ropen(Fid*), *rcreate(Fid*), 88 *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), 89 *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*); 90 91 int needfid[] = { 92 [Tversion] 0, 93 [Tflush] 0, 94 [Tauth] 0, 95 [Tattach] 0, 96 [Twalk] 1, 97 [Topen] 1, 98 [Tcreate] 1, 99 [Tread] 1, 100 [Twrite] 1, 101 [Tclunk] 1, 102 [Tremove] 1, 103 [Tstat] 1, 104 [Twstat] 1, 105 }; 106 107 char *(*fcalls[])(Fid*) = { 108 [Tversion] rversion, 109 [Tflush] rflush, 110 [Tauth] rauth, 111 [Tattach] rattach, 112 [Twalk] rwalk, 113 [Topen] ropen, 114 [Tcreate] rcreate, 115 [Tread] rread, 116 [Twrite] rwrite, 117 [Tclunk] rclunk, 118 [Tremove] rremove, 119 [Tstat] rstat, 120 [Twstat] rwstat, 121 }; 122 123 char Eperm[] = "permission denied"; 124 char Enotdir[] = "not a directory"; 125 char Enoauth[] = "ramfs: authentication not required"; 126 char Enotexist[] = "file does not exist"; 127 char Einuse[] = "file in use"; 128 char Eexist[] = "file exists"; 129 char Eisdir[] = "file is a directory"; 130 char Enotowner[] = "not owner"; 131 char Eisopen[] = "file already open for I/O"; 132 char Excl[] = "exclusive use file already open"; 133 char Ename[] = "illegal name"; 134 char Eversion[] = "unknown 9P version"; 135 char Enotempty[] = "directory not empty"; 136 137 int debug; 138 int private; 139 140 void 141 notifyf(void *a, char *s) 142 { 143 USED(a); 144 if(strncmp(s, "interrupt", 9) == 0) 145 noted(NCONT); 146 noted(NDFLT); 147 } 148 149 void 150 main(int argc, char *argv[]) 151 { 152 Ram *r; 153 char *defmnt; 154 int p[2]; 155 int fd; 156 int stdio = 0; 157 char *service; 158 159 service = "ramfs"; 160 defmnt = "/tmp"; 161 ARGBEGIN{ 162 case 'D': 163 debug = 1; 164 break; 165 case 'i': 166 defmnt = 0; 167 stdio = 1; 168 mfd[0] = 0; 169 mfd[1] = 1; 170 break; 171 case 's': 172 defmnt = 0; 173 break; 174 case 'm': 175 defmnt = EARGF(usage()); 176 break; 177 case 'p': 178 private++; 179 break; 180 case 'S': 181 defmnt = 0; 182 service = EARGF(usage()); 183 break; 184 default: 185 usage(); 186 }ARGEND 187 188 if(pipe(p) < 0) 189 error("pipe failed"); 190 if(!stdio){ 191 mfd[0] = p[0]; 192 mfd[1] = p[0]; 193 if(defmnt == 0){ 194 char buf[64]; 195 snprint(buf, sizeof buf, "#s/%s", service); 196 fd = create(buf, OWRITE|ORCLOSE, 0666); 197 if(fd < 0) 198 error("create failed"); 199 sprint(buf, "%d", p[1]); 200 if(write(fd, buf, strlen(buf)) < 0) 201 error("writing service file"); 202 } 203 } 204 205 user = getuser(); 206 notify(notifyf); 207 nram = 1; 208 r = &ram[0]; 209 r->busy = 1; 210 r->data = 0; 211 r->ndata = 0; 212 r->perm = DMDIR | 0775; 213 r->qid.type = QTDIR; 214 r->qid.path = 0LL; 215 r->qid.vers = 0; 216 r->parent = 0; 217 r->user = user; 218 r->group = user; 219 r->muid = user; 220 r->atime = time(0); 221 r->mtime = r->atime; 222 r->name = estrdup("."); 223 224 if(debug) 225 fmtinstall('F', fcallfmt); 226 switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){ 227 case -1: 228 error("fork"); 229 case 0: 230 close(p[1]); 231 io(); 232 break; 233 default: 234 close(p[0]); /* don't deadlock if child fails */ 235 if(defmnt && mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0) 236 error("mount failed"); 237 } 238 exits(0); 239 } 240 241 char* 242 rversion(Fid*) 243 { 244 Fid *f; 245 246 for(f = fids; f; f = f->next) 247 if(f->busy) 248 rclunk(f); 249 if(thdr.msize > sizeof mdata) 250 rhdr.msize = sizeof mdata; 251 else 252 rhdr.msize = thdr.msize; 253 messagesize = rhdr.msize; 254 if(strncmp(thdr.version, "9P2000", 6) != 0) 255 return Eversion; 256 rhdr.version = "9P2000"; 257 return 0; 258 } 259 260 char* 261 rauth(Fid*) 262 { 263 return "ramfs: no authentication required"; 264 } 265 266 char* 267 rflush(Fid *f) 268 { 269 USED(f); 270 return 0; 271 } 272 273 char* 274 rattach(Fid *f) 275 { 276 /* no authentication! */ 277 f->busy = 1; 278 f->rclose = 0; 279 f->ram = &ram[0]; 280 rhdr.qid = f->ram->qid; 281 if(thdr.uname[0]) 282 f->user = estrdup(thdr.uname); 283 else 284 f->user = "none"; 285 if(strcmp(user, "none") == 0) 286 user = f->user; 287 return 0; 288 } 289 290 char* 291 clone(Fid *f, Fid **nf) 292 { 293 if(f->open) 294 return Eisopen; 295 if(f->ram->busy == 0) 296 return Enotexist; 297 *nf = newfid(thdr.newfid); 298 (*nf)->busy = 1; 299 (*nf)->open = 0; 300 (*nf)->rclose = 0; 301 (*nf)->ram = f->ram; 302 (*nf)->user = f->user; /* no ref count; the leakage is minor */ 303 return 0; 304 } 305 306 char* 307 rwalk(Fid *f) 308 { 309 Ram *r, *fram; 310 char *name; 311 Ram *parent; 312 Fid *nf; 313 char *err; 314 ulong t; 315 int i; 316 317 err = nil; 318 nf = nil; 319 rhdr.nwqid = 0; 320 if(thdr.newfid != thdr.fid){ 321 err = clone(f, &nf); 322 if(err) 323 return err; 324 f = nf; /* walk the new fid */ 325 } 326 fram = f->ram; 327 if(thdr.nwname > 0){ 328 t = time(0); 329 for(i=0; i<thdr.nwname && i<MAXWELEM; i++){ 330 if((fram->qid.type & QTDIR) == 0){ 331 err = Enotdir; 332 break; 333 } 334 if(fram->busy == 0){ 335 err = Enotexist; 336 break; 337 } 338 fram->atime = t; 339 name = thdr.wname[i]; 340 if(strcmp(name, ".") == 0){ 341 Found: 342 rhdr.nwqid++; 343 rhdr.wqid[i] = fram->qid; 344 continue; 345 } 346 parent = &ram[fram->parent]; 347 if(!perm(f, parent, Pexec)){ 348 err = Eperm; 349 break; 350 } 351 if(strcmp(name, "..") == 0){ 352 fram = parent; 353 goto Found; 354 } 355 for(r=ram; r < &ram[nram]; r++) 356 if(r->busy && r->parent==fram-ram && strcmp(name, r->name)==0){ 357 fram = r; 358 goto Found; 359 } 360 break; 361 } 362 if(i==0 && err == nil) 363 err = Enotexist; 364 } 365 if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){ 366 /* clunk the new fid, which is the one we walked */ 367 f->busy = 0; 368 f->ram = nil; 369 } 370 if(rhdr.nwqid > 0) 371 err = nil; /* didn't get everything in 9P2000 right! */ 372 if(rhdr.nwqid == thdr.nwname) /* update the fid after a successful walk */ 373 f->ram = fram; 374 return err; 375 } 376 377 char * 378 ropen(Fid *f) 379 { 380 Ram *r; 381 int mode, trunc; 382 383 if(f->open) 384 return Eisopen; 385 r = f->ram; 386 if(r->busy == 0) 387 return Enotexist; 388 if(r->perm & DMEXCL) 389 if(r->open) 390 return Excl; 391 mode = thdr.mode; 392 if(r->qid.type & QTDIR){ 393 if(mode != OREAD) 394 return Eperm; 395 rhdr.qid = r->qid; 396 return 0; 397 } 398 if(mode & ORCLOSE){ 399 /* can't remove root; must be able to write parent */ 400 if(r->qid.path==0 || !perm(f, &ram[r->parent], Pwrite)) 401 return Eperm; 402 f->rclose = 1; 403 } 404 trunc = mode & OTRUNC; 405 mode &= OPERM; 406 if(mode==OWRITE || mode==ORDWR || trunc) 407 if(!perm(f, r, Pwrite)) 408 return Eperm; 409 if(mode==OREAD || mode==ORDWR) 410 if(!perm(f, r, Pread)) 411 return Eperm; 412 if(mode==OEXEC) 413 if(!perm(f, r, Pexec)) 414 return Eperm; 415 if(trunc && (r->perm&DMAPPEND)==0){ 416 r->ndata = 0; 417 if(r->data) 418 free(r->data); 419 r->data = 0; 420 r->qid.vers++; 421 } 422 rhdr.qid = r->qid; 423 rhdr.iounit = messagesize-IOHDRSZ; 424 f->open = 1; 425 r->open++; 426 return 0; 427 } 428 429 char * 430 rcreate(Fid *f) 431 { 432 Ram *r; 433 char *name; 434 long parent, prm; 435 436 if(f->open) 437 return Eisopen; 438 if(f->ram->busy == 0) 439 return Enotexist; 440 parent = f->ram - ram; 441 if((f->ram->qid.type&QTDIR) == 0) 442 return Enotdir; 443 /* must be able to write parent */ 444 if(!perm(f, f->ram, Pwrite)) 445 return Eperm; 446 prm = thdr.perm; 447 name = thdr.name; 448 if(strcmp(name, ".")==0 || strcmp(name, "..")==0) 449 return Ename; 450 for(r=ram; r<&ram[nram]; r++) 451 if(r->busy && parent==r->parent) 452 if(strcmp((char*)name, r->name)==0) 453 return Einuse; 454 for(r=ram; r->busy; r++) 455 if(r == &ram[Nram-1]) 456 return "no free ram resources"; 457 r->busy = 1; 458 r->qid.path = ++path; 459 r->qid.vers = 0; 460 if(prm & DMDIR) 461 r->qid.type |= QTDIR; 462 r->parent = parent; 463 free(r->name); 464 r->name = estrdup(name); 465 r->user = f->user; 466 r->group = f->ram->group; 467 r->muid = f->ram->muid; 468 if(prm & DMDIR) 469 prm = (prm&~0777) | (f->ram->perm&prm&0777); 470 else 471 prm = (prm&(~0777|0111)) | (f->ram->perm&prm&0666); 472 r->perm = prm; 473 r->ndata = 0; 474 if(r-ram >= nram) 475 nram = r - ram + 1; 476 r->atime = time(0); 477 r->mtime = r->atime; 478 f->ram->mtime = r->atime; 479 f->ram = r; 480 rhdr.qid = r->qid; 481 rhdr.iounit = messagesize-IOHDRSZ; 482 f->open = 1; 483 if(thdr.mode & ORCLOSE) 484 f->rclose = 1; 485 r->open++; 486 return 0; 487 } 488 489 char* 490 rread(Fid *f) 491 { 492 Ram *r; 493 uchar *buf; 494 long off; 495 int n, m, cnt; 496 497 if(f->ram->busy == 0) 498 return Enotexist; 499 n = 0; 500 rhdr.count = 0; 501 off = thdr.offset; 502 buf = rdata; 503 cnt = thdr.count; 504 if(cnt > messagesize) /* shouldn't happen, anyway */ 505 cnt = messagesize; 506 if(f->ram->qid.type & QTDIR){ 507 for(r=ram+1; off > 0; r++){ 508 if(r->busy && r->parent==f->ram-ram) 509 off -= ramstat(r, statbuf, sizeof statbuf); 510 if(r == &ram[nram-1]) 511 return 0; 512 } 513 for(; r<&ram[nram] && n < cnt; r++){ 514 if(!r->busy || r->parent!=f->ram-ram) 515 continue; 516 m = ramstat(r, buf+n, cnt-n); 517 if(m == 0) 518 break; 519 n += m; 520 } 521 rhdr.data = (char*)rdata; 522 rhdr.count = n; 523 return 0; 524 } 525 r = f->ram; 526 if(off >= r->ndata) 527 return 0; 528 r->atime = time(0); 529 n = cnt; 530 if(off+n > r->ndata) 531 n = r->ndata - off; 532 rhdr.data = r->data+off; 533 rhdr.count = n; 534 return 0; 535 } 536 537 char* 538 rwrite(Fid *f) 539 { 540 Ram *r; 541 ulong off; 542 int cnt; 543 544 r = f->ram; 545 if(r->busy == 0) 546 return Enotexist; 547 off = thdr.offset; 548 if(r->perm & DMAPPEND) 549 off = r->ndata; 550 cnt = thdr.count; 551 if(r->qid.type & QTDIR) 552 return Eisdir; 553 if(off+cnt >= Maxsize) /* sanity check */ 554 return "write too big"; 555 if(off+cnt > r->ndata) 556 r->data = erealloc(r->data, off+cnt); 557 if(off > r->ndata) 558 memset(r->data+r->ndata, 0, off-r->ndata); 559 if(off+cnt > r->ndata) 560 r->ndata = off+cnt; 561 memmove(r->data+off, thdr.data, cnt); 562 r->qid.vers++; 563 r->mtime = time(0); 564 rhdr.count = cnt; 565 return 0; 566 } 567 568 static int 569 emptydir(Ram *dr) 570 { 571 long didx = dr - ram; 572 Ram *r; 573 574 for(r=ram; r<&ram[nram]; r++) 575 if(r->busy && didx==r->parent) 576 return 0; 577 return 1; 578 } 579 580 char * 581 realremove(Ram *r) 582 { 583 if(r->qid.type & QTDIR && !emptydir(r)) 584 return Enotempty; 585 r->ndata = 0; 586 if(r->data) 587 free(r->data); 588 r->data = 0; 589 r->parent = 0; 590 memset(&r->qid, 0, sizeof r->qid); 591 free(r->name); 592 r->name = nil; 593 r->busy = 0; 594 return nil; 595 } 596 597 char * 598 rclunk(Fid *f) 599 { 600 char *e = nil; 601 602 if(f->open) 603 f->ram->open--; 604 if(f->rclose) 605 e = realremove(f->ram); 606 f->busy = 0; 607 f->open = 0; 608 f->ram = 0; 609 return e; 610 } 611 612 char * 613 rremove(Fid *f) 614 { 615 Ram *r; 616 617 if(f->open) 618 f->ram->open--; 619 f->busy = 0; 620 f->open = 0; 621 r = f->ram; 622 f->ram = 0; 623 if(r->qid.path == 0 || !perm(f, &ram[r->parent], Pwrite)) 624 return Eperm; 625 ram[r->parent].mtime = time(0); 626 return realremove(r); 627 } 628 629 char * 630 rstat(Fid *f) 631 { 632 if(f->ram->busy == 0) 633 return Enotexist; 634 rhdr.nstat = ramstat(f->ram, statbuf, sizeof statbuf); 635 rhdr.stat = statbuf; 636 return 0; 637 } 638 639 char * 640 rwstat(Fid *f) 641 { 642 Ram *r, *s; 643 Dir dir; 644 645 if(f->ram->busy == 0) 646 return Enotexist; 647 convM2D(thdr.stat, thdr.nstat, &dir, (char*)statbuf); 648 r = f->ram; 649 650 /* 651 * To change length, must have write permission on file. 652 */ 653 if(dir.length!=~0 && dir.length!=r->ndata){ 654 if(!perm(f, r, Pwrite)) 655 return Eperm; 656 } 657 658 /* 659 * To change name, must have write permission in parent 660 * and name must be unique. 661 */ 662 if(dir.name[0]!='\0' && strcmp(dir.name, r->name)!=0){ 663 if(!perm(f, &ram[r->parent], Pwrite)) 664 return Eperm; 665 for(s=ram; s<&ram[nram]; s++) 666 if(s->busy && s->parent==r->parent) 667 if(strcmp(dir.name, s->name)==0) 668 return Eexist; 669 } 670 671 /* 672 * To change mode, must be owner or group leader. 673 * Because of lack of users file, leader=>group itself. 674 */ 675 if(dir.mode!=~0 && r->perm!=dir.mode){ 676 if(strcmp(f->user, r->user) != 0) 677 if(strcmp(f->user, r->group) != 0) 678 return Enotowner; 679 } 680 681 /* 682 * To change group, must be owner and member of new group, 683 * or leader of current group and leader of new group. 684 * Second case cannot happen, but we check anyway. 685 */ 686 if(dir.gid[0]!='\0' && strcmp(r->group, dir.gid)!=0){ 687 if(strcmp(f->user, r->user) == 0) 688 // if(strcmp(f->user, dir.gid) == 0) 689 goto ok; 690 if(strcmp(f->user, r->group) == 0) 691 if(strcmp(f->user, dir.gid) == 0) 692 goto ok; 693 return Enotowner; 694 ok:; 695 } 696 697 /* all ok; do it */ 698 if(dir.mode != ~0){ 699 dir.mode &= ~DMDIR; /* cannot change dir bit */ 700 dir.mode |= r->perm&DMDIR; 701 r->perm = dir.mode; 702 } 703 if(dir.name[0] != '\0'){ 704 free(r->name); 705 r->name = estrdup(dir.name); 706 } 707 if(dir.gid[0] != '\0') 708 r->group = estrdup(dir.gid); 709 if(dir.length!=~0 && dir.length!=r->ndata){ 710 r->data = erealloc(r->data, dir.length); 711 if(r->ndata < dir.length) 712 memset(r->data+r->ndata, 0, dir.length-r->ndata); 713 r->ndata = dir.length; 714 } 715 ram[r->parent].mtime = time(0); 716 return 0; 717 } 718 719 uint 720 ramstat(Ram *r, uchar *buf, uint nbuf) 721 { 722 int n; 723 Dir dir; 724 725 dir.name = r->name; 726 dir.qid = r->qid; 727 dir.mode = r->perm; 728 dir.length = r->ndata; 729 dir.uid = r->user; 730 dir.gid = r->group; 731 dir.muid = r->muid; 732 dir.atime = r->atime; 733 dir.mtime = r->mtime; 734 n = convD2M(&dir, buf, nbuf); 735 if(n > 2) 736 return n; 737 return 0; 738 } 739 740 Fid * 741 newfid(int fid) 742 { 743 Fid *f, *ff; 744 745 ff = 0; 746 for(f = fids; f; f = f->next) 747 if(f->fid == fid) 748 return f; 749 else if(!ff && !f->busy) 750 ff = f; 751 if(ff){ 752 ff->fid = fid; 753 return ff; 754 } 755 f = emalloc(sizeof *f); 756 f->ram = nil; 757 f->fid = fid; 758 f->next = fids; 759 fids = f; 760 return f; 761 } 762 763 void 764 io(void) 765 { 766 char *err, buf[40]; 767 int n, pid, ctl; 768 Fid *fid; 769 770 pid = getpid(); 771 if(private){ 772 snprint(buf, sizeof buf, "/proc/%d/ctl", pid); 773 ctl = open(buf, OWRITE); 774 if(ctl < 0){ 775 fprint(2, "can't protect ramfs\n"); 776 }else{ 777 fprint(ctl, "noswap\n"); 778 fprint(ctl, "private\n"); 779 close(ctl); 780 } 781 } 782 783 for(;;){ 784 /* 785 * reading from a pipe or a network device 786 * will give an error after a few eof reads. 787 * however, we cannot tell the difference 788 * between a zero-length read and an interrupt 789 * on the processes writing to us, 790 * so we wait for the error. 791 */ 792 n = read9pmsg(mfd[0], mdata, messagesize); 793 if(n < 0){ 794 rerrstr(buf, sizeof buf); 795 if(buf[0]=='\0' || strstr(buf, "hungup")) 796 exits(""); 797 error("mount read"); 798 } 799 if(n == 0) 800 continue; 801 if(convM2S(mdata, n, &thdr) == 0) 802 continue; 803 804 if(debug) 805 fprint(2, "ramfs %d:<-%F\n", pid, &thdr); 806 807 if(thdr.type<0 || thdr.type>=nelem(fcalls) || !fcalls[thdr.type]) 808 err = "bad fcall type"; 809 else if(((fid=newfid(thdr.fid))==nil || !fid->ram) && needfid[thdr.type]) 810 err = "fid not in use"; 811 else 812 err = (*fcalls[thdr.type])(fid); 813 if(err){ 814 rhdr.type = Rerror; 815 rhdr.ename = err; 816 }else{ 817 rhdr.type = thdr.type + 1; 818 rhdr.fid = thdr.fid; 819 } 820 rhdr.tag = thdr.tag; 821 if(debug) 822 fprint(2, "ramfs %d:->%F\n", pid, &rhdr);/**/ 823 n = convS2M(&rhdr, mdata, messagesize); 824 if(n == 0) 825 error("convS2M error on write"); 826 if(write(mfd[1], mdata, n) != n) 827 error("mount write"); 828 } 829 } 830 831 int 832 perm(Fid *f, Ram *r, int p) 833 { 834 if((p*Pother) & r->perm) 835 return 1; 836 if(strcmp(f->user, r->group)==0 && ((p*Pgroup) & r->perm)) 837 return 1; 838 if(strcmp(f->user, r->user)==0 && ((p*Powner) & r->perm)) 839 return 1; 840 return 0; 841 } 842 843 void 844 error(char *s) 845 { 846 fprint(2, "%s: %s: %r\n", argv0, s); 847 exits(s); 848 } 849 850 void * 851 emalloc(ulong n) 852 { 853 void *p; 854 855 p = malloc(n); 856 if(!p) 857 error("out of memory"); 858 memset(p, 0, n); 859 return p; 860 } 861 862 void * 863 erealloc(void *p, ulong n) 864 { 865 p = realloc(p, n); 866 if(!p) 867 error("out of memory"); 868 return p; 869 } 870 871 char * 872 estrdup(char *q) 873 { 874 char *p; 875 int n; 876 877 n = strlen(q)+1; 878 p = malloc(n); 879 if(!p) 880 error("out of memory"); 881 memmove(p, q, n); 882 return p; 883 } 884 885 void 886 usage(void) 887 { 888 fprint(2, "usage: %s [-Dips] [-m mountpoint] [-S srvname]\n", argv0); 889 exits("usage"); 890 } 891