ref: 334cd4d87fc9fd8e48183128e9d60de669ab29fe
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, Req *req); s32 writefile(u64 dblkno, u64 qpath, s16 uid, char *wbuf, s32 wbufsize, u64 offset, Req *req); 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; Dentry *d; if(authattach(req) < 0){ return; } if((uid = lookupid(req->ifcall.uname)) <= 0){ respond(req, "no such user"); return; } dbuf = getbufchk(Bdroot, Breadonly, Tdentry, Qproot); if(dbuf == nil){ respond(req, "phase error"); return; } d = dbuf->d; req->fid->aux = newaux(Bdroot, uid); req->fid->qid = (Qid){Qproot, d->qid.version, QTDIR}; putbuf(dbuf); 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); } freeaux(fid->aux); } static void fsmkdir(Dentry *d, Dir *dir, char *buf) { memset(dir, 0, sizeof(*dir)); dir->qid = (Qid){d->qid.path, d->qid.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; 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 = getbufchk(((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 = dbuf->d; /* nothing to do for already zero'ed out slots */ if(d->path != Qpnone) fsmkdir(d, &req->d, nil); putbuf(dbuf); 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 = getbufchk(((Aux*)req->fid->aux)->dblkno, Bwritable, Tdentry, req->fid->qid.path); if(dbuf == nil){ respond(req, errstring[Eperm]); return; } d = dbuf->d; if(canaccess(((Aux*)req->fid->aux)->uid, d, DMWRITE) == 0){ putbuf(dbuf); 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); 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; } } putbuf(dbuf); respond(req, nil); 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->qid.path == 0); fsmkdir(d, &dir, nbuf); 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; } readfile(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path, req->ofcall.data, req->ifcall.count, req->ifcall.offset, req); } static void fswrite(Req *req) { 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; } writefile(((Aux*)req->fid->aux)->dblkno, req->fid->qid.path, ((Aux*)req->fid->aux)->uid, req->ifcall.data, req->ifcall.count, req->ifcall.offset, req); } 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 = getbufchk(aux->dblkno, Breadonly, Tdentry, fid->qid.path); if(dbuf == nil) return errstring[Ephase]; d = dbuf->d; if(chatty9p > 1) dprint("fswalk1 .. d->name %s d->qid.path %llud d->pdblkno %llud\n", d->name, d->qid.path, d->pdblkno); pbuf = getbufchk(d->pdblkno, Breadonly, Tdentry, d->pqpath); if(pbuf == nil) return errstring[Ephase]; putbuf(dbuf); p = pbuf->d; *qid = (Qid){p->qid.path, p->qid.version, (p->mode&DMDIR) ? QTDIR : QTFILE}; if(aux != nil) aux->dblkno = pbuf->blkno; putbuf(pbuf); 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->qid.path, d->qid.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->user. */ 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 = getbufchk(aux->dblkno, Bwritable, Tdentry, fid->qid.path); if(dbuf == nil){ respond(req, errstring[Ephase]); return; } dparent = dbuf->d; if(canaccess(((Aux*)req->fid->aux)->uid, dparent, DMWRITE) == 0){ respond(req, errstring[Eperm]); return; } if(canaccess(aux->uid, dparent, DMWRITE) == 0){ putbuf(dbuf); 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); 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 = getbufchk(zblkno, Bwritable, Tdentry, Qpnone); if(cbuf == nil){ putbuf(dbuf); respond(req, errstring[Ephase]); return; } }else{ cbuf = allocblock(Tdentry, fid->qid.path); if(cbuf == nil){ putbuf(dbuf); respond(req, errstring[Efull]); return; } } dchild = cbuf->d; dchild->size = 0; dchild->pdblkno = dbuf->blkno; dchild->pqpath = dparent->qid.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); putbuf(dbuf); 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); putbuf(dbuf); respond(req, errstring[Eaccess]); return; } dchild->qid.path = newqpath(); dchild->qid.version = 0; strncpy(dchild->name, req->ifcall.name, Namelen); fid->qid = (Qid){dchild->qid.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->qid.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->qid.path); putbuf(cbuf); /* 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); respond(req, errstring[Ephase]); return; } putbuf(dbuf); respond(req, nil); return; }else{ cbuf = getbuf(blkno, Dentryunits, Breadonly, Bused); if(cbuf == nil){ putbuf(dbuf); respond(req, errstring[Ephase]); return; } dchild = cbuf->d; /* nothing to do for already zero'ed out slots */ if(dchild->path == Qpnone){ if(zblkno == 0) zblkno = cbuf->blkno; goto Nextdentry; } if(dchild->tag != Tdentry){ putbuf(cbuf); putbuf(dbuf); 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->qid.path, dchild->qid.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); if(req->ofcall.qid.path >= Qpusers && req->ofcall.qid.type == QTFILE) truncatefile(req->ofcall.qid.path, blkno, aux->uid); putbuf(dbuf); /* DBG("> mafscreate c->path %s mode 0x%ux omode0 0x%ux\n" " c->qid.path 0x%zux c->qid.vers %lud c->qid.type %ud 0x%ux\n" " c->aux 0x%p\n", chanpath(c), c->mode, omode, c->qid.path, c->qid.vers, c->qid.type, c->qid.type, c->aux);*/ respond(req, nil); return; } Nextdentry: putbuf(cbuf); } } } /* 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 = getbufchk(aux->dblkno, Breadonly, Tdentry, fid->qid.path); if(dbuf == nil){ respond(req, errstring[Ephase]); return; } d = dbuf->d; 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->qid.path, d->qid.version, (d->mode&DMDIR) ? QTDIR : QTFILE}; fid->omode = omode; req->ofcall.iounit = Iounit; req->ofcall.qid = fid->qid; putbuf(dbuf); 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); respond(req, errstring[Einval]); return; perm: if(dbuf != nil) putbuf(dbuf); 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. */ void shutdown(void) { u64 n; /* User *u, *v; */ if(chatty9p > 1) dprint("shutdown\n"); shuttingdown = 1; 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->users; while(u != nil){ v = u->next; free(u); u = v; } t->users = 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 = getbufchk(dblkno, Breadonly, Tdentry, qpath); if(dbuf == nil) return 0; filesize = dbuf->d->size; putbuf(dbuf); return filesize; } s32 readfile(u64 dblkno, u64 qpath, char *rbuf, s32 rbufsize, u64 offset, Req *req) { Dentry *d; s64 tosend, sent, filesize; s32 n; Iobuf *dbuf, *buf; dbuf = getbufchk(dblkno, Breadonly, Tdentry, qpath); if(dbuf == nil) return 0; d = dbuf->d; filesize = d->size; if(offset >= filesize){ putbuf(dbuf); return 0; } if(filesize < Ddatasize){ n = min(filesize-offset, rbufsize); memcpy(rbuf, d->buf+offset, n); putbuf(dbuf); return n; } if(filesize - offset > rbufsize) tosend = rbufsize; else tosend = filesize - offset; for(sent = 0; sent < tosend && offset+sent < d->size; ){ buf = getdatablkat(d, (offset+sent)/Datablocksize, Breadonly); if(buf == nil){ putbuf(dbuf); return -1; } n = min(Datablocksize-((offset+sent)%Datablocksize), tosend-sent); memcpy(rbuf+sent, buf->io->buf+((offset+sent)%Datablocksize), n); sent += n; putbuf(buf); } if(req != nil){ req->ofcall.count = sent; req->ofcall.offset = req->ifcall.offset+sent; respond(req, nil); } putbuf(dbuf); return sent; } /* only for updating existing data */ s32 update(Dentry *d, u64 /* dblkno */, char *wbuf, s32 wbufsize, u64 offset) { Iobuf *buf; s32 howmuch; u64 blkno, to; 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/Datablocksize); 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/Datablocksize, blkno); buf = getbufchk(blkno, Bwritable, Tdata, d->qid.path); if(buf == nil) return -1; /* overlay the new contents */ to = offset%Datablocksize; howmuch = min(Datablocksize-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); return howmuch; } s32 append(Dentry *d, u64 dblkno, char *wbuf, s32 wbufsize) { Iobuf *buf; s32 howmuch; u64 blkno, lastblksize; if(d == nil || wbuf == nil || wbufsize == 0) return 0; if(chatty9p > 1) dprint("append wbufsize %d\n", wbufsize); if((lastblksize=d->size%Datablocksize) == 0){ /* last block is full, use a new block */ if(chatty9p > 1) dprint("append new data block rel2abs d->name %s reli d->size %llud" " d->size/Datablocksize %llud\n", d->name, d->size, d->size/Datablocksize); /* write single block */ howmuch = min(Datablocksize, wbufsize); buf = allocblock(Tdata, d->qid.path); if(buf == nil) return -1; blkno = buf->blkno; memcpy(buf->io->buf, wbuf, howmuch); putbuf(buf); if(addrelative(d, dblkno, d->size/Datablocksize, blkno) == 0){ panic("could not write Tdata block\n"); freeblock(blkno, Tdata, d->qid.path); return -2; } return howmuch; }else{ /* last block is partially full, fill it up */ blkno = rel2abs(d, d->size/Datablocksize); if(blkno == 0) panic("append update rel2abs blkno == 0" " d->name %s reli d->size %llud" " d->size/Datablocksize %llud s.blkno %llud\n", d->name, d->size, d->size/Datablocksize, blkno); if(chatty9p > 1) dprint("append update rel2abs d->name %s reli d->size %llud" " d->size/Datablocksize %llud blkno %llud\n", d->name, d->size, d->size/Datablocksize, blkno); buf = getbufchk(blkno, Bwritable, Tdata, d->qid.path); if(buf == nil) return -1; howmuch = min(Datablocksize-lastblksize, wbufsize); if(chatty9p > 1) dprint("fill lastblksize %llud howmuch %llud\n", lastblksize, howmuch); memcpy(buf->io->buf+lastblksize, wbuf, howmuch); putbuf(buf); return howmuch; } } /* pad blanks one span at a time */ static s32 padblanks(Dentry *d, u64 dblkno, s32 size) { s8 *buf; int n; if(d == nil || size <= 0) return 0; n = min(size, Datablocksize - d->size%Datablocksize); buf = emalloc9p(n); n = append(d, dblkno, buf, n); free(buf); return n; } /* 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, Req *req) { Dentry *d; s64 written; s32 n; Iobuf *dbuf, *buf; u64 blkno; dbuf = getbufchk(dblkno, Bwritable, Tdentry, qpath); if(dbuf == nil) return 0; d = dbuf->d; d->muid = uid; // odentry(d); // little data, stuff it in the Dentry if(d->size<=Ddatasize && offset+wbufsize <= Ddatasize){ memcpy(d->buf+offset, wbuf, wbufsize); if(offset+wbufsize > d->size) d->size = offset+wbufsize; written = wbufsize; goto writeend; } // more data, relocate it to a proper data block if(d->size > 0 && d->size <= Ddatasize && offset+wbufsize > Ddatasize){ buf = allocblock(Tdata, d->qid.path); if(buf == nil){ written = 0; goto writeend; } blkno = buf->blkno; memcpy(buf->io->buf, d->buf, d->size); putbuf(buf); memset(d->buf, 0, d->size); d->dblocks[0] = blkno; } 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){ /* new blank blocks until offset fill blank data upto offset */ if(chatty9p > 1) dprint("writefile(): blank blocks until offset\n"); n = padblanks(d, dblkno, offset+wbufsize-d->size); if(chatty9p > 1) dprint("writefile(): padblanks returned %d\n", n); if(n<0){ dprint("padblanks has an issue %d\n", n); goto writeend; }else d->size += n; }else if(offset+written < d->size){ /* replacing existing data no change to file size */ if(chatty9p > 1) dprint("writefile(): replace existing data\n"); 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){ /* append data, changes file size data blocks from offset until offset+wbufsize */ if(chatty9p > 1) dprint("writefile(): append\n"); n = append(d, dblkno, wbuf+written, /* from where */ wbufsize-written /* how much */); if(chatty9p > 1) dprint("writefile(): append returned %d\n", n); if(n<0){ dprint("append has an issue %d\n", n); goto writeend; } written += n; d->size += n; } else panic("writefile: should not be here"); } writeend: d->mtime = nsec(); if(req != nil){ if(written == -3) respond(req, errstring[Efull]); else if(written == -1 || written == -2) respond(req, errstring[Ephase]); else{ req->ofcall.count = written; req->ofcall.offset = req->ifcall.offset+written; respond(req, nil); } } putbuf(dbuf); return written; }