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