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