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