xref: /illumos-gate/usr/src/cmd/vi/port/ex_io.c (revision dbed73cbda2229fd1aa6dc5743993cae7f0a7ee9)
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 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
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 #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 #include <unistd.h>
42 
43 /*
44  * File input/output, source, preserve and recover
45  */
46 
47 /*
48  * Following remember where . was in the previous file for return
49  * on file switching.
50  */
51 int	altdot;
52 int	oldadot;
53 bool	wasalt;
54 short	isalt;
55 
56 long	cntch;			/* Count of characters on unit io */
57 #ifndef VMUNIX
58 short	cntln;			/* Count of lines " */
59 #else
60 int	cntln;
61 #endif
62 long	cntnull;		/* Count of nulls " */
63 long	cntodd;			/* Count of non-ascii characters " */
64 
65 static void chkmdln();
66 extern int	getchar();
67 
68 /*
69  * Parse file name for command encoded by comm.
70  * If comm is E then command is doomed and we are
71  * parsing just so user won't have to retype the name.
72  */
73 void
74 filename(int comm)
75 {
76 	int c = comm, d;
77 	int i;
78 
79 	d = getchar();
80 	if (endcmd(d)) {
81 		if (savedfile[0] == 0 && comm != 'f')
82 			error(value(vi_TERSE) ? gettext("No file") :
83 gettext("No current filename"));
84 		CP(file, savedfile);
85 		wasalt = (isalt > 0) ? isalt-1 : 0;
86 		isalt = 0;
87 		oldadot = altdot;
88 		if (c == 'e' || c == 'E')
89 			altdot = lineDOT();
90 		if (d == EOF)
91 			ungetchar(d);
92 	} else {
93 		ungetchar(d);
94 		getone();
95 		eol();
96 		if (savedfile[0] == 0 && c != 'E' && c != 'e') {
97 			c = 'e';
98 			edited = 0;
99 		}
100 		wasalt = strcmp(file, altfile) == 0;
101 		oldadot = altdot;
102 		switch (c) {
103 
104 		case 'f':
105 			edited = 0;
106 			/* fall into ... */
107 
108 		case 'e':
109 			if (savedfile[0]) {
110 				altdot = lineDOT();
111 				CP(altfile, savedfile);
112 			}
113 			CP(savedfile, file);
114 			break;
115 
116 		default:
117 			if (file[0]) {
118 				if (c != 'E')
119 					altdot = lineDOT();
120 				CP(altfile, file);
121 			}
122 			break;
123 		}
124 	}
125 	if (hush && comm != 'f' || comm == 'E')
126 		return;
127 	if (file[0] != 0) {
128 		lprintf("\"%s\"", file);
129 		if (comm == 'f') {
130 			if (value(vi_READONLY))
131 				viprintf(gettext(" [Read only]"));
132 			if (!edited)
133 				viprintf(gettext(" [Not edited]"));
134 			if (tchng)
135 				viprintf(gettext(" [Modified]"));
136 		}
137 		flush();
138 	} else
139 		viprintf(gettext("No file "));
140 	if (comm == 'f') {
141 		if (!(i = lineDOL()))
142 			i++;
143 		/*
144 		 * TRANSLATION_NOTE
145 		 *	Reference order of arguments must not
146 		 *	be changed using '%digit$', since vi's
147 		 *	viprintf() does not support it.
148 		 */
149 		viprintf(gettext(" line %d of %d --%ld%%--"), lineDOT(),
150 		    lineDOL(), (long)(100 * lineDOT() / i));
151 	}
152 }
153 
154 /*
155  * Get the argument words for a command into genbuf
156  * expanding # and %.
157  */
158 int
159 getargs(void)
160 {
161 	int c;
162 	unsigned char *cp, *fp;
163 	static unsigned char fpatbuf[32];	/* hence limit on :next +/pat */
164 	char	multic[MB_LEN_MAX + 1];
165 	int	len;
166 	wchar_t	wc;
167 
168 	pastwh();
169 	if (peekchar() == '+') {
170 		for (cp = fpatbuf;;) {
171 			if (!isascii(c = peekchar()) && (c != EOF)) {
172 				if ((len = _mbftowc(multic, &wc, getchar, &peekc)) > 0) {
173 					if ((cp + len) >= &fpatbuf[sizeof(fpatbuf)])
174 						error(gettext("Pattern too long"));
175 					strncpy(cp, multic, len);
176 					cp += len;
177 					continue;
178 				}
179 			}
180 
181 			c = getchar();
182 			*cp++ = c;
183 			if (cp >= &fpatbuf[sizeof(fpatbuf)])
184 				error(gettext("Pattern too long"));
185 			if (c == '\\' && isspace(peekchar()))
186 				c = getchar();
187 			if (c == EOF || isspace(c)) {
188 				ungetchar(c);
189 				*--cp = 0;
190 				firstpat = &fpatbuf[1];
191 				break;
192 			}
193 		}
194 	}
195 	if (skipend())
196 		return (0);
197 	CP(genbuf, "echo "); cp = &genbuf[5];
198 	for (;;) {
199 		if (!isascii(c = peekchar())) {
200 			if (endcmd(c) && c != '"')
201 				break;
202 			if ((len = _mbftowc(multic, &wc, getchar, &peekc)) > 0) {
203 				if ((cp + len) > &genbuf[LBSIZE - 2])
204 					error(gettext("Argument buffer overflow"));
205 				strncpy(cp, multic, len);
206 				cp += len;
207 				continue;
208 			}
209 		}
210 
211 		if (endcmd(c) && c != '"')
212 			break;
213 
214 		c = getchar();
215 		switch (c) {
216 
217 		case '\\':
218 			if (any(peekchar(), "#%|"))
219 				c = getchar();
220 			/* fall into... */
221 
222 		default:
223 			if (cp > &genbuf[LBSIZE - 2])
224 flong:
225 				error(gettext("Argument buffer overflow"));
226 			*cp++ = c;
227 			break;
228 
229 		case '#':
230 			fp = (unsigned char *)altfile;
231 			if (*fp == 0)
232 				error(value(vi_TERSE) ?
233 gettext("No alternate filename") :
234 gettext("No alternate filename to substitute for #"));
235 			goto filexp;
236 
237 		case '%':
238 			fp = savedfile;
239 			if (*fp == 0)
240 				error(value(vi_TERSE) ?
241 gettext("No current filename") :
242 gettext("No current filename to substitute for %%"));
243 filexp:
244 			while (*fp) {
245 				if (cp > &genbuf[LBSIZE - 2])
246 					goto flong;
247 				*cp++ = *fp++;
248 			}
249 			break;
250 		}
251 	}
252 	*cp = 0;
253 	return (1);
254 }
255 
256 /*
257  * Glob the argument words in genbuf, or if no globbing
258  * is implied, just split them up directly.
259  */
260 void
261 glob(struct glob *gp)
262 {
263 	int pvec[2];
264 	unsigned char **argv = gp->argv;
265 	unsigned char *cp = gp->argspac;
266 	int c;
267 	unsigned char ch;
268 	int nleft = NCARGS;
269 
270 	gp->argc0 = 0;
271 	if (gscan() == 0) {
272 		unsigned char *v = genbuf + 5;		/* strlen("echo ") */
273 
274 		for (;;) {
275 			while (isspace(*v))
276 				v++;
277 			if (!*v)
278 				break;
279 			*argv++ = cp;
280 			while (*v && !isspace(*v))
281 				*cp++ = *v++;
282 			*cp++ = 0;
283 			gp->argc0++;
284 		}
285 		*argv = 0;
286 		return;
287 	}
288 	if (pipe(pvec) < 0)
289 		error(gettext("Can't make pipe to glob"));
290 	pid = fork();
291 	io = pvec[0];
292 	if (pid < 0) {
293 		close(pvec[1]);
294 		error(gettext("Can't fork to do glob"));
295 	}
296 	if (pid == 0) {
297 		int oerrno;
298 
299 		close(1);
300 		dup(pvec[1]);
301 		close(pvec[0]);
302 		close(2);	/* so errors don't mess up the screen */
303 		open("/dev/null", 1);
304 		execlp((char *)svalue(vi_SHELL), "sh", "-c", genbuf, (char *)0);
305 		oerrno = errno; close(1); dup(2); errno = oerrno;
306 		filioerr(svalue(vi_SHELL));
307 	}
308 	close(pvec[1]);
309 	do {
310 		*argv = cp;
311 		for (;;) {
312 			if (read(io, &ch, 1) != 1) {
313 				close(io);
314 				c = -1;
315 			} else
316 				c = ch;
317 			if (c <= 0 || isspace(c))
318 				break;
319 			*cp++ = c;
320 			if (--nleft <= 0)
321 				error(gettext("Arg list too long"));
322 		}
323 		if (cp != *argv) {
324 			--nleft;
325 			*cp++ = 0;
326 			gp->argc0++;
327 			if (gp->argc0 >= NARGS)
328 				error(gettext("Arg list too long"));
329 			argv++;
330 		}
331 	} while (c >= 0);
332 	waitfor();
333 	if (gp->argc0 == 0)
334 		error(gettext("No match"));
335 }
336 
337 /*
338  * Scan genbuf for shell metacharacters.
339  * Set is union of v7 shell and csh metas.
340  */
341 int
342 gscan(void)
343 {
344 	unsigned char *cp;
345 	int	len;
346 
347 	for (cp = genbuf; *cp; cp += len) {
348 		if (any(*cp, "~{[*?$`'\"\\"))
349 			return (1);
350 		if ((len = mblen((char *)cp, MB_CUR_MAX)) <= 0)
351 			len = 1;
352 	}
353 	return (0);
354 }
355 
356 /*
357  * Parse one filename into file.
358  */
359 struct glob G;
360 void
361 getone(void)
362 {
363 	unsigned char *str;
364 
365 	if (getargs() == 0)
366 		error(gettext("Missing filename"));
367 	glob(&G);
368 	if (G.argc0 > 1)
369 		error(value(vi_TERSE) ? gettext("Ambiguous") :
370 gettext("Too many file names"));
371 	if (G.argc0 < 1)
372 		error(gettext("Missing filename"));
373 	str = G.argv[G.argc0 - 1];
374 	if (strlen(str) > FNSIZE - 4)
375 		error(gettext("Filename too long"));
376 samef:
377 	CP(file, str);
378 }
379 
380 /*
381  * Read a file from the world.
382  * C is command, 'e' if this really an edit (or a recover).
383  */
384 void
385 rop(int c)
386 {
387 	int i;
388 	struct stat64 stbuf;
389 	short magic;
390 	static int ovro;	/* old value(vi_READONLY) */
391 	static int denied;	/* 1 if READONLY was set due to file permissions */
392 
393 	io = open(file, 0);
394 	if (io < 0) {
395 		if (c == 'e' && errno == ENOENT) {
396 			edited++;
397 			/*
398 			 * If the user just did "ex foo" he is probably
399 			 * creating a new file.  Don't be an error, since
400 			 * this is ugly, and it messes up the + option.
401 			 */
402 			if (!seenprompt) {
403 				viprintf(gettext(" [New file]"));
404 				noonl();
405 				return;
406 			}
407 		}
408 
409 		if (value(vi_READONLY) && denied) {
410 			value(vi_READONLY) = ovro;
411 			denied = 0;
412 		}
413 		syserror(0);
414 	}
415 	if (fstat64(io, &stbuf))
416 		syserror(0);
417 	switch (FTYPE(stbuf) & S_IFMT) {
418 
419 	case S_IFBLK:
420 		error(gettext(" Block special file"));
421 
422 	case S_IFCHR:
423 		if (isatty(io))
424 			error(gettext(" Teletype"));
425 		if (samei(&stbuf, "/dev/null"))
426 			break;
427 		error(gettext(" Character special file"));
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 			getline(*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 		getline(*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