47f07b2669
introducing new ctrunc() function that invalidates any caches for the passed in chan, invoked when handling wstat with a specified file length or on file creation/truncation. test program to reproduce the problem: #include <u.h> #include <libc.h> #include <libsec.h> void main(int argc, char *argv[]) { int fd; Dir *d, nd; fd = create("xxx", ORDWR, 0666); write(fd, "1234", 4); d = dirstat("xxx"); assert(d->length == 4); nulldir(&nd); nd.length = 0; dirwstat("xxx", &nd); d = dirstat("xxx"); assert(d->length == 0); fd = open("xxx", OREAD); assert(read(fd, (void*)&d, 4) == 0); }
522 lines
8 KiB
C
522 lines
8 KiB
C
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "../port/error.h"
|
|
|
|
enum
|
|
{
|
|
NHASH = 128,
|
|
NFILE = 4093, /* should be prime */
|
|
MAXCACHE = 8*1024*1024,
|
|
|
|
MAPBITS = 8*sizeof(ulong),
|
|
NBITMAP = (PGROUND(MAXCACHE)/BY2PG + MAPBITS-1) / MAPBITS,
|
|
};
|
|
|
|
/* devmnt.c: parallel read ahread implementation */
|
|
extern void mntrahinit(Mntrah *rah);
|
|
extern long mntrahread(Mntrah *rah, Chan *c, uchar *buf, long len, vlong off);
|
|
|
|
typedef struct Mntcache Mntcache;
|
|
struct Mntcache
|
|
{
|
|
Qid qid;
|
|
int dev;
|
|
int type;
|
|
|
|
QLock;
|
|
Proc *locked;
|
|
ulong nlocked;
|
|
|
|
Mntcache *hash;
|
|
Mntcache *prev;
|
|
Mntcache *next;
|
|
|
|
/* page bitmap of valid pages */
|
|
ulong bitmap[NBITMAP];
|
|
|
|
/* read ahead state */
|
|
Mntrah rah;
|
|
};
|
|
|
|
typedef struct Cache Cache;
|
|
struct Cache
|
|
{
|
|
Lock;
|
|
Mntcache *alloc;
|
|
Mntcache *head;
|
|
Mntcache *tail;
|
|
Mntcache *hash[NHASH];
|
|
};
|
|
|
|
Image fscache;
|
|
|
|
static Cache cache;
|
|
|
|
void
|
|
cinit(void)
|
|
{
|
|
int i;
|
|
Mntcache *m;
|
|
|
|
m = xalloc(sizeof(Mntcache)*NFILE);
|
|
if (m == nil)
|
|
panic("cinit: no memory");
|
|
|
|
cache.alloc = m;
|
|
cache.head = m;
|
|
|
|
for(i = 0; i < NFILE-1; i++) {
|
|
m->next = m+1;
|
|
m->prev = m-1;
|
|
m++;
|
|
}
|
|
|
|
cache.tail = m;
|
|
cache.tail->next = nil;
|
|
cache.head->prev = nil;
|
|
|
|
fscache.notext = 1;
|
|
}
|
|
|
|
static uintptr
|
|
cacheaddr(Mntcache *m, ulong pn)
|
|
{
|
|
uintptr da = pn * NFILE + (m - cache.alloc);
|
|
return (da << PGSHIFT) | (da >> (sizeof(da)*8 - PGSHIFT));
|
|
}
|
|
|
|
static void
|
|
cnodata(Mntcache *m)
|
|
{
|
|
memset(m->bitmap, 0, sizeof(m->bitmap));
|
|
}
|
|
|
|
static void
|
|
ctail(Mntcache *m)
|
|
{
|
|
/* Unlink and send to the tail */
|
|
if(m->prev != nil)
|
|
m->prev->next = m->next;
|
|
else
|
|
cache.head = m->next;
|
|
if(m->next != nil)
|
|
m->next->prev = m->prev;
|
|
else
|
|
cache.tail = m->prev;
|
|
|
|
if(cache.tail != nil) {
|
|
m->prev = cache.tail;
|
|
cache.tail->next = m;
|
|
m->next = nil;
|
|
cache.tail = m;
|
|
}
|
|
else {
|
|
cache.head = m;
|
|
cache.tail = m;
|
|
m->prev = nil;
|
|
m->next = nil;
|
|
}
|
|
}
|
|
|
|
/* called with cache locked */
|
|
static Mntcache*
|
|
clookup(Chan *c, int skipvers)
|
|
{
|
|
Mntcache *m;
|
|
|
|
for(m = cache.hash[c->qid.path%NHASH]; m != nil; m = m->hash)
|
|
if(eqchantdqid(c, m->type, m->dev, m->qid, skipvers) && c->qid.type == m->qid.type)
|
|
return m;
|
|
|
|
return nil;
|
|
}
|
|
|
|
/*
|
|
* resursive Mntcache locking. Mntcache.rah is protected by the
|
|
* same lock and we want to call cupdate() from mntrahread()
|
|
* while holding the lock.
|
|
*/
|
|
static int
|
|
cancachelock(Mntcache *m)
|
|
{
|
|
if(m->locked == up || canqlock(m)){
|
|
m->locked = up;
|
|
m->nlocked++;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
static void
|
|
cachelock(Mntcache *m)
|
|
{
|
|
if(m->locked != up){
|
|
qlock(m);
|
|
assert(m->nlocked == 0);
|
|
m->locked = up;
|
|
}
|
|
m->nlocked++;
|
|
|
|
}
|
|
static void
|
|
cacheunlock(Mntcache *m)
|
|
{
|
|
assert(m->locked == up);
|
|
if(--m->nlocked == 0){
|
|
m->locked = nil;
|
|
qunlock(m);
|
|
}
|
|
}
|
|
|
|
/* return locked Mntcache if still valid else reset mcp */
|
|
static Mntcache*
|
|
ccache(Chan *c)
|
|
{
|
|
Mntcache *m;
|
|
|
|
m = c->mcp;
|
|
if(m != nil) {
|
|
cachelock(m);
|
|
if(eqchantdqid(c, m->type, m->dev, m->qid, 0) && c->qid.type == m->qid.type)
|
|
return m;
|
|
c->mcp = nil;
|
|
cacheunlock(m);
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
int
|
|
copen(Chan *c)
|
|
{
|
|
Mntcache *m, *f, **l;
|
|
|
|
/* directories aren't cacheable */
|
|
if(c->qid.type&QTDIR){
|
|
c->mcp = nil;
|
|
return 0;
|
|
}
|
|
|
|
lock(&cache);
|
|
m = clookup(c, 0);
|
|
if(m != nil){
|
|
ctail(m);
|
|
unlock(&cache);
|
|
c->mcp = m;
|
|
return 1;
|
|
}
|
|
m = clookup(c, 1);
|
|
if(m == nil)
|
|
m = cache.head;
|
|
ctail(m);
|
|
|
|
l = &cache.hash[m->qid.path%NHASH];
|
|
for(f = *l; f != nil; f = f->hash) {
|
|
if(f == m) {
|
|
*l = m->hash;
|
|
break;
|
|
}
|
|
l = &f->hash;
|
|
}
|
|
|
|
if(!cancachelock(m)){
|
|
unlock(&cache);
|
|
cachelock(m);
|
|
lock(&cache);
|
|
f = clookup(c, 0);
|
|
if(f != nil) {
|
|
/*
|
|
* someone got there first while cache lock
|
|
* was released and added a updated Mntcache
|
|
* for us. update LRU and use it.
|
|
*/
|
|
ctail(f);
|
|
unlock(&cache);
|
|
cacheunlock(m);
|
|
c->mcp = f;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
m->qid = c->qid;
|
|
m->dev = c->dev;
|
|
m->type = c->type;
|
|
|
|
l = &cache.hash[c->qid.path%NHASH];
|
|
m->hash = *l;
|
|
*l = m;
|
|
|
|
unlock(&cache);
|
|
|
|
m->rah.vers = m->qid.vers;
|
|
mntrahinit(&m->rah);
|
|
cnodata(m);
|
|
cacheunlock(m);
|
|
c->mcp = m;
|
|
return 0;
|
|
}
|
|
|
|
enum {
|
|
VABITS = 8*sizeof(uintptr) - 2*PGSHIFT,
|
|
VAMASK = (((uintptr)1 << VABITS)-1) << PGSHIFT,
|
|
};
|
|
|
|
static Page*
|
|
cpage(Mntcache *m, ulong pn, ulong *po, ulong *pe)
|
|
{
|
|
ulong b;
|
|
Page *p;
|
|
|
|
b = 1 << (pn%MAPBITS);
|
|
if((m->bitmap[pn/MAPBITS] & b) == 0)
|
|
return nil;
|
|
p = lookpage(&fscache, cacheaddr(m, pn));
|
|
if(p == nil){
|
|
m->bitmap[pn/MAPBITS] &= ~b;
|
|
return nil;
|
|
}
|
|
*po = p->va & (BY2PG-1);
|
|
*pe = 1 + (p->va >> (PGSHIFT+VABITS));
|
|
assert(*po < *pe);
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
cpageset(Page *p, ulong po, ulong pe)
|
|
{
|
|
assert(po < pe);
|
|
p->va = po | (p->va & VAMASK) | ((uintptr)pe - 1) << (PGSHIFT+VABITS);
|
|
}
|
|
|
|
int
|
|
cread(Chan *c, uchar *buf, int len, vlong off)
|
|
{
|
|
KMap *k;
|
|
Page *p;
|
|
Mntcache *m;
|
|
int l, tot;
|
|
ulong offset, pn, po, pe;
|
|
|
|
if(len <= 0)
|
|
return 0;
|
|
|
|
m = ccache(c);
|
|
if(m == nil)
|
|
return 0;
|
|
|
|
if(waserror()){
|
|
cacheunlock(m);
|
|
nexterror();
|
|
}
|
|
|
|
tot = 0;
|
|
if(off >= MAXCACHE)
|
|
goto Prefetch;
|
|
|
|
offset = off;
|
|
if(offset+len > MAXCACHE)
|
|
len = MAXCACHE - offset;
|
|
pn = offset / BY2PG;
|
|
offset &= (BY2PG-1);
|
|
|
|
while(len > 0){
|
|
p = cpage(m, pn, &po, &pe);
|
|
if(p == nil)
|
|
break;
|
|
if(offset < po || offset >= pe){
|
|
putpage(p);
|
|
break;
|
|
}
|
|
l = pe - offset;
|
|
if(l > len)
|
|
l = len;
|
|
|
|
k = kmap(p);
|
|
if(waserror()) {
|
|
kunmap(k);
|
|
putpage(p);
|
|
nexterror();
|
|
}
|
|
memmove(buf, (uchar*)VA(k) + offset, l);
|
|
kunmap(k);
|
|
putpage(p);
|
|
poperror();
|
|
|
|
tot += l;
|
|
buf += l;
|
|
len -= l;
|
|
|
|
offset += l;
|
|
offset &= (BY2PG-1);
|
|
if(offset != 0)
|
|
break;
|
|
|
|
pn++;
|
|
}
|
|
|
|
Prefetch:
|
|
if(len > 0){
|
|
if(m->rah.vers != m->qid.vers){
|
|
mntrahinit(&m->rah);
|
|
m->rah.vers = m->qid.vers;
|
|
}
|
|
off += tot;
|
|
tot += mntrahread(&m->rah, c, buf, len, off);
|
|
}
|
|
cacheunlock(m);
|
|
poperror();
|
|
|
|
return tot;
|
|
}
|
|
|
|
/* invalidate pages in page bitmap */
|
|
static void
|
|
invalidate(Mntcache *m, ulong offset, int len)
|
|
{
|
|
ulong pn;
|
|
|
|
for(pn = offset/BY2PG; len > 0; pn++, len -= BY2PG)
|
|
m->bitmap[pn/MAPBITS] &= ~(1 << (pn%MAPBITS));
|
|
}
|
|
|
|
/* replace buf data from [off, off+len) in the cache or invalidate */
|
|
static void
|
|
cachedata(Mntcache *m, uchar *buf, int len, vlong off)
|
|
{
|
|
int l;
|
|
Page *p;
|
|
KMap *k;
|
|
ulong offset, pn, po, pe;
|
|
|
|
if(off >= MAXCACHE || len <= 0){
|
|
cacheunlock(m);
|
|
return;
|
|
}
|
|
|
|
offset = off;
|
|
if(offset+len > MAXCACHE)
|
|
len = MAXCACHE - offset;
|
|
pn = offset / BY2PG;
|
|
offset &= (BY2PG-1);
|
|
|
|
while(len > 0){
|
|
l = BY2PG - offset;
|
|
if(l > len)
|
|
l = len;
|
|
p = cpage(m, pn, &po, &pe);
|
|
if(p != nil){
|
|
if(offset > pe || (offset+l) < po){
|
|
/* cached range not extendable, set new cached range */
|
|
po = offset;
|
|
pe = offset+l;
|
|
} else {
|
|
/* extend cached range */
|
|
if(offset < po)
|
|
po = offset;
|
|
if((offset+l) > pe)
|
|
pe = offset+l;
|
|
}
|
|
} else {
|
|
if(needpages(nil)){
|
|
invalidate(m, offset + pn*BY2PG, len);
|
|
break;
|
|
}
|
|
p = newpage(0, nil, pn*BY2PG);
|
|
p->daddr = cacheaddr(m, pn);
|
|
cachedel(&fscache, p->daddr);
|
|
cachepage(p, &fscache);
|
|
m->bitmap[pn/MAPBITS] |= 1 << (pn%MAPBITS);
|
|
|
|
po = offset;
|
|
pe = offset+l;
|
|
}
|
|
cpageset(p, po, pe);
|
|
|
|
k = kmap(p);
|
|
if(waserror()) {
|
|
kunmap(k);
|
|
putpage(p);
|
|
invalidate(m, offset + pn*BY2PG, len);
|
|
cacheunlock(m);
|
|
nexterror();
|
|
}
|
|
memmove((uchar*)VA(k) + offset, buf, l);
|
|
poperror();
|
|
kunmap(k);
|
|
putpage(p);
|
|
|
|
offset = 0;
|
|
pn++;
|
|
buf += l;
|
|
len -= l;
|
|
}
|
|
cacheunlock(m);
|
|
}
|
|
|
|
void
|
|
cupdate(Chan *c, uchar *buf, int len, vlong off)
|
|
{
|
|
Mntcache *m;
|
|
|
|
m = ccache(c);
|
|
if(m == nil)
|
|
return;
|
|
cachedata(m, buf, len, off);
|
|
}
|
|
|
|
void
|
|
cwrite(Chan* c, uchar *buf, int len, vlong off)
|
|
{
|
|
Mntcache *m;
|
|
|
|
m = ccache(c);
|
|
if(m == nil)
|
|
return;
|
|
m->qid.vers++;
|
|
c->qid.vers++;
|
|
if(c->qid.type&QTAPPEND){
|
|
cacheunlock(m);
|
|
return;
|
|
}
|
|
cachedata(m, buf, len, off);
|
|
}
|
|
|
|
void
|
|
ctrunc(Chan *c)
|
|
{
|
|
Mntcache *m;
|
|
|
|
if(c->qid.type&QTDIR)
|
|
return;
|
|
|
|
if((c->flag&COPEN) == 0){
|
|
lock(&cache);
|
|
c->mcp = clookup(c, 0);
|
|
unlock(&cache);
|
|
}
|
|
|
|
m = ccache(c);
|
|
if(m == nil)
|
|
return;
|
|
mntrahinit(&m->rah);
|
|
cnodata(m);
|
|
cacheunlock(m);
|
|
|
|
if((c->flag&COPEN) == 0)
|
|
c->mcp = nil;
|
|
}
|
|
|
|
void
|
|
cclunk(Chan *c)
|
|
{
|
|
Mntcache *m;
|
|
|
|
m = ccache(c);
|
|
if(m == nil)
|
|
return;
|
|
mntrahinit(&m->rah);
|
|
cacheunlock(m);
|
|
c->mcp = nil;
|
|
}
|