Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"

enum {
        Buffersize = 64*1024,
};

typedef struct Inbuf Inbuf;
struct Inbuf
{
        int     fd;
        uchar   *lim;
        uchar   *rptr;
        uchar   *wptr;
        uchar   data[Buffersize+7];
};

static void
addtomessage(Message *m, uchar *p, int n, int done)
{
        int i, len;

        // add to message (+1 in malloc is for a trailing NUL)
        if(m->lim - m->end < n){
                if(m->start != nil){
                        i = m->end-m->start;
                        if(done)
                                len = i + n;
                        else
                                len = (4*(i+n))/3;
                        m->start = erealloc(m->start, len + 1);
                        m->end = m->start + i;
                } else {
                        if(done)
                                len = n;
                        else
                                len = 2*n;
                        m->start = emalloc(len + 1);
                        m->end = m->start;
                }
                m->lim = m->start + len;
                *m->lim = '\0';
        }

        memmove(m->end, p, n);
        m->end += n;
        *m->end = '\0';
}

//
//  read in a single message
//
static int
readmessage(Message *m, Inbuf *inb)
{
        int i, n, done;
        uchar *p, *np;
        char sdigest[SHA1dlen*2+1];
        char tmp[64];

        for(done = 0; !done;){
                n = inb->wptr - inb->rptr;
                if(n < 6){
                        if(n)
                                memmove(inb->data, inb->rptr, n);
                        inb->rptr = inb->data;
                        inb->wptr = inb->rptr + n;
                        i = read(inb->fd, inb->wptr, Buffersize);
                        if(i < 0){
                                if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
                                        strcpy(tmp, "unknown mailbox");
                                fprint(2, "error reading '%s': %r\n", tmp);
                                return -1;
                        }
                        if(i == 0){
                                if(n != 0)
                                        addtomessage(m, inb->rptr, n, 1);
                                if(m->end == m->start)
                                        return -1;
                                break;
                        }
                        inb->wptr += i;
                }

                // look for end of message
                for(p = inb->rptr; p < inb->wptr; p = np+1){
                        // first part of search for '\nFrom '
                        np = memchr(p, '\n', inb->wptr - p);
                        if(np == nil){
                                p = inb->wptr;
                                break;
                        }

                        /*
                         *  if we've found a \n but there's
                         *  not enough room for '\nFrom ', don't do
                         *  the comparison till we've read in more.
                         */
                        if(inb->wptr - np < 6){
                                p = np;
                                break;
                        }

                        if(strncmp((char*)np, "\nFrom ", 6) == 0){
                                done = 1;
                                p = np+1;
                                break;
                        }
                }

                // add to message (+ 1 in malloc is for a trailing null)
                n = p - inb->rptr;
                addtomessage(m, inb->rptr, n, done);
                inb->rptr += n;
        }

        // if it doesn't start with a 'From ', this ain't a mailbox
        if(strncmp(m->start, "From ", 5) != 0)
                return -1;

        // dump trailing newline, make sure there's a trailing null
        // (helps in body searches)
        if(*(m->end-1) == '\n')
                m->end--;
        *m->end = 0;
        m->bend = m->rbend = m->end;

        // digest message
        sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
        for(i = 0; i < SHA1dlen; i++)
                sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
        m->sdigest = s_copy(sdigest);

        return 0;
}


// throw out deleted messages.  return number of freshly deleted messages
int
purgedeleted(Mailbox *mb)
{
        Message *m, *next;
        int newdels;

        // forget about what's no longer in the mailbox
        newdels = 0;
        for(m = mb->root->part; m != nil; m = next){
                next = m->next;
                if(m->deleted && m->refs == 0){
                        if(m->inmbox)
                                newdels++;
                        delmessage(mb, m);
                }
        }
        return newdels;
}

//
//  read in the mailbox and parse into messages.
//
static char*
_readmbox(Mailbox *mb, int doplumb, Mlock *lk)
{
        int fd, n;
        String *tmp;
        Dir *d;
        static char err[Errlen];
        Message *m, **l;
        Inbuf *inb;
        char *x;

        l = &mb->root->part;

        /*
         *  open the mailbox.  If it doesn't exist, try the temporary one.
         */
        n = 0;
retry:
        fd = open(mb->path, OREAD);
        if(fd < 0){
                rerrstr(err, sizeof(err));
                if(strstr(err, "exclusive lock") != 0 && n++ < 20){
                        sleep(500);     /* wait for lock to go away */
                        goto retry;
                }
                if(strstr(err, "exist") != 0){
                        tmp = s_copy(mb->path);
                        s_append(tmp, ".tmp");
                        if(sysrename(s_to_c(tmp), mb->path) == 0){
                                s_free(tmp);
                                goto retry;
                        }
                        s_free(tmp);
                }
                return err;
        }

        /*
         *  a new qid.path means reread the mailbox, while
         *  a new qid.vers means read any new messages
         */
        d = dirfstat(fd);
        if(d == nil){
                close(fd);
                errstr(err, sizeof(err));
                return err;
        }
        if(mb->d != nil){
                if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
                        close(fd);
                        free(d);
                        return nil;
                }
                if(d->qid.path == mb->d->qid.path){
                        while(*l != nil)
                                l = &(*l)->next;
                        seek(fd, mb->d->length, 0);
                }
                free(mb->d);
        }
        mb->d = d;
        mb->vers++;
        henter(PATH(0, Qtop), mb->name,
                (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);

        inb = emalloc(sizeof(Inbuf));
        inb->rptr = inb->wptr = inb->data;
        inb->fd = fd;

        //  read new messages
        snprint(err, sizeof err, "reading '%s'", mb->path);
        logmsg(err, nil);
        for(;;){
                if(lk != nil)
                        syslockrefresh(lk);
                m = newmessage(mb->root);
                m->mallocd = 1;
                m->inmbox = 1;
                if(readmessage(m, inb) < 0){
                        delmessage(mb, m);
                        mb->root->subname--;
                        break;
                }

                // merge mailbox versions
                while(*l != nil){
                        if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
                                // matches mail we already read, discard
                                logmsg("duplicate", *l);
                                delmessage(mb, m);
                                mb->root->subname--;
                                m = nil;
                                l = &(*l)->next;
                                break;
                        } else {
                                // old mail no longer in box, mark deleted
                                logmsg("disappeared", *l);
                                if(doplumb)
                                        mailplumb(mb, *l, 1);
                                (*l)->inmbox = 0;
                                (*l)->deleted = 1;
                                l = &(*l)->next;
                        }
                }
                if(m == nil)
                        continue;

                x = strchr(m->start, '\n');
                if(x == nil)
                        m->header = m->end;
                else
                        m->header = x + 1;
                m->mheader = m->mhend = m->header;
                parseunix(m);
                parse(m, 0, mb, 0);
                logmsg("new", m);

                /* chain in */
                *l = m;
                l = &m->next;
                if(doplumb)
                        mailplumb(mb, m, 0);

        }
        logmsg("mbox read", nil);

        // whatever is left has been removed from the mbox, mark deleted
        while(*l != nil){
                if(doplumb)
                        mailplumb(mb, *l, 1);
                (*l)->inmbox = 0;
                (*l)->deleted = 1;
                l = &(*l)->next;
        }

        close(fd);
        free(inb);
        return nil;
}

static void
_writembox(Mailbox *mb, Mlock *lk)
{
        Dir *d;
        Message *m;
        String *tmp;
        int mode, errs;
        Biobuf *b;

        tmp = s_copy(mb->path);
        s_append(tmp, ".tmp");

        /*
         * preserve old files permissions, if possible
         */
        d = dirstat(mb->path);
        if(d != nil){
                mode = d->mode&0777;
                free(d);
        } else
                mode = MBOXMODE;

        sysremove(s_to_c(tmp));
        b = sysopen(s_to_c(tmp), "alc", mode);
        if(b == 0){
                fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
                return;
        }

        logmsg("writing new mbox", nil);
        errs = 0;
        for(m = mb->root->part; m != nil; m = m->next){
                if(lk != nil)
                        syslockrefresh(lk);
                if(m->deleted)
                        continue;
                logmsg("writing", m);
                if(Bwrite(b, m->start, m->end - m->start) < 0)
                        errs = 1;
                if(Bwrite(b, "\n", 1) < 0)
                        errs = 1;
        }
        logmsg("wrote new mbox", nil);

        if(sysclose(b) < 0)
                errs = 1;

        if(errs){
                fprint(2, "error writing temporary mail file\n");
                s_free(tmp);
                return;
        }

        sysremove(mb->path);
        if(sysrename(s_to_c(tmp), mb->path) < 0)
                fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
                        s_to_c(tmp), mb->path);
        s_free(tmp);
        if(mb->d != nil)
                free(mb->d);
        mb->d = dirstat(mb->path);
}

char*
plan9syncmbox(Mailbox *mb, int doplumb)
{
        Mlock *lk;
        char *rv;

        lk = nil;
        if(mb->dolock){
                lk = syslock(mb->path);
                if(lk == nil)
                        return "can't lock mailbox";
        }

        rv = _readmbox(mb, doplumb, lk);                /* interpolate */
        if(purgedeleted(mb) > 0)
                _writembox(mb, lk);

        if(lk != nil)
                sysunlock(lk);

        return rv;
}

//
//  look to see if we can open this mail box
//
char*
plan9mbox(Mailbox *mb, char *path)
{
        static char err[Errlen];
        String *tmp;

        if(access(path, AEXIST) < 0){
                errstr(err, sizeof(err));
                tmp = s_copy(path);
                s_append(tmp, ".tmp");
                if(access(s_to_c(tmp), AEXIST) < 0){
                        s_free(tmp);
                        return err;
                }
                s_free(tmp);
        }

        mb->sync = plan9syncmbox;
        return nil;
}