1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. 24 * Copyright (c) 2016 by Delphix. All rights reserved. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 31 /* Copyright (c) 1981 Regents of the University of California */ 32 33 #include "ex.h" 34 #include "ex_argv.h" 35 #include "ex_temp.h" 36 #include "ex_tty.h" 37 #include "ex_vis.h" 38 #include <stdlib.h> 39 #include <unistd.h> 40 41 /* 42 * File input/output, source, preserve and recover 43 */ 44 45 /* 46 * Following remember where . was in the previous file for return 47 * on file switching. 48 */ 49 int altdot; 50 int oldadot; 51 bool wasalt; 52 short isalt; 53 54 long cntch; /* Count of characters on unit io */ 55 #ifndef VMUNIX 56 short cntln; /* Count of lines " */ 57 #else 58 int cntln; 59 #endif 60 long cntnull; /* Count of nulls " */ 61 long cntodd; /* Count of non-ascii characters " */ 62 63 static void chkmdln(); 64 extern int getchar(); 65 66 /* 67 * Parse file name for command encoded by comm. 68 * If comm is E then command is doomed and we are 69 * parsing just so user won't have to retype the name. 70 */ 71 void 72 filename(int comm) 73 { 74 int c = comm, d; 75 int i; 76 77 d = getchar(); 78 if (endcmd(d)) { 79 if (savedfile[0] == 0 && comm != 'f') 80 error(value(vi_TERSE) ? gettext("No file") : 81 gettext("No current filename")); 82 CP(file, savedfile); 83 wasalt = (isalt > 0) ? isalt-1 : 0; 84 isalt = 0; 85 oldadot = altdot; 86 if (c == 'e' || c == 'E') 87 altdot = lineDOT(); 88 if (d == EOF) 89 ungetchar(d); 90 } else { 91 ungetchar(d); 92 getone(); 93 eol(); 94 if (savedfile[0] == 0 && c != 'E' && c != 'e') { 95 c = 'e'; 96 edited = 0; 97 } 98 wasalt = strcmp(file, altfile) == 0; 99 oldadot = altdot; 100 switch (c) { 101 102 case 'f': 103 edited = 0; 104 /* FALLTHROUGH */ 105 106 case 'e': 107 if (savedfile[0]) { 108 altdot = lineDOT(); 109 CP(altfile, savedfile); 110 } 111 CP(savedfile, file); 112 break; 113 114 default: 115 if (file[0]) { 116 if (c != 'E') 117 altdot = lineDOT(); 118 CP(altfile, file); 119 } 120 break; 121 } 122 } 123 if (hush && comm != 'f' || comm == 'E') 124 return; 125 if (file[0] != 0) { 126 lprintf("\"%s\"", file); 127 if (comm == 'f') { 128 if (value(vi_READONLY)) 129 viprintf(gettext(" [Read only]")); 130 if (!edited) 131 viprintf(gettext(" [Not edited]")); 132 if (tchng) 133 viprintf(gettext(" [Modified]")); 134 } 135 flush(); 136 } else 137 viprintf(gettext("No file ")); 138 if (comm == 'f') { 139 if (!(i = lineDOL())) 140 i++; 141 /* 142 * TRANSLATION_NOTE 143 * Reference order of arguments must not 144 * be changed using '%digit$', since vi's 145 * viprintf() does not support it. 146 */ 147 viprintf(gettext(" line %d of %d --%ld%%--"), lineDOT(), 148 lineDOL(), (long)(100 * lineDOT() / i)); 149 } 150 } 151 152 /* 153 * Get the argument words for a command into genbuf 154 * expanding # and %. 155 */ 156 int 157 getargs(void) 158 { 159 int c; 160 unsigned char *cp, *fp; 161 static unsigned char fpatbuf[32]; /* hence limit on :next +/pat */ 162 char multic[MB_LEN_MAX + 1]; 163 int len; 164 wchar_t wc; 165 166 pastwh(); 167 if (peekchar() == '+') { 168 for (cp = fpatbuf;;) { 169 if (!isascii(c = peekchar()) && (c != EOF)) { 170 if ((len = _mbftowc(multic, &wc, getchar, &peekc)) > 0) { 171 if ((cp + len) >= &fpatbuf[sizeof(fpatbuf)]) 172 error(gettext("Pattern too long")); 173 strncpy(cp, multic, len); 174 cp += len; 175 continue; 176 } 177 } 178 179 c = getchar(); 180 *cp++ = c; 181 if (cp >= &fpatbuf[sizeof(fpatbuf)]) 182 error(gettext("Pattern too long")); 183 if (c == '\\' && isspace(peekchar())) 184 c = getchar(); 185 if (c == EOF || isspace(c)) { 186 ungetchar(c); 187 *--cp = 0; 188 firstpat = &fpatbuf[1]; 189 break; 190 } 191 } 192 } 193 if (skipend()) 194 return (0); 195 CP(genbuf, "echo "); cp = &genbuf[5]; 196 for (;;) { 197 if (!isascii(c = peekchar())) { 198 if (endcmd(c) && c != '"') 199 break; 200 if ((len = _mbftowc(multic, &wc, getchar, &peekc)) > 0) { 201 if ((cp + len) > &genbuf[LBSIZE - 2]) 202 error(gettext("Argument buffer overflow")); 203 strncpy(cp, multic, len); 204 cp += len; 205 continue; 206 } 207 } 208 209 if (endcmd(c) && c != '"') 210 break; 211 212 c = getchar(); 213 switch (c) { 214 215 case '\\': 216 if (any(peekchar(), "#%|")) 217 c = getchar(); 218 /* FALLTHROUGH */ 219 220 default: 221 if (cp > &genbuf[LBSIZE - 2]) 222 flong: 223 error(gettext("Argument buffer overflow")); 224 *cp++ = c; 225 break; 226 227 case '#': 228 fp = (unsigned char *)altfile; 229 if (*fp == 0) 230 error(value(vi_TERSE) ? 231 gettext("No alternate filename") : 232 gettext("No alternate filename to substitute for #")); 233 goto filexp; 234 235 case '%': 236 fp = savedfile; 237 if (*fp == 0) 238 error(value(vi_TERSE) ? 239 gettext("No current filename") : 240 gettext("No current filename to substitute for %%")); 241 filexp: 242 while (*fp) { 243 if (cp > &genbuf[LBSIZE - 2]) 244 goto flong; 245 *cp++ = *fp++; 246 } 247 break; 248 } 249 } 250 *cp = 0; 251 return (1); 252 } 253 254 /* 255 * Glob the argument words in genbuf, or if no globbing 256 * is implied, just split them up directly. 257 */ 258 void 259 glob(struct glob *gp) 260 { 261 int pvec[2]; 262 unsigned char **argv = gp->argv; 263 unsigned char *cp = gp->argspac; 264 int c; 265 unsigned char ch; 266 int nleft = NCARGS; 267 268 gp->argc0 = 0; 269 if (gscan() == 0) { 270 unsigned char *v = genbuf + 5; /* strlen("echo ") */ 271 272 for (;;) { 273 while (isspace(*v)) 274 v++; 275 if (!*v) 276 break; 277 *argv++ = cp; 278 while (*v && !isspace(*v)) 279 *cp++ = *v++; 280 *cp++ = 0; 281 gp->argc0++; 282 } 283 *argv = 0; 284 return; 285 } 286 if (pipe(pvec) < 0) 287 error(gettext("Can't make pipe to glob")); 288 pid = fork(); 289 io = pvec[0]; 290 if (pid < 0) { 291 close(pvec[1]); 292 error(gettext("Can't fork to do glob")); 293 } 294 if (pid == 0) { 295 int oerrno; 296 297 close(1); 298 dup(pvec[1]); 299 close(pvec[0]); 300 close(2); /* so errors don't mess up the screen */ 301 open("/dev/null", 1); 302 execlp((char *)svalue(vi_SHELL), "sh", "-c", genbuf, (char *)0); 303 oerrno = errno; close(1); dup(2); errno = oerrno; 304 filioerr(svalue(vi_SHELL)); 305 } 306 close(pvec[1]); 307 do { 308 *argv = cp; 309 for (;;) { 310 if (read(io, &ch, 1) != 1) { 311 close(io); 312 c = -1; 313 } else 314 c = ch; 315 if (c <= 0 || isspace(c)) 316 break; 317 *cp++ = c; 318 if (--nleft <= 0) 319 error(gettext("Arg list too long")); 320 } 321 if (cp != *argv) { 322 --nleft; 323 *cp++ = 0; 324 gp->argc0++; 325 if (gp->argc0 >= NARGS) 326 error(gettext("Arg list too long")); 327 argv++; 328 } 329 } while (c >= 0); 330 waitfor(); 331 if (gp->argc0 == 0) 332 error(gettext("No match")); 333 } 334 335 /* 336 * Scan genbuf for shell metacharacters. 337 * Set is union of v7 shell and csh metas. 338 */ 339 int 340 gscan(void) 341 { 342 unsigned char *cp; 343 int len; 344 345 for (cp = genbuf; *cp; cp += len) { 346 if (any(*cp, "~{[*?$`'\"\\")) 347 return (1); 348 if ((len = mblen((char *)cp, MB_CUR_MAX)) <= 0) 349 len = 1; 350 } 351 return (0); 352 } 353 354 /* 355 * Parse one filename into file. 356 */ 357 struct glob G; 358 void 359 getone(void) 360 { 361 unsigned char *str; 362 363 if (getargs() == 0) 364 error(gettext("Missing filename")); 365 glob(&G); 366 if (G.argc0 > 1) 367 error(value(vi_TERSE) ? gettext("Ambiguous") : 368 gettext("Too many file names")); 369 if (G.argc0 < 1) 370 error(gettext("Missing filename")); 371 str = G.argv[G.argc0 - 1]; 372 if (strlen(str) > FNSIZE - 4) 373 error(gettext("Filename too long")); 374 samef: 375 CP(file, str); 376 } 377 378 /* 379 * Read a file from the world. 380 * C is command, 'e' if this really an edit (or a recover). 381 */ 382 void 383 rop(int c) 384 { 385 int i; 386 struct stat64 stbuf; 387 short magic; 388 static int ovro; /* old value(vi_READONLY) */ 389 static int denied; /* 1 if READONLY was set due to file permissions */ 390 391 io = open(file, 0); 392 if (io < 0) { 393 if (c == 'e' && errno == ENOENT) { 394 edited++; 395 /* 396 * If the user just did "ex foo" they're probably 397 * creating a new file. Don't be an error, since 398 * this is ugly, and it messes up the + option. 399 */ 400 if (!seenprompt) { 401 viprintf(gettext(" [New file]")); 402 noonl(); 403 return; 404 } 405 } 406 407 if (value(vi_READONLY) && denied) { 408 value(vi_READONLY) = ovro; 409 denied = 0; 410 } 411 syserror(0); 412 } 413 if (fstat64(io, &stbuf)) 414 syserror(0); 415 switch (FTYPE(stbuf) & S_IFMT) { 416 417 case S_IFBLK: 418 error(gettext(" Block special file")); 419 /* FALLTHROUGH */ 420 421 case S_IFCHR: 422 if (isatty(io)) 423 error(gettext(" Teletype")); 424 if (samei(&stbuf, "/dev/null")) 425 break; 426 error(gettext(" Character special file")); 427 /* FALLTHROUGH */ 428 429 case S_IFDIR: 430 error(gettext(" Directory")); 431 432 } 433 if (c != 'r') { 434 if (value(vi_READONLY) && denied) { 435 value(vi_READONLY) = ovro; 436 denied = 0; 437 } 438 if ((FMODE(stbuf) & 0222) == 0 || access((char *)file, 2) < 0) { 439 ovro = value(vi_READONLY); 440 denied = 1; 441 value(vi_READONLY) = 1; 442 } 443 } 444 if (hush == 0 && value(vi_READONLY)) { 445 viprintf(gettext(" [Read only]")); 446 flush(); 447 } 448 if (c == 'r') 449 setdot(); 450 else 451 setall(); 452 453 /* If it is a read command, then we must set dot to addr1 454 * (value of N in :Nr ). In the default case, addr1 will 455 * already be set to dot. 456 * 457 * Next, it is necessary to mark the beginning (undap1) and 458 * ending (undap2) addresses affected (for undo). Note that 459 * rop2() and rop3() will adjust the value of undap2. 460 */ 461 if (FIXUNDO && inopen && c == 'r') { 462 dot = addr1; 463 undap1 = undap2 = dot + 1; 464 } 465 rop2(); 466 rop3(c); 467 } 468 469 void 470 rop2(void) 471 { 472 line *first, *last, *a; 473 474 deletenone(); 475 clrstats(); 476 first = addr2 + 1; 477 (void)append(getfile, addr2); 478 last = dot; 479 if (value(vi_MODELINES)) 480 for (a=first; a<=last; a++) { 481 if (a==first+5 && last-first > 10) 482 a = last - 4; 483 getaline(*a); 484 chkmdln(linebuf); 485 } 486 } 487 488 void 489 rop3(int c) 490 { 491 492 if (iostats() == 0 && c == 'e') 493 edited++; 494 if (c == 'e') { 495 if (wasalt || firstpat) { 496 line *addr = zero + oldadot; 497 498 if (addr > dol) 499 addr = dol; 500 if (firstpat) { 501 globp = (*firstpat) ? firstpat : (unsigned char *)"$"; 502 commands(1,1); 503 firstpat = 0; 504 } else if (addr >= one) { 505 if (inopen) 506 dot = addr; 507 markpr(addr); 508 } else 509 goto other; 510 } else 511 other: 512 if (dol > zero) { 513 if (inopen) 514 dot = one; 515 markpr(one); 516 } 517 if(FIXUNDO) 518 undkind = UNDNONE; 519 if (inopen) { 520 vcline = 0; 521 vreplace(0, lines, lineDOL()); 522 } 523 } 524 if (laste) { 525 #ifdef VMUNIX 526 tlaste(); 527 #endif 528 laste = 0; 529 sync(); 530 } 531 } 532 533 /* 534 * Are these two really the same inode? 535 */ 536 int 537 samei(struct stat64 *sp, unsigned char *cp) 538 { 539 struct stat64 stb; 540 541 if (stat64((char *)cp, &stb) < 0) 542 return (0); 543 return (IDENTICAL((*sp), stb)); 544 } 545 546 /* Returns from edited() */ 547 #define EDF 0 /* Edited file */ 548 #define NOTEDF -1 /* Not edited file */ 549 #define PARTBUF 1 /* Write of partial buffer to Edited file */ 550 551 /* 552 * Write a file. 553 */ 554 void 555 wop(dofname) 556 bool dofname; /* if 1 call filename, else use savedfile */ 557 { 558 int c, exclam, nonexist; 559 line *saddr1, *saddr2; 560 struct stat64 stbuf; 561 char *messagep; 562 563 c = 0; 564 exclam = 0; 565 if (dofname) { 566 if (peekchar() == '!') 567 exclam++, ignchar(); 568 (void)skipwh(); 569 while (peekchar() == '>') 570 ignchar(), c++, (void)skipwh(); 571 if (c != 0 && c != 2) 572 error(gettext("Write forms are 'w' and 'w>>'")); 573 filename('w'); 574 } else { 575 if (savedfile[0] == 0) 576 error(value(vi_TERSE) ? gettext("No file") : 577 gettext("No current filename")); 578 saddr1=addr1; 579 saddr2=addr2; 580 addr1=one; 581 addr2=dol; 582 CP(file, savedfile); 583 if (inopen) { 584 vclrech(0); 585 splitw++; 586 } 587 lprintf("\"%s\"", file); 588 } 589 nonexist = stat64((char *)file, &stbuf); 590 switch (c) { 591 592 case 0: 593 if (!exclam && (!value(vi_WRITEANY) || value(vi_READONLY))) 594 switch (edfile()) { 595 596 case NOTEDF: 597 if (nonexist) 598 break; 599 if (ISCHR(stbuf)) { 600 if (samei(&stbuf, (unsigned char *)"/dev/null")) 601 break; 602 if (samei(&stbuf, (unsigned char *)"/dev/tty")) 603 break; 604 } 605 io = open(file, 1); 606 if (io < 0) 607 syserror(0); 608 if (!isatty(io)) 609 serror(value(vi_TERSE) ? 610 (unsigned char *)gettext(" File exists") : 611 (unsigned char *)gettext(" File exists - use \"w! %s\" to overwrite"), 612 file); 613 close(io); 614 break; 615 616 case EDF: 617 if (value(vi_READONLY)) 618 error(gettext(" File is read only")); 619 break; 620 621 case PARTBUF: 622 if (value(vi_READONLY)) 623 error(gettext(" File is read only")); 624 error(gettext(" Use \"w!\" to write partial buffer")); 625 } 626 cre: 627 /* 628 synctmp(); 629 */ 630 io = creat(file, 0666); 631 if (io < 0) 632 syserror(0); 633 writing = 1; 634 if (hush == 0) 635 if (nonexist) 636 viprintf(gettext(" [New file]")); 637 else if (value(vi_WRITEANY) && edfile() != EDF) 638 viprintf(gettext(" [Existing file]")); 639 break; 640 641 case 2: 642 io = open(file, 1); 643 if (io < 0) { 644 if (exclam || value(vi_WRITEANY)) 645 goto cre; 646 syserror(0); 647 } 648 lseek(io, 0l, 2); 649 break; 650 } 651 if (write_quit && inopen && (argc == 0 || morargc == argc)) 652 setty(normf); 653 putfile(0); 654 if (fsync(io) < 0) { 655 /* 656 * For NFS files write in putfile doesn't return error, but 657 * fsync does. So, catch it here. 658 */ 659 messagep = (char *)gettext( 660 "\r\nYour file has been preserved\r\n"); 661 (void) preserve(); 662 write(1, messagep, strlen(messagep)); 663 664 wrerror(); 665 } 666 (void)iostats(); 667 if (c != 2 && addr1 == one && addr2 == dol) { 668 if (eq(file, savedfile)) 669 edited = 1; 670 sync(); 671 } 672 if (!dofname) { 673 addr1 = saddr1; 674 addr2 = saddr2; 675 } 676 writing = 0; 677 } 678 679 /* 680 * Is file the edited file? 681 * Work here is that it is not considered edited 682 * if this is a partial buffer, and distinguish 683 * all cases. 684 */ 685 int 686 edfile(void) 687 { 688 689 if (!edited || !eq(file, savedfile)) 690 return (NOTEDF); 691 return (addr1 == one && addr2 == dol ? EDF : PARTBUF); 692 } 693 694 /* 695 * Extract the next line from the io stream. 696 */ 697 unsigned char *nextip; 698 699 int 700 getfile(void) 701 { 702 short c; 703 unsigned char *lp; 704 unsigned char *fp; 705 706 lp = linebuf; 707 fp = nextip; 708 do { 709 if (--ninbuf < 0) { 710 ninbuf = read(io, genbuf, LBSIZE) - 1; 711 if (ninbuf < 0) { 712 if (lp != linebuf) { 713 lp++; 714 viprintf( 715 gettext(" [Incomplete last line]")); 716 break; 717 } 718 return (EOF); 719 } 720 if(crflag == -1) { 721 if(isencrypt(genbuf, ninbuf + 1)) 722 crflag = 2; 723 else 724 crflag = -2; 725 } 726 if (crflag > 0 && run_crypt(cntch, genbuf, ninbuf+1, perm) == -1) { 727 smerror(gettext("Cannot decrypt block of text\n")); 728 break; 729 } 730 fp = genbuf; 731 cntch += ninbuf+1; 732 } 733 if (lp >= &linebuf[LBSIZE]) { 734 error(gettext(" Line too long")); 735 } 736 c = *fp++; 737 if (c == 0) { 738 cntnull++; 739 continue; 740 } 741 *lp++ = c; 742 } while (c != '\n'); 743 *--lp = 0; 744 nextip = fp; 745 cntln++; 746 return (0); 747 } 748 749 /* 750 * Write a range onto the io stream. 751 */ 752 void 753 putfile(int isfilter) 754 { 755 line *a1; 756 unsigned char *lp; 757 unsigned char *fp; 758 int nib; 759 bool ochng = chng; 760 char *messagep; 761 762 chng = 1; /* set to force file recovery procedures in */ 763 /* the event of an interrupt during write */ 764 a1 = addr1; 765 clrstats(); 766 cntln = addr2 - a1 + 1; 767 nib = BUFSIZE; 768 fp = genbuf; 769 do { 770 getaline(*a1++); 771 lp = linebuf; 772 for (;;) { 773 if (--nib < 0) { 774 nib = fp - genbuf; 775 if(kflag && !isfilter) 776 if (run_crypt(cntch, genbuf, nib, perm) == -1) 777 wrerror(); 778 if (write(io, genbuf, nib) != nib) { 779 messagep = (char *)gettext( 780 "\r\nYour file has been preserved\r\n"); 781 (void) preserve(); 782 write(1, messagep, strlen(messagep)); 783 784 if (!isfilter) 785 wrerror(); 786 return; 787 } 788 cntch += nib; 789 nib = BUFSIZE - 1; 790 fp = genbuf; 791 } 792 if ((*fp++ = *lp++) == 0) { 793 fp[-1] = '\n'; 794 break; 795 } 796 } 797 } while (a1 <= addr2); 798 nib = fp - genbuf; 799 if(kflag && !isfilter) 800 if (run_crypt(cntch, genbuf, nib, perm) == -1) 801 wrerror(); 802 if ((cntch == 0) && (nib == 1)) { 803 cntln = 0; 804 return; 805 } 806 if (write(io, genbuf, nib) != nib) { 807 messagep = (char *)gettext( 808 "\r\nYour file has been preserved\r\n"); 809 (void) preserve(); 810 write(1, messagep, strlen(messagep)); 811 812 if(!isfilter) 813 wrerror(); 814 return; 815 } 816 cntch += nib; 817 chng = ochng; /* reset chng to original value */ 818 } 819 820 /* 821 * A write error has occurred; if the file being written was 822 * the edited file then we consider it to have changed since it is 823 * now likely scrambled. 824 */ 825 void 826 wrerror(void) 827 { 828 829 if (eq(file, savedfile) && edited) 830 change(); 831 syserror(1); 832 } 833 834 /* 835 * Source command, handles nested sources. 836 * Traps errors since it mungs unit 0 during the source. 837 */ 838 short slevel; 839 short ttyindes; 840 841 void 842 source(fil, okfail) 843 unsigned char *fil; 844 bool okfail; 845 { 846 jmp_buf osetexit; 847 int saveinp, ointty, oerrno; 848 unsigned char *saveglobp; 849 short savepeekc; 850 851 signal(SIGINT, SIG_IGN); 852 saveinp = dup(0); 853 savepeekc = peekc; 854 saveglobp = globp; 855 peekc = 0; globp = 0; 856 if (saveinp < 0) 857 error(gettext("Too many nested sources")); 858 if (slevel <= 0) 859 ttyindes = saveinp; 860 close(0); 861 if (open(fil, 0) < 0) { 862 oerrno = errno; 863 setrupt(); 864 dup(saveinp); 865 close(saveinp); 866 errno = oerrno; 867 if (!okfail) 868 filioerr(fil); 869 return; 870 } 871 slevel++; 872 ointty = intty; 873 intty = isatty(0); 874 oprompt = value(vi_PROMPT); 875 value(vi_PROMPT) &= intty; 876 getexit(osetexit); 877 setrupt(); 878 if (setexit() == 0) 879 commands(1, 1); 880 else if (slevel > 1) { 881 close(0); 882 dup(saveinp); 883 close(saveinp); 884 slevel--; 885 resexit(osetexit); 886 reset(); 887 } 888 intty = ointty; 889 value(vi_PROMPT) = oprompt; 890 close(0); 891 dup(saveinp); 892 close(saveinp); 893 globp = saveglobp; 894 peekc = savepeekc; 895 slevel--; 896 resexit(osetexit); 897 } 898 899 /* 900 * Clear io statistics before a read or write. 901 */ 902 void 903 clrstats(void) 904 { 905 906 ninbuf = 0; 907 cntch = 0; 908 cntln = 0; 909 cntnull = 0; 910 cntodd = 0; 911 } 912 913 /* 914 * Io is finished, close the unit and print statistics. 915 */ 916 int 917 iostats(void) 918 { 919 920 close(io); 921 io = -1; 922 if (hush == 0) { 923 if (value(vi_TERSE)) 924 viprintf(" %d/%D", cntln, cntch); 925 else if (cntln == 1 && cntch == 1) { 926 viprintf(gettext(" 1 line, 1 character")); 927 } else if (cntln == 1 && cntch != 1) { 928 viprintf(gettext(" 1 line, %D characters"), cntch); 929 } else if (cntln != 1 && cntch != 1) { 930 /* 931 * TRANSLATION_NOTE 932 * Reference order of arguments must not 933 * be changed using '%digit$', since vi's 934 * viprintf() does not support it. 935 */ 936 viprintf(gettext(" %d lines, %D characters"), cntln, 937 cntch); 938 } else { 939 /* ridiculous */ 940 viprintf(gettext(" %d lines, 1 character"), cntln); 941 } 942 if (cntnull || cntodd) { 943 viprintf(" ("); 944 if (cntnull) { 945 viprintf(gettext("%D null"), cntnull); 946 if (cntodd) 947 viprintf(", "); 948 } 949 if (cntodd) 950 viprintf(gettext("%D non-ASCII"), cntodd); 951 putchar(')'); 952 } 953 noonl(); 954 flush(); 955 } 956 return (cntnull != 0 || cntodd != 0); 957 } 958 959 960 static void chkmdln(aline) 961 unsigned char *aline; 962 { 963 unsigned char *beg, *end; 964 unsigned char cmdbuf[1024]; 965 char *strchr(), *strrchr(); 966 bool savetty; 967 int savepeekc; 968 int savechng; 969 unsigned char *savefirstpat; 970 unsigned char *p; 971 int len; 972 973 beg = (unsigned char *)strchr((char *)aline, ':'); 974 if (beg == NULL) 975 return; 976 if ((len = beg - aline) < 2) 977 return; 978 979 if ((beg - aline) != 2) { 980 if ((p = beg - ((unsigned int)MB_CUR_MAX * 2) - 2) < aline) 981 p = aline; 982 for ( ; p < (beg - 2); p += len) { 983 if ((len = mblen((char *)p, MB_CUR_MAX)) <= 0) 984 len = 1; 985 } 986 if (p != (beg - 2)) 987 return; 988 } 989 990 if (!((beg[-2] == 'e' && p[-1] == 'x') 991 || (beg[-2] == 'v' && beg[-1] == 'i'))) 992 return; 993 994 strncpy(cmdbuf, beg+1, sizeof cmdbuf); 995 end = (unsigned char *)strrchr(cmdbuf, ':'); 996 if (end == NULL) 997 return; 998 *end = 0; 999 globp = cmdbuf; 1000 savepeekc = peekc; 1001 peekc = 0; 1002 savetty = intty; 1003 intty = 0; 1004 savechng = chng; 1005 savefirstpat = firstpat; 1006 firstpat = (unsigned char *)""; 1007 commands(1, 1); 1008 peekc = savepeekc; 1009 globp = 0; 1010 intty = savetty; 1011 /* chng being increased indicates that text was changed */ 1012 if (savechng < chng) 1013 laste = 0; 1014 firstpat = savefirstpat; 1015 } 1016