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