xref: /original-bsd/usr.bin/ex/ex_io.c (revision e74403ba)
1 /* Copyright (c) 1981 Regents of the University of California */
2 static char *sccsid = "@(#)ex_io.c	7.7	04/17/84";
3 #include "ex.h"
4 #include "ex_argv.h"
5 #include "ex_temp.h"
6 #include "ex_tty.h"
7 #include "ex_vis.h"
8 
9 /*
10  * File input/output, source, preserve and recover
11  */
12 
13 /*
14  * Following remember where . was in the previous file for return
15  * on file switching.
16  */
17 int	altdot;
18 int	oldadot;
19 bool	wasalt;
20 short	isalt;
21 
22 long	cntch;			/* Count of characters on unit io */
23 #ifndef VMUNIX
24 short	cntln;			/* Count of lines " */
25 #else
26 int	cntln;
27 #endif
28 long	cntnull;		/* Count of nulls " */
29 long	cntodd;			/* Count of non-ascii characters " */
30 
31 /*
32  * Parse file name for command encoded by comm.
33  * If comm is E then command is doomed and we are
34  * parsing just so user won't have to retype the name.
35  */
36 filename(comm)
37 	int comm;
38 {
39 	register int c = comm, d;
40 	register int i;
41 
42 	d = getchar();
43 	if (endcmd(d)) {
44 		if (savedfile[0] == 0 && comm != 'f')
45 			error("No file|No current filename");
46 		CP(file, savedfile);
47 		wasalt = (isalt > 0) ? isalt-1 : 0;
48 		isalt = 0;
49 		oldadot = altdot;
50 		if (c == 'e' || c == 'E')
51 			altdot = lineDOT();
52 		if (d == EOF)
53 			ungetchar(d);
54 	} else {
55 		ungetchar(d);
56 		getone();
57 		eol();
58 		if (savedfile[0] == 0 && c != 'E' && c != 'e') {
59 			c = 'e';
60 			edited = 0;
61 		}
62 		wasalt = strcmp(file, altfile) == 0;
63 		oldadot = altdot;
64 		switch (c) {
65 
66 		case 'f':
67 			edited = 0;
68 			/* fall into ... */
69 
70 		case 'e':
71 			if (savedfile[0]) {
72 				altdot = lineDOT();
73 				CP(altfile, savedfile);
74 			}
75 			CP(savedfile, file);
76 			break;
77 
78 		default:
79 			if (file[0]) {
80 				if (c != 'E')
81 					altdot = lineDOT();
82 				CP(altfile, file);
83 			}
84 			break;
85 		}
86 	}
87 	if (hush && comm != 'f' || comm == 'E')
88 		return;
89 	if (file[0] != 0) {
90 		lprintf("\"%s\"", file);
91 		if (comm == 'f') {
92 			if (value(READONLY))
93 				printf(" [Read only]");
94 			if (!edited)
95 				printf(" [Not edited]");
96 			if (tchng)
97 				printf(" [Modified]");
98 		}
99 		flush();
100 	} else
101 		printf("No file ");
102 	if (comm == 'f') {
103 		if (!(i = lineDOL()))
104 			i++;
105 		printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(),
106 		    (long) 100 * lineDOT() / i);
107 	}
108 }
109 
110 /*
111  * Get the argument words for a command into genbuf
112  * expanding # and %.
113  */
114 getargs()
115 {
116 	register int c;
117 	register char *cp, *fp;
118 	static char fpatbuf[32];	/* hence limit on :next +/pat */
119 
120 	pastwh();
121 	if (peekchar() == '+') {
122 		for (cp = fpatbuf;;) {
123 			c = *cp++ = getchar();
124 			if (cp >= &fpatbuf[sizeof(fpatbuf)])
125 				error("Pattern too long");
126 			if (c == '\\' && isspace(peekchar()))
127 				c = getchar();
128 			if (c == EOF || isspace(c)) {
129 				ungetchar(c);
130 				*--cp = 0;
131 				firstpat = &fpatbuf[1];
132 				break;
133 			}
134 		}
135 	}
136 	if (skipend())
137 		return (0);
138 	CP(genbuf, "echo "); cp = &genbuf[5];
139 	for (;;) {
140 		c = getchar();
141 		if (endcmd(c)) {
142 			ungetchar(c);
143 			break;
144 		}
145 		switch (c) {
146 
147 		case '\\':
148 			if (any(peekchar(), "#%|"))
149 				c = getchar();
150 			/* fall into... */
151 
152 		default:
153 			if (cp > &genbuf[LBSIZE - 2])
154 flong:
155 				error("Argument buffer overflow");
156 			*cp++ = c;
157 			break;
158 
159 		case '#':
160 			fp = altfile;
161 			if (*fp == 0)
162 				error("No alternate filename@to substitute for #");
163 			goto filexp;
164 
165 		case '%':
166 			fp = savedfile;
167 			if (*fp == 0)
168 				error("No current filename@to substitute for %%");
169 filexp:
170 			while (*fp) {
171 				if (cp > &genbuf[LBSIZE - 2])
172 					goto flong;
173 				*cp++ = *fp++;
174 			}
175 			break;
176 		}
177 	}
178 	*cp = 0;
179 	return (1);
180 }
181 
182 /*
183  * Glob the argument words in genbuf, or if no globbing
184  * is implied, just split them up directly.
185  */
186 glob(gp)
187 	struct glob *gp;
188 {
189 	int pvec[2];
190 	register char **argv = gp->argv;
191 	register char *cp = gp->argspac;
192 	register int c;
193 	char ch;
194 	int nleft = NCARGS;
195 
196 	gp->argc0 = 0;
197 	if (gscan() == 0) {
198 		register char *v = genbuf + 5;		/* strlen("echo ") */
199 
200 		for (;;) {
201 			while (isspace(*v))
202 				v++;
203 			if (!*v)
204 				break;
205 			*argv++ = cp;
206 			while (*v && !isspace(*v))
207 				*cp++ = *v++;
208 			*cp++ = 0;
209 			gp->argc0++;
210 		}
211 		*argv = 0;
212 		return;
213 	}
214 	if (pipe(pvec) < 0)
215 		error("Can't make pipe to glob");
216 	pid = fork();
217 	io = pvec[0];
218 	if (pid < 0) {
219 		close(pvec[1]);
220 		error("Can't fork to do glob");
221 	}
222 	if (pid == 0) {
223 		int oerrno;
224 
225 		close(1);
226 		dup(pvec[1]);
227 		close(pvec[0]);
228 		close(2);	/* so errors don't mess up the screen */
229 		open("/dev/null", 1);
230 		execl(svalue(SHELL), "sh", "-c", genbuf, 0);
231 		oerrno = errno; close(1); dup(2); errno = oerrno;
232 		filioerr(svalue(SHELL));
233 	}
234 	close(pvec[1]);
235 	do {
236 		*argv = cp;
237 		for (;;) {
238 			if (read(io, &ch, 1) != 1) {
239 				close(io);
240 				c = -1;
241 			} else
242 				c = ch & TRIM;
243 			if (c <= 0 || isspace(c))
244 				break;
245 			*cp++ = c;
246 			if (--nleft <= 0)
247 				error("Arg list too long");
248 		}
249 		if (cp != *argv) {
250 			--nleft;
251 			*cp++ = 0;
252 			gp->argc0++;
253 			if (gp->argc0 >= NARGS)
254 				error("Arg list too long");
255 			argv++;
256 		}
257 	} while (c >= 0);
258 	waitfor();
259 	if (gp->argc0 == 0)
260 		error("No match");
261 }
262 
263 /*
264  * Scan genbuf for shell metacharacters.
265  * Set is union of v7 shell and csh metas.
266  */
267 gscan()
268 {
269 	register char *cp;
270 
271 	for (cp = genbuf; *cp; cp++)
272 		if (any(*cp, "~{[*?$`'\"\\"))
273 			return (1);
274 	return (0);
275 }
276 
277 /*
278  * Parse one filename into file.
279  */
280 struct glob G;
281 getone()
282 {
283 	register char *str;
284 
285 	if (getargs() == 0)
286 		error("Missing filename");
287 	glob(&G);
288 	if (G.argc0 > 1)
289 		error("Ambiguous|Too many file names");
290 	str = G.argv[G.argc0 - 1];
291 	if (strlen(str) > FNSIZE - 4)
292 		error("Filename too long");
293 samef:
294 	CP(file, str);
295 }
296 
297 /*
298  * Read a file from the world.
299  * C is command, 'e' if this really an edit (or a recover).
300  */
301 rop(c)
302 	int c;
303 {
304 	register int i;
305 	struct stat stbuf;
306 	short magic;
307 	static int ovro;	/* old value(READONLY) */
308 	static int denied;	/* 1 if READONLY was set due to file permissions */
309 
310 	io = open(file, 0);
311 	if (io < 0) {
312 		if (c == 'e' && errno == ENOENT) {
313 			edited++;
314 			/*
315 			 * If the user just did "ex foo" he is probably
316 			 * creating a new file.  Don't be an error, since
317 			 * this is ugly, and it screws up the + option.
318 			 */
319 			if (!seenprompt) {
320 				printf(" [New file]");
321 				noonl();
322 				return;
323 			}
324 		}
325 		syserror();
326 	}
327 	if (fstat(io, &stbuf))
328 		syserror();
329 	switch (stbuf.st_mode & S_IFMT) {
330 
331 	case S_IFBLK:
332 		error(" Block special file");
333 
334 	case S_IFCHR:
335 		if (isatty(io))
336 			error(" Teletype");
337 		if (samei(&stbuf, "/dev/null"))
338 			break;
339 		error(" Character special file");
340 
341 	case S_IFDIR:
342 		error(" Directory");
343 
344 	case S_IFREG:
345 #ifdef CRYPT
346 		if (xflag)
347 			break;
348 #endif
349 		i = read(io, (char *) &magic, sizeof(magic));
350 		lseek(io, 0l, 0);
351 		if (i != sizeof(magic))
352 			break;
353 		switch (magic) {
354 
355 		case 0405:	/* data overlay on exec */
356 		case 0407:	/* unshared */
357 		case 0410:	/* shared text */
358 		case 0411:	/* separate I/D */
359 		case 0413:	/* VM/Unix demand paged */
360 		case 0430:	/* PDP-11 Overlay shared */
361 		case 0431:	/* PDP-11 Overlay sep I/D */
362 			error(" Executable");
363 
364 		/*
365 		 * We do not forbid the editing of portable archives
366 		 * because it is reasonable to edit them, especially
367 		 * if they are archives of text files.  This is
368 		 * especially useful if you archive source files together
369 		 * and copy them to another system with ~%take, since
370 		 * the files sometimes show up munged and must be fixed.
371 		 */
372 		case 0177545:
373 		case 0177555:
374 			error(" Archive");
375 
376 		default:
377 #ifdef mbb
378 			/* C/70 has a 10 bit byte */
379 			if (magic & 03401600)
380 #else
381 			/* Everybody else has an 8 bit byte */
382 			if (magic & 0100200)
383 #endif
384 				error(" Non-ascii file");
385 			break;
386 		}
387 	}
388 	if (c != 'r') {
389 		if (value(READONLY) && denied) {
390 			value(READONLY) = ovro;
391 			denied = 0;
392 		}
393 		if ((stbuf.st_mode & 0222) == 0 || access(file, 2) < 0) {
394 			ovro = value(READONLY);
395 			denied = 1;
396 			value(READONLY) = 1;
397 		}
398 	}
399 	if (value(READONLY)) {
400 		printf(" [Read only]");
401 		flush();
402 	}
403 	if (c == 'r')
404 		setdot();
405 	else
406 		setall();
407 	if (FIXUNDO && inopen && c == 'r')
408 		undap1 = undap2 = dot + 1;
409 	rop2();
410 	rop3(c);
411 }
412 
413 rop2()
414 {
415 	line *first, *last, *a;
416 
417 	deletenone();
418 	clrstats();
419 	first = addr2 + 1;
420 	ignore(append(getfile, addr2));
421 	last = dot;
422 	/*
423 	 *	if the modeline variable is set,
424 	 *	check the first and last five lines of the file
425 	 *	for a mode line.
426 	 */
427 	if (value(MODELINE)) {
428 		for (a=first; a<=last; a++) {
429 			if (a==first+5 && last-first > 10)
430 				a = last - 4;
431 			getline(*a);
432 			checkmodeline(linebuf);
433 		}
434 	}
435 }
436 
437 rop3(c)
438 	int c;
439 {
440 
441 	if (iostats() == 0 && c == 'e')
442 		edited++;
443 	if (c == 'e') {
444 		if (wasalt || firstpat) {
445 			register line *addr = zero + oldadot;
446 
447 			if (addr > dol)
448 				addr = dol;
449 			if (firstpat) {
450 				globp = (*firstpat) ? firstpat : "$";
451 				commands(1,1);
452 				firstpat = 0;
453 			} else if (addr >= one) {
454 				if (inopen)
455 					dot = addr;
456 				markpr(addr);
457 			} else
458 				goto other;
459 		} else
460 other:
461 			if (dol > zero) {
462 				if (inopen)
463 					dot = one;
464 				markpr(one);
465 			}
466 		if(FIXUNDO)
467 			undkind = UNDNONE;
468 		if (inopen) {
469 			vcline = 0;
470 			vreplace(0, LINES, lineDOL());
471 		}
472 	}
473 }
474 
475 /*
476  * Are these two really the same inode?
477  */
478 samei(sp, cp)
479 	struct stat *sp;
480 	char *cp;
481 {
482 	struct stat stb;
483 
484 	if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev)
485 		return (0);
486 	return (sp->st_ino == stb.st_ino);
487 }
488 
489 /* Returns from edited() */
490 #define	EDF	0		/* Edited file */
491 #define	NOTEDF	-1		/* Not edited file */
492 #define	PARTBUF	1		/* Write of partial buffer to Edited file */
493 
494 /*
495  * Write a file.
496  */
497 wop(dofname)
498 bool dofname;	/* if 1 call filename, else use savedfile */
499 {
500 	register int c, exclam, nonexist;
501 	line *saddr1, *saddr2;
502 	struct stat stbuf;
503 
504 	c = 0;
505 	exclam = 0;
506 	if (dofname) {
507 		if (peekchar() == '!')
508 			exclam++, ignchar();
509 		ignore(skipwh());
510 		while (peekchar() == '>')
511 			ignchar(), c++, ignore(skipwh());
512 		if (c != 0 && c != 2)
513 			error("Write forms are 'w' and 'w>>'");
514 		filename('w');
515 	} else {
516 		if (savedfile[0] == 0)
517 			error("No file|No current filename");
518 		saddr1=addr1;
519 		saddr2=addr2;
520 		addr1=one;
521 		addr2=dol;
522 		CP(file, savedfile);
523 		if (inopen) {
524 			vclrech(0);
525 			splitw++;
526 		}
527 		lprintf("\"%s\"", file);
528 	}
529 	nonexist = stat(file, &stbuf);
530 	switch (c) {
531 
532 	case 0:
533 		if (!exclam && (!value(WRITEANY) || value(READONLY)))
534 		switch (edfile()) {
535 
536 		case NOTEDF:
537 			if (nonexist)
538 				break;
539 			if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
540 				if (samei(&stbuf, "/dev/null"))
541 					break;
542 				if (samei(&stbuf, "/dev/tty"))
543 					break;
544 			}
545 			io = open(file, 1);
546 			if (io < 0)
547 				syserror();
548 			if (!isatty(io))
549 				serror(" File exists| File exists - use \"w! %s\" to overwrite", file);
550 			close(io);
551 			break;
552 
553 		case EDF:
554 			if (value(READONLY))
555 				error(" File is read only");
556 			break;
557 
558 		case PARTBUF:
559 			if (value(READONLY))
560 				error(" File is read only");
561 			error(" Use \"w!\" to write partial buffer");
562 		}
563 cre:
564 /*
565 		synctmp();
566 */
567 #ifdef V6
568 		io = creat(file, 0644);
569 #else
570 		io = creat(file, 0666);
571 #endif
572 		if (io < 0)
573 			syserror();
574 		writing = 1;
575 		if (hush == 0)
576 			if (nonexist)
577 				printf(" [New file]");
578 			else if (value(WRITEANY) && edfile() != EDF)
579 				printf(" [Existing file]");
580 		break;
581 
582 	case 2:
583 		io = open(file, 1);
584 		if (io < 0) {
585 			if (exclam || value(WRITEANY))
586 				goto cre;
587 			syserror();
588 		}
589 		lseek(io, 0l, 2);
590 		break;
591 	}
592 	putfile(0);
593 	ignore(iostats());
594 	if (c != 2 && addr1 == one && addr2 == dol) {
595 		if (eq(file, savedfile))
596 			edited = 1;
597 		sync();
598 	}
599 	if (!dofname) {
600 		addr1 = saddr1;
601 		addr2 = saddr2;
602 	}
603 	writing = 0;
604 }
605 
606 /*
607  * Is file the edited file?
608  * Work here is that it is not considered edited
609  * if this is a partial buffer, and distinguish
610  * all cases.
611  */
612 edfile()
613 {
614 
615 	if (!edited || !eq(file, savedfile))
616 		return (NOTEDF);
617 	return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
618 }
619 
620 /*
621  * Extract the next line from the io stream.
622  */
623 char *nextip;
624 
625 getfile()
626 {
627 	register short c;
628 	register char *lp, *fp;
629 
630 	lp = linebuf;
631 	fp = nextip;
632 	do {
633 		if (--ninbuf < 0) {
634 			ninbuf = read(io, genbuf, LBSIZE) - 1;
635 			if (ninbuf < 0) {
636 				if (lp != linebuf) {
637 					lp++;
638 					printf(" [Incomplete last line]");
639 					break;
640 				}
641 				return (EOF);
642 			}
643 #ifdef CRYPT
644 			fp = genbuf;
645 			while(fp < &genbuf[ninbuf]) {
646 				if (*fp++ & 0200) {
647 					if (kflag)
648 						crblock(perm, genbuf, ninbuf+1,
649 cntch);
650 					break;
651 				}
652 			}
653 #endif
654 			fp = genbuf;
655 			cntch += ninbuf+1;
656 		}
657 		if (lp >= &linebuf[LBSIZE]) {
658 			error(" Line too long");
659 		}
660 		c = *fp++;
661 		if (c == 0) {
662 			cntnull++;
663 			continue;
664 		}
665 		if (c & QUOTE) {
666 			cntodd++;
667 			c &= TRIM;
668 			if (c == 0)
669 				continue;
670 		}
671 		*lp++ = c;
672 	} while (c != '\n');
673 	*--lp = 0;
674 	nextip = fp;
675 	cntln++;
676 	return (0);
677 }
678 
679 /*
680  * Write a range onto the io stream.
681  */
682 putfile(isfilter)
683 int isfilter;
684 {
685 	line *a1;
686 	register char *fp, *lp;
687 	register int nib;
688 
689 	a1 = addr1;
690 	clrstats();
691 	cntln = addr2 - a1 + 1;
692 	if (cntln == 0)
693 		return;
694 	nib = BUFSIZ;
695 	fp = genbuf;
696 	do {
697 		getline(*a1++);
698 		lp = linebuf;
699 		for (;;) {
700 			if (--nib < 0) {
701 				nib = fp - genbuf;
702 #ifdef CRYPT
703                 		if(kflag && !isfilter)
704                                         crblock(perm, genbuf, nib, cntch);
705 #endif
706 				if (write(io, genbuf, nib) != nib) {
707 					wrerror();
708 				}
709 				cntch += nib;
710 				nib = BUFSIZ - 1;
711 				fp = genbuf;
712 			}
713 			if ((*fp++ = *lp++) == 0) {
714 				fp[-1] = '\n';
715 				break;
716 			}
717 		}
718 	} while (a1 <= addr2);
719 	nib = fp - genbuf;
720 #ifdef CRYPT
721 	if(kflag && !isfilter)
722 		crblock(perm, genbuf, nib, cntch);
723 #endif
724 	if (write(io, genbuf, nib) != nib) {
725 		wrerror();
726 	}
727 	cntch += nib;
728 }
729 
730 /*
731  * A write error has occurred;  if the file being written was
732  * the edited file then we consider it to have changed since it is
733  * now likely scrambled.
734  */
735 wrerror()
736 {
737 
738 	if (eq(file, savedfile) && edited)
739 		change();
740 	syserror();
741 }
742 
743 /*
744  * Source command, handles nested sources.
745  * Traps errors since it mungs unit 0 during the source.
746  */
747 short slevel;
748 short ttyindes;
749 
750 source(fil, okfail)
751 	char *fil;
752 	bool okfail;
753 {
754 	jmp_buf osetexit;
755 	register int saveinp, ointty, oerrno;
756 	char *saveglobp;
757 	short savepeekc;
758 
759 	signal(SIGINT, SIG_IGN);
760 	saveinp = dup(0);
761 	savepeekc = peekc;
762 	saveglobp = globp;
763 	peekc = 0; globp = 0;
764 	if (saveinp < 0)
765 		error("Too many nested sources");
766 	if (slevel <= 0)
767 		ttyindes = saveinp;
768 	close(0);
769 	if (open(fil, 0) < 0) {
770 		oerrno = errno;
771 		setrupt();
772 		dup(saveinp);
773 		close(saveinp);
774 		errno = oerrno;
775 		if (!okfail)
776 			filioerr(fil);
777 		return;
778 	}
779 	slevel++;
780 	ointty = intty;
781 	intty = isatty(0);
782 	oprompt = value(PROMPT);
783 	value(PROMPT) &= intty;
784 	getexit(osetexit);
785 	setrupt();
786 	if (setexit() == 0)
787 		commands(1, 1);
788 	else if (slevel > 1) {
789 		close(0);
790 		dup(saveinp);
791 		close(saveinp);
792 		slevel--;
793 		resexit(osetexit);
794 		reset();
795 	}
796 	intty = ointty;
797 	value(PROMPT) = oprompt;
798 	close(0);
799 	dup(saveinp);
800 	close(saveinp);
801 	globp = saveglobp;
802 	peekc = savepeekc;
803 	slevel--;
804 	resexit(osetexit);
805 }
806 
807 /*
808  * Clear io statistics before a read or write.
809  */
810 clrstats()
811 {
812 
813 	ninbuf = 0;
814 	cntch = 0;
815 	cntln = 0;
816 	cntnull = 0;
817 	cntodd = 0;
818 }
819 
820 /*
821  * Io is finished, close the unit and print statistics.
822  */
823 iostats()
824 {
825 
826 	(void) fsync(io);
827 	close(io);
828 	io = -1;
829 	if (hush == 0) {
830 		if (value(TERSE))
831 			printf(" %d/%D", cntln, cntch);
832 		else
833 			printf(" %d line%s, %D character%s", cntln, plural((long) cntln),
834 			    cntch, plural(cntch));
835 		if (cntnull || cntodd) {
836 			printf(" (");
837 			if (cntnull) {
838 				printf("%D null", cntnull);
839 				if (cntodd)
840 					printf(", ");
841 			}
842 			if (cntodd)
843 				printf("%D non-ASCII", cntodd);
844 			putchar(')');
845 		}
846 		noonl();
847 		flush();
848 	}
849 	return (cntnull != 0 || cntodd != 0);
850 }
851 
852 #if USG | USG3TTY
853 /* It's so wonderful how we all speak the same language... */
854 # define index strchr
855 # define rindex strrchr
856 #endif
857 
858 checkmodeline(line)
859 char *line;
860 {
861 	char *beg, *end;
862 	char cmdbuf[1024];
863 	char *index(), *rindex();
864 
865 	beg = index(line, ':');
866 	if (beg == NULL)
867 		return;
868 	if (&beg[-3] < line)
869 		return;
870 	if (!(  ( (beg[-3] == ' ' || beg[-3] == '\t')
871 	        && beg[-2] == 'e'
872 		&& beg[-1] == 'x')
873 	     || ( (beg[-3] == ' ' || beg[-3] == '\t')
874 	        && beg[-2] == 'v'
875 		&& beg[-1] == 'i'))) return;
876 	strncpy(cmdbuf, beg+1, sizeof cmdbuf);
877 	end = rindex(cmdbuf, ':');
878 	if (end == NULL)
879 		return;
880 	*end = 0;
881 	globp = cmdbuf;
882 	commands(1, 1);
883 }
884