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