BSD 2 development
[unix-history] / src / ex / ex_io.c
CommitLineData
54e8bdce
BJ
1/* Copyright (c) 1979 Regents of the University of California */
2#include "ex.h"
3#include "ex_argv.h"
4#include "ex_temp.h"
5#include "ex_tty.h"
6#include "ex_vis.h"
7
8/*
9 * File input/output, unix escapes, source, filtering preserve and recover
10 */
11
12/*
13 * Following remember where . was in the previous file for return
14 * on file switching.
15 */
16short altdot;
17short oldadot;
18bool wasalt;
19
20long cntch; /* Count of characters on unit io */
21short cntln; /* Count of lines " */
22long cntnull; /* Count of nulls " */
23long cntodd; /* Count of non-ascii characters " */
24
25/*
26 * Parse file name for command encoded by comm.
27 * If comm is E then command is doomed and we are
28 * parsing just so user won't have to retype the name.
29 */
30filename(comm)
31 int comm;
32{
33 register int c = comm, d;
34 register int i;
35
36 d = getchar();
37 if (endcmd(d)) {
38 if (savedfile[0] == 0 && comm != 'f')
39 error("No file|No current filename");
40 CP(file, savedfile);
41 wasalt = 0;
42 oldadot = altdot;
43 if (d == EOF)
44 ungetchar(d);
45 } else {
46 ungetchar(d);
47 getone();
48 eol();
49 if (savedfile[0] == 0 && c != 'E' && c != 'e') {
50 c = 'e';
51 edited = 0;
52 }
53 wasalt = strcmp(file, altfile) == 0;
54 oldadot = altdot;
55 switch (c) {
56
57 case 'f':
58 edited = 0;
59 /* fall into ... */
60
61 case 'e':
62 if (savedfile[0]) {
63 altdot = lineDOT();
64 CP(altfile, savedfile);
65 }
66 CP(savedfile, file);
67 break;
68
69 default:
70 if (file[0]) {
71 if (c != 'E')
72 altdot = lineDOT();
73 CP(altfile, file);
74 }
75 break;
76 }
77 }
78 if (hush && comm != 'f' || comm == 'E')
79 return;
80 if (file[0] != 0) {
81 lprintf("\"%s\"", file);
82 if (comm == 'f') {
83 if (!edited)
84 printf(" [Not edited]");
85 if (tchng)
86 printf(" [Modified]");
87 }
88 flush();
89 } else
90 printf("No file ");
91 if (comm == 'f') {
92 if (!(i = lineDOL()))
93 i++;
94 printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(),
95 (long) 100 * lineDOT() / i);
96 }
97}
98
99/*
100 * Get the argument words for a command into genbuf
101 * expanding # and %.
102 */
103getargs()
104{
105 register int c;
106 register char *cp, *fp;
107
108 if (skipend())
109 return (0);
110 CP(genbuf, "echo "); cp = &genbuf[5];
111 for (;;) {
112 c = getchar();
113 if (endcmd(c)) {
114 ungetchar(c);
115 break;
116 }
117 switch (c) {
118
119 case '\\':
120 if (any(peekchar(), "#%"))
121 c = getchar();
122 /* fall into... */
123
124 default:
125 if (cp > &genbuf[LBSIZE - 2])
126flong:
127 error("Argument buffer overflow");
128 *cp++ = c;
129 break;
130
131 case '#':
132 fp = altfile;
133 if (*fp == 0)
134 error("No alternate filename@to substitute for #");
135 goto filexp;
136
137 case '%':
138 fp = savedfile;
139 if (*fp == 0)
140 error("No current filename@to substitute for %%");
141filexp:
142 while (*fp) {
143 if (cp > &genbuf[LBSIZE - 2])
144 goto flong;
145 *cp++ = *fp++;
146 }
147 break;
148 }
149 }
150 *cp = 0;
151 return (1);
152}
153
154/*
155 * Glob the argument words in genbuf, or if no globbing
156 * is implied, just split them up directly.
157 */
158glob(gp)
159 struct glob *gp;
160{
161 int pvec[2];
162 register char **argv = gp->argv;
163 register char *cp = gp->argspac;
164 register int c;
165 char ch;
166 int nleft = NCARGS;
167
168 gp->argc0 = 0;
169 if (gscan() == 0) {
170 register char *v = genbuf + 5; /* strlen("echo ") */
171
172 for (;;) {
173 while (isspace(*v))
174 v++;
175 if (!*v)
176 break;
177 *argv++ = cp;
178 while (*v && !isspace(*v))
179 *cp++ = *v++;
180 *cp++ = 0;
181 gp->argc0++;
182 }
183 *argv = 0;
184 return;
185 }
186 if (pipe(pvec) < 0)
187 error("Can't make pipe to glob");
188 pid = fork();
189 io = pvec[0];
190 if (pid < 0) {
191 close(pvec[1]);
192 error("Can't fork to do glob");
193 }
194 if (pid == 0) {
195 int oerrno;
196
197 close(1);
198 dup(pvec[1]);
199 close(pvec[0]);
200 execl(svalue(SHELL), "sh", "-c", genbuf, 0);
201 die++;
202 oerrno = errno; close(1); dup(2); errno = oerrno;
203 filioerr(svalue(SHELL));
204 }
205 close(pvec[1]);
206 do {
207 *argv = cp;
208 for (;;) {
209 if (read(io, &ch, 1) != 1) {
210 close(io);
211 c = -1;
212 } else
213 c = ch & TRIM;
214 if (c <= 0 || isspace(c))
215 break;
216 *cp++ = c;
217 if (--nleft <= 0)
218 error("Arg list too long");
219 }
220 if (cp != *argv) {
221 --nleft;
222 *cp++ = 0;
223 gp->argc0++;
224 if (gp->argc0 >= NARGS)
225 error("Arg list too long");
226 argv++;
227 }
228 } while (c >= 0);
229 waitfor();
230 if (gp->argc0 == 0)
231 error(NOSTR);
232}
233
234/*
235 * Scan genbuf for shell metacharacters.
236 * Set is union of v7 shell and csh metas.
237 */
238gscan()
239{
240 register char *cp;
241
242 for (cp = genbuf; *cp; cp++)
243 if (any(*cp, "~{[*?$`'\"\\"))
244 return (1);
245 return (0);
246}
247
248/*
249 * Parse one filename into file.
250 */
251getone()
252{
253 register char *str;
254 struct glob G;
255
256 if (getargs() == 0)
257 error("Missing filename");
258 glob(&G);
259 if (G.argv[0][0] == '+') {
260 firstln = getn(G.argv[0] + 1);
261 if (firstln == 0)
262 firstln = 20000;
263 if (G.argc0 == 1) {
264 str = savedfile;
265 goto samef;
266 }
267 }
268 else if (G.argc0 > 1)
269 error("Ambiguous|Too many file names");
270 str = G.argv[G.argc0 - 1];
271 if (strlen(str) > FNSIZE - 4)
272 error("Filename too long");
273samef:
274 CP(file, str);
275}
276
277/*
278 * Read a file from the world.
279 * C is command, 'e' if this really an edit (or a recover).
280 */
281rop(c)
282 int c;
283{
284 register int i;
285 struct stat stbuf;
286 short magic;
287
288 if (firstln)
289 wasalt = 2, oldadot = firstln, firstln = 0;
290 io = open(file, 0);
291 if (io < 0) {
292 if (c == 'e' && errno == ENOENT)
293 edited++;
294 syserror();
295 }
296 if (fstat(io, &stbuf))
297 syserror();
298 switch (stbuf.st_mode & S_IFMT) {
299
300 case S_IFBLK:
301 error(" Block special file");
302
303 case S_IFCHR:
304 if (isatty(io))
305 error(" Teletype");
306 if (samei(&stbuf, "/dev/null"))
307 break;
308 error(" Character special file");
309
310 case S_IFDIR:
311 error(" Directory");
312
313 case S_IFREG:
314 i = read(io, (char *) &magic, sizeof(magic));
315 lseek(io, 0l, 0);
316 if (i != sizeof(magic))
317 break;
318 switch (magic) {
319
320 case 0405:
321 case 0407:
322 case 0410:
323 case 0411:
324 error(" Executable");
325
326 case 0177545:
327 case 0177555:
328 error(" Archive");
329
330 default:
331 if (magic & 0100200)
332 error(" Non-ascii file");
333 break;
334 }
335 }
336 if (c == 'r')
337 setdot();
338 else
339 setall();
340 if (inopen && c == 'r')
341 undap1 = undap2 = dot + 1;
342 rop2();
343 rop3(c);
344}
345
346rop2()
347{
348
349 deletenone();
350 clrstats();
351 ignore(append(getfile, addr2));
352}
353
354rop3(c)
355 int c;
356{
357
358 if (iostats() == 0 && c == 'e')
359 edited++;
360 if (c == 'e') {
361 if (wasalt) {
362 register line *addr = zero + oldadot;
363
364 if (addr > dol)
365 addr = dol;
366 if (addr >= one) {
367 if (inopen || wasalt == 2)
368 dot = addr;
369 markpr(addr);
370 } else
371 goto other;
372 } else
373other:
374 if (dol > zero) {
375 if (inopen)
376 dot = one;
377 markpr(one);
378 }
379 undkind = UNDNONE;
380 if (inopen) {
381 vcline = 0;
382 vreplace(0, LINES, lineDOL());
383 }
384 }
385 if (laste) {
386 laste = 0;
387 sync();
388 }
389}
390
391/*
392 * Are these two really the same inode?
393 */
394samei(sp, cp)
395 struct stat *sp;
396 char *cp;
397{
398 struct stat stb;
399
400 if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev)
401 return (0);
402 return (sp->st_ino == stb.st_ino);
403}
404
405/* Returns from edited() */
406#define EDF 0 /* Edited file */
407#define NOTEDF -1 /* Not edited file */
408#define PARTBUF 1 /* Write of partial buffer to Edited file */
409
410/*
411 * Write a file.
412 */
413wop()
414{
415 register int c, exclam, nonexist;
416 struct stat stbuf;
417
418 c = 0;
419 exclam = 0;
420 if (peekchar() == '!')
421 exclam++, ignchar();
422 ignore(skipwh());
423 while (peekchar() == '>')
424 ignchar(), c++, ignore(skipwh());
425 if (c != 0 && c != 2)
426 error("Write forms are 'w' and 'w>>'");
427 filename('w');
428 nonexist = stat(file, &stbuf);
429 switch (c) {
430
431 case 0:
432 if (!exclam && !value(WRITEANY)) switch (edfile()) {
433
434 case NOTEDF:
435 if (nonexist)
436 break;
437 if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
438 if (samei(&stbuf, "/dev/null"))
439 break;
440 if (samei(&stbuf, "/dev/tty"))
441 break;
442 }
443 io = open(file, 1);
444 if (io < 0)
445 syserror();
446 if (!isatty(io))
447 serror(" File exists| File exists - use \"w! %s\" to overwrite", file);
448 close(io);
449 break;
450
451 case PARTBUF:
452 error(" Use \"w!\" to write partial buffer");
453 }
454cre:
455 synctmp();
456#ifdef V6
457 io = creat(file, 0644);
458#else
459 io = creat(file, 0666);
460#endif
461 if (io < 0)
462 syserror();
463 if (hush == 0)
464 if (nonexist)
465 printf(" [New file]");
466 else if (value(WRITEANY) && edfile() != EDF)
467 printf(" [Existing file]");
468 break;
469
470 case 2:
471 io = open(file, 1);
472 if (io < 0) {
473 if (exclam || value(WRITEANY))
474 goto cre;
475 syserror();
476 }
477 lseek(io, 0l, 2);
478 break;
479 }
480 putfile();
481 ignore(iostats());
482 if (c != 2 && addr1 == one && addr2 == dol) {
483 if (eq(file, savedfile))
484 edited = 1;
485 sync();
486 }
487}
488
489/*
490 * Is file the edited file?
491 * Work here is that it is not considered edited
492 * if this is a partial buffer, and distinguish
493 * all cases.
494 */
495edfile()
496{
497
498 if (!edited || !eq(file, savedfile))
499 return (NOTEDF);
500 return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
501}
502
503/*
504 * First part of a shell escape,
505 * parse the line, expanding # and % and ! and printing if implied.
506 */
507unix0(warn)
508 bool warn;
509{
510 register char *up, *fp;
511 register short c;
512 char printub, puxb[UXBSIZE + sizeof (int)];
513
514 printub = 0;
515 CP(puxb, uxb);
516 c = getchar();
517 if (c == '\n' || c == EOF)
518 error("Incomplete shell escape command@- use 'shell' to get a shell");
519 up = uxb;
520 do {
521 switch (c) {
522
523 case '\\':
524 if (any(peekchar(), "%#!"))
525 c = getchar();
526 default:
527 if (up >= &uxb[UXBSIZE]) {
528tunix:
529 uxb[0] = 0;
530 error("Command too long");
531 }
532 *up++ = c;
533 break;
534
535 case '!':
536 fp = puxb;
537 if (*fp == 0) {
538 uxb[0] = 0;
539 error("No previous command@to substitute for !");
540 }
541 printub++;
542 while (*fp) {
543 if (up >= &uxb[UXBSIZE])
544 goto tunix;
545 *up++ = *fp++;
546 }
547 break;
548
549 case '#':
550 fp = altfile;
551 if (*fp == 0) {
552 uxb[0] = 0;
553 error("No alternate filename@to substitute for #");
554 }
555 goto uexp;
556
557 case '%':
558 fp = savedfile;
559 if (*fp == 0) {
560 uxb[0] = 0;
561 error("No filename@to substitute for %%");
562 }
563uexp:
564 printub++;
565 while (*fp) {
566 if (up >= &uxb[UXBSIZE])
567 goto tunix;
568 *up++ = *fp++ | QUOTE;
569 }
570 break;
571 }
572 c = getchar();
573 } while (c == '|' || !endcmd(c));
574 if (c == EOF)
575 ungetchar(c);
576 *up = 0;
577 if (!inopen)
578 resetflav();
579 if (warn && hush == 0 && chng && xchng != chng && value(WARN)) {
580 xchng = chng;
581 vnfl();
582 printf(mesg("[No write]|[No write since last change]"));
583 noonl();
584 flush();
585 } else
586 warn = 0;
587 if (printub) {
588 if (uxb[0] == 0)
589 error("No previous command@to repeat");
590 if (inopen) {
591 splitw++;
592 vclean();
593 vgoto(WECHO, 0);
594 }
595 if (warn)
596 vnfl();
597 lprintf("!%s", uxb);
598 if (inopen) {
599 vclreol();
600 vgoto(WECHO, 0);
601 } else
602 putnl();
603 flush();
604 }
605}
606
607/*
608 * Do the real work for execution of a shell escape.
609 * Mode is like the number passed to open system calls
610 * and indicates filtering. If input is implied, newstdin
611 * must have been setup already.
612 */
613unixex(opt, up, newstdin, mode)
614 char *opt, *up;
615 int newstdin, mode;
616{
617 int pvec[2], f;
618
619 signal(SIGINT, SIG_IGN);
620 if (inopen)
621 f = setty(normf);
622 if ((mode & 1) && pipe(pvec) < 0) {
623 /* Newstdin should be io so it will be closed */
624 if (inopen)
625 setty(f);
626 error("Can't make pipe for filter");
627 }
628 pid = fork();
629 if (pid < 0) {
630 if (mode & 1) {
631 close(pvec[0]);
632 close(pvec[1]);
633 }
634 setrupt();
635 error("No more processes");
636 }
637 if (pid == 0) {
638 die++;
639 if (mode & 2) {
640 close(0);
641 dup(newstdin);
642 close(newstdin);
643 }
644 if (mode & 1) {
645 close(pvec[0]);
646 close(1);
647 dup(pvec[1]);
648 if (inopen) {
649 close(2);
650 dup(1);
651 }
652 close(pvec[1]);
653 }
654 if (io)
655 close(io);
656 if (tfile)
657 close(tfile);
658 close(erfile);
659 signal(SIGHUP, oldhup);
660 signal(SIGQUIT, oldquit);
661 if (ruptible)
662 signal(SIGINT, SIG_DFL);
663 execl(svalue(SHELL), "sh", opt, up, (char *) 0);
664 printf("No %s!\n", svalue(SHELL));
665 error(NOSTR);
666 }
667 if (mode & 1) {
668 io = pvec[0];
669 close(pvec[1]);
670 }
671 if (newstdin)
672 close(newstdin);
673 return (f);
674}
675
676/*
677 * Wait for the command to complete.
678 * F is for restoration of tty mode if from open/visual.
679 * C flags suppression of printing.
680 */
681unixwt(c, f)
682 bool c;
683 int f;
684{
685
686 waitfor();
687 if (inopen)
688 setty(f);
689 setrupt();
690 if (!inopen && c && hush == 0) {
691 printf("!\n");
692 flush();
693 termreset();
694 gettmode();
695 }
696}
697
698/*
699 * Setup a pipeline for the filtration implied by mode
700 * which is like a open number. If input is required to
701 * the filter, then a child editor is created to write it.
702 * If output is catch it from io which is created by unixex.
703 */
704filter(mode)
705 register int mode;
706{
707 static int pvec[2];
708 register int f;
709 register int lines = lineDOL();
710
711 mode++;
712 if (mode & 2) {
713 signal(SIGINT, SIG_IGN);
714 if (pipe(pvec) < 0)
715 error("Can't make pipe");
716 pid = fork();
717 io = pvec[0];
718 if (pid < 0) {
719 setrupt();
720 close(pvec[1]);
721 error("No more processes");
722 }
723 if (pid == 0) {
724 die++;
725 setrupt();
726 io = pvec[1];
727 close(pvec[0]);
728 putfile();
729 exit(0);
730 }
731 close(pvec[1]);
732 io = pvec[0];
733 setrupt();
734 }
735 f = unixex("-c", uxb, (mode & 2) ? pvec[0] : 0, mode);
736 if (mode == 3) {
737 delete(0);
738 addr2 = addr1 - 1;
739 }
740 if (mode & 1)
741 ignore(append(getfile, addr2));
742 close(io);
743 io = -1;
744 unixwt(!inopen, f);
745 netchHAD(lines);
746}
747
748/*
749 * Set up to do a recover, getting io to be a pipe from
750 * the recover process.
751 */
752recover()
753{
754 static int pvec[2];
755
756 if (pipe(pvec) < 0)
757 error(" Can't make pipe for recovery");
758 pid = fork();
759 io = pvec[0];
760 if (pid < 0) {
761 close(pvec[1]);
762 error(" Can't fork to execute recovery");
763 }
764 if (pid == 0) {
765 close(2);
766 dup(1);
767 close(1);
768 dup(pvec[1]);
769 close(pvec[1]);
770 execl(EXRECOVER, "exrecover", svalue(DIRECTORY), file, (char *) 0);
771 die++;
772 close(1);
773 dup(2);
774 error(" No recovery routine");
775 }
776 close(pvec[1]);
777}
778
779/*
780 * Wait for the process (pid an external) to complete.
781 */
782waitfor()
783{
784
785 do
786 rpid = wait(&status);
787 while (rpid != pid && rpid != -1);
788 status = (status >> 8) & 0377;
789}
790
791/*
792 * The end of a recover operation. If the process
793 * exits non-zero, force not edited; otherwise force
794 * a write.
795 */
796revocer()
797{
798
799 waitfor();
800 if (pid == rpid && status != 0)
801 edited = 0;
802 else
803 change();
804}
805
806/*
807 * Extract the next line from the io stream.
808 */
809static char *nextip;
810
811getfile()
812{
813 register short c;
814 register char *lp, *fp;
815
816 lp = linebuf;
817 fp = nextip;
818 do {
819 if (--ninbuf < 0) {
820 ninbuf = read(io, genbuf, LBSIZE) - 1;
821 if (ninbuf < 0) {
822 if (lp != linebuf) {
823 printf(" [Incomplete last line]");
824 break;
825 }
826 return (EOF);
827 }
828 fp = genbuf;
829 }
830 if (lp >= &linebuf[LBSIZE]) {
831 error(" Line too long");
832 }
833 c = *fp++;
834 if (c == 0) {
835 cntnull++;
836 continue;
837 }
838 if (c & QUOTE) {
839 cntodd++;
840 c &= TRIM;
841 if (c == 0)
842 continue;
843 }
844 *lp++ = c;
845 } while (c != '\n');
846 cntch += lp - linebuf;
847 *--lp = 0;
848 nextip = fp;
849 cntln++;
850 return (0);
851}
852
853/*
854 * Write a range onto the io stream.
855 */
856putfile()
857{
858 line *a1;
859 register char *fp, *lp;
860 register int nib;
861
862 a1 = addr1;
863 clrstats();
864 cntln = addr2 - a1 + 1;
865 if (cntln == 0)
866 return;
867 nib = BUFSIZ;
868 fp = genbuf;
869 do {
870 getline(*a1++);
871 lp = linebuf;
872 for (;;) {
873 if (--nib < 0) {
874 nib = fp - genbuf;
875 if (write(io, genbuf, nib) != nib) {
876 wrerror();
877 }
878 cntch += nib;
879 nib = BUFSIZ - 1;
880 fp = genbuf;
881 }
882 if ((*fp++ = *lp++) == 0) {
883 fp[-1] = '\n';
884 break;
885 }
886 }
887 } while (a1 <= addr2);
888 nib = fp - genbuf;
889 if (write(io, genbuf, nib) != nib) {
890 wrerror();
891 }
892 cntch += nib;
893}
894
895/*
896 * A write error has occurred; if the file being written was
897 * the edited file then we consider it to have changed since it is
898 * now likely scrambled.
899 */
900wrerror()
901{
902
903 if (eq(file, savedfile) && edited)
904 change();
905 syserror();
906}
907
908/*
909 * Source command, handles nested sources.
910 * Traps errors since it mungs unit 0 during the source.
911 */
912static short slevel;
913
914source(fil, okfail)
915 char *fil;
916 bool okfail;
917{
918 jmp_buf osetexit;
919 register int saveinp, ointty, oerrno;
920
921 signal(SIGINT, SIG_IGN);
922 saveinp = dup(0);
923 if (saveinp < 0)
924 error("Too many nested sources");
925 close(0);
926 if (open(fil, 0) < 0) {
927 oerrno = errno;
928 setrupt();
929 dup(saveinp);
930 close(saveinp);
931 errno = oerrno;
932 if (!okfail)
933 filioerr(fil);
934 return;
935 }
936 slevel++;
937 ointty = intty;
938 intty = isatty(0);
939 getexit(osetexit);
940 setrupt();
941 if (setexit() == 0)
942 commands(1, 1);
943 else if (slevel > 1) {
944 close(0);
945 dup(saveinp);
946 close(saveinp);
947 slevel--;
948 resexit(osetexit);
949 reset();
950 }
951 intty = ointty;
952 close(0);
953 dup(saveinp);
954 close(saveinp);
955 slevel--;
956 resexit(osetexit);
957}
958
959/*
960 * Clear io statistics before a read or write.
961 */
962clrstats()
963{
964
965 ninbuf = 0;
966 cntch = 0;
967 cntln = 0;
968 cntnull = 0;
969 cntodd = 0;
970}
971
972/*
973 * Io is finished, close the unit and print statistics.
974 */
975iostats()
976{
977
978 close(io);
979 io = -1;
980 if (hush == 0) {
981 if (value(TERSE))
982 printf(" %d/%D", cntln, cntch);
983 else
984 printf(" %d line%s, %D character%s", cntln, plural((long) cntln),
985 cntch, plural(cntch));
986 if (cntnull || cntodd) {
987 printf(" (");
988 if (cntnull) {
989 printf("%D null", cntnull);
990 if (cntodd)
991 printf(", ");
992 }
993 if (cntodd)
994 printf("%D non-ASCII", cntodd);
995 putchar(')');
996 }
997 noonl();
998 flush();
999 }
1000 return (cntnull != 0 || cntodd != 0);
1001}