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