1 /* Copyright (c) 1980 Regents of the University of California */ 2 static char *sccsid = "@(#)ex_io.c 6.2 10/23/80"; 3 #include "ex.h" 4 #include "ex_argv.h" 5 #include "ex_temp.h" 6 #include "ex_tty.h" 7 #include "ex_vis.h" 8 9 /* 10 * File input/output, source, preserve and recover 11 */ 12 13 /* 14 * Following remember where . was in the previous file for return 15 * on file switching. 16 */ 17 int altdot; 18 int oldadot; 19 bool wasalt; 20 short isalt; 21 22 long cntch; /* Count of characters on unit io */ 23 #ifndef VMUNIX 24 short cntln; /* Count of lines " */ 25 #else 26 int cntln; 27 #endif 28 long cntnull; /* Count of nulls " */ 29 long cntodd; /* Count of non-ascii characters " */ 30 31 /* 32 * Parse file name for command encoded by comm. 33 * If comm is E then command is doomed and we are 34 * parsing just so user won't have to retype the name. 35 */ 36 filename(comm) 37 int comm; 38 { 39 register int c = comm, d; 40 register int i; 41 42 d = getchar(); 43 if (endcmd(d)) { 44 if (savedfile[0] == 0 && comm != 'f') 45 error("No file|No current filename"); 46 CP(file, savedfile); 47 wasalt = (isalt > 0) ? isalt-1 : 0; 48 isalt = 0; 49 oldadot = altdot; 50 if (c == 'e' || c == 'E') 51 altdot = lineDOT(); 52 if (d == EOF) 53 ungetchar(d); 54 } else { 55 ungetchar(d); 56 getone(); 57 eol(); 58 if (savedfile[0] == 0 && c != 'E' && c != 'e') { 59 c = 'e'; 60 edited = 0; 61 } 62 wasalt = strcmp(file, altfile) == 0; 63 oldadot = altdot; 64 switch (c) { 65 66 case 'f': 67 edited = 0; 68 /* fall into ... */ 69 70 case 'e': 71 if (savedfile[0]) { 72 altdot = lineDOT(); 73 CP(altfile, savedfile); 74 } 75 CP(savedfile, file); 76 break; 77 78 default: 79 if (file[0]) { 80 if (c != 'E') 81 altdot = lineDOT(); 82 CP(altfile, file); 83 } 84 break; 85 } 86 } 87 if (hush && comm != 'f' || comm == 'E') 88 return; 89 if (file[0] != 0) { 90 lprintf("\"%s\"", file); 91 if (comm == 'f') { 92 if (value(READONLY)) 93 printf(" [Read only]"); 94 if (!edited) 95 printf(" [Not edited]"); 96 if (tchng) 97 printf(" [Modified]"); 98 } 99 flush(); 100 } else 101 printf("No file "); 102 if (comm == 'f') { 103 if (!(i = lineDOL())) 104 i++; 105 printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(), 106 (long) 100 * lineDOT() / i); 107 } 108 } 109 110 /* 111 * Get the argument words for a command into genbuf 112 * expanding # and %. 113 */ 114 getargs() 115 { 116 register int c; 117 register char *cp, *fp; 118 static char fpatbuf[32]; /* hence limit on :next +/pat */ 119 120 pastwh(); 121 if (peekchar() == '+') { 122 for (cp = fpatbuf;;) { 123 c = *cp++ = getchar(); 124 if (cp >= &fpatbuf[sizeof(fpatbuf)]) 125 error("Pattern too long"); 126 if (c == '\\' && isspace(peekchar())) 127 c = getchar(); 128 if (c == EOF || isspace(c)) { 129 ungetchar(c); 130 *--cp = 0; 131 firstpat = &fpatbuf[1]; 132 break; 133 } 134 } 135 } 136 if (skipend()) 137 return (0); 138 CP(genbuf, "echo "); cp = &genbuf[5]; 139 for (;;) { 140 c = getchar(); 141 if (endcmd(c)) { 142 ungetchar(c); 143 break; 144 } 145 switch (c) { 146 147 case '\\': 148 if (any(peekchar(), "#%|")) 149 c = getchar(); 150 /* fall into... */ 151 152 default: 153 if (cp > &genbuf[LBSIZE - 2]) 154 flong: 155 error("Argument buffer overflow"); 156 *cp++ = c; 157 break; 158 159 case '#': 160 fp = altfile; 161 if (*fp == 0) 162 error("No alternate filename@to substitute for #"); 163 goto filexp; 164 165 case '%': 166 fp = savedfile; 167 if (*fp == 0) 168 error("No current filename@to substitute for %%"); 169 filexp: 170 while (*fp) { 171 if (cp > &genbuf[LBSIZE - 2]) 172 goto flong; 173 *cp++ = *fp++; 174 } 175 break; 176 } 177 } 178 *cp = 0; 179 return (1); 180 } 181 182 /* 183 * Glob the argument words in genbuf, or if no globbing 184 * is implied, just split them up directly. 185 */ 186 glob(gp) 187 struct glob *gp; 188 { 189 int pvec[2]; 190 register char **argv = gp->argv; 191 register char *cp = gp->argspac; 192 register int c; 193 char ch; 194 int nleft = NCARGS; 195 196 gp->argc0 = 0; 197 if (gscan() == 0) { 198 register char *v = genbuf + 5; /* strlen("echo ") */ 199 200 for (;;) { 201 while (isspace(*v)) 202 v++; 203 if (!*v) 204 break; 205 *argv++ = cp; 206 while (*v && !isspace(*v)) 207 *cp++ = *v++; 208 *cp++ = 0; 209 gp->argc0++; 210 } 211 *argv = 0; 212 return; 213 } 214 if (pipe(pvec) < 0) 215 error("Can't make pipe to glob"); 216 pid = fork(); 217 io = pvec[0]; 218 if (pid < 0) { 219 close(pvec[1]); 220 error("Can't fork to do glob"); 221 } 222 if (pid == 0) { 223 int oerrno; 224 225 close(1); 226 dup(pvec[1]); 227 close(pvec[0]); 228 close(2); /* so errors don't mess up the screen */ 229 open("/dev/null", 1); 230 execl(svalue(SHELL), "sh", "-c", genbuf, 0); 231 oerrno = errno; close(1); dup(2); errno = oerrno; 232 filioerr(svalue(SHELL)); 233 } 234 close(pvec[1]); 235 do { 236 *argv = cp; 237 for (;;) { 238 if (read(io, &ch, 1) != 1) { 239 close(io); 240 c = -1; 241 } else 242 c = ch & TRIM; 243 if (c <= 0 || isspace(c)) 244 break; 245 *cp++ = c; 246 if (--nleft <= 0) 247 error("Arg list too long"); 248 } 249 if (cp != *argv) { 250 --nleft; 251 *cp++ = 0; 252 gp->argc0++; 253 if (gp->argc0 >= NARGS) 254 error("Arg list too long"); 255 argv++; 256 } 257 } while (c >= 0); 258 waitfor(); 259 if (gp->argc0 == 0) 260 error("No match"); 261 } 262 263 /* 264 * Scan genbuf for shell metacharacters. 265 * Set is union of v7 shell and csh metas. 266 */ 267 gscan() 268 { 269 register char *cp; 270 271 for (cp = genbuf; *cp; cp++) 272 if (any(*cp, "~{[*?$`'\"\\")) 273 return (1); 274 return (0); 275 } 276 277 /* 278 * Parse one filename into file. 279 */ 280 getone() 281 { 282 register char *str; 283 struct glob G; 284 285 if (getargs() == 0) 286 error("Missing filename"); 287 glob(&G); 288 if (G.argc0 > 1) 289 error("Ambiguous|Too many file names"); 290 str = G.argv[G.argc0 - 1]; 291 if (strlen(str) > FNSIZE - 4) 292 error("Filename too long"); 293 samef: 294 CP(file, str); 295 } 296 297 /* 298 * Read a file from the world. 299 * C is command, 'e' if this really an edit (or a recover). 300 */ 301 rop(c) 302 int c; 303 { 304 register int i; 305 struct stat stbuf; 306 short magic; 307 static int ovro; /* old value(READONLY) */ 308 static int denied; /* 1 if READONLY was set due to file permissions */ 309 310 io = open(file, 0); 311 if (io < 0) { 312 if (c == 'e' && errno == ENOENT) { 313 edited++; 314 /* 315 * If the user just did "ex foo" he is probably 316 * creating a new file. Don't be an error, since 317 * this is ugly, and it screws up the + option. 318 */ 319 if (!seenprompt) { 320 printf(" [New file]"); 321 noonl(); 322 return; 323 } 324 } 325 syserror(); 326 } 327 if (fstat(io, &stbuf)) 328 syserror(); 329 switch (stbuf.st_mode & S_IFMT) { 330 331 case S_IFBLK: 332 error(" Block special file"); 333 334 case S_IFCHR: 335 if (isatty(io)) 336 error(" Teletype"); 337 if (samei(&stbuf, "/dev/null")) 338 break; 339 error(" Character special file"); 340 341 case S_IFDIR: 342 error(" Directory"); 343 344 case S_IFREG: 345 #ifdef CRYPT 346 if (xflag) 347 break; 348 #endif 349 i = read(io, (char *) &magic, sizeof(magic)); 350 lseek(io, 0l, 0); 351 if (i != sizeof(magic)) 352 break; 353 switch (magic) { 354 355 case 0405: /* Interdata? overlay */ 356 case 0407: /* unshared */ 357 case 0410: /* shared text */ 358 case 0411: /* separate I/D */ 359 case 0413: /* VM/Unix demand paged */ 360 case 0430: /* PDP-11 Overlay shared */ 361 case 0431: /* PDP-11 Overlay sep I/D */ 362 error(" Executable"); 363 364 /* 365 * We do not forbid the editing of portable archives 366 * because it is reasonable to edit them, especially 367 * if they are archives of text files. This is 368 * especially useful if you archive source files together 369 * and copy them to another system with ~%take, since 370 * the files sometimes show up munged and must be fixed. 371 */ 372 case 0177545: 373 case 0177555: 374 error(" Archive"); 375 376 default: 377 if (magic & 0100200) 378 error(" Non-ascii file"); 379 break; 380 } 381 } 382 if (c != 'r') { 383 if (value(READONLY) && denied) { 384 value(READONLY) = ovro; 385 denied = 0; 386 } 387 if ((stbuf.st_mode & 0222) == 0 || access(file, 2) < 0) { 388 ovro = value(READONLY); 389 denied = 1; 390 value(READONLY) = 1; 391 } 392 } 393 if (value(READONLY)) { 394 printf(" [Read only]"); 395 flush(); 396 } 397 if (c == 'r') 398 setdot(); 399 else 400 setall(); 401 if (FIXUNDO && inopen && c == 'r') 402 undap1 = undap2 = dot + 1; 403 rop2(); 404 rop3(c); 405 } 406 407 rop2() 408 { 409 410 deletenone(); 411 clrstats(); 412 ignore(append(getfile, addr2)); 413 } 414 415 rop3(c) 416 int c; 417 { 418 419 if (iostats() == 0 && c == 'e') 420 edited++; 421 if (c == 'e') { 422 if (wasalt || firstpat) { 423 register line *addr = zero + oldadot; 424 425 if (addr > dol) 426 addr = dol; 427 if (firstpat) { 428 globp = (*firstpat) ? firstpat : "$"; 429 commands(1,1); 430 firstpat = 0; 431 } else if (addr >= one) { 432 if (inopen) 433 dot = addr; 434 markpr(addr); 435 } else 436 goto other; 437 } else 438 other: 439 if (dol > zero) { 440 if (inopen) 441 dot = one; 442 markpr(one); 443 } 444 if(FIXUNDO) 445 undkind = UNDNONE; 446 if (inopen) { 447 vcline = 0; 448 vreplace(0, LINES, lineDOL()); 449 } 450 } 451 if (laste) { 452 #ifdef VMUNIX 453 tlaste(); 454 #endif 455 laste = 0; 456 sync(); 457 } 458 } 459 460 /* 461 * Are these two really the same inode? 462 */ 463 samei(sp, cp) 464 struct stat *sp; 465 char *cp; 466 { 467 struct stat stb; 468 469 if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev) 470 return (0); 471 return (sp->st_ino == stb.st_ino); 472 } 473 474 /* Returns from edited() */ 475 #define EDF 0 /* Edited file */ 476 #define NOTEDF -1 /* Not edited file */ 477 #define PARTBUF 1 /* Write of partial buffer to Edited file */ 478 479 /* 480 * Write a file. 481 */ 482 wop(dofname) 483 bool dofname; /* if 1 call filename, else use savedfile */ 484 { 485 register int c, exclam, nonexist; 486 line *saddr1, *saddr2; 487 struct stat stbuf; 488 489 c = 0; 490 exclam = 0; 491 if (dofname) { 492 if (peekchar() == '!') 493 exclam++, ignchar(); 494 ignore(skipwh()); 495 while (peekchar() == '>') 496 ignchar(), c++, ignore(skipwh()); 497 if (c != 0 && c != 2) 498 error("Write forms are 'w' and 'w>>'"); 499 filename('w'); 500 } else { 501 if (savedfile[0] == 0) 502 error("No file|No current filename"); 503 saddr1=addr1; 504 saddr2=addr2; 505 addr1=one; 506 addr2=dol; 507 CP(file, savedfile); 508 if (inopen) { 509 vclrech(0); 510 splitw++; 511 } 512 lprintf("\"%s\"", file); 513 } 514 nonexist = stat(file, &stbuf); 515 switch (c) { 516 517 case 0: 518 if (!exclam && (!value(WRITEANY) || value(READONLY))) 519 switch (edfile()) { 520 521 case NOTEDF: 522 if (nonexist) 523 break; 524 if ((stbuf.st_mode & S_IFMT) == S_IFCHR) { 525 if (samei(&stbuf, "/dev/null")) 526 break; 527 if (samei(&stbuf, "/dev/tty")) 528 break; 529 } 530 io = open(file, 1); 531 if (io < 0) 532 syserror(); 533 if (!isatty(io)) 534 serror(" File exists| File exists - use \"w! %s\" to overwrite", file); 535 close(io); 536 break; 537 538 case EDF: 539 if (value(READONLY)) 540 error(" File is read only"); 541 break; 542 543 case PARTBUF: 544 if (value(READONLY)) 545 error(" File is read only"); 546 error(" Use \"w!\" to write partial buffer"); 547 } 548 cre: 549 /* 550 synctmp(); 551 */ 552 #ifdef V6 553 io = creat(file, 0644); 554 #else 555 io = creat(file, 0666); 556 #endif 557 if (io < 0) 558 syserror(); 559 writing = 1; 560 if (hush == 0) 561 if (nonexist) 562 printf(" [New file]"); 563 else if (value(WRITEANY) && edfile() != EDF) 564 printf(" [Existing file]"); 565 break; 566 567 case 2: 568 io = open(file, 1); 569 if (io < 0) { 570 if (exclam || value(WRITEANY)) 571 goto cre; 572 syserror(); 573 } 574 lseek(io, 0l, 2); 575 break; 576 } 577 putfile(); 578 ignore(iostats()); 579 if (c != 2 && addr1 == one && addr2 == dol) { 580 if (eq(file, savedfile)) 581 edited = 1; 582 sync(); 583 } 584 if (!dofname) { 585 addr1 = saddr1; 586 addr2 = saddr2; 587 } 588 writing = 0; 589 } 590 591 /* 592 * Is file the edited file? 593 * Work here is that it is not considered edited 594 * if this is a partial buffer, and distinguish 595 * all cases. 596 */ 597 edfile() 598 { 599 600 if (!edited || !eq(file, savedfile)) 601 return (NOTEDF); 602 return (addr1 == one && addr2 == dol ? EDF : PARTBUF); 603 } 604 605 /* 606 * Extract the next line from the io stream. 607 */ 608 static char *nextip; 609 610 getfile() 611 { 612 register short c; 613 register char *lp, *fp; 614 615 lp = linebuf; 616 fp = nextip; 617 do { 618 if (--ninbuf < 0) { 619 ninbuf = read(io, genbuf, LBSIZE) - 1; 620 if (ninbuf < 0) { 621 if (lp != linebuf) { 622 lp++; 623 printf(" [Incomplete last line]"); 624 break; 625 } 626 return (EOF); 627 } 628 #ifdef CRYPT 629 fp = genbuf; 630 while(fp < &genbuf[ninbuf]) { 631 if (*fp++ & 0200) { 632 if (kflag) 633 crblock(perm, genbuf, ninbuf+1, 634 cntch); 635 break; 636 } 637 } 638 #endif 639 fp = genbuf; 640 cntch += ninbuf+1; 641 } 642 if (lp >= &linebuf[LBSIZE]) { 643 error(" Line too long"); 644 } 645 c = *fp++; 646 if (c == 0) { 647 cntnull++; 648 continue; 649 } 650 if (c & QUOTE) { 651 cntodd++; 652 c &= TRIM; 653 if (c == 0) 654 continue; 655 } 656 *lp++ = c; 657 } while (c != '\n'); 658 *--lp = 0; 659 nextip = fp; 660 cntln++; 661 return (0); 662 } 663 664 /* 665 * Write a range onto the io stream. 666 */ 667 putfile() 668 { 669 line *a1; 670 register char *fp, *lp; 671 register int nib; 672 673 a1 = addr1; 674 clrstats(); 675 cntln = addr2 - a1 + 1; 676 if (cntln == 0) 677 return; 678 nib = BUFSIZ; 679 fp = genbuf; 680 do { 681 getline(*a1++); 682 lp = linebuf; 683 for (;;) { 684 if (--nib < 0) { 685 nib = fp - genbuf; 686 #ifdef CRYPT 687 if(kflag) 688 crblock(perm, genbuf, nib, cntch); 689 #endif 690 if (write(io, genbuf, nib) != nib) { 691 wrerror(); 692 } 693 cntch += nib; 694 nib = BUFSIZ - 1; 695 fp = genbuf; 696 } 697 if ((*fp++ = *lp++) == 0) { 698 fp[-1] = '\n'; 699 break; 700 } 701 } 702 } while (a1 <= addr2); 703 nib = fp - genbuf; 704 #ifdef CRYPT 705 if(kflag) 706 crblock(perm, genbuf, nib, cntch); 707 #endif 708 if (write(io, genbuf, nib) != nib) { 709 wrerror(); 710 } 711 cntch += nib; 712 } 713 714 /* 715 * A write error has occurred; if the file being written was 716 * the edited file then we consider it to have changed since it is 717 * now likely scrambled. 718 */ 719 wrerror() 720 { 721 722 if (eq(file, savedfile) && edited) 723 change(); 724 syserror(); 725 } 726 727 /* 728 * Source command, handles nested sources. 729 * Traps errors since it mungs unit 0 during the source. 730 */ 731 short slevel; 732 short ttyindes; 733 734 source(fil, okfail) 735 char *fil; 736 bool okfail; 737 { 738 jmp_buf osetexit; 739 register int saveinp, ointty, oerrno; 740 char savepeekc, *saveglobp; 741 742 signal(SIGINT, SIG_IGN); 743 saveinp = dup(0); 744 savepeekc = peekc; 745 saveglobp = globp; 746 peekc = 0; globp = 0; 747 if (saveinp < 0) 748 error("Too many nested sources"); 749 if (slevel <= 0) 750 ttyindes = saveinp; 751 close(0); 752 if (open(fil, 0) < 0) { 753 oerrno = errno; 754 setrupt(); 755 dup(saveinp); 756 close(saveinp); 757 errno = oerrno; 758 if (!okfail) 759 filioerr(fil); 760 return; 761 } 762 slevel++; 763 ointty = intty; 764 intty = isatty(0); 765 oprompt = value(PROMPT); 766 value(PROMPT) &= intty; 767 getexit(osetexit); 768 setrupt(); 769 if (setexit() == 0) 770 commands(1, 1); 771 else if (slevel > 1) { 772 close(0); 773 dup(saveinp); 774 close(saveinp); 775 slevel--; 776 resexit(osetexit); 777 reset(); 778 } 779 intty = ointty; 780 value(PROMPT) = oprompt; 781 close(0); 782 dup(saveinp); 783 close(saveinp); 784 globp = saveglobp; 785 peekc = savepeekc; 786 slevel--; 787 resexit(osetexit); 788 } 789 790 /* 791 * Clear io statistics before a read or write. 792 */ 793 clrstats() 794 { 795 796 ninbuf = 0; 797 cntch = 0; 798 cntln = 0; 799 cntnull = 0; 800 cntodd = 0; 801 } 802 803 /* 804 * Io is finished, close the unit and print statistics. 805 */ 806 iostats() 807 { 808 809 close(io); 810 io = -1; 811 if (hush == 0) { 812 if (value(TERSE)) 813 printf(" %d/%D", cntln, cntch); 814 else 815 printf(" %d line%s, %D character%s", cntln, plural((long) cntln), 816 cntch, plural(cntch)); 817 if (cntnull || cntodd) { 818 printf(" ("); 819 if (cntnull) { 820 printf("%D null", cntnull); 821 if (cntodd) 822 printf(", "); 823 } 824 if (cntodd) 825 printf("%D non-ASCII", cntodd); 826 putchar(')'); 827 } 828 noonl(); 829 flush(); 830 } 831 return (cntnull != 0 || cntodd != 0); 832 } 833