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
filename(int comm)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
getargs(void)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
glob(struct glob * gp)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
gscan(void)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
getone(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
rop(int c)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
rop2(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
rop3(int c)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
samei(struct stat64 * sp,unsigned char * cp)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
wop(dofname)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
edfile(void)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
getfile(void)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
putfile(int isfilter)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
wrerror(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
source(fil,okfail)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
clrstats(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
iostats(void)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
chkmdln(aline)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