| #include <u.h> |
| #include <libc.h> |
| #include <fcall.h> |
| |
| /* |
| * Rather than reading /adm/users, which is a lot of work for |
| * a toy program, we assume all groups have the form |
| * NNN:user:user: |
| * meaning that each user is the leader of his own group. |
| */ |
| |
| enum |
| { |
| OPERM = 0x3, /* mask of all permission types in open mode */ |
| Nram = 2048, |
| Maxsize = 512*1024*1024, |
| Maxfdata = 8192 |
| }; |
| |
| typedef struct Fid Fid; |
| typedef struct Ram Ram; |
| |
| struct Fid |
| { |
| short busy; |
| short open; |
| short rclose; |
| int fid; |
| Fid *next; |
| char *user; |
| Ram *ram; |
| }; |
| |
| struct Ram |
| { |
| short busy; |
| short open; |
| long parent; /* index in Ram array */ |
| Qid qid; |
| long perm; |
| char *name; |
| ulong atime; |
| ulong mtime; |
| char *user; |
| char *group; |
| char *muid; |
| char *data; |
| long ndata; |
| }; |
| |
| enum |
| { |
| Pexec = 1, |
| Pwrite = 2, |
| Pread = 4, |
| Pother = 1, |
| Pgroup = 8, |
| Powner = 64 |
| }; |
| |
| ulong path; /* incremented for each new file */ |
| Fid *fids; |
| Ram ram[Nram]; |
| int nram; |
| int mfd[2]; |
| char *user; |
| uchar mdata[IOHDRSZ+Maxfdata]; |
| uchar rdata[Maxfdata]; /* buffer for data in reply */ |
| uchar statbuf[STATMAX]; |
| Fcall thdr; |
| Fcall rhdr; |
| int messagesize = sizeof mdata; |
| |
| Fid * newfid(int); |
| uint ramstat(Ram*, uchar*, uint); |
| void error(char*); |
| void io(void); |
| void *erealloc(void*, ulong); |
| void *emalloc(ulong); |
| char *estrdup(char*); |
| void usage(void); |
| int perm(Fid*, Ram*, int); |
| |
| char *rflush(Fid*), *rversion(Fid*), *rauth(Fid*), |
| *rattach(Fid*), *rwalk(Fid*), |
| *ropen(Fid*), *rcreate(Fid*), |
| *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), |
| *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*); |
| |
| char *(*fcalls[Tmax])(Fid*); |
| |
| static void |
| initfcalls(void) |
| { |
| fcalls[Tversion]= rversion; |
| fcalls[Tflush]= rflush; |
| fcalls[Tauth]= rauth; |
| fcalls[Tattach]= rattach; |
| fcalls[Twalk]= rwalk; |
| fcalls[Topen]= ropen; |
| fcalls[Tcreate]= rcreate; |
| fcalls[Tread]= rread; |
| fcalls[Twrite]= rwrite; |
| fcalls[Tclunk]= rclunk; |
| fcalls[Tremove]= rremove; |
| fcalls[Tstat]= rstat; |
| fcalls[Twstat]= rwstat; |
| } |
| |
| char Eperm[] = "permission denied"; |
| char Enotdir[] = "not a directory"; |
| char Enoauth[] = "ramfs: authentication not required"; |
| char Enotexist[] = "file does not exist"; |
| char Einuse[] = "file in use"; |
| char Eexist[] = "file exists"; |
| char Eisdir[] = "file is a directory"; |
| char Enotowner[] = "not owner"; |
| char Eisopen[] = "file already open for I/O"; |
| char Excl[] = "exclusive use file already open"; |
| char Ename[] = "illegal name"; |
| char Eversion[] = "unknown 9P version"; |
| char Enotempty[] = "directory not empty"; |
| char Ebadfid[] = "bad fid"; |
| |
| int debug; |
| int private; |
| |
| void |
| notifyf(void *a, char *s) |
| { |
| USED(a); |
| if(strncmp(s, "interrupt", 9) == 0) |
| noted(NCONT); |
| noted(NDFLT); |
| } |
| |
| void |
| main(int argc, char *argv[]) |
| { |
| Ram *r; |
| char *defmnt; |
| int p[2]; |
| int stdio = 0; |
| char *service; |
| |
| initfcalls(); |
| service = "ramfs"; |
| defmnt = nil; |
| ARGBEGIN{ |
| case 'D': |
| debug = 1; |
| break; |
| case 'i': |
| defmnt = 0; |
| stdio = 1; |
| mfd[0] = 0; |
| mfd[1] = 1; |
| break; |
| case 's': |
| defmnt = nil; |
| break; |
| case 'm': |
| defmnt = ARGF(); |
| break; |
| case 'p': |
| private++; |
| break; |
| case 'S': |
| defmnt = 0; |
| service = EARGF(usage()); |
| break; |
| default: |
| usage(); |
| }ARGEND |
| USED(defmnt); |
| |
| if(pipe(p) < 0) |
| error("pipe failed"); |
| if(!stdio){ |
| mfd[0] = p[0]; |
| mfd[1] = p[0]; |
| if(post9pservice(p[1], service, nil) < 0) |
| sysfatal("post9pservice %s: %r", service); |
| } |
| |
| user = getuser(); |
| notify(notifyf); |
| nram = 2; |
| r = &ram[0]; |
| r->busy = 1; |
| r->data = 0; |
| r->ndata = 0; |
| r->perm = DMDIR | 0775; |
| r->qid.type = QTDIR; |
| r->qid.path = 0; |
| r->qid.vers = 0; |
| r->parent = 0; |
| r->user = user; |
| r->group = user; |
| r->muid = user; |
| r->atime = time(0); |
| r->mtime = r->atime; |
| r->name = estrdup("."); |
| |
| r = &ram[1]; |
| r->busy = 1; |
| r->data = 0; |
| r->ndata = 0; |
| r->perm = 0666; |
| r->qid.type = 0; |
| r->qid.path = 1; |
| r->qid.vers = 0; |
| r->parent = 0; |
| r->user = user; |
| r->group = user; |
| r->muid = user; |
| r->atime = time(0); |
| r->mtime = r->atime; |
| r->name = estrdup("file"); |
| |
| if(debug) |
| fmtinstall('F', fcallfmt); |
| switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){ |
| case -1: |
| error("fork"); |
| case 0: |
| close(p[1]); |
| io(); |
| break; |
| default: |
| close(p[0]); /* don't deadlock if child fails */ |
| } |
| exits(0); |
| } |
| |
| char* |
| rversion(Fid *x) |
| { |
| Fid *f; |
| |
| USED(x); |
| for(f = fids; f; f = f->next) |
| if(f->busy) |
| rclunk(f); |
| if(thdr.msize > sizeof mdata) |
| rhdr.msize = sizeof mdata; |
| else |
| rhdr.msize = thdr.msize; |
| messagesize = rhdr.msize; |
| if(strncmp(thdr.version, "9P2000", 6) != 0) |
| return Eversion; |
| rhdr.version = "9P2000"; |
| return 0; |
| } |
| |
| char* |
| rauth(Fid *x) |
| { |
| if(x->busy) |
| return Ebadfid; |
| return "ramfs: no authentication required"; |
| } |
| |
| char* |
| rflush(Fid *f) |
| { |
| USED(f); |
| return 0; |
| } |
| |
| char* |
| rattach(Fid *f) |
| { |
| /* no authentication! */ |
| if(f->busy) |
| return Ebadfid; |
| f->busy = 1; |
| f->rclose = 0; |
| f->ram = &ram[0]; |
| rhdr.qid = f->ram->qid; |
| if(thdr.uname[0]) |
| f->user = estrdup(thdr.uname); |
| else |
| f->user = "none"; |
| if(strcmp(user, "none") == 0) |
| user = f->user; |
| return 0; |
| } |
| |
| char* |
| xclone(Fid *f, Fid **nf) |
| { |
| if(!f->busy) |
| return Ebadfid; |
| if(f->open) |
| return Eisopen; |
| if(f->ram->busy == 0) |
| return Enotexist; |
| *nf = newfid(thdr.newfid); |
| (*nf)->busy = 1; |
| (*nf)->open = 0; |
| (*nf)->rclose = 0; |
| (*nf)->ram = f->ram; |
| (*nf)->user = f->user; /* no ref count; the leakage is minor */ |
| return 0; |
| } |
| |
| char* |
| rwalk(Fid *f) |
| { |
| Ram *r, *fram; |
| char *name; |
| Ram *parent; |
| Fid *nf; |
| char *err; |
| ulong t; |
| int i; |
| |
| if(!f->busy) |
| return Ebadfid; |
| err = nil; |
| nf = nil; |
| rhdr.nwqid = 0; |
| if(thdr.newfid != thdr.fid){ |
| err = xclone(f, &nf); |
| if(err) |
| return err; |
| f = nf; /* walk the new fid */ |
| } |
| fram = f->ram; |
| if(thdr.nwname > 0){ |
| t = time(0); |
| for(i=0; i<thdr.nwname && i<MAXWELEM; i++){ |
| if((fram->qid.type & QTDIR) == 0){ |
| err = Enotdir; |
| break; |
| } |
| if(fram->busy == 0){ |
| err = Enotexist; |
| break; |
| } |
| fram->atime = t; |
| name = thdr.wname[i]; |
| if(strcmp(name, ".") == 0){ |
| Found: |
| rhdr.nwqid++; |
| rhdr.wqid[i] = fram->qid; |
| continue; |
| } |
| parent = &ram[fram->parent]; |
| if(!perm(f, parent, Pexec)){ |
| err = Eperm; |
| break; |
| } |
| if(strcmp(name, "..") == 0){ |
| fram = parent; |
| goto Found; |
| } |
| for(r=ram; r < &ram[nram]; r++) |
| if(r->busy && r->parent==fram-ram && strcmp(name, r->name)==0){ |
| fram = r; |
| goto Found; |
| } |
| break; |
| } |
| if(i==0 && err == nil) |
| err = Enotexist; |
| } |
| if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){ |
| /* clunk the new fid, which is the one we walked */ |
| fprint(2, "f %d zero busy\n", f->fid); |
| f->busy = 0; |
| f->ram = nil; |
| } |
| if(rhdr.nwqid == thdr.nwname) /* update the fid after a successful walk */ |
| f->ram = fram; |
| assert(f->busy); |
| return err; |
| } |
| |
| char * |
| ropen(Fid *f) |
| { |
| Ram *r; |
| int mode, trunc; |
| |
| if(!f->busy) |
| return Ebadfid; |
| if(f->open) |
| return Eisopen; |
| r = f->ram; |
| if(r->busy == 0) |
| return Enotexist; |
| if(r->perm & DMEXCL) |
| if(r->open) |
| return Excl; |
| mode = thdr.mode; |
| if(r->qid.type & QTDIR){ |
| if(mode != OREAD) |
| return Eperm; |
| rhdr.qid = r->qid; |
| return 0; |
| } |
| if(mode & ORCLOSE){ |
| /* can't remove root; must be able to write parent */ |
| if(r->qid.path==0 || !perm(f, &ram[r->parent], Pwrite)) |
| return Eperm; |
| f->rclose = 1; |
| } |
| trunc = mode & OTRUNC; |
| mode &= OPERM; |
| if(mode==OWRITE || mode==ORDWR || trunc) |
| if(!perm(f, r, Pwrite)) |
| return Eperm; |
| if(mode==OREAD || mode==ORDWR) |
| if(!perm(f, r, Pread)) |
| return Eperm; |
| if(mode==OEXEC) |
| if(!perm(f, r, Pexec)) |
| return Eperm; |
| if(trunc && (r->perm&DMAPPEND)==0){ |
| r->ndata = 0; |
| if(r->data) |
| free(r->data); |
| r->data = 0; |
| r->qid.vers++; |
| } |
| rhdr.qid = r->qid; |
| rhdr.iounit = messagesize-IOHDRSZ; |
| f->open = 1; |
| r->open++; |
| return 0; |
| } |
| |
| char * |
| rcreate(Fid *f) |
| { |
| Ram *r; |
| char *name; |
| long parent, prm; |
| |
| if(!f->busy) |
| return Ebadfid; |
| if(f->open) |
| return Eisopen; |
| if(f->ram->busy == 0) |
| return Enotexist; |
| parent = f->ram - ram; |
| if((f->ram->qid.type&QTDIR) == 0) |
| return Enotdir; |
| /* must be able to write parent */ |
| if(!perm(f, f->ram, Pwrite)) |
| return Eperm; |
| prm = thdr.perm; |
| name = thdr.name; |
| if(strcmp(name, ".")==0 || strcmp(name, "..")==0) |
| return Ename; |
| for(r=ram; r<&ram[nram]; r++) |
| if(r->busy && parent==r->parent) |
| if(strcmp((char*)name, r->name)==0) |
| return Einuse; |
| for(r=ram; r->busy; r++) |
| if(r == &ram[Nram-1]) |
| return "no free ram resources"; |
| r->busy = 1; |
| r->qid.path = ++path; |
| r->qid.vers = 0; |
| if(prm & DMDIR) |
| r->qid.type |= QTDIR; |
| r->parent = parent; |
| free(r->name); |
| r->name = estrdup(name); |
| r->user = f->user; |
| r->group = f->ram->group; |
| r->muid = f->ram->muid; |
| if(prm & DMDIR) |
| prm = (prm&~0777) | (f->ram->perm&prm&0777); |
| else |
| prm = (prm&(~0777|0111)) | (f->ram->perm&prm&0666); |
| r->perm = prm; |
| r->ndata = 0; |
| if(r-ram >= nram) |
| nram = r - ram + 1; |
| r->atime = time(0); |
| r->mtime = r->atime; |
| f->ram->mtime = r->atime; |
| f->ram = r; |
| rhdr.qid = r->qid; |
| rhdr.iounit = messagesize-IOHDRSZ; |
| f->open = 1; |
| if(thdr.mode & ORCLOSE) |
| f->rclose = 1; |
| r->open++; |
| return 0; |
| } |
| |
| char* |
| rread(Fid *f) |
| { |
| Ram *r; |
| uchar *buf; |
| long off; |
| int n, m, cnt; |
| |
| if(!f->busy) |
| return Ebadfid; |
| if(f->ram->busy == 0) |
| return Enotexist; |
| n = 0; |
| rhdr.count = 0; |
| off = thdr.offset; |
| buf = rdata; |
| cnt = thdr.count; |
| if(cnt > messagesize) /* shouldn't happen, anyway */ |
| cnt = messagesize; |
| if(f->ram->qid.type & QTDIR){ |
| for(r=ram+1; off > 0; r++){ |
| if(r->busy && r->parent==f->ram-ram) |
| off -= ramstat(r, statbuf, sizeof statbuf); |
| if(r == &ram[nram-1]) |
| return 0; |
| } |
| for(; r<&ram[nram] && n < cnt; r++){ |
| if(!r->busy || r->parent!=f->ram-ram) |
| continue; |
| m = ramstat(r, buf+n, cnt-n); |
| if(m == 0) |
| break; |
| n += m; |
| } |
| rhdr.data = (char*)rdata; |
| rhdr.count = n; |
| return 0; |
| } |
| r = f->ram; |
| if(off >= r->ndata) |
| return 0; |
| r->atime = time(0); |
| n = cnt; |
| if(off+n > r->ndata) |
| n = r->ndata - off; |
| rhdr.data = r->data+off; |
| rhdr.count = n; |
| return 0; |
| } |
| |
| char* |
| rwrite(Fid *f) |
| { |
| Ram *r; |
| ulong off; |
| int cnt; |
| |
| r = f->ram; |
| if(!f->busy) |
| return Ebadfid; |
| if(r->busy == 0) |
| return Enotexist; |
| off = thdr.offset; |
| if(r->perm & DMAPPEND) |
| off = r->ndata; |
| cnt = thdr.count; |
| if(r->qid.type & QTDIR) |
| return Eisdir; |
| if(off+cnt >= Maxsize) /* sanity check */ |
| return "write too big"; |
| if(off+cnt > r->ndata) |
| r->data = erealloc(r->data, off+cnt); |
| if(off > r->ndata) |
| memset(r->data+r->ndata, 0, off-r->ndata); |
| if(off+cnt > r->ndata) |
| r->ndata = off+cnt; |
| memmove(r->data+off, thdr.data, cnt); |
| r->qid.vers++; |
| r->mtime = time(0); |
| rhdr.count = cnt; |
| return 0; |
| } |
| |
| static int |
| emptydir(Ram *dr) |
| { |
| long didx = dr - ram; |
| Ram *r; |
| |
| for(r=ram; r<&ram[nram]; r++) |
| if(r->busy && didx==r->parent) |
| return 0; |
| return 1; |
| } |
| |
| char * |
| realremove(Ram *r) |
| { |
| if(r->qid.type & QTDIR && !emptydir(r)) |
| return Enotempty; |
| r->ndata = 0; |
| if(r->data) |
| free(r->data); |
| r->data = 0; |
| r->parent = 0; |
| memset(&r->qid, 0, sizeof r->qid); |
| free(r->name); |
| r->name = nil; |
| r->busy = 0; |
| return nil; |
| } |
| |
| char * |
| rclunk(Fid *f) |
| { |
| char *e = nil; |
| |
| if(f->open) |
| f->ram->open--; |
| if(f->rclose) |
| e = realremove(f->ram); |
| fprint(2, "clunk fid %d busy=%d\n", f->fid, f->busy); |
| fprint(2, "f %d zero busy\n", f->fid); |
| f->busy = 0; |
| f->open = 0; |
| f->ram = 0; |
| return e; |
| } |
| |
| char * |
| rremove(Fid *f) |
| { |
| Ram *r; |
| |
| if(f->open) |
| f->ram->open--; |
| fprint(2, "f %d zero busy\n", f->fid); |
| f->busy = 0; |
| f->open = 0; |
| r = f->ram; |
| f->ram = 0; |
| if(r->qid.path == 0 || !perm(f, &ram[r->parent], Pwrite)) |
| return Eperm; |
| ram[r->parent].mtime = time(0); |
| return realremove(r); |
| } |
| |
| char * |
| rstat(Fid *f) |
| { |
| if(!f->busy) |
| return Ebadfid; |
| if(f->ram->busy == 0) |
| return Enotexist; |
| rhdr.nstat = ramstat(f->ram, statbuf, sizeof statbuf); |
| rhdr.stat = statbuf; |
| return 0; |
| } |
| |
| char * |
| rwstat(Fid *f) |
| { |
| Ram *r, *s; |
| Dir dir; |
| |
| if(!f->busy) |
| return Ebadfid; |
| if(f->ram->busy == 0) |
| return Enotexist; |
| convM2D(thdr.stat, thdr.nstat, &dir, (char*)statbuf); |
| r = f->ram; |
| |
| /* |
| * To change length, must have write permission on file. |
| */ |
| if(dir.length!=~0 && dir.length!=r->ndata){ |
| if(!perm(f, r, Pwrite)) |
| return Eperm; |
| } |
| |
| /* |
| * To change name, must have write permission in parent |
| * and name must be unique. |
| */ |
| if(dir.name[0]!='\0' && strcmp(dir.name, r->name)!=0){ |
| if(!perm(f, &ram[r->parent], Pwrite)) |
| return Eperm; |
| for(s=ram; s<&ram[nram]; s++) |
| if(s->busy && s->parent==r->parent) |
| if(strcmp(dir.name, s->name)==0) |
| return Eexist; |
| } |
| |
| /* |
| * To change mode, must be owner or group leader. |
| * Because of lack of users file, leader=>group itself. |
| */ |
| if(dir.mode!=~0 && r->perm!=dir.mode){ |
| if(strcmp(f->user, r->user) != 0) |
| if(strcmp(f->user, r->group) != 0) |
| return Enotowner; |
| } |
| |
| /* |
| * To change group, must be owner and member of new group, |
| * or leader of current group and leader of new group. |
| * Second case cannot happen, but we check anyway. |
| */ |
| if(dir.gid[0]!='\0' && strcmp(r->group, dir.gid)!=0){ |
| if(strcmp(f->user, r->user) == 0) |
| /* if(strcmp(f->user, dir.gid) == 0) */ |
| goto ok; |
| if(strcmp(f->user, r->group) == 0) |
| if(strcmp(f->user, dir.gid) == 0) |
| goto ok; |
| return Enotowner; |
| ok:; |
| } |
| |
| /* all ok; do it */ |
| if(dir.mode != ~0){ |
| dir.mode &= ~DMDIR; /* cannot change dir bit */ |
| dir.mode |= r->perm&DMDIR; |
| r->perm = dir.mode; |
| } |
| if(dir.name[0] != '\0'){ |
| free(r->name); |
| r->name = estrdup(dir.name); |
| } |
| if(dir.gid[0] != '\0') |
| r->group = estrdup(dir.gid); |
| if(dir.length!=~0 && dir.length!=r->ndata){ |
| r->data = erealloc(r->data, dir.length); |
| if(r->ndata < dir.length) |
| memset(r->data+r->ndata, 0, dir.length-r->ndata); |
| r->ndata = dir.length; |
| } |
| ram[r->parent].mtime = time(0); |
| return 0; |
| } |
| |
| uint |
| ramstat(Ram *r, uchar *buf, uint nbuf) |
| { |
| int n; |
| Dir dir; |
| |
| dir.name = r->name; |
| dir.qid = r->qid; |
| dir.mode = r->perm; |
| dir.length = r->ndata; |
| dir.uid = r->user; |
| dir.gid = r->group; |
| dir.muid = r->muid; |
| dir.atime = r->atime; |
| dir.mtime = r->mtime; |
| n = convD2M(&dir, buf, nbuf); |
| if(n > 2) |
| return n; |
| return 0; |
| } |
| |
| Fid * |
| newfid(int fid) |
| { |
| Fid *f, *ff; |
| |
| ff = 0; |
| for(f = fids; f; f = f->next) |
| if(f->fid == fid) |
| return f; |
| else if(!ff && !f->busy) |
| ff = f; |
| if(ff){ |
| ff->fid = fid; |
| return ff; |
| } |
| f = emalloc(sizeof *f); |
| f->ram = nil; |
| f->fid = fid; |
| f->next = fids; |
| fids = f; |
| return f; |
| } |
| |
| void |
| io(void) |
| { |
| char *err, buf[20]; |
| int n, pid, ctl; |
| |
| pid = getpid(); |
| if(private){ |
| snprint(buf, sizeof buf, "/proc/%d/ctl", pid); |
| ctl = open(buf, OWRITE); |
| if(ctl < 0){ |
| fprint(2, "can't protect ramfs\n"); |
| }else{ |
| fprint(ctl, "noswap\n"); |
| fprint(ctl, "private\n"); |
| close(ctl); |
| } |
| } |
| |
| for(;;){ |
| /* |
| * reading from a pipe or a network device |
| * will give an error after a few eof reads. |
| * however, we cannot tell the difference |
| * between a zero-length read and an interrupt |
| * on the processes writing to us, |
| * so we wait for the error. |
| */ |
| n = read9pmsg(mfd[0], mdata, messagesize); |
| if(n < 0) |
| error("mount read"); |
| if(n == 0) |
| error("mount eof"); |
| if(convM2S(mdata, n, &thdr) == 0) |
| continue; |
| |
| if(debug) |
| fprint(2, "ramfs %d:<-%F\n", pid, &thdr); |
| |
| if(!fcalls[thdr.type]) |
| err = "bad fcall type"; |
| else |
| err = (*fcalls[thdr.type])(newfid(thdr.fid)); |
| if(err){ |
| rhdr.type = Rerror; |
| rhdr.ename = err; |
| }else{ |
| rhdr.type = thdr.type + 1; |
| rhdr.fid = thdr.fid; |
| } |
| rhdr.tag = thdr.tag; |
| if(debug) |
| fprint(2, "ramfs %d:->%F\n", pid, &rhdr);/**/ |
| n = convS2M(&rhdr, mdata, messagesize); |
| if(n == 0) |
| error("convS2M error on write"); |
| if(write(mfd[1], mdata, n) != n) |
| error("mount write"); |
| } |
| } |
| |
| int |
| perm(Fid *f, Ram *r, int p) |
| { |
| if((p*Pother) & r->perm) |
| return 1; |
| if(strcmp(f->user, r->group)==0 && ((p*Pgroup) & r->perm)) |
| return 1; |
| if(strcmp(f->user, r->user)==0 && ((p*Powner) & r->perm)) |
| return 1; |
| return 0; |
| } |
| |
| void |
| error(char *s) |
| { |
| fprint(2, "%s: %s: %r\n", argv0, s); |
| exits(s); |
| } |
| |
| void * |
| emalloc(ulong n) |
| { |
| void *p; |
| |
| p = malloc(n); |
| if(!p) |
| error("out of memory"); |
| memset(p, 0, n); |
| return p; |
| } |
| |
| void * |
| erealloc(void *p, ulong n) |
| { |
| p = realloc(p, n); |
| if(!p) |
| error("out of memory"); |
| return p; |
| } |
| |
| char * |
| estrdup(char *q) |
| { |
| char *p; |
| int n; |
| |
| n = strlen(q)+1; |
| p = malloc(n); |
| if(!p) |
| error("out of memory"); |
| memmove(p, q, n); |
| return p; |
| } |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: %s [-is] [-m mountpoint]\n", argv0); |
| exits("usage"); |
| } |
| |