* Copyright (c) 1980, 1991 The Regents of the University of California.
* %sccs.include.redist.c%
"@(#) Copyright (c) 1991 The Regents of the University of California.\n\
static char sccsid
[] = "@(#)csh.c 5.37 (Berkeley) %G%";
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
, STRtildothist
, 0};
static int readf
__P((void *, char *, int));
static fpos_t seekf
__P((void *, fpos_t, int));
static int writef
__P((void *, const char *, int));
static int closef
__P((void *));
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
* XXX: This is to keep programs that use stdio happy.
* what we really want is freunopen() ....
* Closing cshin cshout and csherr (which are really stdin stdout
* and stderr at this point and then reopening them in the same order
* gives us again stdin == cshin stdout == cshout and stderr == csherr.
* If that was not the case builtins like printf that use stdio
* would break. But in any case we could fix that with memcpy and
* a bit of pointer manipulation...
* Fortunately this is not needed under the current implementation
if (!(cshin
= funopen((void *) &SHIN
, readf
, writef
, seekf
, closef
)))
if (!(cshout
= funopen((void *) &SHOUT
, readf
, writef
, seekf
, closef
)))
if (!(csherr
= funopen((void *) &SHERR
, readf
, writef
, seekf
, closef
)))
(void) setvbuf(cshin
, NULL
, _IOLBF
, 0);
(void) setvbuf(cshout
, NULL
, _IOLBF
, 0);
(void) setvbuf(csherr
, NULL
, _IOLBF
, 0);
* 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
!= euid
|| gid
!= egid
)) {
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
);
(void) fprintf(csherr
, "Warning: no access to tty (%s).\n",
(void) fprintf(csherr
, "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 */
sig_t oparintr
= parintr
;
sigset_t omask
= sigblock(sigmask(SIGINT
));
parintr
= SIG_IGN
; /* Disable onintr */
(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
)
* Source history before .login so that it is available in .login
if ((cp
= value(STRhistfile
)) != STRNULL
)
dosource(loadhist
, NULL
);
(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) fprintf(cshout
, "logout\n");
(void) fprintf(cshout
, "exit\n");
(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) {
if ((*cp
!= '/' || *cp
== '\0') && (euid
== 0 || uid
== 0))
"Warning: imported path contains relative components\n");
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.
Char buf
[BUFSIZ
], hbuf
[BUFSIZ
], *hfile
;
* If $savehist is just set, we use the value of $history
* else we use the value in $savehist
if (shist
= adrof(STRsavehist
)) {
if (shist
->vec
[0][0] != '\0')
(void) Strcpy(hbuf
, shist
->vec
[0]);
else if ((shist
= adrof(STRhistory
)) && shist
->vec
[0][0] != '\0')
(void) Strcpy(hbuf
, shist
->vec
[0]);
if ((hfile
= value(STRhistfile
)) == STRNULL
) {
hfile
= Strcpy(buf
, value(STRhome
));
(void) Strcat(buf
, STRsldthist
);
if ((fp
= creat(short2str(hfile
), 0600)) == -1)
(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
* 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
));
(void) fprintf(cshout
, "\n");
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
else if (intty
&& wantnl
) {
(void) fputc('\r', cshout
);
(void) fputc('\n', cshout
);
* 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.
static struct command
*savet
= NULL
;
struct command
*t
= savet
;
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()
freesyn(savet
), savet
= NULL
;
* 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.
if (aret
== F_SEEK
&& fseekp
== feobp
)
* 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.
savet
= syntax(paraml
.next
, ¶ml
, 0);
execute(savet
, (tpgrp
> 0 ? tpgrp
: -1), NULL
, NULL
);
freesyn((struct command
*) savet
), savet
= NULL
;
if (*v
&& eq(*v
, 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
) ||
(void) fprintf(cshout
, "You have %smail.\n", new ? "new " : "");
(void) fprintf(cshout
, "%s in %s.\n", new ? "New mail" : "Mail",
* 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
));
* When didfds is set, we do I/O from 0, 1, 2 otherwise from 15, 16, 17
* We also check if the shell has already changed the decriptor to point to
* 0, 1, 2 when didfds is set.
#define DESC(a) (*((int *) (a)) - (didfds && *((int *) a) >= FSHIN ? FSHIN : 0))
return read(DESC(oreo
), buf
, siz
);
return write(DESC(oreo
), buf
, siz
);
return lseek(DESC(oreo
), off
, whence
);
return close(DESC(oreo
));
* Print the visible version of a string.
char uenc
[5]; /* 4 + NULL */
return fputc(ch
& TRIM
, fp
);
* XXX: When we are in AsciiOnly we want all characters >= 0200 to
* be encoded, but currently there is no way in vis to do that.
(void) vis(uenc
, ch
& TRIM
, VIS_NOSLASH
, 0);
* 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(SHERR
= dcopy(2, FSHERR
), 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) \
if (euid
!= 0 && uid
!= 0)
*blkp
++ = Strsave(STRdot
);
for (cp
= value(STRprompt
); *cp
; cp
++)
(void) fprintf(cshout
, "%d", eventno
+ 1);
if (*cp
== '\\' && cp
[1] == HIST
)
(void) vis_fputc(*cp
| QUOTE
, cshout
);
* Prompt for forward reading loop body content.
(void) fprintf(cshout
, "? ");