Index: head/bin/sh/TOUR =================================================================== --- head/bin/sh/TOUR (revision 317881) +++ head/bin/sh/TOUR (revision 317882) @@ -1,288 +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 builtins.h builtins.c + 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. 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 Variable substitution - CTLENDVAR End of variable substitution + 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. The name of the variable -comes next, terminated by an equals sign. If the type is not -VSNORMAL, then the text field in the substitution follows, ter- -minated by a CTLENDVAR byte. +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 splitting and file name -generation, have the CTLESC characters removed during the vari- -able and command substitution phase. Words which are subject to -splitting and file name generation have the CTLESC characters re- -moved as part of the file name phase. +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: Arguments are processed in three passes. The first -(performed by the routine argstr) performs variable and command -substitution. The second (ifsbreakup) performs word splitting -and the third (expandmeta) performs file name generation. +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, which is processed by the mkbuilt- -ins command. +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 builtin -command it causes the builtin command to terminate with an exit -status of 2. +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 makefile in this directory compiles these programs -in the normal fashion (so that they can be run regardless of -whether the invoker is ash), but also creates a library named -bltinlib.a which can be linked with ash. 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 pro- -gram is to be compiled stand-alone. +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 it's own output routines. There are three out- +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/eval.c =================================================================== --- head/bin/sh/eval.c (revision 317881) +++ head/bin/sh/eval.c (revision 317882) @@ -1,1382 +1,1382 @@ /*- * 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 /* For WIFSIGNALED(status) */ #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) { exitstatus = 2; 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; 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 || exception == EXEXEC) exitstatus = 2; 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) { if (idx == BLTINCMD || idx == COMMANDCMD || idx == ECHOCMD || idx == FALSECMD || idx == JOBIDCMD || idx == JOBSCMD || idx == KILLCMD || idx == PRINTFCMD || idx == PWDCMD || idx == TESTCMD || idx == TIMESCMD || idx == TRUECMD || idx == TYPECMD) 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 realstatus; 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.nleft = 0; memout.nextc = memout.buf; memout.bufsize = 64; 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 != 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.nextc - memout.buf; memout.buf = NULL; } if (cmdentry.u.index != EXECCMD) popredir(); if (e != -1) { if ((e != EXERROR && e != EXEXEC) || 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, &realstatus); INTON; if (iflag && loopnest > 0 && WIFSIGNALED(realstatus)) { 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 redirection + * 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 317881) +++ head/bin/sh/exec.c (revision 317882) @@ -1,777 +1,776 @@ /*- * 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 */ int exerrno = 0; /* Last exec error */ 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; int e; if (strchr(argv[0], '/') != NULL) { tryexec(argv[0], argv, envp); e = errno; } else { e = ENOENT; while ((cmdname = padvance(&path, argv[0])) != NULL) { if (--idx < 0 && pathopt == 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) { exerrno = 127; exerror(EXEXEC, "%s: not found", argv[0]); } else { exerrno = 126; exerror(EXEXEC, "%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 an option (indicated by * a percent sign) appears in the path entry then the global variable * pathopt will be set to point to it; otherwise pathopt will be set to * NULL. */ const char *pathopt; char * padvance(const char **path, const char *name) { const char *p, *start; char *q; size_t len, namelen; if (*path == NULL) return NULL; start = *path; for (p = start; *p && *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); pathopt = NULL; if (*p == '%') { pathopt = ++p; while (*p && *p != ':') p++; } 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; char *name; if (cmdp->cmdtype == CMDNORMAL) { idx = cmdp->param.index; path = pathval(); do { name = padvance(&path, 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; 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, and not invalidated by cd, we're done */ + /* 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, name)) != NULL; stunalloc(fullname)) { idx++; if (pathopt) { if (strncmp(pathopt, "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 (pathopt) { /* 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 command entries. The argument specifies the first entry in - * PATH which has changed. + * 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; char *name; int j = entry.u.index; do { name = padvance(&path2, 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/expand.c =================================================================== --- head/bin/sh/expand.c (revision 317881) +++ head/bin/sh/expand.c (revision 317882) @@ -1,1543 +1,1543 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1997-2005 * Herbert Xu . All rights reserved. * Copyright (c) 2010-2015 * Jilles Tjoelker . 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[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Routines to expand arguments to commands. We have to deal with * backquotes, shell variables, and file metacharacters. */ #include "shell.h" #include "main.h" #include "nodes.h" #include "eval.h" #include "expand.h" #include "syntax.h" #include "parser.h" #include "jobs.h" #include "options.h" #include "var.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "arith.h" #include "show.h" #include "builtins.h" enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK }; struct worddest { struct arglist *list; enum wordstate state; }; static char *expdest; /* output of current string */ static const char *argstr(const char *, struct nodelist **restrict, int, struct worddest *); static const char *exptilde(const char *, int); static const char *expari(const char *, struct nodelist **restrict, int, struct worddest *); static void expbackq(union node *, int, int, struct worddest *); static const char *subevalvar_trim(const char *, struct nodelist **restrict, int, int, int); static const char *subevalvar_misc(const char *, struct nodelist **restrict, const char *, int, int, int); static const char *evalvar(const char *, struct nodelist **restrict, int, struct worddest *); static int varisset(const char *, int); static void strtodest(const char *, int, int, int, struct worddest *); static void reprocess(int, int, int, int, struct worddest *); static void varvalue(const char *, int, int, int, struct worddest *); static void expandmeta(char *, struct arglist *); static void expmeta(char *, char *, struct arglist *); static int expsortcmp(const void *, const void *); static int patmatch(const char *, const char *); static void cvtnum(int, char *); static int collate_range_cmp(wchar_t, wchar_t); void emptyarglist(struct arglist *list) { list->args = list->smallarg; list->count = 0; list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]); } void appendarglist(struct arglist *list, char *str) { char **newargs; int newcapacity; if (list->count >= list->capacity) { newcapacity = list->capacity * 2; if (newcapacity < 16) newcapacity = 16; if (newcapacity > INT_MAX / (int)sizeof(newargs[0])) error("Too many entries in arglist"); newargs = stalloc(newcapacity * sizeof(newargs[0])); memcpy(newargs, list->args, list->count * sizeof(newargs[0])); list->args = newargs; list->capacity = newcapacity; } list->args[list->count++] = str; } static int collate_range_cmp(wchar_t c1, wchar_t c2) { wchar_t s1[2], s2[2]; s1[0] = c1; s1[1] = L'\0'; s2[0] = c2; s2[1] = L'\0'; return (wcscoll(s1, s2)); } static char * stputs_quotes(const char *data, const char *syntax, char *p) { while (*data) { CHECKSTRSPACE(2, p); if (syntax[(int)*data] == CCTL) USTPUTC(CTLESC, p); USTPUTC(*data++, p); } return (p); } #define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) static char * nextword(char c, int flag, char *p, struct worddest *dst) { int is_ws; is_ws = c == '\t' || c == '\n' || c == ' '; if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK : dst->state != WORD_WS_DELIMITED) || c == '\0') { STPUTC('\0', p); if (flag & EXP_GLOB) expandmeta(grabstackstr(p), dst->list); else appendarglist(dst->list, grabstackstr(p)); dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE; } else if (!is_ws && dst->state == WORD_WS_DELIMITED) dst->state = WORD_IDLE; /* Reserve space while the stack string is empty. */ appendarglist(dst->list, NULL); dst->list->count--; STARTSTACKSTR(p); return p; } #define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist) static char * stputs_split(const char *data, const char *syntax, int flag, char *p, struct worddest *dst) { const char *ifs; char c; ifs = ifsset() ? ifsval() : " \t\n"; while (*data) { CHECKSTRSPACE(2, p); c = *data++; if (strchr(ifs, c) != NULL) { NEXTWORD(c, flag, p, dst); continue; } if (flag & EXP_GLOB && syntax[(int)c] == CCTL) USTPUTC(CTLESC, p); USTPUTC(c, p); } return (p); } #define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst) /* * Perform expansions on an argument, placing the resulting list of arguments * in arglist. Parameter expansion, command substitution and arithmetic * expansion are always performed; additional expansions can be requested * via flag (EXP_*). * The result is left in the stack string. * When arglist is NULL, perform here document expansion. * - * Caution: this function uses global state and is not reentrant. - * However, a new invocation after an interrupted invocation is safe - * and will reset the global state for the new call. + * When doing something that may cause this to be re-entered, make sure + * the stack string is empty via grabstackstr() and do not assume expdest + * remains valid. */ void expandarg(union node *arg, struct arglist *arglist, int flag) { struct worddest exparg; struct nodelist *argbackq; if (fflag) flag &= ~EXP_GLOB; argbackq = arg->narg.backquote; exparg.list = arglist; exparg.state = WORD_IDLE; STARTSTACKSTR(expdest); argstr(arg->narg.text, &argbackq, flag, &exparg); if (arglist == NULL) { STACKSTRNUL(expdest); return; /* here document expanded */ } if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() || exparg.state == WORD_QUOTEMARK) { STPUTC('\0', expdest); if (flag & EXP_SPLIT) { if (flag & EXP_GLOB) expandmeta(grabstackstr(expdest), exparg.list); else appendarglist(exparg.list, grabstackstr(expdest)); } } if ((flag & EXP_SPLIT) == 0) appendarglist(arglist, grabstackstr(expdest)); } /* * Perform parameter expansion, command substitution and arithmetic * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. * This is used to expand word in ${var+word} etc. * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC * characters to allow for further processing. * * If EXP_SPLIT is set, dst receives any complete words produced. */ static const char * argstr(const char *p, struct nodelist **restrict argbackq, int flag, struct worddest *dst) { char c; int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */ int firsteq = 1; int split_lit; int lit_quoted; split_lit = flag & EXP_SPLIT_LIT; lit_quoted = flag & EXP_LIT_QUOTED; flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) p = exptilde(p, flag); for (;;) { CHECKSTRSPACE(2, expdest); switch (c = *p++) { case '\0': return (p - 1); case CTLENDVAR: case CTLENDARI: return (p); case CTLQUOTEMARK: lit_quoted = 1; /* "$@" syntax adherence hack */ if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 && p[2] == '@' && p[3] == '=') break; if ((flag & EXP_SPLIT) != 0 && expdest == stackblock()) dst->state = WORD_QUOTEMARK; break; case CTLQUOTEEND: lit_quoted = 0; break; case CTLESC: c = *p++; if (split_lit && !lit_quoted && strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { NEXTWORD(c, flag, expdest, dst); break; } if (quotes) USTPUTC(CTLESC, expdest); USTPUTC(c, expdest); break; case CTLVAR: p = evalvar(p, argbackq, flag, dst); break; case CTLBACKQ: case CTLBACKQ|CTLQUOTE: expbackq((*argbackq)->n, c & CTLQUOTE, flag, dst); *argbackq = (*argbackq)->next; break; case CTLARI: p = expari(p, argbackq, flag, dst); break; case ':': case '=': /* * sort of a hack - expand tildes in variable * assignments (after the first '=' and after ':'s). */ if (split_lit && !lit_quoted && strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { NEXTWORD(c, flag, expdest, dst); break; } USTPUTC(c, expdest); if (flag & EXP_VARTILDE && *p == '~' && (c != '=' || firsteq)) { if (c == '=') firsteq = 0; p = exptilde(p, flag); } break; default: if (split_lit && !lit_quoted && strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { NEXTWORD(c, flag, expdest, dst); break; } USTPUTC(c, expdest); } } } /* * Perform tilde expansion, placing the result in the stack string and * returning the next position in the input string to process. */ static const char * exptilde(const char *p, int flag) { char c; const char *startp = p; const char *user; struct passwd *pw; char *home; int len; for (;;) { c = *p; switch(c) { case CTLESC: /* This means CTL* are always considered quoted. */ case CTLVAR: case CTLBACKQ: case CTLBACKQ | CTLQUOTE: case CTLARI: case CTLENDARI: case CTLQUOTEMARK: return (startp); case ':': if ((flag & EXP_VARTILDE) == 0) break; /* FALLTHROUGH */ case '\0': case '/': case CTLENDVAR: len = p - startp - 1; STPUTBIN(startp + 1, len, expdest); STACKSTRNUL(expdest); user = expdest - len; if (*user == '\0') { home = lookupvar("HOME"); } else { pw = getpwnam(user); home = pw != NULL ? pw->pw_dir : NULL; } STADJUST(-len, expdest); if (home == NULL || *home == '\0') return (startp); strtodest(home, flag, VSNORMAL, 1, NULL); return (p); } p++; } } /* * Expand arithmetic expression. */ static const char * expari(const char *p, struct nodelist **restrict argbackq, int flag, struct worddest *dst) { char *q, *start; arith_t result; int begoff; int quoted; int adj; quoted = *p++ == '"'; begoff = expdest - stackblock(); p = argstr(p, argbackq, 0, NULL); STPUTC('\0', expdest); start = stackblock() + begoff; q = grabstackstr(expdest); result = arith(start); ungrabstackstr(q, expdest); start = stackblock() + begoff; adj = start - expdest; STADJUST(adj, expdest); CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); adj = strlen(expdest); STADJUST(adj, expdest); if (!quoted) reprocess(expdest - adj - stackblock(), flag, VSNORMAL, 0, dst); return p; } /* * Perform command substitution. */ static void expbackq(union node *cmd, int quoted, int flag, struct worddest *dst) { struct backcmd in; int i; char buf[128]; char *p; char *dest = expdest; char lastc; char const *syntax = quoted? DQSYNTAX : BASESYNTAX; int quotes = flag & (EXP_GLOB | EXP_CASE); size_t nnl; const char *ifs; int startloc; INTOFF; p = grabstackstr(dest); evalbackcmd(cmd, &in); ungrabstackstr(p, dest); p = in.buf; startloc = dest - stackblock(); nnl = 0; if (!quoted && flag & EXP_SPLIT) ifs = ifsset() ? ifsval() : " \t\n"; else ifs = ""; - /* Don't copy trailing newlines */ + /* Remove trailing newlines */ for (;;) { if (--in.nleft < 0) { if (in.fd < 0) break; while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) ; TRACE(("expbackq: read returns %d\n", i)); if (i <= 0) break; p = buf; in.nleft = i - 1; } lastc = *p++; if (lastc == '\0') continue; if (nnl > 0 && lastc != '\n') { NEXTWORD('\n', flag, dest, dst); nnl = 0; } if (strchr(ifs, lastc) != NULL) { if (lastc == '\n') nnl++; else NEXTWORD(lastc, flag, dest, dst); } else { CHECKSTRSPACE(2, dest); if (quotes && syntax[(int)lastc] == CCTL) USTPUTC(CTLESC, dest); USTPUTC(lastc, dest); } } while (dest > stackblock() + startloc && STTOPC(dest) == '\n') STUNPUTC(dest); if (in.fd >= 0) close(in.fd); if (in.buf) ckfree(in.buf); if (in.jp) { p = grabstackstr(dest); exitstatus = waitforjob(in.jp, (int *)NULL); ungrabstackstr(p, dest); } TRACE(("expbackq: done\n")); expdest = dest; INTON; } static void recordleft(const char *str, const char *loc, char *startp) { int amount; amount = ((str - 1) - (loc - startp)) - expdest; STADJUST(amount, expdest); while (loc != str - 1) *startp++ = *loc++; } static const char * subevalvar_trim(const char *p, struct nodelist **restrict argbackq, int strloc, int subtype, int startloc) { char *startp; char *loc = NULL; char *str; int c = 0; int amount; p = argstr(p, argbackq, EXP_CASE | EXP_TILDE, NULL); STACKSTRNUL(expdest); startp = stackblock() + startloc; str = stackblock() + strloc; switch (subtype) { case VSTRIMLEFT: for (loc = startp; loc < str; loc++) { c = *loc; *loc = '\0'; if (patmatch(str, startp)) { *loc = c; recordleft(str, loc, startp); return p; } *loc = c; } break; case VSTRIMLEFTMAX: for (loc = str - 1; loc >= startp;) { c = *loc; *loc = '\0'; if (patmatch(str, startp)) { *loc = c; recordleft(str, loc, startp); return p; } *loc = c; loc--; } break; case VSTRIMRIGHT: for (loc = str - 1; loc >= startp;) { if (patmatch(str, loc)) { amount = loc - expdest; STADJUST(amount, expdest); return p; } loc--; } break; case VSTRIMRIGHTMAX: for (loc = startp; loc < str - 1; loc++) { if (patmatch(str, loc)) { amount = loc - expdest; STADJUST(amount, expdest); return p; } } break; default: abort(); } amount = (expdest - stackblock() - strloc) + 1; STADJUST(-amount, expdest); return p; } static const char * subevalvar_misc(const char *p, struct nodelist **restrict argbackq, const char *var, int subtype, int startloc, int varflags) { char *startp; int amount; p = argstr(p, argbackq, EXP_TILDE, NULL); STACKSTRNUL(expdest); startp = stackblock() + startloc; switch (subtype) { case VSASSIGN: setvar(var, startp, 0); amount = startp - expdest; STADJUST(amount, expdest); return p; case VSQUESTION: if (*p != CTLENDVAR) { outfmt(out2, "%s\n", startp); error((char *)NULL); } error("%.*s: parameter %snot set", (int)(p - var - 1), var, (varflags & VSNUL) ? "null or " : ""); default: abort(); } } /* * Expand a variable, and return a pointer to the next character in the * input string. */ static const char * evalvar(const char *p, struct nodelist **restrict argbackq, int flag, struct worddest *dst) { int subtype; int varflags; const char *var; const char *val; int patloc; int c; int set; int special; int startloc; int varlen; int varlenb; char buf[21]; varflags = (unsigned char)*p++; subtype = varflags & VSTYPE; var = p; special = 0; if (! is_name(*p)) special = 1; p = strchr(p, '=') + 1; if (varflags & VSLINENO) { set = 1; special = 1; val = NULL; } else if (special) { set = varisset(var, varflags & VSNUL); val = NULL; } else { val = bltinlookup(var, 1); if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { val = NULL; set = 0; } else set = 1; } varlen = 0; startloc = expdest - stackblock(); if (!set && uflag && *var != '@' && *var != '*') { switch (subtype) { case VSNORMAL: case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: case VSLENGTH: error("%.*s: parameter not set", (int)(p - var - 1), var); } } if (set && subtype != VSPLUS) { /* insert the value of the variable */ if (special) { if (varflags & VSLINENO) { if (p - var > (ptrdiff_t)sizeof(buf)) abort(); memcpy(buf, var, p - var - 1); buf[p - var - 1] = '\0'; strtodest(buf, flag, subtype, varflags & VSQUOTE, dst); } else varvalue(var, varflags & VSQUOTE, subtype, flag, dst); if (subtype == VSLENGTH) { varlenb = expdest - stackblock() - startloc; varlen = varlenb; if (localeisutf8) { val = stackblock() + startloc; for (;val != expdest; val++) if ((*val & 0xC0) == 0x80) varlen--; } STADJUST(-varlenb, expdest); } } else { if (subtype == VSLENGTH) { for (;*val; val++) if (!localeisutf8 || (*val & 0xC0) != 0x80) varlen++; } else strtodest(val, flag, subtype, varflags & VSQUOTE, dst); } } if (subtype == VSPLUS) set = ! set; switch (subtype) { case VSLENGTH: cvtnum(varlen, buf); strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst); break; case VSNORMAL: return p; case VSPLUS: case VSMINUS: if (!set) { return argstr(p, argbackq, flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) | (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst); } break; case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: if (!set) break; /* * Terminate the string and start recording the pattern * right after it */ STPUTC('\0', expdest); patloc = expdest - stackblock(); p = subevalvar_trim(p, argbackq, patloc, subtype, startloc); reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst); if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE) dst->state = WORD_QUOTEMARK; return p; case VSASSIGN: case VSQUESTION: if (!set) { p = subevalvar_misc(p, argbackq, var, subtype, startloc, varflags); /* assert(subtype == VSASSIGN); */ val = lookupvar(var); strtodest(val, flag, subtype, varflags & VSQUOTE, dst); return p; } break; case VSERROR: c = p - var - 1; error("${%.*s%s}: Bad substitution", c, var, (c > 0 && *p != CTLENDVAR) ? "..." : ""); default: abort(); } { /* skip to end of alternative */ int nesting = 1; for (;;) { if ((c = *p++) == CTLESC) p++; else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) *argbackq = (*argbackq)->next; else if (c == CTLVAR) { if ((*p++ & VSTYPE) != VSNORMAL) nesting++; } else if (c == CTLENDVAR) { if (--nesting == 0) break; } } } return p; } /* - * Test whether a specialized variable is set. + * Test whether a special or positional parameter is set. */ static int varisset(const char *name, int nulok) { if (*name == '!') return backgndpidset(); else if (*name == '@' || *name == '*') { if (*shellparam.p == NULL) return 0; if (nulok) { char **av; for (av = shellparam.p; *av; av++) if (**av != '\0') return 1; return 0; } } else if (is_digit(*name)) { char *ap; long num; errno = 0; num = strtol(name, NULL, 10); if (errno != 0 || num > shellparam.nparam) return 0; if (num == 0) ap = arg0; else ap = shellparam.p[num - 1]; if (nulok && (ap == NULL || *ap == '\0')) return 0; } return 1; } static void strtodest(const char *p, int flag, int subtype, int quoted, struct worddest *dst) { if (subtype == VSLENGTH || subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX) STPUTS(p, expdest); else if (flag & EXP_SPLIT && !quoted && dst != NULL) STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst); else if (flag & (EXP_GLOB | EXP_CASE)) STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); else STPUTS(p, expdest); } static void reprocess(int startloc, int flag, int subtype, int quoted, struct worddest *dst) { static char *buf = NULL; static size_t buflen = 0; char *startp; size_t len, zpos, zlen; startp = stackblock() + startloc; len = expdest - startp; if (len >= SIZE_MAX / 2) abort(); INTOFF; if (len >= buflen) { ckfree(buf); buf = NULL; } if (buflen < 128) buflen = 128; while (len >= buflen) buflen <<= 1; if (buf == NULL) buf = ckmalloc(buflen); INTON; memcpy(buf, startp, len); buf[len] = '\0'; STADJUST(-len, expdest); for (zpos = 0;;) { zlen = strlen(buf + zpos); strtodest(buf + zpos, flag, subtype, quoted, dst); zpos += zlen + 1; if (zpos == len + 1) break; if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len))) NEXTWORD('\0', flag, expdest, dst); } } /* - * Add the value of a specialized variable to the stack string. + * Add the value of a special or positional parameter to the stack string. */ static void varvalue(const char *name, int quoted, int subtype, int flag, struct worddest *dst) { int num; char *p; int i; int splitlater; char sep[2]; char **ap; char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1]; if (subtype == VSLENGTH) flag &= ~EXP_FULL; splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX; switch (*name) { case '$': num = rootpid; break; case '?': num = oexitstatus; break; case '#': num = shellparam.nparam; break; case '!': num = backgndpidval(); break; case '-': p = buf; for (i = 0 ; i < NSHORTOPTS ; i++) { if (optval[i]) *p++ = optletter[i]; } *p = '\0'; strtodest(buf, flag, subtype, quoted, dst); return; case '@': if (flag & EXP_SPLIT && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted, dst); if (*ap) { if (splitlater) STPUTC('\0', expdest); else NEXTWORD('\0', flag, expdest, dst); } } if (shellparam.nparam > 0) dst->state = WORD_QUOTEMARK; return; } /* FALLTHROUGH */ case '*': if (ifsset()) sep[0] = ifsval()[0]; else sep[0] = ' '; sep[1] = '\0'; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted, dst); if (!*ap) break; if (sep[0]) strtodest(sep, flag, subtype, quoted, dst); else if (flag & EXP_SPLIT && !quoted && **ap != '\0') { if (splitlater) STPUTC('\0', expdest); else NEXTWORD('\0', flag, expdest, dst); } } return; default: if (is_digit(*name)) { num = atoi(name); if (num == 0) p = arg0; else if (num > 0 && num <= shellparam.nparam) p = shellparam.p[num - 1]; else return; strtodest(p, flag, subtype, quoted, dst); } return; } cvtnum(num, buf); strtodest(buf, flag, subtype, quoted, dst); } static char expdir[PATH_MAX]; #define expdir_end (expdir + sizeof(expdir)) /* * Perform pathname generation and remove control characters. * At this point, the only control characters should be CTLESC. * The results are stored in the list dstlist. */ static void expandmeta(char *pattern, struct arglist *dstlist) { char *p; int firstmatch; char c; firstmatch = dstlist->count; p = pattern; for (; (c = *p) != '\0'; p++) { /* fast check for meta chars */ if (c == '*' || c == '?' || c == '[') { INTOFF; expmeta(expdir, pattern, dstlist); INTON; break; } } if (dstlist->count == firstmatch) { /* * no matches */ rmescapes(pattern); appendarglist(dstlist, pattern); } else { qsort(&dstlist->args[firstmatch], dstlist->count - firstmatch, sizeof(dstlist->args[0]), expsortcmp); } } /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ static void expmeta(char *enddir, char *name, struct arglist *arglist) { const char *p; const char *q; const char *start; char *endname; int metaflag; struct stat statb; DIR *dirp; struct dirent *dp; int atend; int matchdot; int esc; int namlen; metaflag = 0; start = name; for (p = name; esc = 0, *p; p += esc + 1) { if (*p == '*' || *p == '?') metaflag = 1; else if (*p == '[') { q = p + 1; if (*q == '!' || *q == '^') q++; for (;;) { if (*q == CTLESC) q++; if (*q == '/' || *q == '\0') break; if (*++q == ']') { metaflag = 1; break; } } } else if (*p == '\0') break; else { if (*p == CTLESC) esc++; if (p[esc] == '/') { if (metaflag) break; start = p + esc + 1; } } } if (metaflag == 0) { /* we've reached the end of the file name */ if (enddir != expdir) metaflag++; for (p = name ; ; p++) { if (*p == CTLESC) p++; *enddir++ = *p; if (*p == '\0') break; if (enddir == expdir_end) return; } if (metaflag == 0 || lstat(expdir, &statb) >= 0) appendarglist(arglist, stsavestr(expdir)); return; } endname = name + (p - name); if (start != name) { p = name; while (p < start) { if (*p == CTLESC) p++; *enddir++ = *p++; if (enddir == expdir_end) return; } } if (enddir == expdir) { p = "."; } else if (enddir == expdir + 1 && *expdir == '/') { p = "/"; } else { p = expdir; enddir[-1] = '\0'; } if ((dirp = opendir(p)) == NULL) return; if (enddir != expdir) enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { atend = 0; *endname = '\0'; endname += esc + 1; } matchdot = 0; p = start; if (*p == CTLESC) p++; if (*p == '.') matchdot++; while (! int_pending() && (dp = readdir(dirp)) != NULL) { if (dp->d_name[0] == '.' && ! matchdot) continue; if (patmatch(start, dp->d_name)) { namlen = dp->d_namlen; if (enddir + namlen + 1 > expdir_end) continue; memcpy(enddir, dp->d_name, namlen + 1); if (atend) appendarglist(arglist, stsavestr(expdir)); else { if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_DIR && dp->d_type != DT_LNK) continue; if (enddir + namlen + 2 > expdir_end) continue; enddir[namlen] = '/'; enddir[namlen + 1] = '\0'; expmeta(enddir + namlen + 1, endname, arglist); } } } closedir(dirp); if (! atend) endname[-esc - 1] = esc ? CTLESC : '/'; } static int expsortcmp(const void *p1, const void *p2) { const char *s1 = *(const char * const *)p1; const char *s2 = *(const char * const *)p2; return (strcoll(s1, s2)); } static wchar_t get_wc(const char **p) { wchar_t c; int chrlen; chrlen = mbtowc(&c, *p, 4); if (chrlen == 0) return 0; else if (chrlen == -1) c = 0; else *p += chrlen; return c; } /* * See if a character matches a character class, starting at the first colon * of "[:class:]". * If a valid character class is recognized, a pointer to the next character * after the final closing bracket is stored into *end, otherwise a null * pointer is stored into *end. */ static int match_charclass(const char *p, wchar_t chr, const char **end) { char name[20]; const char *nameend; wctype_t cclass; *end = NULL; p++; nameend = strstr(p, ":]"); if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || nameend == p) return 0; memcpy(name, p, nameend - p); name[nameend - p] = '\0'; *end = nameend + 2; cclass = wctype(name); /* An unknown class matches nothing but is valid nevertheless. */ if (cclass == 0) return 0; return iswctype(chr, cclass); } /* * Returns true if the pattern matches the string. */ static int patmatch(const char *pattern, const char *string) { const char *p, *q, *end; const char *bt_p, *bt_q; char c; wchar_t wc, wc2; p = pattern; q = string; bt_p = NULL; bt_q = NULL; for (;;) { switch (c = *p++) { case '\0': if (*q != '\0') goto backtrack; return 1; case CTLESC: if (*q++ != *p++) goto backtrack; break; case '?': if (*q == '\0') return 0; if (localeisutf8) { wc = get_wc(&q); /* * A '?' does not match invalid UTF-8 but a * '*' does, so backtrack. */ if (wc == 0) goto backtrack; } else q++; break; case '*': c = *p; while (c == '*') c = *++p; /* * If the pattern ends here, we know the string * matches without needing to look at the rest of it. */ if (c == '\0') return 1; /* * First try the shortest match for the '*' that * could work. We can forget any earlier '*' since * there is no way having it match more characters * can help us, given that we are already here. */ bt_p = p; bt_q = q; break; case '[': { const char *savep, *saveq; int invert, found; wchar_t chr; savep = p, saveq = q; invert = 0; if (*p == '!' || *p == '^') { invert++; p++; } found = 0; if (*q == '\0') return 0; if (localeisutf8) { chr = get_wc(&q); if (chr == 0) goto backtrack; } else chr = (unsigned char)*q++; c = *p++; do { if (c == '\0') { p = savep, q = saveq; c = '['; goto dft; } if (c == '[' && *p == ':') { found |= match_charclass(p, chr, &end); if (end != NULL) p = end; } if (c == CTLESC) c = *p++; if (localeisutf8 && c & 0x80) { p--; wc = get_wc(&p); if (wc == 0) /* bad utf-8 */ return 0; } else wc = (unsigned char)c; if (*p == '-' && p[1] != ']') { p++; if (*p == CTLESC) p++; if (localeisutf8) { wc2 = get_wc(&p); if (wc2 == 0) /* bad utf-8 */ return 0; } else wc2 = (unsigned char)*p++; if ( collate_range_cmp(chr, wc) >= 0 && collate_range_cmp(chr, wc2) <= 0 ) found = 1; } else { if (chr == wc) found = 1; } } while ((c = *p++) != ']'); if (found == invert) goto backtrack; break; } dft: default: if (*q == '\0') return 0; if (*q++ == c) break; backtrack: /* * If we have a mismatch (other than hitting the end * of the string), go back to the last '*' seen and * have it match one additional character. */ if (bt_p == NULL) return 0; if (*bt_q == '\0') return 0; bt_q++; p = bt_p; q = bt_q; break; } } } /* * Remove any CTLESC and CTLQUOTEMARK characters from a string. */ void rmescapes(char *str) { char *p, *q; p = str; while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { if (*p++ == '\0') return; } q = p; while (*p) { if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { p++; continue; } if (*p == CTLESC) p++; *q++ = *p++; } *q = '\0'; } /* * See if a pattern matches in a case statement. */ int casematch(union node *pattern, const char *val) { struct stackmark smark; struct nodelist *argbackq; int result; char *p; setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); argstr(pattern->narg.text, &argbackq, EXP_TILDE | EXP_CASE, NULL); STPUTC('\0', expdest); p = grabstackstr(expdest); result = patmatch(p, val); popstackmark(&smark); return result; } /* * Our own itoa(). */ static void cvtnum(int num, char *buf) { char temp[32]; int neg = num < 0; char *p = temp + 31; temp[31] = '\0'; do { *--p = num % 10 + '0'; } while ((num /= 10) != 0); if (neg) *--p = '-'; memcpy(buf, p, temp + 32 - p); } /* * Do most of the work for wordexp(3). */ int wordexpcmd(int argc, char **argv) { size_t len; int i; out1fmt("%08x", argc - 1); for (i = 1, len = 0; i < argc; i++) len += strlen(argv[i]); out1fmt("%08x", (int)len); for (i = 1; i < argc; i++) outbin(argv[i], strlen(argv[i]) + 1, out1); return (0); } /* * Do most of the work for wordexp(3), new version. */ int freebsd_wordexpcmd(int argc __unused, char **argv __unused) { struct arglist arglist; union node *args, *n; size_t len; int ch; int protected = 0; int fd = -1; int i; while ((ch = nextopt("f:p")) != '\0') { switch (ch) { case 'f': fd = number(shoptarg); break; case 'p': protected = 1; break; } } if (*argptr != NULL) error("wrong number of arguments"); if (fd < 0) error("missing fd"); INTOFF; setinputfd(fd, 1); INTON; args = parsewordexp(); popfile(); /* will also close fd */ if (protected) for (n = args; n != NULL; n = n->narg.next) { if (n->narg.backquote != NULL) { outcslow('C', out1); error("command substitution disabled"); } } outcslow(' ', out1); emptyarglist(&arglist); for (n = args; n != NULL; n = n->narg.next) expandarg(n, &arglist, EXP_FULL | EXP_TILDE); for (i = 0, len = 0; i < arglist.count; i++) len += strlen(arglist.args[i]); out1fmt("%016x %016zx", arglist.count, len); for (i = 0; i < arglist.count; i++) outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1); return (0); } Index: head/bin/sh/options.c =================================================================== --- head/bin/sh/options.c (revision 317881) +++ head/bin/sh/options.c (revision 317882) @@ -1,595 +1,597 @@ /*- * 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[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "shell.h" #define DEFINE_OPTIONS #include "options.h" #undef DEFINE_OPTIONS #include "nodes.h" /* for other header files */ #include "eval.h" #include "jobs.h" #include "input.h" #include "output.h" #include "trap.h" #include "var.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "builtins.h" #ifndef NO_HISTORY #include "myhistedit.h" #endif char *arg0; /* value of $0 */ struct shparam shellparam; /* current positional parameters */ char **argptr; /* argument list for builtin commands */ char *shoptarg; /* set by nextopt (like getopt) */ char *nextopt_optptr; /* used by nextopt */ char *minusc; /* argument to -c option */ static void options(int); static void minus_o(char *, int); static void setoption(int, int); static void setoptionbyindex(int, int); static void setparam(int, char **); static int getopts(char *, char *, char **, char ***, char **); /* * Process the shell command line arguments. */ void procargs(int argc, char **argv) { int i; char *scriptname; argptr = argv; if (argc > 0) argptr++; for (i = 0; i < NOPTS; i++) optval[i] = 2; privileged = (getuid() != geteuid() || getgid() != getegid()); options(1); if (*argptr == NULL && minusc == NULL) sflag = 1; if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) { iflag = 1; if (Eflag == 2) Eflag = 1; } if (mflag == 2) mflag = iflag; for (i = 0; i < NOPTS; i++) if (optval[i] == 2) optval[i] = 0; arg0 = argv[0]; if (sflag == 0 && minusc == NULL) { scriptname = *argptr++; setinputfile(scriptname, 0); commandname = arg0 = scriptname; } /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ if (argptr && minusc && *argptr) arg0 = *argptr++; shellparam.p = argptr; shellparam.reset = 1; /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ while (*argptr) { shellparam.nparam++; argptr++; } optschanged(); } void optschanged(void) { setinteractive(); #ifndef NO_HISTORY histedit(); #endif setjobctl(mflag); } /* * Process shell options. The global variable argptr contains a pointer * to the argument list; we advance it past the options. + * If cmdline is true, process the shell's argv; otherwise, process arguments + * to the set special builtin. */ static void options(int cmdline) { char *kp, *p; int val; int c; if (cmdline) minusc = NULL; while ((p = *argptr) != NULL) { argptr++; if ((c = *p++) == '-') { val = 1; /* A "-" or "--" terminates options */ if (p[0] == '\0') goto end_options1; if (p[0] == '-' && p[1] == '\0') goto end_options2; /** * For the benefit of `#!' lines in shell scripts, * treat a string of '-- *#.*' the same as '--'. * This is needed so that a script starting with: * #!/bin/sh -- # -*- perl -*- * will continue to work after a change is made to * kern/imgact_shell.c to NOT token-ize the options * specified on a '#!' line. A bit of a kludge, * but that trick is recommended in documentation * for some scripting languages, and we might as * well continue to support it. */ if (p[0] == '-') { kp = p + 1; while (*kp == ' ' || *kp == '\t') kp++; if (*kp == '#' || *kp == '\0') goto end_options2; } } else if (c == '+') { val = 0; } else { argptr--; break; } while ((c = *p++) != '\0') { if (c == 'c' && cmdline) { char *q; #ifdef NOHACK /* removing this code allows sh -ce 'foo' for compat */ if (*p == '\0') #endif q = *argptr++; if (q == NULL || minusc != NULL) error("Bad -c option"); minusc = q; #ifdef NOHACK break; #endif } else if (c == 'o') { minus_o(*argptr, val); if (*argptr) argptr++; } else setoption(c, val); } } return; /* When processing `set', a single "-" means turn off -x and -v */ end_options1: if (!cmdline) { xflag = vflag = 0; return; } /* * When processing `set', a "--" means the remaining arguments * replace the positional parameters in the active shell. If * there are no remaining options, then all the positional * parameters are cleared (equivalent to doing ``shift $#''). */ end_options2: if (!cmdline) { if (*argptr == NULL) setparam(0, argptr); return; } /* * At this point we are processing options given to 'sh' on a command * line. If an end-of-options marker ("-" or "--") is followed by an * arg of "#", then skip over all remaining arguments. Some scripting * languages (e.g.: perl) document that /bin/sh will implement this * behavior, and they recommend that users take advantage of it to * solve certain issues that can come up when writing a perl script. * Yes, this feature is in /bin/sh to help users write perl scripts. */ p = *argptr; if (p != NULL && p[0] == '#' && p[1] == '\0') { while (*argptr != NULL) argptr++; /* We need to keep the final argument */ argptr--; } } static void minus_o(char *name, int val) { int i; const unsigned char *on; size_t len; if (name == NULL) { if (val) { /* "Pretty" output. */ out1str("Current option settings\n"); for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) out1fmt("%-16.*s%s\n", *on, on + 1, optval[i] ? "on" : "off"); } else { /* Output suitable for re-input to shell. */ for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) out1fmt("%s %co %.*s%s", i % 6 == 0 ? "set" : "", optval[i] ? '-' : '+', *on, on + 1, i % 6 == 5 || i == NOPTS - 1 ? "\n" : ""); } } else { len = strlen(name); for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) if (*on == len && memcmp(on + 1, name, len) == 0) { setoptionbyindex(i, val); return; } error("Illegal option -o %s", name); } } static void setoptionbyindex(int idx, int val) { if (&optval[idx] == &privileged && !val && privileged) { if (setgid(getgid()) == -1) error("setgid"); if (setuid(getuid()) == -1) error("setuid"); } optval[idx] = val; if (val) { /* #%$ hack for ksh semantics */ if (&optval[idx] == &Vflag) Eflag = 0; else if (&optval[idx] == &Eflag) Vflag = 0; } } static void setoption(int flag, int val) { int i; for (i = 0; i < NSHORTOPTS; i++) if (optletter[i] == flag) { setoptionbyindex(i, val); return; } error("Illegal option -%c", flag); } /* * Set the shell parameters. */ static void setparam(int argc, char **argv) { char **newparam; char **ap; ap = newparam = ckmalloc((argc + 1) * sizeof *ap); while (*argv) { *ap++ = savestr(*argv++); } *ap = NULL; freeparam(&shellparam); shellparam.malloc = 1; shellparam.nparam = argc; shellparam.p = newparam; shellparam.optp = NULL; shellparam.reset = 1; shellparam.optnext = NULL; } /* * Free the list of positional parameters. */ void freeparam(struct shparam *param) { char **ap; if (param->malloc) { for (ap = param->p ; *ap ; ap++) ckfree(*ap); ckfree(param->p); } if (param->optp) { for (ap = param->optp ; *ap ; ap++) ckfree(*ap); ckfree(param->optp); } } /* * The shift builtin command. */ int shiftcmd(int argc, char **argv) { int i, n; n = 1; if (argc > 1) n = number(argv[1]); if (n > shellparam.nparam) return 1; INTOFF; shellparam.nparam -= n; if (shellparam.malloc) for (i = 0; i < n; i++) ckfree(shellparam.p[i]); memmove(shellparam.p, shellparam.p + n, (shellparam.nparam + 1) * sizeof(shellparam.p[0])); shellparam.reset = 1; INTON; return 0; } /* - * The set command builtin. + * The set builtin command. */ int setcmd(int argc, char **argv) { if (argc == 1) return showvarscmd(argc, argv); INTOFF; options(0); optschanged(); if (*argptr != NULL) { setparam(argc - (argptr - argv), argptr); } INTON; return 0; } void getoptsreset(const char *value) { while (*value == '0') value++; if (strcmp(value, "1") == 0) shellparam.reset = 1; } /* * The getopts builtin. Shellparam.optnext points to the next argument * to be processed. Shellparam.optptr points to the next character to * be processed in the current argument. If shellparam.optnext is NULL, * then it's the first time getopts has been called. */ int getoptscmd(int argc, char **argv) { char **optbase = NULL, **ap; int i; if (argc < 3) error("usage: getopts optstring var [arg]"); if (shellparam.reset == 1) { INTOFF; if (shellparam.optp) { for (ap = shellparam.optp ; *ap ; ap++) ckfree(*ap); ckfree(shellparam.optp); shellparam.optp = NULL; } if (argc > 3) { shellparam.optp = ckmalloc((argc - 2) * sizeof *ap); memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap); for (i = 0; i < argc - 3; i++) shellparam.optp[i] = savestr(argv[i + 3]); } INTON; optbase = argc == 3 ? shellparam.p : shellparam.optp; shellparam.optnext = optbase; shellparam.optptr = NULL; shellparam.reset = 0; } else optbase = shellparam.optp ? shellparam.optp : shellparam.p; return getopts(argv[1], argv[2], optbase, &shellparam.optnext, &shellparam.optptr); } static int getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, char **optptr) { char *p, *q; char c = '?'; int done = 0; int ind = 0; int err = 0; char s[10]; const char *newoptarg = NULL; if ((p = *optptr) == NULL || *p == '\0') { /* Current word is done, advance */ if (*optnext == NULL) return 1; p = **optnext; if (p == NULL || *p != '-' || *++p == '\0') { atend: ind = *optnext - optfirst + 1; *optnext = NULL; p = NULL; done = 1; goto out; } (*optnext)++; if (p[0] == '-' && p[1] == '\0') /* check for "--" */ goto atend; } c = *p++; for (q = optstr; *q != c; ) { if (*q == '\0') { if (optstr[0] == ':') { s[0] = c; s[1] = '\0'; newoptarg = s; } else out2fmt_flush("Illegal option -%c\n", c); c = '?'; goto out; } if (*++q == ':') q++; } if (*++q == ':') { if (*p == '\0' && (p = **optnext) == NULL) { if (optstr[0] == ':') { s[0] = c; s[1] = '\0'; newoptarg = s; c = ':'; } else { out2fmt_flush("No arg for -%c option\n", c); c = '?'; } goto out; } if (p == **optnext) (*optnext)++; newoptarg = p; p = NULL; } out: if (*optnext != NULL) ind = *optnext - optfirst + 1; *optptr = p; if (newoptarg != NULL) err |= setvarsafe("OPTARG", newoptarg, 0); else { INTOFF; err |= unsetvar("OPTARG"); INTON; } fmtstr(s, sizeof(s), "%d", ind); err |= setvarsafe("OPTIND", s, VNOFUNC); s[0] = c; s[1] = '\0'; err |= setvarsafe(optvar, s, 0); if (err) { *optnext = NULL; *optptr = NULL; flushall(); exraise(EXERROR); } return done; } /* * Standard option processing (a la getopt) for builtin routines. The * only argument that is passed to nextopt is the option string; the - * other arguments are unnecessary. It return the character, or '\0' on + * other arguments are unnecessary. It returns the option, or '\0' on * end of input. */ int nextopt(const char *optstring) { char *p; const char *q; char c; if ((p = nextopt_optptr) == NULL || *p == '\0') { p = *argptr; if (p == NULL || *p != '-' || *++p == '\0') return '\0'; argptr++; if (p[0] == '-' && p[1] == '\0') /* check for "--" */ return '\0'; } c = *p++; for (q = optstring ; *q != c ; ) { if (*q == '\0') error("Illegal option -%c", c); if (*++q == ':') q++; } if (*++q == ':') { if (*p == '\0' && (p = *argptr++) == NULL) error("No arg for -%c option", c); shoptarg = p; p = NULL; } nextopt_optptr = p; return c; }