URI: 
       tnew files - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 7c709434eccd461a3f3c522df6224adf4e50a8da
   DIR parent 72fd2f881907db5e367ffee1fcecb737c44a0090
  HTML Author: rsc <devnull@localhost>
       Date:   Fri, 18 Mar 2005 19:30:22 +0000
       
       new files
       
       Diffstat:
         A src/cmd/netfiles/COPYING            |      24 ++++++++++++++++++++++++
         A src/cmd/netfiles/acme.c             |     636 +++++++++++++++++++++++++++++++
         A src/cmd/netfiles/acme.h             |      82 +++++++++++++++++++++++++++++++
         A src/cmd/netfiles/main.c             |     490 +++++++++++++++++++++++++++++++
         A src/cmd/netfiles/mkfile             |      28 ++++++++++++++++++++++++++++
         A src/cmd/netfiles/netfileget         |      45 +++++++++++++++++++++++++++++++
         A src/cmd/netfiles/netfileput         |      27 +++++++++++++++++++++++++++
         A src/cmd/netfiles/netfilestat        |      52 +++++++++++++++++++++++++++++++
         A src/cmd/netfiles/wait.c             |     120 +++++++++++++++++++++++++++++++
       
       9 files changed, 1504 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/src/cmd/netfiles/COPYING b/src/cmd/netfiles/COPYING
       t@@ -0,0 +1,24 @@
       +
       +Copyright (c) 2005 Russ Cox <rsc@swtch.com>
       +
       +Permission is hereby granted, free of charge, to any person obtaining
       +a copy of this software and associated documentation files (the
       +"Software"), to deal in the Software without restriction, including
       +without limitation the rights to use, copy, modify, merge, publish,
       +distribute, sublicense, and/or sell copies of the Software, and to
       +permit persons to whom the Software is furnished to do so, subject to
       +the following conditions:
       +
       +The above copyright notice and this permission notice shall be
       +included in all copies or substantial portions of the Software.
       +
       +These conditions shall not be whined about.
       +
       +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
       +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
       +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       +
   DIR diff --git a/src/cmd/netfiles/acme.c b/src/cmd/netfiles/acme.c
       t@@ -0,0 +1,636 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <thread.h>
       +#include <9pclient.h>
       +#include "acme.h"
       +
       +extern int *xxx;
       +static CFsys *acmefs;
       +Win *windows;
       +static Win *last;
       +
       +void
       +mountacme(void)
       +{
       +        if(acmefs == nil){
       +                acmefs = nsmount("acme", nil);
       +                if(acmefs == nil)
       +                        sysfatal("cannot mount acme: %r");
       +        }
       +}
       +
       +Win*
       +newwin(void)
       +{
       +        Win *w;
       +        CFid *fid;
       +        char buf[100];
       +        int id, n;
       +
       +        mountacme();
       +        fid = fsopen(acmefs, "new/ctl", ORDWR);
       +        if(fid == nil)
       +                sysfatal("open new/ctl: %r");
       +        n = fsread(fid, buf, sizeof buf-1);
       +        if(n <= 0)
       +                sysfatal("read new/ctl: %r");
       +        buf[n] = 0;
       +        id = atoi(buf);
       +        if(id == 0)
       +                sysfatal("read new/ctl: malformed message: %s", buf);
       +
       +        w = emalloc(sizeof *w);
       +        w->id = id;
       +        w->ctl = fid;
       +        w->next = nil;
       +        w->prev = last;
       +        if(last)
       +                last->next = w;
       +        else
       +                windows = w;
       +        last = w;
       +        return w;
       +}
       +
       +void
       +winclosefiles(Win *w)
       +{
       +        if(w->ctl){
       +                fsclose(w->ctl);
       +                w->ctl = nil;
       +        }
       +        if(w->body){
       +                fsclose(w->body);
       +                w->body = nil;
       +        }
       +        if(w->addr){
       +                fsclose(w->addr);
       +                w->addr = nil;
       +        }
       +        if(w->tag){
       +                fsclose(w->tag);
       +                w->tag = nil;
       +        }
       +        if(w->event){
       +                fsclose(w->event);
       +                w->event = nil;
       +        }
       +        if(w->data){
       +                fsclose(w->data);
       +                w->data = nil;
       +        }
       +        if(w->xdata){
       +                fsclose(w->xdata);
       +                w->xdata = nil;
       +        }
       +}
       +
       +void
       +winfree(Win *w)
       +{
       +        winclosefiles(w);
       +        if(w->c){
       +                chanfree(w->c);
       +                w->c = nil;
       +        }
       +        if(w->next)
       +                w->next->prev = w->prev;
       +        else
       +                last = w->prev;
       +        if(w->prev)
       +                w->prev->next = w->next;
       +        else
       +                windows = w->next;
       +        free(w);
       +}
       +
       +void
       +windeleteall(void)
       +{
       +        Win *w, *next;
       +
       +        for(w=windows; w; w=next){
       +                next = w->next;
       +                winctl(w, "delete");
       +        }
       +}
       +
       +static CFid*
       +wfid(Win *w, char *name)
       +{
       +        char buf[100];
       +        CFid **fid;
       +
       +        if(strcmp(name, "ctl") == 0)
       +                fid = &w->ctl;
       +        else if(strcmp(name, "body") == 0)
       +                fid = &w->body;
       +        else if(strcmp(name, "addr") == 0)
       +                fid = &w->addr;
       +        else if(strcmp(name, "tag") == 0)
       +                fid = &w->tag;
       +        else if(strcmp(name, "event") == 0)
       +                fid = &w->event;
       +        else if(strcmp(name, "data") == 0)
       +                fid = &w->data;
       +        else if(strcmp(name, "xdata") == 0)
       +                fid = &w->xdata;
       +        else{
       +                fid = 0;
       +                sysfatal("bad window file name %s", name);
       +        }
       +
       +        if(*fid == nil){
       +                snprint(buf, sizeof buf, "acme/%d/%s", w->id, name);
       +                *fid = fsopen(acmefs, buf, ORDWR);
       +                if(*fid == nil)
       +                        sysfatal("open %s: %r", buf);
       +        }
       +        return *fid;
       +}
       +
       +int
       +winopenfd(Win *w, char *name, int mode)
       +{
       +        char buf[100];
       +        
       +        snprint(buf, sizeof buf, "%d/%s", w->id, name);
       +        return fsopenfd(acmefs, buf, mode);
       +}
       +
       +int
       +winctl(Win *w, char *fmt, ...)
       +{
       +        char *s;
       +        va_list arg;
       +        CFid *fid;
       +        int n;
       +
       +        va_start(arg, fmt);
       +        s = evsmprint(fmt, arg);
       +        va_end(arg);
       +
       +        fid = wfid(w, "ctl");
       +        n = fspwrite(fid, s, strlen(s), 0);
       +        free(s);
       +        return n;
       +}
       +
       +int
       +winname(Win *w, char *fmt, ...)
       +{
       +        char *s;
       +        va_list arg;
       +        int n;
       +
       +        va_start(arg, fmt);
       +        s = evsmprint(fmt, arg);
       +        va_end(arg);
       +
       +        n = winctl(w, "name %s\n", s);
       +        free(s);
       +        return n;
       +}
       +
       +int
       +winprint(Win *w, char *name, char *fmt, ...)
       +{
       +        char *s;
       +        va_list arg;
       +        int n;
       +
       +        va_start(arg, fmt);
       +        s = evsmprint(fmt, arg);
       +        va_end(arg);
       +
       +        n = fswrite(wfid(w, name), s, strlen(s));
       +        free(s);
       +        return n;
       +}
       +
       +int
       +winaddr(Win *w, char *fmt, ...)
       +{
       +        char *s;
       +        va_list arg;
       +        int n;
       +
       +        va_start(arg, fmt);
       +        s = evsmprint(fmt, arg);
       +        va_end(arg);
       +
       +        n = fswrite(wfid(w, "addr"), s, strlen(s));
       +        free(s);
       +        return n;
       +}
       +
       +int
       +winreadaddr(Win *w, uint *q1)
       +{
       +        char buf[40], *p;
       +        uint q0;
       +        int n;
       +        
       +        n = fspread(wfid(w, "addr"), buf, sizeof buf-1, 0);
       +        if(n <= 0)
       +                return -1;
       +        buf[n] = 0;
       +        q0 = strtoul(buf, &p, 10);
       +        if(q1)
       +                *q1 = strtoul(p, nil, 10);
       +        return q0;
       +}
       +
       +int
       +winread(Win *w, char *file, void *a, int n)
       +{
       +        return fspread(wfid(w, file), a, n, 0);
       +}
       +
       +int
       +winwrite(Win *w, char *file, void *a, int n)
       +{
       +        return fswrite(wfid(w, file), a, n);
       +}
       +
       +char*
       +fsreadm(CFid *fid)
       +{
       +        char *buf;
       +        int n, tot, m;
       +        
       +        m = 128;
       +        buf = emalloc(m+1);
       +        tot = 0;
       +        while((n = fspread(fid, buf+tot, m-tot, tot)) > 0){
       +                tot += n;
       +                if(tot >= m){
       +                        m += 128;
       +                        buf = erealloc(buf, m+1);
       +                }
       +        }
       +        if(n < 0){
       +                free(buf);
       +                return nil;
       +        }
       +        buf[tot] = 0;
       +        return buf;
       +}
       +
       +char*
       +winmread(Win *w, char *file)
       +{
       +        return fsreadm(wfid(w, file));
       +}
       +
       +char*
       +winindex(void)
       +{
       +        CFid *fid;
       +        char *s;
       +        
       +        mountacme();
       +        if((fid = fsopen(acmefs, "index", ORDWR)) == nil)
       +                return nil;
       +        s = fsreadm(fid);
       +        fsclose(fid);
       +        return s;
       +}
       +
       +int
       +winseek(Win *w, char *file, int n, int off)
       +{
       +        return fsseek(wfid(w, file), n, off);
       +}
       +
       +int
       +winwriteevent(Win *w, Event *e)
       +{
       +        char buf[100];
       +
       +        snprint(buf, sizeof buf, "%c%c%d %d \n", e->c1, e->c2, e->q0, e->q1);
       +        return fswrite(wfid(w, "event"), buf, strlen(buf));
       +}
       +
       +int
       +windel(Win *w, int sure)
       +{
       +        return winctl(w, sure ? "delete" : "del");
       +}
       +
       +int
       +winfd(Win *w, char *name, int mode)
       +{
       +        char buf[100];
       +
       +        snprint(buf, sizeof buf, "acme/%d/%s", w->id, name);
       +        return fsopenfd(acmefs, buf, mode);
       +}
       +
       +static void
       +error(Win *w, char *msg)
       +{
       +        if(msg == nil)
       +                longjmp(w->jmp, 1);
       +        fprint(2, "%s: win%d: %s\n", argv0, w->id, msg);
       +        longjmp(w->jmp, 2);
       +}
       +
       +static int
       +getec(Win *w, CFid *efd)
       +{
       +        if(w->nbuf <= 0){
       +                w->nbuf = fsread(efd, w->buf, sizeof w->buf);
       +                if(w->nbuf <= 0)
       +                        error(w, nil);
       +                w->bufp = w->buf;
       +        }
       +        --w->nbuf;
       +        return *w->bufp++;
       +}
       +
       +static int
       +geten(Win *w, CFid *efd)
       +{
       +        int n, c;
       +
       +        n = 0;
       +        while('0'<=(c=getec(w,efd)) && c<='9')
       +                n = n*10+(c-'0');
       +        if(c != ' ')
       +                error(w, "event number syntax");
       +        return n;
       +}
       +
       +static int
       +geter(Win *w, CFid *efd, char *buf, int *nb)
       +{
       +        Rune r;
       +        int n;
       +
       +        r = getec(w, efd);
       +        buf[0] = r;
       +        n = 1;
       +        if(r < Runeself)
       +                goto Return;
       +        while(!fullrune(buf, n))
       +                buf[n++] = getec(w, efd);
       +        chartorune(&r, buf);
       +    Return:
       +        *nb = n;
       +        return r;
       +}
       +
       +static void
       +gete(Win *w, CFid *efd, Event *e)
       +{
       +        int i, nb;
       +
       +        e->c1 = getec(w, efd);
       +        e->c2 = getec(w, efd);
       +        e->q0 = geten(w, efd);
       +        e->q1 = geten(w, efd);
       +        e->flag = geten(w, efd);
       +        e->nr = geten(w, efd);
       +        if(e->nr > EVENTSIZE)
       +                error(w, "event string too long");
       +        e->nb = 0;
       +        for(i=0; i<e->nr; i++){
       +                /* e->r[i] = */ geter(w, efd, e->text+e->nb, &nb);
       +                e->nb += nb;
       +        }
       +/*         e->r[e->nr] = 0; */
       +        e->text[e->nb] = 0;
       +        if(getec(w, efd) != '\n')
       +                error(w, "event syntax 2");
       +}
       +
       +int
       +winreadevent(Win *w, Event *e)
       +{
       +        CFid *efd;
       +        int r;
       +
       +        if((r = setjmp(w->jmp)) != 0){
       +                if(r == 1)
       +                        return 0;
       +                return -1;
       +        }
       +        efd = wfid(w, "event");
       +        gete(w, efd, e);
       +        e->oq0 = e->q0;
       +        e->oq1 = e->q1;
       +
       +        /* expansion */
       +        if(e->flag&2){
       +                gete(w, efd, &w->e2);
       +                if(e->q0==e->q1){
       +                        w->e2.oq0 = e->q0;
       +                        w->e2.oq1 = e->q1;
       +                        w->e2.flag = e->flag;
       +                        *e = w->e2;
       +                }
       +        }
       +
       +        /* chorded argument */
       +        if(e->flag&8){
       +                gete(w, efd, &w->e3);        /* arg */
       +                gete(w, efd, &w->e4);        /* location */
       +                strcpy(e->arg, w->e3.text);
       +                strcpy(e->loc, w->e4.text);
       +        }
       +
       +        return 1;
       +}
       +
       +int
       +eventfmt(Fmt *fmt)
       +{
       +        Event *e;
       +
       +        e = va_arg(fmt->args, Event*);
       +        return fmtprint(fmt, "%c%c %d %d %d %d %q", e->c1, e->c2, e->q0, e->q1, e->flag, e->nr, e->text);
       +}
       +
       +void*
       +emalloc(uint n)
       +{
       +        void *v;
       +
       +        v = mallocz(n, 1);
       +        if(v == nil)
       +                sysfatal("out of memory");
       +        return v;
       +}
       +
       +void*
       +erealloc(void *v, uint n)
       +{
       +        v = realloc(v, n);
       +        if(v == nil)
       +                sysfatal("out of memory");
       +        return v;
       +}
       +
       +char*
       +estrdup(char *s)
       +{
       +        if(s == nil)
       +                return nil;
       +        s = strdup(s);
       +        if(s == nil)
       +                sysfatal("out of memory");
       +        return s;
       +}
       +
       +char*
       +evsmprint(char *s, va_list v)
       +{
       +        s = vsmprint(s, v);
       +        if(s == nil)
       +                sysfatal("out of memory");
       +        return s;
       +}
       +
       +int
       +pipewinto(Win *w, char *name, int errto, char *cmd, ...)
       +{
       +        va_list arg;
       +        char *p;
       +        int fd[3], pid;
       +
       +        va_start(arg, cmd);
       +        p = evsmprint(cmd, arg);
       +        va_end(arg);
       +        fd[0] = winfd(w, name, OREAD);
       +        fd[1] = dup(errto, -1);
       +        fd[2] = dup(errto, -1);
       +        pid = threadspawnl(fd, "rc", "rc", "-c", p, 0);
       +        free(p);
       +        return pid;
       +}
       +
       +int
       +pipetowin(Win *w, char *name, int errto, char *cmd, ...)
       +{
       +        va_list arg;
       +        char *p;
       +        int fd[3], pid, pfd[2];
       +        char buf[1024];
       +        int n;
       +
       +        /*
       +         * cannot use winfd here because of buffering caused
       +         * by pipe.  program might exit before final write to acme
       +         * happens.  so we might return before the final write.
       +         *
       +         * to avoid this, we tend the pipe ourselves.
       +         */
       +        if(pipe(pfd) < 0)
       +                sysfatal("pipe: %r");
       +        va_start(arg, cmd);
       +        p = evsmprint(cmd, arg);
       +        va_end(arg);
       +        fd[0] = open("/dev/null", OREAD);
       +        fd[1] = pfd[1];
       +        if(errto == 0)
       +                fd[2] = dup(fd[1], -1);
       +        else
       +                fd[2] = dup(errto, -1);
       +        pid = threadspawnl(fd, "rc", "rc", "-c", p, 0);
       +        free(p);
       +        while((n = read(pfd[0], buf, sizeof buf)) > 0)
       +                winwrite(w, name, buf, n);
       +        close(pfd[0]);
       +        return pid;
       +}
       +
       +char*
       +sysrun(int errto, char *fmt, ...)
       +{
       +        static char buf[1024];
       +        char *cmd;
       +        va_list arg;
       +        int n, fd[3], p[2], tot, pid;
       +
       +#undef pipe
       +        if(pipe(p) < 0)
       +                sysfatal("pipe: %r");
       +        fd[0] = open("/dev/null", OREAD);
       +        fd[1] = p[1];
       +        if(errto == 0)
       +                fd[2] = dup(fd[1], -1);
       +        else
       +                fd[2] = dup(errto, -1);
       +
       +        va_start(arg, fmt);
       +        cmd = evsmprint(fmt, arg);
       +        va_end(arg);
       +        pid = threadspawnl(fd, "rc", "rc", "-c", cmd, 0);
       +
       +        tot = 0;
       +        while((n = read(p[0], buf+tot, sizeof buf-tot)) > 0)
       +                tot += n;
       +        close(p[0]);
       +        twait(pid);
       +        if(n < 0)
       +                return nil;
       +        free(cmd);
       +        if(tot == sizeof buf)
       +                tot--;
       +        buf[tot] = 0;
       +        while(tot > 0 && isspace(buf[tot-1]))
       +                tot--;
       +        buf[tot] = 0;
       +        if(tot == 0){
       +                werrstr("no output");
       +                return nil;
       +        }
       +        return estrdup(buf);
       +}
       +
       +static void
       +eventreader(void *v)
       +{
       +        Event e[2];
       +        Win *w;
       +        int i;
       +        
       +        w = v;
       +        i = 0;
       +        for(;;){
       +                if(winreadevent(w, &e[i]) <= 0)
       +                        break;
       +                sendp(w->c, &e[i]);
       +                i = 1-i;        /* toggle */
       +        }
       +        sendp(w->c, nil);
       +        threadexits(nil);
       +}
       +
       +Channel*
       +wineventchan(Win *w)
       +{
       +        if(w->c == nil){
       +                w->c = chancreate(sizeof(Event*), 0);
       +                threadcreate(eventreader, w, 32*1024);
       +        }
       +        return w->c;
       +}
       +
       +char*
       +wingetname(Win *w)
       +{
       +        int n;
       +        char *p;
       +        
       +        n = winread(w, "tag", w->name, sizeof w->name-1);
       +        if(n <= 0)
       +                return nil;
       +        w->name[n] = 0;
       +        p = strchr(w->name, ' ');
       +        if(p)
       +                *p = 0;
       +        return w->name;
       +}
       +
   DIR diff --git a/src/cmd/netfiles/acme.h b/src/cmd/netfiles/acme.h
       t@@ -0,0 +1,82 @@
       +typedef struct Event Event;
       +typedef struct Win Win;
       +
       +#define        EVENTSIZE        256
       +struct Event
       +{
       +        int        c1;
       +        int        c2;
       +        int        oq0;
       +        int        oq1;
       +        int        q0;
       +        int        q1;
       +        int        flag;
       +        int        nb;
       +        int        nr;
       +        char        text[EVENTSIZE*UTFmax+1];
       +        char        arg[EVENTSIZE*UTFmax+1];
       +        char        loc[EVENTSIZE*UTFmax+1];
       +};
       +
       +struct Win
       +{
       +        int id;
       +        CFid *ctl;
       +        CFid *tag;
       +        CFid *body;
       +        CFid *addr;
       +        CFid *event;
       +        CFid *data;
       +        CFid *xdata;
       +        Channel *c;        /* chan(Event) */
       +        Win *next;
       +        Win *prev;
       +        
       +        /* events */
       +        int nbuf;
       +        char name[1024];
       +        char buf[1024];
       +        char *bufp;
       +        jmp_buf jmp;
       +        Event e2;
       +        Event e3;
       +        Event e4;
       +};
       +
       +Win *newwin(void);
       +
       +int eventfmt(Fmt*);
       +int pipewinto(Win *w, char *name, int, char *fmt, ...);
       +int pipetowin(Win *w, char *name, int, char *fmt, ...);
       +char *sysrun(int errto, char*, ...);
       +int winaddr(Win *w, char *fmt, ...);
       +int winctl(Win *w, char *fmt, ...);
       +int windel(Win *w, int sure);
       +int winfd(Win *w, char *name, int);
       +char *winmread(Win *w, char *file);
       +int winname(Win *w, char *fmt, ...);
       +int winprint(Win *w, char *name, char *fmt, ...);
       +int winread(Win *w, char *file, void *a, int n);
       +int winseek(Win *w, char *file, int n, int off);
       +int winreadaddr(Win *w, uint*);
       +int winreadevent(Win *w, Event *e);
       +int winwrite(Win *w, char *file, void *a, int n);
       +int winwriteevent(Win *w, Event *e);
       +int winopenfd(Win *w, char *name, int mode);
       +void windeleteall(void);
       +void winfree(Win *w);
       +void winclosefiles(Win *w);
       +Channel *wineventchan(Win *w);
       +char *winindex(void);
       +void mountacme(void);
       +char *wingetname(Win *w);
       +
       +void *erealloc(void*, uint);
       +void *emalloc(uint);
       +char *estrdup(char*);
       +char *evsmprint(char*, va_list);
       +
       +int twait(int);
       +void twaitinit(void);
       +
       +extern Win *windows;
   DIR diff --git a/src/cmd/netfiles/main.c b/src/cmd/netfiles/main.c
       t@@ -0,0 +1,490 @@
       +/*
       + * Remote file system editing client.
       + * Only talks to acme - external programs do all the hard work.
       + * 
       + * If you add a plumbing rule:
       +
       +# /n/ paths go to simulator in acme
       +kind is text
       +data matches '[a-zA-Z0-9_\-./]+('$addr')?'
       +data matches '(/n/[a-zA-Z0-9_\-./]+)('$addr')?'
       +plumb to netfileedit
       +plumb client Netfiles
       +
       + * then plumbed paths starting with /n/ will find their way here.
       + *
       + * Perhaps on startup should look for windows named /n/ and attach to them?
       + * Or might that be too aggressive?
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <thread.h>
       +#include <9pclient.h>
       +#include <plumb.h>
       +#include "acme.h"
       +
       +char *root = "/n/";
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: Netfiles\n");
       +        threadexitsall("usage");
       +}
       +
       +extern int chatty9pclient;
       +int debug;
       +#define dprint if(debug)print
       +Win *mkwin(char*);
       +int do3(Win *w, char *arg);
       +
       +enum {
       +        STACK = 128*1024,
       +};
       +
       +enum {
       +        Put,
       +        Get,
       +        Del,
       +        Delete,
       +        Debug,
       +        XXX
       +};
       +
       +char *cmds[] = {
       +        "Put",
       +        "Get",
       +        "Del",
       +        "Delete",
       +        "Debug",
       +        nil
       +};
       +
       +typedef struct Arg Arg;
       +struct Arg
       +{
       +        char *file;
       +        char *addr;
       +        Channel *c;
       +};
       +
       +Arg*
       +arg(char *file, char *addr, Channel *c)
       +{
       +        Arg *a;
       +                
       +        a = emalloc(sizeof *a);
       +        a->file = estrdup(file);
       +        a->addr = estrdup(addr);
       +        a->c = c;
       +        return a;
       +}
       +
       +/*
       + * return window id of a window named name or name/
       + * assumes name is cleaned.
       + */
       +int
       +nametowinid(char *name)
       +{
       +        char *index, *p, *next;
       +        int len, n;
       +
       +        index = winindex();
       +        n = -1;
       +        len = strlen(name);
       +        for(p=index; p && *p; p=next){
       +                if((next = strchr(p, '\n')) != nil)
       +                        *next = 0;
       +                if(strlen(p) <= 5*12)
       +                        continue;
       +                if(memcmp(p+5*12, name, len)!=0)
       +                        continue;
       +                if(p[5*12+len]!=' ' && (p[5*12+len]!='/' || p[5*12+len+1]!=' '))
       +                        continue;
       +                n = atoi(p);
       +                break;
       +        }
       +        free(index);
       +        return n;
       +}
       +
       +/* 
       + * look up window by name
       + */
       +Win*
       +nametowin(char *name)
       +{
       +        int id;
       +        Win *w;
       +        
       +        id = nametowinid(name);
       +        if(id == -1)
       +                return nil;
       +        for(w=windows; w; w=w->next)
       +                if(w->id == id)
       +                        return w;
       +        return nil;
       +}
       +
       +/*
       + * look for s in list
       + */
       +int
       +lookup(char *s, char **list)
       +{
       +        int i;
       +        
       +        for(i=0; list[i]; i++)
       +                if(strcmp(list[i], s) == 0)
       +                        return i;
       +        return -1;
       +}
       +
       +/*
       + * move to top of file
       + */
       +void
       +wintop(Win *w)
       +{
       +        winaddr(w, "#0");
       +        winctl(w, "dot=addr");
       +        winctl(w, "show");
       +}
       +
       +/*
       + * Expand the click further than acme usually does -- all non-white space is okay.
       + */
       +char*
       +expandarg(Win *w, Event *e)
       +{
       +        if(e->c2 == 'l')
       +                return estrdup(e->text);
       +        dprint("expand %d %d %d %d\n", e->oq0, e->oq1, e->q0, e->q1);
       +        if(e->oq0 == e->oq1 && e->q0 != e->q1)
       +                winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1);
       +        else
       +                winaddr(w, "#%ud,#%ud", e->q0, e->q1);
       +        return winmread(w, "xdata");
       +}
       +
       +/*
       + * handle a plumbing message
       + */
       +void
       +doplumb(void *vm)
       +{
       +        char *addr;
       +        Plumbmsg *m;
       +        Win *w;
       +        
       +        m = vm;
       +        if(m->ndata >= 1024){
       +                fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n",
       +                        m->ndata, m->data);
       +                plumbfree(m);
       +                return;
       +        }
       +        
       +        addr = plumblookup(m->attr, "addr");
       +        w = nametowin(m->data);
       +        if(w == nil)
       +                w = mkwin(m->data);
       +        winaddr(w, "%s", addr);
       +        winctl(w, "dot=addr");
       +        winctl(w, "show");
       +//        windecref(w);
       +        plumbfree(m);
       +}
       +
       +/*
       + * dispatch messages from the plumber
       + */
       +void
       +plumbthread(void *v)
       +{
       +        CFid *fid;
       +        Plumbmsg *m;
       +        
       +        fid = plumbopenfid("netfileedit", OREAD);
       +        if(fid == nil){
       +                fprint(2, "cannot open plumb/netfileedit: %r\n");
       +                return;
       +        }
       +        while((m = plumbrecvfid(fid)) != nil)
       +                threadcreate(doplumb, m, STACK);
       +        fsclose(fid);
       +}
       +
       +/*
       + * parse /n/system/path
       + */
       +int
       +parsename(char *name, char **server, char **path)
       +{
       +        char *p, *nul;
       +        
       +        cleanname(name);
       +        if(strncmp(name, "/n/", 3) != 0 && name[3] == 0)
       +                return -1;
       +        nul = nil;
       +        if((p = strchr(name+3, '/')) == nil)
       +                *path = estrdup("/");
       +        else{
       +                *path = estrdup(p);
       +                *p = 0;
       +                nul = p;
       +        }
       +        p = name+3;
       +        if(p[0] == 0){
       +                free(*path);
       +                *server = *path = nil;
       +                if(nul)
       +                        *nul = '/';
       +                return -1;
       +        }
       +        *server = estrdup(p);
       +        if(nul)
       +                *nul = '/';
       +        return 0;
       +}
       +
       +/*
       + * shell out to find the type of a given file
       + */
       +char*
       +filestat(char *server, char *path)
       +{
       +        return sysrun(2, "9 netstat %q %q", server, path);
       +}
       +
       +/*
       + * manage a single window
       + */
       +void
       +filethread(void *v)
       +{
       +        char *arg, *name, *p, *server, *path, *type;
       +        Arg *a;
       +        Channel *c;
       +        Event *e;
       +        Win *w;
       +
       +        a = v;
       +        threadsetname("file %s", a->file);
       +        w = newwin();
       +        winname(w, a->file);
       +        winprint(w, "tag", "Get Put Look ");
       +        c = wineventchan(w);
       +        
       +        goto caseGet;
       +        
       +        while((e=recvp(c)) != nil){
       +                if(e->c1!='K')
       +                        dprint("acme %E\n", e);
       +                if(e->c1=='M')
       +                switch(e->c2){
       +                case 'x':
       +                case 'X':
       +                        switch(lookup(e->text, cmds)){
       +                        caseGet:
       +                        case Get:
       +                                server = nil;
       +                                path = nil;
       +                                if(parsename(name=wingetname(w), &server, &path) < 0){
       +                                        fprint(2, "Netfiles: bad name %s\n", name);
       +                                        goto out;
       +                                }
       +                                type = filestat(server, path);
       +                                if(type == nil)
       +                                        type = estrdup("");
       +                                if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
       +                                        winaddr(w, ",");
       +                                        winprint(w, "data", "[reading...]");
       +                                        winaddr(w, ",");
       +                                        if(strcmp(type, "file")==0)
       +                                                twait(pipetowin(w, "data", 2, "9 netget %q %q", server, path));
       +                                        else
       +                                                twait(pipetowin(w, "data", 2, "9 netget -d %q %q | winid=%d mc", server, path, w->id));
       +                                        cleanname(name);
       +                                        if(strcmp(type, "directory")==0){
       +                                                p = name+strlen(name);
       +                                                if(p[-1] != '/'){
       +                                                        p[0] = '/';
       +                                                        p[1] = 0;
       +                                                }
       +                                        }
       +                                        winname(w, name);
       +                                        wintop(w);
       +                                        winctl(w, "clean");
       +                                        if(a && a->addr){
       +                                                winaddr(w, "%s", a->addr);
       +                                                winctl(w, "dot=addr");
       +                                                winctl(w, "show");
       +                                        }
       +                                }
       +                                free(type);
       +                        out:
       +                                free(server);
       +                                free(path);
       +                                if(a){
       +                                        if(a->c){
       +                                                sendp(a->c, w);
       +                                                a->c = nil;
       +                                        }
       +                                        free(a->file);
       +                                        free(a->addr);
       +                                        free(a);
       +                                        a = nil;
       +                                }
       +                                break;
       +                        case Put:
       +                                server = nil;
       +                                path = nil;
       +                                if(parsename(name=wingetname(w), &server, &path) < 0){
       +                                        fprint(2, "Netfiles: bad name %s\n", name);
       +                                        goto out;
       +                                }
       +                                if(twait(pipewinto(w, "body", 2, "9 netput %q %q", server, path)) >= 0){
       +                                        cleanname(name);
       +                                        winname(w, name);
       +                                        winctl(w, "clean");
       +                                }
       +                                free(server);
       +                                free(path);
       +                                break;
       +                        case Del:
       +                                winctl(w, "del");
       +                                break;
       +                        case Delete:
       +                                winctl(w, "delete");
       +                                break;
       +                        case Debug:
       +                                debug = !debug;
       +                                break;
       +                        default:
       +                                winwriteevent(w, e);
       +                                break;
       +                        }
       +                        break;
       +                case 'l':
       +                case 'L':
       +                        arg = expandarg(w, e);
       +                        if(arg!=nil && do3(w, arg) < 0)
       +                                winwriteevent(w, e);
       +                        free(arg);
       +                        break;
       +                }
       +        }
       +        winfree(w);
       +}
       +
       +/*
       + * handle a button 3 click
       + */
       +int
       +do3(Win *w, char *text)
       +{
       +        char *addr, *name, *type, *server, *path, *p, *q;
       +        static char lastfail[1000];
       +
       +        if(text[0] == '/')
       +                name = estrdup(text);
       +        else{
       +                p = wingetname(w);
       +                q = strrchr(p, '/');
       +                *(q+1) = 0;
       +                name = emalloc(strlen(p)+1+strlen(text)+1);
       +                strcpy(name, p);
       +                strcat(name, "/");
       +                strcat(name, text);
       +        }
       +        dprint("do3 %s => %s\n", text, name);
       +        if((addr = strchr(name, ':')) != nil)
       +                *addr++ = 0;
       +        cleanname(name);
       +        if(strcmp(name, lastfail) == 0){
       +                free(name);
       +                return -1;
       +        }
       +        if(parsename(name, &server, &path) < 0){
       +                free(name);
       +                return -1;
       +        }
       +        type = filestat(server, path);
       +        free(server);
       +        free(path);
       +        if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
       +                w = nametowin(name);
       +                if(w == nil)
       +                        w = mkwin(name);
       +                winaddr(w, "%s", addr);
       +                winctl(w, "dot=addr");
       +                winctl(w, "show");
       +                free(name);
       +                free(type);
       +                return 0;
       +        }
       +        /*
       +         * remember last name that didn't exist so that
       +         * only the first right-click is slow when searching for text.
       +         */
       +        strecpy(lastfail, lastfail+sizeof lastfail, name);
       +        free(name);
       +        return -1;
       +}
       +
       +Win*
       +mkwin(char *name)
       +{
       +        Arg *a;
       +        Channel *c;
       +        Win *w;
       +        
       +        c = chancreate(sizeof(void*), 0);
       +        a = arg(name, nil, c);
       +        threadcreate(filethread, a, STACK);
       +        w = recvp(c);
       +        chanfree(c);
       +        return w;
       +}
       +
       +void
       +loopthread(void *v)
       +{
       +        QLock lk;
       +        
       +        qlock(&lk);
       +        qlock(&lk);
       +}
       +        
       +void
       +threadmain(int argc, char **argv)
       +{
       +        ARGBEGIN{
       +        case '9':
       +                chatty9pclient = 1;
       +                break;
       +        case 'D':
       +                debug = 1;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND
       +        
       +        if(argc)
       +                usage();
       +
       +        threadnotify(nil, 0);        /* set up correct default handlers */
       +
       +        fmtinstall('E', eventfmt);
       +        doquote = needsrcquote;
       +        quotefmtinstall();
       +        
       +        twaitinit();
       +        threadcreate(plumbthread, nil, STACK);
       +        threadcreate(loopthread, nil, STACK);
       +        threadexits(nil);
       +}
       +
   DIR diff --git a/src/cmd/netfiles/mkfile b/src/cmd/netfiles/mkfile
       t@@ -0,0 +1,28 @@
       +<$PLAN9/src/mkhdr
       +
       +TARG=Netfiles
       +
       +OFILES=\
       +        acme.$O\
       +        main.$O\
       +        wait.$O\
       +        
       +HFILES=acme.h
       +
       +<$PLAN9/src/mkone
       +
       +XTARG=\
       +        netget\
       +        netput\
       +        netstat\
       +
       +install:V:
       +        for i in $XTARG; do
       +                cp $i $BIN
       +        done
       +
       +push:V:
       +        tar cf - mkfile acme.c main.c wait.c acme.h netget netput netstat |
       +        gzip >netfiles.tar.gz
       +        scp netfiles.tar.gz swtch.com:www/swtch.com
       +
   DIR diff --git a/src/cmd/netfiles/netfileget b/src/cmd/netfiles/netfileget
       t@@ -0,0 +1,45 @@
       +#!/usr/local/plan9/bin/rc
       +
       +f=getfile
       +if(~ $1 -d){
       +        f=getdir
       +        shift
       +}
       +
       +if(! ~ $#* 2){
       +        echo 'usage: netget [-d] system path' >[1=2]
       +        exit usage
       +}
       +
       +ns=`{namespace}
       +if(u test -S $ns/$1)
       +        f=$f^9p
       +
       +t=/tmp/netget.$pid.$USER
       +fn sigexit { rm -f $t }
       +
       +fn getfile {
       +        rm -f $t
       +        if(! echo get $2 $t | sftp -b - $1 >/dev/null)
       +                exit 1
       +        cat $t
       +}
       +
       +fn getfile9p {
       +        if(! 9p read $1/$2)
       +                exit 1
       +}
       +
       +fn getdir {
       +        if(! {echo cd $2; echo ls -l} | sftp -b - $1 | sed '1,2d; s/sftp> //g; /^$/d' >$t)
       +                exit 1
       +        cat $t | awk '$NF == "." || $NF == ".." { next } {s = $NF; if($0 ~ /^d/) s = s "/"; print s}'
       +}
       +
       +fn getdir9p {
       +        9p ls -l $1/$2 | awk '{s=$NF; if($0 ~ /^d/) s=s"/"; print s}'
       +}
       +
       +$f $1 $2
       +exit 0
       +
   DIR diff --git a/src/cmd/netfiles/netfileput b/src/cmd/netfiles/netfileput
       t@@ -0,0 +1,27 @@
       +#!/usr/local/plan9/bin/rc
       +
       +if(! ~ $#* 2){
       +        echo 'usage: netput system path' >[1=2]
       +        exit usage
       +}
       +
       +f=putfile
       +ns=`{namespace}
       +if(u test -S $ns/$1)
       +        f=$f^9p
       +
       +t=/tmp/netget.$pid.$USER
       +fn sigexit { rm -f $t }
       +
       +fn putfile{
       +        cat >$t
       +        if(! echo put $t $2 | sftp -b - $1 >/dev/null)
       +                exit 1
       +}
       +fn putfile9p{
       +        if(! 9p write $1/$2)
       +                exit 1
       +}
       +
       +$f $1 $2
       +exit 0
   DIR diff --git a/src/cmd/netfiles/netfilestat b/src/cmd/netfiles/netfilestat
       t@@ -0,0 +1,52 @@
       +#!/usr/local/plan9/bin/rc
       +
       +if(! ~ $#* 2){
       +        echo usage: netisdir system path >[1=2]
       +        exit usage
       +}
       +
       +f=dostat
       +ns=`{namespace}
       +if(u test -S $ns/$1)
       +        f=$f^9p
       +
       +t=/tmp/netisdir.$pid.$USER
       +fn sigexit { rm -f $t }
       +
       +fn dostat {
       +        {
       +                echo !echo XXX connected
       +                echo cd $2
       +                echo !echo XXX directory exists
       +        }  | sftp -b - $1 >$t >[2=1]
       +        if(9 grep -s XXX.directory.exists $t){
       +                echo directory
       +                exit 0
       +        }
       +        if(9 grep -s 'is not a directory' $t){
       +                echo file
       +                exit 0
       +        }
       +        cat $t | sed 's/sftp> //g; /^$/d; /XXX/d; /^cd /d' >[1=2]
       +        if(! 9 grep -s XXX.connected $t){
       +                echo connect failed
       +                exit 0
       +        }
       +        echo nonexistent
       +        exit 0
       +}
       +
       +fn dostat9p {
       +        if(! 9p ls -ld $1/$2 >$t >[2]/dev/null){
       +                echo nonexistent
       +                exit 0
       +        }
       +        if(9 grep -s '^d' $t){
       +                echo directory
       +                exit 0
       +        }
       +        echo file
       +        exit 0
       +}
       +
       +$f $1 $2
   DIR diff --git a/src/cmd/netfiles/wait.c b/src/cmd/netfiles/wait.c
       t@@ -0,0 +1,120 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <thread.h>
       +#include <9pclient.h>
       +#include "acme.h"
       +
       +extern int debug;
       +
       +#define dprint if(debug)print
       +
       +typedef struct Waitreq Waitreq;
       +struct Waitreq
       +{
       +        int pid;
       +        Channel *c;
       +};
       +
       +/*
       + * watch the exiting children
       + */
       +Channel *twaitchan;        /* chan(Waitreq) */
       +void
       +waitthread(void *v)
       +{
       +        Alt a[3];
       +        Waitmsg *w, **wq;
       +        Waitreq *rq, r;
       +        int i, nrq, nwq;
       +
       +        threadsetname("waitthread");
       +        a[0].c = threadwaitchan();
       +        a[0].v = &w;
       +        a[0].op = CHANRCV;
       +        a[1].c = twaitchan;
       +        a[1].v = &r;
       +        a[1].op = CHANRCV;
       +        a[2].op = CHANEND;
       +
       +        nrq = 0;
       +        nwq = 0;
       +        rq = nil;
       +        wq = nil;
       +        dprint("wait: start\n");
       +        for(;;){
       +        cont2:;
       +                dprint("wait: alt\n");
       +                switch(alt(a)){
       +                case 0:
       +                        dprint("wait: pid %d exited\n", w->pid);
       +                        for(i=0; i<nrq; i++){
       +                                if(rq[i].pid == w->pid){
       +                                        dprint("wait: match with rq chan %p\n", rq[i].c);
       +                                        sendp(rq[i].c, w);
       +                                        rq[i] = rq[--nrq];
       +                                        goto cont2;
       +                                }
       +                        }
       +                        if(i == nrq){
       +                                dprint("wait: queueing waitmsg\n");
       +                                wq = erealloc(wq, (nwq+1)*sizeof(wq[0]));
       +                                wq[nwq++] = w;
       +                        }
       +                        break;
       +                
       +                case 1:
       +                        dprint("wait: req for pid %d chan %p\n", r.pid, r.c);
       +                        for(i=0; i<nwq; i++){
       +                                if(w->pid == r.pid){
       +                                        dprint("wait: match with waitmsg\n");
       +                                        sendp(r.c, w);
       +                                        wq[i] = wq[--nwq];
       +                                        goto cont2;
       +                                }
       +                        }
       +                        if(i == nwq){
       +                                dprint("wait: queueing req\n");
       +                                rq = erealloc(rq, (nrq+1)*sizeof(rq[0]));
       +                                rq[nrq] = r;
       +                                dprint("wait: queueing req pid %d chan %p\n", rq[nrq].pid, rq[nrq].c);
       +                                nrq++;
       +                        }
       +                        break;
       +                }
       +        }
       +}
       +
       +Waitmsg*
       +twaitfor(int pid)
       +{
       +        Waitreq r;
       +        Waitmsg *w;
       +        
       +        r.pid = pid;
       +        r.c = chancreate(sizeof(Waitmsg*), 1);
       +        send(twaitchan, &r);
       +        w = recvp(r.c);
       +        chanfree(r.c);
       +        return w;
       +}
       +
       +int
       +twait(int pid)
       +{
       +        int x;
       +        Waitmsg *w;
       +        
       +        w = twaitfor(pid);
       +        x = w->msg[0] != 0 ? -1 : 0;
       +        free(w);
       +        return x;
       +}
       +
       +void
       +twaitinit(void)
       +{
       +        threadwaitchan();        /* allocate it before returning */
       +        twaitchan = chancreate(sizeof(Waitreq), 10);
       +        threadcreate(waitthread, nil, 128*1024);
       +}
       +