#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", ifileid: 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", ifileid: 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; }