#include #include #include #include mainstacksize = 16*1024; enum { PATHLEN = 512, Etextlen = 256, }; typedef struct Emsg { char from; char type; int a1; int a2; int flags; int len; char text[Etextlen*UTFmax+1]; } Emsg; typedef struct Event { Emsg msg[4]; } Event; typedef struct Window { int ctl; int event; int addr; int data; int tag; int id; char name[PATHLEN]; Dir *dir; Channel *ech; /* Event * */ Event e[2]; int seq; int sure; } Window; void usage(void) { fprint(2, "usage: %s [file ...]\n", argv0); exits("usage"); } void fatal(char *fmt, ...) { va_list args; char buf[1024], *p; p = buf; if(argv0) p = seprint(p, buf+sizeof(buf), "%s: ", argv0); va_start(args, fmt); vseprint(p, buf+sizeof(buf), fmt, args); va_end(args); fprint(2, "%s\n", buf); threadkillgrp(threadgetgrp()); exits(buf); } int aopen(Window *w, char *filename, int mode) { char buf[128]; sprint(buf, "/mnt/acme/%d/%s", w->id, filename); return open(buf, mode); } Window * newwindow(void) { Window *w; char buf[12]; if((w = malloc(sizeof *w)) == nil) fatal("malloc: %r"); if((w->ctl = open("/mnt/acme/new/ctl", ORDWR)) < 0) fatal("newwindow: open ctl: %r"); if(read(w->ctl, buf, 12) != 12) fatal("newwindow: read ctl: %r"); w->id = atoi(buf); if((w->event = aopen(w, "event", ORDWR)) < 0) fatal("newwindow: open event: %r"); if((w->addr = aopen(w, "addr", ORDWR)) < 0) fatal("newwindow: open addr: %r"); if((w->data = aopen(w, "data", ORDWR)) < 0) fatal("newwindow: open data: %r"); if((w->tag = aopen(w, "tag", ORDWR)) < 0) fatal("newwindow: open tag: %r"); w->ech = chancreate(sizeof(Event *), 0); return w; } void winname(Window *w, char *filename) { int fd; char path[PATHLEN]; if((fd = open(filename, OREAD)) < 0) fatal("winname: open %s: %r", filename); if(fd2path(fd, path, sizeof path)) fatal("winname: fd2path %s: %r", filename); close(fd); fprint(w->ctl, "name %s\n", path); strcpy(w->name, path); } void parseemsg(Emsg *emsg, char **p) { char *s, *t; int i; Rune r; s = *p; emsg->from = *s++; emsg->type = *s++; emsg->a1 = atoi(s); while(*s++ != ' '); emsg->a2 = atoi(s); while(*s++ != ' '); emsg->flags = atoi(s); while(*s++ != ' '); emsg->len = atoi(s); while(*s++ != ' '); // FIXME -- this fails for 256 greek runes t = s; for(i=0; i < emsg->len; i++) t += chartorune(&r, t); memcpy(emsg->text, s, t-s); emsg->text[t-s] = '\0'; s += t-s; *p = s + 1; } void eventproc(void *v) { Window *w; char buf[4096], *p; int n, i, j; threadsetname("event"); w = v; for(i=0;; i = 1-i) { n = read(w->event, buf, sizeof buf - 1); if(n < 0) { sendp(w->ech, nil); threadexits(nil); } for(j=0, p=buf; p - buf < n; j++) { if(i > nelem(w->e->msg) - 1) { fprint(2, "%s: %d events/read\n", argv0, i); break; } parseemsg(&w->e[i].msg[j], &p); } sendp(w->ech, &w->e[i]); } } void eventdefault(Window *w, Event *e) { Emsg *m = &e->msg[0]; fprint(w->event, "%c%c%d %d\n", m->from, m->type, m->a1, m->a2); } void delcmd(Window *w) { if(write(w->ctl, "del\n", 4) != 4) return; } void getcmd(Window *w, int sure) { char *cmd; Waitmsg *wmsg; if(!sure) { fprint(2, "%s modified\n", w->name[0] ? w->name : "unnamed file"); return; } // FIXME -- be more careful about writing partial utf sequences if((cmd = smprint("tag/read %s", w->name)) == nil) { fprint(2, "%s: Get %s: smprint: %r\n", argv0, w->name); return; } switch(fork()) { case -1: fprint(2, "%s: Get %s: fork: %r\n", argv0, w->name); return; case 0: fprint(w->addr, "0,$"); dup(w->data, 1); close(w->data); execl("/bin/rc", "rc", "-c", cmd, nil); fprint(2, "%s: Get %s: exec: %r\n", argv0, w->name); exits("exec Get"); } free(cmd); if((wmsg = wait()) == nil) return; if(wmsg->msg[0] == '\0') { fprint(w->addr, "0"); fprint(w->ctl, "dot=addr\n" "show\n" "clean\n"); } free(wmsg); } void putcmd(Window *w) { Dir *d; char *cmd; Waitmsg *wmsg; if(access(w->name, AEXIST) == 0 && (d = dirstat(w->name)) != nil) { if(d->type != w->dir->type || d->dev != w->dir->dev || d->qid.path != w->dir->qid.path || d->qid.vers != w->dir->qid.vers) { if(d->qid.vers == w->dir->qid.vers) { fprint(2, "%s not written; file already exists\n", w->name); } else { fprint(2, "%s modified%s%s since last read\n", w->name, d->muid[0] ? " by " : "", d->muid); } free(w->dir); w->dir = d; return; } free(d); } if((cmd = smprint("tag/write %s", w->name)) == nil) { fprint(2, "%s: Put %s: smprint: %r\n", argv0, w->name); return; } switch(fork()) { case -1: fprint(2, "%s: Put %s: fork: %r\n", argv0, w->name); return; case 0: fprint(w->addr, "0,$"); dup(w->data, 0); close(w->data); execl("/bin/rc", "rc", "-c", cmd, nil); fprint(2, "%s: Put %s: exec: %r\n", argv0, w->name); exits("exec Put"); } free(cmd); if((wmsg = wait()) == nil) return; if(wmsg->msg[0] == '\0') fprint(w->ctl, "clean\n"); free(wmsg); } int iscmd(char *s, char *cmd) { int len; while(*s == ' ' || *s == ' ' || *s == '\n') s++; len = strlen(cmd); return strncmp(s, cmd, len) == 0 && (s[len] == '\0' || s[len] == ' ' || s[len] == ' ' || s[len] == '\n'); } int isdirty(Window *w) { char m; if(pread(w->ctl, &m, 1, 4*(11+1) + 10) != 1) { fprint(2, "%s: read dirty: %r\n", argv0); return 1; } return m == '1'; } int docmd(Window *w, char *cmd) { if(iscmd(cmd, "Del")) { delcmd(w); return 1; } if(iscmd(cmd, "Get")) { getcmd(w, isdirty(w) ? w->sure == w->seq : 1); w->sure = w->seq; return 1; } if(iscmd(cmd, "Put")) { putcmd(w); return 1; } return 0; } void editproc(void *v) { char *filename; Window *w; Dir *d; Event *e; int n; char *s; threadsetname("edit"); filename = v; if((d = dirstat(filename)) == nil) { fprint(2, "%s: %s: %r\n", argv0, filename); threadexits(nil); } w = newwindow(); w->dir = d; winname(w, filename); fprint(w->ctl, "menu\n" "dirty\n"); getcmd(w, 1); proccreate(eventproc, w, 8*1024); while((e = recvp(w->ech)) != nil) { if(e->msg[0].type == 'I' || e->msg[0].type == 'D') w->seq++; if(e->msg[0].from == 'K' && (e->msg[0].type == 'i' || e->msg[0].type == 'd')) { // FIXME -- look at the address to see if it is the name being edited if((n = pread(w->tag, w->name, sizeof w->name - 1, 0)) < 0) { fprint(2, "%s: read tag: %r\n", argv0); continue; } w->name[n] = '\0'; *strchr(w->name, ' ') = '\0'; continue; } if(e->msg[0].from != 'M') continue; switch(e->msg[0].type) { case 'x': case 'X': s = e->msg[0].text; if(e->msg[0].flags & 2) s = e->msg[1].text; if(!docmd(w, s) && e->msg[0].flags & 1) eventdefault(w, e); break; default: if(e->msg[0].flags & 1) eventdefault(w, e); } } free(w); threadexits(nil); } void neweditwindow(char *filename) { procrfork(editproc, filename, 8*1024, RFFDG|RFNOTEG); threadsetgrp(threadgetgrp()+1); } void threadmain(int argc, char **argv) { int plumber; Plumbmsg *p; ARGBEGIN { default: usage(); } ARGEND while(argc > 0) { neweditwindow(argv[0]); argc--; argv++; } threadsetname("plumb"); if((plumber = plumbopen("tag", OREAD|OCEXEC)) < 0) threadexits(nil); for(; (p = plumbrecv(plumber)) != nil; free(p)) neweditwindow(p->data); fatal("plumbrecv: %r"); }