Index: head/bin/sh/TOUR =================================================================== --- head/bin/sh/TOUR (revision 340283) +++ head/bin/sh/TOUR (revision 340284) @@ -1,301 +1,301 @@ # @(#)TOUR 8.1 (Berkeley) 5/31/93 # $FreeBSD$ NOTE -- This is the original TOUR paper distributed with ash and does not represent the current state of the shell. It is provided anyway since it provides helpful information for how the shell is structured, but be warned that things have changed -- the current shell is still under development. ================================================================ A Tour through Ash Copyright 1989 by Kenneth Almquist. DIRECTORIES: The subdirectory bltin contains commands which can be compiled stand-alone. The rest of the source is in the main ash directory. SOURCE CODE GENERATORS: Files whose names begin with "mk" are programs that generate source code. A complete list of these programs is: program input files generates ------- ----------- --------- mkbuiltins builtins.def builtins.h builtins.c mknodes nodetypes nodes.h nodes.c mksyntax - syntax.h syntax.c mktokens - token.h There are undoubtedly too many of these. EXCEPTIONS: Code for dealing with exceptions appears in exceptions.c. The C language doesn't include exception handling, so I implement it using setjmp and longjmp. The global variable exception contains the type of exception. EXERROR is raised by -calling error. EXINT is an interrupt. +calling error or errorwithstatus. EXINT is an interrupt. INTERRUPTS: In an interactive shell, an interrupt will cause an EXINT exception to return to the main command loop. (Exception: EXINT is not raised if the user traps interrupts using the trap command.) The INTOFF and INTON macros (defined in exception.h) provide uninterruptible critical sections. Between the execution of INTOFF and the execution of INTON, interrupt signals will be held for later delivery. INTOFF and INTON can be nested. MEMALLOC.C: Memalloc.c defines versions of malloc and realloc which call error when there is no memory left. It also defines a stack oriented memory allocation scheme. Allocating off a stack is probably more efficient than allocation using malloc, but the big advantage is that when an exception occurs all we have to do to free up the memory in use at the time of the exception is to restore the stack pointer. The stack is implemented using a linked list of blocks. STPUTC: If the stack were contiguous, it would be easy to store strings on the stack without knowing in advance how long the string was going to be: p = stackptr; *p++ = c; /* repeated as many times as needed */ stackptr = p; The following three macros (defined in memalloc.h) perform these operations, but grow the stack if you run off the end: STARTSTACKSTR(p); STPUTC(c, p); /* repeated as many times as needed */ grabstackstr(p); We now start a top-down look at the code: MAIN.C: The main routine performs some initialization, executes the user's profile if necessary, and calls cmdloop. Cmdloop repeatedly parses and executes commands. OPTIONS.C: This file contains the option processing code. It is called from main to parse the shell arguments when the shell is invoked, and it also contains the set builtin. The -i and -m op- tions (the latter turns on job control) require changes in signal handling. The routines setjobctl (in jobs.c) and setinteractive (in trap.c) are called to handle changes to these options. PARSING: The parser code is all in parser.c. A recursive des- cent parser is used. Syntax tables (generated by mksyntax) are used to classify characters during lexical analysis. There are four tables: one for normal use, one for use when inside single quotes and dollar single quotes, one for use when inside double quotes and one for use in arithmetic. The tables are machine dependent because they are indexed by character variables and the range of a char varies from machine to machine. PARSE OUTPUT: The output of the parser consists of a tree of nodes. The various types of nodes are defined in the file node- types. Nodes of type NARG are used to represent both words and the con- tents of here documents. An early version of ash kept the con- tents of here documents in temporary files, but keeping here do- cuments in memory typically results in significantly better per- formance. It would have been nice to make it an option to use temporary files for here documents, for the benefit of small machines, but the code to keep track of when to delete the tem- porary files was complex and I never fixed all the bugs in it. (AT&T has been maintaining the Bourne shell for more than ten years, and to the best of my knowledge they still haven't gotten it to handle temporary files correctly in obscure cases.) The text field of a NARG structure points to the text of the word. The text consists of ordinary characters and a number of special codes defined in parser.h. The special codes are: CTLVAR Parameter expansion CTLENDVAR End of parameter expansion CTLBACKQ Command substitution CTLBACKQ|CTLQUOTE Command substitution inside double quotes CTLARI Arithmetic expansion CTLENDARI End of arithmetic expansion CTLESC Escape next character A variable substitution contains the following elements: CTLVAR type name '=' [ alternative-text CTLENDVAR ] The type field is a single character specifying the type of sub- stitution. The possible types are: VSNORMAL $var VSMINUS ${var-text} VSMINUS|VSNUL ${var:-text} VSPLUS ${var+text} VSPLUS|VSNUL ${var:+text} VSQUESTION ${var?text} VSQUESTION|VSNUL ${var:?text} VSASSIGN ${var=text} VSASSIGN|VSNUL ${var:=text} VSTRIMLEFT ${var#text} VSTRIMLEFTMAX ${var##text} VSTRIMRIGHT ${var%text} VSTRIMRIGHTMAX ${var%%text} VSLENGTH ${#var} VSERROR delayed error In addition, the type field will have the VSQUOTE flag set if the variable is enclosed in double quotes and the VSLINENO flag if LINENO is being expanded (the parameter name is the decimal line number). The parameter's name comes next, terminated by an equals sign. If the type is not VSNORMAL (including when it is VSLENGTH), then the text field in the substitution follows, terminated by a CTLENDVAR byte. The type VSERROR is used to allow parsing bad substitutions like ${var[7]} and generate an error when they are expanded. Commands in back quotes are parsed and stored in a linked list. The locations of these commands in the string are indicated by CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether the back quotes were enclosed in double quotes. Arithmetic expansion starts with CTLARI and ends with CTLENDARI. The character CTLESC escapes the next character, so that in case any of the CTL characters mentioned above appear in the input, they can be passed through transparently. CTLESC is also used to escape '*', '?', '[', and '!' characters which were quoted by the user and thus should not be used for file name generation. CTLESC characters have proved to be particularly tricky to get right. In the case of here documents which are not subject to variable and command substitution, the parser doesn't insert any CTLESC characters to begin with (so the contents of the text field can be written without any processing). Other here docu- ments, and words which are not subject to file name generation, have the CTLESC characters removed during the variable and command substitution phase. Words which are subject to file name generation have the CTLESC characters removed as part of the file name phase. EXECUTION: Command execution is handled by the following files: eval.c The top level routines. redir.c Code to handle redirection of input and output. jobs.c Code to handle forking, waiting, and job control. exec.c Code to do path searches and the actual exec sys call. expand.c Code to evaluate arguments. var.c Maintains the variable symbol table. Called from expand.c. EVAL.C: Evaltree recursively executes a parse tree. The exit status is returned in the global variable exitstatus. The alter- native entry evalbackcmd is called to evaluate commands in back quotes. It saves the result in memory if the command is a buil- tin; otherwise it forks off a child to execute the command and connects the standard output of the child to a pipe. JOBS.C: To create a process, you call makejob to return a job structure, and then call forkshell (passing the job structure as an argument) to create the process. Waitforjob waits for a job to complete. These routines take care of process groups if job control is defined. REDIR.C: Ash allows file descriptors to be redirected and then restored without forking off a child process. This is accom- plished by duplicating the original file descriptors. The redir- tab structure records where the file descriptors have been dupli- cated to. EXEC.C: The routine find_command locates a command, and enters the command in the hash table if it is not already there. The third argument specifies whether it is to print an error message if the command is not found. (When a pipeline is set up, find_command is called for all the commands in the pipeline be- fore any forking is done, so to get the commands into the hash table of the parent process. But to make command hashing as transparent as possible, we silently ignore errors at that point and only print error messages if the command cannot be found later.) The routine shellexec is the interface to the exec system call. EXPAND.C: As the routine argstr generates words by parameter expansion, command substitution and arithmetic expansion, it performs word splitting on the result. As each word is output, the routine expandmeta performs file name generation (if enabled). VAR.C: Variables are stored in a hash table. Probably we should switch to extensible hashing. The variable name is stored in the same string as the value (using the format "name=value") so that no string copying is needed to create the environment of a com- mand. Variables which the shell references internally are preal- located so that the shell can reference the values of these vari- ables without doing a lookup. When a program is run, the code in eval.c sticks any environment variables which precede the command (as in "PATH=xxx command") in the variable table as the simplest way to strip duplicates, and then calls "environment" to get the value of the environment. BUILTIN COMMANDS: The procedures for handling these are scat- tered throughout the code, depending on which location appears most appropriate. They can be recognized because their names al- ways end in "cmd". The mapping from names to procedures is specified in the file builtins.def, which is processed by the mkbuiltins command. A builtin command is invoked with argc and argv set up like a normal program. A builtin command is allowed to overwrite its arguments. Builtin routines can call nextopt to do option pars- ing. This is kind of like getopt, but you don't pass argc and argv to it. Builtin routines can also call error. This routine normally terminates the shell (or returns to the main command loop if the shell is interactive), but when called from a non- special builtin command it causes the builtin command to terminate with an exit status of 2. The directory bltins contains commands which can be compiled in- dependently but can also be built into the shell for efficiency reasons. The header file bltin.h takes care of most of the differences between the ash and the stand-alone environment. The user should call the main routine "main", and #define main to be the name of the routine to use when the program is linked into ash. This #define should appear before bltin.h is included; bltin.h will #undef main if the program is to be compiled stand-alone. A similar approach is used for a few utilities from bin and usr.bin. CD.C: This file defines the cd and pwd builtins. SIGNALS: Trap.c implements the trap command. The routine set- signal figures out what action should be taken when a signal is received and invokes the signal system call to set the signal ac- tion appropriately. When a signal that a user has set a trap for is caught, the routine "onsig" sets a flag. The routine dotrap is called at appropriate points to actually handle the signal. When an interrupt is caught and no trap has been set for that signal, the routine "onint" in error.c is called. OUTPUT: Ash uses its own output routines. There are three out- put structures allocated. "Output" represents the standard out- put, "errout" the standard error, and "memout" contains output which is to be stored in memory. This last is used when a buil- tin command appears in backquotes, to allow its output to be col- lected without doing any I/O through the UNIX operating system. The variables out1 and out2 normally point to output and errout, respectively, but they are set to point to memout when appropri- ate inside backquotes. INPUT: The basic input routine is pgetc, which reads from the current input file. There is a stack of input files; the current input file is the top file on this stack. The code allows the input to come from a string rather than a file. (This is for the -c option and the "." and eval builtin commands.) The global variable plinno is saved and restored when files are pushed and popped from the stack. The parser routines store the number of the current line in this variable. DEBUGGING: If DEBUG is defined in shell.h, then the shell will write debugging information to the file $HOME/trace. Most of this is done using the TRACE macro, which takes a set of printf arguments inside two sets of parenthesis. Example: "TRACE(("n=%d0, n))". The double parenthesis are necessary be- cause the preprocessor can't handle functions with a variable number of arguments. Defining DEBUG also causes the shell to generate a core dump if it is sent a quit signal. The tracing code is in show.c. Index: head/bin/sh/error.c =================================================================== --- head/bin/sh/error.c (revision 340283) +++ head/bin/sh/error.c (revision 340284) @@ -1,199 +1,202 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 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. 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 * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /* * Errors and exceptions. */ #include "shell.h" #include "eval.h" #include "main.h" #include "options.h" #include "output.h" #include "error.h" #include "nodes.h" /* show.h needs nodes.h */ #include "show.h" #include "trap.h" #include #include #include #include /* * Code to handle exceptions in C. */ struct jmploc *handler; volatile sig_atomic_t exception; volatile sig_atomic_t suppressint; volatile sig_atomic_t intpending; -static void exverror(int, const char *, va_list) __printf0like(2, 0) __dead2; +static void verrorwithstatus(int, const char *, va_list) __printf0like(2, 0) __dead2; /* * Called to raise an exception. Since C doesn't include exceptions, we * just do a longjmp to the exception handler. The type of exception is * stored in the global variable "exception". * * Interrupts are disabled; they should be reenabled when the exception is * caught. */ void exraise(int e) { INTOFF; if (handler == NULL) abort(); exception = e; longjmp(handler->loc, 1); } /* * Called from trap.c when a SIGINT is received and not suppressed, or when * an interrupt is pending and interrupts are re-enabled using INTON. * (If the user specifies that SIGINT is to be trapped or ignored using the * trap builtin, then this routine is not called.) Suppressint is nonzero * when interrupts are held using the INTOFF macro. If SIGINTs are not * suppressed and the shell is not a root shell, then we want to be * terminated if we get here, as if we were terminated directly by a SIGINT. * Arrange for this here. */ void onint(void) { sigset_t sigs; intpending = 0; sigemptyset(&sigs); sigprocmask(SIG_SETMASK, &sigs, NULL); /* * This doesn't seem to be needed, since main() emits a newline. */ #if 0 if (tcgetpgrp(0) == getpid()) write(STDERR_FILENO, "\n", 1); #endif if (rootshell && iflag) exraise(EXINT); else { signal(SIGINT, SIG_DFL); kill(getpid(), SIGINT); _exit(128 + SIGINT); } } static void vwarning(const char *msg, va_list ap) { if (commandname) outfmt(out2, "%s: ", commandname); else if (arg0) outfmt(out2, "%s: ", arg0); doformat(out2, msg, ap); out2fmt_flush("\n"); } void warning(const char *msg, ...) { va_list ap; va_start(ap, msg); vwarning(msg, ap); va_end(ap); } /* * Exverror is called to raise the error exception. If the first argument * is not NULL then error prints an error message using printf style * formatting. It then raises the error exception. */ static void -exverror(int cond, const char *msg, va_list ap) +verrorwithstatus(int status, const char *msg, va_list ap) { /* * An interrupt trumps an error. Certain places catch error * exceptions or transform them to a plain nonzero exit code * in child processes, and if an error exception can be handled, * an interrupt can be handled as well. * * exraise() will disable interrupts for the exception handler. */ FORCEINTON; #ifdef DEBUG if (msg) - TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid())); + TRACE(("verrorwithstatus(%d, \"%s\") pid=%d\n", + status, msg, getpid())); else - TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); + TRACE(("verrorwithstatus(%d, NULL) pid=%d\n", + status, getpid())); #endif if (msg) vwarning(msg, ap); flushall(); - exraise(cond); + exitstatus = status; + exraise(EXERROR); } void error(const char *msg, ...) { va_list ap; va_start(ap, msg); - exverror(EXERROR, msg, ap); + verrorwithstatus(2, msg, ap); va_end(ap); } void -exerror(int cond, const char *msg, ...) +errorwithstatus(int status, const char *msg, ...) { va_list ap; va_start(ap, msg); - exverror(cond, msg, ap); + verrorwithstatus(status, msg, ap); va_end(ap); } Index: head/bin/sh/error.h =================================================================== --- head/bin/sh/error.h (revision 340283) +++ head/bin/sh/error.h (revision 340284) @@ -1,95 +1,94 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 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. 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 * SUCH DAMAGE. * * @(#)error.h 8.2 (Berkeley) 5/4/95 * $FreeBSD$ */ /* * We enclose jmp_buf in a structure so that we can declare pointers to * jump locations. The global variable handler contains the location to * jump to when an exception occurs, and the global variable exception * contains a code identifying the exception. To implement nested * exception handlers, the user should save the value of handler on entry * to an inner scope, set handler to point to a jmploc structure for the * inner scope, and restore handler on exit from the scope. */ #include #include struct jmploc { jmp_buf loc; }; extern struct jmploc *handler; extern volatile sig_atomic_t exception; /* exceptions */ #define EXINT 0 /* SIGINT received */ -#define EXERROR 1 /* a generic error */ -#define EXEXEC 2 /* command execution failed */ -#define EXEXIT 3 /* call exitshell(exitstatus) */ +#define EXERROR 1 /* a generic error with exitstatus */ +#define EXEXIT 2 /* call exitshell(exitstatus) */ /* * These macros allow the user to suspend the handling of interrupt signals * over a period of time. This is similar to SIGHOLD to or sigblock, but * much more efficient and portable. (But hacking the kernel is so much * more fun than worrying about efficiency and portability. :-)) */ extern volatile sig_atomic_t suppressint; extern volatile sig_atomic_t intpending; #define INTOFF suppressint++ #define INTON { if (--suppressint == 0 && intpending) onint(); } #define is_int_on() suppressint #define SETINTON(s) do { suppressint = (s); if (suppressint == 0 && intpending) onint(); } while (0) #define FORCEINTON {suppressint = 0; if (intpending) onint();} #define SET_PENDING_INT intpending = 1 #define CLEAR_PENDING_INT intpending = 0 #define int_pending() intpending void exraise(int) __dead2; void onint(void) __dead2; void warning(const char *, ...) __printflike(1, 2); void error(const char *, ...) __printf0like(1, 2) __dead2; -void exerror(int, const char *, ...) __printf0like(2, 3) __dead2; +void errorwithstatus(int, const char *, ...) __printf0like(2, 3) __dead2; /* * BSD setjmp saves the signal mask, which violates ANSI C and takes time, * so we use _setjmp instead. */ #define setjmp(jmploc) _setjmp(jmploc) #define longjmp(jmploc, val) _longjmp(jmploc, val) Index: head/bin/sh/eval.c =================================================================== --- head/bin/sh/eval.c (revision 340283) +++ head/bin/sh/eval.c (revision 340284) @@ -1,1384 +1,1375 @@ /*- * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 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. 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 * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include /* * Evaluate a command. */ #include "shell.h" #include "nodes.h" #include "syntax.h" #include "expand.h" #include "parser.h" #include "jobs.h" #include "eval.h" #include "builtins.h" #include "options.h" #include "exec.h" #include "redir.h" #include "input.h" #include "output.h" #include "trap.h" #include "var.h" #include "memalloc.h" #include "error.h" #include "show.h" #include "mystring.h" #ifndef NO_HISTORY #include "myhistedit.h" #endif int evalskip; /* set if we are skipping commands */ int skipcount; /* number of levels to skip */ static int loopnest; /* current loop nesting level */ int funcnest; /* depth of function calls */ static int builtin_flags; /* evalcommand flags for builtins */ char *commandname; struct arglist *cmdenviron; int exitstatus; /* exit status of last command */ int oexitstatus; /* saved exit status */ static void evalloop(union node *, int); static void evalfor(union node *, int); static union node *evalcase(union node *); static void evalsubshell(union node *, int); static void evalredir(union node *, int); static void exphere(union node *, struct arglist *); static void expredir(union node *); static void evalpipe(union node *); static int is_valid_fast_cmdsubst(union node *n); static void evalcommand(union node *, int, struct backcmd *); static void prehash(union node *); /* * Called to reset things after an exception. */ void reseteval(void) { evalskip = 0; loopnest = 0; } /* * The eval command. */ int evalcmd(int argc, char **argv) { char *p; char *concat; char **ap; if (argc > 1) { p = argv[1]; if (argc > 2) { STARTSTACKSTR(concat); ap = argv + 2; for (;;) { STPUTS(p, concat); if ((p = *ap++) == NULL) break; STPUTC(' ', concat); } STPUTC('\0', concat); p = grabstackstr(concat); } evalstring(p, builtin_flags); } else exitstatus = 0; return exitstatus; } /* * Execute a command or commands contained in a string. */ void evalstring(const char *s, int flags) { union node *n; struct stackmark smark; int flags_exit; int any; flags_exit = flags & EV_EXIT; flags &= ~EV_EXIT; any = 0; setstackmark(&smark); setinputstring(s, 1); while ((n = parsecmd(0)) != NEOF) { if (n != NULL && !nflag) { if (flags_exit && preadateof()) evaltree(n, flags | EV_EXIT); else evaltree(n, flags); any = 1; if (evalskip) break; } popstackmark(&smark); setstackmark(&smark); } popfile(); popstackmark(&smark); if (!any) exitstatus = 0; if (flags_exit) exraise(EXEXIT); } /* * Evaluate a parse tree. The value is left in the global variable * exitstatus. */ void evaltree(union node *n, int flags) { int do_etest; union node *next; struct stackmark smark; setstackmark(&smark); do_etest = 0; if (n == NULL) { TRACE(("evaltree(NULL) called\n")); exitstatus = 0; goto out; } do { next = NULL; #ifndef NO_HISTORY displayhist = 1; /* show history substitutions done with fc */ #endif TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); switch (n->type) { case NSEMI: evaltree(n->nbinary.ch1, flags & ~EV_EXIT); if (evalskip) goto out; next = n->nbinary.ch2; break; case NAND: evaltree(n->nbinary.ch1, EV_TESTED); if (evalskip || exitstatus != 0) { goto out; } next = n->nbinary.ch2; break; case NOR: evaltree(n->nbinary.ch1, EV_TESTED); if (evalskip || exitstatus == 0) goto out; next = n->nbinary.ch2; break; case NREDIR: evalredir(n, flags); break; case NSUBSHELL: evalsubshell(n, flags); do_etest = !(flags & EV_TESTED); break; case NBACKGND: evalsubshell(n, flags); break; case NIF: { evaltree(n->nif.test, EV_TESTED); if (evalskip) goto out; if (exitstatus == 0) next = n->nif.ifpart; else if (n->nif.elsepart) next = n->nif.elsepart; else exitstatus = 0; break; } case NWHILE: case NUNTIL: evalloop(n, flags & ~EV_EXIT); break; case NFOR: evalfor(n, flags & ~EV_EXIT); break; case NCASE: next = evalcase(n); break; case NCLIST: next = n->nclist.body; break; case NCLISTFALLTHRU: if (n->nclist.body) { evaltree(n->nclist.body, flags & ~EV_EXIT); if (evalskip) goto out; } next = n->nclist.next; break; case NDEFUN: defun(n->narg.text, n->narg.next); exitstatus = 0; break; case NNOT: evaltree(n->nnot.com, EV_TESTED); if (evalskip) goto out; exitstatus = !exitstatus; break; case NPIPE: evalpipe(n); do_etest = !(flags & EV_TESTED); break; case NCMD: evalcommand(n, flags, (struct backcmd *)NULL); do_etest = !(flags & EV_TESTED); break; default: out1fmt("Node type = %d\n", n->type); flushout(&output); break; } n = next; popstackmark(&smark); setstackmark(&smark); } while (n != NULL); out: popstackmark(&smark); if (pendingsig) dotrap(); if (eflag && exitstatus != 0 && do_etest) exitshell(exitstatus); if (flags & EV_EXIT) exraise(EXEXIT); } static void evalloop(union node *n, int flags) { int status; loopnest++; status = 0; for (;;) { if (!evalskip) evaltree(n->nbinary.ch1, EV_TESTED); if (evalskip) { if (evalskip == SKIPCONT && --skipcount <= 0) { evalskip = 0; continue; } if (evalskip == SKIPBREAK && --skipcount <= 0) evalskip = 0; if (evalskip == SKIPRETURN) status = exitstatus; break; } if (n->type == NWHILE) { if (exitstatus != 0) break; } else { if (exitstatus == 0) break; } evaltree(n->nbinary.ch2, flags); status = exitstatus; } loopnest--; exitstatus = status; } static void evalfor(union node *n, int flags) { struct arglist arglist; union node *argp; int i; int status; emptyarglist(&arglist); for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { oexitstatus = exitstatus; expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); } loopnest++; status = 0; for (i = 0; i < arglist.count; i++) { setvar(n->nfor.var, arglist.args[i], 0); evaltree(n->nfor.body, flags); status = exitstatus; if (evalskip) { if (evalskip == SKIPCONT && --skipcount <= 0) { evalskip = 0; continue; } if (evalskip == SKIPBREAK && --skipcount <= 0) evalskip = 0; break; } } loopnest--; exitstatus = status; } /* * Evaluate a case statement, returning the selected tree. * * The exit status needs care to get right. */ static union node * evalcase(union node *n) { union node *cp; union node *patp; struct arglist arglist; emptyarglist(&arglist); oexitstatus = exitstatus; expandarg(n->ncase.expr, &arglist, EXP_TILDE); for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { if (casematch(patp, arglist.args[0])) { while (cp->nclist.next && cp->type == NCLISTFALLTHRU && cp->nclist.body == NULL) cp = cp->nclist.next; if (cp->nclist.next && cp->type == NCLISTFALLTHRU) return (cp); if (cp->nclist.body == NULL) exitstatus = 0; return (cp->nclist.body); } } } exitstatus = 0; return (NULL); } /* * Kick off a subshell to evaluate a tree. */ static void evalsubshell(union node *n, int flags) { struct job *jp; int backgnd = (n->type == NBACKGND); oexitstatus = exitstatus; expredir(n->nredir.redirect); if ((!backgnd && flags & EV_EXIT && !have_traps()) || forkshell(jp = makejob(n, 1), n, backgnd) == 0) { if (backgnd) flags &=~ EV_TESTED; redirect(n->nredir.redirect, 0); evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ } else if (! backgnd) { INTOFF; exitstatus = waitforjob(jp, (int *)NULL); INTON; } else exitstatus = 0; } /* * Evaluate a redirected compound command. */ static void evalredir(union node *n, int flags) { struct jmploc jmploc; struct jmploc *savehandler; volatile int in_redirect = 1; oexitstatus = exitstatus; expredir(n->nredir.redirect); savehandler = handler; if (setjmp(jmploc.loc)) { int e; handler = savehandler; e = exception; popredir(); - if (e == EXERROR || e == EXEXEC) { - if (in_redirect) { - if (e == EXERROR) - exitstatus = 2; - FORCEINTON; - return; - } + if (e == EXERROR && in_redirect) { + FORCEINTON; + return; } longjmp(handler->loc, 1); } else { INTOFF; handler = &jmploc; redirect(n->nredir.redirect, REDIR_PUSH); in_redirect = 0; INTON; evaltree(n->nredir.n, flags); } INTOFF; handler = savehandler; popredir(); INTON; } static void exphere(union node *redir, struct arglist *fn) { struct jmploc jmploc; struct jmploc *savehandler; struct localvar *savelocalvars; int need_longjmp = 0; unsigned char saveoptreset; redir->nhere.expdoc = ""; savelocalvars = localvars; localvars = NULL; saveoptreset = shellparam.reset; forcelocal++; savehandler = handler; if (setjmp(jmploc.loc)) - need_longjmp = exception != EXERROR && exception != EXEXEC; + need_longjmp = exception != EXERROR; else { handler = &jmploc; expandarg(redir->nhere.doc, fn, 0); redir->nhere.expdoc = fn->args[0]; INTOFF; } handler = savehandler; forcelocal--; poplocalvars(); localvars = savelocalvars; shellparam.reset = saveoptreset; if (need_longjmp) longjmp(handler->loc, 1); INTON; } /* * Compute the names of the files in a redirection list. */ static void expredir(union node *n) { union node *redir; for (redir = n ; redir ; redir = redir->nfile.next) { struct arglist fn; emptyarglist(&fn); switch (redir->type) { case NFROM: case NTO: case NFROMTO: case NAPPEND: case NCLOBBER: expandarg(redir->nfile.fname, &fn, EXP_TILDE); redir->nfile.expfname = fn.args[0]; break; case NFROMFD: case NTOFD: if (redir->ndup.vname) { expandarg(redir->ndup.vname, &fn, EXP_TILDE); fixredir(redir, fn.args[0], 1); } break; case NXHERE: exphere(redir, &fn); break; } } } /* * Evaluate a pipeline. All the processes in the pipeline are children * of the process creating the pipeline. (This differs from some versions * of the shell, which make the last process in a pipeline the parent * of all the rest.) */ static void evalpipe(union node *n) { struct job *jp; struct nodelist *lp; int pipelen; int prevfd; int pip[2]; TRACE(("evalpipe(%p) called\n", (void *)n)); pipelen = 0; for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) pipelen++; INTOFF; jp = makejob(n, pipelen); prevfd = -1; for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { prehash(lp->n); pip[1] = -1; if (lp->next) { if (pipe(pip) < 0) { if (prevfd >= 0) close(prevfd); error("Pipe call failed: %s", strerror(errno)); } } if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { INTON; if (prevfd > 0) { dup2(prevfd, 0); close(prevfd); } if (pip[1] >= 0) { if (!(prevfd >= 0 && pip[0] == 0)) close(pip[0]); if (pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } } evaltree(lp->n, EV_EXIT); } if (prevfd >= 0) close(prevfd); prevfd = pip[0]; if (pip[1] != -1) close(pip[1]); } INTON; if (n->npipe.backgnd == 0) { INTOFF; exitstatus = waitforjob(jp, (int *)NULL); TRACE(("evalpipe: job done exit status %d\n", exitstatus)); INTON; } else exitstatus = 0; } static int is_valid_fast_cmdsubst(union node *n) { return (n->type == NCMD); } /* * Execute a command inside back quotes. If it's a builtin command, we * want to save its output in a block obtained from malloc. Otherwise * we fork off a subprocess and get the output of the command via a pipe. * Should be called with interrupts off. */ void evalbackcmd(union node *n, struct backcmd *result) { int pip[2]; struct job *jp; struct stackmark smark; struct jmploc jmploc; struct jmploc *savehandler; struct localvar *savelocalvars; unsigned char saveoptreset; result->fd = -1; result->buf = NULL; result->nleft = 0; result->jp = NULL; if (n == NULL) { exitstatus = 0; return; } setstackmark(&smark); exitstatus = oexitstatus; if (is_valid_fast_cmdsubst(n)) { savelocalvars = localvars; localvars = NULL; saveoptreset = shellparam.reset; forcelocal++; savehandler = handler; if (setjmp(jmploc.loc)) { if (exception == EXERROR) - exitstatus = 2; - else if (exception == EXEXEC) /* nothing */; else if (exception != 0) { handler = savehandler; forcelocal--; poplocalvars(); localvars = savelocalvars; shellparam.reset = saveoptreset; longjmp(handler->loc, 1); } } else { handler = &jmploc; evalcommand(n, EV_BACKCMD, result); } handler = savehandler; forcelocal--; poplocalvars(); localvars = savelocalvars; shellparam.reset = saveoptreset; } else { if (pipe(pip) < 0) error("Pipe call failed: %s", strerror(errno)); jp = makejob(n, 1); if (forkshell(jp, n, FORK_NOJOB) == 0) { FORCEINTON; close(pip[0]); if (pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } evaltree(n, EV_EXIT); } close(pip[1]); result->fd = pip[0]; result->jp = jp; } popstackmark(&smark); TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", result->fd, result->buf, result->nleft, result->jp)); } static int mustexpandto(const char *argtext, const char *mask) { for (;;) { if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { argtext++; continue; } if (*argtext == CTLESC) argtext++; else if (BASESYNTAX[(int)*argtext] == CCTL) return (0); if (*argtext != *mask) return (0); if (*argtext == '\0') return (1); argtext++; mask++; } } static int isdeclarationcmd(struct narg *arg) { int have_command = 0; if (arg == NULL) return (0); while (mustexpandto(arg->text, "command")) { have_command = 1; arg = &arg->next->narg; if (arg == NULL) return (0); /* * To also allow "command -p" and "command --" as part of * a declaration command, add code here. * We do not do this, as ksh does not do it either and it * is not required by POSIX. */ } return (mustexpandto(arg->text, "export") || mustexpandto(arg->text, "readonly") || (mustexpandto(arg->text, "local") && (have_command || !isfunc("local")))); } static void xtracecommand(struct arglist *varlist, int argc, char **argv) { char sep = 0; const char *text, *p, *ps4; int i; ps4 = expandstr(ps4val()); out2str(ps4 != NULL ? ps4 : ps4val()); for (i = 0; i < varlist->count; i++) { text = varlist->args[i]; if (sep != 0) out2c(' '); p = strchr(text, '='); if (p != NULL) { p++; outbin(text, p - text, out2); out2qstr(p); } else out2qstr(text); sep = ' '; } for (i = 0; i < argc; i++) { text = argv[i]; if (sep != 0) out2c(' '); out2qstr(text); sep = ' '; } out2c('\n'); flushout(&errout); } /* * Check if a builtin can safely be executed in the same process, * even though it should be in a subshell (command substitution). * Note that jobid, jobs, times and trap can show information not * available in a child process; this is deliberate. * The arguments should already have been expanded. */ static int safe_builtin(int idx, int argc, char **argv) { /* Generated from builtins.def. */ if (safe_builtin_always(idx)) return (1); if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || idx == UMASKCMD) return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); if (idx == SETCMD) return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || argv[1][0] == '+') && argv[1][1] == 'o' && argv[1][2] == '\0')); return (0); } /* * Execute a simple command. * Note: This may or may not return if (flags & EV_EXIT). */ static void evalcommand(union node *cmd, int flags, struct backcmd *backcmd) { union node *argp; struct arglist arglist; struct arglist varlist; char **argv; int argc; char **envp; int varflag; int mode; int pip[2]; struct cmdentry cmdentry; struct job *jp; struct jmploc jmploc; struct jmploc *savehandler; char *savecmdname; struct shparam saveparam; struct localvar *savelocalvars; struct parsefile *savetopfile; volatile int e; char *lastarg; int signaled; int do_clearcmdentry; const char *path = pathval(); int i; /* First expand the arguments. */ TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); emptyarglist(&arglist); emptyarglist(&varlist); varflag = 1; jp = NULL; do_clearcmdentry = 0; oexitstatus = exitstatus; exitstatus = 0; /* Add one slot at the beginning for tryexec(). */ appendarglist(&arglist, nullstr); for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { if (varflag && isassignment(argp->narg.text)) { expandarg(argp, varflag == 1 ? &varlist : &arglist, EXP_VARTILDE); continue; } else if (varflag == 1) varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); } appendarglist(&arglist, nullstr); expredir(cmd->ncmd.redirect); argc = arglist.count - 2; argv = &arglist.args[1]; argv[argc] = NULL; lastarg = NULL; if (iflag && funcnest == 0 && argc > 0) lastarg = argv[argc - 1]; /* Print the command if xflag is set. */ if (xflag) xtracecommand(&varlist, argc, argv); /* Now locate the command. */ if (argc == 0) { /* Variable assignment(s) without command */ cmdentry.cmdtype = CMDBUILTIN; cmdentry.u.index = BLTINCMD; cmdentry.special = 0; } else { static const char PATH[] = "PATH="; int cmd_flags = 0, bltinonly = 0; /* * Modify the command lookup path, if a PATH= assignment * is present */ for (i = 0; i < varlist.count; i++) if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) { path = varlist.args[i] + sizeof(PATH) - 1; /* * On `PATH=... command`, we need to make * sure that the command isn't using the * non-updated hash table of the outer PATH * setting and we need to make sure that * the hash table isn't filled with items * from the temporary setting. * * It would be better to forbit using and * updating the table while this command * runs, by the command finding mechanism * is heavily integrated with hash handling, * so we just delete the hash before and after * the command runs. Partly deleting like * changepatch() does doesn't seem worth the * bookinging effort, since most such runs add * directories in front of the new PATH. */ clearcmdentry(); do_clearcmdentry = 1; } for (;;) { if (bltinonly) { cmdentry.u.index = find_builtin(*argv, &cmdentry.special); if (cmdentry.u.index < 0) { cmdentry.u.index = BLTINCMD; argv--; argc++; break; } } else find_command(argv[0], &cmdentry, cmd_flags, path); /* implement the bltin and command builtins here */ if (cmdentry.cmdtype != CMDBUILTIN) break; if (cmdentry.u.index == BLTINCMD) { if (argc == 1) break; argv++; argc--; bltinonly = 1; } else if (cmdentry.u.index == COMMANDCMD) { if (argc == 1) break; if (!strcmp(argv[1], "-p")) { if (argc == 2) break; if (argv[2][0] == '-') { if (strcmp(argv[2], "--")) break; if (argc == 3) break; argv += 3; argc -= 3; } else { argv += 2; argc -= 2; } path = _PATH_STDPATH; clearcmdentry(); do_clearcmdentry = 1; } else if (!strcmp(argv[1], "--")) { if (argc == 2) break; argv += 2; argc -= 2; } else if (argv[1][0] == '-') break; else { argv++; argc--; } cmd_flags |= DO_NOFUNC; bltinonly = 0; } else break; } /* * Special builtins lose their special properties when * called via 'command'. */ if (cmd_flags & DO_NOFUNC) cmdentry.special = 0; } /* Fork off a child process if necessary. */ if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) && ((flags & EV_EXIT) == 0 || have_traps())) || ((flags & EV_BACKCMD) != 0 && (cmdentry.cmdtype != CMDBUILTIN || !safe_builtin(cmdentry.u.index, argc, argv)))) { jp = makejob(cmd, 1); mode = FORK_FG; if (flags & EV_BACKCMD) { mode = FORK_NOJOB; if (pipe(pip) < 0) error("Pipe call failed: %s", strerror(errno)); } if (cmdentry.cmdtype == CMDNORMAL && cmd->ncmd.redirect == NULL && varlist.count == 0 && (mode == FORK_FG || mode == FORK_NOJOB) && !disvforkset() && !iflag && !mflag) { vforkexecshell(jp, argv, environment(), path, cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); goto parent; } if (forkshell(jp, cmd, mode) != 0) goto parent; /* at end of routine */ if (flags & EV_BACKCMD) { FORCEINTON; close(pip[0]); if (pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } flags &= ~EV_BACKCMD; } flags |= EV_EXIT; } /* This is the child process if a fork occurred. */ /* Execute the command. */ if (cmdentry.cmdtype == CMDFUNCTION) { #ifdef DEBUG trputs("Shell function: "); trargs(argv); #endif saveparam = shellparam; shellparam.malloc = 0; shellparam.reset = 1; shellparam.nparam = argc - 1; shellparam.p = argv + 1; shellparam.optp = NULL; shellparam.optnext = NULL; INTOFF; savelocalvars = localvars; localvars = NULL; reffunc(cmdentry.u.func); savehandler = handler; if (setjmp(jmploc.loc)) { popredir(); unreffunc(cmdentry.u.func); poplocalvars(); localvars = savelocalvars; freeparam(&shellparam); shellparam = saveparam; funcnest--; handler = savehandler; longjmp(handler->loc, 1); } handler = &jmploc; funcnest++; redirect(cmd->ncmd.redirect, REDIR_PUSH); INTON; for (i = 0; i < varlist.count; i++) mklocal(varlist.args[i]); exitstatus = oexitstatus; evaltree(getfuncnode(cmdentry.u.func), flags & (EV_TESTED | EV_EXIT)); INTOFF; unreffunc(cmdentry.u.func); poplocalvars(); localvars = savelocalvars; freeparam(&shellparam); shellparam = saveparam; handler = savehandler; funcnest--; popredir(); INTON; if (evalskip == SKIPRETURN) { evalskip = 0; skipcount = 0; } if (jp) exitshell(exitstatus); } else if (cmdentry.cmdtype == CMDBUILTIN) { #ifdef DEBUG trputs("builtin command: "); trargs(argv); #endif mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; if (flags == EV_BACKCMD) { memout.nextc = memout.buf; mode |= REDIR_BACKQ; } savecmdname = commandname; savetopfile = getcurrentfile(); cmdenviron = &varlist; e = -1; savehandler = handler; if (setjmp(jmploc.loc)) { e = exception; if (e == EXINT) exitstatus = SIGINT+128; - else if (e != EXEXEC && e != EXEXIT) - exitstatus = 2; goto cmddone; } handler = &jmploc; redirect(cmd->ncmd.redirect, mode); outclearerror(out1); /* * If there is no command word, redirection errors should * not be fatal but assignment errors should. */ if (argc == 0) cmdentry.special = 1; listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); if (argc > 0) bltinsetlocale(); commandname = argv[0]; argptr = argv + 1; nextopt_optptr = NULL; /* initialize nextopt */ builtin_flags = flags; exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); flushall(); if (outiserror(out1)) { warning("write error on stdout"); if (exitstatus == 0 || exitstatus == 1) exitstatus = 2; } cmddone: if (argc > 0) bltinunsetlocale(); cmdenviron = NULL; out1 = &output; out2 = &errout; freestdout(); handler = savehandler; commandname = savecmdname; if (jp) exitshell(exitstatus); if (flags == EV_BACKCMD) { backcmd->buf = memout.buf; backcmd->nleft = memout.buf != NULL ? memout.nextc - memout.buf : 0; memout.buf = NULL; memout.nextc = NULL; memout.bufend = NULL; memout.bufsize = 64; } if (cmdentry.u.index != EXECCMD) popredir(); if (e != -1) { - if ((e != EXERROR && e != EXEXEC) - || cmdentry.special) + if (e != EXERROR || cmdentry.special) exraise(e); popfilesupto(savetopfile); if (flags != EV_BACKCMD) FORCEINTON; } } else { #ifdef DEBUG trputs("normal command: "); trargs(argv); #endif redirect(cmd->ncmd.redirect, 0); for (i = 0; i < varlist.count; i++) setvareq(varlist.args[i], VEXPORT|VSTACK); envp = environment(); shellexec(argv, envp, path, cmdentry.u.index); /*NOTREACHED*/ } goto out; parent: /* parent process gets here (if we forked) */ if (mode == FORK_FG) { /* argument to fork */ INTOFF; exitstatus = waitforjob(jp, &signaled); INTON; if (iflag && loopnest > 0 && signaled) { evalskip = SKIPBREAK; skipcount = loopnest; } } else if (mode == FORK_NOJOB) { backcmd->fd = pip[0]; close(pip[1]); backcmd->jp = jp; } out: if (lastarg) setvar("_", lastarg, 0); if (do_clearcmdentry) clearcmdentry(); } /* * Search for a command. This is called before we fork so that the * location of the command will be available in the parent as well as * the child. The check for "goodname" is an overly conservative * check that the name will not be subject to expansion. */ static void prehash(union node *n) { struct cmdentry entry; if (n && n->type == NCMD && n->ncmd.args) if (goodname(n->ncmd.args->narg.text)) find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); } /* * Builtin commands. Builtin commands whose functions are closely * tied to evaluation are implemented here. */ /* * No command given, a bltin command with no arguments, or a bltin command * with an invalid name. */ int bltincmd(int argc, char **argv) { if (argc > 1) { out2fmt_flush("%s: not found\n", argv[1]); return 127; } /* * Preserve exitstatus of a previous possible command substitution * as POSIX mandates */ return exitstatus; } /* * Handle break and continue commands. Break, continue, and return are * all handled by setting the evalskip flag. The evaluation routines * above all check this flag, and if it is set they start skipping * commands rather than executing them. The variable skipcount is * the number of loops to break/continue, or the number of function * levels to return. (The latter is always 1.) It should probably * be an error to break out of more loops than exist, but it isn't * in the standard shell so we don't make it one here. */ int breakcmd(int argc, char **argv) { long n; char *end; if (argc > 1) { /* Allow arbitrarily large numbers. */ n = strtol(argv[1], &end, 10); if (!is_digit(argv[1][0]) || *end != '\0') error("Illegal number: %s", argv[1]); } else n = 1; if (n > loopnest) n = loopnest; if (n > 0) { evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; skipcount = n; } return 0; } /* * The `command' command. */ int commandcmd(int argc __unused, char **argv __unused) { const char *path; int ch; int cmd = -1; path = bltinlookup("PATH", 1); while ((ch = nextopt("pvV")) != '\0') { switch (ch) { case 'p': path = _PATH_STDPATH; break; case 'v': cmd = TYPECMD_SMALLV; break; case 'V': cmd = TYPECMD_BIGV; break; } } if (cmd != -1) { if (*argptr == NULL || argptr[1] != NULL) error("wrong number of arguments"); return typecmd_impl(2, argptr - 1, cmd, path); } if (*argptr != NULL) error("commandcmd bad call"); /* * Do nothing successfully if no command was specified; * ksh also does this. */ return 0; } /* * The return command. */ int returncmd(int argc, char **argv) { int ret = argc > 1 ? number(argv[1]) : oexitstatus; evalskip = SKIPRETURN; skipcount = 1; return ret; } int falsecmd(int argc __unused, char **argv __unused) { return 1; } int truecmd(int argc __unused, char **argv __unused) { return 0; } int execcmd(int argc, char **argv) { int i; /* * Because we have historically not supported any options, * only treat "--" specially. */ if (argc > 1 && strcmp(argv[1], "--") == 0) argc--, argv++; if (argc > 1) { iflag = 0; /* exit on error */ mflag = 0; optschanged(); for (i = 0; i < cmdenviron->count; i++) setvareq(cmdenviron->args[i], VEXPORT|VSTACK); shellexec(argv + 1, environment(), pathval(), 0); } return 0; } int timescmd(int argc __unused, char **argv __unused) { struct rusage ru; long shumins, shsmins, chumins, chsmins; double shusecs, shssecs, chusecs, chssecs; if (getrusage(RUSAGE_SELF, &ru) < 0) return 1; shumins = ru.ru_utime.tv_sec / 60; shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; shsmins = ru.ru_stime.tv_sec / 60; shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; if (getrusage(RUSAGE_CHILDREN, &ru) < 0) return 1; chumins = ru.ru_utime.tv_sec / 60; chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; chsmins = ru.ru_stime.tv_sec / 60; chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); return 0; } Index: head/bin/sh/exec.c =================================================================== --- head/bin/sh/exec.c (revision 340283) +++ head/bin/sh/exec.c (revision 340284) @@ -1,783 +1,780 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 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. 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 * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include /* * When commands are first encountered, they are entered in a hash table. * This ensures that a full path search will not have to be done for them * on each invocation. * * We should investigate converting to a linear search, even though that * would make the command name "hash" a misnomer. */ #include "shell.h" #include "main.h" #include "nodes.h" #include "parser.h" #include "redir.h" #include "eval.h" #include "exec.h" #include "builtins.h" #include "var.h" #include "options.h" #include "input.h" #include "output.h" #include "syntax.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "show.h" #include "jobs.h" #include "alias.h" #define CMDTABLESIZE 31 /* should be prime */ struct tblentry { struct tblentry *next; /* next entry in hash chain */ union param param; /* definition of builtin function */ int special; /* flag for special builtin commands */ signed char cmdtype; /* index identifying command */ char cmdname[]; /* name of command */ }; static struct tblentry *cmdtable[CMDTABLESIZE]; static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ static void tryexec(char *, char **, char **); static void printentry(struct tblentry *, int); static struct tblentry *cmdlookup(const char *, int); static void delete_cmd_entry(void); static void addcmdentry(const char *, struct cmdentry *); /* * Exec a program. Never returns. If you change this routine, you may * have to change the find_command routine as well. * * The argv array may be changed and element argv[-1] should be writable. */ void shellexec(char **argv, char **envp, const char *path, int idx) { char *cmdname; const char *opt; int e; if (strchr(argv[0], '/') != NULL) { tryexec(argv[0], argv, envp); e = errno; } else { e = ENOENT; while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) { if (--idx < 0 && opt == NULL) { tryexec(cmdname, argv, envp); if (errno != ENOENT && errno != ENOTDIR) e = errno; if (e == ENOEXEC) break; } stunalloc(cmdname); } } /* Map to POSIX errors */ - if (e == ENOENT || e == ENOTDIR) { - exitstatus = 127; - exerror(EXEXEC, "%s: not found", argv[0]); - } else { - exitstatus = 126; - exerror(EXEXEC, "%s: %s", argv[0], strerror(e)); - } + if (e == ENOENT || e == ENOTDIR) + errorwithstatus(127, "%s: not found", argv[0]); + else + errorwithstatus(126, "%s: %s", argv[0], strerror(e)); } static void tryexec(char *cmd, char **argv, char **envp) { int e, in; ssize_t n; char buf[256]; execve(cmd, argv, envp); e = errno; if (e == ENOEXEC) { INTOFF; in = open(cmd, O_RDONLY | O_NONBLOCK); if (in != -1) { n = pread(in, buf, sizeof buf, 0); close(in); if (n > 0 && memchr(buf, '\0', n) != NULL) { errno = ENOEXEC; return; } } *argv = cmd; *--argv = __DECONST(char *, _PATH_BSHELL); execve(_PATH_BSHELL, argv, envp); } errno = e; } /* * Do a path search. The variable path (passed by reference) should be * set to the start of the path before the first call; padvance will update * this value as it proceeds. Successive calls to padvance will return * the possible path expansions in sequence. If popt is not NULL, options * are processed: if an option (indicated by a percent sign) appears in * the path entry then *popt will be set to point to it; else *popt will be * set to NULL. If popt is NULL, percent signs are not special. */ char * padvance(const char **path, const char **popt, const char *name) { const char *p, *start; char *q; size_t len, namelen; if (*path == NULL) return NULL; start = *path; if (popt != NULL) for (p = start; *p && *p != ':' && *p != '%'; p++) ; /* nothing */ else for (p = start; *p && *p != ':'; p++) ; /* nothing */ namelen = strlen(name); len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ STARTSTACKSTR(q); CHECKSTRSPACE(len, q); if (p != start) { memcpy(q, start, p - start); q += p - start; *q++ = '/'; } memcpy(q, name, namelen + 1); if (popt != NULL) { if (*p == '%') { *popt = ++p; while (*p && *p != ':') p++; } else *popt = NULL; } if (*p == ':') *path = p + 1; else *path = NULL; return stalloc(len); } /*** Command hashing code ***/ int hashcmd(int argc __unused, char **argv __unused) { struct tblentry **pp; struct tblentry *cmdp; int c; int verbose; struct cmdentry entry; char *name; int errors; errors = 0; verbose = 0; while ((c = nextopt("rv")) != '\0') { if (c == 'r') { clearcmdentry(); } else if (c == 'v') { verbose++; } } if (*argptr == NULL) { for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { if (cmdp->cmdtype == CMDNORMAL) printentry(cmdp, verbose); } } return 0; } while ((name = *argptr) != NULL) { if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDNORMAL) delete_cmd_entry(); find_command(name, &entry, DO_ERR, pathval()); if (entry.cmdtype == CMDUNKNOWN) errors = 1; else if (verbose) { cmdp = cmdlookup(name, 0); if (cmdp != NULL) printentry(cmdp, verbose); else { outfmt(out2, "%s: not found\n", name); errors = 1; } flushall(); } argptr++; } return errors; } static void printentry(struct tblentry *cmdp, int verbose) { int idx; const char *path, *opt; char *name; if (cmdp->cmdtype == CMDNORMAL) { idx = cmdp->param.index; path = pathval(); do { name = padvance(&path, &opt, cmdp->cmdname); stunalloc(name); } while (--idx >= 0); out1str(name); } else if (cmdp->cmdtype == CMDBUILTIN) { out1fmt("builtin %s", cmdp->cmdname); } else if (cmdp->cmdtype == CMDFUNCTION) { out1fmt("function %s", cmdp->cmdname); if (verbose) { INTOFF; name = commandtext(getfuncnode(cmdp->param.func)); out1c(' '); out1str(name); ckfree(name); INTON; } #ifdef DEBUG } else { error("internal error: cmdtype %d", cmdp->cmdtype); #endif } out1c('\n'); } /* * Resolve a command name. If you change this routine, you may have to * change the shellexec routine as well. */ void find_command(const char *name, struct cmdentry *entry, int act, const char *path) { struct tblentry *cmdp, loc_cmd; int idx; const char *opt; char *fullname; struct stat statb; int e; int i; int spec; int cd; /* If name contains a slash, don't use the hash table */ if (strchr(name, '/') != NULL) { entry->cmdtype = CMDNORMAL; entry->u.index = 0; entry->special = 0; return; } cd = 0; /* If name is in the table, we're done */ if ((cmdp = cmdlookup(name, 0)) != NULL) { if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) cmdp = NULL; else goto success; } /* Check for builtin next */ if ((i = find_builtin(name, &spec)) >= 0) { INTOFF; cmdp = cmdlookup(name, 1); if (cmdp->cmdtype == CMDFUNCTION) cmdp = &loc_cmd; cmdp->cmdtype = CMDBUILTIN; cmdp->param.index = i; cmdp->special = spec; INTON; goto success; } /* We have to search path. */ e = ENOENT; idx = -1; for (;(fullname = padvance(&path, &opt, name)) != NULL; stunalloc(fullname)) { idx++; if (opt) { if (strncmp(opt, "func", 4) == 0) { /* handled below */ } else { continue; /* ignore unimplemented options */ } } if (fullname[0] != '/') cd = 1; if (stat(fullname, &statb) < 0) { if (errno != ENOENT && errno != ENOTDIR) e = errno; continue; } e = EACCES; /* if we fail, this will be the error */ if (!S_ISREG(statb.st_mode)) continue; if (opt) { /* this is a %func directory */ readcmdfile(fullname); if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) error("%s not defined in %s", name, fullname); stunalloc(fullname); goto success; } #ifdef notdef if (statb.st_uid == geteuid()) { if ((statb.st_mode & 0100) == 0) goto loop; } else if (statb.st_gid == getegid()) { if ((statb.st_mode & 010) == 0) goto loop; } else { if ((statb.st_mode & 01) == 0) goto loop; } #endif TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); INTOFF; stunalloc(fullname); cmdp = cmdlookup(name, 1); if (cmdp->cmdtype == CMDFUNCTION) cmdp = &loc_cmd; cmdp->cmdtype = CMDNORMAL; cmdp->param.index = idx; cmdp->special = 0; INTON; goto success; } if (act & DO_ERR) { if (e == ENOENT || e == ENOTDIR) outfmt(out2, "%s: not found\n", name); else outfmt(out2, "%s: %s\n", name, strerror(e)); } entry->cmdtype = CMDUNKNOWN; entry->u.index = 0; entry->special = 0; return; success: if (cd) cmdtable_cd = 1; entry->cmdtype = cmdp->cmdtype; entry->u = cmdp->param; entry->special = cmdp->special; } /* * Search the table of builtin commands. */ int find_builtin(const char *name, int *special) { const unsigned char *bp; size_t len; len = strlen(name); for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) { if (bp[0] == len && memcmp(bp + 2, name, len) == 0) { *special = (bp[1] & BUILTIN_SPECIAL) != 0; return bp[1] & ~BUILTIN_SPECIAL; } } return -1; } /* * Called when a cd is done. If any entry in cmdtable depends on the current * directory, simply clear cmdtable completely. */ void hashcd(void) { if (cmdtable_cd) clearcmdentry(); } /* * Called before PATH is changed. The argument is the new value of PATH; * pathval() still returns the old value at this point. Called with * interrupts off. */ void changepath(const char *newval __unused) { clearcmdentry(); } /* * Clear out cached utility locations. */ void clearcmdentry(void) { struct tblentry **tblp; struct tblentry **pp; struct tblentry *cmdp; INTOFF; for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { pp = tblp; while ((cmdp = *pp) != NULL) { if (cmdp->cmdtype == CMDNORMAL) { *pp = cmdp->next; ckfree(cmdp); } else { pp = &cmdp->next; } } } cmdtable_cd = 0; INTON; } /* * Locate a command in the command hash table. If "add" is nonzero, * add the command to the table if it is not already present. The * variable "lastcmdentry" is set to point to the address of the link * pointing to the entry, so that delete_cmd_entry can delete the * entry. */ static struct tblentry **lastcmdentry; static struct tblentry * cmdlookup(const char *name, int add) { unsigned int hashval; const char *p; struct tblentry *cmdp; struct tblentry **pp; size_t len; p = name; hashval = (unsigned char)*p << 4; while (*p) hashval += *p++; pp = &cmdtable[hashval % CMDTABLESIZE]; for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { if (equal(cmdp->cmdname, name)) break; pp = &cmdp->next; } if (add && cmdp == NULL) { INTOFF; len = strlen(name); cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); cmdp->next = NULL; cmdp->cmdtype = CMDUNKNOWN; memcpy(cmdp->cmdname, name, len + 1); INTON; } lastcmdentry = pp; return cmdp; } /* * Delete the command entry returned on the last lookup. */ static void delete_cmd_entry(void) { struct tblentry *cmdp; INTOFF; cmdp = *lastcmdentry; *lastcmdentry = cmdp->next; ckfree(cmdp); INTON; } /* * Add a new command entry, replacing any existing command entry for * the same name. */ static void addcmdentry(const char *name, struct cmdentry *entry) { struct tblentry *cmdp; INTOFF; cmdp = cmdlookup(name, 1); if (cmdp->cmdtype == CMDFUNCTION) { unreffunc(cmdp->param.func); } cmdp->cmdtype = entry->cmdtype; cmdp->param = entry->u; cmdp->special = entry->special; INTON; } /* * Define a shell function. */ void defun(const char *name, union node *func) { struct cmdentry entry; INTOFF; entry.cmdtype = CMDFUNCTION; entry.u.func = copyfunc(func); entry.special = 0; addcmdentry(name, &entry); INTON; } /* * Delete a function if it exists. * Called with interrupts off. */ int unsetfunc(const char *name) { struct tblentry *cmdp; if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { unreffunc(cmdp->param.func); delete_cmd_entry(); return (0); } return (0); } /* * Check if a function by a certain name exists. */ int isfunc(const char *name) { struct tblentry *cmdp; cmdp = cmdlookup(name, 0); return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); } /* * Shared code for the following builtin commands: * type, command -v, command -V */ int typecmd_impl(int argc, char **argv, int cmd, const char *path) { struct cmdentry entry; struct tblentry *cmdp; const char *const *pp; struct alias *ap; int i; int error1 = 0; if (path != pathval()) clearcmdentry(); for (i = 1; i < argc; i++) { /* First look at the keywords */ for (pp = parsekwd; *pp; pp++) if (**pp == *argv[i] && equal(*pp, argv[i])) break; if (*pp) { if (cmd == TYPECMD_SMALLV) out1fmt("%s\n", argv[i]); else out1fmt("%s is a shell keyword\n", argv[i]); continue; } /* Then look at the aliases */ if ((ap = lookupalias(argv[i], 1)) != NULL) { if (cmd == TYPECMD_SMALLV) { out1fmt("alias %s=", argv[i]); out1qstr(ap->val); outcslow('\n', out1); } else out1fmt("%s is an alias for %s\n", argv[i], ap->val); continue; } /* Then check if it is a tracked alias */ if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { entry.cmdtype = cmdp->cmdtype; entry.u = cmdp->param; entry.special = cmdp->special; } else { /* Finally use brute force */ find_command(argv[i], &entry, 0, path); } switch (entry.cmdtype) { case CMDNORMAL: { if (strchr(argv[i], '/') == NULL) { const char *path2 = path; const char *opt2; char *name; int j = entry.u.index; do { name = padvance(&path2, &opt2, argv[i]); stunalloc(name); } while (--j >= 0); if (cmd == TYPECMD_SMALLV) out1fmt("%s\n", name); else out1fmt("%s is%s %s\n", argv[i], (cmdp && cmd == TYPECMD_TYPE) ? " a tracked alias for" : "", name); } else { if (eaccess(argv[i], X_OK) == 0) { if (cmd == TYPECMD_SMALLV) out1fmt("%s\n", argv[i]); else out1fmt("%s is %s\n", argv[i], argv[i]); } else { if (cmd != TYPECMD_SMALLV) outfmt(out2, "%s: %s\n", argv[i], strerror(errno)); error1 |= 127; } } break; } case CMDFUNCTION: if (cmd == TYPECMD_SMALLV) out1fmt("%s\n", argv[i]); else out1fmt("%s is a shell function\n", argv[i]); break; case CMDBUILTIN: if (cmd == TYPECMD_SMALLV) out1fmt("%s\n", argv[i]); else if (entry.special) out1fmt("%s is a special shell builtin\n", argv[i]); else out1fmt("%s is a shell builtin\n", argv[i]); break; default: if (cmd != TYPECMD_SMALLV) outfmt(out2, "%s: not found\n", argv[i]); error1 |= 127; break; } } if (path != pathval()) clearcmdentry(); return error1; } /* * Locate and print what a word is... */ int typecmd(int argc, char **argv) { if (argc > 2 && strcmp(argv[1], "--") == 0) argc--, argv++; return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); } Index: head/bin/sh/jobs.c =================================================================== --- head/bin/sh/jobs.c (revision 340283) +++ head/bin/sh/jobs.c (revision 340284) @@ -1,1553 +1,1553 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 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. 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 * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "shell.h" #if JOBS #include #undef CEOF /* syntax.h redefines this */ #endif #include "redir.h" #include "exec.h" #include "show.h" #include "main.h" #include "parser.h" #include "nodes.h" #include "jobs.h" #include "options.h" #include "trap.h" #include "syntax.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "var.h" #include "builtins.h" #include "eval.h" /* * A job structure contains information about a job. A job is either a * single process or a set of processes contained in a pipeline. In the * latter case, pidlist will be non-NULL, and will point to a -1 terminated * array of pids. */ struct procstat { pid_t pid; /* process id */ int status; /* status flags (defined above) */ char *cmd; /* text of command being run */ }; /* states */ #define JOBSTOPPED 1 /* all procs are stopped */ #define JOBDONE 2 /* all procs are completed */ struct job { struct procstat ps0; /* status of process */ struct procstat *ps; /* status or processes when more than one */ short nprocs; /* number of processes */ pid_t pgrp; /* process group of this job */ char state; /* true if job is finished */ char used; /* true if this entry is in used */ char changed; /* true if status has changed */ char foreground; /* true if running in the foreground */ char remembered; /* true if $! referenced */ #if JOBS char jobctl; /* job running under job control */ struct job *next; /* job used after this one */ #endif }; static struct job *jobtab; /* array of jobs */ static int njobs; /* size of array */ static pid_t backgndpid = -1; /* pid of last background process */ static struct job *bgjob = NULL; /* last background process */ #if JOBS static struct job *jobmru; /* most recently used job list */ static pid_t initialpgrp; /* pgrp of shell on invocation */ #endif static int ttyfd = -1; /* mode flags for dowait */ #define DOWAIT_BLOCK 0x1 /* wait until a child exits */ #define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signal */ #define DOWAIT_SIG_TRAP 0x4 /* if DOWAIT_SIG, abort on trapped signal only */ #if JOBS static void restartjob(struct job *); #endif static void freejob(struct job *); static int waitcmdloop(struct job *); static struct job *getjob_nonotfound(const char *); static struct job *getjob(const char *); pid_t killjob(const char *, int); static pid_t dowait(int, struct job *); static void checkzombies(void); static void cmdtxt(union node *); static void cmdputs(const char *); #if JOBS static void setcurjob(struct job *); static void deljob(struct job *); static struct job *getcurjob(struct job *); #endif static void printjobcmd(struct job *); static void showjob(struct job *, int); /* * Turn job control on and off. */ static int jobctl; #if JOBS static void jobctl_notty(void) { if (ttyfd >= 0) { close(ttyfd); ttyfd = -1; } if (!iflag) { setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); jobctl = 1; return; } out2fmt_flush("sh: can't access tty; job control turned off\n"); mflag = 0; } void setjobctl(int on) { int i; if (on == jobctl || rootshell == 0) return; if (on) { if (ttyfd != -1) close(ttyfd); if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { i = 0; while (i <= 2 && !isatty(i)) i++; if (i > 2 || (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { jobctl_notty(); return; } } if (ttyfd < 10) { /* * Keep our TTY file descriptor out of the way of * the user's redirections. */ if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { jobctl_notty(); return; } close(ttyfd); ttyfd = i; } do { /* while we are in the background */ initialpgrp = tcgetpgrp(ttyfd); if (initialpgrp < 0) { jobctl_notty(); return; } if (initialpgrp != getpgrp()) { if (!iflag) { initialpgrp = -1; jobctl_notty(); return; } kill(0, SIGTTIN); continue; } } while (0); setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); setpgid(0, rootpid); tcsetpgrp(ttyfd, rootpid); } else { /* turning job control off */ setpgid(0, initialpgrp); if (ttyfd >= 0) { tcsetpgrp(ttyfd, initialpgrp); close(ttyfd); ttyfd = -1; } setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); } jobctl = on; } #endif #if JOBS int fgcmd(int argc __unused, char **argv __unused) { struct job *jp; pid_t pgrp; int status; nextopt(""); jp = getjob(*argptr); if (jp->jobctl == 0) error("job not created under job control"); printjobcmd(jp); flushout(&output); pgrp = jp->ps[0].pid; if (ttyfd >= 0) tcsetpgrp(ttyfd, pgrp); restartjob(jp); jp->foreground = 1; INTOFF; status = waitforjob(jp, (int *)NULL); INTON; return status; } int bgcmd(int argc __unused, char **argv __unused) { struct job *jp; nextopt(""); do { jp = getjob(*argptr); if (jp->jobctl == 0) error("job not created under job control"); if (jp->state == JOBDONE) continue; restartjob(jp); jp->foreground = 0; out1fmt("[%td] ", jp - jobtab + 1); printjobcmd(jp); } while (*argptr != NULL && *++argptr != NULL); return 0; } static void restartjob(struct job *jp) { struct procstat *ps; int i; if (jp->state == JOBDONE) return; setcurjob(jp); INTOFF; kill(-jp->ps[0].pid, SIGCONT); for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { if (WIFSTOPPED(ps->status)) { ps->status = -1; jp->state = 0; } } INTON; } #endif int jobscmd(int argc __unused, char *argv[] __unused) { char *id; int ch, mode; mode = SHOWJOBS_DEFAULT; while ((ch = nextopt("lps")) != '\0') { switch (ch) { case 'l': mode = SHOWJOBS_VERBOSE; break; case 'p': mode = SHOWJOBS_PGIDS; break; case 's': mode = SHOWJOBS_PIDS; break; } } if (*argptr == NULL) showjobs(0, mode); else while ((id = *argptr++) != NULL) showjob(getjob(id), mode); return (0); } static void printjobcmd(struct job *jp) { struct procstat *ps; int i; for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { out1str(ps->cmd); if (i > 0) out1str(" | "); } out1c('\n'); } static void showjob(struct job *jp, int mode) { char s[64]; char statebuf[16]; const char *statestr, *coredump; struct procstat *ps; struct job *j; int col, curr, i, jobno, prev, procno, status; char c; procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; jobno = jp - jobtab + 1; curr = prev = 0; #if JOBS if ((j = getcurjob(NULL)) != NULL) { curr = j - jobtab + 1; if ((j = getcurjob(j)) != NULL) prev = j - jobtab + 1; } #endif coredump = ""; status = jp->ps[jp->nprocs - 1].status; if (jp->state == 0) { statestr = "Running"; #if JOBS } else if (jp->state == JOBSTOPPED) { ps = jp->ps + jp->nprocs - 1; while (!WIFSTOPPED(ps->status) && ps > jp->ps) ps--; if (WIFSTOPPED(ps->status)) i = WSTOPSIG(ps->status); else i = -1; statestr = strsignal(i); if (statestr == NULL) statestr = "Suspended"; #endif } else if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) statestr = "Done"; else { fmtstr(statebuf, sizeof(statebuf), "Done(%d)", WEXITSTATUS(status)); statestr = statebuf; } } else { i = WTERMSIG(status); statestr = strsignal(i); if (statestr == NULL) statestr = "Unknown signal"; if (WCOREDUMP(status)) coredump = " (core dumped)"; } for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { out1fmt("%d\n", (int)ps->pid); continue; } if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) continue; if (jobno == curr && ps == jp->ps) c = '+'; else if (jobno == prev && ps == jp->ps) c = '-'; else c = ' '; if (ps == jp->ps) fmtstr(s, 64, "[%d] %c ", jobno, c); else fmtstr(s, 64, " %c ", c); out1str(s); col = strlen(s); if (mode == SHOWJOBS_VERBOSE) { fmtstr(s, 64, "%d ", (int)ps->pid); out1str(s); col += strlen(s); } if (ps == jp->ps) { out1str(statestr); out1str(coredump); col += strlen(statestr) + strlen(coredump); } do { out1c(' '); col++; } while (col < 30); if (mode == SHOWJOBS_VERBOSE) { out1str(ps->cmd); out1c('\n'); } else printjobcmd(jp); } } /* * Print a list of jobs. If "change" is nonzero, only print jobs whose * statuses have changed since the last call to showjobs. * * If the shell is interrupted in the process of creating a job, the * result may be a job structure containing zero processes. Such structures * will be freed here. */ void showjobs(int change, int mode) { int jobno; struct job *jp; TRACE(("showjobs(%d) called\n", change)); checkzombies(); for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { if (! jp->used) continue; if (jp->nprocs == 0) { freejob(jp); continue; } if (change && ! jp->changed) continue; showjob(jp, mode); if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { jp->changed = 0; /* Hack: discard jobs for which $! has not been * referenced in interactive mode when they terminate. */ if (jp->state == JOBDONE && !jp->remembered && (iflag || jp != bgjob)) { freejob(jp); } } } } /* * Mark a job structure as unused. */ static void freejob(struct job *jp) { struct procstat *ps; int i; INTOFF; if (bgjob == jp) bgjob = NULL; for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { if (ps->cmd != nullstr) ckfree(ps->cmd); } if (jp->ps != &jp->ps0) ckfree(jp->ps); jp->used = 0; #if JOBS deljob(jp); #endif INTON; } int waitcmd(int argc __unused, char **argv __unused) { struct job *job; int retval; nextopt(""); if (*argptr == NULL) return (waitcmdloop(NULL)); do { job = getjob_nonotfound(*argptr); if (job == NULL) retval = 127; else retval = waitcmdloop(job); argptr++; } while (*argptr != NULL); return (retval); } static int waitcmdloop(struct job *job) { int status, retval, sig; struct job *jp; /* * Loop until a process is terminated or stopped, or a SIGINT is * received. */ do { if (job != NULL) { if (job->state == JOBDONE) { status = job->ps[job->nprocs - 1].status; if (WIFEXITED(status)) retval = WEXITSTATUS(status); else retval = WTERMSIG(status) + 128; if (! iflag || ! job->changed) freejob(job); else { job->remembered = 0; if (job == bgjob) bgjob = NULL; } return retval; } } else { for (jp = jobtab ; jp < jobtab + njobs; jp++) if (jp->used && jp->state == JOBDONE) { if (! iflag || ! jp->changed) freejob(jp); else { jp->remembered = 0; if (jp == bgjob) bgjob = NULL; } } for (jp = jobtab ; ; jp++) { if (jp >= jobtab + njobs) { /* no running procs */ return 0; } if (jp->used && jp->state == 0) break; } } } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); sig = pendingsig_waitcmd; pendingsig_waitcmd = 0; return sig + 128; } int jobidcmd(int argc __unused, char **argv __unused) { struct job *jp; int i; nextopt(""); jp = getjob(*argptr); for (i = 0 ; i < jp->nprocs ; ) { out1fmt("%d", (int)jp->ps[i].pid); out1c(++i < jp->nprocs? ' ' : '\n'); } return 0; } /* * Convert a job name to a job structure. */ static struct job * getjob_nonotfound(const char *name) { int jobno; struct job *found, *jp; size_t namelen; pid_t pid; int i; if (name == NULL) { #if JOBS name = "%+"; #else error("No current job"); #endif } if (name[0] == '%') { if (is_digit(name[1])) { jobno = number(name + 1); if (jobno > 0 && jobno <= njobs && jobtab[jobno - 1].used != 0) return &jobtab[jobno - 1]; #if JOBS } else if ((name[1] == '%' || name[1] == '+') && name[2] == '\0') { if ((jp = getcurjob(NULL)) == NULL) error("No current job"); return (jp); } else if (name[1] == '-' && name[2] == '\0') { if ((jp = getcurjob(NULL)) == NULL || (jp = getcurjob(jp)) == NULL) error("No previous job"); return (jp); #endif } else if (name[1] == '?') { found = NULL; for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && strstr(jp->ps[0].cmd, name + 2) != NULL) { if (found) error("%s: ambiguous", name); found = jp; } } if (found != NULL) return (found); } else { namelen = strlen(name); found = NULL; for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && strncmp(jp->ps[0].cmd, name + 1, namelen - 1) == 0) { if (found) error("%s: ambiguous", name); found = jp; } } if (found) return found; } } else if (is_number(name)) { pid = (pid_t)number(name); for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && jp->ps[jp->nprocs - 1].pid == pid) return jp; } } return NULL; } static struct job * getjob(const char *name) { struct job *jp; jp = getjob_nonotfound(name); if (jp == NULL) error("No such job: %s", name); return (jp); } int killjob(const char *name, int sig) { struct job *jp; int i, ret; jp = getjob(name); if (jp->state == JOBDONE) return 0; if (jp->jobctl) return kill(-jp->ps[0].pid, sig); ret = -1; errno = ESRCH; for (i = 0; i < jp->nprocs; i++) if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { if (kill(jp->ps[i].pid, sig) == 0) ret = 0; } else ret = 0; return ret; } /* * Return a new job structure, */ struct job * makejob(union node *node __unused, int nprocs) { int i; struct job *jp; for (i = njobs, jp = jobtab ; ; jp++) { if (--i < 0) { INTOFF; if (njobs == 0) { jobtab = ckmalloc(4 * sizeof jobtab[0]); #if JOBS jobmru = NULL; #endif } else { jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); memcpy(jp, jobtab, njobs * sizeof jp[0]); #if JOBS /* Relocate `next' pointers and list head */ if (jobmru != NULL) jobmru = &jp[jobmru - jobtab]; for (i = 0; i < njobs; i++) if (jp[i].next != NULL) jp[i].next = &jp[jp[i].next - jobtab]; #endif if (bgjob != NULL) bgjob = &jp[bgjob - jobtab]; /* Relocate `ps' pointers */ for (i = 0; i < njobs; i++) if (jp[i].ps == &jobtab[i].ps0) jp[i].ps = &jp[i].ps0; ckfree(jobtab); jobtab = jp; } jp = jobtab + njobs; for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) ; INTON; break; } if (jp->used == 0) break; } INTOFF; jp->state = 0; jp->used = 1; jp->changed = 0; jp->nprocs = 0; jp->foreground = 0; jp->remembered = 0; #if JOBS jp->jobctl = jobctl; jp->next = NULL; #endif if (nprocs > 1) { jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); } else { jp->ps = &jp->ps0; } INTON; TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, jp - jobtab + 1)); return jp; } #if JOBS static void setcurjob(struct job *cj) { struct job *jp, *prev; for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { if (jp == cj) { if (prev != NULL) prev->next = jp->next; else jobmru = jp->next; jp->next = jobmru; jobmru = cj; return; } } cj->next = jobmru; jobmru = cj; } static void deljob(struct job *j) { struct job *jp, *prev; for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { if (jp == j) { if (prev != NULL) prev->next = jp->next; else jobmru = jp->next; return; } } } /* * Return the most recently used job that isn't `nj', and preferably one * that is stopped. */ static struct job * getcurjob(struct job *nj) { struct job *jp; /* Try to find a stopped one.. */ for (jp = jobmru; jp != NULL; jp = jp->next) if (jp->used && jp != nj && jp->state == JOBSTOPPED) return (jp); /* Otherwise the most recently used job that isn't `nj' */ for (jp = jobmru; jp != NULL; jp = jp->next) if (jp->used && jp != nj) return (jp); return (NULL); } #endif /* * Fork of a subshell. If we are doing job control, give the subshell its * own process group. Jp is a job structure that the job is to be added to. * N is the command that will be evaluated by the child. Both jp and n may * be NULL. The mode parameter can be one of the following: * FORK_FG - Fork off a foreground process. * FORK_BG - Fork off a background process. * FORK_NOJOB - Like FORK_FG, but don't give the process its own * process group even if job control is on. * * When job control is turned off, background processes have their standard * input redirected to /dev/null (except for the second and later processes * in a pipeline). */ pid_t forkshell(struct job *jp, union node *n, int mode) { pid_t pid; pid_t pgrp; TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, mode)); INTOFF; if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) checkzombies(); flushall(); pid = fork(); if (pid == -1) { TRACE(("Fork failed, errno=%d\n", errno)); INTON; error("Cannot fork: %s", strerror(errno)); } if (pid == 0) { struct job *p; int wasroot; int i; TRACE(("Child shell %d\n", (int)getpid())); wasroot = rootshell; rootshell = 0; handler = &main_handler; closescript(); INTON; forcelocal = 0; clear_traps(); #if JOBS jobctl = 0; /* do job control only in root shell */ if (wasroot && mode != FORK_NOJOB && mflag) { if (jp == NULL || jp->nprocs == 0) pgrp = getpid(); else pgrp = jp->ps[0].pid; if (setpgid(0, pgrp) == 0 && mode == FORK_FG && ttyfd >= 0) { /*** this causes superfluous TIOCSPGRPS ***/ if (tcsetpgrp(ttyfd, pgrp) < 0) error("tcsetpgrp failed, errno=%d", errno); } setsignal(SIGTSTP); setsignal(SIGTTOU); } else if (mode == FORK_BG) { ignoresig(SIGINT); ignoresig(SIGQUIT); if ((jp == NULL || jp->nprocs == 0) && ! fd0_redirected_p ()) { close(0); if (open(_PATH_DEVNULL, O_RDONLY) != 0) error("cannot open %s: %s", _PATH_DEVNULL, strerror(errno)); } } #else if (mode == FORK_BG) { ignoresig(SIGINT); ignoresig(SIGQUIT); if ((jp == NULL || jp->nprocs == 0) && ! fd0_redirected_p ()) { close(0); if (open(_PATH_DEVNULL, O_RDONLY) != 0) error("cannot open %s: %s", _PATH_DEVNULL, strerror(errno)); } } #endif INTOFF; for (i = njobs, p = jobtab ; --i >= 0 ; p++) if (p->used) freejob(p); INTON; if (wasroot && iflag) { setsignal(SIGINT); setsignal(SIGQUIT); setsignal(SIGTERM); } return pid; } if (rootshell && mode != FORK_NOJOB && mflag) { if (jp == NULL || jp->nprocs == 0) pgrp = pid; else pgrp = jp->ps[0].pid; setpgid(pid, pgrp); } if (mode == FORK_BG) { if (bgjob != NULL && bgjob->state == JOBDONE && !bgjob->remembered && !iflag) freejob(bgjob); backgndpid = pid; /* set $! */ bgjob = jp; } if (jp) { struct procstat *ps = &jp->ps[jp->nprocs++]; ps->pid = pid; ps->status = -1; ps->cmd = nullstr; if (iflag && rootshell && n) ps->cmd = commandtext(n); jp->foreground = mode == FORK_FG; #if JOBS setcurjob(jp); #endif } INTON; TRACE(("In parent shell: child = %d\n", (int)pid)); return pid; } pid_t vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) { pid_t pid; struct jmploc jmploc; struct jmploc *savehandler; TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], (void *)pip)); INTOFF; flushall(); savehandler = handler; pid = vfork(); if (pid == -1) { TRACE(("Vfork failed, errno=%d\n", errno)); INTON; error("Cannot fork: %s", strerror(errno)); } if (pid == 0) { TRACE(("Child shell %d\n", (int)getpid())); if (setjmp(jmploc.loc)) - _exit(exception == EXEXEC ? exitstatus : 2); + _exit(exitstatus); if (pip != NULL) { close(pip[0]); if (pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } } handler = &jmploc; shellexec(argv, envp, path, idx); } handler = savehandler; if (jp) { struct procstat *ps = &jp->ps[jp->nprocs++]; ps->pid = pid; ps->status = -1; ps->cmd = nullstr; jp->foreground = 1; #if JOBS setcurjob(jp); #endif } INTON; TRACE(("In parent shell: child = %d\n", (int)pid)); return pid; } /* * Wait for job to finish. * * Under job control we have the problem that while a child process is * running interrupts generated by the user are sent to the child but not * to the shell. This means that an infinite loop started by an inter- * active user may be hard to kill. With job control turned off, an * interactive user may place an interactive program inside a loop. If * the interactive program catches interrupts, the user doesn't want * these interrupts to also abort the loop. The approach we take here * is to have the shell ignore interrupt signals while waiting for a * foreground process to terminate, and then send itself an interrupt * signal if the child process was terminated by an interrupt signal. * Unfortunately, some programs want to do a bit of cleanup and then * exit on interrupt; unless these processes terminate themselves by * sending a signal to themselves (instead of calling exit) they will * confuse this approach. */ int waitforjob(struct job *jp, int *signaled) { #if JOBS int propagate_int = jp->jobctl && jp->foreground; #endif int status; int st; INTOFF; TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); while (jp->state == 0) if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | DOWAIT_SIG_TRAP : 0), jp) == -1) dotrap(); #if JOBS if (jp->jobctl) { if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) error("tcsetpgrp failed, errno=%d\n", errno); } if (jp->state == JOBSTOPPED) setcurjob(jp); #endif status = jp->ps[jp->nprocs - 1].status; if (signaled != NULL) *signaled = WIFSIGNALED(status); /* convert to 8 bits */ if (WIFEXITED(status)) st = WEXITSTATUS(status); #if JOBS else if (WIFSTOPPED(status)) st = WSTOPSIG(status) + 128; #endif else st = WTERMSIG(status) + 128; if (! JOBS || jp->state == JOBDONE) freejob(jp); if (int_pending()) { if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) CLEAR_PENDING_INT; } #if JOBS else if (rootshell && propagate_int && WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) kill(getpid(), SIGINT); #endif INTON; return st; } static void dummy_handler(int sig __unused) { } /* * Wait for a process to terminate. */ static pid_t dowait(int mode, struct job *job) { struct sigaction sa, osa; sigset_t mask, omask; pid_t pid; int status; struct procstat *sp; struct job *jp; struct job *thisjob; const char *sigstr; int done; int stopped; int sig; int coredump; int wflags; int restore_sigchld; TRACE(("dowait(%d, %p) called\n", mode, job)); restore_sigchld = 0; if ((mode & DOWAIT_SIG) != 0) { sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &omask); INTOFF; if (!issigchldtrapped()) { restore_sigchld = 1; sa.sa_handler = dummy_handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGCHLD, &sa, &osa); } } do { #if JOBS if (iflag) wflags = WUNTRACED | WCONTINUED; else #endif wflags = 0; if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) wflags |= WNOHANG; pid = wait3(&status, wflags, (struct rusage *)NULL); TRACE(("wait returns %d, status=%d\n", (int)pid, status)); if (pid == 0 && (mode & DOWAIT_SIG) != 0) { pid = -1; if (((mode & DOWAIT_SIG_TRAP) != 0 ? pendingsig : pendingsig_waitcmd) != 0) { errno = EINTR; break; } sigsuspend(&omask); if (int_pending()) break; } } while (pid == -1 && errno == EINTR); if (pid == -1 && errno == ECHILD && job != NULL) job->state = JOBDONE; if ((mode & DOWAIT_SIG) != 0) { if (restore_sigchld) sigaction(SIGCHLD, &osa, NULL); sigprocmask(SIG_SETMASK, &omask, NULL); INTON; } if (pid <= 0) return pid; INTOFF; thisjob = NULL; for (jp = jobtab ; jp < jobtab + njobs ; jp++) { if (jp->used && jp->nprocs > 0) { done = 1; stopped = 1; for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { if (sp->pid == -1) continue; if (sp->pid == pid && (sp->status == -1 || WIFSTOPPED(sp->status))) { TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", (int)pid, sp->status, status)); if (WIFCONTINUED(status)) { sp->status = -1; jp->state = 0; } else sp->status = status; thisjob = jp; } if (sp->status == -1) stopped = 0; else if (WIFSTOPPED(sp->status)) done = 0; } if (stopped) { /* stopped or done */ int state = done? JOBDONE : JOBSTOPPED; if (jp->state != state) { TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); jp->state = state; if (jp != job) { if (done && !jp->remembered && !iflag && jp != bgjob) freejob(jp); #if JOBS else if (done) deljob(jp); #endif } } } } } INTON; if (!thisjob || thisjob->state == 0) ; else if ((!rootshell || !iflag || thisjob == job) && thisjob->foreground && thisjob->state != JOBSTOPPED) { sig = 0; coredump = 0; for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) if (WIFSIGNALED(sp->status)) { sig = WTERMSIG(sp->status); coredump = WCOREDUMP(sp->status); } if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { sigstr = strsignal(sig); if (sigstr != NULL) out2str(sigstr); else out2str("Unknown signal"); if (coredump) out2str(" (core dumped)"); out2c('\n'); flushout(out2); } } else { TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); thisjob->changed = 1; } return pid; } /* * return 1 if there are stopped jobs, otherwise 0 */ int job_warning = 0; int stoppedjobs(void) { int jobno; struct job *jp; if (job_warning) return (0); for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { if (jp->used == 0) continue; if (jp->state == JOBSTOPPED) { out2fmt_flush("You have stopped jobs.\n"); job_warning = 2; return (1); } } return (0); } static void checkzombies(void) { while (njobs > 0 && dowait(0, NULL) > 0) ; } int backgndpidset(void) { return backgndpid != -1; } pid_t backgndpidval(void) { if (bgjob != NULL && !forcelocal) bgjob->remembered = 1; return backgndpid; } /* * Return a string identifying a command (to be printed by the * jobs command. */ static char *cmdnextc; static int cmdnleft; #define MAXCMDTEXT 200 char * commandtext(union node *n) { char *name; cmdnextc = name = ckmalloc(MAXCMDTEXT); cmdnleft = MAXCMDTEXT - 4; cmdtxt(n); *cmdnextc = '\0'; return name; } static void cmdtxtdogroup(union node *n) { cmdputs("; do "); cmdtxt(n); cmdputs("; done"); } static void cmdtxtredir(union node *n, const char *op, int deffd) { char s[2]; if (n->nfile.fd != deffd) { s[0] = n->nfile.fd + '0'; s[1] = '\0'; cmdputs(s); } cmdputs(op); if (n->type == NTOFD || n->type == NFROMFD) { if (n->ndup.dupfd >= 0) s[0] = n->ndup.dupfd + '0'; else s[0] = '-'; s[1] = '\0'; cmdputs(s); } else { cmdtxt(n->nfile.fname); } } static void cmdtxt(union node *n) { union node *np; struct nodelist *lp; if (n == NULL) return; switch (n->type) { case NSEMI: cmdtxt(n->nbinary.ch1); cmdputs("; "); cmdtxt(n->nbinary.ch2); break; case NAND: cmdtxt(n->nbinary.ch1); cmdputs(" && "); cmdtxt(n->nbinary.ch2); break; case NOR: cmdtxt(n->nbinary.ch1); cmdputs(" || "); cmdtxt(n->nbinary.ch2); break; case NPIPE: for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { cmdtxt(lp->n); if (lp->next) cmdputs(" | "); } break; case NSUBSHELL: cmdputs("("); cmdtxt(n->nredir.n); cmdputs(")"); break; case NREDIR: case NBACKGND: cmdtxt(n->nredir.n); break; case NIF: cmdputs("if "); cmdtxt(n->nif.test); cmdputs("; then "); cmdtxt(n->nif.ifpart); cmdputs("..."); break; case NWHILE: cmdputs("while "); cmdtxt(n->nbinary.ch1); cmdtxtdogroup(n->nbinary.ch2); break; case NUNTIL: cmdputs("until "); cmdtxt(n->nbinary.ch1); cmdtxtdogroup(n->nbinary.ch2); break; case NFOR: cmdputs("for "); cmdputs(n->nfor.var); cmdputs(" in ..."); break; case NCASE: cmdputs("case "); cmdputs(n->ncase.expr->narg.text); cmdputs(" in ..."); break; case NDEFUN: cmdputs(n->narg.text); cmdputs("() ..."); break; case NNOT: cmdputs("! "); cmdtxt(n->nnot.com); break; case NCMD: for (np = n->ncmd.args ; np ; np = np->narg.next) { cmdtxt(np); if (np->narg.next) cmdputs(" "); } for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { cmdputs(" "); cmdtxt(np); } break; case NARG: cmdputs(n->narg.text); break; case NTO: cmdtxtredir(n, ">", 1); break; case NAPPEND: cmdtxtredir(n, ">>", 1); break; case NTOFD: cmdtxtredir(n, ">&", 1); break; case NCLOBBER: cmdtxtredir(n, ">|", 1); break; case NFROM: cmdtxtredir(n, "<", 0); break; case NFROMTO: cmdtxtredir(n, "<>", 0); break; case NFROMFD: cmdtxtredir(n, "<&", 0); break; case NHERE: case NXHERE: cmdputs("<<..."); break; default: cmdputs("???"); break; } } static void cmdputs(const char *s) { const char *p; char *q; char c; int subtype = 0; if (cmdnleft <= 0) return; p = s; q = cmdnextc; while ((c = *p++) != '\0') { if (c == CTLESC) *q++ = *p++; else if (c == CTLVAR) { *q++ = '$'; if (--cmdnleft > 0) *q++ = '{'; subtype = *p++; if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) *q++ = '#'; } else if (c == '=' && subtype != 0) { *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; if (*q) q++; else cmdnleft++; if (((subtype & VSTYPE) == VSTRIMLEFTMAX || (subtype & VSTYPE) == VSTRIMRIGHTMAX) && --cmdnleft > 0) *q = q[-1], q++; subtype = 0; } else if (c == CTLENDVAR) { *q++ = '}'; } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { cmdnleft -= 5; if (cmdnleft > 0) { *q++ = '$'; *q++ = '('; *q++ = '.'; *q++ = '.'; *q++ = '.'; *q++ = ')'; } } else if (c == CTLARI) { cmdnleft -= 2; if (cmdnleft > 0) { *q++ = '$'; *q++ = '('; *q++ = '('; } p++; } else if (c == CTLENDARI) { if (--cmdnleft > 0) { *q++ = ')'; *q++ = ')'; } } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) cmdnleft++; /* ignore */ else *q++ = c; if (--cmdnleft <= 0) { *q++ = '.'; *q++ = '.'; *q++ = '.'; break; } } cmdnextc = q; } Index: head/bin/sh/main.c =================================================================== --- head/bin/sh/main.c (revision 340283) +++ head/bin/sh/main.c (revision 340284) @@ -1,348 +1,339 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 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. 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 * SUCH DAMAGE. */ #ifndef lint static char const copyright[] = "@(#) Copyright (c) 1991, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/28/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "shell.h" #include "main.h" #include "mail.h" #include "options.h" #include "output.h" #include "parser.h" #include "nodes.h" #include "expand.h" #include "eval.h" #include "jobs.h" #include "input.h" #include "trap.h" #include "var.h" #include "show.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "exec.h" #include "cd.h" #include "redir.h" #include "builtins.h" int rootpid; int rootshell; struct jmploc main_handler; int localeisutf8, initial_localeisutf8; static void reset(void); static void cmdloop(int); static void read_profile(const char *); static char *find_dot_file(char *); /* * Main routine. We initialize things, parse the arguments, execute * profiles if we're a login shell, and then call cmdloop to execute * commands. The setjmp call sets up the location to jump to when an * exception occurs. When an exception occurs the variable "state" * is used to figure out how far we had gotten. */ int main(int argc, char *argv[]) { struct stackmark smark, smark2; volatile int state; char *shinit; (void) setlocale(LC_ALL, ""); initcharset(); state = 0; if (setjmp(main_handler.loc)) { - switch (exception) { - case EXERROR: - exitstatus = 2; - break; - - default: - break; - } - if (state == 0 || iflag == 0 || ! rootshell || exception == EXEXIT) exitshell(exitstatus); reset(); if (exception == EXINT) out2fmt_flush("\n"); popstackmark(&smark); FORCEINTON; /* enable interrupts */ if (state == 1) goto state1; else if (state == 2) goto state2; else if (state == 3) goto state3; else goto state4; } handler = &main_handler; #ifdef DEBUG opentrace(); trputs("Shell args: "); trargs(argv); #endif rootpid = getpid(); rootshell = 1; INTOFF; initvar(); setstackmark(&smark); setstackmark(&smark2); procargs(argc, argv); pwd_init(iflag); INTON; if (iflag) chkmail(1); if (argv[0] && argv[0][0] == '-') { state = 1; read_profile("/etc/profile"); state1: state = 2; if (privileged == 0) read_profile("${HOME-}/.profile"); else read_profile("/etc/suid_profile"); } state2: state = 3; if (!privileged && iflag) { if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { state = 3; read_profile(shinit); } } state3: state = 4; popstackmark(&smark2); if (minusc) { evalstring(minusc, sflag ? 0 : EV_EXIT); } state4: if (sflag || minusc == NULL) { cmdloop(1); } exitshell(exitstatus); /*NOTREACHED*/ return 0; } static void reset(void) { reseteval(); resetinput(); } /* * Read and execute commands. "Top" is nonzero for the top level command * loop; it turns on prompting if the shell is interactive. */ static void cmdloop(int top) { union node *n; struct stackmark smark; int inter; int numeof = 0; TRACE(("cmdloop(%d) called\n", top)); setstackmark(&smark); for (;;) { if (pendingsig) dotrap(); inter = 0; if (iflag && top) { inter++; showjobs(1, SHOWJOBS_DEFAULT); chkmail(0); flushout(&output); } n = parsecmd(inter); /* showtree(n); DEBUG */ if (n == NEOF) { if (!top || numeof >= 50) break; if (!stoppedjobs()) { if (!Iflag) break; out2fmt_flush("\nUse \"exit\" to leave shell.\n"); } numeof++; } else if (n != NULL && nflag == 0) { job_warning = (job_warning == 2) ? 1 : 0; numeof = 0; evaltree(n, 0); } popstackmark(&smark); setstackmark(&smark); if (evalskip != 0) { if (evalskip == SKIPRETURN) evalskip = 0; break; } } popstackmark(&smark); } /* * Read /etc/profile or .profile. Return on error. */ static void read_profile(const char *name) { int fd; const char *expandedname; expandedname = expandstr(name); if (expandedname == NULL) return; INTOFF; if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 0) setinputfd(fd, 1); INTON; if (fd < 0) return; cmdloop(0); popfile(); } /* * Read a file containing shell functions. */ void readcmdfile(const char *name) { setinputfile(name, 1); cmdloop(0); popfile(); } /* * Take commands from a file. To be compatible we should do a path * search for the file, which is necessary to find sub-commands. */ static char * find_dot_file(char *basename) { char *fullname; const char *opt; const char *path = pathval(); struct stat statb; /* don't try this for absolute or relative paths */ if( strchr(basename, '/')) return basename; while ((fullname = padvance(&path, &opt, basename)) != NULL) { if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { /* * Don't bother freeing here, since it will * be freed by the caller. */ return fullname; } stunalloc(fullname); } return basename; } int dotcmd(int argc, char **argv) { char *filename, *fullname; if (argc < 2) error("missing filename"); exitstatus = 0; /* * Because we have historically not supported any options, * only treat "--" specially. */ filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1]; fullname = find_dot_file(filename); setinputfile(fullname, 1); commandname = fullname; cmdloop(0); popfile(); return exitstatus; } int exitcmd(int argc, char **argv) { if (stoppedjobs()) return 0; if (argc > 1) exitshell(number(argv[1])); else exitshell_savedstatus(); }