#include "common.h"
#include <libsec.h>
#include "dat.h"
#define idprint(...) if(iflag > 1) fprint(2, __VA_ARGS__); else {}
#define iprint(...) if(iflag) fprint(2, __VA_ARGS__); else {}
static char *magic = "idx magic v7\n";
static char *mbmagic = "genericv1";
enum {
Idxfields = 21,
Idxto = 30000, /* index timeout in ms */
Idxstep = 300, /* sleep between tries */
};
void
idxfree(Idx *i)
{
if(i->str)
free(i->str);
else{
free(i->digest);
free(i->ffrom);
free(i->from);
free(i->to);
free(i->cc);
free(i->bcc);
free(i->replyto);
free(i->messageid);
free(i->subject);
free(i->sender);
free(i->inreplyto);
free(i->idxaux);
}
memset(i, 0, sizeof *i);
}
static char*
∂(char *x)
{
if(x)
return x;
return "";
}
static int
pridxmsg(Biobuf *b, Idx *x)
{
Bprint(b, "%#A %ux %D %lud ", x->digest, x->flags&~Frecent, x->fileid, x->lines);
Bprint(b, "%q %q %q %q %q ", ∂(x->ffrom), ∂(x->from), ∂(x->to), ∂(x->cc), ∂(x->bcc));
Bprint(b, "%q %q %q %q %q ", ∂(x->replyto), ∂(x->messageid), ∂(x->subject), ∂(x->sender), ∂(x->inreplyto));
Bprint(b, "%s %d %lud %lud ", rtab[x->type].s, x->disposition, x->size, x->rawbsize);
Bprint(b, "%lud %q %d\n", x->ibadchars, ∂(x->idxaux), x->nparts);
return 0;
}
static int
pridx0(Biobuf *b, Mailbox *mb, Message *m, int l)
{
for(; m; m = m->next){
if(l == 0)
if(insurecache(mb, m) == -1)
continue;
if(pridxmsg(b, m))
return -1;
if(m->part)
pridx0(b, mb, m->part, l + 1);
m->cstate &= ~Cidxstale;
m->cstate |= Cidx;
if(l == 0)
msgdecref(mb, m);
}
return 0;
}
void
genericidxwrite(Biobuf *b, Mailbox*)
{
Bprint(b, "%s\n", mbmagic);
}
static int
pridx(Biobuf *b, Mailbox *mb)
{
int i;
Bprint(b, magic);
mb->idxwrite(b, mb);
// prrefs(b);
i = pridx0(b, mb, mb->root->part, 0);
return i;
}
static char *eopen[] = {
"not found",
"does not exist",
"file is locked",
"exclusive lock",
0,
};
static char *ecreate[] = {
"already exists",
"file is locked",
"exclusive lock",
0,
};
static int
bad(char **t)
{
char buf[ERRMAX];
int i;
rerrstr(buf, sizeof buf);
for(i = 0; t[i]; i++)
if(strstr(buf, t[i]))
return 0;
return 1;
}
static int
forceexcl(int fd)
{
int r;
Dir *d;
d = dirfstat(fd);
if(d == nil)
return 0; /* ignore: assume file removed */
if(d->mode & DMEXCL){
free(d);
return 0;
}
d->mode |= DMEXCL;
d->qid.type |= QTEXCL;
r = dirfwstat(fd, d);
free(d);
if(r == -1)
return 0; /* ignore unwritable (e.g dump) */
close(fd);
return -1;
}
static int
exopen(char *s)
{
int i, fd;
for(i = 0; i < Idxto/Idxstep; i++){
if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(eopen)){
if(fd != -1 && forceexcl(fd) == -1)
continue;
return fd;
}
if((fd = create(s, OWRITE|OEXCL, DMEXCL|0600)) >= 0 || bad(ecreate))
return fd;
sleep(Idxstep);
}
werrstr("lock timeout");
return -1;
}
static Message*
findmessage(Mailbox *, Message *parent, int n)
{
Message *m;
for(m = parent->part; m; m = m->next)
if(!m->digest && n-- == 0)
return m;
return 0;
}
static int
validmessage(Mailbox *mb, Message *m, int level)
{
if(level){
if(m->digest != 0)
goto lose;
if(m->fileid <= 1000000ull<<8)
if(m->fileid != 0)
goto lose;
}else{
if(m->digest == 0)
goto lose;
if(m->size == 0)
goto lose;
if(m->fileid <= 1000000ull<<8)
goto lose;
if(mtreefind(mb, m->digest))
goto lose;
}
return 1;
lose:
eprint("invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid);
return 0;
}
/*
* n.b.: we don't insure this is the index version we last read.
*
* we may overwrite changes. dualing deletes should sync eventually.
* mboxsync should complain about missing messages but
* mutable information (which is not in the email itself)
* may be lost.
*/
int
wridxfile(Mailbox *mb)
{
char buf[Pathlen + 4];
int r, fd;
Biobuf b;
Dir *d;
assert(semacquire(&mb->idxsem, 0) != -1);
snprint(buf, sizeof buf, "%s.idx", mb->path);
iprint("wridxfile %s\n", buf);
if((fd = exopen(buf)) == -1){
rerrstr(buf, sizeof buf);
if(strcmp(buf, "no creates") != 0)
if(strstr(buf, "file system read only") == 0)
eprint("wridxfile: %r\n");
semrelease(&mb->idxsem, 1);
return -1;
}
seek(fd, 0, 0);
Binit(&b, fd, OWRITE);
r = pridx(&b, mb);
Bterm(&b);
d = dirfstat(fd);
if(d == 0)
sysfatal("dirfstat: %r");
mb->qid = d->qid;
free(d);
close(fd);
semrelease(&mb->idxsem, 1);
return r;
}
static int
nibble(int c)
{
if(c >= '0' && c <= '9')
return c - '0';
if(c < 0x20)
c += 0x20;
if(c >= 'a' && c <= 'f')
return c - 'a'+10;
return 0xff;
}
static uchar*
hackdigest(char *s)
{
uchar t[SHA1dlen];
int i;
if(strcmp(s, "-") == 0)
return 0;
if(strlen(s) != 2*SHA1dlen){
eprint("bad digest %s\n", s);
return 0;
}
for(i = 0; i < SHA1dlen; i++)
t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]);
memmove(s, t, SHA1dlen);
return (uchar*)s;
}
static uvlong
rdfileid(char *s, int level)
{
char *p;
uvlong uv;
uv = strtoul(s, &p, 0);
if((level == 0 && uv < 1000000) || *p != '.')
return 0;
return uv<<8 | strtoul(p + 1, 0, 10);
}
static char*
∫(char *x)
{
if(x && *x)
return x;
return nil;
}
/*
* strategy: use top-level avl tree to merge index with
* our ideas about the mailbox. new or old messages
* with corrupt index entries are marked Dead. they
* will be cleared out of the mailbox and are kept out
* of the index. when messages are marked Dead, a
* reread of the mailbox is forced.
*
* side note. if we get a new message while we are
* running it is added to the list in order but m->id
* looks out-of-order. this is because m->id must
* increase monotonicly. a new instance of the fs
* will result in a different ordering.
*/
static int
rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level, int doplumb)
{
char *f[Idxfields + 1], *s;
uchar *digest;
int n, flags, nparts, good, bad, redux;
Message *m, **ll, *l;
bad = good = redux = 0;
ll = &parent->part;
nparts = npart;
for(; npart != 0 && (s = Brdstr(b, '\n', 1)); npart--){
m = 0;
digest = 0;
n = tokenize(s, f, nelem(f));
if(n != Idxfields){
dead:
eprint("bad index %#A %d %d n=%d\n", digest, level, npart, n);
bad++;
free(s);
if(level)
return -1;
if(m)
m->deleted = Dead;
continue;
}
digest = hackdigest(f[0]);
if(digest == 0 ^ level != 0)
goto dead;
if(level == 0)
m = mtreefind(mb, digest);
else
m = findmessage(mb, parent, nparts - npart);
if(m){
/*
* read in mutable information.
* currently this is only flags
*/
redux++;
if(level == 0)
m->deleted &= ~Dmark;
if(m->nparts)
if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1)
goto dead;
ll = &m->next;
idprint("%d seen before %d... %.2ux", level, m->id, m->cstate);
flags = m->flags;
m->flags |= strtoul(f[1], 0, 16);
if(flags != m->flags)
m->cstate |= Cidxstale;
m->cstate |= Cidx;
idprint("→%.2ux\n", m->cstate);
free(s);
// s = 0;
continue;
}
m = newmessage(parent);
idprint("%d new %d %#A\n", level, m->id, digest);
m->digest = digest;
m->flags = strtoul(f[1], 0, 16);
m->fileid = rdfileid(f[2], level);
m->lines = atoi(f[3]);
m->ffrom = ∫(f[4]);
m->from = ∫(f[5]);
m->to = ∫(f[6]);
m->cc = ∫(f[7]);
m->bcc = ∫(f[8]);
m->replyto = ∫(f[9]);
m->messageid = ∫(f[10]);
m->subject = ∫(f[11]);
m->sender = ∫(f[12]);
m->inreplyto = ∫(f[13]);
m->type = newrefs(f[14]);
m->disposition = atoi(f[15]);
m->size = strtoul(f[16], 0, 0);
m->rawbsize = strtoul(f[17], 0, 0);
m->ibadchars = strtoul(f[18], 0, 0);
m->idxaux = ∫(f[19]);
m->nparts = strtoul(f[20], 0, 0);
m->cstate &= ~Cidxstale;
m->cstate |= Cidx;
m->str = s;
s = 0;
if(!validmessage(mb, m, level))
goto dead;
if(level == 0){
mtreeadd(mb, m);
m->inmbox = 1;
}
cachehash(mb, m); /* hokey */
l = *ll;
*ll = m;
ll = &m->next;
*ll = l;
good++;
if(m->nparts)
if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1)
goto dead;
if(doplumb && level == 0)
mailplumb(mb, m, 0);
}
if(level == 0 && bad + redux > 0)
iprint("idx: %d %d %d\n", good, bad, redux);
if(bad)
return -1;
return 0;
}
/* bug: should check time. */
static int
qidcmp(int fd, Qid *q)
{
int r;
Dir *d;
Qid q0;
d = dirfstat(fd);
if(!d)
sysfatal("dirfstat: %r");
r = 1;
if(d->qid.path == q->path)
if(d->qid.vers == q->vers)
r = 0;
q0 = *q;
*q = d->qid;
free(d);
if(q0.path != 0 && r)
iprint("qidcmp ... index changed [%ld .. %ld]\n", q0.vers, q->vers);
return r;
}
static int
verscmp(Biobuf *b, Mailbox *mb)
{
char *s;
int n;
n = -1;
if(s = Brdstr(b, '\n', 0))
n = strcmp(s, magic);
free(s);
if(n)
return -1;
n = -1;
if(s = Brdstr(b, '\n', 0))
n = mb->idxread(s, mb);
free(s);
return n;
}
int
genericidxread(char *s, Mailbox*)
{
return strcmp(s, mbmagic);
}
void
genericidxinvalid(Mailbox *mb)
{
if(mb->d)
memset(&mb->d->qid, 0, sizeof mb->d->qid);
mb->waketime = time(0);
}
void
mark(Mailbox *mb)
{
Message *m;
for(m = mb->root->part; m != nil; m = m->next)
m->deleted |= Dmark;
}
int
unmark(Mailbox *mb)
{
int i;
Message *m;
i = 0;
for(m = mb->root->part; m != nil; m = m->next)
if(m->deleted & Dmark){
i++;
m->deleted &= ~Dmark; /* let mailbox scan figure this out. BOTCH?? */
}
return i;
}
int
rdidxfile0(Mailbox *mb, int doplumb)
{
char buf[Pathlen + 4];
int r, v;
Biobuf *b;
snprint(buf, sizeof buf, "%s.idx", mb->path);
b = Bopen(buf, OREAD);
if(b == nil)
return -2;
if(qidcmp(Bfildes(b), &mb->qid) == 0)
r = 0;
else if(verscmp(b, mb) == -1)
r = -1;
else{
mark(mb);
r = rdidx(b, mb, mb->root, -1, 0, doplumb);
v = unmark(mb);
if(r == 0 && v > 0)
r = -1;
}
Bterm(b);
return r;
}
int
rdidxfile(Mailbox *mb, int doplumb)
{
int r;
assert(semacquire(&mb->idxsem, 0) > 0);
r = rdidxfile0(mb, doplumb);
if(r == -1 && mb->idxinvalid)
mb->idxinvalid(mb);
semrelease(&mb->idxsem, 1);
return r;
}
|