xref: /illumos-gate/usr/src/cmd/vi/port/ex_io.c (revision 33efde4275d24731ef87927237b0ffb0630b6b2d)
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 	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
rop(int c)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" they're 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 		/* FALLTHROUGH */
419 
420 	case S_IFCHR:
421 		if (isatty(io))
422 			error(gettext(" Teletype"));
423 		if (samei(&stbuf, "/dev/null"))
424 			break;
425 		error(gettext(" Character special file"));
426 		/* FALLTHROUGH */
427 
428 	case S_IFDIR:
429 		error(gettext(" Directory"));
430 
431 	}
432 	if (c != 'r') {
433 		if (value(vi_READONLY) && denied) {
434 			value(vi_READONLY) = ovro;
435 			denied = 0;
436 		}
437 		if ((FMODE(stbuf) & 0222) == 0 || access((char *)file, 2) < 0) {
438 			ovro = value(vi_READONLY);
439 			denied = 1;
440 			value(vi_READONLY) = 1;
441 		}
442 	}
443 	if (hush == 0 && value(vi_READONLY)) {
444 		viprintf(gettext(" [Read only]"));
445 		flush();
446 	}
447 	if (c == 'r')
448 		setdot();
449 	else
450 		setall();
451 
452 	/* If it is a read command, then we must set dot to addr1
453 	 * (value of N in :Nr ).  In the default case, addr1 will
454 	 * already be set to dot.
455 	 *
456 	 * Next, it is necessary to mark the beginning (undap1) and
457 	 * ending (undap2) addresses affected (for undo).  Note that
458 	 * rop2() and rop3() will adjust the value of undap2.
459 	 */
460 	if (FIXUNDO && inopen && c == 'r') {
461 		dot = addr1;
462 		undap1 = undap2 = dot + 1;
463 	}
464 	rop2();
465 	rop3(c);
466 }
467 
468 void
rop2(void)469 rop2(void)
470 {
471 	line *first, *last, *a;
472 
473 	deletenone();
474 	clrstats();
475 	first = addr2 + 1;
476 	(void)append(getfile, addr2);
477 	last = dot;
478 	if (value(vi_MODELINES))
479 		for (a=first; a<=last; a++) {
480 			if (a==first+5 && last-first > 10)
481 				a = last - 4;
482 			getaline(*a);
483 				chkmdln(linebuf);
484 		}
485 }
486 
487 void
rop3(int c)488 rop3(int c)
489 {
490 
491 	if (iostats() == 0 && c == 'e')
492 		edited++;
493 	if (c == 'e') {
494 		if (wasalt || firstpat) {
495 			line *addr = zero + oldadot;
496 
497 			if (addr > dol)
498 				addr = dol;
499 			if (firstpat) {
500 				globp = (*firstpat) ? firstpat : (unsigned char *)"$";
501 				commands(1,1);
502 				firstpat = 0;
503 			} else if (addr >= one) {
504 				if (inopen)
505 					dot = addr;
506 				markpr(addr);
507 			} else
508 				goto other;
509 		} else
510 other:
511 			if (dol > zero) {
512 				if (inopen)
513 					dot = one;
514 				markpr(one);
515 			}
516 		if(FIXUNDO)
517 			undkind = UNDNONE;
518 		if (inopen) {
519 			vcline = 0;
520 			vreplace(0, lines, lineDOL());
521 		}
522 	}
523 	if (laste) {
524 #ifdef VMUNIX
525 		tlaste();
526 #endif
527 		laste = 0;
528 		sync();
529 	}
530 }
531 
532 /*
533  * Are these two really the same inode?
534  */
535 int
samei(struct stat64 * sp,unsigned char * cp)536 samei(struct stat64 *sp, unsigned char *cp)
537 {
538 	struct stat64 stb;
539 
540 	if (stat64((char *)cp, &stb) < 0)
541 		return (0);
542 	return (IDENTICAL((*sp), stb));
543 }
544 
545 /* Returns from edited() */
546 #define	EDF	0		/* Edited file */
547 #define	NOTEDF	-1		/* Not edited file */
548 #define	PARTBUF	1		/* Write of partial buffer to Edited file */
549 
550 /*
551  * Write a file.
552  */
553 void
wop(dofname)554 wop(dofname)
555 bool dofname;	/* if 1 call filename, else use savedfile */
556 {
557 	int c, exclam, nonexist;
558 	line *saddr1, *saddr2;
559 	struct stat64 stbuf;
560 	char *messagep;
561 
562 	c = 0;
563 	exclam = 0;
564 	if (dofname) {
565 		if (peekchar() == '!')
566 			exclam++, ignchar();
567 		(void)skipwh();
568 		while (peekchar() == '>')
569 			ignchar(), c++, (void)skipwh();
570 		if (c != 0 && c != 2)
571 			error(gettext("Write forms are 'w' and 'w>>'"));
572 		filename('w');
573 	} else {
574 		if (savedfile[0] == 0)
575 			error(value(vi_TERSE) ? gettext("No file") :
576 gettext("No current filename"));
577 		saddr1=addr1;
578 		saddr2=addr2;
579 		addr1=one;
580 		addr2=dol;
581 		CP(file, savedfile);
582 		if (inopen) {
583 			vclrech(0);
584 			splitw++;
585 		}
586 		lprintf("\"%s\"", file);
587 	}
588 	nonexist = stat64((char *)file, &stbuf);
589 	switch (c) {
590 
591 	case 0:
592 		if (!exclam && (!value(vi_WRITEANY) || value(vi_READONLY)))
593 		switch (edfile()) {
594 
595 		case NOTEDF:
596 			if (nonexist)
597 				break;
598 			if (ISCHR(stbuf)) {
599 				if (samei(&stbuf, (unsigned char *)"/dev/null"))
600 					break;
601 				if (samei(&stbuf, (unsigned char *)"/dev/tty"))
602 					break;
603 			}
604 			io = open(file, 1);
605 			if (io < 0)
606 				syserror(0);
607 			if (!isatty(io))
608 				serror(value(vi_TERSE) ?
609 				    (unsigned char *)gettext(" File exists") :
610 (unsigned char *)gettext(" File exists - use \"w! %s\" to overwrite"),
611 				    file);
612 			close(io);
613 			break;
614 
615 		case EDF:
616 			if (value(vi_READONLY))
617 				error(gettext(" File is read only"));
618 			break;
619 
620 		case PARTBUF:
621 			if (value(vi_READONLY))
622 				error(gettext(" File is read only"));
623 			error(gettext(" Use \"w!\" to write partial buffer"));
624 		}
625 cre:
626 /*
627 		synctmp();
628 */
629 		io = creat(file, 0666);
630 		if (io < 0)
631 			syserror(0);
632 		writing = 1;
633 		if (hush == 0)
634 			if (nonexist)
635 				viprintf(gettext(" [New file]"));
636 			else if (value(vi_WRITEANY) && edfile() != EDF)
637 				viprintf(gettext(" [Existing file]"));
638 		break;
639 
640 	case 2:
641 		io = open(file, 1);
642 		if (io < 0) {
643 			if (exclam || value(vi_WRITEANY))
644 				goto cre;
645 			syserror(0);
646 		}
647 		lseek(io, 0l, 2);
648 		break;
649 	}
650 	if (write_quit && inopen && (argc == 0 || morargc == argc))
651 		setty(normf);
652 	putfile(0);
653 	if (fsync(io) < 0) {
654 		/*
655 		 * For NFS files write in putfile doesn't return error, but
656 		 * fsync does.  So, catch it here.
657 		 */
658 		messagep = (char *)gettext(
659 		    "\r\nYour file has been preserved\r\n");
660 		(void) preserve();
661 		write(1, messagep, strlen(messagep));
662 
663 		wrerror();
664 	}
665 	(void)iostats();
666 	if (c != 2 && addr1 == one && addr2 == dol) {
667 		if (eq(file, savedfile))
668 			edited = 1;
669 		sync();
670 	}
671 	if (!dofname) {
672 		addr1 = saddr1;
673 		addr2 = saddr2;
674 	}
675 	writing = 0;
676 }
677 
678 /*
679  * Is file the edited file?
680  * Work here is that it is not considered edited
681  * if this is a partial buffer, and distinguish
682  * all cases.
683  */
684 int
edfile(void)685 edfile(void)
686 {
687 
688 	if (!edited || !eq(file, savedfile))
689 		return (NOTEDF);
690 	return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
691 }
692 
693 /*
694  * Extract the next line from the io stream.
695  */
696 unsigned char *nextip;
697 
698 int
getfile(void)699 getfile(void)
700 {
701 	short c;
702 	unsigned char *lp;
703 	unsigned char *fp;
704 
705 	lp = linebuf;
706 	fp = nextip;
707 	do {
708 		if (--ninbuf < 0) {
709 			ninbuf = read(io, genbuf, LBSIZE) - 1;
710 			if (ninbuf < 0) {
711 				if (lp != linebuf) {
712 					lp++;
713 					viprintf(
714 					    gettext(" [Incomplete last line]"));
715 					break;
716 				}
717 				return (EOF);
718 			}
719 			if(crflag == -1) {
720 				if(isencrypt(genbuf, ninbuf + 1))
721 					crflag = 2;
722 				else
723 					crflag = -2;
724 			}
725 			if (crflag > 0 && run_crypt(cntch, genbuf, ninbuf+1, perm) == -1) {
726 					smerror(gettext("Cannot decrypt block of text\n"));
727 					break;
728 			}
729 			fp = genbuf;
730 			cntch += ninbuf+1;
731 		}
732 		if (lp >= &linebuf[LBSIZE]) {
733 			error(gettext(" Line too long"));
734 		}
735 		c = *fp++;
736 		if (c == 0) {
737 			cntnull++;
738 			continue;
739 		}
740 		*lp++ = c;
741 	} while (c != '\n');
742 	*--lp = 0;
743 	nextip = fp;
744 	cntln++;
745 	return (0);
746 }
747 
748 /*
749  * Write a range onto the io stream.
750  */
751 void
putfile(int isfilter)752 putfile(int isfilter)
753 {
754 	line *a1;
755 	unsigned char *lp;
756 	unsigned char *fp;
757 	int nib;
758 	bool ochng = chng;
759 	char *messagep;
760 
761 	chng = 1;		/* set to force file recovery procedures in */
762 				/* the event of an interrupt during write   */
763 	a1 = addr1;
764 	clrstats();
765 	cntln = addr2 - a1 + 1;
766 	nib = BUFSIZE;
767 	fp = genbuf;
768 	do {
769 		getaline(*a1++);
770 		lp = linebuf;
771 		for (;;) {
772 			if (--nib < 0) {
773 				nib = fp - genbuf;
774                 		if(kflag && !isfilter)
775                                         if (run_crypt(cntch, genbuf, nib, perm) == -1)
776 					  wrerror();
777 				if (write(io, genbuf, nib) != nib) {
778 				    messagep = (char *)gettext(
779 					"\r\nYour file has been preserved\r\n");
780 				    (void) preserve();
781 				    write(1, messagep, strlen(messagep));
782 
783 				    if (!isfilter)
784 					wrerror();
785 				    return;
786 				}
787 				cntch += nib;
788 				nib = BUFSIZE - 1;
789 				fp = genbuf;
790 			}
791 			if ((*fp++ = *lp++) == 0) {
792 				fp[-1] = '\n';
793 				break;
794 			}
795 		}
796 	} while (a1 <= addr2);
797 	nib = fp - genbuf;
798 	if(kflag && !isfilter)
799 		if (run_crypt(cntch, genbuf, nib, perm) == -1)
800 			wrerror();
801 	if ((cntch == 0) && (nib == 1)) {
802 		cntln = 0;
803 		return;
804 	}
805 	if (write(io, genbuf, nib) != nib) {
806 		messagep = (char *)gettext(
807 		    "\r\nYour file has been preserved\r\n");
808 		(void) preserve();
809 		write(1, messagep, strlen(messagep));
810 
811 		if(!isfilter)
812 			wrerror();
813 		return;
814 	}
815 	cntch += nib;
816 	chng = ochng;			/* reset chng to original value */
817 }
818 
819 /*
820  * A write error has occurred;  if the file being written was
821  * the edited file then we consider it to have changed since it is
822  * now likely scrambled.
823  */
824 void
wrerror(void)825 wrerror(void)
826 {
827 
828 	if (eq(file, savedfile) && edited)
829 		change();
830 	syserror(1);
831 }
832 
833 /*
834  * Source command, handles nested sources.
835  * Traps errors since it mungs unit 0 during the source.
836  */
837 short slevel;
838 short ttyindes;
839 
840 void
source(fil,okfail)841 source(fil, okfail)
842 	unsigned char *fil;
843 	bool okfail;
844 {
845 	jmp_buf osetexit;
846 	int saveinp, ointty, oerrno;
847 	unsigned char *saveglobp;
848 	short savepeekc;
849 
850 	signal(SIGINT, SIG_IGN);
851 	saveinp = dup(0);
852 	savepeekc = peekc;
853 	saveglobp = globp;
854 	peekc = 0; globp = 0;
855 	if (saveinp < 0)
856 		error(gettext("Too many nested sources"));
857 	if (slevel <= 0)
858 		ttyindes = saveinp;
859 	close(0);
860 	if (open(fil, 0) < 0) {
861 		oerrno = errno;
862 		setrupt();
863 		dup(saveinp);
864 		close(saveinp);
865 		errno = oerrno;
866 		if (!okfail)
867 			filioerr(fil);
868 		return;
869 	}
870 	slevel++;
871 	ointty = intty;
872 	intty = isatty(0);
873 	oprompt = value(vi_PROMPT);
874 	value(vi_PROMPT) &= intty;
875 	getexit(osetexit);
876 	setrupt();
877 	if (setexit() == 0)
878 		commands(1, 1);
879 	else if (slevel > 1) {
880 		close(0);
881 		dup(saveinp);
882 		close(saveinp);
883 		slevel--;
884 		resexit(osetexit);
885 		reset();
886 	}
887 	intty = ointty;
888 	value(vi_PROMPT) = oprompt;
889 	close(0);
890 	dup(saveinp);
891 	close(saveinp);
892 	globp = saveglobp;
893 	peekc = savepeekc;
894 	slevel--;
895 	resexit(osetexit);
896 }
897 
898 /*
899  * Clear io statistics before a read or write.
900  */
901 void
clrstats(void)902 clrstats(void)
903 {
904 
905 	ninbuf = 0;
906 	cntch = 0;
907 	cntln = 0;
908 	cntnull = 0;
909 	cntodd = 0;
910 }
911 
912 /*
913  * Io is finished, close the unit and print statistics.
914  */
915 int
iostats(void)916 iostats(void)
917 {
918 
919 	close(io);
920 	io = -1;
921 	if (hush == 0) {
922 		if (value(vi_TERSE))
923 			viprintf(" %d/%D", cntln, cntch);
924 		else if (cntln == 1 && cntch == 1) {
925 			viprintf(gettext(" 1 line, 1 character"));
926 		} else if (cntln == 1 && cntch != 1) {
927 			viprintf(gettext(" 1 line, %D characters"), cntch);
928 		} else if (cntln != 1 && cntch != 1) {
929 			/*
930 			 * TRANSLATION_NOTE
931 			 *	Reference order of arguments must not
932 			 *	be changed using '%digit$', since vi's
933 			 *	viprintf() does not support it.
934 			 */
935 			viprintf(gettext(" %d lines, %D characters"), cntln,
936 			    cntch);
937 		} else {
938 			/* ridiculous */
939 			viprintf(gettext(" %d lines, 1 character"), cntln);
940 		}
941 		if (cntnull || cntodd) {
942 			viprintf(" (");
943 			if (cntnull) {
944 				viprintf(gettext("%D null"), cntnull);
945 				if (cntodd)
946 					viprintf(", ");
947 			}
948 			if (cntodd)
949 				viprintf(gettext("%D non-ASCII"), cntodd);
950 			putchar(')');
951 		}
952 		noonl();
953 		flush();
954 	}
955 	return (cntnull != 0 || cntodd != 0);
956 }
957 
958 
chkmdln(aline)959 static void chkmdln(aline)
960 unsigned char *aline;
961 {
962 	unsigned char *beg, *end;
963 	unsigned char cmdbuf[1024];
964 	char *strchr(), *strrchr();
965 	bool savetty;
966 	int  savepeekc;
967 	int  savechng;
968 	unsigned char	*savefirstpat;
969 	unsigned char	*p;
970 	int	len;
971 
972 	beg = (unsigned char *)strchr((char *)aline, ':');
973 	if (beg == NULL)
974 		return;
975 	if ((len = beg - aline) < 2)
976 		return;
977 
978 	if ((beg - aline) != 2) {
979 		if ((p = beg - ((unsigned int)MB_CUR_MAX * 2) - 2) < aline)
980 			p = aline;
981 		for ( ; p < (beg - 2); p += len) {
982 			if ((len = mblen((char *)p, MB_CUR_MAX)) <= 0)
983 				len = 1;
984 		}
985 		if (p !=  (beg - 2))
986 			return;
987 	}
988 
989 	if (!((beg[-2] == 'e' && p[-1] == 'x')
990 	||    (beg[-2] == 'v' && beg[-1] == 'i')))
991 	 	return;
992 
993 	strncpy(cmdbuf, beg+1, sizeof cmdbuf);
994 	end = (unsigned char *)strrchr(cmdbuf, ':');
995 	if (end == NULL)
996 		return;
997 	*end = 0;
998 	globp = cmdbuf;
999 	savepeekc = peekc;
1000 	peekc = 0;
1001 	savetty = intty;
1002 	intty = 0;
1003 	savechng = chng;
1004 	savefirstpat = firstpat;
1005 	firstpat = (unsigned char *)"";
1006 	commands(1, 1);
1007 	peekc = savepeekc;
1008 	globp = 0;
1009 	intty = savetty;
1010 	/* chng being increased indicates that text was changed */
1011 	if (savechng < chng)
1012 		laste = 0;
1013 	firstpat = savefirstpat;
1014 }
1015