plan9fox/sys/src/cmd/upas/fs/cache.c
Alex Musolino 2c6cc12133 upas/fs: fix infinite loop in putcache (again)
The previous attempt to fix this problem (see changesets b32199e0f90a
and 00ae79a6ba50) caused all calls to cachefree to free the cached
message contents in addition to updating the LRU list.  This causes
problems for the POP3 driver since it provides no fetch function; once
a message is evicted from the LRU cache, its contents is lost.

This time we fix cachefree to always update the LRU list but only free
the cached message contents if the driver provides a fetch function or
the force flag is set.
2018-12-31 00:00:09 +10:30

401 lines
7.2 KiB
C

#include "common.h"
#include <libsec.h>
#include "dat.h"
static void
addlru(Mcache *c, Message *m)
{
Message *l, **ll;
if((m->cstate & (Cheader|Cbody)) == 0)
return;
c->nlru++;
ll = &c->lru;
while((l = *ll) != nil){
if(l == m){
c->nlru--;
*ll = m->lru;
} else {
ll = &l->lru;
}
}
m->lru = nil;
*ll = m;
}
static void
notecache(Mailbox *mb, Message *m, long sz)
{
assert(Topmsg(mb, m));
assert(sz >= 0 && sz <= Maxmsg);
m->csize += sz;
mb->cached += sz;
addlru(mb, m);
}
void
cachefree(Mailbox *mb, Message *m, int force)
{
long i;
Message *s, **ll;
if(Topmsg(mb, m)){
for(ll = &mb->lru; *ll != nil; ll = &((*ll)->lru)){
if(*ll == m){
mb->nlru--;
*ll = m->lru;
m->lru = nil;
break;
}
}
if(mb->decache)
mb->decache(mb, m);
mb->cached -= m->csize;
}
for(s = m->part; s; s = s->next)
cachefree(mb, s, force);
if(!force && mb->fetch == nil)
return;
if(m->mallocd){
free(m->start);
m->mallocd = 0;
}
if(m->ballocd){
free(m->body);
m->ballocd = 0;
}
if(m->hallocd){
free(m->header);
m->hallocd = 0;
}
for(i = 0; i < nelem(m->references); i++){
free(m->references[i]);
m->references[i] = 0;
}
m->csize = 0;
m->start = 0;
m->end = 0;
m->header = 0;
m->hend = 0;
m->hlen = -1;
m->body = 0;
m->bend = 0;
m->mheader = 0;
m->mhend = 0;
m->decoded = 0;
m->converted = 0;
m->badchars = 0;
m->cstate &= ~(Cheader|Cbody);
}
void
putcache(Mailbox *mb, Message *m)
{
int n;
while(!Topmsg(mb, m)) m = m->whole;
addlru(mb, m);
while(mb->lru != nil && (mb->cached > cachetarg || mb->nlru > 10)){
n = 0;
while(mb->lru->refs > 0){
if(++n >= mb->nlru)
return;
addlru(mb, mb->lru);
}
cachefree(mb, mb->lru, 0);
}
}
static int
squeeze(Message *m, uvlong o, long l, int c)
{
char *p, *q, *e;
int n;
q = memchr(m->start + o, c, l);
if(q == nil)
return 0;
n = 0;
e = m->start + o + l;
for(p = q; q < e; q++){
if(*q == c){
n++;
continue;
}
*p++ = *q;
}
return n;
}
void
msgrealloc(Message *m, ulong l)
{
long l0, h0, m0, me, b0;
l0 = m->end - m->start;
m->mallocd = 1;
h0 = m->hend - m->start;
m0 = m->mheader - m->start;
me = m->mhend - m->start;
b0 = m->body - m->start;
assert(h0 >= 0 && m0 >= 0 && me >= 0 && b0 >= 0);
m->start = erealloc(m->start, l + 1);
m->rbody = m->start + b0;
m->rbend = m->end = m->start + l0;
if(!m->hallocd){
m->header = m->start;
m->hend = m->start + h0;
}
if(!m->ballocd){
m->body = m->start + b0;
m->bend = m->start + l0;
}
m->mheader = m->start + m0;
m->mhend = m->start + me;
}
/*
* the way we squeeze out bad characters is exceptionally sneaky.
*/
static int
fetch(Mailbox *mb, Message *m, uvlong o, ulong l)
{
int expand;
long l0, n, sz0;
top:
l0 = m->end - m->start;
assert(l0 >= 0);
dprint("fetch %lud sz %lud o %llud l %lud badchars %d\n", l0, m->size, o, l, m->badchars);
if(l0 == m->size || o > m->size)
return 0;
expand = 0;
if(o + l > m->size)
l = m->size - o;
if(o + l == m->size)
l += m->ibadchars - m->badchars;
if(o + l > l0){
expand = 1;
msgrealloc(m, o + m->badchars + l);
}
assert(l0 <= o);
sz0 = m->size;
if(mb->fetch(mb, m, o + m->badchars, l) == -1){
logmsg(m, "can't fetch %D %llud %lud", m->fileid, o, l);
m->deleted = Dead;
return -1;
}
if(m->size - sz0)
l += m->size - sz0; /* awful botch for gmail */
if(expand){
/* grumble. poor planning. */
if(m->badchars > 0)
memmove(m->start + o, m->start + o + m->badchars, l);
n = squeeze(m, o, l, 0);
n += squeeze(m, o, l - n, '\r');
if(n > 0){
if(m->ibadchars == 0)
dprint(" %ld more badchars\n", n);
l -= n;
m->badchars += n;
msgrealloc(m, o + l);
}
notecache(mb, m, l);
m->bend = m->rbend = m->end = m->start + o + l;
if(n)
if(o + l + n == m->size && m->cstate&Cidx){
dprint(" redux %llud %ld\n", o + l, n);
o += l;
l = n;
goto top;
}
}else
eprint("unhandled case in fetch\n");
*m->end = 0;
return 0;
}
void
cachehash(Mailbox *mb, Message *m)
{
assert(mb->refs >= 0);
if(mb->refs == 0)
return;
if(m->whole == m->whole->whole)
henter(PATH(mb->id, Qmbox), m->name,
(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
else
henter(PATH(m->whole->id, Qdir), m->name,
(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
henter(PATH(m->id, Qdir), "xxx",
(Qid){PATH(m->id, Qmax), 0, QTFILE}, m, mb); /* sleezy speedup */
}
static char *itab[] = {
"idx",
"stale",
"header",
"body",
"new",
};
char*
cstate(Message *m)
{
char *p, *e;
int i, s;
static char buf[64];
s = m->cstate;
p = e = buf;
e += sizeof buf;
for(i = 0; i < 8; i++)
if(s & 1<<i)
if(i < nelem(itab))
p = seprint(p, e, "%s ", itab[i]);
if(p > buf)
p--;
p[0] = 0;
return buf;
}
static int
middlecache(Mailbox *mb, Message *m)
{
int y;
y = 0;
while(!Topmsg(mb, m)){
m = m->whole;
if((m->cstate & Cbody) == 0)
y = 1;
}
if(y == 0)
return 0;
dprint("middlecache %lud [%D] %lud %lud\n",
m->id, m->fileid, (ulong)(m->end - m->start), m->size);
return cachebody(mb, m);
}
int
cacheheaders(Mailbox *mb, Message *m)
{
char *p, *e;
int r;
ulong o;
if(!mb->fetch || m->cstate&Cheader)
return 0;
if(!Topmsg(mb, m))
return middlecache(mb, m);
dprint("cacheheaders %lud %D\n", m->id, m->fileid);
if(m->size < 10000)
r = fetch(mb, m, 0, m->size);
else for(r = 0; (o = m->end - m->start) < m->size; ){
if((r = fetch(mb, m, o, 4096)) < 0)
break;
p = m->start + o;
if(o)
p--;
for(e = m->end - 2; p < e; p++){
p = memchr(p, '\n', e - p);
if(p == nil)
break;
if(p[1] == '\n' || (p[1] == '\r' && p[2] == '\n'))
goto found;
}
}
if(r < 0)
return -1;
found:
parseheaders(mb, m, mb->addfrom, 0);
return 0;
}
void
digestmessage(Mailbox *mb, Message *m)
{
assert(m->digest == nil);
m->digest = emalloc(SHA1dlen);
sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
if(mtreeisdup(mb, m)){
logmsg(m, "dup detected");
m->deleted = Dup; /* no dups allowed */
}else
mtreeadd(mb, m);
dprint("%lud %#A\n", m->id, m->digest);
}
int
cachebody(Mailbox *mb, Message *m)
{
ulong o;
while(!Topmsg(mb, m))
m = m->whole;
if(mb->fetch == nil || m->cstate&Cbody)
return 0;
o = m->end - m->start;
dprint("cachebody %lud [%D] %lud %lud %s", m->id, m->fileid, o, m->size, cstate(m));
if(o < m->size)
if(fetch(mb, m, o, m->size - o) < 0)
return -1;
if((m->cstate&Cidx) == 0){
assert(m->ibadchars == 0);
if(m->badchars > 0)
dprint("reducing size %ld %ld\n", m->size, m->size - m->badchars);
m->size -= m->badchars; /* sneaky */
m->ibadchars = m->badchars;
}
if(m->digest == nil)
digestmessage(mb, m);
if(m->lines == 0)
m->lines = countlines(m);
parse(mb, m, mb->addfrom, 0);
dprint(" →%s\n", cstate(m));
return 0;
}
int
cacheidx(Mailbox *mb, Message *m)
{
if(m->cstate & Cidx)
return 0;
if(cachebody(mb, m) < 0)
return -1;
m->cstate |= Cidxstale|Cidx;
return 0;
}
static int
countparts(Message *m)
{
Message *p;
if(m->nparts == 0)
for(p = m->part; p; p = p->next){
countparts(p);
m->nparts++;
}
return m->nparts;
}
int
insurecache(Mailbox *mb, Message *m)
{
if((m->deleted & ~Deleted) != 0 || !m->inmbox)
return -1;
msgincref(mb, m);
cacheidx(mb, m);
if((m->cstate & Cidx) == 0){
logmsg(m, "%s: can't cache: %s: %r", mb->path, m->name);
msgdecref(mb, m);
return -1;
}
if(m->digest == nil)
sysfatal("digest?");
countparts(m);
return 0;
}