fixed various bugs prior to 3.6 release
[unix-history] / usr / src / usr.bin / ex / ex_io.c
CommitLineData
7c4625ef 1/* Copyright (c) 1980 Regents of the University of California */
f0f2d980 2static char *sccsid = "@(#)ex_io.c 6.2 %G%";
544cd46c
MH
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/*
d266c416 10 * File input/output, source, preserve and recover
544cd46c
MH
11 */
12
13/*
14 * Following remember where . was in the previous file for return
15 * on file switching.
16 */
44232d5b
MH
17int altdot;
18int oldadot;
544cd46c 19bool wasalt;
d266c416 20short isalt;
544cd46c
MH
21
22long cntch; /* Count of characters on unit io */
44232d5b 23#ifndef VMUNIX
544cd46c 24short cntln; /* Count of lines " */
44232d5b
MH
25#else
26int cntln;
27#endif
544cd46c
MH
28long cntnull; /* Count of nulls " */
29long 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 */
36filename(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);
d266c416
MH
47 wasalt = (isalt > 0) ? isalt-1 : 0;
48 isalt = 0;
544cd46c 49 oldadot = altdot;
d266c416
MH
50 if (c == 'e' || c == 'E')
51 altdot = lineDOT();
544cd46c
MH
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') {
d266c416
MH
92 if (value(READONLY))
93 printf(" [Read only]");
544cd46c
MH
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 */
114getargs()
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 '\\':
d266c416 148 if (any(peekchar(), "#%|"))
544cd46c
MH
149 c = getchar();
150 /* fall into... */
151
152 default:
153 if (cp > &genbuf[LBSIZE - 2])
154flong:
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 %%");
169filexp:
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 */
186glob(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]);
887e3e0d
MH
228 close(2); /* so errors don't mess up the screen */
229 open("/dev/null", 1);
544cd46c
MH
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)
887e3e0d 260 error("No match");
544cd46c
MH
261}
262
263/*
264 * Scan genbuf for shell metacharacters.
265 * Set is union of v7 shell and csh metas.
266 */
267gscan()
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 */
280getone()
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");
293samef:
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 */
301rop(c)
302 int c;
303{
304 register int i;
305 struct stat stbuf;
306 short magic;
dcbc1067
MH
307 static int ovro; /* old value(READONLY) */
308 static int denied; /* 1 if READONLY was set due to file permissions */
544cd46c
MH
309
310 io = open(file, 0);
311 if (io < 0) {
04379bab 312 if (c == 'e' && errno == ENOENT) {
544cd46c 313 edited++;
04379bab
MH
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 }
544cd46c
MH
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:
1807bf33 345#ifdef CRYPT
d266c416
MH
346 if (xflag)
347 break;
1807bf33 348#endif
544cd46c
MH
349 i = read(io, (char *) &magic, sizeof(magic));
350 lseek(io, 0l, 0);
351 if (i != sizeof(magic))
352 break;
353 switch (magic) {
354
f0f2d980
MH
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 */
544cd46c
MH
362 error(" Executable");
363
f0f2d980
MH
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 */
544cd46c
MH
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 }
04379bab
MH
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 }
dcbc1067 392 }
fdc664ec 393 if (value(READONLY)) {
718337ed 394 printf(" [Read only]");
fdc664ec
MH
395 flush();
396 }
544cd46c
MH
397 if (c == 'r')
398 setdot();
399 else
400 setall();
887e3e0d 401 if (FIXUNDO && inopen && c == 'r')
544cd46c
MH
402 undap1 = undap2 = dot + 1;
403 rop2();
404 rop3(c);
405}
406
407rop2()
408{
409
410 deletenone();
411 clrstats();
412 ignore(append(getfile, addr2));
413}
414
415rop3(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
438other:
439 if (dol > zero) {
440 if (inopen)
441 dot = one;
442 markpr(one);
443 }
887e3e0d
MH
444 if(FIXUNDO)
445 undkind = UNDNONE;
544cd46c
MH
446 if (inopen) {
447 vcline = 0;
448 vreplace(0, LINES, lineDOL());
449 }
450 }
451 if (laste) {
7c4625ef
MH
452#ifdef VMUNIX
453 tlaste();
454#endif
544cd46c
MH
455 laste = 0;
456 sync();
457 }
458}
459
460/*
461 * Are these two really the same inode?
462 */
463samei(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 */
482wop(dofname)
483bool 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 {
887e3e0d
MH
501 if (savedfile[0] == 0)
502 error("No file|No current filename");
544cd46c
MH
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:
d266c416
MH
518 if (!exclam && (!value(WRITEANY) || value(READONLY)))
519 switch (edfile()) {
544cd46c
MH
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
d266c416
MH
538 case EDF:
539 if (value(READONLY))
540 error(" File is read only");
541 break;
542
544cd46c 543 case PARTBUF:
d266c416
MH
544 if (value(READONLY))
545 error(" File is read only");
544cd46c
MH
546 error(" Use \"w!\" to write partial buffer");
547 }
548cre:
44232d5b 549/*
544cd46c 550 synctmp();
44232d5b 551*/
544cd46c
MH
552#ifdef V6
553 io = creat(file, 0644);
554#else
555 io = creat(file, 0666);
556#endif
557 if (io < 0)
558 syserror();
04379bab 559 writing = 1;
544cd46c
MH
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 }
04379bab 588 writing = 0;
544cd46c
MH
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 */
597edfile()
598{
599
600 if (!edited || !eq(file, savedfile))
601 return (NOTEDF);
602 return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
603}
604
544cd46c
MH
605/*
606 * Extract the next line from the io stream.
607 */
608static char *nextip;
609
610getfile()
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) {
d266c416 622 lp++;
544cd46c
MH
623 printf(" [Incomplete last line]");
624 break;
625 }
626 return (EOF);
627 }
1807bf33 628#ifdef CRYPT
544cd46c 629 fp = genbuf;
d266c416
MH
630 while(fp < &genbuf[ninbuf]) {
631 if (*fp++ & 0200) {
632 if (kflag)
633 crblock(perm, genbuf, ninbuf+1,
634cntch);
635 break;
636 }
637 }
1807bf33 638#endif
d266c416
MH
639 fp = genbuf;
640 cntch += ninbuf+1;
544cd46c
MH
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');
544cd46c
MH
658 *--lp = 0;
659 nextip = fp;
660 cntln++;
661 return (0);
662}
663
664/*
665 * Write a range onto the io stream.
666 */
667putfile()
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;
1807bf33 686#ifdef CRYPT
d266c416
MH
687 if(kflag)
688 crblock(perm, genbuf, nib, cntch);
1807bf33 689#endif
544cd46c
MH
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;
1807bf33 704#ifdef CRYPT
d266c416
MH
705 if(kflag)
706 crblock(perm, genbuf, nib, cntch);
1807bf33 707#endif
544cd46c
MH
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 */
719wrerror()
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 */
d266c416
MH
731short slevel;
732short ttyindes;
544cd46c
MH
733
734source(fil, okfail)
735 char *fil;
736 bool okfail;
737{
738 jmp_buf osetexit;
739 register int saveinp, ointty, oerrno;
04379bab 740 char savepeekc, *saveglobp;
544cd46c
MH
741
742 signal(SIGINT, SIG_IGN);
743 saveinp = dup(0);
04379bab
MH
744 savepeekc = peekc;
745 saveglobp = globp;
746 peekc = 0; globp = 0;
544cd46c
MH
747 if (saveinp < 0)
748 error("Too many nested sources");
d266c416
MH
749 if (slevel <= 0)
750 ttyindes = saveinp;
544cd46c
MH
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);
04379bab
MH
784 globp = saveglobp;
785 peekc = savepeekc;
544cd46c
MH
786 slevel--;
787 resexit(osetexit);
788}
789
790/*
791 * Clear io statistics before a read or write.
792 */
793clrstats()
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 */
806iostats()
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}