#include "common.h" #include #include "dat.h" int findcache(Mcache *c, Message *m) { int i; for(i = 0; i < c->ntab; i++) if(c->ctab[i] == m) return i; return -1; } static void prcache(Mcache *c, char *prefix) { int j; Message *m; if(!debug) return; for(j = 0; j < c->ntab; j++){ m = c->ctab[j]; dprint("%s%d/%s\t%p\t%d\t%ld\n", prefix, j, m->name, m, m->refs, m->csize); } } /* debugging only */ static void dupchk(Mcache *c) { int i, j; if(!debug) return; for(i = 0; i < c->ntab; i++) for(j = i + 1; j < c->ntab; j++) if(c->ctab[i] == c->ctab[j]) goto lose; return; lose: for(j = 0; j < c->ntab; j++) dprint("%d\t%p %d\t%ld\n", j, c->ctab[j], c->ctab[j]->refs, c->ctab[j]->size); abort(); } int addcache(Mcache *c, Message *m) { int i; if((i = findcache(c, m)) < 0){ if(c->ntab + 1 == nelem(c->ctab)) abort(); i = c->ntab++; }else{ /* rotate */ if(i == c->ntab - 1) return i; /* silly shortcut to prevent excessive printage. */ dprint("addcache rotate %d %d\n", i, c->ntab); prcache(c, ""); memmove(c->ctab + i, c->ctab + i + 1, (c->ntab - i - 1)*sizeof c->ctab[0]); i = c->ntab - 1; c->ctab[i] = m; dupchk(c); } dprint("addcache %d %d %p\n", i, c->ntab, m); c->ctab[i] = m; return i; } static void notecache(Mailbox *mb, Message *m, long sz) { assert(Topmsg(mb, m)); assert(sz >= 0 && sz < Maxmsg); m->csize += sz; mb->cached += sz; addcache(mb, m); } static long cachefree0(Mailbox *mb, Message *m, int force) { long sz, i; Message *s; if(!force && !mb->fetch) return 0; for(s = m->part; s; s = s->next) cachefree(mb, s, force); dprint("cachefree: %D %p, %p\n", m->fileid, m, m->start); 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; } sz = m->csize; 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; if(mb->decache) mb->decache(mb, m); m->decoded = 0; m->converted = 0; m->badchars = 0; m->cstate &= ~(Cheader|Cbody); if(Topmsg(mb, m)) mb->cached -= sz; return sz; } long cachefree(Mailbox *mb, Message *m, int force) { long sz, i; sz = cachefree0(mb, m, force); for(i = 0; i < mb->ntab; i++) if(m == mb->ctab[i]){ mb->ntab--; memmove(mb->ctab + i, mb->ctab + i + 1, sizeof m*mb->ntab - i); dupchk(mb); break; } return sz; } enum{ Maxntab = nelem(mbl->ctab) - 10, }; vlong sumcache(Mcache *c) { int i; vlong sz; sz = 0; for(i = 0; i < c->ntab; i++) sz += c->ctab[i]->csize; return sz; } int scancache(Mcache *c) { int i; for(i = 0; i < c->ntab; i++) if(c->ctab[i]->csize > Maxmsg) return -1; return 0; } /* debugging only */ static void chkcsize(Mailbox *mb, vlong sz, vlong sz0) { int j; Mcache *c; Message *m; if(sumcache(mb) == mb->cached) if(scancache(mb) == 0) return; eprint("sz0 %lld sz %lld sum %lld sumca %lld\n", sz0, sz, sumcache(mb), mb->cached); eprint("%lld\n", sumcache(mb)); c = mb; for(j = 0; j < c->ntab; j++){ m = c->ctab[j]; eprint("%d %p %d %ld %ld\n", j, m, m->refs, m->csize, m->size); } abort(); } /* * strategy: start with i = 0. while cache exceeds limits, * find j so that all the [i:j] elements have refs == 0. * uncache all the [i:j], reduce ntab by i-j. the tail * [j+1:ntab] is shifted to [i:ntab], and finally i = i+1. * we may safely skip the new i, since the condition * that stopped our scan there still holds. */ void putcache(Mailbox *mb, Message *m) { int i, j, k; vlong sz, sz0; Message **p; p = mb->ctab; sz0 = mb->cached; dupchk(mb); for(i = 0;; i++){ sz = mb->cached; for(j = i;; j++){ if(j >= mb->ntab || sz < cachetarg && mb->ntab - (j - i) < Maxntab){ if(j != i) break; chkcsize(mb, sz, sz0); return; } if(p[j]->refs > 0) break; sz -= p[j]->csize; } if(sz == mb->cached){ if(i >= mb->ntab) break; continue; } for(k = i; k < j; k++) cachefree0(mb, p[k], 0); mb->ntab -= j - i; memmove(p + i, p + j, (mb->ntab - i)*sizeof *p); } chkcsize(mb, sz, sz0); k = 0; for(i = 0; i < mb->ntab; i++) k += p[i]->refs > 0; if((mb->ntab > 1 || k != mb->ntab) && Topmsg(mb, m)) eprint("cache overflow: %D %llud bytes; %d entries\n", m? m->fileid: 1ll, mb->cached, mb->ntab); if(k == mb->ntab) return; debug = 1; prcache(mb, ""); abort(); } 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); assert(m->badchars < Maxmsg/10); 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) { // fprint(2, "cachehash %P\n", mpair(mb, m)); 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 */ } void newcachehash(Mailbox *mb, Message *m, int doplumb) { if(doplumb) mailplumb(mb, m, 0); else if(insurecache(mb, m) == 0) msgdecref(mb, m); /* avoid cachehash on error? */ cachehash(mb, m); } static char *itab[] = { "idx", "stale", "header", "body" }; 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< 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 %d [%D] %lud %lud\n", m->id, m->fileid, 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 %d %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 == 0); 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("%d %#A\n", m->id, m->digest); } int cachebody(Mailbox *mb, Message *m) { ulong o; while(!Topmsg(mb, m)) m = m->whole; if(!mb->fetch || m->cstate&Cbody) return 0; o = m->end - m->start; dprint("cachebody %d [%D] %lud %lud %s\n", 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 == 0) 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) == -1) 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 || !m->inmbox) return -1; msgincref(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 == 0) sysfatal("digest?"); countparts(m); return 0; }