Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Acme interface to nntpfs.
 */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "win.h"
#include <ctype.h>

int canpost;
int debug;
int nshow = 20;

int lo; /* next message to look at in dir */
int hi; /* current hi message in dir */
char *dir = "/mnt/news";
char *group;
char *from;

typedef struct Article Article;
struct Article {
        Ref;
        Article *prev;
        Article *next;
        Window *w;
        int n;
        int dead;
        int dirtied;
        int sayspost;
        int headers;
        int ispost;
};

Article *mlist;
Window *root;

int
cistrncmp(char *a, char *b, int n)
{
        while(n-- > 0){
                if(tolower(*a++) != tolower(*b++))
                        return -1;
        }
        return 0;
}

int
cistrcmp(char *a, char *b)
{
        for(;;){
                if(tolower(*a) != tolower(*b++))
                        return -1;
                if(*a++ == 0)
                        break;
        }
        return 0;
}

char*
skipwhite(char *p)
{
        while(isspace(*p))
                p++;
        return p;
}

int
gethi(void)
{
        Dir *d;
        int hi;

        if((d = dirstat(dir)) == nil)
                return -1;
        hi = d->qid.vers;
        free(d);
        return hi;
}

char*
fixfrom(char *s)
{
        char *r, *w;

        s = estrdup(s);
        /* remove quotes */
        for(r=w=s; *r; r++)
                if(*r != '"')
                        *w++ = *r;
        *w = '\0';
        return s;
}

char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", };
char *mon[] = {
        "Jan", "Feb", "Mar", "Apr",
        "May", "Jun", "Jul", "Aug",
        "Sep", "Oct", "Nov", "Dec",
};

char*
fixdate(char *s)
{
        char *f[10], *m, *t, *wd, tmp[40];
        int d, i, j, nf, hh, mm;

        nf = tokenize(s, f, nelem(f));

        wd = nil;
        d = 0;
        m = nil;
        t = nil;
        for(i=0; i<nf; i++){
                for(j=0; j<7; j++)
                        if(cistrncmp(day[j], f[i], 3)==0)
                                wd = day[j];
                for(j=0; j<12; j++)
                        if(cistrncmp(mon[j], f[i], 3)==0)
                                m = mon[j];
                j = atoi(f[i]);
                if(1 <= j && j <= 31 && d != 0)
                        d = j;
                if(strchr(f[i], ':'))
                        t = f[i];
        }

        if(d==0 || wd==nil || m==nil || t==nil)
                return nil;

        hh = strtol(t, 0, 10);
        mm = strtol(strchr(t, ':')+1, 0, 10);
        sprint(tmp, "%s %d %s %d:%.2d", wd, d, m, hh, mm);
        return estrdup(tmp);
}

void
msgheadline(Biobuf *bin, int n, Biobuf *bout)
{
        char *p, *q;
        char *date;
        char *from;
        char *subject;

        date = nil;
        from = nil;
        subject = nil;
        while(p = Brdline(bin, '\n')){
                p[Blinelen(bin)-1] = '\0';
                if((q = strchr(p, ':')) == nil)
                        continue;
                *q++ = '\0';
                if(cistrcmp(p, "from")==0)
                        from = fixfrom(skipwhite(q));
                else if(cistrcmp(p, "subject")==0)
                        subject = estrdup(skipwhite(q));
                else if(cistrcmp(p, "date")==0)
                        date = fixdate(skipwhite(q));
        }

        Bprint(bout, "%d/\t%s", n, from ? from : "");
        if(date)
                Bprint(bout, "\t%s", date);
        if(subject)
                Bprint(bout, "\n\t%s", subject);
        Bprint(bout, "\n");

        free(date);
        free(from);
        free(subject);
}

/*
 * Write the headers for at most nshow messages to b,
 * starting with hi and working down to lo.
 * Return number of first message not scanned.
 */
int
adddir(Biobuf *body, int hi, int lo, int nshow)
{
        char *p, *q, tmp[40];
        int i, n;
        Biobuf *b;

        n = 0;
        for(i=hi; i>=lo && n<nshow; i--){
                sprint(tmp, "%d", i);
                p = estrstrdup(dir, tmp);
                if(access(p, OREAD) < 0){
                        free(p);
                        break;
                }
                q = estrstrdup(p, "/header");
                free(p);
                b = Bopen(q, OREAD);
                free(q);
                if(b == nil)
                        continue;
                msgheadline(b, i, body);
                Bterm(b);
                n++;
        }
        return i;
}

/* 
 * Show the first nshow messages in the window.
 * This depends on nntpfs presenting contiguously
 * numbered directories, and on the qid version being
 * the topmost numbered directory.
 */
void
dirwindow(Window *w)
{
        if((hi=gethi()) < 0)
                return;

        if(w->data < 0)
                w->data = winopenfile(w, "data");

        fprint(w->ctl, "dirty\n");
        
        winopenbody(w, OWRITE);
        lo = adddir(w->body, hi, 0, nshow);
        winclean(w);
}

/* return 1 if handled, 0 otherwise */
static int
iscmd(char *s, char *cmd)
{
        int len;

        len = strlen(cmd);
        return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
}

static char*
skip(char *s, char *cmd)
{
        s += strlen(cmd);
        while(*s==' ' || *s=='\t' || *s=='\n')
                s++;
        return s;
}

void
unlink(Article *m)
{
        if(mlist == m)
                mlist = m->next;

        if(m->next)
                m->next->prev = m->prev;
        if(m->prev)
                m->prev->next = m->next;
        m->next = m->prev = nil;
}

int mesgopen(char*);
int fillmesgwindow(int, Article*);
Article *newpost(void);
void replywindow(Article*);
void mesgpost(Article*);

/*
 * Depends on d.qid.vers being highest numbered message in dir.
 */
void
acmetimer(Article *m, Window *w)
{
        Biobuf *b;
        Dir *d;

        assert(m==nil && w==root);

        if((d = dirstat(dir))==nil | hi==d->qid.vers){
                free(d);
                return;
        }

        if(w->data < 0)
                w->data = winopenfile(w, "data");
        if(winsetaddr(w, "0", 0))
                write(w->data, "", 0);

        b = emalloc(sizeof(*b));
        Binit(b, w->data, OWRITE);
        adddir(b, d->qid.vers, hi+1, d->qid.vers);
        hi = d->qid.vers;
        Bterm(b);
        free(b);
        free(d);
        winselect(w, "0,.", 0);
}

int
acmeload(Article*, Window*, char *s)
{
        int nopen;

//fprint(2, "load %s\n", s);

        nopen = 0;
        while(*s){
                /* skip directory name */
                if(strncmp(s, dir, strlen(dir))==0)
                        s += strlen(dir);
                nopen += mesgopen(s);
                if((s = strchr(s, '\n')) == nil)
                        break;
                s = skip(s, "");
        }
        return nopen;
}

int
acmecmd(Article *m, Window *w, char *s)
{
        int n;
        Biobuf *b;

//fprint(2, "cmd %s\n", s);

        s = skip(s, "");

        if(iscmd(s, "Del")){
                if(m == nil){   /* don't close dir until messages close */
                        if(mlist != nil){
                                ctlprint(mlist->w->ctl, "show\n");
                                return 1;
                        }
                        if(windel(w, 0))
                                threadexitsall(nil);
                        return 1;
                }else{
                        if(windel(w, 0))
                                m->dead = 1;
                        return 1;
                }
        }
        if(m==nil && iscmd(s, "More")){
                s = skip(s, "More");
                if(n = atoi(s))
                        nshow = n;

                if(w->data < 0)
                        w->data = winopenfile(w, "data");
                winsetaddr(w, "$", 1);
        
                fprint(w->ctl, "dirty\n");

                b = emalloc(sizeof(*b));
                Binit(b, w->data, OWRITE);
                lo = adddir(b, lo, 0, nshow);
                Bterm(b);
                free(b);                
                winclean(w);
                winsetaddr(w, ".,", 0);
        }
        if(m!=nil && !m->ispost && iscmd(s, "Headers")){
                m->headers = !m->headers;
                fillmesgwindow(-1, m);
                return 1;
        }
        if(iscmd(s, "Newpost")){
                m = newpost();
                winopenbody(m->w, OWRITE);
                Bprint(m->w->body, "%s\nsubject: \n\n", group);
                winclean(m->w);
                winselect(m->w, "$", 0);
                return 1;
        }
        if(m!=nil && !m->ispost && iscmd(s, "Reply")){
                replywindow(m);
                return 1;
        }
//      if(m!=nil && iscmd(s, "Replymail")){
//              fprint(2, "no replymail yet\n");
//              return 1;
//      }
        if(iscmd(s, "Post")){
                mesgpost(m);
                return 1;
        }
        return 0;
}

void
acmeevent(Article *m, Window *w, Event *e)
{
        Event *ea, *e2, *eq;
        char *s, *t, *buf;
        int na;
        //int n;
        //ulong q0, q1;

        switch(e->c1){  /* origin of action */
        default:
        Unknown:
                fprint(2, "unknown message %c%c\n", e->c1, e->c2);
                break;

        case 'T':       /* bogus timer event! */
                acmetimer(m, w);
                break;

        case 'F':       /* generated by our actions; ignore */
                break;

        case 'E':       /* write to body or tag; can't affect us */
                break;

        case 'K':       /* type away; we don't care */
                if(m && (e->c2 == 'I' || e->c2 == 'D')){
                        m->dirtied = 1;
                        if(!m->sayspost){
                                wintagwrite(w, "Post ", 5);
                                m->sayspost = 1;
                        }
                }
                break;

        case 'M':       /* mouse event */
                switch(e->c2){          /* type of action */
                case 'x':       /* mouse: button 2 in tag */
                case 'X':       /* mouse: button 2 in body */
                        ea = nil;
                        //e2 = nil;
                        s = e->b;
                        if(e->flag & 2){        /* null string with non-null expansion */
                                e2 = recvp(w->cevent);
                                if(e->nb==0)
                                        s = e2->b;
                        }
                        if(e->flag & 8){        /* chorded argument */
                                ea = recvp(w->cevent);  /* argument */
                                na = ea->nb;
                                recvp(w->cevent);               /* ignore origin */
                        }else
                                na = 0;
                        
                        /* append chorded arguments */
                        if(na){
                                t = emalloc(strlen(s)+1+na+1);
                                sprint(t, "%s %s", s, ea->b);
                                s = t;
                        }
                        /* if it's a known command, do it */
                        /* if it's a long message, it can't be for us anyway */
                //      DPRINT(2, "exec: %s\n", s);
                        if(!acmecmd(m, w, s))   /* send it back */
                                winwriteevent(w, e);
                        if(na)
                                free(s);
                        break;

                case 'l':       /* mouse: button 3 in tag */
                case 'L':       /* mouse: button 3 in body */
                        //buf = nil;
                        eq = e;
                        if(e->flag & 2){
                                e2 = recvp(w->cevent);
                                eq = e2;
                        }
                        s = eq->b;
                        if(eq->q1>eq->q0 && eq->nb==0){
                                buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
                                winread(w, eq->q0, eq->q1, buf);
                                s = buf;
                        }
                        if(!acmeload(m, w, s))
                                winwriteevent(w, e);
                        break;

                case 'i':       /* mouse: text inserted in tag */
                case 'd':       /* mouse: text deleted from tag */
                        break;

                case 'I':       /* mouse: text inserted in body */
                case 'D':       /* mouse: text deleted from body */
                        if(m == nil)
                                break;

                        m->dirtied = 1;
                        if(!m->sayspost){
                                wintagwrite(w, "Post ", 5);
                                m->sayspost = 1;
                        }
                        break;

                default:
                        goto Unknown;
                }
        }
}

void
dirthread(void *v)
{
        Event *e;
        Window *w;

        w = v;
        while(e = recvp(w->cevent))
                acmeevent(nil, w, e);

        threadexitsall(nil);
}

void
mesgthread(void *v)
{
        Event *e;
        Article *m;

        m = v;
        while(!m->dead && (e = recvp(m->w->cevent)))
                acmeevent(m, m->w, e);

//fprint(2, "msg %p exits\n", m);
        unlink(m);
        free(m->w);
        free(m);
        threadexits(nil);
}

/*
Xref: news.research.att.com comp.os.plan9:7360
Newsgroups: comp.os.plan9
Path: news.research.att.com!batch0!uunet!ffx.uu.net!finch!news.mindspring.net!newsfeed.mathworks.com!fu-berlin.de!server1.netnews.ja.net!hgmp.mrc.ac.uk!pegasus.csx.cam.ac.uk!bath.ac.uk!ccsdhd
From: Stephen Adam <saadam@bigpond.com>
Subject: Future of Plan9
Approved: plan9mod@bath.ac.uk
X-Newsreader: Microsoft Outlook Express 5.00.2014.211
X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211
Sender: ccsdhd@bath.ac.uk (Dennis Davis)
Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST
NNTP-Posting-Host: 203.54.121.233
Organization: Telstra BigPond Internet Services (http://www.bigpond.com)
X-Date: Wed, 13 Dec 2000 20:43:37 +1000
Lines: 12
Message-ID: <xbIZ5.157945$e5.114349@newsfeeds.bigpond.com>
References: <95pghu$3lf$1@news.fas.harvard.edu> <95ph36$3m9$1@news.fas.harvard.edu> <slrn980iic.u5q.mperrin@hcs.harvard.edu> <95png6$4ln$1@news.fas.harvard.edu> <95poqg$4rq$1@news.fas.harvard.edu> <slrn980vh8.2gb.myLastName@is07.fas.harvard.edu> <95q40h$66c$2@news.fas.harvard.edu> <95qjhu$8ke$1@news.fas.harvard.edu> <95riue$bu2$1@news.fas.harvard.edu> <95rnar$cbu$1@news.fas.harvard.edu>
X-Msmail-Priority: Normal
X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST)
X-Priority: 3
Date: Wed, 13 Dec 2000 10:49:50 GMT
*/

char *skipheader[] = 
{
        "x-",
        "path:",
        "xref:",
        "approved:",
        "sender:",
        "nntp-",
        "organization:",
        "lines:",
        "message-id:",
        "references:",
        "reply-to:",
        "mime-",
        "content-",
};

int
fillmesgwindow(int fd, Article *m)
{
        Biobuf *b;
        char *p, tmp[40];
        int i, inhdr, copy, xfd;
        Window *w;

        xfd = -1;
        if(fd == -1){
                sprint(tmp, "%d/article", m->n);
                p = estrstrdup(dir, tmp);
                if((xfd = open(p, OREAD)) < 0){
                        free(p);        
                        return 0;
                }
                free(p);
                fd = xfd;
        }

        w = m->w;
        if(w->data < 0)
                w->data = winopenfile(w, "data");
        if(winsetaddr(w, ",", 0))
                write(w->data, "", 0);

        winopenbody(m->w, OWRITE);
        b = emalloc(sizeof(*b));
        Binit(b, fd, OREAD);

        inhdr = 1;
        copy = 1;
        while(p = Brdline(b, '\n')){
                if(Blinelen(b)==1)
                        inhdr = 0, copy=1;
                if(inhdr && !isspace(p[0])){
                        copy = 1;
                        if(!m->headers){
                                if(cistrncmp(p, "from:", 5)==0){
                                        p[Blinelen(b)-1] = '\0';
                                        p = fixfrom(skip(p, "from:"));
                                        Bprint(m->w->body, "From: %s\n", p);
                                        free(p);
                                        copy = 0;
                                        continue;
                                }
                                for(i=0; i<nelem(skipheader); i++)
                                        if(cistrncmp(p, skipheader[i], strlen(skipheader[i]))==0)
                                                copy=0;
                        }
                }
                if(copy)
                        Bwrite(m->w->body, p, Blinelen(b));
        }
        Bterm(b);
        free(b);
        winclean(m->w);
        if(xfd != -1)
                close(xfd);
        return 1;
}

Article*
newpost(void)
{
        Article *m;
        char *p, tmp[40];
        static int nnew;

        m = emalloc(sizeof *m);
        sprint(tmp, "Post%d", ++nnew);
        p = estrstrdup(dir, tmp);

        m->w = newwindow();
        proccreate(wineventproc, m->w, STACK);
        winname(m->w, p);
        wintagwrite(m->w, "Post ", 5);
        m->sayspost = 1;
        m->ispost = 1;
        threadcreate(mesgthread, m, STACK);

        if(mlist){
                m->next = mlist;
                mlist->prev = m;
        }
        mlist = m;
        return m;
}

void
replywindow(Article *m)
{
        Biobuf *b;
        char *p, *ep, *q, tmp[40];
        int fd, copy;
        Article *reply;

        sprint(tmp, "%d/article", m->n);
        p = estrstrdup(dir, tmp);
        if((fd = open(p, OREAD)) < 0){
                free(p);        
                return;
        }
        free(p);

        reply = newpost();
        winopenbody(reply->w, OWRITE);
        b = emalloc(sizeof(*b));
        Binit(b, fd, OREAD);
        copy = 0;
        while(p = Brdline(b, '\n')){
                if(Blinelen(b)==1)
                        break;
                ep = p+Blinelen(b);
                if(!isspace(*p)){
                        copy = 0;
                        if(cistrncmp(p, "newsgroups:", 11)==0){
                                for(q=p+11; *q!='\n'; q++)
                                        if(*q==',')
                                                *q = ' ';
                                copy = 1;
                        }else if(cistrncmp(p, "subject:", 8)==0){
                                if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){
                                        p = skip(p, "subject:");
                                        ep[-1] = '\0';
                                        Bprint(reply->w->body, "Subject: Re: %s\n", p);
                                }else
                                        copy = 1;
                        }else if(cistrncmp(p, "message-id:", 11)==0){
                                Bprint(reply->w->body, "References: ");
                                p += 11;
                                copy = 1;
                        }
                }
                if(copy)
                        Bwrite(reply->w->body, p, ep-p);
        }
        Bterm(b);
        close(fd);
        free(b);
        Bprint(reply->w->body, "\n");
        winclean(reply->w);
        winselect(reply->w, "$", 0);
}

char*
skipbl(char *s, char *e)
{
        while(s < e){
                if(*s!=' ' && *s!='\t' && *s!=',')
                        break;
                s++;
        }
        return s;
}

char*
findbl(char *s, char *e)
{
        while(s < e){
                if(*s==' ' || *s=='\t' || *s==',')
                        break;
                s++;
        }
        return s;
}

/*
 * comma-separate possibly blank-separated strings in line; e points before newline
 */
void
commas(char *s, char *e)
{
        char *t;

        /* may have initial blanks */
        s = skipbl(s, e);
        while(s < e){
                s = findbl(s, e);
                if(s == e)
                        break;
                t = skipbl(s, e);
                if(t == e)      /* no more words */
                        break;
                /* patch comma */
                *s++ = ',';
                while(s < t)
                        *s++ = ' ';
        }
}
void
mesgpost(Article *m)
{
        Biobuf *b;
        char *p, *ep;
        int isfirst, ishdr, havegroup, havefrom;

        p = estrstrdup(dir, "post");
        if((b = Bopen(p, OWRITE)) == nil){
                fprint(2, "cannot open %s: %r\n", p);
                free(p);
                return;
        }
        free(p);

        winopenbody(m->w, OREAD);
        ishdr = 1;
        isfirst = 1;
        havegroup = havefrom = 0;
        while(p = Brdline(m->w->body, '\n')){
                ep = p+Blinelen(m->w->body);
                if(ishdr && p+1==ep){
                        if(!havegroup)
                                Bprint(b, "Newsgroups: %s\n", group);
                        if(!havefrom)
                                Bprint(b, "From: %s\n", from);
                        ishdr = 0;
                }
                if(ishdr){
                        ep[-1] = '\0';
                        if(isfirst && strchr(p, ':')==0){       /* group list */
                                commas(p, ep);
                                Bprint(b, "newsgroups: %s\n", p);
                                havegroup = 1;
                                isfirst = 0;
                                continue;
                        }
                        if(cistrncmp(p, "newsgroup:", 10)==0){
                                commas(skip(p, "newsgroup:"), ep);
                                Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:"));
                                havegroup = 1;
                                continue;
                        }
                        if(cistrncmp(p, "newsgroups:", 11)==0){
                                commas(skip(p, "newsgroups:"), ep);
                                Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:"));
                                havegroup = 1;
                                continue;
                        }
                        if(cistrncmp(p, "from:", 5)==0)
                                havefrom = 1;
                        ep[-1] = '\n';
                }
                Bwrite(b, p, ep-p);
        }
        winclosebody(m->w);
        Bflush(b);
        if(write(Bfildes(b), "", 0) == 0)
                winclean(m->w);
        else
                fprint(2, "post: %r\n");
        Bterm(b);
}

int
mesgopen(char *s)
{
        char *p, tmp[40];
        int fd, n;
        Article *m;

        n = atoi(s);
        if(n==0)
                return 0;

        for(m=mlist; m; m=m->next){
                if(m->n == n){
                        ctlprint(m->w->ctl, "show\n");
                        return 1;
                }
        }

        sprint(tmp, "%d/article", n);
        p = estrstrdup(dir, tmp);
        if((fd = open(p, OREAD)) < 0){
                free(p);        
                return 0;
        }

        m = emalloc(sizeof(*m));
        m->w = newwindow();
        m->n = n;
        proccreate(wineventproc, m->w, STACK);
        p[strlen(p)-strlen("article")] = '\0';
        winname(m->w, p);
        if(canpost)
                wintagwrite(m->w, "Reply ", 6);
        wintagwrite(m->w, "Headers ", 8);

        free(p);
        if(mlist){
                m->next = mlist;
                mlist->prev = m;
        }
        mlist = m;
        threadcreate(mesgthread, m, STACK);

        fillmesgwindow(fd, m);
        close(fd);
        windormant(m->w);
        return 1;
}

void
usage(void)
{
        fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n");
        exits("usage");
}

void
timerproc(void *v)
{
        Event e;
        Window *w;

        memset(&e, 0, sizeof e);
        e.c1 = 'T';
        w = v;

        for(;;){
                sleep(60*1000);
                sendp(w->cevent, &e);
        }
}

char*
findfrom(void)
{
        char *p, *u;
        Biobuf *b;

        u = getuser();
        if(u==nil)
                return "glenda";

        p = estrstrstrdup("/usr/", u, "/lib/newsfrom");
        b = Bopen(p, OREAD);
        free(p);
        if(b){
                p = Brdline(b, '\n');
                if(p){
                        p[Blinelen(b)-1] = '\0';
                        p = estrdup(p);
                        Bterm(b);
                        return p;
                }
                Bterm(b);
        }

        p = estrstrstrdup("/mail/box/", u, "/headers");
        b = Bopen(p, OREAD);
        free(p);
        if(b){
                while(p = Brdline(b, '\n')){
                        p[Blinelen(b)-1] = '\0';
                        if(cistrncmp(p, "from:", 5)==0){
                                p = estrdup(skip(p, "from:"));
                                Bterm(b);
                                return p;
                        }
                }
                Bterm(b);
        }

        return u;
}

void
threadmain(int argc, char **argv)
{
        char *p, *q;
        Dir *d;
        Window *w;

        ARGBEGIN{
        case 'D':
                debug++;
                break;
        case 'd':
                dir = EARGF(usage());
                break;
        default:
                usage();
                break;
        }ARGEND

        if(argc != 1)
                usage();

        from = findfrom();

        group = estrdup(argv[0]);       /* someone will be cute */
        while(q=strchr(group, '/'))
                *q = '.';

        p = estrdup(argv[0]);
        while(q=strchr(p, '.'))
                *q = '/';
        p = estrstrstrdup(dir, "/", p);
        cleanname(p);

        if((d = dirstat(p)) == nil){    /* maybe it is a new group */
                if((d = dirstat(dir)) == nil){
                        fprint(2, "dirstat(%s) fails: %r\n", dir);
                        threadexitsall(nil);
                }
                if((d->mode&DMDIR)==0){
                        fprint(2, "%s not a directory\n", dir);
                        threadexitsall(nil);
                }
                free(d);
                if((d = dirstat(p)) == nil){
                        fprint(2, "stat %s: %r\n", p);
                        threadexitsall(nil);
                }
        }
        if((d->mode&DMDIR)==0){
                fprint(2, "%s not a directory\n", dir);
                threadexitsall(nil);
        }
        free(d);
        dir = estrstrdup(p, "/");

        q = estrstrdup(dir, "post");
        canpost = access(q, AWRITE)==0;

        w = newwindow();
        root = w;
        proccreate(wineventproc, w, STACK);
        proccreate(timerproc, w, STACK);

        winname(w, dir);
        if(canpost)
                wintagwrite(w, "Newpost ", 8);
        wintagwrite(w, "More ", 5);
        dirwindow(w);
        threadcreate(dirthread, w, STACK);
        threadexits(nil);
}