#include "common.h"
#include "dat.h"
typedef struct {
int debug;
} Mdir;
#define mdprint(mdir, ...) if(mdir->debug) fprint(2, __VA_ARGS__)
static int
slurp(char *f, char *b, uvlong o, long l)
{
int fd, r;
if((fd = open(f, OREAD)) == -1)
return -1;
seek(fd, o, 0);
r = readn(fd, b, l) != l;
close(fd);
return r? -1: 0;
}
static void
parseunix(Message *m)
{
char *s, *p;
int l;
l = m->header - m->start;
m->unixheader = smprint("%.*s", l, m->start);
s = m->start + 5;
if((p = strchr(s, ' ')) == nil)
abort();
*p = 0;
m->unixfrom = strdup(s);
*p = ' ';
}
static int
mdirfetch(Mailbox *mb, Message *m, uvlong o, ulong l)
{
char buf[Pathlen], *x;
Mdir *mdir;
mdir = mb->aux;
mdprint(mdir, "mdirfetch(%D) ...", m->fileid);
snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid);
if(slurp(buf, m->start + o, o, l) == -1){
logmsg(m, "fetch failed: %r");
mdprint(mdir, "%r\n");
return -1;
}
if(m->header == nil)
m->header = m->start;
if(m->header == m->start)
if(o + l >= 36)
if(strncmp(m->start, "From ", 5) == 0)
if(x = strchr(m->start, '\n')){
m->header = x + 1;
if(m->unixfrom == nil)
parseunix(m);
}
m->mheader = m->mhend = m->header;
mdprint(mdir, "fetched [%llud, %llud]\n", o, o + l);
return 0;
}
static int
setsize(Mailbox *mb, Message *m)
{
char buf[Pathlen];
Dir *d;
snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid);
if((d = dirstat(buf)) == nil)
return -1;
m->size = d->length;
free(d);
return 0;
}
/* must be [0-9]+(\..*)? */
int
dirskip(Dir *a, uvlong *uv)
{
char *p;
if(a->length == 0)
return 1;
*uv = strtoul(a->name, &p, 0);
if(*uv < 1000000 || *p != '.')
return 1;
*uv = *uv<<8 | strtoul(p + 1, &p, 10);
if(*p)
return 1;
return 0;
}
static int
vcmp(vlong a, vlong b)
{
a -= b;
if(a > 0)
return 1;
if(a < 0)
return -1;
return 0;
}
static int
dircmp(Dir *a, Dir *b)
{
uvlong x, y;
dirskip(a, &x);
dirskip(b, &y);
return vcmp(x, y);
}
static char*
mdirread(Mdir* mdir, Mailbox* mb, int doplumb, int *new)
{
int i, nnew, ndel, fd, n, c;
uvlong uv;
Dir *d;
Message *m, **ll;
static char err[ERRMAX];
mdprint(mdir, "mdirread()\n");
if((fd = open(mb->path, OREAD)) == -1){
errstr(err, sizeof err);
return err;
}
if((d = dirfstat(fd)) == nil){
errstr(err, sizeof err);
close(fd);
return err;
}
*new = nnew = 0;
if(mb->d){
if(d->qid.path == mb->d->qid.path)
if(d->qid.vers == mb->d->qid.vers){
mdprint(mdir, "\tqids match\n");
close(fd);
free(d);
goto finished;
}
free(mb->d);
}
logmsg(nil, "reading %s (mdir)", mb->path);
mb->d = d;
n = dirreadall(fd, &d);
close(fd);
if(n == -1){
errstr(err, sizeof err);
return err;
}
qsort(d, n, sizeof *d, (int(*)(void*, void*))dircmp);
ndel = 0;
ll = &mb->root->part;
for(i = 0; *ll || i < n; ){
if(i < n && dirskip(d + i, &uv)){
i++;
continue;
}
c = -1;
if(i >= n)
c = 1;
else if(*ll)
c = vcmp(uv, (*ll)->fileid);
mdprint(mdir, "consider %s and %D -> %d\n", i<n? d[i].name: 0, *ll? (*ll)->fileid: 1ull, c);
if(c < 0){
/* new message */
mdprint(mdir, "new: %s (%D)\n", d[i].name, *ll? (*ll)->fileid: 0);
m = newmessage(mb->root);
m->fileid = uv;
if(setsize(mb, m) < 0 || m->size >= Maxmsg){
/* message disappeared? unchain */
mdprint(mdir, "deleted → %r (%D)\n", m->fileid);
logmsg(m, "disappeared");
if(doplumb)
mailplumb(mb, m, 1); /* redundant */
unnewmessage(mb, mb->root, m);
/* we're out of sync; note this by dropping cached qid */
mb->d->qid.path = 0;
break;
}
m->inmbox = 1;
nnew++;
m->next = *ll;
*ll = m;
ll = &m->next;
logmsg(m, "new %s", d[i].name);
i++;
newcachehash(mb, m, doplumb);
putcache(mb, m);
}else if(c > 0){
/* deleted message; */
mdprint(mdir, "deleted: %s (%D)\n", i<n? d[i].name: 0, *ll? (*ll)->fileid: 0);
ndel++;
logmsg(*ll, "deleted (refs=%d)", *ll? (*ll)->refs: -42);
if(doplumb)
mailplumb(mb, *ll, 1);
(*ll)->inmbox = 0;
(*ll)->deleted = Disappear;
ll = &(*ll)->next;
}else{
//logmsg(*ll, "duplicate %s", d[i].name);
i++;
ll = &(*ll)->next;
}
}
free(d);
logmsg(nil, "mbox read: %d new %d deleted", nnew, ndel);
finished:
*new = nnew;
return nil;
}
static void
mdirdelete(Mailbox *mb, Message *m)
{
char mpath[Pathlen];
Mdir* mdir;
mdir = mb->aux;
snprint(mpath, sizeof mpath, "%s/%D", mb->path, m->fileid);
mdprint(mdir, "remove: %s\n", mpath);
/* may have been removed by other fs. just log the error. */
if(remove(mpath) == -1)
logmsg(m, "remove: %s: %r", mpath);
m->inmbox = 0;
}
static char*
mdirsync(Mailbox* mb, int doplumb, int *new)
{
Mdir *mdir;
mdir = mb->aux;
mdprint(mdir, "mdirsync()\n");
return mdirread(mdir, mb, doplumb, new);
}
static char*
mdirctl(Mailbox *mb, int c, char **v)
{
Mdir *mdir;
mdir = mb->aux;
if(c == 1 && strcmp(*v, "debug") == 0)
mdir->debug = 1;
else if(c == 1 && strcmp(*v, "nodebug") == 0)
mdir->debug = 0;
else
return "bad mdir control message";
return nil;
}
static void
mdirclose(Mailbox *mb)
{
free(mb->aux);
}
static int
qidcmp(Qid *a, Qid *b)
{
if(a->path != b->path)
return 1;
return a->vers - b->vers;
}
/*
* .idx strategy. we save the qid.path and .vers
* of the mdir directory and the date to the index.
* we accept the work done by the other upas/fs if
* the index is based on the same (or a newer)
* qid. in any event, we recheck the directory after
* the directory is four hours old.
*/
static int
idxr(char *s, Mailbox *mb)
{
char *f[5];
long t, δt, n;
Dir d;
n = tokenize(s, f, nelem(f));
if(n != 4 || strcmp(f[0], "mdirv1") != 0)
return -1;
t = strtoul(f[1], 0, 0);
δt = time(0) - t;
if(δt < 0 || δt > 4*3600)
return 0;
memset(&d, 0, sizeof d);
d.qid.path = strtoull(f[2], 0, 0);
d.qid.vers = strtoull(f[3], 0, 0);
if(mb->d && qidcmp(&mb->d->qid, &d.qid) >= 0)
return 0;
if(mb->d == 0)
mb->d = emalloc(sizeof d);
mb->d->qid = d.qid;
mb->d->mtime = t;
return 0;
}
static void
idxw(Biobuf *b, Mailbox *mb)
{
Qid q;
memset(&q, 0, sizeof q);
if(mb->d)
q = mb->d->qid;
Bprint(b, "mdirv1 %lud %llud %lud\n", time(0), q.path, q.vers);
}
char*
mdirmbox(Mailbox *mb, char *path)
{
int m;
Dir *d;
Mdir *mdir;
d = dirstat(path);
if(!d && mb->flags & DMcreate){
createfolder(getuser(), path);
d = dirstat(path);
}
m = d && (d->mode & DMDIR);
free(d);
if(!m)
return Enotme;
snprint(mb->path, sizeof mb->path, "%s", path);
mdir = emalloc(sizeof *mdir);
mdir->debug = 0;
mb->aux = mdir;
mb->sync = mdirsync;
mb->close = mdirclose;
mb->fetch = mdirfetch;
mb->delete = mdirdelete;
mb->remove = localremove;
mb->rename = localrename;
mb->idxread = idxr;
mb->idxwrite = idxw;
mb->ctl = mdirctl;
return nil;
}
|