| plan 9 kernel history: overview | file list | diff list |
1993/0304/port/devmnt.c (diff list | history)
| port/devmnt.c on 1990/0227 | ||
| 1990/0227 | #include "u.h" | |
| 1992/0321 | #include "../port/lib.h" | |
| 1990/0227 | #include "mem.h" #include "dat.h" #include "fns.h" | |
| 1992/0111 | #include "../port/error.h" | |
| 1990/0227 | #include "devtab.h" | |
| 1991/0911 | typedef struct Mntrpc Mntrpc; struct Mntrpc | |
| 1990/0227 | { | |
| 1991/0911 | Mntrpc *list; /* Free/pending list */ Fcall request; /* Outgoing file system protocol message */ Fcall reply; /* Incoming reply */ Mnt *m; /* Mount device during rpc */ Rendez r; /* Place to hang out */ char *rpc; /* I/O Data buffer */ char done; /* Rpc completed */ char flushed; /* Flush was sent */ | |
| 1992/0305 | ushort flushtag; /* Tag flush sent on */ | |
| 1991/0911 | char flush[MAXMSG]; /* Somewhere to build flush */ | |
| 1990/0227 | }; | |
| 1991/0911 | struct Mnt | |
| 1990/0604 | { | |
| 1991/0911 | Ref; /* Count of attached channels */ Chan *c; /* Channel to file service */ Proc *rip; /* Reader in progress */ Mntrpc *queue; /* Queue of pending requests on this channel */ int id; /* Multiplexor id for channel check */ Mnt *list; /* Free list */ char mux; /* Set if the device aleady does the multiplexing */ | |
| 1991/0926 | int blocksize; /* read/write block size */ | |
| 1992/0305 | ushort flushtag; /* Tag to send flush on */ ushort flushbase; /* Base tag of flush window for this buffer */ | |
| 1990/0604 | }; | |
| 1991/0911 | struct Mntalloc | |
| 1990/0227 | { Lock; | |
| 1992/0620 | Mnt *list; /* Mount devices in used */ Mnt *mntfree; /* Free list */ | |
| 1991/0911 | Mntrpc *rpcfree; int id; | |
| 1992/0620 | int rpctag; | |
| 1991/0911 | }mntalloc; | |
| 1990/0227 | ||
| 1992/0613 | #define MAXRPC (MAXFDATA+MAXMSG) | |
| 1991/0911 | #define limit(n, max) (n > max ? max : n) | |
| 1990/0227 | ||
| 1992/0825 | Chan* mattach(Mnt*, char*, char*); void mntauth(Mnt *, Mntrpc *, char *, ushort); Mnt* mntchk(Chan*); | |
| 1991/0911 | void mntdirfix(uchar*, Chan*); | |
| 1991/1004 | void mntdoclunk(Mnt *, Mntrpc *); | |
| 1992/0825 | int mntflush(Mnt*, Mntrpc*); void mntfree(Mntrpc*); void mntgate(Mnt*); | |
| 1992/0320 | void mntpntfree(Mnt*); | |
| 1992/0825 | void mntqrm(Mnt*, Mntrpc*); Mntrpc* mntralloc(void); long mntrdwr(int , Chan*, void*,long , ulong); void mntrpcread(Mnt*, Mntrpc*); void mountio(Mnt*, Mntrpc*); void mountmux(Mnt*, Mntrpc*); void mountrpc(Mnt*, Mntrpc*); int rpcattn(Mntrpc*); | |
| 1990/0227 | ||
| 1991/0911 | enum | |
| 1990/0303 | { | |
| 1991/0911 | Tagspace = 1, | |
| 1992/0620 | Tagfls = 0x8000, | |
| 1992/0305 | Tagend = 0xfffe, | |
| 1992/0301 | ALIGN = 256, /* Vme block mode alignment */ | |
| 1991/0911 | }; | |
| 1990/0604 | ||
| 1990/0227 | void | |
| 1991/0911 | mntreset(void) | |
| 1990/0227 | { | |
| 1991/0911 | mntalloc.id = 1; | |
| 1992/0620 | mntalloc.rpctag = Tagspace; | |
| 1990/0604 | } | |
| 1990/0227 | void mntinit(void) { } Chan* | |
| 1991/0911 | mntattach(char *muxattach) | |
| 1990/0227 | { | |
| 1992/0620 | Mnt *m; | |
| 1992/0320 | Chan *c; | |
| 1990/0227 | struct bogus{ Chan *chan; char *spec; | |
| 1992/0312 | char *serv; | |
| 1990/0227 | }bogus; | |
| 1991/0911 | bogus = *((struct bogus *)muxattach); | |
| 1992/0620 | c = bogus.chan; lock(&mntalloc); for(m = mntalloc.list; m; m = m->list) { if(m->c == c && m->id) { | |
| 1991/0911 | lock(m); | |
| 1992/0620 | if(m->id && m->ref > 0 && m->c == c) { unlock(&mntalloc); | |
| 1991/0911 | m->ref++; unlock(m); | |
| 1992/0312 | return mattach(m, bogus.spec, bogus.serv); | |
| 1991/0911 | } unlock(m); } | |
| 1991/0901 | } | |
| 1991/0911 | m = mntalloc.mntfree; | |
| 1992/0620 | if(m != 0) mntalloc.mntfree = m->list; else { m = malloc(sizeof(Mnt)); if(m == 0) { unlock(&mntalloc); exhausted("mount devices"); } m->flushbase = Tagfls; m->flushtag = Tagfls; } m->list = mntalloc.list; mntalloc.list = m; | |
| 1991/0911 | m->id = mntalloc.id++; lock(m); unlock(&mntalloc); | |
| 1992/0620 | ||
| 1991/0904 | m->ref = 1; | |
| 1991/0911 | m->queue = 0; m->rip = 0; | |
| 1992/0620 | m->c = c; | |
| 1991/1011 | m->c->flag |= CMSG; | |
| 1992/0915 | m->blocksize = MAXFDATA;/**/ | |
| 1991/0911 | switch(devchar[m->c->type]) { default: m->mux = 0; | |
| 1992/0318 | break; | |
| 1992/0620 | case 'C': /* Cyclone */ | |
| 1992/0317 | m->mux = 1; break; | |
| 1991/0911 | } incref(m->c); | |
| 1990/0227 | unlock(m); | |
| 1990/0604 | ||
| 1992/0320 | if(waserror()) { close(m->c); | |
| 1992/0325 | if(decref(m) == 0) mntpntfree(m); | |
| 1992/0320 | nexterror(); } c = mattach(m, bogus.spec, bogus.serv); poperror(); return c; | |
| 1991/0911 | } | |
| 1990/0604 | ||
| 1991/0911 | Chan * | |
| 1992/0312 | mattach(Mnt *m, char *spec, char *serv) | |
| 1991/0911 | { Chan *c; Mntrpc *r; r = mntralloc(); c = devattach('M', spec); | |
| 1991/0918 | lock(&mntalloc); c->dev = mntalloc.id++; unlock(&mntalloc); | |
| 1992/0620 | c->mntptr = m; | |
| 1991/0911 | ||
| 1992/0314 | if(waserror()){ mntfree(r); | |
| 1992/0320 | /* Close must not be called since it will call mnt recursively */ chanfree(c); | |
| 1992/0314 | nexterror(); } | |
| 1992/0318 | memset(r->request.auth, 0, sizeof r->request.auth); if(*serv) mntauth(m, r, serv, c->fid); | |
| 1992/0312 | ||
| 1991/0911 | r->request.type = Tattach; r->request.fid = c->fid; | |
| 1991/1105 | memmove(r->request.uname, u->p->user, NAMELEN); | |
| 1991/0911 | strncpy(r->request.aname, spec, NAMELEN); mountrpc(m, r); c->qid = r->reply.qid; c->mchan = m->c; | |
| 1990/0303 | c->mqid = c->qid; | |
| 1990/0227 | poperror(); | |
| 1991/0911 | mntfree(r); | |
| 1990/0227 | return c; } | |
| 1992/0318 | void mntauth(Mnt *m, Mntrpc *f, char *serv, ushort fid) { | |
| 1992/0825 | int i; | |
| 1992/0318 | Mntrpc *r; | |
| 1992/0322 | uchar chal[AUTHLEN]; | |
| 1992/0318 | r = mntralloc(); if(waserror()) { mntfree(r); return; } r->request.type = Tauth; r->request.fid = fid; memmove(r->request.uname, u->p->user, NAMELEN); chal[0] = FScchal; | |
| 1992/0322 | for(i = 1; i < AUTHLEN; i++) | |
| 1992/0603 | chal[i] = nrand(256); | |
| 1992/0318 | ||
| 1992/0322 | memmove(r->request.chal, chal, AUTHLEN); strncpy(r->request.chal+AUTHLEN, serv, NAMELEN); encrypt(u->p->pgrp->crypt->key, r->request.chal, AUTHLEN+NAMELEN); | |
| 1992/0318 | mountrpc(m, r); | |
| 1992/0322 | decrypt(u->p->pgrp->crypt->key, r->reply.chal, 2*AUTHLEN+2*DESKEYLEN); | |
| 1992/0318 | chal[0] = FSctick; poperror(); | |
| 1992/0322 | if(memcmp(chal, r->reply.chal, AUTHLEN) != 0) { | |
| 1992/0318 | mntfree(r); error(Eperm); } | |
| 1992/0322 | memmove(f->request.auth, r->reply.chal+AUTHLEN+DESKEYLEN, AUTHLEN+DESKEYLEN); | |
| 1992/0318 | mntfree(r); } | |
| 1990/0227 | Chan* mntclone(Chan *c, Chan *nc) { Mnt *m; | |
| 1991/0911 | Mntrpc *r; int alloc = 0; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(nc == 0) { | |
| 1990/0227 | nc = newchan(); | |
| 1991/0911 | alloc = 1; | |
| 1990/0227 | } | |
| 1992/0824 | if(waserror()) { | |
| 1991/0911 | mntfree(r); if(alloc) close(nc); | |
| 1990/0227 | nexterror(); } | |
| 1991/0911 | r->request.type = Tclone; r->request.fid = c->fid; r->request.newfid = nc->fid; mountrpc(m, r); | |
| 1993/0304 | devclone(c, nc); | |
| 1990/0303 | nc->mqid = c->qid; | |
| 1991/0904 | incref(m); | |
| 1991/0911 | ||
| 1992/0312 | USED(alloc); | |
| 1991/0911 | poperror(); mntfree(r); | |
| 1990/0227 | return nc; } int mntwalk(Chan *c, char *name) { Mnt *m; | |
| 1991/0911 | Mntrpc *r; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(waserror()) { mntfree(r); return 0; | |
| 1990/0227 | } | |
| 1991/0911 | r->request.type = Twalk; r->request.fid = c->fid; strncpy(r->request.name, name, NAMELEN); mountrpc(m, r); c->qid = r->reply.qid; | |
| 1990/0227 | poperror(); | |
| 1991/0911 | mntfree(r); return 1; | |
| 1990/0227 | } void mntstat(Chan *c, char *dp) { Mnt *m; | |
| 1991/0911 | Mntrpc *r; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(waserror()) { mntfree(r); | |
| 1990/0227 | nexterror(); } | |
| 1991/0911 | r->request.type = Tstat; r->request.fid = c->fid; mountrpc(m, r); memmove(dp, r->reply.stat, DIRLEN); mntdirfix((uchar*)dp, c); | |
| 1990/0227 | poperror(); | |
| 1991/0911 | mntfree(r); | |
| 1990/0227 | } Chan* mntopen(Chan *c, int omode) { Mnt *m; | |
| 1991/0911 | Mntrpc *r; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(waserror()) { mntfree(r); | |
| 1990/0227 | nexterror(); } | |
| 1991/0911 | r->request.type = Topen; r->request.fid = c->fid; r->request.mode = omode; mountrpc(m, r); c->qid = r->reply.qid; | |
| 1990/0227 | c->offset = 0; c->mode = openmode(omode); c->flag |= COPEN; | |
| 1991/0911 | poperror(); mntfree(r); | |
| 1990/0227 | return c; } void mntcreate(Chan *c, char *name, int omode, ulong perm) { Mnt *m; | |
| 1991/0911 | Mntrpc *r; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(waserror()) { mntfree(r); | |
| 1990/0227 | nexterror(); } | |
| 1991/0911 | r->request.type = Tcreate; r->request.fid = c->fid; r->request.mode = omode; r->request.perm = perm; strncpy(r->request.name, name, NAMELEN); mountrpc(m, r); c->qid = r->reply.qid; | |
| 1990/0227 | c->flag |= COPEN; c->mode = openmode(omode); | |
| 1991/0911 | poperror(); mntfree(r); | |
| 1990/0227 | } void | |
| 1990/0604 | mntclunk(Chan *c, int t) | |
| 1990/0227 | { Mnt *m; | |
| 1991/1004 | Mntrpc *r; | |
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(waserror()){ | |
| 1991/1004 | mntdoclunk(m, r); nexterror(); | |
| 1991/0911 | } | |
| 1990/0227 | ||
| 1991/0911 | r->request.type = t; r->request.fid = c->fid; mountrpc(m, r); | |
| 1991/1004 | mntdoclunk(m, r); poperror(); } void mntdoclunk(Mnt *m, Mntrpc *r) { Mntrpc *q; mntfree(r); if(decref(m) == 0) { for(q = m->queue; q; q = r) { r = q->list; q->flushed = 0; mntfree(q); } m->id = 0; close(m->c); | |
| 1992/0320 | mntpntfree(m); | |
| 1991/1004 | } | |
| 1992/0320 | } void mntpntfree(Mnt *m) { | |
| 1992/0620 | Mnt *f, **l; | |
| 1992/0320 | lock(&mntalloc); | |
| 1992/0620 | l = &mntalloc.list; for(f = *l; f; f = f->list) { if(f == m) { *l = m->list; break; } l = &f->list; } | |
| 1992/0320 | m->list = mntalloc.mntfree; mntalloc.mntfree = m; unlock(&mntalloc); | |
| 1990/0227 | } | |
| 1990/0604 | void mntclose(Chan *c) { mntclunk(c, Tclunk); } | |
| 1991/0911 | void mntremove(Chan *c) | |
| 1990/0227 | { | |
| 1991/0911 | mntclunk(c, Tremove); } void mntwstat(Chan *c, char *dp) { | |
| 1990/0227 | Mnt *m; | |
| 1991/0911 | Mntrpc *r; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); r = mntralloc(); if(waserror()) { mntfree(r); | |
| 1990/0227 | nexterror(); } | |
| 1991/0911 | r->request.type = Twstat; r->request.fid = c->fid; memmove(r->request.stat, dp, DIRLEN); mountrpc(m, r); | |
| 1990/0227 | poperror(); | |
| 1991/0911 | mntfree(r); | |
| 1990/0227 | } long | |
| 1991/0411 | mntread(Chan *c, void *buf, long n, ulong offset) | |
| 1990/0227 | { | |
| 1991/0911 | uchar *p, *e; | |
| 1990/0227 | ||
| 1991/0911 | n = mntrdwr(Tread, c, buf, n, offset); if(c->qid.path & CHDIR) for(p = (uchar*)buf, e = &p[n]; p < e; p += DIRLEN) mntdirfix(p, c); | |
| 1990/0227 | return n; } long | |
| 1991/0411 | mntwrite(Chan *c, void *buf, long n, ulong offset) | |
| 1990/0227 | { | |
| 1991/0911 | return mntrdwr(Twrite, c, buf, n, offset); | |
| 1990/0227 | } | |
| 1991/0911 | long mntrdwr(int type, Chan *c, void *buf, long n, ulong offset) | |
| 1990/0227 | { Mnt *m; | |
| 1991/0911 | Mntrpc *r; char *uba; | |
| 1992/0825 | ulong cnt, nr; | |
| 1990/0227 | ||
| 1991/0911 | m = mntchk(c); uba = buf; for(cnt = 0; n; n -= nr) { r = mntralloc(); if(waserror()) { mntfree(r); nexterror(); } r->request.type = type; r->request.fid = c->fid; r->request.offset = offset; r->request.data = uba; | |
| 1991/0926 | r->request.count = limit(n, m->blocksize); | |
| 1991/0911 | mountrpc(m, r); nr = r->reply.count; if(type == Tread) memmove(uba, r->reply.data, nr); poperror(); mntfree(r); offset += nr; uba += nr; cnt += nr; if(nr != r->request.count) break; | |
| 1990/0227 | } | |
| 1991/0911 | return cnt; | |
| 1990/0227 | } void | |
| 1991/0911 | mountrpc(Mnt *m, Mntrpc *r) | |
| 1990/0604 | { | |
| 1992/0620 | r->reply.tag = 0; /* poison the old values */ | |
| 1992/0613 | r->reply.type = 4; | |
| 1992/0620 | ||
| 1991/0911 | mountio(m, r); if(r->reply.type == Rerror) | |
| 1992/0112 | error(r->reply.ename); | |
| 1992/0620 | ||
| 1991/0911 | if(r->reply.type == Rflush) | |
| 1992/0111 | error(Eintr); | |
| 1991/0911 | if(r->reply.type != r->request.type+1) { | |
| 1992/0620 | print("mnt: mismatched reply 0x%lux T%d R%d tags req %d fls %d rep %d\n", r, r->request.type, r->reply.type, r->request.tag, r->flushtag, r->reply.tag); | |
| 1992/0113 | error(Emountrpc); | |
| 1990/1123 | } } void | |
| 1991/0911 | mountio(Mnt *m, Mntrpc *r) | |
| 1990/1123 | { | |
| 1991/0911 | int n; | |
| 1990/1124 | ||
| 1991/0911 | lock(m); | |
| 1992/0305 | r->flushed = 0; | |
| 1991/0911 | r->m = m; r->list = m->queue; m->queue = r; unlock(m); /* Transmit a file system rpc */ n = convS2M(&r->request, r->rpc); if(waserror()) { | |
| 1991/0925 | if(mntflush(m, r) == 0) nexterror(); | |
| 1991/0911 | } | |
| 1991/0925 | else { | |
| 1992/0825 | if((*devtab[m->c->type].write)(m->c, r->rpc, n, 0) != n) error(Emountrpc); | |
| 1991/0925 | poperror(); } | |
| 1991/0911 | if(m->mux) { mntqrm(m, r); mntrpcread(m, r); | |
| 1990/1124 | return; | |
| 1991/0808 | } | |
| 1990/1124 | ||
| 1991/0911 | /* Gate readers onto the mount point one at a time */ for(;;) { lock(m); if(m->rip == 0) break; unlock(m); if(waserror()) { if(mntflush(m, r) == 0) nexterror(); continue; } sleep(&r->r, rpcattn, r); poperror(); if(r->done) return; | |
| 1990/1124 | } | |
| 1991/0911 | m->rip = u->p; unlock(m); while(r->done == 0) { mntrpcread(m, r); mountmux(m, r); } mntgate(m); | |
| 1990/1124 | } void | |
| 1991/0911 | mntrpcread(Mnt *m, Mntrpc *r) | |
| 1990/1124 | { | |
| 1991/0911 | int n; | |
| 1992/0505 | ||
| 1991/0911 | for(;;) { if(waserror()) { if(mntflush(m, r) == 0) { if(m->mux == 0) mntgate(m); nexterror(); } continue; | |
| 1990/0604 | } | |
| 1991/0911 | r->reply.type = 0; r->reply.tag = 0; | |
| 1992/0825 | n = (*devtab[m->c->type].read)(m->c, r->rpc, MAXRPC, 0); | |
| 1991/0911 | poperror(); if(n == 0) continue; | |
| 1992/0503 | ||
| 1991/0911 | if(convM2S(r->rpc, &r->reply, n) != 0) return; } | |
| 1990/0604 | } | |
| 1990/11211 | ||
| 1991/0911 | void mntgate(Mnt *m) | |
| 1990/0717 | { | |
| 1991/0911 | Mntrpc *q; lock(m); m->rip = 0; for(q = m->queue; q; q = q->list) if(q->done == 0) { lock(&q->r); if(q->r.p) { unlock(&q->r); unlock(m); wakeup(&q->r); return; } unlock(&q->r); } unlock(m); | |
| 1990/0717 | } | |
| 1990/11211 | ||
| 1990/0604 | void | |
| 1991/0911 | mountmux(Mnt *m, Mntrpc *r) | |
| 1990/0227 | { | |
| 1991/0911 | char *dp; | |
| 1992/0825 | Mntrpc **l, *q; | |
| 1990/0227 | ||
| 1991/0911 | lock(m); l = &m->queue; for(q = *l; q; q = q->list) { | |
| 1992/0305 | if(q->request.tag == r->reply.tag || q->flushed && q->flushtag == r->reply.tag) { | |
| 1991/0911 | *l = q->list; unlock(m); | |
| 1992/0305 | if(q != r) { /* Completed someone else */ dp = q->rpc; q->rpc = r->rpc; r->rpc = dp; memmove(&q->reply, &r->reply, sizeof(Fcall)); q->done = 1; wakeup(&q->r); }else q->done = 1; | |
| 1991/0911 | return; } l = &q->list; | |
| 1990/0227 | } | |
| 1991/0911 | unlock(m); } | |
| 1990/0227 | ||
| 1991/0911 | int mntflush(Mnt *m, Mntrpc *r) { int n; | |
| 1992/0825 | Fcall flush; | |
| 1990/0511 | ||
| 1992/0305 | lock(m); r->flushtag = m->flushtag++; if(m->flushtag == Tagend) m->flushtag = m->flushbase; r->flushed = 1; unlock(m); | |
| 1990/0511 | ||
| 1991/0911 | flush.type = Tflush; flush.tag = r->flushtag; flush.oldtag = r->request.tag; n = convS2M(&flush, r->flush); if(waserror()) { | |
| 1992/0111 | if(strcmp(u->error, Eintr) == 0) | |
| 1991/0911 | return 1; mntqrm(m, r); return 0; | |
| 1990/03081 | } | |
| 1992/0825 | (*devtab[m->c->type].write)(m->c, r->flush, n, 0); | |
| 1991/0911 | poperror(); return 1; } | |
| 1991/0901 | ||
| 1991/0911 | Mntrpc * mntralloc(void) { Mntrpc *new; | |
| 1991/0901 | ||
| 1992/0620 | lock(&mntalloc); new = mntalloc.rpcfree; if(new != 0) mntalloc.rpcfree = new->list; else { new = xalloc(sizeof(Mntrpc)+MAXRPC); if(new == 0) { | |
| 1991/0911 | unlock(&mntalloc); | |
| 1992/0620 | exhausted("mount rpc buffer"); | |
| 1991/0911 | } | |
| 1992/0620 | new->rpc = (char*)new+sizeof(Mntrpc); new->request.tag = mntalloc.rpctag++; | |
| 1991/0904 | } | |
| 1992/0620 | unlock(&mntalloc); new->done = 0; new->flushed = 0; return new; | |
| 1991/0911 | } void mntfree(Mntrpc *r) { lock(&mntalloc); r->list = mntalloc.rpcfree; mntalloc.rpcfree = r; unlock(&mntalloc); } void mntqrm(Mnt *m, Mntrpc *r) { Mntrpc **l, *f; lock(m); r->done = 1; r->flushed = 0; l = &m->queue; for(f = *l; f; f = f->list) { if(f == r) { *l = r->list; break; | |
| 1990/0703 | } | |
| 1991/0911 | l = &f->list; | |
| 1990/0227 | } | |
| 1991/0911 | unlock(m); } | |
| 1990/0227 | ||
| 1991/0911 | Mnt * mntchk(Chan *c) { Mnt *m; | |
| 1992/0620 | m = c->mntptr; | |
| 1991/0918 | /* Was it closed and reused ? */ | |
| 1992/0620 | if(m->id == 0 || m->id >= c->dev) /* Sanity check */ | |
| 1990/11211 | error(Eshutdown); | |
| 1991/0911 | return m; | |
| 1990/0717 | } | |
| 1990/1220 | void | |
| 1991/0911 | mntdirfix(uchar *dirbuf, Chan *c) { | |
| 1992/1223 | dirbuf[DIRLEN-4] = devchar[c->type]>>0; dirbuf[DIRLEN-3] = devchar[c->type]>>8; | |
| 1991/0911 | dirbuf[DIRLEN-2] = c->dev; dirbuf[DIRLEN-1] = c->dev>>8; } int rpcattn(Mntrpc *r) { return r->done || r->m->rip == 0; } | |