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