mail back errors (perhaps this is why mail gets lost.....)
[unix-history] / usr / src / bin / csh / csh.c
CommitLineData
3d48fab8 1static char *sccsid = "@(#)csh.c 4.4 %G%";
04d3b78c
BJ
2
3#include "sh.h"
4#include <sys/ioctl.h>
5/*
6 * C Shell
7 *
8 * Bill Joy, UC Berkeley, California, USA
9 * October 1978, May 1980
10 *
11 * Jim Kulp, IIASA, Laxenburg, Austria
12 * April 1980
13 */
14
15char *pathlist[] = { ".", "/usr/ucb", "/bin", "/usr/bin", 0 };
63af20f7 16char *dumphist[] = { "history", "-c", "+1000" };
04d3b78c
BJ
17char HIST = '!';
18char HISTSUB = '^';
19bool nofile;
20bool reenter;
21bool nverbose;
22bool nexececho;
23bool quitit;
24bool fast;
25bool prompt = 1;
63af20f7 26bool enterhist = 0;
04d3b78c
BJ
27
28main(c, av)
29 int c;
30 char **av;
31{
32 register char **v, *cp;
33 register int f;
34
35 settimes(); /* Immed. estab. timing base */
36 v = av;
37 if (eq(v[0], "a.out")) /* A.out's are quittable */
38 quitit = 1;
39 uid = getuid();
40 loginsh = **v == '-';
41 if (loginsh)
42 time(&chktim);
43
44 /*
45 * Move the descriptors to safe places.
46 * The variable didfds is 0 while we have only FSH* to work with.
47 * When didfds is true, we have 0,1,2 and prefer to use these.
48 */
49 initdesc();
50
51 /*
52 * Initialize the shell variables.
53 * ARGV and PROMPT are initialized later.
54 * STATUS is also munged in several places.
55 * CHILD is munged when forking/waiting
56 */
57
58 set("status", "0");
59 dinit(cp = getenv("HOME")); /* dinit thinks that HOME == cwd in a
60 * login shell */
61 if (cp == NOSTR)
62 fast++; /* No home -> can't read scripts */
63 else
64 set("home", savestr(cp));
65 /*
66 * Grab other useful things from the environment.
67 * Should we grab everything??
68 */
69 if ((cp = getenv("USER")) != NOSTR)
70 set("user", savestr(cp));
71 if ((cp = getenv("TERM")) != NOSTR)
72 set("term", savestr(cp));
73 /*
74 * Re-initialize path if set in environment
75 */
76 if ((cp = getenv("PATH")) == NOSTR)
77 set1("path", saveblk(pathlist), &shvhed);
78 else {
79 register unsigned i = 0;
80 register char *dp;
81 register char **pv;
82
83 for (dp = cp; *dp; dp++)
84 if (*dp == ':')
85 i++;
86 pv = (char **)calloc(i+2, sizeof (char **));
87 for (dp = cp, i = 0; ;)
88 if (*dp == ':') {
89 *dp = 0;
90 pv[i++] = savestr(*cp ? cp : ".");
91 *dp++ = ':';
92 cp = dp;
93 } else if (*dp++ == 0) {
94 pv[i++] = savestr(*cp ? cp : ".");
95 break;
96 }
97 pv[i] = 0;
98 set1("path", pv, &shvhed);
99 }
100 set("shell", SHELLPATH);
101
102 doldol = putn(getpid()); /* For $$ */
103 shtemp = strspl("/tmp/sh", doldol); /* For << */
104
105 /*
106 * Record the interrupt states from the parent process.
107 * If the parent is non-interruptible our hand must be forced
108 * or we (and our children) won't be either.
109 * Our children inherit termination from our parent.
110 * We catch it only if we are the login shell.
111 */
112 parintr = signal(SIGINT, SIG_IGN); /* parents interruptibility */
113 sigset(SIGINT, parintr); /* ... restore */
114 parterm = signal(SIGTERM, SIG_IGN); /* parents terminability */
115 signal(SIGTERM, parterm); /* ... restore */
116
117 /*
118 * Process the arguments.
119 *
120 * Note that processing of -v/-x is actually delayed till after
121 * script processing.
122 *
123 * We set the first character of our name to be '-' if we are
124 * a shell running interruptible commands. Many programs which
125 * examine ps'es use this to filter such shells out.
126 */
127 c--, v++;
128 while (c > 0 && (cp = v[0])[0] == '-') {
129 do switch (*cp++) {
130
131 case 0: /* - Interruptible, no prompt */
132 prompt = 0;
133 setintr++;
134 nofile++;
135 break;
136
137 case 'c': /* -c Command input from arg */
138 if (c == 1)
139 exit(0);
140 c--, v++;
141 arginp = v[0];
142 prompt = 0;
143 nofile++;
144 break;
145
146 case 'e': /* -e Exit on any error */
147 exiterr++;
148 break;
149
150 case 'f': /* -f Fast start */
151 fast++;
152 break;
153
154 case 'i': /* -i Interactive, even if !intty */
155 intact++;
156 nofile++;
157 break;
158
159 case 'n': /* -n Don't execute */
160 noexec++;
161 break;
162
163 case 'q': /* -q (Undoc'd) ... die on quit */
164 quitit = 1;
165 break;
166
167 case 's': /* -s Read from std input */
168 nofile++;
169 break;
170
171 case 't': /* -t Read one line from input */
172 onelflg = 2;
173 prompt = 0;
174 nofile++;
175 break;
176
177 case 'v': /* -v Echo hist expanded input */
178 nverbose = 1; /* ... later */
179 break;
180
181 case 'x': /* -x Echo just before execution */
182 nexececho = 1; /* ... later */
183 break;
184
185 case 'V': /* -V Echo hist expanded input */
186 setNS("verbose"); /* NOW! */
187 break;
188
189 case 'X': /* -X Echo just before execution */
190 setNS("echo"); /* NOW! */
191 break;
192
193 } while (*cp);
194 v++, c--;
195 }
196
197 if (quitit) /* With all due haste, for debugging */
198 signal(SIGQUIT, SIG_DFL);
199
200 /*
201 * Unless prevented by -, -c, -i, -s, or -t, if there
202 * are remaining arguments the first of them is the name
203 * of a shell file from which to read commands.
204 */
205 if (nofile == 0 && c > 0) {
206 nofile = open(v[0], 0);
207 if (nofile < 0) {
208 child++; /* So this ... */
209 Perror(v[0]); /* ... doesn't return */
210 }
211 file = v[0];
212 SHIN = dmove(nofile, FSHIN); /* Replace FSHIN */
213 prompt = 0;
214 c--, v++;
215 }
216 /*
217 * Consider input a tty if it really is or we are interactive.
218 */
219 intty = intact || isatty(SHIN);
220 /*
221 * Decide whether we should play with signals or not.
222 * If we are explicitly told (via -i, or -) or we are a login
223 * shell (arg0 starts with -) or the input and output are both
224 * the ttys("csh", or "csh</dev/ttyx>/dev/ttyx")
225 * Note that in only the login shell is it likely that parent
226 * may have set signals to be ignored
227 */
228 if (loginsh || intact || intty && isatty(SHOUT))
229 setintr = 1;
230#ifdef TELL
231 settell();
232#endif
233 /*
234 * Save the remaining arguments in argv.
235 */
236 setq("argv", v, &shvhed);
237
238 /*
239 * Set up the prompt.
240 */
241 if (prompt)
242 set("prompt", uid == 0 ? "# " : "% ");
243
244 /*
245 * If we are an interactive shell, then start fiddling
246 * with the signals; this is a tricky game.
247 */
248 shpgrp = getpgrp(0);
249 opgrp = tpgrp = -1;
250 oldisc = -1;
251 if (setintr) {
252 **av = '-';
253 if (!quitit) /* Wary! */
254 signal(SIGQUIT, SIG_IGN);
255 sigset(SIGINT, pintr);
256 sighold(SIGINT);
257 signal(SIGTERM, SIG_IGN);
258 if (quitit == 0 && arginp == 0) {
259 signal(SIGTSTP, SIG_IGN);
260 signal(SIGTTIN, SIG_IGN);
261 signal(SIGTTOU, SIG_IGN);
262 /*
263 * Wait till in foreground, in case someone
264 * stupidly runs
265 * csh &
266 * dont want to try to grab away the tty.
267 */
268 if (isatty(FSHDIAG))
269 f = FSHDIAG;
270 else if (isatty(FSHOUT))
271 f = FSHOUT;
272 else if (isatty(OLDSTD))
273 f = OLDSTD;
274 else
275 f = -1;
276retry:
277 if (ioctl(f, TIOCGPGRP, &tpgrp) == 0 && tpgrp != -1) {
278 int ldisc;
279 if (tpgrp != shpgrp) {
280 int old = sigsys(SIGTTIN, SIG_DFL);
281 kill(0, SIGTTIN);
282 sigsys(SIGTTIN, old);
283 goto retry;
284 }
285 if (ioctl(f, TIOCGETD, &oldisc) != 0)
286 goto notty;
287 if (oldisc != NTTYDISC) {
288 printf("Switching to new tty driver...\n");
289 ldisc = NTTYDISC;
290 ioctl(f, TIOCSETD, &ldisc);
291 } else
292 oldisc = -1;
293 opgrp = shpgrp;
294 shpgrp = getpid();
295 tpgrp = shpgrp;
296 ioctl(f, TIOCSPGRP, &shpgrp);
297 setpgrp(0, shpgrp);
298 dcopy(f, FSHTTY);
299 ioctl(FSHTTY, FIOCLEX, 0);
300 } else {
301notty:
302 printf("Warning: no access to tty; thus no job control in this shell...\n");
303 tpgrp = -1;
304 }
305 }
306 }
307 sigset(SIGCHLD, pchild); /* while signals not ready */
308
309 /*
310 * Set an exit here in case of an interrupt or error reading
311 * the shell start-up scripts.
312 */
313 setexit();
314 haderr = 0; /* In case second time through */
315 if (!fast && reenter == 0) {
316 reenter++;
317 /* Will have value("home") here because set fast if don't */
318 srccat(value("home"), "/.cshrc");
319 if (!fast && !arginp && !onelflg)
320 dohash();
321 if (loginsh) {
04d3b78c 322 srccat(value("home"), "/.login");
63af20f7
KM
323 enterhist = 1;
324 srccat(value("home"), "/.history");
325 enterhist = 0;
04d3b78c
BJ
326 }
327 }
328
329 /*
330 * Now are ready for the -v and -x flags
331 */
332 if (nverbose)
333 setNS("verbose");
334 if (nexececho)
335 setNS("echo");
336
337 /*
338 * All the rest of the world is inside this call.
339 * The argument to process indicates whether it should
340 * catch "error unwinds". Thus if we are a interactive shell
341 * our call here will never return by being blown past on an error.
342 */
343 process(setintr);
344
345 /*
346 * Mop-up.
347 */
348 if (loginsh) {
349 printf("logout\n");
350 close(SHIN);
351 child++;
352 goodbye();
353 }
354 exitstat();
355}
356
357untty()
358{
359
360 if (tpgrp > 0) {
361 setpgrp(0, opgrp);
362 ioctl(FSHTTY, TIOCSPGRP, &opgrp);
363 if (oldisc != -1 && oldisc != NTTYDISC) {
364 printf("\nReverting to old tty driver...\n");
365 ioctl(FSHTTY, TIOCSETD, &oldisc);
366 }
367 }
368}
369
370importpath(cp)
371char *cp;
372{
373 register int i = 0;
374 register char *dp;
375 register char **pv;
376 int c;
377 static char dot[2] = {'.', 0};
378
379 for (dp = cp; *dp; dp++)
380 if (*dp == ':')
381 i++;
382 /*
383 * i+2 where i is the number of colons in the path.
384 * There are i+1 directories in the path plus we need
385 * room for a zero terminator.
386 */
387 pv = (char **) calloc(i+2, sizeof (char **));
388 dp = cp;
389 i = 0;
390 if (*dp)
391 for (;;) {
392 if ((c = *dp) == ':' || c == 0) {
393 *dp = 0;
394 pv[i++] = savestr(*cp ? cp : dot);
395 if (c) {
396 cp = dp + 1;
397 *dp = ':';
398 } else
399 break;
400 }
401 dp++;
402 }
403 pv[i] = 0;
404 set1("path", pv, &shvhed);
405}
406
407/*
408 * Source to the file which is the catenation of the argument names.
409 */
410srccat(cp, dp)
411 char *cp, *dp;
412{
413 register char *ep = strspl(cp, dp);
414 register int unit = dmove(open(ep, 0), -1);
415
416 /* ioctl(unit, FIOCLEX, NULL); */
417 xfree(ep);
418#ifdef INGRES
419 srcunit(unit, 0);
420#else
421 srcunit(unit, 1);
422#endif
423}
424
425/*
426 * Source to a unit. If onlyown it must be our file or our group or
427 * we don't chance it. This occurs on ".cshrc"s and the like.
428 */
429srcunit(unit, onlyown)
430 register int unit;
431 bool onlyown;
432{
433 /* We have to push down a lot of state here */
434 /* All this could go into a structure */
435 int oSHIN = -1, oldintty = intty;
436 struct whyle *oldwhyl = whyles;
437 char *ogointr = gointr, *oarginp = arginp;
438 char *oevalp = evalp, **oevalvec = evalvec;
439 int oonelflg = onelflg;
440#ifdef TELL
441 bool otell = cantell;
442#endif
443 struct Bin saveB;
444
445 /* The (few) real local variables */
446 jmp_buf oldexit;
447 int reenter;
448
449 if (unit < 0)
450 return;
451 if (didfds)
452 donefds();
453 if (onlyown) {
454 struct stat stb;
455
456 if (fstat(unit, &stb) < 0 || (stb.st_uid != uid && stb.st_gid != getgid())) {
457 close(unit);
458 return;
459 }
460 }
461
462 /*
463 * There is a critical section here while we are pushing down the
464 * input stream since we have stuff in different structures.
465 * If we weren't careful an interrupt could corrupt SHIN's Bin
466 * structure and kill the shell.
467 *
468 * We could avoid the critical region by grouping all the stuff
469 * in a single structure and pointing at it to move it all at
470 * once. This is less efficient globally on many variable references
471 * however.
472 */
473 getexit(oldexit);
474 reenter = 0;
475 if (setintr)
476 sighold(SIGINT);
477 setexit();
478 reenter++;
479 if (reenter == 1) {
480 /* Setup the new values of the state stuff saved above */
481 copy((char *)&saveB, (char *)&B, sizeof saveB);
482 fbuf = (char **) 0;
483 fseekp = feobp = fblocks = 0;
484 oSHIN = SHIN, SHIN = unit, arginp = 0, onelflg = 0;
485 intty = isatty(SHIN), whyles = 0, gointr = 0;
486 evalvec = 0; evalp = 0;
487 /*
488 * Now if we are allowing commands to be interrupted,
489 * we let ourselves be interrupted.
490 */
491 if (setintr)
492 sigrelse(SIGINT);
493#ifdef TELL
494 settell();
495#endif
496 process(0); /* 0 -> blow away on errors */
497 }
498 if (setintr)
499 sigrelse(SIGINT);
500 if (oSHIN >= 0) {
501 register int i;
502
503 /* We made it to the new state... free up its storage */
504 /* This code could get run twice but xfree doesn't care */
505 for (i = 0; i < fblocks; i++)
506 xfree(fbuf[i]);
507 xfree((char *)fbuf);
508
509 /* Reset input arena */
510 copy((char *)&B, (char *)&saveB, sizeof B);
511
512 close(SHIN), SHIN = oSHIN;
513 arginp = oarginp, onelflg = oonelflg;
514 evalp = oevalp, evalvec = oevalvec;
515 intty = oldintty, whyles = oldwhyl, gointr = ogointr;
516#ifdef TELL
517 cantell = otell;
518#endif
519 }
520
521 resexit(oldexit);
522 /*
523 * If process reset() (effectively an unwind) then
524 * we must also unwind.
525 */
526 if (reenter >= 2)
527 error(NOSTR);
528}
529
530goodbye()
531{
63af20f7
KM
532 char buf[BUFSIZ];
533 int fp, ftmp, oldidfds;
04d3b78c
BJ
534
535 if (loginsh) {
63af20f7
KM
536 strcpy(buf, value("home"));
537 strcat(buf, "/.history");
538 fp = creat(buf, 0777);
539 if (fp != -1) {
540 oldidfds = didfds;
541 didfds = 0;
542 ftmp = SHOUT;
543 SHOUT = fp;
544 dohist(dumphist);
545 close(fp);
546 SHOUT = ftmp;
547 didfds = oldidfds;
548 }
04d3b78c
BJ
549 signal(SIGQUIT, SIG_IGN);
550 sigset(SIGINT, SIG_IGN);
551 signal(SIGTERM, SIG_IGN);
552 setintr = 0; /* No interrupts after "logout" */
553 if (adrof("home"))
554 srccat(value("home"), "/.logout");
555 }
556 exitstat();
557}
558
559exitstat()
560{
561
562 /*
563 * Note that if STATUS is corrupted (i.e. getn bombs)
564 * then error will exit directly because we poke child here.
565 * Otherwise we might continue unwarrantedly (sic).
566 */
567 child++;
568 exit(getn(value("status")));
569}
570
571char *jobargv[2] = { "jobs", 0 };
572/*
573 * Catch an interrupt, e.g. during lexical input.
574 * If we are an interactive shell, we reset the interrupt catch
575 * immediately. In any case we drain the shell output,
576 * and finally go through the normal error mechanism, which
577 * gets a chance to make the shell go away.
578 */
579pintr()
401149be
BJ
580{
581 pintr1(1);
582}
583
584pintr1(wantnl)
585 bool wantnl;
04d3b78c
BJ
586{
587 register char **v;
588
589 if (setintr) {
590 sigrelse(SIGINT);
591 if (pjobs) {
592 pjobs = 0;
593 printf("\n");
594 dojobs(jobargv);
595 bferr("Interrupted");
596 }
597 }
598 if (setintr)
599 sighold(SIGINT);
600 sigrelse(SIGCHLD);
601 draino();
602
603 /*
604 * If we have an active "onintr" then we search for the label.
605 * Note that if one does "onintr -" then we shan't be interruptible
606 * so we needn't worry about that here.
607 */
608 if (gointr) {
609 search(ZGOTO, 0, gointr);
610 timflg = 0;
611 if (v = pargv)
612 pargv = 0, blkfree(v);
613 if (v = gargv)
614 gargv = 0, blkfree(v);
615 reset();
401149be 616 } else if (intty && wantnl)
04d3b78c
BJ
617 printf("\n"); /* Some like this, others don't */
618 error(NOSTR);
619}
620
621/*
622 * Process is the main driving routine for the shell.
623 * It runs all command processing, except for those within { ... }
624 * in expressions (which is run by a routine evalav in sh.exp.c which
625 * is a stripped down process), and `...` evaluation which is run
626 * also by a subset of this code in sh.glob.c in the routine backeval.
627 *
628 * The code here is a little strange because part of it is interruptible
629 * and hence freeing of structures appears to occur when none is necessary
630 * if this is ignored.
631 *
632 * Note that if catch is not set then we will unwind on any error.
633 * If an end-of-file occurs, we return.
634 */
635process(catch)
636 bool catch;
637{
638 register char *cp;
639 jmp_buf osetexit;
640 struct command *t;
641
642 getexit(osetexit);
643 for (;;) {
644 pendjob();
645 paraml.next = paraml.prev = &paraml;
646 paraml.word = "";
647 t = 0;
648 setexit();
63af20f7 649 justpr = enterhist; /* execute if not entering history */
04d3b78c
BJ
650
651 /*
652 * Interruptible during interactive reads
653 */
654 if (setintr)
655 sigrelse(SIGINT);
656
657 /*
658 * For the sake of reset()
659 */
660 freelex(&paraml), freesyn(t), t = 0;
661
662 if (haderr) {
663 if (!catch) {
664 /* unwind */
665 doneinp = 0;
666 resexit(osetexit);
667 reset();
668 }
669 haderr = 0;
670 /*
671 * Every error is eventually caught here or
672 * the shell dies. It is at this
673 * point that we clean up any left-over open
674 * files, by closing all but a fixed number
675 * of pre-defined files. Thus routines don't
676 * have to worry about leaving files open due
677 * to deeper errors... they will get closed here.
678 */
679 closem();
680 continue;
681 }
682 if (doneinp) {
683 doneinp = 0;
684 break;
685 }
686 if (chkstop)
687 chkstop--;
688 if (neednote)
689 pnote();
690 if (intty && evalvec == 0) {
691 mailchk();
692 /*
693 * If we are at the end of the input buffer
694 * then we are going to read fresh stuff.
695 * Otherwise, we are rereading input and don't
696 * need or want to prompt.
697 */
698 if (fseekp == feobp)
699 if (!whyles)
700 for (cp = value("prompt"); *cp; cp++)
701 if (*cp == HIST)
702 printf("%d", eventno + 1);
703 else {
704 if (*cp == '\\' && cp[1] == HIST)
705 cp++;
706 putchar(*cp | QUOTE);
707 }
708 else
709 /*
710 * Prompt for forward reading loop
711 * body content.
712 */
713 printf("? ");
714 flush();
715 }
716 err = 0;
717
718 /*
719 * Echo not only on VERBOSE, but also with history expansion.
720 * If there is a lexical error then we forego history echo.
721 */
722 if (lex(&paraml) && !err && intty || adrof("verbose")) {
723 haderr = 1;
724 prlex(&paraml);
725 haderr = 0;
726 }
727
728 /*
729 * The parser may lose space if interrupted.
730 */
731 if (setintr)
732 sighold(SIGINT);
733
734 /*
63af20f7
KM
735 * Save input text on the history list if
736 * reading in old history, or it
04d3b78c
BJ
737 * is from the terminal at the top level and not
738 * in a loop.
739 */
63af20f7 740 if (enterhist || catch && intty && !whyles)
04d3b78c
BJ
741 savehist(&paraml);
742
743 /*
744 * Print lexical error messages.
745 */
746 if (err)
747 error(err);
748
749 /*
750 * If had a history command :p modifier then
751 * this is as far as we should go
752 */
753 if (justpr)
754 reset();
755
756 alias(&paraml);
757
758 /*
759 * Parse the words of the input into a parse tree.
760 */
761 t = syntax(paraml.next, &paraml, 0);
762 if (err)
763 error(err);
764
765 /*
766 * Execute the parse tree
767 */
768 execute(t, tpgrp);
769
770 /*
771 * Made it!
772 */
773 freelex(&paraml), freesyn(t);
774 }
775 resexit(osetexit);
776}
777
778dosource(t)
779 register char **t;
780{
781 register char *f;
782 register int u;
783
784 t++;
785 f = globone(*t);
786 u = dmove(open(f, 0), -1);
787 xfree(f);
788 if (u < 0)
789 Perror(f);
790 srcunit(u, 0);
791}
792
793/*
794 * Check for mail.
795 * If we are a login shell, then we don't want to tell
796 * about any mail file unless its been modified
797 * after the time we started.
798 * This prevents us from telling the user things he already
799 * knows, since the login program insists on saying
800 * "You have mail."
801 */
802mailchk()
803{
804 register struct varent *v;
805 register char **vp;
806 time_t t;
807 int intvl, cnt;
808 struct stat stb;
809 bool new;
810
811 v = adrof("mail");
812 if (v == 0)
813 return;
814 time(&t);
815 vp = v->vec;
816 cnt = blklen(vp);
817 intvl = (cnt && number(*vp)) ? (--cnt, getn(*vp++)) : MAILINTVL;
818 if (intvl < 1)
819 intvl = 1;
820 if (chktim + intvl > t)
821 return;
822 for (; *vp; vp++) {
823 if (stat(*vp, &stb) < 0)
824 continue;
825 new = stb.st_mtime > time0;
826 if (stb.st_size == 0 || stb.st_atime > stb.st_mtime ||
827 (stb.st_atime < chktim && stb.st_mtime < chktim) ||
828 loginsh && !new)
829 continue;
830 if (cnt == 1)
831 printf("You have %smail.\n", new ? "new " : "");
832 else
833 printf("%s in %s.\n", new ? "New mail" : "Mail", *vp);
834 }
835 chktim = t;
836}
837
838#include <pwd.h>
839/*
840 * Extract a home directory from the password file
841 * The argument points to a buffer where the name of the
842 * user whose home directory is sought is currently.
843 * We write the home directory of the user back there.
844 */
845gethdir(home)
846 char *home;
847{
848 register struct passwd *pp = getpwnam(home);
849
850 if (pp == 0)
851 return (1);
852 strcpy(home, pp->pw_dir);
853 return (0);
854}
855
856/*
857 * Move the initial descriptors to their eventual
858 * resting places, closin all other units.
859 */
860initdesc()
861{
862
863 didcch = 0; /* Havent closed for child */
864 didfds = 0; /* 0, 1, 2 aren't set up */
865 SHIN = dcopy(0, FSHIN);
866 SHOUT = dcopy(1, FSHOUT);
867 SHDIAG = dcopy(2, FSHDIAG);
868 OLDSTD = dcopy(SHIN, FOLDSTD);
869 closem();
870}
871
872exit(i)
873 int i;
874{
875
876 untty();
877#ifdef PROF
878 IEH3exit(i);
879#else
880 _exit(i);
881#endif
882}