win.c (15339B)
1 #include <u.h> 2 #include <libc.h> 3 #include <thread.h> 4 #include <fcall.h> 5 #include <9pclient.h> 6 #include "term.h" 7 8 const char *termprog = "win"; 9 10 #define EVENTSIZE 256 11 #define STACK 32768 12 13 typedef struct Event Event; 14 typedef struct Q Q; 15 16 struct Event 17 { 18 int c1; 19 int c2; 20 int q0; 21 int q1; 22 int flag; 23 int nb; 24 int nr; 25 char b[EVENTSIZE*UTFmax+1]; 26 Rune r[EVENTSIZE+1]; 27 }; 28 29 Event blank = { 30 'M', 31 'X', 32 0, 0, 0, 1, 1, 33 { ' ', 0 }, 34 { ' ', 0 } 35 }; 36 37 struct Q 38 { 39 QLock lk; 40 int p; 41 int k; 42 }; 43 44 Q q; 45 46 CFid *eventfd; 47 CFid *addrfd; 48 CFid *datafd; 49 CFid *ctlfd; 50 /* int bodyfd; */ 51 52 char *typing; 53 int ntypeb; 54 int ntyper; 55 int ntypebreak; 56 int debug; 57 int rcfd; 58 int cook = 1; 59 int password; 60 int israw(int); 61 62 char *name; 63 64 char **prog; 65 Channel *cwait; 66 int pid = -1; 67 68 int label(char*, int); 69 void error(char*, ...); 70 void stdinproc(void*); 71 void stdoutproc(void*); 72 void type(Event*, int, CFid*, CFid*); 73 void sende(Event*, int, CFid*, CFid*, CFid*, int); 74 char *onestring(int, char**); 75 int delete(Event*); 76 void deltype(uint, uint); 77 void sendbs(int, int); 78 void runproc(void*); 79 80 int 81 fsfidprint(CFid *fid, char *fmt, ...) 82 { 83 char buf[256]; 84 va_list arg; 85 int n; 86 87 va_start(arg, fmt); 88 n = vsnprint(buf, sizeof buf, fmt, arg); 89 va_end(arg); 90 return fswrite(fid, buf, n); 91 } 92 93 void 94 usage(void) 95 { 96 fprint(2, "usage: win cmd args...\n"); 97 threadexitsall("usage"); 98 } 99 100 void 101 waitthread(void *v) 102 { 103 recvp(cwait); 104 threadexitsall(nil); 105 } 106 107 void 108 hangupnote(void *a, char *msg) 109 { 110 if(strcmp(msg, "hangup") == 0 && pid != 0){ 111 postnote(PNGROUP, pid, "hangup"); 112 noted(NDFLT); 113 } 114 if(strstr(msg, "child")){ 115 char buf[128]; 116 int n; 117 118 n = awaitnohang(buf, sizeof buf-1); 119 if(n > 0){ 120 buf[n] = 0; 121 if(atoi(buf) == pid) 122 threadexitsall(0); 123 } 124 noted(NCONT); 125 } 126 noted(NDFLT); 127 } 128 129 void 130 threadmain(int argc, char **argv) 131 { 132 int fd, id; 133 char buf[256]; 134 char buf1[128]; 135 CFsys *fs; 136 char *dump; 137 138 dump = onestring(argc, argv); 139 140 ARGBEGIN{ 141 case 'd': 142 debug = 1; 143 break; 144 case 'n': 145 name = EARGF(usage()); 146 break; 147 default: 148 usage(); 149 }ARGEND 150 151 prog = argv; 152 153 if(name == nil){ 154 if(argc > 0) 155 name = argv[0]; 156 else{ 157 name = sysname(); 158 if(name == nil) 159 name = "gnot"; 160 } 161 } 162 163 /* 164 * notedisable("sys: write on closed pipe"); 165 * not okay to disable the note, because that 166 * gets inherited by the subshell, so that something 167 * as simple as "yes | sed 10q" never exits. 168 * call notifyoff instead. (is notedisable ever safe?) 169 */ 170 notifyoff("sys: write on closed pipe"); 171 172 noteenable("sys: child"); 173 notify(hangupnote); 174 175 if((fs = nsmount("acme", "")) == 0) 176 sysfatal("nsmount acme: %r"); 177 ctlfd = fsopen(fs, "new/ctl", ORDWR|OCEXEC); 178 if(ctlfd == 0 || fsread(ctlfd, buf, 12) != 12) 179 sysfatal("ctl: %r"); 180 id = atoi(buf); 181 snprint(buf, sizeof buf, "%d", id); 182 putenv("winid", buf); 183 sprint(buf, "%d/tag", id); 184 fd = fsopenfd(fs, buf, OWRITE|OCEXEC); 185 write(fd, " Send", 1+4); 186 close(fd); 187 sprint(buf, "%d/event", id); 188 eventfd = fsopen(fs, buf, ORDWR|OCEXEC); 189 sprint(buf, "%d/addr", id); 190 addrfd = fsopen(fs, buf, ORDWR|OCEXEC); 191 sprint(buf, "%d/data", id); 192 datafd = fsopen(fs, buf, ORDWR|OCEXEC); 193 sprint(buf, "%d/body", id); 194 /* bodyfd = fsopenfd(fs, buf, ORDWR|OCEXEC); */ 195 if(eventfd==nil || addrfd==nil || datafd==nil) 196 sysfatal("data files: %r"); 197 /* 198 if(eventfd<0 || addrfd<0 || datafd<0 || bodyfd<0) 199 sysfatal("data files: %r"); 200 */ 201 fsunmount(fs); 202 203 cwait = threadwaitchan(); 204 threadcreate(waitthread, nil, STACK); 205 pid = rcstart(argc, argv, &rcfd, nil); 206 if(pid == -1) 207 sysfatal("exec failed"); 208 209 getwd(buf1, sizeof buf1); 210 sprint(buf, "name %s/-%s\n0\n", buf1, name); 211 fswrite(ctlfd, buf, strlen(buf)); 212 sprint(buf, "dumpdir %s/\n", buf1); 213 fswrite(ctlfd, buf, strlen(buf)); 214 sprint(buf, "dump %s\n", dump); 215 fswrite(ctlfd, buf, strlen(buf)); 216 sprint(buf, "scroll"); 217 fswrite(ctlfd, buf, strlen(buf)); 218 219 updatewinsize(25, 80, 0, 0); 220 proccreate(stdoutproc, nil, STACK); 221 stdinproc(nil); 222 } 223 224 void 225 error(char *s, ...) 226 { 227 va_list arg; 228 229 if(s){ 230 va_start(arg, s); 231 s = vsmprint(s, arg); 232 va_end(arg); 233 fprint(2, "win: %s: %r\n", s); 234 } 235 if(pid != -1) 236 postnote(PNGROUP, pid, "hangup"); 237 threadexitsall(s); 238 } 239 240 char* 241 onestring(int argc, char **argv) 242 { 243 char *p; 244 int i, n; 245 static char buf[1024]; 246 247 if(argc == 0) 248 return ""; 249 p = buf; 250 for(i=0; i<argc; i++){ 251 n = strlen(argv[i]); 252 if(p+n+1 >= buf+sizeof buf) 253 break; 254 memmove(p, argv[i], n); 255 p += n; 256 *p++ = ' '; 257 } 258 p[-1] = 0; 259 return buf; 260 } 261 262 int 263 getec(CFid *efd) 264 { 265 static char buf[8192]; 266 static char *bufp; 267 static int nbuf; 268 269 if(nbuf == 0){ 270 nbuf = fsread(efd, buf, sizeof buf); 271 if(nbuf <= 0) 272 error(nil); 273 bufp = buf; 274 } 275 --nbuf; 276 return *bufp++; 277 } 278 279 int 280 geten(CFid *efd) 281 { 282 int n, c; 283 284 n = 0; 285 while('0'<=(c=getec(efd)) && c<='9') 286 n = n*10+(c-'0'); 287 if(c != ' ') 288 error("event number syntax"); 289 return n; 290 } 291 292 int 293 geter(CFid *efd, char *buf, int *nb) 294 { 295 Rune r; 296 int n; 297 298 r = getec(efd); 299 buf[0] = r; 300 n = 1; 301 if(r < Runeself) 302 goto Return; 303 while(!fullrune(buf, n)) 304 buf[n++] = getec(efd); 305 chartorune(&r, buf); 306 Return: 307 *nb = n; 308 return r; 309 } 310 311 void 312 gete(CFid *efd, Event *e) 313 { 314 int i, nb; 315 316 e->c1 = getec(efd); 317 e->c2 = getec(efd); 318 e->q0 = geten(efd); 319 e->q1 = geten(efd); 320 e->flag = geten(efd); 321 e->nr = geten(efd); 322 if(e->nr > EVENTSIZE) 323 error("event string too long"); 324 e->nb = 0; 325 for(i=0; i<e->nr; i++){ 326 e->r[i] = geter(efd, e->b+e->nb, &nb); 327 e->nb += nb; 328 } 329 e->r[e->nr] = 0; 330 e->b[e->nb] = 0; 331 if(getec(efd) != '\n') 332 error("event syntax 2"); 333 } 334 335 int 336 nrunes(char *s, int nb) 337 { 338 int i, n; 339 Rune r; 340 341 n = 0; 342 for(i=0; i<nb; n++) 343 i += chartorune(&r, s+i); 344 return n; 345 } 346 347 void 348 stdinproc(void *v) 349 { 350 CFid *cfd = ctlfd; 351 CFid *efd = eventfd; 352 CFid *dfd = datafd; 353 CFid *afd = addrfd; 354 int fd0 = rcfd; 355 Event e, e2, e3, e4; 356 int n; 357 358 USED(v); 359 360 for(;;){ 361 if(debug) 362 fprint(2, "typing[%d,%d)\n", q.p, q.p+ntyper); 363 gete(efd, &e); 364 if(debug) 365 fprint(2, "msg %c%c q[%d,%d)... ", e.c1, e.c2, e.q0, e.q1); 366 qlock(&q.lk); 367 switch(e.c1){ 368 default: 369 Unknown: 370 print("unknown message %c%c\n", e.c1, e.c2); 371 break; 372 373 case 'E': /* write to body or tag; can't affect us */ 374 switch(e.c2){ 375 case 'I': 376 case 'D': /* body */ 377 if(debug) 378 fprint(2, "shift typing %d... ", e.q1-e.q0); 379 q.p += e.q1-e.q0; 380 break; 381 382 case 'i': 383 case 'd': /* tag */ 384 break; 385 386 default: 387 goto Unknown; 388 } 389 break; 390 391 case 'F': /* generated by our actions; ignore */ 392 break; 393 394 case 'K': 395 case 'M': 396 switch(e.c2){ 397 case 'I': 398 if(e.nr == 1 && e.r[0] == 0x7F) { 399 char buf[1]; 400 fsprint(addrfd, "#%ud,#%ud", e.q0, e.q1); 401 fswrite(datafd, "", 0); 402 buf[0] = 0x7F; 403 write(fd0, buf, 1); 404 break; 405 } 406 if(e.q0 < q.p){ 407 if(debug) 408 fprint(2, "shift typing %d... ", e.q1-e.q0); 409 q.p += e.q1-e.q0; 410 } 411 else if(e.q0 <= q.p+ntyper){ 412 if(debug) 413 fprint(2, "type... "); 414 type(&e, fd0, afd, dfd); 415 } 416 break; 417 418 case 'D': 419 n = delete(&e); 420 q.p -= n; 421 if(israw(fd0) && e.q1 >= q.p+n) 422 sendbs(fd0, n); 423 break; 424 425 case 'x': 426 case 'X': 427 if(e.flag & 2) 428 gete(efd, &e2); 429 if(e.flag & 8){ 430 gete(efd, &e3); 431 gete(efd, &e4); 432 } 433 if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){ 434 /* send it straight back */ 435 fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1); 436 break; 437 } 438 if(e.q0==e.q1 && (e.flag&2)){ 439 e2.flag = e.flag; 440 e = e2; 441 } 442 char buf[100]; 443 snprint(buf, sizeof buf, "%.*S", e.nr, e.r); 444 if(cistrcmp(buf, "cook") == 0) { 445 cook = 1; 446 break; 447 } 448 if(cistrcmp(buf, "nocook") == 0) { 449 cook = 0; 450 break; 451 } 452 if(e.flag & 8){ 453 if(e.q1 != e.q0){ 454 sende(&e, fd0, cfd, afd, dfd, 0); 455 sende(&blank, fd0, cfd, afd, dfd, 0); 456 } 457 sende(&e3, fd0, cfd, afd, dfd, 1); 458 }else if(e.q1 != e.q0) 459 sende(&e, fd0, cfd, afd, dfd, 1); 460 break; 461 462 case 'l': 463 case 'L': 464 /* just send it back */ 465 if(e.flag & 2) 466 gete(efd, &e2); 467 fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1); 468 break; 469 470 case 'd': 471 case 'i': 472 break; 473 474 default: 475 goto Unknown; 476 } 477 } 478 qunlock(&q.lk); 479 } 480 } 481 482 int 483 dropcr(char *p, int n) 484 { 485 int i; 486 char *w, *r, *q; 487 488 r = p; 489 w = p; 490 for(i=0; i<n; i++) { 491 switch(*r) { 492 case '\b': 493 if(w > p) 494 w--; 495 break; 496 case '\r': 497 while(i<n-1 && *(r+1) == '\r') { 498 r++; 499 i++; 500 } 501 if(i<n && *(r+1) != '\n') { 502 q = r; 503 while(q>p && *(q-1) != '\n') 504 q--; 505 if(q > p) { 506 w = q; 507 break; 508 } 509 } 510 *w++ = '\n'; 511 break; 512 default: 513 *w++ = *r; 514 break; 515 } 516 r++; 517 } 518 return w-p; 519 } 520 521 void 522 stdoutproc(void *v) 523 { 524 int fd1 = rcfd; 525 CFid *afd = addrfd; 526 CFid *dfd = datafd; 527 int n, m, w, npart; 528 char *buf, *s, *t; 529 Rune r; 530 char x[16], hold[UTFmax]; 531 532 USED(v); 533 buf = malloc(8192+UTFmax+1); 534 npart = 0; 535 for(;;){ 536 /* Let typing have a go -- maybe there's a rubout waiting. */ 537 yield(); 538 n = read(fd1, buf+npart, 8192); 539 if(n <= 0) 540 error(nil); 541 542 n = echocancel(buf+npart, n); 543 if(n == 0) 544 continue; 545 546 n = dropcrnl(buf+npart, n); 547 if(n == 0) 548 continue; 549 550 n = dropcr(buf+npart, n); 551 if(n == 0) 552 continue; 553 554 /* squash NULs */ 555 s = memchr(buf+npart, 0, n); 556 if(s){ 557 for(t=s; s<buf+npart+n; s++) 558 if(*t = *s) /* assign = */ 559 t++; 560 n = t-(buf+npart); 561 } 562 563 n += npart; 564 565 /* hold on to final partial rune */ 566 npart = 0; 567 while(n>0 && (buf[n-1]&0xC0)){ 568 --n; 569 npart++; 570 if((buf[n]&0xC0)!=0x80){ 571 if(fullrune(buf+n, npart)){ 572 w = chartorune(&r, buf+n); 573 n += w; 574 npart -= w; 575 } 576 break; 577 } 578 } 579 if(n > 0){ 580 memmove(hold, buf+n, npart); 581 buf[n] = 0; 582 n = label(buf, n); 583 buf[n] = 0; 584 585 // clumsy but effective: notice password 586 // prompts so we can disable echo. 587 password = 0; 588 if(cistrstr(buf, "password") || cistrstr(buf, "passphrase")) { 589 int i; 590 591 i = n; 592 while(i > 0 && buf[i-1] == ' ') 593 i--; 594 password = i > 0 && buf[i-1] == ':'; 595 } 596 597 qlock(&q.lk); 598 m = sprint(x, "#%d", q.p); 599 if(fswrite(afd, x, m) != m){ 600 fprint(2, "stdout writing address %s: %r; resetting\n", x); 601 if(fswrite(afd, "$", 1) < 0) 602 fprint(2, "reset: %r\n"); 603 fsseek(afd, 0, 0); 604 m = fsread(afd, x, sizeof x-1); 605 if(m >= 0){ 606 x[m] = 0; 607 q.p = atoi(x); 608 } 609 } 610 if(fswrite(dfd, buf, n) != n) 611 error("stdout writing body"); 612 /* Make sure acme scrolls to the end of the above write. */ 613 if(fswrite(dfd, nil, 0) != 0) 614 error("stdout flushing body"); 615 q.p += nrunes(buf, n); 616 qunlock(&q.lk); 617 memmove(buf, hold, npart); 618 } 619 } 620 } 621 622 char wdir[512]; 623 int 624 label(char *sr, int n) 625 { 626 char *sl, *el, *er, *r, *p; 627 628 er = sr+n; 629 for(r=er-1; r>=sr; r--) 630 if(*r == '\007') 631 break; 632 if(r < sr) 633 return n; 634 635 el = r+1; 636 if(el-sr > sizeof wdir - strlen(name) - 20) 637 sr = el - (sizeof wdir - strlen(name) - 20); 638 for(sl=el-3; sl>=sr; sl--) 639 if(sl[0]=='\033' && sl[1]==']' && sl[2]==';') 640 break; 641 if(sl < sr) 642 return n; 643 644 *r = 0; 645 if(strcmp(sl+3, "*9term-hold+") != 0) { 646 /* 647 * add /-sysname if not present 648 */ 649 snprint(wdir, sizeof wdir, "name %s", sl+3); 650 p = strrchr(wdir, '/'); 651 if(p==nil || *(p+1) != '-'){ 652 p = wdir+strlen(wdir); 653 if(*(p-1) != '/') 654 *p++ = '/'; 655 *p++ = '-'; 656 strcpy(p, name); 657 } 658 strcat(wdir, "\n0\n"); 659 fswrite(ctlfd, wdir, strlen(wdir)); 660 } 661 662 memmove(sl, el, er-el); 663 n -= (el-sl); 664 return n; 665 } 666 667 int 668 delete(Event *e) 669 { 670 uint q0, q1; 671 int deltap; 672 673 q0 = e->q0; 674 q1 = e->q1; 675 if(q1 <= q.p) 676 return e->q1-e->q0; 677 if(q0 >= q.p+ntyper) 678 return 0; 679 deltap = 0; 680 if(q0 < q.p){ 681 deltap = q.p-q0; 682 q0 = 0; 683 }else 684 q0 -= q.p; 685 if(q1 > q.p+ntyper) 686 q1 = ntyper; 687 else 688 q1 -= q.p; 689 deltype(q0, q1); 690 return deltap; 691 } 692 693 void 694 addtype(int c, uint p0, char *b, int nb, int nr) 695 { 696 int i, w; 697 Rune r; 698 uint p; 699 char *b0; 700 701 for(i=0; i<nb; i+=w){ 702 w = chartorune(&r, b+i); 703 if((r==0x7F||r==3) && c=='K'){ 704 write(rcfd, "\x7F", 1); 705 /* toss all typing */ 706 q.p += ntyper+nr; 707 ntypebreak = 0; 708 ntypeb = 0; 709 ntyper = 0; 710 /* buglet: more than one delete ignored */ 711 return; 712 } 713 if(r=='\n' || r==0x04) 714 ntypebreak++; 715 } 716 typing = realloc(typing, ntypeb+nb); 717 if(typing == nil) 718 error("realloc"); 719 if(p0 == ntyper) 720 memmove(typing+ntypeb, b, nb); 721 else{ 722 b0 = typing; 723 for(p=0; p<p0 && b0<typing+ntypeb; p++){ 724 w = chartorune(&r, b0+i); 725 b0 += w; 726 } 727 if(p != p0) 728 error("typing: findrune"); 729 memmove(b0+nb, b0, (typing+ntypeb)-b0); 730 memmove(b0, b, nb); 731 } 732 ntypeb += nb; 733 ntyper += nr; 734 } 735 736 int 737 israw(int fd0) 738 { 739 return (!cook || password) && !isecho(fd0); 740 } 741 742 void 743 sendtype(int fd0) 744 { 745 int i, n, nr, raw; 746 747 raw = israw(fd0); 748 while(ntypebreak || (raw && ntypeb > 0)){ 749 for(i=0; i<ntypeb; i++) 750 if(typing[i]=='\n' || typing[i]==0x04 || (i==ntypeb-1 && raw)){ 751 if((typing[i] == '\n' || typing[i] == 0x04) && ntypebreak > 0) 752 ntypebreak--; 753 n = i+1; 754 i++; 755 if(!raw) 756 echoed(typing, n); 757 if(write(fd0, typing, n) != n) 758 error("sending to program"); 759 nr = nrunes(typing, i); 760 q.p += nr; 761 ntyper -= nr; 762 ntypeb -= i; 763 memmove(typing, typing+i, ntypeb); 764 goto cont2; 765 } 766 print("no breakchar\n"); 767 ntypebreak = 0; 768 cont2:; 769 } 770 } 771 772 void 773 sendbs(int fd0, int n) 774 { 775 char buf[128]; 776 int m; 777 778 memset(buf, 0x08, sizeof buf); 779 while(n > 0) { 780 m = sizeof buf; 781 if(m > n) 782 m = n; 783 n -= m; 784 write(fd0, buf, m); 785 } 786 } 787 788 void 789 deltype(uint p0, uint p1) 790 { 791 int w; 792 uint p, b0, b1; 793 Rune r; 794 795 /* advance to p0 */ 796 b0 = 0; 797 for(p=0; p<p0 && b0<ntypeb; p++){ 798 w = chartorune(&r, typing+b0); 799 b0 += w; 800 } 801 if(p != p0) 802 error("deltype 1"); 803 /* advance to p1 */ 804 b1 = b0; 805 for(; p<p1 && b1<ntypeb; p++){ 806 w = chartorune(&r, typing+b1); 807 b1 += w; 808 if(r=='\n' || r==0x04) 809 ntypebreak--; 810 } 811 if(p != p1) 812 error("deltype 2"); 813 memmove(typing+b0, typing+b1, ntypeb-b1); 814 ntypeb -= b1-b0; 815 ntyper -= p1-p0; 816 } 817 818 void 819 type(Event *e, int fd0, CFid *afd, CFid *dfd) 820 { 821 int m, n, nr; 822 char buf[128]; 823 824 if(e->nr > 0) 825 addtype(e->c1, e->q0-q.p, e->b, e->nb, e->nr); 826 else{ 827 m = e->q0; 828 while(m < e->q1){ 829 n = sprint(buf, "#%d", m); 830 fswrite(afd, buf, n); 831 n = fsread(dfd, buf, sizeof buf); 832 nr = nrunes(buf, n); 833 while(m+nr > e->q1){ 834 do; while(n>0 && (buf[--n]&0xC0)==0x80); 835 --nr; 836 } 837 if(n == 0) 838 break; 839 addtype(e->c1, m-q.p, buf, n, nr); 840 m += nr; 841 } 842 } 843 if(israw(fd0)) { 844 n = sprint(buf, "#%d,#%d", e->q0, e->q1); 845 fswrite(afd, buf, n); 846 fswrite(dfd, "", 0); 847 q.p -= e->q1 - e->q0; 848 } 849 sendtype(fd0); 850 if(e->nb > 0 && e->b[e->nb-1] == '\n') 851 cook = 1; 852 } 853 854 void 855 sende(Event *e, int fd0, CFid *cfd, CFid *afd, CFid *dfd, int donl) 856 { 857 int l, m, n, nr, lastc, end; 858 char abuf[16], buf[128]; 859 860 end = q.p+ntyper; 861 l = sprint(abuf, "#%d", end); 862 fswrite(afd, abuf, l); 863 if(e->nr > 0){ 864 fswrite(dfd, e->b, e->nb); 865 addtype(e->c1, ntyper, e->b, e->nb, e->nr); 866 lastc = e->r[e->nr-1]; 867 }else{ 868 m = e->q0; 869 lastc = 0; 870 while(m < e->q1){ 871 n = sprint(buf, "#%d", m); 872 fswrite(afd, buf, n); 873 n = fsread(dfd, buf, sizeof buf); 874 nr = nrunes(buf, n); 875 while(m+nr > e->q1){ 876 do; while(n>0 && (buf[--n]&0xC0)==0x80); 877 --nr; 878 } 879 if(n == 0) 880 break; 881 l = sprint(abuf, "#%d", end); 882 fswrite(afd, abuf, l); 883 fswrite(dfd, buf, n); 884 addtype(e->c1, ntyper, buf, n, nr); 885 lastc = buf[n-1]; 886 m += nr; 887 end += nr; 888 } 889 } 890 if(donl && lastc!='\n'){ 891 fswrite(dfd, "\n", 1); 892 addtype(e->c1, ntyper, "\n", 1, 1); 893 } 894 fswrite(cfd, "dot=addr", 8); 895 sendtype(fd0); 896 }