* Copyright (c) 1980, 1991 The Regents of the University of California.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
"@(#) Copyright (c) 1991 The Regents of the University of California.\n\
static char sccsid
[] = "@(#)csh.c 5.26 (Berkeley) 6/8/91";
extern bool MapsAreInited
;
extern bool NLSMapsAreInited
;
* Bill Joy, UC Berkeley, California, USA
* Jim Kulp, IIASA, Laxenburg, Austria
* Christos Zoulas, Cornell University
Char
*dumphist
[] = {STRhistory
, STRmh
, 0, 0};
Char
*loadhist
[] = {STRsource
, STRmh
, STRhistfile
, 0};
static int srccat
__P((Char
*, Char
*));
static int srcfile
__P((char *, bool, bool));
static void phup
__P((int));
static void srcunit
__P((int, bool, bool));
static void mailchk
__P((void));
static Char
**defaultpath
__P((void));
settimes(); /* Immed. estab. timing base */
* Initialize non constant strings
STR_BSHELL
= SAVE(_PATH_BSHELL
);
STR_SHELLPATH
= SAVE(_PATH_CSHELL
);
STR_environ
= blk2short(environ
);
environ
= short2blk(STR_environ
); /* So that we can free it */
STR_WORD_CHARS
= SAVE(WORD_CHARS
);
word_chars
= STR_WORD_CHARS
;
if (eq(str2short(tempv
[0]), STRaout
)) /* A.out's are quittable */
* We are a login shell if: 1. we were invoked as -<something> and we had
* no arguments 2. or we were invoked only with the -l flag
loginsh
= (**tempv
== '-' && argc
== 1) ||
(argc
== 2 && tempv
[1][0] == '-' && tempv
[1][1] == 'l' &&
if (loginsh
&& **tempv
!= '-') {
for (tcp
= *tempv
; *tcp
++;);
for (tcp
--; tcp
>= *tempv
; tcp
--)
(void) setlocale(LC_ALL
, "");
for (k
= 0200; k
<= 0377 && !Isprint(k
); k
++);
AsciiOnly
= getenv("LANG") == NULL
&& getenv("LC_CTYPE") == NULL
;
* 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
* Initialize the shell variables. ARGV and PROMPT are initialized later.
* STATUS is also munged in several places. CHILD is munged when
set(STRstatus
, Strsave(STR0
));
if ((tcp
= getenv("HOME")) != NULL
)
fast
= 1; /* No home -> can't read scripts */
dinit(cp
); /* dinit thinks that HOME == cwd in a login
* Grab other useful things from the environment. Should we grab
if ((tcp
= getenv("LOGNAME")) != NULL
||
(tcp
= getenv("USER")) != NULL
)
if ((tcp
= getenv("TERM")) != NULL
)
* Re-initialize path if set in environment
if ((tcp
= getenv("PATH")) == NULL
)
set1(STRpath
, defaultpath(), &shvhed
);
set(STRshell
, Strsave(STR_SHELLPATH
));
doldol
= putn((int) getpid()); /* For $$ */
shtemp
= Strspl(STRtmpsh
, 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.
/* parents interruptibility */
(void) sigvec(SIGINT
, NULL
, &osv
);
parintr
= (void (*) ()) osv
.sv_handler
;
(void) sigvec(SIGTERM
, NULL
, &osv
);
parterm
= (void (*) ()) osv
.sv_handler
;
(void) signal(SIGHUP
, phup
); /* exit processing on HUP */
(void) signal(SIGXCPU
, phup
); /* ...and on XCPU */
(void) signal(SIGXFSZ
, phup
); /* ...and on XFSZ */
* Note that processing of -v/-x is actually delayed till after script
* 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 (argc
> 0 && (tcp
= tempv
[0])[0] == '-' && *++tcp
!= '\0' && !batch
) {
case 0: /* - Interruptible, no prompt */
case 'b': /* -b Next arg is input file */
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 'm': /* -m read .cshrc (from su) */
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(STRverbose
); /* NOW! */
case 'X': /* -X Echo just before execution */
setNS(STRecho
); /* NOW! */
if (quitit
) /* With all due haste, for debugging */
(void) 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
if (nofile
== 0 && argc
> 0) {
nofile
= open(tempv
[0], O_RDONLY
);
child
= 1; /* So this doesn't return */
stderror(ERR_SYSTEM
, tempv
[0], strerror(errno
));
* Replace FSHIN. Handle /dev/std{in,out,err} specially
* since once they are closed we cannot open them again.
* In that case we use our own saved descriptors
if ((SHIN
= dmove(nofile
, FSHIN
)) < 0)
stderror(ERR_SYSTEM
, tempv
[0], strerror(errno
));
(void) ioctl(SHIN
, FIOCLEX
, NULL
);
/* argc not used any more */ tempv
++;
if (intty
|| (intact
&& isatty(SHOUT
))) {
if (!batch
&& (uid
!= geteuid() || gid
!= getegid())) {
child
= 1; /* So this doesn't return */
stderror(ERR_SYSTEM
, "csh", strerror(errno
));
* 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
if (loginsh
|| intact
|| intty
&& isatty(SHOUT
))
* Save the remaining arguments in argv.
setq(STRargv
, blk2short(tempv
), &shvhed
);
set(STRprompt
, Strsave(uid
== 0 ? STRsymhash
: STRsymcent
));
/* that's a meta-questionmark */
set(STRprompt2
, Strsave(STRmquestion
));
* If we are an interactive shell, then start fiddling with the signals;
(void) signal(SIGQUIT
, SIG_IGN
);
(void) signal(SIGINT
, pintr
);
(void) sigblock(sigmask(SIGINT
));
(void) signal(SIGTERM
, SIG_IGN
);
if (quitit
== 0 && arginp
== 0) {
(void) signal(SIGTSTP
, SIG_IGN
);
(void) signal(SIGTTIN
, SIG_IGN
);
(void) signal(SIGTTOU
, SIG_IGN
);
* Wait till in foreground, in case someone stupidly runs csh &
* dont want to try to grab away the tty.
if ((tpgrp
= tcgetpgrp(f
)) != -1) {
sig_t old
= signal(SIGTTIN
, SIG_DFL
);
(void) signal(SIGTTIN
, old
);
* Setpgid will fail if we are a session leader and
* mypid == mypgrp (POSIX 4.3.3)
if (setpgid(0, shpgrp
) == -1)
* We do that after we set our process group, to make sure
* that the process group belongs to a process in the same
* session as the tty (our process and our group) (POSIX 7.2.4)
if (tcsetpgrp(f
, shpgrp
) == -1)
(void) ioctl(dcopy(f
, FSHTTY
), FIOCLEX
, NULL
);
xprintf("Warning: no access to tty (%s).\n", strerror(errno
));
xprintf("Thus no job control in this shell.\n");
if ((setintr
== 0) && (parintr
== SIG_DFL
))
(void) signal(SIGCHLD
, pchild
); /* while signals not ready */
* Set an exit here in case of an interrupt or error reading the shell
reenter
= setexit(); /* PWP */
haderr
= 0; /* In case second time through */
if (!fast
&& reenter
== 0) {
/* Will have value(STRhome) here because set fast if don't */
sigset_t omask
= sigblock(sigmask(SIGINT
));
(void) srcfile(_PATH_DOTCSHRC
, 0, 0);
if (!fast
&& !arginp
&& !onelflg
)
(void) srcfile(_PATH_DOTLOGIN
, 0, 0);
(void) sigsetmask(omask
);
(void) srccat(value(STRhome
), STRsldotcshrc
);
if (!fast
&& !arginp
&& !onelflg
&& !havhash
)
(void) srccat(value(STRhome
), STRsldotlogin
);
* 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
(void) setpgid(0, opgrp
);
(void) tcsetpgrp(FSHTTY
, opgrp
);
* 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
**) xcalloc((size_t) (i
+ 2), sizeof(Char
**));
if ((c
= *dp
) == ':' || c
== 0) {
pv
[i
++] = Strsave(*cp
? cp
: STRdot
);
set1(STRpath
, pv
, &shvhed
);
* Source to the file which is the catenation of the argument names.
register Char
*ep
= Strspl(cp
, dp
);
char *ptr
= short2str(ep
);
return srcfile(ptr
, mflag
? 0 : 1, 0);
* Source to a file putting the file descriptor in a safe place (> 2).
srcfile(f
, onlyown
, flag
)
if ((unit
= open(f
, O_RDONLY
)) == -1)
(void) ioctl(unit
, FIOCLEX
, NULL
);
srcunit(unit
, onlyown
, flag
);
* 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
, oinsource
= insource
;
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) {
* 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
* 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 however.
omask
= sigblock(sigmask(SIGINT
));
/* Setup the new values of the state stuff saved above */
bcopy((char *) &B
, (char *) &(saveB
), sizeof(B
));
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
(void) sigsetmask(omask
);
if ((my_reenter
= setexit()) == 0)
process(0); /* 0 -> blow away on errors */
(void) sigsetmask(omask
);
/* 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
++)
bcopy((char *) &(saveB
), (char *) &B
, sizeof(B
));
(void) 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 we must also unwind.
if (value(STRsavehist
)[0] == '\0')
(void) Strcpy(buf
, value(STRhome
));
(void) Strcat(buf
, STRsldthist
);
fp
= creat(short2str(buf
), 0600);
(void) Strcpy(buf
, value(STRsavehist
));
(void) signal(SIGQUIT
, SIG_IGN
);
(void) signal(SIGINT
, SIG_IGN
);
(void) signal(SIGTERM
, SIG_IGN
);
setintr
= 0; /* No interrupts after "logout" */
set(STRlogout
, STRnormal
);
(void) srcfile(_PATH_DOTLOGOUT
, 0, 0);
(void) srccat(value(STRhome
), STRsldtlogout
);
* Note that if STATUS is corrupted (i.e. getn bombs) then error will exit
* directly because we poke child here. Otherwise we might continue
xexit(getn(value(STRstatus
)));
* in the event of a HUP we want to save the history
Char
*jobargv
[2] = {STRjobs
, 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.
omask
= sigblock((sigset_t
) 0);
(void) sigsetmask(omask
& ~sigmask(SIGINT
));
stderror(ERR_NAME
| ERR_INTR
);
(void) sigsetmask(omask
& ~sigmask(SIGCHLD
));
* 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
search(T_GOTO
, 0, gointr
);
else if (intty
&& wantnl
) {
* 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
(void) sigsetmask(sigblock((sigset_t
) 0) & ~sigmask(SIGINT
));
* For the sake of reset()
* 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
) && !seterr
&& intty
|| adrof(STRverbose
)) {
* The parser may lose space if interrupted.
(void) sigblock(sigmask(SIGINT
));
* Save input text on the history list if reading in old history, or it
* is from the terminal at the top level and not in a loop.
* PWP: entry of items in the history list while in a while loop is done
if (enterhist
|| catch && intty
&& !whyles
)
* Print lexical error messages, except when sourcing history lists.
if (!enterhist
&& seterr
)
* If had a history command :p modifier then this is as far as we
* Parse the words of the input into a parse tree.
t
= syntax(paraml
.next
, ¶ml
, 0);
execute(t
, (tpgrp
> 0 ? tpgrp
: -1), NULL
, NULL
);
if (*t
&& eq(*t
, STRmh
)) {
stderror(ERR_NAME
| ERR_HFLAG
);
f
= globone(buf
, G_ERROR
);
(void) strcpy((char *) buf
, short2str(f
));
if (!srcfile((char *) buf
, 0, hflg
) && !hflg
)
stderror(ERR_SYSTEM
, (char *) buf
, strerror(errno
));
* 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
;
if (stat(short2str(*vp
), &stb
) < 0)
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
) ||
xprintf("You have %smail.\n", new ? "new " : "");
xprintf("%s in %s.\n", new ? "New mail" : "Mail", short2str(*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.
if (h
= value(STRhome
)) {
if (pw
= getpwnam(short2str(home
))) {
(void) Strcpy(home
, str2short(pw
->pw_dir
));
* Move the initial descriptors to their eventual
* resting places, closin all other units.
didfds
= 0; /* 0, 1, 2 aren't set up */
(void) ioctl(SHIN
= dcopy(0, FSHIN
), FIOCLEX
, NULL
);
(void) ioctl(SHOUT
= dcopy(1, FSHOUT
), FIOCLEX
, NULL
);
(void) ioctl(SHDIAG
= dcopy(2, FSHDIAG
), FIOCLEX
, NULL
);
(void) ioctl(OLDSTD
= dcopy(SHIN
, FOLDSTD
), FIOCLEX
, NULL
);
blkp
= blk
= (Char
**) xmalloc((size_t) sizeof(Char
*) * 10);
if (stat(ptr = a, &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFDIR) \
*blkp
++ = Strsave(STRdot
);
for (cp
= value(STRprompt
); *cp
; cp
++)
xprintf("%d", eventno
+ 1);
if (*cp
== '\\' && cp
[1] == HIST
)
* Prompt for forward reading loop body content.