| #include <u.h> |
| #include <libc.h> |
| #include <thread.h> |
| #include <fcall.h> |
| #include <9pclient.h> |
| #include "term.h" |
| |
| const char *termprog = "win"; |
| |
| #define EVENTSIZE 256 |
| #define STACK 32768 |
| |
| typedef struct Event Event; |
| typedef struct Q Q; |
| |
| struct Event |
| { |
| int c1; |
| int c2; |
| int q0; |
| int q1; |
| int flag; |
| int nb; |
| int nr; |
| char b[EVENTSIZE*UTFmax+1]; |
| Rune r[EVENTSIZE+1]; |
| }; |
| |
| Event blank = { |
| 'M', |
| 'X', |
| 0, 0, 0, 1, 1, |
| { ' ', 0 }, |
| { ' ', 0 } |
| }; |
| |
| struct Q |
| { |
| QLock lk; |
| int p; |
| int k; |
| }; |
| |
| Q q; |
| |
| CFid *eventfd; |
| CFid *addrfd; |
| CFid *datafd; |
| CFid *ctlfd; |
| /* int bodyfd; */ |
| |
| char *typing; |
| int ntypeb; |
| int ntyper; |
| int ntypebreak; |
| int debug; |
| int rcfd; |
| int cook = 1; |
| int password; |
| int israw(int); |
| |
| char *name; |
| |
| char **prog; |
| Channel *cwait; |
| int pid = -1; |
| |
| int label(char*, int); |
| void error(char*, ...); |
| void stdinproc(void*); |
| void stdoutproc(void*); |
| void type(Event*, int, CFid*, CFid*); |
| void sende(Event*, int, CFid*, CFid*, CFid*, int); |
| char *onestring(int, char**); |
| int delete(Event*); |
| void deltype(uint, uint); |
| void sendbs(int, int); |
| void runproc(void*); |
| |
| int |
| fsfidprint(CFid *fid, char *fmt, ...) |
| { |
| char buf[256]; |
| va_list arg; |
| int n; |
| |
| va_start(arg, fmt); |
| n = vsnprint(buf, sizeof buf, fmt, arg); |
| va_end(arg); |
| return fswrite(fid, buf, n); |
| } |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: win cmd args...\n"); |
| threadexitsall("usage"); |
| } |
| |
| void |
| waitthread(void *v) |
| { |
| recvp(cwait); |
| threadexitsall(nil); |
| } |
| |
| void |
| hangupnote(void *a, char *msg) |
| { |
| if(strcmp(msg, "hangup") == 0 && pid != 0){ |
| postnote(PNGROUP, pid, "hangup"); |
| noted(NDFLT); |
| } |
| if(strstr(msg, "child")){ |
| char buf[128]; |
| int n; |
| |
| n = awaitnohang(buf, sizeof buf-1); |
| if(n > 0){ |
| buf[n] = 0; |
| if(atoi(buf) == pid) |
| threadexitsall(0); |
| } |
| noted(NCONT); |
| } |
| noted(NDFLT); |
| } |
| |
| void |
| threadmain(int argc, char **argv) |
| { |
| int fd, id; |
| char buf[256]; |
| char buf1[128]; |
| CFsys *fs; |
| char *dump; |
| |
| dump = onestring(argc, argv); |
| |
| ARGBEGIN{ |
| case 'd': |
| debug = 1; |
| break; |
| case 'n': |
| name = EARGF(usage()); |
| break; |
| default: |
| usage(); |
| }ARGEND |
| |
| prog = argv; |
| |
| if(name == nil){ |
| if(argc > 0) |
| name = argv[0]; |
| else{ |
| name = sysname(); |
| if(name == nil) |
| name = "gnot"; |
| } |
| } |
| |
| /* |
| * notedisable("sys: write on closed pipe"); |
| * not okay to disable the note, because that |
| * gets inherited by the subshell, so that something |
| * as simple as "yes | sed 10q" never exits. |
| * call notifyoff instead. (is notedisable ever safe?) |
| */ |
| notifyoff("sys: write on closed pipe"); |
| |
| noteenable("sys: child"); |
| notify(hangupnote); |
| |
| if((fs = nsmount("acme", "")) == 0) |
| sysfatal("nsmount acme: %r"); |
| ctlfd = fsopen(fs, "new/ctl", ORDWR|OCEXEC); |
| if(ctlfd == 0 || fsread(ctlfd, buf, 12) != 12) |
| sysfatal("ctl: %r"); |
| id = atoi(buf); |
| snprint(buf, sizeof buf, "%d", id); |
| putenv("winid", buf); |
| sprint(buf, "%d/tag", id); |
| fd = fsopenfd(fs, buf, OWRITE|OCEXEC); |
| write(fd, " Send", 1+4); |
| close(fd); |
| sprint(buf, "%d/event", id); |
| eventfd = fsopen(fs, buf, ORDWR|OCEXEC); |
| sprint(buf, "%d/addr", id); |
| addrfd = fsopen(fs, buf, ORDWR|OCEXEC); |
| sprint(buf, "%d/data", id); |
| datafd = fsopen(fs, buf, ORDWR|OCEXEC); |
| sprint(buf, "%d/body", id); |
| /* bodyfd = fsopenfd(fs, buf, ORDWR|OCEXEC); */ |
| if(eventfd==nil || addrfd==nil || datafd==nil) |
| sysfatal("data files: %r"); |
| /* |
| if(eventfd<0 || addrfd<0 || datafd<0 || bodyfd<0) |
| sysfatal("data files: %r"); |
| */ |
| fsunmount(fs); |
| |
| cwait = threadwaitchan(); |
| threadcreate(waitthread, nil, STACK); |
| pid = rcstart(argc, argv, &rcfd, nil); |
| if(pid == -1) |
| sysfatal("exec failed"); |
| |
| getwd(buf1, sizeof buf1); |
| sprint(buf, "name %s/-%s\n0\n", buf1, name); |
| fswrite(ctlfd, buf, strlen(buf)); |
| sprint(buf, "dumpdir %s/\n", buf1); |
| fswrite(ctlfd, buf, strlen(buf)); |
| sprint(buf, "dump %s\n", dump); |
| fswrite(ctlfd, buf, strlen(buf)); |
| sprint(buf, "scroll"); |
| fswrite(ctlfd, buf, strlen(buf)); |
| |
| updatewinsize(25, 80, 0, 0); |
| proccreate(stdoutproc, nil, STACK); |
| stdinproc(nil); |
| } |
| |
| void |
| error(char *s, ...) |
| { |
| va_list arg; |
| |
| if(s){ |
| va_start(arg, s); |
| s = vsmprint(s, arg); |
| va_end(arg); |
| fprint(2, "win: %s: %r\n", s); |
| } |
| if(pid != -1) |
| postnote(PNGROUP, pid, "hangup"); |
| threadexitsall(s); |
| } |
| |
| char* |
| onestring(int argc, char **argv) |
| { |
| char *p; |
| int i, n; |
| static char buf[1024]; |
| |
| if(argc == 0) |
| return ""; |
| p = buf; |
| for(i=0; i<argc; i++){ |
| n = strlen(argv[i]); |
| if(p+n+1 >= buf+sizeof buf) |
| break; |
| memmove(p, argv[i], n); |
| p += n; |
| *p++ = ' '; |
| } |
| p[-1] = 0; |
| return buf; |
| } |
| |
| int |
| getec(CFid *efd) |
| { |
| static char buf[8192]; |
| static char *bufp; |
| static int nbuf; |
| |
| if(nbuf == 0){ |
| nbuf = fsread(efd, buf, sizeof buf); |
| if(nbuf <= 0) |
| error(nil); |
| bufp = buf; |
| } |
| --nbuf; |
| return *bufp++; |
| } |
| |
| int |
| geten(CFid *efd) |
| { |
| int n, c; |
| |
| n = 0; |
| while('0'<=(c=getec(efd)) && c<='9') |
| n = n*10+(c-'0'); |
| if(c != ' ') |
| error("event number syntax"); |
| return n; |
| } |
| |
| int |
| geter(CFid *efd, char *buf, int *nb) |
| { |
| Rune r; |
| int n; |
| |
| r = getec(efd); |
| buf[0] = r; |
| n = 1; |
| if(r < Runeself) |
| goto Return; |
| while(!fullrune(buf, n)) |
| buf[n++] = getec(efd); |
| chartorune(&r, buf); |
| Return: |
| *nb = n; |
| return r; |
| } |
| |
| void |
| gete(CFid *efd, Event *e) |
| { |
| int i, nb; |
| |
| e->c1 = getec(efd); |
| e->c2 = getec(efd); |
| e->q0 = geten(efd); |
| e->q1 = geten(efd); |
| e->flag = geten(efd); |
| e->nr = geten(efd); |
| if(e->nr > EVENTSIZE) |
| error("event string too long"); |
| e->nb = 0; |
| for(i=0; i<e->nr; i++){ |
| e->r[i] = geter(efd, e->b+e->nb, &nb); |
| e->nb += nb; |
| } |
| e->r[e->nr] = 0; |
| e->b[e->nb] = 0; |
| if(getec(efd) != '\n') |
| error("event syntax 2"); |
| } |
| |
| int |
| nrunes(char *s, int nb) |
| { |
| int i, n; |
| Rune r; |
| |
| n = 0; |
| for(i=0; i<nb; n++) |
| i += chartorune(&r, s+i); |
| return n; |
| } |
| |
| void |
| stdinproc(void *v) |
| { |
| CFid *cfd = ctlfd; |
| CFid *efd = eventfd; |
| CFid *dfd = datafd; |
| CFid *afd = addrfd; |
| int fd0 = rcfd; |
| Event e, e2, e3, e4; |
| int n; |
| |
| USED(v); |
| |
| for(;;){ |
| if(debug) |
| fprint(2, "typing[%d,%d)\n", q.p, q.p+ntyper); |
| gete(efd, &e); |
| if(debug) |
| fprint(2, "msg %c%c q[%d,%d)... ", e.c1, e.c2, e.q0, e.q1); |
| qlock(&q.lk); |
| switch(e.c1){ |
| default: |
| Unknown: |
| print("unknown message %c%c\n", e.c1, e.c2); |
| break; |
| |
| case 'E': /* write to body or tag; can't affect us */ |
| switch(e.c2){ |
| case 'I': |
| case 'D': /* body */ |
| if(debug) |
| fprint(2, "shift typing %d... ", e.q1-e.q0); |
| q.p += e.q1-e.q0; |
| break; |
| |
| case 'i': |
| case 'd': /* tag */ |
| break; |
| |
| default: |
| goto Unknown; |
| } |
| break; |
| |
| case 'F': /* generated by our actions; ignore */ |
| break; |
| |
| case 'K': |
| case 'M': |
| switch(e.c2){ |
| case 'I': |
| if(e.nr == 1 && e.r[0] == 0x7F) { |
| char buf[1]; |
| fsprint(addrfd, "#%ud,#%ud", e.q0, e.q1); |
| fswrite(datafd, "", 0); |
| buf[0] = 0x7F; |
| write(fd0, buf, 1); |
| break; |
| } |
| if(e.q0 < q.p){ |
| if(debug) |
| fprint(2, "shift typing %d... ", e.q1-e.q0); |
| q.p += e.q1-e.q0; |
| } |
| else if(e.q0 <= q.p+ntyper){ |
| if(debug) |
| fprint(2, "type... "); |
| type(&e, fd0, afd, dfd); |
| } |
| break; |
| |
| case 'D': |
| n = delete(&e); |
| q.p -= n; |
| if(israw(fd0) && e.q1 >= q.p+n) |
| sendbs(fd0, n); |
| break; |
| |
| case 'x': |
| case 'X': |
| if(e.flag & 2) |
| gete(efd, &e2); |
| if(e.flag & 8){ |
| gete(efd, &e3); |
| gete(efd, &e4); |
| } |
| if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){ |
| /* send it straight back */ |
| fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1); |
| break; |
| } |
| if(e.q0==e.q1 && (e.flag&2)){ |
| e2.flag = e.flag; |
| e = e2; |
| } |
| char buf[100]; |
| snprint(buf, sizeof buf, "%.*S", e.nr, e.r); |
| if(cistrcmp(buf, "cook") == 0) { |
| cook = 1; |
| break; |
| } |
| if(cistrcmp(buf, "nocook") == 0) { |
| cook = 0; |
| break; |
| } |
| if(e.flag & 8){ |
| if(e.q1 != e.q0){ |
| sende(&e, fd0, cfd, afd, dfd, 0); |
| sende(&blank, fd0, cfd, afd, dfd, 0); |
| } |
| sende(&e3, fd0, cfd, afd, dfd, 1); |
| }else if(e.q1 != e.q0) |
| sende(&e, fd0, cfd, afd, dfd, 1); |
| break; |
| |
| case 'l': |
| case 'L': |
| /* just send it back */ |
| if(e.flag & 2) |
| gete(efd, &e2); |
| fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1); |
| break; |
| |
| case 'd': |
| case 'i': |
| break; |
| |
| default: |
| goto Unknown; |
| } |
| } |
| qunlock(&q.lk); |
| } |
| } |
| |
| int |
| dropcr(char *p, int n) |
| { |
| int i; |
| char *w, *r, *q; |
| |
| r = p; |
| w = p; |
| for(i=0; i<n; i++) { |
| switch(*r) { |
| case '\b': |
| if(w > p) |
| w--; |
| break; |
| case '\r': |
| while(i<n-1 && *(r+1) == '\r') { |
| r++; |
| i++; |
| } |
| if(i<n && *(r+1) != '\n') { |
| q = r; |
| while(q>p && *(q-1) != '\n') |
| q--; |
| if(q > p) { |
| w = q; |
| break; |
| } |
| } |
| *w++ = '\n'; |
| break; |
| default: |
| *w++ = *r; |
| break; |
| } |
| r++; |
| } |
| return w-p; |
| } |
| |
| void |
| stdoutproc(void *v) |
| { |
| int fd1 = rcfd; |
| CFid *afd = addrfd; |
| CFid *dfd = datafd; |
| int n, m, w, npart; |
| char *buf, *s, *t; |
| Rune r; |
| char x[16], hold[UTFmax]; |
| |
| USED(v); |
| buf = malloc(8192+UTFmax+1); |
| npart = 0; |
| for(;;){ |
| /* Let typing have a go -- maybe there's a rubout waiting. */ |
| yield(); |
| n = read(fd1, buf+npart, 8192); |
| if(n <= 0) |
| error(nil); |
| |
| n = echocancel(buf+npart, n); |
| if(n == 0) |
| continue; |
| |
| n = dropcrnl(buf+npart, n); |
| if(n == 0) |
| continue; |
| |
| n = dropcr(buf+npart, n); |
| if(n == 0) |
| continue; |
| |
| /* squash NULs */ |
| s = memchr(buf+npart, 0, n); |
| if(s){ |
| for(t=s; s<buf+npart+n; s++) |
| if(*t = *s) /* assign = */ |
| t++; |
| n = t-(buf+npart); |
| } |
| |
| n += npart; |
| |
| /* hold on to final partial rune */ |
| npart = 0; |
| while(n>0 && (buf[n-1]&0xC0)){ |
| --n; |
| npart++; |
| if((buf[n]&0xC0)!=0x80){ |
| if(fullrune(buf+n, npart)){ |
| w = chartorune(&r, buf+n); |
| n += w; |
| npart -= w; |
| } |
| break; |
| } |
| } |
| if(n > 0){ |
| memmove(hold, buf+n, npart); |
| buf[n] = 0; |
| n = label(buf, n); |
| buf[n] = 0; |
| |
| // clumsy but effective: notice password |
| // prompts so we can disable echo. |
| password = 0; |
| if(cistrstr(buf, "password") || cistrstr(buf, "passphrase")) { |
| int i; |
| |
| i = n; |
| while(i > 0 && buf[i-1] == ' ') |
| i--; |
| password = i > 0 && buf[i-1] == ':'; |
| } |
| |
| qlock(&q.lk); |
| m = sprint(x, "#%d", q.p); |
| if(fswrite(afd, x, m) != m){ |
| fprint(2, "stdout writing address %s: %r; resetting\n", x); |
| if(fswrite(afd, "$", 1) < 0) |
| fprint(2, "reset: %r\n"); |
| fsseek(afd, 0, 0); |
| m = fsread(afd, x, sizeof x-1); |
| if(m >= 0){ |
| x[m] = 0; |
| q.p = atoi(x); |
| } |
| } |
| if(fswrite(dfd, buf, n) != n) |
| error("stdout writing body"); |
| /* Make sure acme scrolls to the end of the above write. */ |
| if(fswrite(dfd, nil, 0) != 0) |
| error("stdout flushing body"); |
| q.p += nrunes(buf, n); |
| qunlock(&q.lk); |
| memmove(buf, hold, npart); |
| } |
| } |
| } |
| |
| char wdir[512]; |
| int |
| label(char *sr, int n) |
| { |
| char *sl, *el, *er, *r, *p; |
| |
| er = sr+n; |
| for(r=er-1; r>=sr; r--) |
| if(*r == '\007') |
| break; |
| if(r < sr) |
| return n; |
| |
| el = r+1; |
| if(el-sr > sizeof wdir - strlen(name) - 20) |
| sr = el - sizeof wdir - strlen(name) - 20; |
| for(sl=el-3; sl>=sr; sl--) |
| if(sl[0]=='\033' && sl[1]==']' && sl[2]==';') |
| break; |
| if(sl < sr) |
| return n; |
| |
| *r = 0; |
| if(strcmp(sl+3, "*9term-hold+") != 0) { |
| /* |
| * add /-sysname if not present |
| */ |
| snprint(wdir, sizeof wdir, "name %s", sl+3); |
| p = strrchr(wdir, '/'); |
| if(p==nil || *(p+1) != '-'){ |
| p = wdir+strlen(wdir); |
| if(*(p-1) != '/') |
| *p++ = '/'; |
| *p++ = '-'; |
| strcpy(p, name); |
| } |
| strcat(wdir, "\n0\n"); |
| fswrite(ctlfd, wdir, strlen(wdir)); |
| } |
| |
| memmove(sl, el, er-el); |
| n -= (el-sl); |
| return n; |
| } |
| |
| int |
| delete(Event *e) |
| { |
| uint q0, q1; |
| int deltap; |
| |
| q0 = e->q0; |
| q1 = e->q1; |
| if(q1 <= q.p) |
| return e->q1-e->q0; |
| if(q0 >= q.p+ntyper) |
| return 0; |
| deltap = 0; |
| if(q0 < q.p){ |
| deltap = q.p-q0; |
| q0 = 0; |
| }else |
| q0 -= q.p; |
| if(q1 > q.p+ntyper) |
| q1 = ntyper; |
| else |
| q1 -= q.p; |
| deltype(q0, q1); |
| return deltap; |
| } |
| |
| void |
| addtype(int c, uint p0, char *b, int nb, int nr) |
| { |
| int i, w; |
| Rune r; |
| uint p; |
| char *b0; |
| |
| for(i=0; i<nb; i+=w){ |
| w = chartorune(&r, b+i); |
| if((r==0x7F||r==3) && c=='K'){ |
| write(rcfd, "\x7F", 1); |
| /* toss all typing */ |
| q.p += ntyper+nr; |
| ntypebreak = 0; |
| ntypeb = 0; |
| ntyper = 0; |
| /* buglet: more than one delete ignored */ |
| return; |
| } |
| if(r=='\n' || r==0x04) |
| ntypebreak++; |
| } |
| typing = realloc(typing, ntypeb+nb); |
| if(typing == nil) |
| error("realloc"); |
| if(p0 == ntyper) |
| memmove(typing+ntypeb, b, nb); |
| else{ |
| b0 = typing; |
| for(p=0; p<p0 && b0<typing+ntypeb; p++){ |
| w = chartorune(&r, b0+i); |
| b0 += w; |
| } |
| if(p != p0) |
| error("typing: findrune"); |
| memmove(b0+nb, b0, (typing+ntypeb)-b0); |
| memmove(b0, b, nb); |
| } |
| ntypeb += nb; |
| ntyper += nr; |
| } |
| |
| int |
| israw(int fd0) |
| { |
| return (!cook || password) && !isecho(fd0); |
| } |
| |
| void |
| sendtype(int fd0) |
| { |
| int i, n, nr, raw; |
| |
| raw = israw(fd0); |
| while(ntypebreak || (raw && ntypeb > 0)){ |
| for(i=0; i<ntypeb; i++) |
| if(typing[i]=='\n' || typing[i]==0x04 || (i==ntypeb-1 && raw)){ |
| if((typing[i] == '\n' || typing[i] == 0x04) && ntypebreak > 0) |
| ntypebreak--; |
| n = i+1; |
| i++; |
| if(!raw) |
| echoed(typing, n); |
| if(write(fd0, typing, n) != n) |
| error("sending to program"); |
| nr = nrunes(typing, i); |
| q.p += nr; |
| ntyper -= nr; |
| ntypeb -= i; |
| memmove(typing, typing+i, ntypeb); |
| goto cont2; |
| } |
| print("no breakchar\n"); |
| ntypebreak = 0; |
| cont2:; |
| } |
| } |
| |
| void |
| sendbs(int fd0, int n) |
| { |
| char buf[128]; |
| int m; |
| |
| memset(buf, 0x08, sizeof buf); |
| while(n > 0) { |
| m = sizeof buf; |
| if(m > n) |
| m = n; |
| n -= m; |
| write(fd0, buf, m); |
| } |
| } |
| |
| void |
| deltype(uint p0, uint p1) |
| { |
| int w; |
| uint p, b0, b1; |
| Rune r; |
| |
| /* advance to p0 */ |
| b0 = 0; |
| for(p=0; p<p0 && b0<ntypeb; p++){ |
| w = chartorune(&r, typing+b0); |
| b0 += w; |
| } |
| if(p != p0) |
| error("deltype 1"); |
| /* advance to p1 */ |
| b1 = b0; |
| for(; p<p1 && b1<ntypeb; p++){ |
| w = chartorune(&r, typing+b1); |
| b1 += w; |
| if(r=='\n' || r==0x04) |
| ntypebreak--; |
| } |
| if(p != p1) |
| error("deltype 2"); |
| memmove(typing+b0, typing+b1, ntypeb-b1); |
| ntypeb -= b1-b0; |
| ntyper -= p1-p0; |
| } |
| |
| void |
| type(Event *e, int fd0, CFid *afd, CFid *dfd) |
| { |
| int m, n, nr; |
| char buf[128]; |
| |
| if(e->nr > 0) |
| addtype(e->c1, e->q0-q.p, e->b, e->nb, e->nr); |
| else{ |
| m = e->q0; |
| while(m < e->q1){ |
| n = sprint(buf, "#%d", m); |
| fswrite(afd, buf, n); |
| n = fsread(dfd, buf, sizeof buf); |
| nr = nrunes(buf, n); |
| while(m+nr > e->q1){ |
| do; while(n>0 && (buf[--n]&0xC0)==0x80); |
| --nr; |
| } |
| if(n == 0) |
| break; |
| addtype(e->c1, m-q.p, buf, n, nr); |
| m += nr; |
| } |
| } |
| if(israw(fd0)) { |
| n = sprint(buf, "#%d,#%d", e->q0, e->q1); |
| fswrite(afd, buf, n); |
| fswrite(dfd, "", 0); |
| q.p -= e->q1 - e->q0; |
| } |
| sendtype(fd0); |
| if(e->nb > 0 && e->b[e->nb-1] == '\n') |
| cook = 1; |
| } |
| |
| void |
| sende(Event *e, int fd0, CFid *cfd, CFid *afd, CFid *dfd, int donl) |
| { |
| int l, m, n, nr, lastc, end; |
| char abuf[16], buf[128]; |
| |
| end = q.p+ntyper; |
| l = sprint(abuf, "#%d", end); |
| fswrite(afd, abuf, l); |
| if(e->nr > 0){ |
| fswrite(dfd, e->b, e->nb); |
| addtype(e->c1, ntyper, e->b, e->nb, e->nr); |
| lastc = e->r[e->nr-1]; |
| }else{ |
| m = e->q0; |
| lastc = 0; |
| while(m < e->q1){ |
| n = sprint(buf, "#%d", m); |
| fswrite(afd, buf, n); |
| n = fsread(dfd, buf, sizeof buf); |
| nr = nrunes(buf, n); |
| while(m+nr > e->q1){ |
| do; while(n>0 && (buf[--n]&0xC0)==0x80); |
| --nr; |
| } |
| if(n == 0) |
| break; |
| l = sprint(abuf, "#%d", end); |
| fswrite(afd, abuf, l); |
| fswrite(dfd, buf, n); |
| addtype(e->c1, ntyper, buf, n, nr); |
| lastc = buf[n-1]; |
| m += nr; |
| end += nr; |
| } |
| } |
| if(donl && lastc!='\n'){ |
| fswrite(dfd, "\n", 1); |
| addtype(e->c1, ntyper, "\n", 1, 1); |
| } |
| fswrite(cfd, "dot=addr", 8); |
| sendtype(fd0); |
| } |