release 3.4, June 24, 1980
[unix-history] / usr / src / usr.bin / ex / ex_io.c
CommitLineData
544cd46c
MH
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/*
d266c416 9 * File input/output, source, preserve and recover
544cd46c
MH
10 */
11
12/*
13 * Following remember where . was in the previous file for return
14 * on file switching.
15 */
44232d5b
MH
16int altdot;
17int oldadot;
544cd46c 18bool wasalt;
d266c416 19short isalt;
544cd46c
MH
20
21long cntch; /* Count of characters on unit io */
44232d5b 22#ifndef VMUNIX
544cd46c 23short cntln; /* Count of lines " */
44232d5b
MH
24#else
25int cntln;
26#endif
544cd46c
MH
27long cntnull; /* Count of nulls " */
28long cntodd; /* Count of non-ascii characters " */
29
30/*
31 * Parse file name for command encoded by comm.
32 * If comm is E then command is doomed and we are
33 * parsing just so user won't have to retype the name.
34 */
35filename(comm)
36 int comm;
37{
38 register int c = comm, d;
39 register int i;
40
41 d = getchar();
42 if (endcmd(d)) {
43 if (savedfile[0] == 0 && comm != 'f')
44 error("No file|No current filename");
45 CP(file, savedfile);
d266c416
MH
46 wasalt = (isalt > 0) ? isalt-1 : 0;
47 isalt = 0;
544cd46c 48 oldadot = altdot;
d266c416
MH
49 if (c == 'e' || c == 'E')
50 altdot = lineDOT();
544cd46c
MH
51 if (d == EOF)
52 ungetchar(d);
53 } else {
54 ungetchar(d);
55 getone();
56 eol();
57 if (savedfile[0] == 0 && c != 'E' && c != 'e') {
58 c = 'e';
59 edited = 0;
60 }
61 wasalt = strcmp(file, altfile) == 0;
62 oldadot = altdot;
63 switch (c) {
64
65 case 'f':
66 edited = 0;
67 /* fall into ... */
68
69 case 'e':
70 if (savedfile[0]) {
71 altdot = lineDOT();
72 CP(altfile, savedfile);
73 }
74 CP(savedfile, file);
75 break;
76
77 default:
78 if (file[0]) {
79 if (c != 'E')
80 altdot = lineDOT();
81 CP(altfile, file);
82 }
83 break;
84 }
85 }
86 if (hush && comm != 'f' || comm == 'E')
87 return;
88 if (file[0] != 0) {
89 lprintf("\"%s\"", file);
90 if (comm == 'f') {
d266c416
MH
91 if (value(READONLY))
92 printf(" [Read only]");
544cd46c
MH
93 if (!edited)
94 printf(" [Not edited]");
95 if (tchng)
96 printf(" [Modified]");
97 }
98 flush();
99 } else
100 printf("No file ");
101 if (comm == 'f') {
102 if (!(i = lineDOL()))
103 i++;
104 printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(),
105 (long) 100 * lineDOT() / i);
106 }
107}
108
109/*
110 * Get the argument words for a command into genbuf
111 * expanding # and %.
112 */
113getargs()
114{
115 register int c;
116 register char *cp, *fp;
117 static char fpatbuf[32]; /* hence limit on :next +/pat */
118
119 pastwh();
120 if (peekchar() == '+') {
121 for (cp = fpatbuf;;) {
122 c = *cp++ = getchar();
123 if (cp >= &fpatbuf[sizeof(fpatbuf)])
124 error("Pattern too long");
125 if (c == '\\' && isspace(peekchar()))
126 c = getchar();
127 if (c == EOF || isspace(c)) {
128 ungetchar(c);
129 *--cp = 0;
130 firstpat = &fpatbuf[1];
131 break;
132 }
133 }
134 }
135 if (skipend())
136 return (0);
137 CP(genbuf, "echo "); cp = &genbuf[5];
138 for (;;) {
139 c = getchar();
140 if (endcmd(c)) {
141 ungetchar(c);
142 break;
143 }
144 switch (c) {
145
146 case '\\':
d266c416 147 if (any(peekchar(), "#%|"))
544cd46c
MH
148 c = getchar();
149 /* fall into... */
150
151 default:
152 if (cp > &genbuf[LBSIZE - 2])
153flong:
154 error("Argument buffer overflow");
155 *cp++ = c;
156 break;
157
158 case '#':
159 fp = altfile;
160 if (*fp == 0)
161 error("No alternate filename@to substitute for #");
162 goto filexp;
163
164 case '%':
165 fp = savedfile;
166 if (*fp == 0)
167 error("No current filename@to substitute for %%");
168filexp:
169 while (*fp) {
170 if (cp > &genbuf[LBSIZE - 2])
171 goto flong;
172 *cp++ = *fp++;
173 }
174 break;
175 }
176 }
177 *cp = 0;
178 return (1);
179}
180
181/*
182 * Glob the argument words in genbuf, or if no globbing
183 * is implied, just split them up directly.
184 */
185glob(gp)
186 struct glob *gp;
187{
188 int pvec[2];
189 register char **argv = gp->argv;
190 register char *cp = gp->argspac;
191 register int c;
192 char ch;
193 int nleft = NCARGS;
194
195 gp->argc0 = 0;
196 if (gscan() == 0) {
197 register char *v = genbuf + 5; /* strlen("echo ") */
198
199 for (;;) {
200 while (isspace(*v))
201 v++;
202 if (!*v)
203 break;
204 *argv++ = cp;
205 while (*v && !isspace(*v))
206 *cp++ = *v++;
207 *cp++ = 0;
208 gp->argc0++;
209 }
210 *argv = 0;
211 return;
212 }
213 if (pipe(pvec) < 0)
214 error("Can't make pipe to glob");
215 pid = fork();
216 io = pvec[0];
217 if (pid < 0) {
218 close(pvec[1]);
219 error("Can't fork to do glob");
220 }
221 if (pid == 0) {
222 int oerrno;
223
224 close(1);
225 dup(pvec[1]);
226 close(pvec[0]);
887e3e0d
MH
227 close(2); /* so errors don't mess up the screen */
228 open("/dev/null", 1);
544cd46c
MH
229 execl(svalue(SHELL), "sh", "-c", genbuf, 0);
230 oerrno = errno; close(1); dup(2); errno = oerrno;
231 filioerr(svalue(SHELL));
232 }
233 close(pvec[1]);
234 do {
235 *argv = cp;
236 for (;;) {
237 if (read(io, &ch, 1) != 1) {
238 close(io);
239 c = -1;
240 } else
241 c = ch & TRIM;
242 if (c <= 0 || isspace(c))
243 break;
244 *cp++ = c;
245 if (--nleft <= 0)
246 error("Arg list too long");
247 }
248 if (cp != *argv) {
249 --nleft;
250 *cp++ = 0;
251 gp->argc0++;
252 if (gp->argc0 >= NARGS)
253 error("Arg list too long");
254 argv++;
255 }
256 } while (c >= 0);
257 waitfor();
258 if (gp->argc0 == 0)
887e3e0d 259 error("No match");
544cd46c
MH
260}
261
262/*
263 * Scan genbuf for shell metacharacters.
264 * Set is union of v7 shell and csh metas.
265 */
266gscan()
267{
268 register char *cp;
269
270 for (cp = genbuf; *cp; cp++)
271 if (any(*cp, "~{[*?$`'\"\\"))
272 return (1);
273 return (0);
274}
275
276/*
277 * Parse one filename into file.
278 */
279getone()
280{
281 register char *str;
282 struct glob G;
283
284 if (getargs() == 0)
285 error("Missing filename");
286 glob(&G);
287 if (G.argc0 > 1)
288 error("Ambiguous|Too many file names");
289 str = G.argv[G.argc0 - 1];
290 if (strlen(str) > FNSIZE - 4)
291 error("Filename too long");
292samef:
293 CP(file, str);
294}
295
296/*
297 * Read a file from the world.
298 * C is command, 'e' if this really an edit (or a recover).
299 */
300rop(c)
301 int c;
302{
303 register int i;
304 struct stat stbuf;
305 short magic;
306
307 io = open(file, 0);
308 if (io < 0) {
309 if (c == 'e' && errno == ENOENT)
310 edited++;
311 syserror();
312 }
313 if (fstat(io, &stbuf))
314 syserror();
315 switch (stbuf.st_mode & S_IFMT) {
316
317 case S_IFBLK:
318 error(" Block special file");
319
320 case S_IFCHR:
321 if (isatty(io))
322 error(" Teletype");
323 if (samei(&stbuf, "/dev/null"))
324 break;
325 error(" Character special file");
326
327 case S_IFDIR:
328 error(" Directory");
329
330 case S_IFREG:
d266c416
MH
331 if (xflag)
332 break;
544cd46c
MH
333 i = read(io, (char *) &magic, sizeof(magic));
334 lseek(io, 0l, 0);
335 if (i != sizeof(magic))
336 break;
337 switch (magic) {
338
339 case 0405:
340 case 0407:
341 case 0410:
342 case 0411:
343 error(" Executable");
344
345 case 0177545:
346 case 0177555:
347 error(" Archive");
348
349 default:
350 if (magic & 0100200)
351 error(" Non-ascii file");
352 break;
353 }
354 }
355 if (c == 'r')
356 setdot();
357 else
358 setall();
887e3e0d 359 if (FIXUNDO && inopen && c == 'r')
544cd46c
MH
360 undap1 = undap2 = dot + 1;
361 rop2();
362 rop3(c);
363}
364
365rop2()
366{
367
368 deletenone();
369 clrstats();
370 ignore(append(getfile, addr2));
371}
372
373rop3(c)
374 int c;
375{
376
377 if (iostats() == 0 && c == 'e')
378 edited++;
379 if (c == 'e') {
380 if (wasalt || firstpat) {
381 register line *addr = zero + oldadot;
382
383 if (addr > dol)
384 addr = dol;
385 if (firstpat) {
386 globp = (*firstpat) ? firstpat : "$";
387 commands(1,1);
388 firstpat = 0;
389 } else if (addr >= one) {
390 if (inopen)
391 dot = addr;
392 markpr(addr);
393 } else
394 goto other;
395 } else
396other:
397 if (dol > zero) {
398 if (inopen)
399 dot = one;
400 markpr(one);
401 }
887e3e0d
MH
402 if(FIXUNDO)
403 undkind = UNDNONE;
544cd46c
MH
404 if (inopen) {
405 vcline = 0;
406 vreplace(0, LINES, lineDOL());
407 }
408 }
409 if (laste) {
410 laste = 0;
411 sync();
412 }
413}
414
415/*
416 * Are these two really the same inode?
417 */
418samei(sp, cp)
419 struct stat *sp;
420 char *cp;
421{
422 struct stat stb;
423
424 if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev)
425 return (0);
426 return (sp->st_ino == stb.st_ino);
427}
428
429/* Returns from edited() */
430#define EDF 0 /* Edited file */
431#define NOTEDF -1 /* Not edited file */
432#define PARTBUF 1 /* Write of partial buffer to Edited file */
433
434/*
435 * Write a file.
436 */
437wop(dofname)
438bool dofname; /* if 1 call filename, else use savedfile */
439{
440 register int c, exclam, nonexist;
441 line *saddr1, *saddr2;
442 struct stat stbuf;
443
444 c = 0;
445 exclam = 0;
446 if (dofname) {
447 if (peekchar() == '!')
448 exclam++, ignchar();
449 ignore(skipwh());
450 while (peekchar() == '>')
451 ignchar(), c++, ignore(skipwh());
452 if (c != 0 && c != 2)
453 error("Write forms are 'w' and 'w>>'");
454 filename('w');
455 } else {
887e3e0d
MH
456 if (savedfile[0] == 0)
457 error("No file|No current filename");
544cd46c
MH
458 saddr1=addr1;
459 saddr2=addr2;
460 addr1=one;
461 addr2=dol;
462 CP(file, savedfile);
463 if (inopen) {
464 vclrech(0);
465 splitw++;
466 }
467 lprintf("\"%s\"", file);
468 }
469 nonexist = stat(file, &stbuf);
470 switch (c) {
471
472 case 0:
d266c416
MH
473 if (!exclam && (!value(WRITEANY) || value(READONLY)))
474 switch (edfile()) {
544cd46c
MH
475
476 case NOTEDF:
477 if (nonexist)
478 break;
479 if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
480 if (samei(&stbuf, "/dev/null"))
481 break;
482 if (samei(&stbuf, "/dev/tty"))
483 break;
484 }
485 io = open(file, 1);
486 if (io < 0)
487 syserror();
488 if (!isatty(io))
489 serror(" File exists| File exists - use \"w! %s\" to overwrite", file);
490 close(io);
491 break;
492
d266c416
MH
493 case EDF:
494 if (value(READONLY))
495 error(" File is read only");
496 break;
497
544cd46c 498 case PARTBUF:
d266c416
MH
499 if (value(READONLY))
500 error(" File is read only");
544cd46c
MH
501 error(" Use \"w!\" to write partial buffer");
502 }
503cre:
44232d5b 504/*
544cd46c 505 synctmp();
44232d5b 506*/
544cd46c
MH
507#ifdef V6
508 io = creat(file, 0644);
509#else
510 io = creat(file, 0666);
511#endif
512 if (io < 0)
513 syserror();
514 if (hush == 0)
515 if (nonexist)
516 printf(" [New file]");
517 else if (value(WRITEANY) && edfile() != EDF)
518 printf(" [Existing file]");
519 break;
520
521 case 2:
522 io = open(file, 1);
523 if (io < 0) {
524 if (exclam || value(WRITEANY))
525 goto cre;
526 syserror();
527 }
528 lseek(io, 0l, 2);
529 break;
530 }
531 putfile();
532 ignore(iostats());
533 if (c != 2 && addr1 == one && addr2 == dol) {
534 if (eq(file, savedfile))
535 edited = 1;
536 sync();
537 }
538 if (!dofname) {
539 addr1 = saddr1;
540 addr2 = saddr2;
541 }
542}
543
544/*
545 * Is file the edited file?
546 * Work here is that it is not considered edited
547 * if this is a partial buffer, and distinguish
548 * all cases.
549 */
550edfile()
551{
552
553 if (!edited || !eq(file, savedfile))
554 return (NOTEDF);
555 return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
556}
557
544cd46c
MH
558/*
559 * Extract the next line from the io stream.
560 */
561static char *nextip;
562
563getfile()
564{
565 register short c;
566 register char *lp, *fp;
567
568 lp = linebuf;
569 fp = nextip;
570 do {
571 if (--ninbuf < 0) {
572 ninbuf = read(io, genbuf, LBSIZE) - 1;
573 if (ninbuf < 0) {
574 if (lp != linebuf) {
d266c416 575 lp++;
544cd46c
MH
576 printf(" [Incomplete last line]");
577 break;
578 }
579 return (EOF);
580 }
581 fp = genbuf;
d266c416
MH
582 while(fp < &genbuf[ninbuf]) {
583 if (*fp++ & 0200) {
584 if (kflag)
585 crblock(perm, genbuf, ninbuf+1,
586cntch);
587 break;
588 }
589 }
590 fp = genbuf;
591 cntch += ninbuf+1;
544cd46c
MH
592 }
593 if (lp >= &linebuf[LBSIZE]) {
594 error(" Line too long");
595 }
596 c = *fp++;
597 if (c == 0) {
598 cntnull++;
599 continue;
600 }
601 if (c & QUOTE) {
602 cntodd++;
603 c &= TRIM;
604 if (c == 0)
605 continue;
606 }
607 *lp++ = c;
608 } while (c != '\n');
544cd46c
MH
609 *--lp = 0;
610 nextip = fp;
611 cntln++;
612 return (0);
613}
614
615/*
616 * Write a range onto the io stream.
617 */
618putfile()
619{
620 line *a1;
621 register char *fp, *lp;
622 register int nib;
623
624 a1 = addr1;
625 clrstats();
626 cntln = addr2 - a1 + 1;
627 if (cntln == 0)
628 return;
629 nib = BUFSIZ;
630 fp = genbuf;
631 do {
632 getline(*a1++);
633 lp = linebuf;
634 for (;;) {
635 if (--nib < 0) {
636 nib = fp - genbuf;
d266c416
MH
637 if(kflag)
638 crblock(perm, genbuf, nib, cntch);
544cd46c
MH
639 if (write(io, genbuf, nib) != nib) {
640 wrerror();
641 }
642 cntch += nib;
643 nib = BUFSIZ - 1;
644 fp = genbuf;
645 }
646 if ((*fp++ = *lp++) == 0) {
647 fp[-1] = '\n';
648 break;
649 }
650 }
651 } while (a1 <= addr2);
652 nib = fp - genbuf;
d266c416
MH
653 if(kflag)
654 crblock(perm, genbuf, nib, cntch);
544cd46c
MH
655 if (write(io, genbuf, nib) != nib) {
656 wrerror();
657 }
658 cntch += nib;
659}
660
661/*
662 * A write error has occurred; if the file being written was
663 * the edited file then we consider it to have changed since it is
664 * now likely scrambled.
665 */
666wrerror()
667{
668
669 if (eq(file, savedfile) && edited)
670 change();
671 syserror();
672}
673
674/*
675 * Source command, handles nested sources.
676 * Traps errors since it mungs unit 0 during the source.
677 */
d266c416
MH
678short slevel;
679short ttyindes;
544cd46c
MH
680
681source(fil, okfail)
682 char *fil;
683 bool okfail;
684{
685 jmp_buf osetexit;
686 register int saveinp, ointty, oerrno;
544cd46c
MH
687
688 signal(SIGINT, SIG_IGN);
689 saveinp = dup(0);
690 if (saveinp < 0)
691 error("Too many nested sources");
d266c416
MH
692 if (slevel <= 0)
693 ttyindes = saveinp;
544cd46c
MH
694 close(0);
695 if (open(fil, 0) < 0) {
696 oerrno = errno;
697 setrupt();
698 dup(saveinp);
699 close(saveinp);
700 errno = oerrno;
701 if (!okfail)
702 filioerr(fil);
703 return;
704 }
705 slevel++;
706 ointty = intty;
707 intty = isatty(0);
708 oprompt = value(PROMPT);
709 value(PROMPT) &= intty;
710 getexit(osetexit);
711 setrupt();
712 if (setexit() == 0)
713 commands(1, 1);
714 else if (slevel > 1) {
715 close(0);
716 dup(saveinp);
717 close(saveinp);
718 slevel--;
719 resexit(osetexit);
720 reset();
721 }
722 intty = ointty;
723 value(PROMPT) = oprompt;
724 close(0);
725 dup(saveinp);
726 close(saveinp);
727 slevel--;
728 resexit(osetexit);
729}
730
731/*
732 * Clear io statistics before a read or write.
733 */
734clrstats()
735{
736
737 ninbuf = 0;
738 cntch = 0;
739 cntln = 0;
740 cntnull = 0;
741 cntodd = 0;
742}
743
744/*
745 * Io is finished, close the unit and print statistics.
746 */
747iostats()
748{
749
750 close(io);
751 io = -1;
752 if (hush == 0) {
753 if (value(TERSE))
754 printf(" %d/%D", cntln, cntch);
755 else
756 printf(" %d line%s, %D character%s", cntln, plural((long) cntln),
757 cntch, plural(cntch));
758 if (cntnull || cntodd) {
759 printf(" (");
760 if (cntnull) {
761 printf("%D null", cntnull);
762 if (cntodd)
763 printf(", ");
764 }
765 if (cntodd)
766 printf("%D non-ASCII", cntodd);
767 putchar(')');
768 }
769 noonl();
770 flush();
771 }
772 return (cntnull != 0 || cntodd != 0);
773}