xref: /original-bsd/usr.bin/ex/ex_io.c (revision 6c57d260)
1 /* Copyright (c) 1980 Regents of the University of California */
2 static char *sccsid = "@(#)ex_io.c	6.2 10/23/80";
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 getone()
281 {
282 	register char *str;
283 	struct glob G;
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:	/* Interdata? overlay */
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 			if (magic & 0100200)
378 				error(" Non-ascii file");
379 			break;
380 		}
381 	}
382 	if (c != 'r') {
383 		if (value(READONLY) && denied) {
384 			value(READONLY) = ovro;
385 			denied = 0;
386 		}
387 		if ((stbuf.st_mode & 0222) == 0 || access(file, 2) < 0) {
388 			ovro = value(READONLY);
389 			denied = 1;
390 			value(READONLY) = 1;
391 		}
392 	}
393 	if (value(READONLY)) {
394 		printf(" [Read only]");
395 		flush();
396 	}
397 	if (c == 'r')
398 		setdot();
399 	else
400 		setall();
401 	if (FIXUNDO && inopen && c == 'r')
402 		undap1 = undap2 = dot + 1;
403 	rop2();
404 	rop3(c);
405 }
406 
407 rop2()
408 {
409 
410 	deletenone();
411 	clrstats();
412 	ignore(append(getfile, addr2));
413 }
414 
415 rop3(c)
416 	int c;
417 {
418 
419 	if (iostats() == 0 && c == 'e')
420 		edited++;
421 	if (c == 'e') {
422 		if (wasalt || firstpat) {
423 			register line *addr = zero + oldadot;
424 
425 			if (addr > dol)
426 				addr = dol;
427 			if (firstpat) {
428 				globp = (*firstpat) ? firstpat : "$";
429 				commands(1,1);
430 				firstpat = 0;
431 			} else if (addr >= one) {
432 				if (inopen)
433 					dot = addr;
434 				markpr(addr);
435 			} else
436 				goto other;
437 		} else
438 other:
439 			if (dol > zero) {
440 				if (inopen)
441 					dot = one;
442 				markpr(one);
443 			}
444 		if(FIXUNDO)
445 			undkind = UNDNONE;
446 		if (inopen) {
447 			vcline = 0;
448 			vreplace(0, LINES, lineDOL());
449 		}
450 	}
451 	if (laste) {
452 #ifdef VMUNIX
453 		tlaste();
454 #endif
455 		laste = 0;
456 		sync();
457 	}
458 }
459 
460 /*
461  * Are these two really the same inode?
462  */
463 samei(sp, cp)
464 	struct stat *sp;
465 	char *cp;
466 {
467 	struct stat stb;
468 
469 	if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev)
470 		return (0);
471 	return (sp->st_ino == stb.st_ino);
472 }
473 
474 /* Returns from edited() */
475 #define	EDF	0		/* Edited file */
476 #define	NOTEDF	-1		/* Not edited file */
477 #define	PARTBUF	1		/* Write of partial buffer to Edited file */
478 
479 /*
480  * Write a file.
481  */
482 wop(dofname)
483 bool dofname;	/* if 1 call filename, else use savedfile */
484 {
485 	register int c, exclam, nonexist;
486 	line *saddr1, *saddr2;
487 	struct stat stbuf;
488 
489 	c = 0;
490 	exclam = 0;
491 	if (dofname) {
492 		if (peekchar() == '!')
493 			exclam++, ignchar();
494 		ignore(skipwh());
495 		while (peekchar() == '>')
496 			ignchar(), c++, ignore(skipwh());
497 		if (c != 0 && c != 2)
498 			error("Write forms are 'w' and 'w>>'");
499 		filename('w');
500 	} else {
501 		if (savedfile[0] == 0)
502 			error("No file|No current filename");
503 		saddr1=addr1;
504 		saddr2=addr2;
505 		addr1=one;
506 		addr2=dol;
507 		CP(file, savedfile);
508 		if (inopen) {
509 			vclrech(0);
510 			splitw++;
511 		}
512 		lprintf("\"%s\"", file);
513 	}
514 	nonexist = stat(file, &stbuf);
515 	switch (c) {
516 
517 	case 0:
518 		if (!exclam && (!value(WRITEANY) || value(READONLY)))
519 		switch (edfile()) {
520 
521 		case NOTEDF:
522 			if (nonexist)
523 				break;
524 			if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
525 				if (samei(&stbuf, "/dev/null"))
526 					break;
527 				if (samei(&stbuf, "/dev/tty"))
528 					break;
529 			}
530 			io = open(file, 1);
531 			if (io < 0)
532 				syserror();
533 			if (!isatty(io))
534 				serror(" File exists| File exists - use \"w! %s\" to overwrite", file);
535 			close(io);
536 			break;
537 
538 		case EDF:
539 			if (value(READONLY))
540 				error(" File is read only");
541 			break;
542 
543 		case PARTBUF:
544 			if (value(READONLY))
545 				error(" File is read only");
546 			error(" Use \"w!\" to write partial buffer");
547 		}
548 cre:
549 /*
550 		synctmp();
551 */
552 #ifdef V6
553 		io = creat(file, 0644);
554 #else
555 		io = creat(file, 0666);
556 #endif
557 		if (io < 0)
558 			syserror();
559 		writing = 1;
560 		if (hush == 0)
561 			if (nonexist)
562 				printf(" [New file]");
563 			else if (value(WRITEANY) && edfile() != EDF)
564 				printf(" [Existing file]");
565 		break;
566 
567 	case 2:
568 		io = open(file, 1);
569 		if (io < 0) {
570 			if (exclam || value(WRITEANY))
571 				goto cre;
572 			syserror();
573 		}
574 		lseek(io, 0l, 2);
575 		break;
576 	}
577 	putfile();
578 	ignore(iostats());
579 	if (c != 2 && addr1 == one && addr2 == dol) {
580 		if (eq(file, savedfile))
581 			edited = 1;
582 		sync();
583 	}
584 	if (!dofname) {
585 		addr1 = saddr1;
586 		addr2 = saddr2;
587 	}
588 	writing = 0;
589 }
590 
591 /*
592  * Is file the edited file?
593  * Work here is that it is not considered edited
594  * if this is a partial buffer, and distinguish
595  * all cases.
596  */
597 edfile()
598 {
599 
600 	if (!edited || !eq(file, savedfile))
601 		return (NOTEDF);
602 	return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
603 }
604 
605 /*
606  * Extract the next line from the io stream.
607  */
608 static	char *nextip;
609 
610 getfile()
611 {
612 	register short c;
613 	register char *lp, *fp;
614 
615 	lp = linebuf;
616 	fp = nextip;
617 	do {
618 		if (--ninbuf < 0) {
619 			ninbuf = read(io, genbuf, LBSIZE) - 1;
620 			if (ninbuf < 0) {
621 				if (lp != linebuf) {
622 					lp++;
623 					printf(" [Incomplete last line]");
624 					break;
625 				}
626 				return (EOF);
627 			}
628 #ifdef CRYPT
629 			fp = genbuf;
630 			while(fp < &genbuf[ninbuf]) {
631 				if (*fp++ & 0200) {
632 					if (kflag)
633 						crblock(perm, genbuf, ninbuf+1,
634 cntch);
635 					break;
636 				}
637 			}
638 #endif
639 			fp = genbuf;
640 			cntch += ninbuf+1;
641 		}
642 		if (lp >= &linebuf[LBSIZE]) {
643 			error(" Line too long");
644 		}
645 		c = *fp++;
646 		if (c == 0) {
647 			cntnull++;
648 			continue;
649 		}
650 		if (c & QUOTE) {
651 			cntodd++;
652 			c &= TRIM;
653 			if (c == 0)
654 				continue;
655 		}
656 		*lp++ = c;
657 	} while (c != '\n');
658 	*--lp = 0;
659 	nextip = fp;
660 	cntln++;
661 	return (0);
662 }
663 
664 /*
665  * Write a range onto the io stream.
666  */
667 putfile()
668 {
669 	line *a1;
670 	register char *fp, *lp;
671 	register int nib;
672 
673 	a1 = addr1;
674 	clrstats();
675 	cntln = addr2 - a1 + 1;
676 	if (cntln == 0)
677 		return;
678 	nib = BUFSIZ;
679 	fp = genbuf;
680 	do {
681 		getline(*a1++);
682 		lp = linebuf;
683 		for (;;) {
684 			if (--nib < 0) {
685 				nib = fp - genbuf;
686 #ifdef CRYPT
687                 		if(kflag)
688                                         crblock(perm, genbuf, nib, cntch);
689 #endif
690 				if (write(io, genbuf, nib) != nib) {
691 					wrerror();
692 				}
693 				cntch += nib;
694 				nib = BUFSIZ - 1;
695 				fp = genbuf;
696 			}
697 			if ((*fp++ = *lp++) == 0) {
698 				fp[-1] = '\n';
699 				break;
700 			}
701 		}
702 	} while (a1 <= addr2);
703 	nib = fp - genbuf;
704 #ifdef CRYPT
705 	if(kflag)
706 		crblock(perm, genbuf, nib, cntch);
707 #endif
708 	if (write(io, genbuf, nib) != nib) {
709 		wrerror();
710 	}
711 	cntch += nib;
712 }
713 
714 /*
715  * A write error has occurred;  if the file being written was
716  * the edited file then we consider it to have changed since it is
717  * now likely scrambled.
718  */
719 wrerror()
720 {
721 
722 	if (eq(file, savedfile) && edited)
723 		change();
724 	syserror();
725 }
726 
727 /*
728  * Source command, handles nested sources.
729  * Traps errors since it mungs unit 0 during the source.
730  */
731 short slevel;
732 short ttyindes;
733 
734 source(fil, okfail)
735 	char *fil;
736 	bool okfail;
737 {
738 	jmp_buf osetexit;
739 	register int saveinp, ointty, oerrno;
740 	char savepeekc, *saveglobp;
741 
742 	signal(SIGINT, SIG_IGN);
743 	saveinp = dup(0);
744 	savepeekc = peekc;
745 	saveglobp = globp;
746 	peekc = 0; globp = 0;
747 	if (saveinp < 0)
748 		error("Too many nested sources");
749 	if (slevel <= 0)
750 		ttyindes = saveinp;
751 	close(0);
752 	if (open(fil, 0) < 0) {
753 		oerrno = errno;
754 		setrupt();
755 		dup(saveinp);
756 		close(saveinp);
757 		errno = oerrno;
758 		if (!okfail)
759 			filioerr(fil);
760 		return;
761 	}
762 	slevel++;
763 	ointty = intty;
764 	intty = isatty(0);
765 	oprompt = value(PROMPT);
766 	value(PROMPT) &= intty;
767 	getexit(osetexit);
768 	setrupt();
769 	if (setexit() == 0)
770 		commands(1, 1);
771 	else if (slevel > 1) {
772 		close(0);
773 		dup(saveinp);
774 		close(saveinp);
775 		slevel--;
776 		resexit(osetexit);
777 		reset();
778 	}
779 	intty = ointty;
780 	value(PROMPT) = oprompt;
781 	close(0);
782 	dup(saveinp);
783 	close(saveinp);
784 	globp = saveglobp;
785 	peekc = savepeekc;
786 	slevel--;
787 	resexit(osetexit);
788 }
789 
790 /*
791  * Clear io statistics before a read or write.
792  */
793 clrstats()
794 {
795 
796 	ninbuf = 0;
797 	cntch = 0;
798 	cntln = 0;
799 	cntnull = 0;
800 	cntodd = 0;
801 }
802 
803 /*
804  * Io is finished, close the unit and print statistics.
805  */
806 iostats()
807 {
808 
809 	close(io);
810 	io = -1;
811 	if (hush == 0) {
812 		if (value(TERSE))
813 			printf(" %d/%D", cntln, cntch);
814 		else
815 			printf(" %d line%s, %D character%s", cntln, plural((long) cntln),
816 			    cntch, plural(cntch));
817 		if (cntnull || cntodd) {
818 			printf(" (");
819 			if (cntnull) {
820 				printf("%D null", cntnull);
821 				if (cntodd)
822 					printf(", ");
823 			}
824 			if (cntodd)
825 				printf("%D non-ASCII", cntodd);
826 			putchar(')');
827 		}
828 		noonl();
829 		flush();
830 	}
831 	return (cntnull != 0 || cntodd != 0);
832 }
833