ref: 84d66beb90246495861436c041297528298ba74f
dir: /9p.c/
#include "all.h" Tlock *tlockhead = nil, *tlocktail = nil; QLock tlock; extern u32 mpsrvpid; extern u8 synchronouswrites; s32 readfile(u64 dblkno, u64 qpath, char *rbuf, s32 rbufsize, u64 offset); s32 writefile(u64 dblkno, u64 qpath, s16 uid, char *wbuf, s32 wbufsize, u64 offset); s32 writeallappend(Iobuf *dbuf, Dentry *d, u64 dblkno); Aux* newaux(u64 addr, u16 uid) { Aux *a; a = emalloc9p(sizeof(Aux)); a->dblkno = addr; a->uid = uid; return a; } void freeaux(Aux *a) { if(a == nil) return; free(a); } static void fsattach(Req *req) { short uid; Iobuf *dbuf; if(authattach(req) < 0){ return; } if((uid = lookupid(req->ifcall.uname)) <= 0){ respond(req, "no such user"); return; } dbuf = getmetachk(Bdroot, Breadonly, Tdentry, Qproot); if(dbuf == nil){ respond(req, "phase error"); return; } req->fid->aux = newaux(Bdroot, uid); req->fid->qid = (Qid){Qproot, dbuf->cur->version, QTDIR}; putbuf(dbuf, 0); req->ofcall.qid = req->fid->qid; respond(req, nil); } static void fsdestroyfid(Fid *fid) { Tlock *t; if((fid->qid.type & QTAUTH) != 0){ authdestroy(fid); return; } if(((Aux*)fid->aux)->tlocked == 1){ qlock(&tlock); if(tlocktail == nil){ qunlock(&tlock); panic("locked but tlock queue is empty\n"); return; }else{ for(t = tlockhead; t != nil; t = t->next){ if(t->dblkno == ((Aux*)fid->aux)->dblkno && t->qpath == fid->qid.path){ if(t->prev != nil) t->prev->next = t->next; if(t->next != nil) t->next->prev = t->prev; if(tlocktail == t) tlocktail = t->prev; if(tlockhead == t) tlockhead = t->next; ((Aux*)fid->aux)->tlocked = 0; free(t); break; } } } qunlock(&tlock); } /* dbuf = getmetachk(((Aux*)fid->aux)->dblkno, Bwritable, Tdentry, fid->qid.path); if(dbuf != nil){ writeallappend(dbuf, (Dentry*)dbuf->new, ((Aux*)fid->aux)->dblkno); putbuf(dbuf, 1); } */ freeaux(fid->aux); } static void fsmkdir(Dentry *d, Dir *dir, char *buf, u64 appendsize) { memset(dir, 0, sizeof(*dir)); dir->qid = (Qid){d->path, d->version, (d->mode&DMDIR)? QTDIR : QTFILE}; dir->mode = (d->mode & 0777) | (dir->qid.type << 24); dir->atime = time(nil); dir->mtime = d->mtime/Nsec; /* ns to seconds */ dir->length = d->size+appendsize; if(dir->qid.type & QTDIR) dir->length = 0; if(buf == nil){ dir->name = estrdup9p(d->name); dir->uid = username(d->uid, dir->uid); dir->gid = username(d->gid, dir->gid); dir->muid = username(d->muid, dir->muid); }else{ memset(buf, 0, Namelen + 3 * Userlen); strncpy(buf, d->name, Namelen - 1); dir->name = buf; dir->uid = username(d->uid, buf + Namelen); dir->gid = username(d->gid, buf + Namelen + Userlen); dir->muid = username(d->muid, buf + Namelen + 2 * Userlen); } } static void fsstat(Req *req) { Dentry *d; Iobuf *dbuf; dbuf = getmetachk(((Aux*)req->fid->aux)->dblkno, Breadonly, Tdentry, req->fid->qid.path); if(dbuf == nil){ dprint("fsstat dbuf == nil dblkno %llud qpath %llud\n", ((Aux*)req->fid->aux)->dblkno, req->fid->qid.path); respond(req, errstring[Ephase]); return; } d = (Dentry*)dbuf->cur; /* nothing to do for already zero'ed out slots */ if(d->path != Qpnone) fsmkdir(d, &req->d, nil, dbuf->appendsize); putbuf(dbuf, 0); respond(req, nil); } int emptystr(char *s) { if(s == nil) return 1; if(s[0] == '\0') return 1; return 0; } static void fswstat(Req *req) { Dentry *d; Iobuf *dbuf; s16 gid; if(shuttingdown){ respond(req, errstring[Eshutdown]); return; } if(readonly){ respond(req, errstring[Eronly]); return; } if(req->fid->qid.path == Qpctl){ respond(req, errstring[Einval]); return; } if((req->d.type & QTDIR) > 0 && req->d.length != ~0){ respond(req, errstring[Einval]); return; } if(((Aux*)req->fid->aux)->uid == None) goto noperm; /* TODO allow changing the length as per stat(5) */ if(req->d.atime != ~0 || req->d.length != ~0) goto noperm; /* stat(5) says that this is illegal */ if(emptystr(req->d.uid) == 0){ respond(req, errstring[Einval]); return; } /* TODO cwfs/9p2.c, hjfs/fs2.c and kfs64.b do more here. Get that stuff in. */ dbuf = getmetachk(((Aux*)req->fid->aux)->dblkno, Bwritable, Tdentry, req->fid->qid.path); if(dbuf == nil){ respond(req, errstring[Eperm]); return; } d = (Dentry*)dbuf->new; if(canaccess(((Aux*)req->fid->aux)->uid, d, DMWRITE) == 0){ putbuf(dbuf, 0); respond(req, errstring[Eaccess]); return; } /* invalid to change the directory bit stat(5) */ if(req->d.mode != ~0 && (req->d.mode&DMDIR) != (d->mode&DMDIR)){ putbuf(dbuf, 0); respond(req, errstring[Einval]); return; } if(emptystr(req->d.name) == 0){ strncpy(d->name, req->d.name, Namelen); d->mtime = nsec(); d->muid = ((Aux*)req->fid->aux)->uid; } if(d->uid == ((Aux*)req->fid->aux)->uid || leadgroup(((Aux*)req->fid->aux)->uid, d->gid) == 1){ if(req->d.mtime != ~0){ d->mtime = req->d.mtime*Nsec; d->muid = ((Aux*)req->fid->aux)->uid; } if(req->d.mode != ~0 && (req->d.mode&DMDIR) == (d->mode&DMDIR) && req->d.mode != d->mode) d->mode = req->d.mode; /* TODO gid setting needs more fine tuning to align with stat(5) */ if(emptystr(req->d.gid) == 0 && (gid = lookupid(req->d.gid)) != 0){ d->gid = gid; } } respond(req, nil); putbuf(dbuf, 1); return; noperm: respond(req, errstring[Eperm]); return; } static void fsread(Req *req) { s32 n; Iobuf *dbuf, *cbuf; Dentry *d; char nbuf[Namelen + 3 * Userlen]; Dir dir; if(shuttingdown){ respond(req, errstring[Eshutdown]); return; } if(req->fid->qid.type == QTAUTH){ authread(req); return; }else if(req->fid->qid.path == Qpctl){ ctlread(req); return; } if((req->fid->qid.type & QTDIR) != 0){ /* using this loop to skip over zero'ed out blocks */ dbuf = cbuf = nil; do{ if(dbuf != nil) freesearchstate(&dbuf, &cbuf); /* some directory, find the child at offset */ d = searchdir(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path, ((Aux*)req->fid->aux)->uid, nil, ((Aux*)req->fid->aux)->dri, &dbuf, &cbuf); if(d == nil){ req->ofcall.offset = req->ifcall.offset; req->ofcall.count = 0; ((Aux*)req->fid->aux)->dri = 0; respond(req, nil); return; } ((Aux*)req->fid->aux)->dri++; }while(d->path == 0); fsmkdir(d, &dir, nbuf, cbuf->appendsize); req->ofcall.count = n = convD2M(&dir, (u8*)req->ofcall.data, req->ifcall.count); req->ofcall.offset = req->ifcall.offset+n; if(n == 0) ((Aux*)req->fid->aux)->dri = 0; freesearchstate(&dbuf, &cbuf); respond(req, nil); return; } n = readfile(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path, req->ofcall.data, req->ifcall.count, req->ifcall.offset); req->ofcall.count = n; req->ofcall.offset = req->ifcall.offset+n; respond(req, nil); } static void fswrite(Req *req) { s32 rv; if(shuttingdown){ respond(req, errstring[Eshutdown]); return; } if(readonly){ respond(req, errstring[Eronly]); return; } if(req->fid->qid.type == QTAUTH){ authwrite(req); return; }else if(req->fid->qid.path == Qpctl){ ctlwrite(req); return; } if((req->fid->qid.type & QTDIR) != 0){ respond(req, errstring[Einval]); return; } rv = writefile(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path, ((Aux*)req->fid->aux)->uid, req->ifcall.data, req->ifcall.count, req->ifcall.offset); if(rv == -3) respond(req, errstring[Efull]); else if(rv == -1 || rv == -2) respond(req, errstring[Ephase]); else{ req->ofcall.count = rv; req->ofcall.offset = req->ifcall.offset+rv; respond(req, nil); } } static void fsremove(Req *req) { Fid *fid; Aux *aux; if(shuttingdown){ respond(req, errstring[Eshutdown]); return; } if(readonly){ respond(req, errstring[Eronly]); return; } fid = req->fid; aux = fid->aux; if(aux == nil || aux->uid == None || fid->qid.path < Qproot || aux->dblkno == 0){ respond(req, errstring[Eperm]); return; } if(fid->qid.type & QTDIR) rmdirectory(fid->qid.path, aux->dblkno); else rmfile(fid->qid.path, aux->dblkno); respond(req, nil); } static char* fswalk1(Fid *fid, char *name, Qid *qid) { Aux *aux; Dentry *d, *p; Iobuf *dbuf, *pbuf, *cbuf; if(shuttingdown){ return nil; } if((fid->qid.type&QTDIR) == 0) return errstring[Enotdir]; aux = fid->aux; if(strcmp(name, "..") == 0){ switch(fid->qid.path){ case Qproot: return nil; default: if(chatty9p > 1) dprint("fswalk1 .. fid->qid.path %llud aux->dblkno %llud\n", fid->qid.path, aux->dblkno); dbuf = getmetachk(aux->dblkno, Breadonly, Tdentry, fid->qid.path); if(dbuf == nil) return errstring[Ephase]; d = (Dentry*)dbuf->cur; if(chatty9p > 1) dprint("fswalk1 .. d->name %s d->qid.path %llud d->pdblkno %llud\n", d->name, d->path, d->pdblkno); pbuf = getmetachk(d->pdblkno, Breadonly, Tdentry, d->pqpath); if(pbuf == nil) return errstring[Ephase]; putbuf(dbuf, 0); p = (Dentry*)pbuf->cur; *qid = (Qid){p->path, p->version, (p->mode&DMDIR) ? QTDIR : QTFILE}; if(aux != nil) aux->dblkno = pbuf->blkno; putbuf(pbuf, 0); fid->qid = *qid; return nil; } } /* assuming that it will ever be here only for directories */ /* some directory, find the child with name or idx */ d = searchdir(aux->dblkno, fid->qid.path, aux->uid, name, 0, &dbuf, &cbuf); if(d == nil) return "directory entry not found"; if((d->mode&DMDIR) && canaccess(d->uid, d, DMEXEC) == 0){ freesearchstate(&dbuf, &cbuf); return errstring[Eperm]; } *qid = (Qid){d->path, d->version, (d->mode&DMDIR) ? QTDIR : QTFILE}; if(aux != nil) aux->dblkno = cbuf->blkno; freesearchstate(&dbuf, &cbuf); fid->qid = *qid; return nil; } static char* fsclone(Fid *oldfid, Fid *newfid) { Aux *o; if(shuttingdown){ return nil; } o = oldfid->aux; if(o == nil) return "bad fid"; newfid->aux = newaux(o->dblkno, o->uid); return nil; } /* * error(Eperm) if open permission not granted for up->newr. */ int permcheck(u16 fileuid, u16 filegid, u16 uid, ulong perm, int omode) { ulong t; static int access[] = { 0400, 0200, 0600, 0100 }; if(uid == fileuid) perm <<= 0; else if(ingroup(uid, filegid, 0) == 0) perm <<= 3; else perm <<= 6; t = access[omode&3]; if((t&perm) != t) return 0; return 1; } /* read the Req.ifcall's perm, name and mode and build the Fid.omode */ static void fscreate(Req *req) { Iobuf *dbuf, *cbuf; u64 reli, blkno; Dentry *dchild, *dparent; u64 zblkno; /* zero'ed dentry that can be reused */ Aux *aux; u32 perm; Fid *fid; Tlock *t; /* no create's when shutting down */ if(shuttingdown){ respond(req, errstring[Eshutdown]); return; } fid = req->fid; aux = fid->aux; if(chatty9p >2) dprint("fscreate aux 0x%p aux->uid %d fid->qid.path %d aux->dblkno %llud\n", aux, aux->uid, fid->qid.path, aux->dblkno); if(aux == nil || aux->uid == None || fid->qid.path < Qproot || aux->dblkno == 0){ respond(req, errstring[Eaccess]); return; } if(strlen(req->ifcall.name) > Namelen){ respond(req, errstring[Etoolong]); return; } if((fid->qid.type & QTDIR) == 0){ respond(req, errstring[Enotdir]); return; } if(req->ifcall.name == nil || strlen(req->ifcall.name) == 0 || req->ifcall.name[0] == '/' || checkname9p2(req->ifcall.name) == 0){ respond(req, errstring[Ebadname]); return; } if(readonly){ respond(req, errstring[Eaccess]); return; } perm = req->ifcall.perm; cbuf = nil; USED(cbuf); zblkno = 0; dbuf = getmetachk(aux->dblkno, Bwritable, Tdentry, fid->qid.path); if(dbuf == nil){ respond(req, errstring[Ephase]); return; } dparent = (Dentry*)dbuf->new; if(canaccess(((Aux*)req->fid->aux)->uid, dparent, DMWRITE) == 0){ respond(req, errstring[Eperm]); return; } if(canaccess(aux->uid, dparent, DMWRITE) == 0){ putbuf(dbuf, 0); respond(req, errstring[Eaccess]); return; } fid->omode = OREAD; switch(req->ifcall.mode & 7) { case OREAD: case OEXEC: fid->omode = OREAD; break; case OWRITE: fid->omode = OWRITE; break; case ORDWR: fid->omode = OREAD+OWRITE; break; default: putbuf(dbuf, 0); respond(req, errstring[Emode]); return; } for(reli = 0, blkno = 1; blkno > 0; reli++){ blkno = rel2abs(dparent, reli); /* if(reli >= Max) error(Etoobig); */ if(blkno == 0){ /* end reached, nothing found, create */ if(zblkno != 0){ cbuf = getmetachk(zblkno, Bwritable, Tdentry, Qpnone); if(cbuf == nil){ putbuf(dbuf, 0); respond(req, errstring[Ephase]); return; } }else{ cbuf = allocmeta(Tdentry, fid->qid.path); if(cbuf == nil){ putbuf(dbuf, 0); respond(req, errstring[Efull]); return; } } dchild = (Dentry*)cbuf->new; dchild->size = 0; dchild->pdblkno = dbuf->blkno; dchild->pqpath = dparent->path; dchild->mtime = nsec(); dchild->uid = dchild->muid = aux->uid; dchild->gid = dparent->gid; /* TODO DMAPPEND attributes */ if(perm&DMDIR){ if((req->ifcall.mode & OTRUNC) || (perm & DMAPPEND) || (fid->omode & OWRITE)){ putbuf(cbuf, 1); putbuf(dbuf, 0); respond(req, errstring[Eaccess]); return; } } if(perm&DMDIR) dchild->mode = DMDIR | (perm & (~0777 | (dparent->mode & 0777))); else dchild->mode = perm & (~0666 | (dparent->mode & 0666)); if(perm&DMAPPEND) dchild->mode |= DMAPPEND; if(perm&DMEXCL) dchild->mode |= DMEXCL; if(canaccess(aux->uid, dchild, DMWRITE) == 0){ putbuf(cbuf, 0); putbuf(dbuf, 0); respond(req, errstring[Eaccess]); return; } dchild->qpath = dchild->path = newqpath(); dchild->version = 0; strncpy(dchild->name, req->ifcall.name, Namelen); fid->qid = (Qid){dchild->path, 0, (perm&DMDIR) ? QTDIR : QTFILE}; aux->dblkno = cbuf->blkno; aux->dri = 0; if(perm&DMEXCL){ t = emalloc9p(sizeof(Tlock)); t->time = nsec(); t->qpath = dchild->path; t->dblkno = aux->dblkno; qlock(&tlock); if(tlocktail == nil){ tlocktail = tlockhead = t; }else{ tlocktail->next = t; t->prev = tlocktail; tlocktail = t; } qunlock(&tlock); aux->tlocked = 1; }else aux->tlocked = 0; req->ofcall.qid = fid->qid; req->ofcall.iounit = Iounit; settag(cbuf, Tdentry, dchild->path); putbuf(cbuf, 1); /* save Iobuf of the content */ /* only add it to the directory dentry if we are adding a new dentry block if we are reusing a zero'ed out slot, it already exists in the directory dentry */ if(zblkno == 0 && addrelative(dparent, dbuf->blkno, reli, aux->dblkno) == 0){ putbuf(dbuf, 1); respond(req, errstring[Ephase]); return; } putbuf(dbuf, 1); respond(req, nil); return; }else{ cbuf = getmeta(blkno, Breadonly, Bused); if(cbuf == nil){ putbuf(dbuf, 0); respond(req, errstring[Ephase]); return; } dchild = (Dentry*)cbuf->cur; /* nothing to do for already zero'ed out slots */ if(dchild->qpath == Qpnone){ if(zblkno == 0) zblkno = cbuf->blkno; goto Nextdentry; } if(dchild->tag != Tdentry){ putbuf(cbuf, 0); putbuf(dbuf, 0); respond(req, errstring[Ephase]); return; } if(strcmp(req->ifcall.name, dchild->name) == 0){ /* req->ifcall.name matched, truncate file and use it */ if(permcheck(dchild->uid, dchild->gid, aux->uid, perm, ORDWR) == 0){ respond(req, errstring[Eperm]); return; } dchild->muid = aux->uid; fid->qid = (Qid){dchild->qpath, dchild->version, (req->ifcall.mode&DMDIR) ? QTDIR : QTFILE}; aux->dblkno = blkno; aux->dri = 0; req->ofcall.qid = fid->qid; req->ofcall.iounit = Iounit; /* save Iobuf of the child so truncate can open it with a wlock(), if needed */ putbuf(cbuf, 0); if(req->ofcall.qid.path >= Qpusers && req->ofcall.qid.type == QTFILE) truncatefile(req->ofcall.qid.path, blkno, aux->uid); putbuf(dbuf, 0); respond(req, nil); return; } Nextdentry: putbuf(cbuf, 0); } } } /* read the Req.ifcall.mode and build the Fid.omode based on the dentry */ static void fsopen(Req *req) { u8 mode, omode; Fid *fid; Iobuf *dbuf; Aux *aux; Dentry *d; Tlock *t; dbuf = nil; mode = req->ifcall.mode; fid = req->fid; aux = req->fid->aux; omode = 0; if(chatty9p > 1) dprint("fsopen fid->qid.path %d mode %d readonly %d\n", fid->qid.path, mode, readonly); if(readonly && (mode & (ORCLOSE | OTRUNC | OWRITE | ORDWR)) != 0) goto inval; dbuf = getmetachk(aux->dblkno, Breadonly, Tdentry, fid->qid.path); if(dbuf == nil){ respond(req, errstring[Ephase]); return; } d = (Dentry*)dbuf->cur; if((mode & OTRUNC) != 0 && canaccess(aux->uid, d, DMWRITE) == 0) goto perm; if((mode & ORCLOSE) != 0) if(canaccess(aux->uid, d, DMWRITE) == 0) goto perm; if((d->mode & DMAPPEND) != 0) mode &= ~OTRUNC; if((d->mode & DMDIR) != 0){ if((mode & (ORCLOSE | OTRUNC | OWRITE | ORDWR)) != 0) goto inval; if(canaccess(aux->uid, d, DMEXEC) == 0) goto perm; } if((d->mode & DMEXCL) != 0){ qlock(&tlock); if(tlocktail != nil){ for(t = tlockhead; t != nil; t = t->next){ if(t->dblkno == ((Aux*)fid->aux)->dblkno && t->qpath == fid->qid.path){ qunlock(&tlock); respond(req, "DMEXCL locked"); return; } } } t = emalloc9p(sizeof(Tlock)); t->time = nsec(); t->qpath = fid->qid.path; t->dblkno = aux->dblkno; if(tlocktail == nil){ tlocktail = tlockhead = t; }else{ tlocktail->next = t; t->prev = tlocktail; tlocktail = t; } qunlock(&tlock); aux->tlocked = 1; } switch(mode & OEXEC){ case ORDWR: omode |= ORDWR; case OWRITE: omode |= OWRITE; break; case OEXEC: case OREAD: omode |= OREAD; break; } if((mode & ORCLOSE) != 0) omode |= ORCLOSE; fid->qid = (Qid){d->path, d->version, (d->mode&DMDIR) ? QTDIR : QTFILE}; fid->omode = omode; req->ofcall.iounit = Iounit; req->ofcall.qid = fid->qid; putbuf(dbuf, 0); if((mode & OTRUNC) == 0){ respond(req, nil); return; } /* truncate file */ truncatefile(fid->qid.path, aux->dblkno, aux->uid); respond(req, nil); return; inval: if(dbuf != nil) putbuf(dbuf, 0); respond(req, errstring[Einval]); return; perm: if(dbuf != nil) putbuf(dbuf, 0); respond(req, errstring[Eaccess]); return; } /* below is from nemo's Pg 252 */ typedef struct Buffer Buffer; typedef struct Work Work; struct Work { void (*f)(Req *r); Req *r; }; struct Buffer { QLock lck; Work works[Nworks]; u16 hd, tl, nworks; Rendez isfull; /* throttling */ Rendez isempty; /* workers do not have to keep polling to find work */ }; Buffer buf; Work get(Buffer *b) { Work w; if(shuttingdown) return (Work){nil,nil}; qlock(&b->lck); if(b->nworks == 0){ rsleep(&b->isempty); if(shuttingdown){ qunlock(&b->lck); return (Work){nil,nil}; } } w = b->works[b->hd]; b->hd = ++b->hd %Nworks; b->nworks--; if(b->nworks == Nworks-1) rwakeup(&b->isfull); qunlock(&b->lck); return w; } static void stats(void); void put(Buffer *b, void (*f)(Req *r), Req *r) { Work w; if(shuttingdown){ respond(r, errstring[Eshutdown]); qlock(&b->lck); rwakeupall(&b->isempty); qunlock(&b->lck); } w.f = f; w.r = r; qlock(&b->lck); if(b->nworks == Nworks){ rsleep(&b->isfull); if(shuttingdown){ qunlock(&b->lck); respond(r, errstring[Eshutdown]); } } b->works[b->tl] = w; b->tl = ++b->tl % Nworks; b->nworks++; if(b->nworks == 1) rwakeup(&b->isempty); qunlock(&b->lck); if(chatty9p > 1) stats(); } struct { u32 pid; Work w; } worker[Nworkers]; /* keeps track of running procs to flush */ void initworks(Buffer *b) { // release all locks, set everything to null values memset(b, 0, sizeof(*b)); // set the locks used by the Rendezes b->isempty.l = &b->lck; b->isfull.l = &b->lck; } int stopworkers(void) { int i, a; a = 0; for(i = 0; i<Nworkers; i++){ if(worker[i].w.f == nil){ if(worker[i].pid > 0 && worker[i].pid != getpid()){ // rwakeup(&buf.isempty); TODO why can't I get this to work? postnote(PNPROC, worker[i].pid, "interrupt"); worker[i].pid = 0; } }else a++; } return a; } static void stats(void) { int n, w, inv, i; n = w = inv = 0; for(i = 0; i<Nworkers; i++){ if(worker[i].pid == 0) inv++; else if(worker[i].w.f == nil) n++; else if(worker[i].w.f != nil) w++; } dprint("Nworkers %d inv %d idle %d working %d buf nworks %d hd %d tl %d\n", Nworkers, inv, n, w, buf.nworks, buf.hd, buf.tl); } /* there are 2 ways to shutdown: 1. by unmounting and removing the /srv/mfs_service file (can be rm and unmount too) 2. by writing halt to /adm/ctl file. unmount() the file system to keep it clean. In the first instance, the srv() process is driving the shutdown. It calls fsend(). rm /srv/mfs_service file does not wait for fsend() to finish. Hence, there is no way to ensure that any pending writes have been flushed to the disk. If the system is shutdown or restarted immediately, there is a high possibility that the filesystem will be in an inconsistent state. This is more probable when disk/mafs (asynchronous writes) is used. In the second instance, fsend() is called by the worker process. It does not return until all the pending writes have been flushed to the disk. It also removes the /srv/mfs_service file and also stops the srv() process. Hence, this is the preferred approach to shutting down the file system. There is no way to unmount() automatically on shutdown. The mount() and unmount() calls are client driven and it is not the responsibility of the server to find all the clients that mounted it. Just shutdown and let the respective clients deal with their mess. */ /* TODO need to track open fids and close them */ void shutdown(void) { u64 n; /* User *u, *v; */ if(chatty9p > 1) dprint("shutdown\n"); shuttingdown = 1; while((n=sync()) > 0){ // if(chatty9p > 1) dprint("shutdown: sync() finds %llud locked blocks\n", n); sleep(1000); } fsok(1); // showextents(&frees); // dprint("&buf.isempty %#p\n", &buf.isempty); if(mpsrvpid){ qlock(&buf.lck); rwakeupall(&buf.isempty); qunlock(&buf.lck); } /* if(synchronouswrites == 0){ while((n=pendingwrites())>0){ if(chatty9p > 1) dprint("shutdown: pendingwrites %llud of write queue\n", n); sleep(1000); } } */ savefrees(Bdfrees); /* if(synchronouswrites == 0){ while((n=pendingwrites())>0){ if(chatty9p > 1) dprint("shutdown: pendingwrites %llud of frees\n", n); sleep(1000); } stopwriter(); } */ /* free users, why bother? leave it alone */ /* u = t->newrs; while(u != nil){ v = u->next; free(u); u = v; } t->newrs = nil;*/ if(chatty9p > 1) dprint("shutdown: exiting\n"); /* chkqunlock(&superlock); exit while holding the lock */ close(devfd); } void work(Buffer *b, int id) { Work w; worker[id].pid = getpid(); w = get(b); while(w.f != nil){ worker[id].w = w; w.f(w.r); worker[id].w = (Work){nil, nil}; w = get(b); } worker[id].pid = 0; } void startproc(Buffer *b, int id) { char name[128]; switch(rfork(RFPROC|RFMEM|RFFDG)){ case -1: panic("can't fork"); case 0: if(chatty9p > 1) dprint("child %d pid: %d\n", id, getpid()); break; default: return; } snprint(name, 128, "%s worker %d", service, id); procsetname(name); work(b, id); if(chatty9p) dprint("%s process exited\n", name); exits(nil); } void fsstart(Srv *) { int i; mpsrvpid=getpid(); initworks(&buf); if(chatty9p > 1) dprint("srv() parent process pid: %d\n", mpsrvpid); for(i = 0; i < Nworkers; i++) startproc(&buf, i); } /* read the comment above shutdown() to understand */ void fsend(Srv *) { if(shuttingdown == 0) shutdown(); /* no need to bother removing /srv/service as we do for cmdhalt in ctlwrite() of ctl.c as the srv() takes care of that cleanup */ } void bfsopen(Req *r) { put(&buf, fsopen, r); } void bfscreate(Req *r) { put(&buf, fscreate, r); } void bfsread(Req *r) { if(r->fid->qid.type == QTAUTH){ authread(r); return; }else put(&buf, fsread, r); } void bfswrite(Req *r) { if(r->fid->qid.type == QTAUTH){ authwrite(r); return; }else put(&buf, fswrite, r); } void bfsstat(Req *r) { put(&buf, fsstat, r); } void bfswstat(Req *r) { put(&buf, fswstat, r); } void bfsremove(Req *r) { put(&buf, fsremove, r); } /* multi process server */ Srv mpsrv = { .auth = auth9p, .attach = fsattach, .destroyfid = fsdestroyfid, .msize = Iounit, .walk1 = fswalk1, .clone = fsclone, .open = bfsopen, .create = bfscreate, .read = bfsread, .write = bfswrite, .stat = bfsstat, .wstat = bfswstat, .remove = bfsremove, .start = fsstart, .end = fsend, }; /* usrv is a uni process server */ Srv usrv = { .auth = auth9p, .attach = fsattach, .destroyfid = fsdestroyfid, .msize = Iounit, .walk1 = fswalk1, .clone = fsclone, .open = fsopen, .create = fscreate, .read = fsread, .write = fswrite, .stat = fsstat, .wstat = fswstat, .remove = fsremove, .end = fsend, }; /* 08:08 < joe7> is there a way to interact with the stdin of a process using srv() from rc? I understand that it runs the srv() loop between the fd's 0 and 1. 08:13 < joe7> I wrote an userspace file server and I want to test it without using postmountsrv (avoiding the pipe interface). 08:45 < cinap_lenrek> {yourfileserver <[0=1] &} | echo 0 >/srv/service 08:46 < cinap_lenrek> then you should be able to mount /srv/service 08:47 < cinap_lenrek> the <>{} operator might also work 08:50 < cinap_lenrek> mount <{ramfs -i <[0=1]} /n/ram 08:50 < cinap_lenrek> works. */ void start9p(int stdio) { int sfd; if(stdio){ usrv.infd = 0; usrv.outfd = 1; srv(&usrv); }else{ sfd = postsrv(&mpsrv, service); if(sfd < 0) sysfatal("postsrv: %r"); close(sfd); } } /* static int dir9p2(Dir* dir, Dentry* dentry, void* strs) { char *op, *p; memset(dir, 0, sizeof(Dir)); mkqid(dir->qid, dentry, 1); dir->mode = (dir->qid.type<<24)|(dentry->mode & 0777); dir->mtime = dentry->mtime; dir->length = dentry->size; op = p = strs; dir->name = p; p += sprint(p, "%s", dentry->name)+1; dir->uid = p; uidtostr(p, dentry->uid); p += strlen(p)+1; dir->gid = p; uidtostr(p, dentry->gid); p += strlen(p)+1; dir->muid = p; strcpy(p, ""); p += strlen(p)+1; return p-op; } */ s32 readfilesize(u64 dblkno, u64 qpath) { u64 filesize; Iobuf *dbuf; dbuf = getmetachk(dblkno, Breadonly, Tdentry, qpath); if(dbuf == nil) return 0; filesize = dbuf->cur->size; putbuf(dbuf, 0); return filesize; } s32 readfile(u64 dblkno, u64 qpath, char *rbuf, s32 rbufsize, u64 offset) { Dentry *d; s64 tosend, sent, filesize; s32 n; Iobuf *dbuf, *buf; u64 datablocksize; sent = 0; dbuf = getmetachk(dblkno, Breadonly, Tdentry, qpath); if(dbuf == nil) return 0; d = (Dentry*)dbuf->cur; filesize = d->size+dbuf->appendsize; if(offset >= filesize) goto readend; if(filesize < Ddatasize){ n = min(filesize-offset, rbufsize); memcpy(rbuf, d->buf+offset, n); sent = n; goto readend; } if(filesize - offset > rbufsize) tosend = rbufsize; else tosend = filesize - offset; for(sent = 0; sent < tosend && offset+sent < d->size; ){ buf = getdatablkat(d, (offset+sent)/Maxdatablocksize); if(buf == nil){ putbuf(dbuf, 0); return -1; } if(buf->len == Maxdatablockunits) datablocksize = Maxdatablocksize; else datablocksize = buf->len*Blocksize -sizeof(Datahdr) -sizeof(u64 /* trailing path */); n = min(datablocksize-((offset+sent)%Maxdatablocksize), tosend-sent); memcpy(rbuf+sent, buf->io->buf+((offset+sent)%Maxdatablocksize), n); sent += n; putbuf(buf, 0); } if(dbuf->append && sent < tosend && offset+sent < d->size+dbuf->appendsize){ /* assuming that rbufsize < Maxdatablocksize */ n = min(d->size+dbuf->appendsize-(offset+sent), tosend-sent); memcpy(rbuf+sent, dbuf->append+(offset+sent-d->size), n); sent += n; } readend: putbuf(dbuf, 0); return sent; } /* only for updating existing data */ /* TODO copy on write */ s32 update(Dentry *d, u64 /* dblkno */, char *wbuf, s32 wbufsize, u64 offset) { Iobuf *buf; s32 howmuch; u64 blkno, to, nblocks; if(d == nil || wbuf == nil || wbufsize == 0) return 0; if(offset+wbufsize > d->size) panic("update(): should not be here\n" " offset %llud wbufsize %d d->size %llud", offset, wbufsize, d->size); /* get the extent overlay data */ blkno = rel2abs(d, offset/Maxdatablocksize); if(offset/Maxdatablocksize < d->size/Maxdatablocksize) nblocks = Maxdatablockunits; else nblocks = nlastdatablocks(d->size); if(chatty9p > 1) dprint("update d->name %s d->size %llud offset %llud" " rel2abs(offset/Datablocksize %llud) = blkno %llud\n", d->name, d->size, offset, offset/Maxdatablocksize, blkno); buf = getbufchk(blkno, nblocks, Bwritable, Tdata, d->path); if(buf == nil) return -1; /* overlay the new contents */ to = offset%Maxdatablocksize; howmuch = min((nblocks*Blocksize)-to, wbufsize); if(chatty9p > 1){ dprint("updating buf->blkno %llud offset %llud size %llud\n", buf->blkno, to, howmuch); showbuf(buf); } memcpy(buf->io->buf+to, wbuf, howmuch); if(chatty9p > 1){ dprint("update after\n"); showbuf(buf); } putbuf(buf, 1); return howmuch; } /* Scenarios: 1. last block is full or no last block 2. last block is partially full */ s32 writefullappend(Iobuf *dbuf, Dentry *d, u64 dblkno) { Iobuf *oldbuf, *newbuf; s32 howmuch; u64 newblkno, newbufsize, lastdatablksize, oldblkno; /* last data block is full or there is no last block. write out the append */ if(d->size%Maxdatablocksize == 0 && dbuf->appendsize == Maxdatablocksize){ /* allocate new blocks */ newbuf = allocblocks(Maxdatablockunits,Tdata, d->path); if(newbuf == nil) return -1; newblkno = newbuf->blkno; newbuf->io->dblkno = dblkno; /* add the contents of append to those new blocks */ memcpy(newbuf->io->buf, dbuf->append, dbuf->appendsize); putbuf(newbuf, 1); /* add this newly allocated blocks to the Dentry */ if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){ panic("could not write Tdata block\n"); freeblocks(newblkno, Maxdatablockunits, Tdata, d->path); return -1; } d->size += dbuf->appendsize; dbuf->appendsize = 0; return 1; }else if(d->size%Maxdatablocksize > 0 && (d->size%Maxdatablocksize)+dbuf->appendsize >= Maxdatablocksize){ /* last data block is not full. write a full last data block and leave the rest in append */ /* allocate new blocks */ newbuf = allocblocks(Maxdatablockunits, Tdata, d->path); if(newbuf == nil) return -1; newblkno = newbuf->blkno; newbuf->io->dblkno = dblkno; newbufsize = 0; /* read from the last block */ /* copy that to the new allocated blocks */ oldbuf = nil; if(lastdatablksize=d->size%Maxdatablocksize){ /* partial block, above is = not == */ oldblkno = rel2abs(d, d->size/Maxdatablocksize); oldbuf = getbufchk(oldblkno, nlastdatablocks(lastdatablksize), Bwritable, Tdata, d->path); if(oldbuf == nil){ freeblocks(newblkno, Maxdatablockunits, Tdata, d->path); return -1; } memcpy(newbuf->io->buf, oldbuf->io->buf, lastdatablksize); newbufsize = lastdatablksize; } /* add the contents of append to those new blocks */ howmuch = Maxdatablocksize-newbufsize; memcpy(newbuf->io->buf+newbufsize, dbuf->append, howmuch); /* move the left over append stuff to the front */ memmove(dbuf->append, dbuf->append+howmuch, dbuf->appendsize-howmuch); dbuf->appendsize -= howmuch; putbuf(newbuf, 1); /* add this newly allocated blocks to the Dentry */ if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){ panic("could not write Tdata block\n"); freeblocks(newblkno, Maxdatablockunits, Tdata, d->path); return -2; } /* free the old last blocks */ if(oldbuf) putbuffree(oldbuf); d->size += howmuch; return 1; } return 0; } /* Scenarios: 1. last block is full or no last block 2. last block is partially full */ s32 writeallappend(Iobuf *dbuf, Dentry *d, u64 dblkno) { Iobuf *oldbuf, *newbuf; u64 newblkno, newbufsize, lastdatablksize, oldblkno, newblocks; s32 rv; if(dbuf->appendsize == 0) return 1; rv = 0; if((d->size%Maxdatablocksize)+dbuf->appendsize >= Maxdatablocksize){ rv = writefullappend(dbuf, d, dblkno); if(rv < 0) return rv; } if(rv < 0) return rv; if(dbuf->appendsize == 0) return 1; /* last data block is full or there is no last block. */ if(d->size%Maxdatablocksize == 0){ /* allocate new blocks */ newbuf = allocblocks(nlastdatablocks(dbuf->appendsize),Tdata, d->path); if(newbuf == nil) return -1; newblkno = newbuf->blkno; newbuf->io->dblkno = dblkno; /* add the contents of append to those new blocks */ memcpy(newbuf->io->buf, dbuf->append, dbuf->appendsize); putbuf(newbuf, 1); /* add this newly allocated blocks to the Dentry */ if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){ panic("could not write Tdata block\n"); freeblocks(newblkno, nlastdatablocks(dbuf->appendsize), Tdata, d->path); return -1; } d->size += dbuf->appendsize; dbuf->appendsize = 0; return 1; } /* allocate new blocks */ newblocks = nlastdatablocks((d->size%Maxdatablocksize) +dbuf->appendsize); newbuf = allocblocks(newblocks, Tdata, d->path); if(newbuf == nil) return -1; newblkno = newbuf->blkno; newbufsize = 0; newbuf->io->dblkno = dblkno; /* read from the last block */ /* copy that to the new allocated blocks */ oldbuf = nil; if(lastdatablksize=d->size%Maxdatablocksize){ /* partial block, above is = not == */ oldblkno = rel2abs(d, d->size/Maxdatablocksize); oldbuf = getbufchk(oldblkno, nlastdatablocks(lastdatablksize), Bwritable, Tdata, d->path); if(oldbuf == nil){ freeblocks(newblkno, newblocks, Tdata, d->path); return -1; } memcpy(newbuf->io->buf, oldbuf->io->buf, lastdatablksize); newbufsize = lastdatablksize; } /* add the contents of append to those new blocks */ /* writefullappend() takes care of bigger sizes */ memcpy(newbuf->io->buf+newbufsize, dbuf->append, dbuf->appendsize); newbuf->io->len = newblocks; /* nothing left in append to move to the front */ putbuf(newbuf, 1); /* add this newly allocated blocks to the Dentry */ if(addrelative(d, dblkno, d->size/Maxdatablocksize, newblkno) == 0){ panic("could not write Tdata block\n"); freeblocks(newblkno, newblocks, Tdata, d->path); return -2; } /* free the old last blocks */ if(oldbuf) putbuffree(oldbuf); d->size += dbuf->appendsize; dbuf->appendsize = 0; return 1; } /* 3 scenarios offset < filesize && offset+wbufsize <= filesize replacing the data in existing blocks offset <= filesize && offset+wbufsize > filesize replacing the data in existing blocks add data to new blocks offset > filesize new blank blocks until offset data blocks from offset until offset+wbufsize */ s32 writefile(u64 dblkno, u64 qpath, s16 uid, char *wbuf, s32 wbufsize, u64 offset) { Dentry *d; s64 written; s32 n, rv; Iobuf *dbuf; u8 dowrite; dbuf = getmetachk(dblkno, Bwritable, Tdentry, qpath); if(dbuf == nil) return 0; d = dbuf->new; d->muid = uid; rv = dowrite = written = 0; // odentry(d); // little data, stuff it in the Dentry if(d->size+dbuf->appendsize<=Ddatasize && offset+wbufsize <= Ddatasize){ memcpy(d->buf+offset, wbuf, wbufsize); if(offset+wbufsize > d->size) d->size = offset+wbufsize; written = wbufsize; dowrite = 1; goto writeend; } // more data, relocate it to a proper data block if(d->size+dbuf->appendsize <= Ddatasize && offset+wbufsize > Ddatasize){ if(dbuf->append == nil){ dbuf->append = allocmemunits(Maxdatablockunits); if(dbuf->append == nil){ rv = -1; goto writeend; } dbuf->appendsize = 0; } memcpy(dbuf->append, d->buf, d->size); dbuf->appendsize = d->size; d->size = 0; dowrite = 1; } for(written = 0; written < wbufsize; ){ if(chatty9p > 1) dprint("writefile(): d->name %s d->size %llud offset %llud" " written %d offset+written %llud wbufsize %ld\n", d->name, d->size, offset, written, offset+written, wbufsize); /* all the below functions only write upto the end of an extent. Hence, the need for a loop to keep repeating. */ if(offset > d->size+dbuf->appendsize){ /* new blank blocks until offset fill blank data upto offset */ if(chatty9p > 1) dprint("writefile(): blank blocks until offset\n"); if(dbuf->append == nil){ dbuf->append = allocmemunits(Maxdatablockunits); if(dbuf->append == nil){ rv = -1; goto writeend; } dbuf->appendsize = 0; } n = min(Maxdatablocksize, offset-d->size); dbuf->appendsize = n; }else if(offset+written < d->size){ /* replacing existing data no change to file size */ if(chatty9p > 1) dprint("writefile(): replace existing data\n"); rv = n = update(d, dblkno, wbuf+written, /* from where */ /* how much */ min(wbufsize-written, d->size-(offset+written)), offset+written); /* to where */ if(chatty9p > 1) dprint("writefile(): update returned %d\n", n); if(n<0){ dprint("update has an issue %d\n", n); goto writeend; }else written += n; }else if(offset+written >= d->size && offset+written < d->size+dbuf->appendsize){ /* changing append contents */ n = min(d->size+dbuf->appendsize-(offset+written),wbufsize-written); memcpy(dbuf->append+(offset+written-d->size), wbuf+written, n); written += n; }else if(offset+written >= d->size+dbuf->appendsize){ /* assuming that wbufsize <= append */ /* append data, changes file size data blocks from offset until offset+wbufsize */ if(dbuf->append == nil){ dbuf->append = allocmemunits(Maxdatablockunits); if(dbuf->append == nil){ rv = -1; goto writeend; } dbuf->appendsize = 0; } n = min(Maxdatablocksize-dbuf->appendsize, wbufsize-written); memcpy(dbuf->append+dbuf->appendsize, wbuf+written, n); dbuf->appendsize += n; written += n; } else panic("writefile: should not be here"); if(dbuf->appendsize == Maxdatablocksize){ dowrite = 1; if((rv=writefullappend(dbuf, d, dblkno)) < 0) goto writeend; } } writeend: d->mtime = nsec(); putbuf(dbuf, dowrite); /* How will this work for partial writes? */ if(rv < 0) return rv; return written; }