static char *sccsid
= "@(#)csh.c 4.17 %G%";
* Bill Joy, UC Berkeley, California, USA
* Jim Kulp, IIASA, Laxenburg, Austria
char *pathlist
[] = { ".", "/usr/ucb", "/bin", "/usr/bin", 0 };
char *dumphist
[] = { "history", "-h", 0, 0 };
char *loadhist
[] = { "source", "-h", "~/.history", 0 };
settimes(); /* Immed. estab. timing base */
if (eq(v
[0], "a.out")) /* A.out's are quittable */
* Move the descriptors to safe places.
* The variable didfds is 0 while we have only FSH* to work with.
* When didfds is true, we have 0,1,2 and prefer to use these.
* Initialize the shell variables.
* ARGV and PROMPT are initialized later.
* STATUS is also munged in several places.
* CHILD is munged when forking/waiting
dinit(cp
= getenv("HOME")); /* dinit thinks that HOME == cwd in a
fast
++; /* No home -> can't read scripts */
set("home", savestr(cp
));
* Grab other useful things from the environment.
* Should we grab everything??
if ((cp
= getenv("USER")) != NOSTR
)
set("user", savestr(cp
));
if ((cp
= getenv("TERM")) != NOSTR
)
set("term", savestr(cp
));
* Re-initialize path if set in environment
if ((cp
= getenv("PATH")) == NOSTR
)
set1("path", saveblk(pathlist
), &shvhed
);
pv
= (char **)calloc(i
+2, sizeof (char **));
pv
[i
++] = savestr(*cp
? cp
: ".");
pv
[i
++] = savestr(*cp
? cp
: ".");
set1("path", pv
, &shvhed
);
doldol
= putn(getpid()); /* For $$ */
shtemp
= strspl("/tmp/sh", doldol
); /* For << */
* Record the interrupt states from the parent process.
* If the parent is non-interruptible our hand must be forced
* or we (and our children) won't be either.
* Our children inherit termination from our parent.
* We catch it only if we are the login shell.
parintr
= signal(SIGINT
, SIG_IGN
); /* parents interruptibility */
sigset(SIGINT
, parintr
); /* ... restore */
parterm
= signal(SIGTERM
, SIG_IGN
); /* parents terminability */
signal(SIGTERM
, parterm
); /* ... restore */
signal(SIGHUP
, phup
); /* exit processing on HUP */
signal(SIGXCPU
, phup
); /* ...and on XCPU */
signal(SIGXFSZ
, phup
); /* ...and on XFSZ */
* Note that processing of -v/-x is actually delayed till after
* We set the first character of our name to be '-' if we are
* a shell running interruptible commands. Many programs which
* examine ps'es use this to filter such shells out.
while (c
> 0 && (cp
= v
[0])[0] == '-') {
case 0: /* - Interruptible, no prompt */
case 'c': /* -c Command input from arg */
case 'e': /* -e Exit on any error */
case 'f': /* -f Fast start */
case 'i': /* -i Interactive, even if !intty */
case 'n': /* -n Don't execute */
case 'q': /* -q (Undoc'd) ... die on quit */
case 's': /* -s Read from std input */
case 't': /* -t Read one line from input */
case 'v': /* -v Echo hist expanded input */
nverbose
= 1; /* ... later */
case 'x': /* -x Echo just before execution */
nexececho
= 1; /* ... later */
case 'V': /* -V Echo hist expanded input */
setNS("verbose"); /* NOW! */
case 'X': /* -X Echo just before execution */
setNS("echo"); /* NOW! */
if (quitit
) /* With all due haste, for debugging */
signal(SIGQUIT
, SIG_DFL
);
* Unless prevented by -, -c, -i, -s, or -t, if there
* are remaining arguments the first of them is the name
* of a shell file from which to read commands.
if (nofile
== 0 && c
> 0) {
child
++; /* So this ... */
Perror(v
[0]); /* ... doesn't return */
SHIN
= dmove(nofile
, FSHIN
); /* Replace FSHIN */
* Consider input a tty if it really is or we are interactive.
intty
= intact
|| isatty(SHIN
);
* Decide whether we should play with signals or not.
* If we are explicitly told (via -i, or -) or we are a login
* shell (arg0 starts with -) or the input and output are both
* the ttys("csh", or "csh</dev/ttyx>/dev/ttyx")
* Note that in only the login shell is it likely that parent
* may have set signals to be ignored
if (loginsh
|| intact
|| intty
&& isatty(SHOUT
))
* Save the remaining arguments in argv.
setq("argv", v
, &shvhed
);
set("prompt", uid
== 0 ? "# " : "% ");
* If we are an interactive shell, then start fiddling
* with the signals; this is a tricky game.
signal(SIGQUIT
, SIG_IGN
);
signal(SIGTERM
, SIG_IGN
);
if (quitit
== 0 && arginp
== 0) {
signal(SIGTSTP
, SIG_IGN
);
signal(SIGTTIN
, SIG_IGN
);
signal(SIGTTOU
, SIG_IGN
);
* Wait till in foreground, in case someone
* dont want to try to grab away the tty.
if (ioctl(f
, TIOCGPGRP
, &tpgrp
) == 0 && tpgrp
!= -1) {
int (*old
)() = sigsys(SIGTTIN
, SIG_DFL
);
if (ioctl(f
, TIOCGETD
, &oldisc
) != 0)
if (oldisc
!= NTTYDISC
) {
printf("Switching to new tty driver...\n");
ioctl(f
, TIOCSETD
, &ldisc
);
ioctl(f
, TIOCSPGRP
, &shpgrp
);
ioctl(FSHTTY
, FIOCLEX
, 0);
printf("Warning: no access to tty; thus no job control in this shell...\n");
if (setintr
== 0 && parintr
== SIG_DFL
)
sigset(SIGCHLD
, pchild
); /* while signals not ready */
* Set an exit here in case of an interrupt or error reading
* the shell start-up scripts.
haderr
= 0; /* In case second time through */
if (!fast
&& reenter
== 0) {
/* Will have value("home") here because set fast if don't */
srccat(value("home"), "/.cshrc");
if (!fast
&& !arginp
&& !onelflg
)
srccat(value("home"), "/.login");
* Now are ready for the -v and -x flags
* All the rest of the world is inside this call.
* The argument to process indicates whether it should
* catch "error unwinds". Thus if we are a interactive shell
* our call here will never return by being blown past on an error.
ioctl(FSHTTY
, TIOCSPGRP
, &opgrp
);
if (oldisc
!= -1 && oldisc
!= NTTYDISC
) {
printf("\nReverting to old tty driver...\n");
ioctl(FSHTTY
, TIOCSETD
, &oldisc
);
static char dot
[2] = {'.', 0};
* i+2 where i is the number of colons in the path.
* There are i+1 directories in the path plus we need
* room for a zero terminator.
pv
= (char **) calloc(i
+2, sizeof (char **));
if ((c
= *dp
) == ':' || c
== 0) {
pv
[i
++] = savestr(*cp
? cp
: dot
);
set1("path", pv
, &shvhed
);
* Source to the file which is the catenation of the argument names.
register char *ep
= strspl(cp
, dp
);
register int unit
= dmove(open(ep
, 0), -1);
/* ioctl(unit, FIOCLEX, NULL); */
* Source to a unit. If onlyown it must be our file or our group or
* we don't chance it. This occurs on ".cshrc"s and the like.
srcunit(unit
, onlyown
, hflg
)
/* We have to push down a lot of state here */
/* All this could go into a structure */
int oSHIN
= -1, oldintty
= intty
;
struct whyle
*oldwhyl
= whyles
;
char *ogointr
= gointr
, *oarginp
= arginp
;
char *oevalp
= evalp
, **oevalvec
= evalvec
;
bool oenterhist
= enterhist
;
/* The (few) real local variables */
if (fstat(unit
, &stb
) < 0 ||
(stb
.st_uid
!= uid
&& stb
.st_gid
!= getgid())) {
* There is a critical section here while we are pushing down the
* input stream since we have stuff in different structures.
* If we weren't careful an interrupt could corrupt SHIN's Bin
* structure and kill the shell.
* We could avoid the critical region by grouping all the stuff
* in a single structure and pointing at it to move it all at
* once. This is less efficient globally on many variable references
/* Setup the new values of the state stuff saved above */
copy((char *)&saveB
, (char *)&B
, sizeof saveB
);
fseekp
= feobp
= fblocks
= 0;
oSHIN
= SHIN
, SHIN
= unit
, arginp
= 0, onelflg
= 0;
intty
= isatty(SHIN
), whyles
= 0, gointr
= 0;
* Now if we are allowing commands to be interrupted,
* we let ourselves be interrupted.
process(0); /* 0 -> blow away on errors */
/* We made it to the new state... free up its storage */
/* This code could get run twice but xfree doesn't care */
for (i
= 0; i
< fblocks
; i
++)
copy((char *)&B
, (char *)&saveB
, sizeof B
);
close(SHIN
), SHIN
= oSHIN
;
arginp
= oarginp
, onelflg
= oonelflg
;
evalp
= oevalp
, evalvec
= oevalvec
;
intty
= oldintty
, whyles
= oldwhyl
, gointr
= ogointr
;
* If process reset() (effectively an unwind) then
if (value("savehist")[0] == '\0')
strcpy(buf
, value("home"));
strcat(buf
, "/.history");
strcpy(buf
, value("savehist"));
signal(SIGQUIT
, SIG_IGN
);
signal(SIGTERM
, SIG_IGN
);
setintr
= 0; /* No interrupts after "logout" */
srccat(value("home"), "/.logout");
* Note that if STATUS is corrupted (i.e. getn bombs)
* then error will exit directly because we poke child here.
* Otherwise we might continue unwarrantedly (sic).
exit(getn(value("status")));
* in the event of a HUP we want to save the history
char *jobargv
[2] = { "jobs", 0 };
* Catch an interrupt, e.g. during lexical input.
* If we are an interactive shell, we reset the interrupt catch
* immediately. In any case we drain the shell output,
* and finally go through the normal error mechanism, which
* gets a chance to make the shell go away.
* If we have an active "onintr" then we search for the label.
* Note that if one does "onintr -" then we shan't be interruptible
* so we needn't worry about that here.
search(ZGOTO
, 0, gointr
);
} else if (intty
&& wantnl
)
printf("\n"); /* Some like this, others don't */
* Process is the main driving routine for the shell.
* It runs all command processing, except for those within { ... }
* in expressions (which is run by a routine evalav in sh.exp.c which
* is a stripped down process), and `...` evaluation which is run
* also by a subset of this code in sh.glob.c in the routine backeval.
* The code here is a little strange because part of it is interruptible
* and hence freeing of structures appears to occur when none is necessary
* Note that if catch is not set then we will unwind on any error.
* If an end-of-file occurs, we return.
paraml
.next
= paraml
.prev
= ¶ml
;
justpr
= enterhist
; /* execute if not entering history */
* Interruptible during interactive reads
* For the sake of reset()
freelex(¶ml
), freesyn(t
), t
= 0;
* Every error is eventually caught here or
* the shell dies. It is at this
* point that we clean up any left-over open
* files, by closing all but a fixed number
* of pre-defined files. Thus routines don't
* have to worry about leaving files open due
* to deeper errors... they will get closed here.
if (intty
&& prompt
&& evalvec
== 0) {
* If we are at the end of the input buffer
* then we are going to read fresh stuff.
* Otherwise, we are rereading input and don't
* need or want to prompt.
* Echo not only on VERBOSE, but also with history expansion.
* If there is a lexical error then we forego history echo.
if (lex(¶ml
) && !err
&& intty
||
* The parser may lose space if interrupted.
* Save input text on the history list if
* reading in old history, or it
* is from the terminal at the top level and not
if (enterhist
|| catch && intty
&& !whyles
)
* Print lexical error messages, except when sourcing
* If had a history command :p modifier then
* this is as far as we should go
* Parse the words of the input into a parse tree.
t
= syntax(paraml
.next
, ¶ml
, 0);
freelex(¶ml
), freesyn(t
);
if (*t
&& eq(*t
, "-h")) {
u
= dmove(open(f
, 0), -1);
* If we are a login shell, then we don't want to tell
* about any mail file unless its been modified
* after the time we started.
* This prevents us from telling the user things he already
* knows, since the login program insists on saying
register struct varent
*v
;
intvl
= (cnt
&& number(*vp
)) ? (--cnt
, getn(*vp
++)) : MAILINTVL
;
new = stb
.st_mtime
> time0
.tv_sec
;
if (stb
.st_size
== 0 || stb
.st_atime
> stb
.st_mtime
||
(stb
.st_atime
< chktim
&& stb
.st_mtime
< chktim
) ||
printf("You have %smail.\n", new ? "new " : "");
printf("%s in %s.\n", new ? "New mail" : "Mail", *vp
);
* Extract a home directory from the password file
* The argument points to a buffer where the name of the
* user whose home directory is sought is currently.
* We write the home directory of the user back there.
register struct passwd
*pp
= getpwnam(home
);
strcpy(home
, pp
->pw_dir
);
* Move the initial descriptors to their eventual
* resting places, closin all other units.
didcch
= 0; /* Havent closed for child */
didfds
= 0; /* 0, 1, 2 aren't set up */
SHOUT
= dcopy(1, FSHOUT
);
SHDIAG
= dcopy(2, FSHDIAG
);
OLDSTD
= dcopy(SHIN
, FOLDSTD
);
for (cp
= value("prompt"); *cp
; cp
++)
printf("%d", eventno
+ 1);
if (*cp
== '\\' && cp
[1] == HIST
)
* Prompt for forward reading loop