diff options
author | Kiyoshi Aman <kiyoshi.aman+adelie@gmail.com> | 2019-02-01 22:55:37 +0000 |
---|---|---|
committer | Kiyoshi Aman <kiyoshi.aman+adelie@gmail.com> | 2019-02-03 18:22:05 -0600 |
commit | 5b57d28ffb6e1ef86b50f7d05d977826eae89bfe (patch) | |
tree | 154a22fe556b49e6927197336f8bf91b12eacd5e /bin/sh | |
download | userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2 userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip |
initial population
Diffstat (limited to 'bin/sh')
83 files changed, 32752 insertions, 0 deletions
diff --git a/bin/sh/TOUR b/bin/sh/TOUR new file mode 100644 index 0000000..30cee04 --- /dev/null +++ b/bin/sh/TOUR @@ -0,0 +1,357 @@ +# $NetBSD: TOUR,v 1.11 2016/10/25 13:01:59 abhinav Exp $ +# @(#)TOUR 8.1 (Berkeley) 5/31/93 + +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 + mkinit *.c init.c + mknodes nodetypes nodes.h nodes.c + mksignames - signames.h signames.c + mksyntax - syntax.h syntax.c + mktokens - token.h + bltin/mkexpr unary_op binary_op operators.h operators.c + +There are undoubtedly too many of these. Mkinit searches all the +C source files for entries looking like: + + INIT { + x = 1; /* executed during initialization */ + } + + RESET { + x = 2; /* executed when the shell does a longjmp + back to the main command loop */ + } + + SHELLPROC { + x = 3; /* executed when the shell runs a shell procedure */ + } + +It pulls this code out into routines which are when particular +events occur. The intent is to improve modularity by isolating +the information about which modules need to be explicitly +initialized/reset within the modules themselves. + +Mkinit recognizes several constructs for placing declarations in +the init.c file. + INCLUDE "file.h" +includes a file. The storage class MKINIT makes a declaration +available in the init.c file, for example: + MKINIT int funcnest; /* depth of function calls */ +MKINIT alone on a line introduces a structure or union declara- +tion: + MKINIT + struct redirtab { + short renamed[10]; + }; +Preprocessor #define statements are copied to init.c without any +special action to request this. + +INDENTATION: The ash source is indented in multiples of six +spaces. The only study that I have heard of on the subject con- +cluded that the optimal amount to indent is in the range of four +to six spaces. I use six spaces since it is not too big a jump +from the widely used eight spaces. If you really hate six space +indentation, use the adjind (source included) program to change +it to something else. + +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. EXSHELLPROC is an excep- +tion which is raised when a shell procedure is invoked. The pur- +pose of EXSHELLPROC is to perform the cleanup actions associated +with other exceptions. After these cleanup actions, the shell +can interpret a shell procedure itself without exec'ing a new +copy of the shell. + +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 -j 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 +three tables: one for normal use, one for use when inside single +quotes, and one for use when inside double quotes. The tables +are machine dependent because they are indexed by character vari- +ables 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 + CTLBACKQ Command substitution + CTLBACKQ|CTLQUOTE Command substitution inside double quotes + 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} + +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. + +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. + +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 +splitting and file name generation have the CTLESC characters re- +moved 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. If the +"/u" directory is simulated, then when "/u/username" is replaced +by the user's home directory, the flag "didudir" is set. This +tells the cd command that it should print out the directory name, +just as it would if the "/u" directory were implemented using +symbolic links. + +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. +There are two consequences of this. First, if an assignment to +PATH precedes the command, the value of PATH before the assign- +ment must be remembered and passed to shellexec. Second, if the +program turns out to be a shell procedure, the strings from the +environment variables which preceded the command must be pulled +out of the table and replaced with strings obtained from malloc, +since the former will automatically be freed when the stack (see +the entry on memalloc.c) is emptied. + +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 mkbuil- +tins 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. + +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. + +CD.C: This file defines the cd and pwd builtins. The pwd com- +mand runs /bin/pwd the first time it is invoked (unless the user +has already done a cd to an absolute pathname), but then +remembers the current directory and updates it when the cd com- +mand is run, so subsequent pwd commands run very fast. The main +complication in the cd command is in the docd command, which +resolves symbolic links into actual names and informs the user +where the user ended up if he crossed a symbolic link. + +SIGNALS: Trap.c implements the trap command. The routine set- +signal figures out what action should be taken when a signal is +received and invokes the signal system call to set the signal ac- +tion appropriately. When a signal that a user has set a trap for +is caught, the routine "onsig" sets a flag. The routine dotrap +is called at appropriate points to actually handle the signal. +When an interrupt is caught and no trap has been set for that +signal, the routine "onint" in error.c is called. + +OUTPUT: Ash uses its own output routines. There are three out- +put structures allocated. "Output" represents the standard out- +put, "errout" the standard error, and "memout" contains output +which is to be stored in memory. This last is used when a buil- +tin command appears in backquotes, to allow its output to be col- +lected without doing any I/O through the UNIX operating system. +The variables out1 and out2 normally point to output and errout, +respectively, but they are set to point to memout when appropri- +ate inside backquotes. + +INPUT: The basic input routine is pgetc, which reads from the +current input file. There is a stack of input files; the current +input file is the top file on this stack. The code allows the +input to come from a string rather than a file. (This is for the +-c option and the "." and eval builtin commands.) The global +variable plinno is saved and restored when files are pushed and +popped from the stack. The parser routines store the number of +the current line in this variable. + +DEBUGGING: If DEBUG is defined in shell.h, then the shell will +write debugging information to the file $HOME/trace. Most of +this is done using the TRACE macro, which takes a set of printf +arguments inside two sets of parenthesis. Example: +"TRACE(("n=%d0, n))". The double parenthesis are necessary be- +cause the preprocessor can't handle functions with a variable +number of arguments. Defining DEBUG also causes the shell to +generate a core dump if it is sent a quit signal. The tracing +code is in show.c. diff --git a/bin/sh/USD.doc/Makefile b/bin/sh/USD.doc/Makefile new file mode 100644 index 0000000..55b7203 --- /dev/null +++ b/bin/sh/USD.doc/Makefile @@ -0,0 +1,12 @@ +# $NetBSD: Makefile,v 1.4 2014/07/05 19:23:00 dholland Exp $ +# @(#)Makefile 8.1 (Berkeley) 8/14/93 + +SECTION=reference/ref1 +ARTICLE=sh +SRCS= referargs t.mac t1 t2 t3 t4 +MACROS=-ms +ROFF_REFER=yes +#REFER_ARGS=-e -p Rv7man +EXTRAHTMLFILES=sh1.png sh2.png sh3.png sh4.png sh5.png + +.include <bsd.doc.mk> diff --git a/bin/sh/USD.doc/Rv7man b/bin/sh/USD.doc/Rv7man new file mode 100644 index 0000000..628c67f --- /dev/null +++ b/bin/sh/USD.doc/Rv7man @@ -0,0 +1,405 @@ +%A L. P. Deutsch +%A B. W. Lampson +%T An online editor +%J Comm. Assoc. Comp. Mach. +%V 10 +%N 12 +%D December 1967 +%P 793-799, 803 +%K qed + +.[ +%r 17 +%K cstr +%R Comp. Sci. Tech. Rep. No. 17 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A B. W. Kernighan +%A L. L. Cherry +%T A System for Typesetting Mathematics +%d May 1974, revised April 1977 +%J Comm. Assoc. Comp. Mach. +%K acm cacm +%V 18 +%P 151-157 +%D March 1975 +.] + +%T U\s-2NIX\s0 Time-Sharing System: Document Preparation +%K unix bstj +%A B. W. Kernighan +%A M. E. Lesk +%A J. F. Ossanna +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2115-2135 +%D 1978 + +%A T. A. Dolotta +%A J. R. Mashey +%T An Introduction to the Programmer's Workbench +%J Proc. 2nd Int. Conf. on Software Engineering +%D October 13-15, 1976 +%P 164-168 + +%T U\s-2NIX\s0 Time-Sharing System: The Programmer's Workbench +%A T. A. Dolotta +%A R. C. Haight +%A J. R. Mashey +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2177-2200 +%D 1978 +%K unix bstj + +%T U\s-2NIX\s0 Time-Sharing System: U\s-2NIX\s0 on a Microprocessor +%K unix bstj +%A H. Lycklama +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2087-2101 +%D 1978 + +%T The C Programming Language +%A B. W. Kernighan +%A D. M. Ritchie +%I Prentice-Hall +%C Englewood Cliffs, New Jersey +%D 1978 + +%T Computer Recreations +%A Aleph-null +%J Software Practice and Experience +%V 1 +%N 2 +%D April-June 1971 +%P 201-204 + +%T U\s-2NIX\s0 Time-Sharing System: The U\s-2NIX\s0 Shell +%A S. R. Bourne +%K unix bstj +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 1971-1990 +%D 1978 + +%A L. P. Deutsch +%A B. W. Lampson +%T \*sSDS\*n 930 time-sharing system preliminary reference manual +%R Doc. 30.10.10, Project \*sGENIE\*n +%C Univ. Cal. at Berkeley +%D April 1965 + +%A R. J. Feiertag +%A E. I. Organick +%T The Multics input-output system +%J Proc. Third Symposium on Operating Systems Principles +%D October 18-20, 1971 +%P 35-41 + +%A D. G. Bobrow +%A J. D. Burchfiel +%A D. L. Murphy +%A R. S. Tomlinson +%T \*sTENEX\*n, a Paged Time Sharing System for the \*sPDP\*n-10 +%J Comm. Assoc. Comp. Mach. +%V 15 +%N 3 +%D March 1972 +%K tenex +%P 135-143 + +%A R. E. Griswold +%A D. R. Hanson +%T An Overview of SL5 +%J SIGPLAN Notices +%V 12 +%N 4 +%D April 1977 +%P 40-50 + +%A E. W. Dijkstra +%T Cooperating Sequential Processes +%B Programming Languages +%E F. Genuys +%I Academic Press +%C New York +%D 1968 +%P 43-112 + +%A J. A. Hawley +%A W. B. Meyer +%T M\s-2UNIX\s0, A Multiprocessing Version of U\s-2NIX\s0 +%K munix unix +%R M.S. Thesis +%I Naval Postgraduate School +%C Monterey, Cal. +%D 1975 + +%T The U\s-2NIX\s0 Time-Sharing System +%K unix bstj +%A D. M. Ritchie +%A K. Thompson +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 1905-1929 +%D 1978 + +%A E. I. Organick +%T The M\s-2ULTICS\s0 System +%K multics +%I M.I.T. Press +%C Cambridge, Mass. +%D 1972 + +%T UNIX for Beginners +%A B. W. Kernighan +%D 1978 + +%T U\s-2NIX\s0 Programmer's Man\&ual +%A K. Thompson +%A D. M. Ritchie +%K unix +%I Bell Laboratories +%O Seventh Edition. +%D 1978 + +%A K. Thompson +%T The U\s-2NIX\s0 Command Language +%B Structured Programming\(emInfotech State of the Art Report +%I Infotech International Ltd. +%C Nicholson House, Maidenhead, Berkshire, England +%D March 1975 +%P 375-384 +%K unix +%X pwb +Brief description of shell syntax and semantics, without much +detail on implementation. +Much on pipes and convenience of hooking programs together. +Includes SERMONETTE: +"Many familiar computing `concepts' are missing from UNIX. +Files have no records. There are no access methods. +There are no file types. These concepts fill a much-needed gap. +I sincerely hope that when future systems are designed by +manufacturers the value of some of these ingrained notions is re-examined. +Like the politician and his `common man', manufacturers have +their `average user'. + +%A J. R. Mashey +%T PWB/UNIX Shell Tutorial +%D September 30, 1977 + +%A D. F. Hartley (Ed.) +%T The Cambridge Multiple Access System \- Users Reference Manual +%I University Mathematical Laboratory +%C Cambridge, England +%D 1968 + +%A P. A. Crisman (Ed.) +%T The Compatible Time-Sharing System +%I M.I.T. Press +%K whole ctss book +%C Cambridge, Mass. +%D 1965 + +%T LR Parsing +%A A. V. Aho +%A S. C. Johnson +%J Comp. Surveys +%V 6 +%N 2 +%P 99-124 +%D June 1974 + +%T Deterministic Parsing of Ambiguous Grammars +%A A. V. Aho +%A S. C. Johnson +%A J. D. Ullman +%J Comm. Assoc. Comp. Mach. +%K acm cacm +%V 18 +%N 8 +%P 441-452 +%D August 1975 + +%A A. V. Aho +%A J. D. Ullman +%T Principles of Compiler Design +%I Addison-Wesley +%C Reading, Mass. +%D 1977 + +.[ +%r 65 +%R Comp. Sci. Tech. Rep. No. 65 +%K CSTR +%A S. C. Johnson +%T Lint, a C Program Checker +%D December 1977 +%O updated version TM 78-1273-3 +%D 1978 +.] + +%T A Portable Compiler: Theory and Practice +%A S. C. Johnson +%J Proc. 5th ACM Symp. on Principles of Programming Languages +%P 97-104 +%D January 1978 + +.[ +%r 39 +%K CSTR +%R Comp. Sci. Tech. Rep. No. 39 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A M. E. Lesk +%T Lex \(em A Lexical Analyzer Generator +%D October 1975 +.] + +.[ +%r 32 +%K CSTR +%R Comp. Sci. Tech. Rep. No. 32 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A S. C. Johnson +%T Yacc \(em Yet Another Compiler-Compiler +%D July 1975 +.] + +%T U\s-2NIX\s0 Time-Sharing System: Portability of C Programs and the U\s-2NIX\s0 System +%K unix bstj +%A S. C. Johnson +%A D. M. Ritchie +%J Bell Sys. Tech. J. +%V 57 +%N 6 +%P 2021-2048 +%D 1978 + +%T Typing Documents on UNIX and GCOS: The -ms Macros for Troff +%A M. E. Lesk +%D 1977 + +%A K. Thompson +%A D. M. Ritchie +%T U\s-2NIX\s0 Programmer's Manual +%K unix +%I Bell Laboratories +%O Sixth Edition +%D May 1975 + +%T The Network U\s-2NIX\s0 System +%K unix +%A G. L. Chesson +%J Operating Systems Review +%V 9 +%N 5 +%P 60-66 +%D 1975 +%O Also in \f2Proc. 5th Symp. on Operating Systems Principles.\f1 + +%T Spider \(em An Experimental Data Communications System +%Z ctr127 +%A A. G. Fraser +%J Proc. IEEE Conf. on Communications +%P 21F +%O IEEE Cat. No. 74CH0859-9-CSCB. +%D June 1974 + +%T A Virtual Channel Network +%A A. G. Fraser +%J Datamation +%P 51-56 +%D February 1975 + +.[ +%r 41 +%K CSTR +%R Comp. Sci. Tech. Rep. No. 41 +%I Bell Laboratories +%C Murray Hill, New Jersey +%A J. W. Hunt +%A M. D. McIlroy +%T An Algorithm for Differential File Comparison +%D June 1976 +.] + +%A F. P. Brooks, Jr. +%T The Mythical Man-Month +%I Addison-Wesley +%C Reading, Mass. +%D 1975 +%X pwb +Readable, classic reference on software engineering and +problems of large projects, from someone with experience in them. +Required reading for any software engineer, even if conclusions may not +always be agreed with. +%br +"The second is the most dangerous system a man ever designs." p.55. +%br +"Hence plan to throw one away; you will, anyhow." p.116. +%br +"Cosgrove has perceptively pointed out that the programmer delivers +satisfaction of a user need rather than any tangible product. +And both the actual need and the user's perception of that need +will change as programs are built, tested, and used." p.117. +%br +"The total cost of maintaining a widely used program is typically 40 percent +or more of the cost of developing it." p.121. +%br +"As shown above, amalgamating prose and program reduces the total +number of characters to be stored." p.175. + +%T A Portable Compiler for the Language C +%A A. Snyder +%I Master's Thesis, M.I.T. +%C Cambridge, Mass. +%D 1974 + +%T The C Language Calling Sequence +%A M. E. Lesk +%A S. C. Johnson +%A D. M. Ritchie +%D 1977 + +%T Optimal Code Generation for Expression Trees +%A A. V. Aho +%A S. C. Johnson +%D 1975 +%J J. Assoc. Comp. Mach. +%K acm jacm +%V 23 +%N 3 +%P 488-501 +%O Also in \f2Proc. ACM Symp. on Theory of Computing,\f1 pp. 207-217, 1975. + +%A R. Sethi +%A J. D. Ullman +%T The Generation of Optimal Code for Arithmetic Expressions +%J J. Assoc. Comp. Mach. +%K acm jacm +%V 17 +%N 4 +%D October 1970 +%P 715-728 +%O Reprinted as pp. 229-247 in \fICompiler Techniques\fR, ed. B. W. Pollack, Auerbach, Princeton NJ (1972). +%X pwb +Optimal approach for straight-line, fixed +number of regs. + +%T Code Generation for Machines with Multiregister +Operations +%A A. V. Aho +%A S. C. Johnson +%A J. D. Ullman +%J Proc. 4th ACM Symp. on Principles of Programming Languages +%P 21-28 +%D January 1977 + diff --git a/bin/sh/USD.doc/referargs b/bin/sh/USD.doc/referargs new file mode 100644 index 0000000..3bb6284 --- /dev/null +++ b/bin/sh/USD.doc/referargs @@ -0,0 +1,8 @@ +.\" $NetBSD: referargs,v 1.1 2014/07/05 19:22:02 dholland Exp $ +.\" +.\" Arguments for refer; these were previously passed on the refer(1) +.\" command line: -e -p Rv7man +.R1 +accumulate +database Rv7man +.R2 diff --git a/bin/sh/USD.doc/t.mac b/bin/sh/USD.doc/t.mac new file mode 100644 index 0000000..9bf65c8 --- /dev/null +++ b/bin/sh/USD.doc/t.mac @@ -0,0 +1,69 @@ +.\" $NetBSD: t.mac,v 1.2 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t.mac 8.1 (Berkeley) 8/14/93 +.\" +.ds ZZ \fB.\|.\|.\fP +.ds ST \v'.3m'\s+2*\s0\v'-.3m' +.ds DO \h'\w'do 'u' +.ds Ca \h'\w'case 'u' +.ds WH \h'\w'while 'u' +.ds VT \|\fB\(or\fP\| +.ds TH \h'\w'then 'u' +.ds DC \*(DO\*(Ca +.ds AP >\h'-.2m'> +.ds HE <\h'-.2m'< +. \" macros for algol 68c reference manual +.ds DA 1977 November 1 +.ds md \v'.25m' +.ds mu \v'-.25m' +.ds U \*(mu\s-3 +.ds V \s0\*(md +.ds L \*(md\s-3 +.ds M \s0\*(mu +.ds S \s-1 +.ds T \s0 +. \" small 1 +.ds O \*S1\*T +.ds h \| +.ds s \|\| +. \" ellipsis +.ds e .\|.\|. +. \" subscripts +.ds 1 \*(md\s-41\s0\*(mu +.ds 2 \*(md\s-42\s0\*(mu diff --git a/bin/sh/USD.doc/t1 b/bin/sh/USD.doc/t1 new file mode 100644 index 0000000..075511f --- /dev/null +++ b/bin/sh/USD.doc/t1 @@ -0,0 +1,553 @@ +.\" $NetBSD: t1,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgment: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t1 8.1 (Berkeley) 8/14/93 +.\" +.EH 'USD:3-%''An Introduction to the UNIX Shell' +.OH 'An Introduction to the UNIX Shell''USD:3-%' +.\".RP +.TL +An Introduction to the UNIX Shell +.AU +S. R. Bourne +.AI +Murray Hill, NJ +.AU +(Updated for 4.3BSD by Mark Seiden) +.AU +(Further updated by Perry E. Metzger)\(dg +.AB +.FS +\(dg This paper was updated in 2010 to reflect most features of modern +POSIX shells, which all follow the design of S.R. Bourne's original v7 +Unix shell. +Among these are ash, bash, ksh and others. +Typically one of these will be installed as /bin/sh on a modern system. +It does not describe the behavior of the c shell (csh). +If it's the c shell (csh) you're interested in, a good place to +begin is William Joy's paper "An Introduction to the C shell" (USD:4). +.FE +.LP +The +.ul +shell +is a command programming language that provides an interface +to the +.UX +operating system. +Its features include +control-flow primitives, parameter passing, variables and +string substitution. +Constructs such as +.ul +while, if then else, case +and +.ul +for +are available. +Two-way communication is possible between the +.ul +shell +and commands. +String-valued parameters, typically file names or flags, may be +passed to a command. +A return code is set by commands that may be used to determine control-flow, +and the standard output from a command may be used +as shell input. +.LP +The +.ul +shell +can modify the environment +in which commands run. +Input and output can be redirected +to files, and processes that communicate through `pipes' +can be invoked. +Commands are found by +searching directories +in the file system in a +sequence that can be defined by the user. +Commands can be read either from the terminal or from a file, +which allows command procedures to be +stored for later use. +.AE +.ds ST \v'.3m'\s+2*\s0\v'-.3m' +.SH +1.0\ Introduction +.LP +The shell is both a command language +and a programming language +that provides an interface to the UNIX +operating system. +This memorandum describes, with +examples, the UNIX shell. +The first section covers most of the +everyday requirements +of terminal users. +Some familiarity with UNIX +is an advantage when reading this section; +see, for example, +"UNIX for beginners". +.[ +unix beginn kernigh 1978 +.] +Section 2 describes those features +of the shell primarily intended +for use within shell procedures. +These include the control-flow +primitives and string-valued variables +provided by the shell. +A knowledge of a programming language +would be a help when reading this section. +The last section describes the more +advanced features of the shell. +References of the form "see \fIpipe\fP (2)" +are to a section of the UNIX manual. +.[ +seventh 1978 ritchie thompson +.] +.SH +1.1\ Simple\ commands +.LP +Simple commands consist of one or more words +separated by blanks. +The first word is the name of the command +to be executed; any remaining words +are passed as arguments to the command. +For example, +.DS + who +.DE +is a command that prints the names +of users logged in. +The command +.DS + ls \(mil +.DE +prints a list of files in the current +directory. +The argument \fI\(mil\fP tells \fIls\fP +to print status information, size and +the creation date for each file. +.SH +1.2\ Input\ output\ redirection +.LP +Most commands produce output on the standard output +that is initially connected to the terminal. +This output may be sent to a file +by writing, for example, +.DS + ls \(mil >file +.DE +The notation \fI>file\fP +is interpreted by the shell and is not passed +as an argument to \fIls.\fP +If \fIfile\fP does not exist then the +shell creates it; +otherwise the original contents of +\fIfile\fP are replaced with the output +from \fIls.\fP +Output may be appended to a file +using the notation +.DS + ls \(mil \*(APfile +.DE +In this case \fIfile\fP is also created if it does not already +exist. +.LP +The standard input of a command may be taken +from a file instead of the terminal by +writing, for example, +.DS + wc <file +.DE +The command \fIwc\fP reads its standard input +(in this case redirected from \fIfile\fP) +and prints the number of characters, words and +lines found. +If only the number of lines is required +then +.DS + wc \(mil <file +.DE +could be used. +.\" I considered adding the following, but have thought better of it +.\" for now. +.\" -- Perry Metzger +.\" +.\" .LP +.\" Error messages are typically printed by commands on a different +.\" channel, called standard error, which may also be redirected using the +.\" notation 2>\|. +.\" For example +.\" .DS +.\" command some args >out 2>errors +.\" .DE +.\" will redirect standard output to the file `out' but standard error +.\" (and thus all error messages) to `errors'. +.\" The notation 2>&1 sets standard error pointing to the same +.\" place as standard out. +.\" Thus: +.\" .DS +.\" command some args 2>&1 >everything +.\" .DE +.\" will put both standard out and standard error into the file `everything'. +.\" See section 3.7 below for more details. +.SH +1.3\ Pipelines\ and\ filters +.LP +The standard output of one command may be +connected to the standard input of another +by writing +the `pipe' operator, +indicated by \*(VT, +as in, +.DS + ls \(mil \*(VT wc +.DE +Two commands connected in this way constitute +a \fIpipeline\fP and +the overall effect is the same as +.DS + ls \(mil >file; wc <file +.DE +except that no \fIfile\fP is used. +Instead the two \fIprocesses\fP are connected +by a pipe (see \fIpipe\fP(2)) and are +run in parallel. +Pipes are unidirectional and +synchronization is achieved by +halting \fIwc\fP when there is +nothing to read and halting \fIls\fP +when the pipe is full. +.LP +A \fIfilter\fP is a command +that reads its standard input, +transforms it in some way, +and prints the result as output. +One such filter, \fIgrep,\fP +selects from its input those lines +that contain some specified string. +For example, +.DS + ls \*(VT grep old +.DE +prints those lines, if any, of the output +from \fIls\fP that contain +the string \fIold.\fP +Another useful filter is \fIsort\fP. +For example, +.DS + who \*(VT sort +.DE +will print an alphabetically sorted list +of logged in users. +.LP +A pipeline may consist of more than two commands, +for example, +.DS + ls \*(VT grep old \*(VT wc \(mil +.DE +prints the number of file names +in the current directory containing +the string \fIold.\fP +.SH +1.4\ Background\ commands +.LP +To execute a command (or pipeline) the shell normally +creates the new \fIprocesses\fP +and waits for them to finish. +A command may be run without waiting +for it to finish. +For example, +.DS + cc pgm.c & +.DE +calls the C compiler to compile +the file \fIpgm.c\|.\fP +The trailing \fB&\fP is an operator that instructs the shell +not to wait for the command to finish. +To help keep track of such a process +the shell reports its job number (see below) and process +id following its creation. +Such a command is said to be running in the \fIbackground\fP. +By contrast, a command executed without the \fB&\fP is said to be +running in the \fIforeground\fP.\(dg +.FS +\(dg Even after execution, one may move commands from the foreground +to the background, or temporarily suspend their execution (which is +known as \fIstopping\fP a command. +This is described in detail in section 3.10 on \fIJob Control\fB. +.FE +.LP +A list of currently active processes, including ones not associated +with the current shell, may be obtained using the \fIps\fP(1) command. +.SH +1.5\ File\ name\ generation +.LP +Many commands accept arguments +which are file names. +For example, +.DS + ls \(mil main.c +.DE +prints information relating to the file \fImain.c\fP\|. +.LP +The shell provides a mechanism +for generating a list of file names +that match a pattern. +For example, +.DS + ls \(mil \*(ST.c +.DE +generates, as arguments to \fIls,\fP +all file names in the current directory that end in \fI.c\|.\fP +The character \*(ST is a pattern that will match any string +including the null string. +In general \fIpatterns\fP are specified +as follows. +.RS +.IP \fB\*(ST\fR 8 +Matches any string of characters +including the null string. +.IP \fB?\fR 8 +Matches any single character. +.IP \fB[\*(ZZ]\fR 8 +Matches any one of the characters +enclosed. +A pair of characters separated by a minus will +match any character lexically between +the pair. +.RE +.LP +For example, +.DS + [a\(miz]\*(ST +.DE +matches all names in the current directory +beginning with +one of the letters \fIa\fP through \fIz.\fP +.DS + /usr/fred/test/? +.DE +matches all names in the directory +\fB/usr/fred/test\fP that consist of a single character. +If no file name is found that matches +the pattern then the pattern is passed, +unchanged, as an argument. +.LP +This mechanism is useful both to save typing +and to select names according to some pattern. +It may also be used to find files. +For example, +.DS + echo /usr/fred/\*(ST/core +.DE +finds and prints the names of all \fIcore\fP files in sub-directories +of \fB/usr/fred\|.\fP +(\fIecho\fP is a standard UNIX command that prints +its arguments, separated by blanks.) +This last feature can be expensive, +requiring a scan of all +sub-directories of \fB/usr/fred\|.\fP +.LP +There is one exception to the general +rules given for patterns. +The character `\fB.\fP' +at the start of a file name must be explicitly +matched. +.DS + echo \*(ST +.DE +will therefore echo all file names in the current +directory not beginning +with `\fB.\fP'\|. +.DS + echo \fB.\fP\*(ST +.DE +will echo all those file names that begin with `\fB.\fP'\|. +This avoids inadvertent matching +of the names `\fB.\fP' and `\fB..\fP' +which mean `the current directory' +and `the parent directory' +respectively. +(Notice that \fIls\fP suppresses +information for the files `\fB.\fP' and `\fB..\fP'\|.) +.LP +Finally, the tilde character, `\fB\(ap\fP', may be used to indicate the +home directory of a user. +The `\fB\(ap\fP' at the beginning of a path name followed by a +non-alphabetic character expands to the current user's home +directory. +If the `\fB\(ap\fP' is followed by a login name, it expands to the named +user's home directory. +For example: +.DS + ls \(ap + cd \(apegbert/ +.DE +will list the contents of the user's home directory and then change +to the home directory of the user ``egbert''. +.SH +1.6\ Quoting +.LP +Characters that have a special meaning +to the shell, such as \fB< > \*(ST ? \*(VT &\|,\fR +are called metacharacters. +A complete list of metacharacters is given +in appendix B. +Any character preceded by a \fB\\\fR is \fIquoted\fP +and loses its special meaning, if any. +The \fB\\\fP is elided so that +.DS + echo \\? +.DE +will echo a single \fB?\|,\fP +and +.DS + echo \\\\ +.DE +will echo a single \fB\\\|.\fR +To allow long strings to be continued over +more than one line +the sequence \fB\\newline\fP +is ignored. +.LP +\fB\\\fP is convenient for quoting +single characters. +When more than one character needs +quoting the above mechanism is clumsy and +error prone. +A string of characters may be quoted +by enclosing the string between single quotes. +For example, +.DS + echo xx\'\*(ST\*(ST\*(ST\*(ST\'xx +.DE +will echo +.DS + xx\*(ST\*(ST\*(ST\*(STxx +.DE +The quoted string may not contain +a single quote +but may contain newlines, which are preserved. +This quoting mechanism is the most +simple and is recommended +for casual use. +.LP +A third quoting mechanism using double quotes +is also available +that prevents interpretation of some but not all +metacharacters. +Discussion of the +details is deferred +to section 3.5\|. +.SH +1.7\ Prompting +.LP +When the shell is used from a terminal it will +issue a prompt before reading a command. +By default this prompt is `\fB$\ \fR'\|. +It may be changed by saying, +for example, +.DS + \s-1PS1\s0="yesdear$ " +.DE +that sets the prompt to be the string \fIyesdear$\|.\fP +If a newline is typed and further input is needed +then the shell will issue the prompt `\fB>\ \fR'\|. +Sometimes this can be caused by mistyping +a quote mark. +If it is unexpected then entering the interrupt character +(typically \s-1CONTROL-C\s0) +will return the shell to read another command. +This prompt may be changed by saying, for example, +.DS + \s-1PS2\s0=more +.DE +Entering the interrupt character may also be used to terminate most +programs running as the current foreground job. +.LP +(\s-1PS1\s0 and \s-1PS2\s0 are \fIshell variables\fP, which will be +described in section 2.4 below.) +.SH +1.8\ The\ shell\ and\ login +.LP +Following \fIlogin\fP(1) +the shell is called to read and execute +commands typed at the terminal. +If the user's login directory +contains the file \fB.profile\fP +then it is assumed to contain commands +and is read by the shell before reading +any commands from the terminal. +.LP +(Most versions of the shell also specify a file that is read and +executed on start-up whether or not the shell is invoked by login. +The \s-1ENV\s0 shell variable, described in section 2.4 below, can be +used to override the name of this file. +See the shell manual page for further information.) +.SH +1.9\ Summary +.sp +.RS +.IP \(bu +\fBls\fP +.br +Print the names of files in the current directory. +.IP \(bu +\fBls >file\fP +.br +Put the output from \fIls\fP into \fIfile.\fP +.IP \(bu +\fBls \*(VT wc \(mil\fR +.br +Print the number of files in the current directory. +.IP \(bu +\fBls \*(VT grep old\fR +.br +Print those file names containing the string \fIold.\fP +.IP \(bu +\fBls \*(VT grep old \*(VT wc \(mil\fR +.br +Print the number of files whose name contains the string \fIold.\fP +.IP \(bu +\fBcc pgm.c &\fR +.br +Run \fIcc\fP in the background. +.RE diff --git a/bin/sh/USD.doc/t2 b/bin/sh/USD.doc/t2 new file mode 100644 index 0000000..d49747e --- /dev/null +++ b/bin/sh/USD.doc/t2 @@ -0,0 +1,971 @@ +.\" $NetBSD: t2,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgment: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t2 8.1 (Berkeley) 6/8/93 +.\" +.SH +2.0\ Shell\ scripts +.LP +The shell may be used to read and execute commands +contained in a file. +For example, +.DS + sh file [ args \*(ZZ ] +.DE +calls the shell to read commands from \fIfile.\fP +Such a file is called a \fIshell script.\fP +Arguments may be supplied with the call +and are referred to in \fIfile\fP +using the positional parameters +\fB$1, $2, \*(ZZ\|.\fR +.LP +For example, if the file \fIwg\fP contains +.DS + who \*(VT grep $1 +.DE +then +.DS + sh wg fred +.DE +is equivalent to +.DS + who \*(VT grep fred +.DE +.LP +UNIX files have three independent attributes, +\fIread,\fP \fIwrite\fP and \fIexecute.\fP +The UNIX command \fIchmod\fP(1) may be used +to make a file executable. +For example, +.DS + chmod +x wg +.DE +will ensure that the file \fIwg\fP has execute status. +Following this, the command +.DS + wg fred +.DE +is equivalent to +.DS + sh wg fred +.DE +This allows shell scripts and other programs +to be used interchangeably. +In either case a new process is created to +run the command. +.LP +The `\fB#\fP' character is used as a comment character by the shell. +All characters following the `#' on a line are ignored. +.LP +A typical modern system has several different shells, some with differing +command syntax, and it is desirable to specify which one should be +invoked when an executable script is invoked. +If the special comment +.DS + #!/\fIpath\fP/\fIto\fP/\fIinterpreter\fP +.DE +appears as the first line in a script, it is used to specify the +absolute pathname of the shell (or other interpreter) that should be +used to execute the file. +(Without such a line, \fB/bin/sh\fP is assumed.) +It is best if a script explicitly states +what shell it is intended for in this manner. +.LP +As well as providing names for the positional +parameters, +the number of positional parameters to a script +is available as \fB$#\|.\fP +The name of the file being executed +is available as \fB$0\|.\fP +.LP +A special shell parameter \fB$\*(ST\fP +is used to substitute for all positional parameters +except \fB$0\|.\fP +A typical use of this is to provide +some default arguments, +as in, +.DS + nroff \(miT450 \(mims $\*(ST +.DE +which simply prepends some arguments +to those already given. +(The variable \fB$@\fP also expands to ``all positional +parameters'', but is subtly different when expanded inside quotes. +See section 3.5, below.) +.SH +2.1\ Control\ flow\ -\ for +.LP +A frequent use of shell scripts is to loop +through the arguments (\fB$1, $2, \*(ZZ\fR) +executing commands once for each argument. +An example of such a script is +\fItel\fP that searches the file +\fB/usr/share/telnos\fR +that contains lines of the form +.DS + \*(ZZ + fred mh0123 + bert mh0789 + \*(ZZ +.DE +The text of \fItel\fP is +.DS + #!/bin/sh + + for i + do + grep $i /usr/share/telnos + done +.DE +The command +.DS + tel fred +.DE +prints those lines in \fB/usr/share/telnos\fR +that contain the string \fIfred\|.\fP +.DS + tel fred bert +.DE +prints those lines containing \fIfred\fP +followed by those for \fIbert.\fP +.LP +The \fBfor\fP loop notation is recognized by the shell +and has the general form +.DS + \fBfor\fR \fIname\fR \fBin\fR \fIw1 w2 \*(ZZ\fR + \fBdo\fR \fIcommand-list\fR + \fBdone\fR +.DE +A \fIcommand-list\fP is a sequence of one or more +simple commands separated or terminated by a newline or semicolon. +Furthermore, reserved words +like \fBdo\fP and \fBdone\fP are only +recognized following a newline or +semicolon. +\fIname\fP is a shell variable that is set +to the words \fIw1 w2 \*(ZZ\fR in turn each time the \fIcommand-list\fP +following \fBdo\fP +is executed. +If \fBin\fR \fIw1 w2 \*(ZZ\fR +is omitted then the loop +is executed once for each positional parameter; +that is, \fBin\fR \fI$\*(ST\fR is assumed. +.LP +Another example of the use of the \fBfor\fP +loop is the \fIcreate\fP command +whose text is +.DS + for i do >$i; done +.DE +The command +.DS + create alpha beta +.DE +ensures that two empty files +\fIalpha\fP and \fIbeta\fP exist +and are empty. +The notation \fI>file\fP may be used on its +own to create or clear the contents of a file. +Notice also that a semicolon (or newline) is required before \fBdone.\fP +.SH +2.2\ Control\ flow\ -\ case +.LP +A multiple way branch is provided for by the +\fBcase\fP notation. +For example, +.DS + case $# in + \*(Ca1) cat \*(AP$1 ;; + \*(Ca2) cat \*(AP$2 <$1 ;; + \*(Ca\*(ST) echo \'usage: append [ from ] to\' ;; + esac +.DE +is an \fIappend\fP command. +When called +with one argument as +.DS + append file +.DE +\fB$#\fP is the string \fI1\fP and +the standard input is copied onto the +end of \fIfile\fP +using the \fIcat\fP command. +.DS + append file1 file2 +.DE +appends the contents of \fIfile1\fP +onto \fIfile2.\fP +If the number of arguments supplied to +\fIappend\fP is other than 1 or 2 +then a message is printed indicating +proper usage. +.LP +The general form of the \fBcase\fP command +is +.DS + \fBcase \fIword \fBin + \*(Ca\fIpattern\|\fB)\ \fIcommand-list\fB\|;; + \*(Ca\*(ZZ + \fBesac\fR +.DE +The shell attempts to match +\fIword\fR with each \fIpattern,\fR +in the order in which the patterns +appear. +If a match is found the +associated \fIcommand-list\fP is +executed and execution +of the \fBcase\fP is complete. +Since \*(ST is the pattern that matches any +string it can be used for the default case. +.LP +A word of caution: +no check is made to ensure that only +one pattern matches +the case argument. +The first match found defines the set of commands +to be executed. +In the example below the commands following +the second \*(ST will never be executed. +.DS + case $# in + \*(Ca\*(ST) \*(ZZ ;; + \*(Ca\*(ST) \*(ZZ ;; + esac +.DE +.LP +Another example of the use of the \fBcase\fP +construction is to distinguish +between different forms +of an argument. +The following example is a fragment of a \fIcc\fP command. +.DS + for i + do case $i in + \*(DC\(mi[ocs]) \*(ZZ ;; + \*(DC\(mi\*(ST) echo "unknown flag $i" ;; + \*(DC\*(ST.c) /lib/c0 $i \*(ZZ ;; + \*(DC\*(ST) echo "unexpected argument $i" ;; + \*(DOesac + done +.DE +.LP +To allow the same commands to be associated +with more than one pattern +the \fBcase\fP command provides +for alternative patterns +separated by a \*(VT\|. +For example, +.DS + case $i in + \*(Ca\(mix\*(VT\(miy) \*(ZZ + esac +.DE +is equivalent to +.DS + case $i in + \*(Ca\(mi[xy]) \*(ZZ + esac +.DE +.LP +The usual quoting conventions apply +so that +.DS + case $i in + \*(Ca\\?) \*(ZZ +.DE +will match the character \fB?\|.\fP +.SH +2.3\ Here\ documents +.LP +The shell script \fItel\fP +in section 2.1 uses the file \fB/usr/share/telnos\fR +to supply the data +for \fIgrep.\fP +An alternative is to include this +data +within the shell script as a \fIhere\fP document, as in, +.DS + for i + do grep $i \*(HE! + \*(DO\*(ZZ + \*(DOfred mh0123 + \*(DObert mh0789 + \*(DO\*(ZZ + ! + done +.DE +In this example +the shell takes the lines between \fB\*(HE!\fR and \fB!\fR +as the standard input for \fIgrep.\fP +The string \fB!\fR is arbitrary, the document +being terminated by a line that consists +of the string following \*(HE\|. +.LP +Parameters are substituted in the document +before it is made available to \fIgrep\fP +as illustrated by the following script +called \fIedg\|.\fP +.DS + ed $3 \*(HE% + g/$1/s//$2/g + w + % +.DE +The call +.DS + edg string1 string2 file +.DE +is then equivalent to the command +.DS + ed file \*(HE% + g/string1/s//string2/g + w + % +.DE +and changes all occurrences of \fIstring1\fP +in \fIfile\fP to \fIstring2\|.\fP +Substitution can be prevented using \\ +to quote the special character \fB$\fP +as in +.DS + ed $3 \*(HE+ + 1,\\$s/$1/$2/g + w + + +.DE +(This version of \fIedg\fP is equivalent to +the first except that \fIed\fP will print +a \fB?\fR if there are no occurrences of +the string \fB$1\|.\fP) +Substitution within a \fIhere\fP document +may be prevented entirely by quoting +the terminating string, +for example, +.DS + grep $i \*(HE'end' + \*(ZZ + end +.DE +The document is presented +without modification to \fIgrep.\fP +If parameter substitution is not required +in a \fIhere\fP document this latter form +is more efficient. +.SH +2.4\ Shell\ variables\(dg +.LP +.FS +Also known as \fIenvironment variables\fB, see \fIenvironment\fB(7). +.FE +The shell +provides string-valued variables. +Variable names begin with a letter +and consist of letters, digits and +underscores. +Variables may be given values by writing, for example, +.DS + user=fred\ box=m000\ acct=mh0000 +.DE +which assigns values to the variables +\fBuser, box\fP and \fBacct.\fP +A variable may be set to the null string +by saying, for example, +.DS + null= +.DE +The value of a variable is substituted +by preceding its name with \fB$\|\fP; +for example, +.DS + echo $user +.DE +will echo \fIfred.\fP +.LP +Variables may be used interactively +to provide abbreviations for frequently +used strings. +For example, +.DS + b=/usr/fred/bin + mv pgm $b +.DE +will move the file \fIpgm\fP +from the current directory to the directory \fB/usr/fred/bin\|.\fR +A more general notation is available for parameter +(or variable) +substitution, as in, +.DS + echo ${user} +.DE +which is equivalent to +.DS + echo $user +.DE +and is used when the parameter name is +followed by a letter or digit. +For example, +.DS + tmp=/tmp/ps + ps a >${tmp}a +.DE +will direct the output of \fIps\fR +to the file \fB/tmp/psa,\fR +whereas, +.DS + ps a >$tmpa +.DE +would cause the value of the variable \fBtmpa\fP +to be substituted. +.LP +Except for \fB$?\fP the following +are set initially by the shell. +\fB$?\fP is set after executing each command. +.RS +.IP \fB$?\fP 8 +The exit status (return code) +of the last command executed +as a decimal string. +Most commands return a zero exit status +if they complete successfully, +otherwise a non-zero exit status is returned. +Testing the value of return codes is dealt with +later under \fBif\fP and \fBwhile\fP commands. +.IP \fB$#\fP 8 +The number of positional parameters +(in decimal). +Used, for example, in the \fIappend\fP command +to check the number of parameters. +.IP \fB$$\fP 8 +The process number of this shell (in decimal). +Since process numbers are unique among +all existing processes, this string is +frequently used to generate +unique +temporary file names. +For example, +.DS + ps a >/tmp/ps$$ + \*(ZZ + rm /tmp/ps$$ +.DE +.IP \fB$\|!\fP 8 +The process number of the last process +run in the background (in decimal). +.IP \fB$\(mi\fP 8 +The current shell flags, such as +\fB\(mix\fR and \fB\(miv\|.\fR +.RE +.LP +Some variables have a special meaning to the +shell and should be avoided for general +use. +.RS +.IP \fB$\s-1MAIL\s0\fP 8 +When used interactively +the shell looks at the file +specified by this variable +before it issues a prompt. +If the specified file has been modified +since it +was last looked at the shell +prints the message +\fIyou have mail\fP before prompting +for the next command. +This variable is typically set +in the file \fB.profile,\fP +in the user's login directory. +For example, +.DS + \s-1MAIL\s0=/usr/spool/mail/fred +.DE +.IP \fB$\s-1HOME\s0\fP 8 +The default argument +for the \fIcd\fP command. +The current directory is used to resolve +file name references that do not begin with +a \fB/\|,\fR +and is changed using the \fIcd\fP command. +For example, +.DS + cd /usr/fred/bin +.DE +makes the current directory \fB/usr/fred/bin\|.\fR +.DS + cat wn +.DE +will print on the terminal the file \fIwn\fP +in this directory. +The command +\fIcd\fP with no argument +is equivalent to +.DS + cd $\s-1HOME\s0 +.DE +This variable is also typically set in the +the user's login profile. +.IP \fB$\s-1PWD\s0\fP 8 +The current working directory. Set by the \fIcd\fB command. +.IP \fB$\s-1PATH\s0\fP 8 +A list of directories that contain commands (the \fIsearch path\fR\|). +Each time a command is executed by the shell +a list of directories is searched +for an executable file. +.ne 5 +If \fB$\s-1PATH\s0\fP is not set +then the current directory, +\fB/bin\fP, and \fB/usr/bin\fP are searched by default. +.ne 5 +Otherwise \fB$\s-1PATH\s0\fP consists of directory +names separated by \fB:\|.\fP +For example, +.DS + \s-1PATH\s0=\fB:\fP/usr/fred/bin\fB:\fP/bin\fB:\fP/usr/bin +.DE +specifies that the current directory +(the null string before the first \fB:\fP\|), +\fB/usr/fred/bin, /bin \fRand\fP /usr/bin\fR +are to be searched in that order. +In this way individual users +can have their own `private' commands +that are accessible independently +of the current directory. +If the command name contains a \fB/\fR then this directory search +is not used; a single attempt +is made to execute the command. +.IP \fB$\s-1PS1\s0\fP 8 +The primary shell prompt string, by default, `\fB$\ \fR'. +.IP \fB$\s-1PS2\s0\fP 8 +The shell prompt when further input is needed, +by default, `\fB>\ \fR'. +.IP \fB$\s-1IFS\s0\fP 8 +The set of characters used by \fIblank +interpretation\fR (see section 3.5). +.IP \fB$\s-1ENV\s0\fP 8 +The shell reads and executes the commands in the file +specified by this variable when it is initially started. +Unlike the \fB.profile\fP file, these commands are executed by all +shells, not just the one started at login. +(Most versions of the shell specify a filename that is used if +\s-1ENV\s0 is not explicitly set. See the manual page for your shell.) +.RE +.SH +2.5\ The\ test\ command +.LP +The \fItest\fP command, although not part of the shell, +is intended for use by shell programs. +For example, +.DS + test \(mif file +.DE +returns zero exit status if \fIfile\fP +exists and non-zero exit status otherwise. +In general \fItest\fP evaluates a predicate +and returns the result as its exit status. +Some of the more frequently used \fItest\fP +arguments are given here, see \fItest\fP(1) +for a complete specification. +.DS + test s true if the argument \fIs\fP is not the null string + test \(mif file true if \fIfile\fP exists + test \(mir file true if \fIfile\fP is readable + test \(miw file true if \fIfile\fP is writable + test \(mid file true if \fIfile\fP is a directory +.DE +The \fItest\fP command is known as `\fB[\fP' and may be invoked as +such. +For aesthetic reasons, the command ignores a close bracket `\fB]\fP' given +at the end of a command so +.DS + [ -f filename ] +.DE +and +.DS + test -f filename +.DE +are completely equivalent. +Typically, the bracket notation is used when \fItest\fP is invoked inside +shell control constructs. +.SH +2.6\ Control\ flow\ -\ while +.LP +The actions of +the \fBfor\fP loop and the \fBcase\fP +branch are determined by data available to the shell. +A \fBwhile\fP or \fBuntil\fP loop +and an \fBif then else\fP branch +are also provided whose +actions are determined by the exit status +returned by commands. +A \fBwhile\fP loop has the general form +.DS + \fBwhile\fP \fIcommand-list\*1\fP + \fBdo\fP \fIcommand-list\*2\fP + \fBdone\fP +.DE +.LP +The value tested by the \fBwhile\fP command +is the exit status of the last simple command +following \fBwhile.\fP +Each time round the loop +\fIcommand-list\*1\fP is executed; +if a zero exit status is returned then +\fIcommand-list\*2\fP +is executed; +otherwise, the loop terminates. +For example, +.DS + while [ $1 ] + do \*(ZZ + \*(DOshift + done +.DE +is equivalent to +.DS + for i + do \*(ZZ + done +.DE +\fIshift\fP is a shell command that +renames the positional parameters +\fB$2, $3, \*(ZZ\fR as \fB$1, $2, \*(ZZ\fR +and loses \fB$1\|.\fP +.LP +Another kind of use for the \fBwhile/until\fP +loop is to wait until some +external event occurs and then run +some commands. +In an \fBuntil\fP loop +the termination condition is reversed. +For example, +.DS + until [ \(mif file ] + do sleep 300; done + \fIcommands\fP +.DE +will loop until \fIfile\fP exists. +Each time round the loop it waits for +5 minutes before trying again. +(Presumably another process +will eventually create the file.) +.LP +The most recent enclosing loop may be exited with the \fBbreak\fP +command, or the rest of the body skipped and the next iteration begun +with the \fBcontinue\fP command. +.LP +The commands \fItrue\fP(1) and \fIfalse\fP(1) return 0 and non-zero +exit statuses respectively. They are sometimes of use in control flow, +e.g.: +.DS + while true + do date; sleep 5 + done +.DE +is an infinite loop that prints the date and time every five seconds. +.SH +2.7\ Control\ flow\ -\ if +.LP +Also available is a +general conditional branch +of the form, +.DS + \fBif\fP \fIcommand-list + \fBthen \fIcommand-list + \fBelse \fIcommand-list + \fBfi\fR +.DE +that tests the value returned by the last simple command +following \fBif.\fP +.LP +The \fBif\fP command may be used +in conjunction with the \fItest\fP command +to test for the existence of a file as in +.DS + if [ \(mif file ] + then \fIprocess file\fP + else \fIdo something else\fP + fi +.DE +.LP +An example of the use of \fBif, case\fP +and \fBfor\fP constructions is given in +section 2.10\|. +.LP +A multiple test \fBif\fP command +of the form +.DS + if \*(ZZ + then \*(ZZ + else if \*(ZZ + then \*(ZZ + else if \*(ZZ + \*(ZZ + fi + fi + fi +.DE +may be written using an extension of the \fBif\fP +notation as, +.DS + if \*(ZZ + then \*(ZZ + elif \*(ZZ + then \*(ZZ + elif \*(ZZ + \*(ZZ + fi +.DE +.LP +The following example is an implementation of the \fItouch\fP command +which changes the `last modified' time for a list +of files. +The command may be used in conjunction +with \fImake\fP(1) to force recompilation of a list +of files. +.DS + #!/bin/sh + + flag= + for i + do case $i in + \*(DC\(mic) flag=N ;; + \*(DC\*(ST) if [ \(mif $i ] + \*(DC then cp $i junk$$; mv junk$$ $i + \*(DC elif [ $flag ] + \*(DC then echo file \\'$i\\' does not exist + \*(DC else >$i + \*(DC fi + \*(DO esac + done +.DE +The \fB\(mic\fP flag is used in this command to +force subsequent files to be created if they do not already exist. +Otherwise, if the file does not exist, an error message is printed. +The shell variable \fIflag\fP +is set to some non-null string if the \fB\(mic\fP +argument is encountered. +The commands +.DS + cp \*(ZZ; mv \*(ZZ +.DE +copy the file and then overwrite it with the copy, +thus causing the last modified date to be updated. +.LP +The sequence +.DS + if command1 + then command2 + fi +.DE +may be written +.DS + command1 && command2 +.DE +Conversely, +.DS + command1 \*(VT\*(VT command2 +.DE +executes \fIcommand2\fP only if \fIcommand1\fP +fails. +In each case the value returned +is that of the last simple command executed. +.LP +Placing a `\fB!\fP' in front of a pipeline inverts its exit +status, almost in the manner of the C operator of the same name. +Thus: +.DS + if ! [ -d $1 ] + then + echo $1 is not a directory + fi +.DE +will print a message only if $1 is not a directory. +.SH +2.8\ Command\ grouping +.LP +Commands may be grouped in two ways, +.DS + \fB{\fI command-list\fB ; }\fR +.DE +and +.DS + \fB(\fI command-list\fB )\fR +.DE +.LP +In the first \fIcommand-list\fP is simply executed. +The second form executes \fIcommand-list\fP +as a separate process. +For example, +.DS + (cd x; rm junk ) +.DE +executes \fIrm junk\fP in the directory +\fBx\fP without changing the current +directory of the invoking shell. +.LP +The commands +.DS + cd x; rm junk +.DE +have the same effect but leave the invoking +shell in the directory \fBx.\fP +.SH +2.9\ Shell\ Functions +.LP +A function may be defined by the syntax +.DS + \fIfuncname\fP() \fB{\fI command-list\fB ; }\fR +.DE +Functions are invoked within a script as though they were separate +commands of the same name. +While they are executed, the +positional parameters \fB$1, $2, \*(ZZ\fR are temporarily set to the +arguments passed to the function. For example: +.DS + count() { + echo $2 : $# + } + + count a b c +.DE +would print `b : 3'. +.SH +2.10\ Debugging\ shell\ scripts +.LP +The shell provides two tracing mechanisms +to help when debugging shell scripts. +The first is invoked within the script +as +.DS + set \(miv +.DE +(\fBv\fP for verbose) and causes lines of the +script to be printed as they are read. +It is useful to help isolate syntax errors. +It may be invoked without modifying the script +by saying +.DS + sh \(miv \fIscript\fP \*(ZZ +.DE +where \fIscript\fP is the name of the shell script. +This flag may be used in conjunction +with the \fB\(min\fP flag which prevents +execution of subsequent commands. +(Note that saying \fIset \(min\fP at a terminal +will render the terminal useless +until an end-of-file is typed.) +.LP +The command +.DS + set \(mix +.DE +will produce an execution +trace. +Following parameter substitution +each command is printed as it is executed. +(Try these at the terminal to see +what effect they have.) +Both flags may be turned off by saying +.DS + set \(mi +.DE +and the current setting of the shell flags is available as \fB$\(mi\|\fR. +.SH +2.11\ The\ man\ command +.LP +The following is a simple implementation of the \fIman\fP command, +which is used to display sections of the UNIX manual on your terminal. +It is called, for example, as +.DS + man sh + man \(mit ed + man 2 fork +.DE +In the first the manual section for \fIsh\fP +is displayed.. +Since no section is specified, section 1 is used. +The second example will typeset (\fB\(mit\fP option) +the manual section for \fIed.\fP +The last prints the \fIfork\fP manual page +from section 2, which covers system calls. +.sp 2 +.DS + #!/bin/sh + + cd /usr/share/man + + # "#" is the comment character + # default is nroff ($N), section 1 ($s) + N=n\ s=1 + + for i + do case $i in +.sp .5 + \*(DC[1\(mi9]\*(ST) s=$i ;; +.sp .5 + \*(DC\(mit) N=t ;; +.sp .5 + \*(DC\(min) N=n ;; +.sp .5 + \*(DC\(mi\*(ST) echo unknown flag \\'$i\\' ;; +.sp .5 + \*(DC\*(ST) if [ \(mif man$s/$i.$s ] + \*(DC then + \*(DC ${N}roff \(miman man$s/$i.$s + \*(DC else # look through all manual sections + \*(DC found=no + \*(DC for j in 1 2 3 4 5 6 7 8 9 + \*(DC do + \*(DC \*(DOif [ \(mif man$j/$i.$j ] + \*(DC \*(DOthen + \*(DC \*(DO\*(THman $j $i + \*(DC \*(DO\*(THfound=yes + \*(DC \*(DO\*(THbreak + \*(DC \*(DOfi + \*(DC done + \*(DC case $found in + \*(DC \*(Cano) echo \\'$i: manual page not found\\' + \*(DC esac + \*(DC fi + \*(DOesac + done +.DE +.ce +.ft B +Figure 1. A version of the man command +.ft R diff --git a/bin/sh/USD.doc/t3 b/bin/sh/USD.doc/t3 new file mode 100644 index 0000000..aab53ee --- /dev/null +++ b/bin/sh/USD.doc/t3 @@ -0,0 +1,976 @@ +.\" $NetBSD: t3,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t3 8.1 (Berkeley) 6/8/93 +.\" +.SH +3.0\ Keyword\ parameters +.LP +Shell variables may be given values +by assignment +or when a shell script is invoked. +An argument to a command of the form +\fIname=value\fP +that precedes the command name +causes \fIvalue\fP +to be assigned to \fIname\fP +before execution of the command begins. +The value of \fIname\fP in the invoking +shell is not affected. +For example, +.DS + user=fred\ command +.DE +will execute \fIcommand\fP with +\fBuser\fP set to \fIfred\fP. +.\" Removed by Perry Metzger because -k is not in POSIX +.\" +.\" The \fB\(mik\fR flag causes arguments of the form +.\" \fIname=value\fP to be interpreted in this way +.\" anywhere in the argument list. +.\" Such \fInames\fP are sometimes +.\" called keyword parameters. +.\" If any arguments remain they +.\" are available as positional +.\" parameters \fB$1, $2, \*(ZZ\|.\fP +.LP +The \fIset\fP command +may also be used to set positional parameters +from within a script. +For example, +.DS + set\ \(mi\(mi\ \*(ST +.DE +will set \fB$1\fP to the first file name +in the current directory, \fB$2\fP to the next, +and so on. +Note that the first argument, \(mi\(mi, ensures correct treatment +when the first file name begins with a \(mi\|. +.LP +.SH +3.1\ Parameter\ transmission +.LP +When a command is executed both positional parameters +and shell variables may be set on invocation. +Variables are also made available implicitly +to a command +by specifying in advance that such parameters +are to be exported from the invoking shell. +For example, +.DS + export\ user\ box=red +.DE +marks the variables \fBuser\fP and \fBbox\fP +for export (setting \fBbox\fP to ``red'' in the process). +When a command is invoked +copies are made of all exportable variables +(also known as \fIenvironment variables\fP) +for use within the invoked program. +Modification of such variables +within an invoked command does not +affect the values in the invoking shell. +It is generally true of +a shell script or other program +that it +cannot modify the state +of its caller without explicit +actions on the part of the caller. +.\" Removed by Perry Metzger because this is confusing to beginners. +.\" +.\" (Shared file descriptors are an +.\" exception to this rule.) +.LP +Names whose value is intended to remain +constant may be declared \fIreadonly\|.\fP +The form of this command is the same as that of the \fIexport\fP +command, +.DS + readonly name[=value] \*(ZZ +.DE +Subsequent attempts to set readonly variables +are illegal. +.SH +3.2\ Parameter\ substitution +.LP +If a shell parameter is not set +then the null string is substituted for it. +For example, if the variable \fBd\fP +is not set +.DS + echo $d +.DE +or +.DS + echo ${d} +.DE +will echo nothing. +A default string may be given +as in +.DS + echo ${d:\(mi\fB.\fR} +.DE +which will echo +the value of the variable \fBd\fP +if it is set and not null and `\fB.\fP' otherwise. +The default string is evaluated using the usual +quoting conventions so that +.DS + echo ${d:\(mi\'\*(ST\'} +.DE +will echo \fB\*(ST\fP if the variable \fBd\fP +is not set or null. +Similarly +.DS + echo ${d:\(mi$1} +.DE +will echo the value of \fBd\fP if it is set and not null +and the value (if any) of \fB$1\fP otherwise. +.LP +The notation ${d:+\fB.\fR} performs the inverse operation. It +substitutes `\fB.\fP' if \fBd\fP is set or not null, and otherwise +substitutes null. +.LP +A variable may be assigned a default value +using +the notation +.DS + echo ${d:=\fB.\fR} +.DE +which substitutes the same string as +.DS + echo ${d:\(mi\fB.\fR} +.DE +and if \fBd\fP were not previously set or null +then it will be set to the string `\fB.\fP'\|. +.LP +If there is no sensible default then +the notation +.DS + echo ${d:?\fImessage\fP} +.DE +will echo the value of the variable \fBd\fP if it is set and not null, +otherwise \fImessage\fP is printed by the shell and +execution of the shell script is abandoned. +If \fImessage\fP is absent then a standard message +is printed. +A shell script that requires some variables +to be set might start as follows: +.DS + :\ ${user:?}\ ${acct:?}\ ${bin:?} + \*(ZZ +.DE +Colon (\fB:\fP) is a command +that is +built in to the shell and does nothing +once its arguments have been evaluated. +If any of the variables \fBuser, acct\fP +or \fBbin\fP are not set then the shell +will abandon execution of the script. +.SH +3.3\ Command\ substitution +.LP +The standard output from a command can be +substituted in a similar way to parameters. +The command \fIpwd\fP prints on its standard +output the name of the current directory. +For example, if the current directory is +\fB/usr/fred/bin\fR +then the commands +.DS + d=$(pwd) +.DE +(or the older notation d=\`pwd\`) +is equivalent to +.DS + d=/usr/fred/bin +.DE +.LP +The entire string inside $(\*(ZZ)\| (or between grave accents \`\*(ZZ\`) +is taken as the command +to be executed +and is replaced with the output from +the command. +(The difference between the $(\*(ZZ) and \`\*(ZZ\` notations is that +the former may be nested, while the latter cannot be.) +.LP +The command is written using the usual quoting conventions, +except that inside \`\*(ZZ\` +a \fB\`\fR must be escaped using +a \fB\\\|\fR. +For example, +.DS + ls $(echo "$HOME") +.DE +is equivalent to +.DS + ls $HOME +.DE +Command substitution occurs in all contexts +where parameter substitution occurs (including \fIhere\fP documents) and the +treatment of the resulting text is the same +in both cases. +This mechanism allows string +processing commands to be used within +shell scripts. +An example of such a command is \fIbasename\fP +which removes a specified suffix from a string. +For example, +.DS + basename main\fB.\fPc \fB.\fPc +.DE +will print the string \fImain\|.\fP +Its use is illustrated by the following +fragment from a \fIcc\fP command. +.DS + case $A in + \*(Ca\*(ZZ + \*(Ca\*(ST\fB.\fPc) B=$(basename $A \fB.\fPc) + \*(Ca\*(ZZ + esac +.DE +that sets \fBB\fP to the part of \fB$A\fP +with the suffix \fB.c\fP stripped. +.LP +Here are some composite examples. +.RS +.IP \(bu +.ft B +for i in \`ls \(mit\`; do \*(ZZ +.ft R +.br +The variable \fBi\fP is set +to the names of files in time order, +most recent first. +.IP \(bu +.ft B +set \(mi\(mi\| \`date\`; echo $6 $2 $3, $4 +.ft R +.br +will print, e.g., +.ft I +1977 Nov 1, 23:59:59 +.ft R +.RE +.SH +3.4\ Arithmetic\ Expansion +.LP +Within a $((\*(ZZ)) construct, integer arithmetic operations are +evaluated. +(The $ in front of variable names is optional within $((\*(ZZ)). +For example: +.DS + x=5; y=1 + echo $(($x+3*2)) + echo $((y+=x)) + echo $y +.DE +will print `11', then `6', then `6' again. +Most of the constructs permitted in C arithmetic operations are +permitted though some (like `++') are not universally supported \(em +see the shell manual page for details. +.SH +3.5\ Evaluation\ and\ quoting +.LP +The shell is a macro processor that +provides parameter substitution, command substitution and file +name generation for the arguments to commands. +This section discusses the order in which +these evaluations occur and the +effects of the various quoting mechanisms. +.LP +Commands are parsed initially according to the grammar +given in appendix A. +Before a command is executed +the following +substitutions occur. +.RS +.IP \(bu +parameter substitution, e.g. \fB$user\fP +.IP \(bu +command substitution, e.g. \fB$(pwd)\fP or \fB\`pwd\`\fP +.IP \(bu +arithmetic expansion, e.g. \fB$(($count+1))\fP +.RS +.LP +Only one evaluation occurs so that if, for example, the value of the variable +\fBX\fP +is the string \fI$y\fP +then +.DS + echo $X +.DE +will echo \fI$y\|.\fP +.RE +.IP \(bu +blank interpretation +.RS +.LP +Following the above substitutions +the resulting characters +are broken into non-blank words (\fIblank interpretation\fP). +For this purpose `blanks' are the characters of the string +\fB$\s-1IFS\s0\fP. +By default, this string consists of blank, tab and newline. +The null string +is not regarded as a word unless it is quoted. +For example, +.DS + echo \'\' +.DE +will pass on the null string as the first argument to \fIecho\fP, +whereas +.DS + echo $null +.DE +will call \fIecho\fR with no arguments +if the variable \fBnull\fP is not set +or set to the null string. +.RE +.IP \(bu +file name generation +.RS +.LP +Each word +is then scanned for the file pattern characters +\fB\*(ST, ?\fR and \fB[\*(ZZ]\fR +and an alphabetical list of file names +is generated to replace the word. +Each such file name is a separate argument. +.RE +.RE +.LP +The evaluations just described also occur +in the list of words associated with a \fBfor\fP +loop. +Only substitution occurs +in the \fIword\fP used +for a \fBcase\fP branch. +.LP +As well as the quoting mechanisms described +earlier using \fB\\\fR and \fB\'\*(ZZ\'\fR +a third quoting mechanism is provided using double quotes. +Within double quotes parameter and command substitution +occurs but file name generation and the interpretation +of blanks does not. +The following characters +have a special meaning within double quotes +and may be quoted using \fB\\\|.\fP +.DS + \fB$ \fPparameter substitution + \fB$()\fP command substitution + \fB\`\fP command substitution + \fB"\fP ends the quoted string + \fB\e\fP quotes the special characters \fB$ \` " \e\fP +.DE +For example, +.DS + echo "$x" +.DE +will pass the value of the variable \fBx\fP as a +single argument to \fIecho.\fP +Similarly, +.DS + echo "$\*(ST" +.DE +will pass the positional parameters as a single +argument and is equivalent to +.DS + echo "$1 $2 \*(ZZ" +.DE +The notation \fB$@\fP +is the same as \fB$\*(ST\fR +except when it is quoted. +.DS + echo "$@" +.DE +will pass the positional parameters, unevaluated, to \fIecho\fR +and is equivalent to +.DS + echo "$1" "$2" \*(ZZ +.DE +.LP +The following table gives, for each quoting mechanism, +the shell metacharacters that are evaluated. +.DS +.ce +.ft I +metacharacter +.ft +.in 1.5i + \e $ * \` " \' +\' n n n n n t +\` y n n t n n +" y y n y t n + + t terminator + y interpreted + n not interpreted + +.in +.ft B +.ce +Figure 2. Quoting mechanisms +.ft +.DE +.LP +In cases where more than one evaluation of a string +is required the built-in command \fIeval\fP +may be used. +For example, +if the variable \fBX\fP has the value +\fI$y\fP, and if \fBy\fP has the value \fIpqr\fP +then +.DS + eval echo $X +.DE +will echo the string \fIpqr\|.\fP +.LP +In general the \fIeval\fP command +evaluates its arguments (as do all commands) +and treats the result as input to the shell. +The input is read and the resulting command(s) +executed. +For example, +.DS + wg=\'eval who\*(VTgrep\' + $wg fred +.DE +is equivalent to +.DS + who\*(VTgrep fred +.DE +In this example, +\fIeval\fP is required +since there is no interpretation +of metacharacters, such as \fB\*(VT\|\fR, following +substitution. +.SH +3.6\ Error\ handling +.LP +The treatment of errors detected by +the shell depends on the type of error +and on whether the shell is being +used interactively. +An interactive shell is one whose +input and output are connected +to a terminal. +.\" Removed by Perry Metzger, obsolete and excess detail +.\" +.\" (as determined by +.\" \fIgtty\fP (2)). +A shell invoked with the \fB\(mii\fP +flag is also interactive. +.LP +Execution of a command (see also 3.7) may fail +for any of the following reasons. +.IP \(bu +Input output redirection may fail. +For example, if a file does not exist +or cannot be created. +.IP \(bu +The command itself does not exist +or cannot be executed. +.IP \(bu +The command terminates abnormally, +for example, with a "bus error" +or "memory fault". +See Figure 2 below for a complete list +of UNIX signals. +.IP \(bu +The command terminates normally +but returns a non-zero exit status. +.LP +In all of these cases the shell +will go on to execute the next command. +Except for the last case an error +message will be printed by the shell. +All remaining errors cause the shell +to exit from a script. +An interactive shell will return +to read another command from the terminal. +Such errors include the following. +.IP \(bu +Syntax errors. +e.g., if \*(ZZ then \*(ZZ done +.IP \(bu +A signal such as interrupt. +The shell waits for the current +command, if any, to finish execution and +then either exits or returns to the terminal. +.IP \(bu +Failure of any of the built-in commands +such as \fIcd.\fP +.LP +The shell flag \fB\(mie\fP +causes the shell to terminate +if any error is detected. +.DS +1 hangup +2 interrupt +3* quit +4* illegal instruction +5* trace trap +6* IOT instruction +7* EMT instruction +8* floating point exception +9 kill (cannot be caught or ignored) +10* bus error +11* segmentation violation +12* bad argument to system call +13 write on a pipe with no one to read it +14 alarm clock +15 software termination (from \fIkill\fP (1)) + +.DE +.ft B +.ce +Figure 3. UNIX signals\(dg +.ft +.FS +\(dg Additional signals have been added in modern Unix. +See \fIsigvec\fP(2) or \fIsignal\fP(3) for an up-to-date list. +.FE +Those signals marked with an asterisk +produce a core dump +if not caught. +However, +the shell itself ignores quit which is the only +external signal that can cause a dump. +The signals in this list of potential interest +to shell programs are 1, 2, 3, 14 and 15. +.SH +3.7\ Fault\ handling +.LP +shell scripts normally terminate +when an interrupt is received from the +terminal. +The \fItrap\fP command is used +if some cleaning up is required, such +as removing temporary files. +For example, +.DS + trap\ \'rm\ /tmp/ps$$; exit\'\ 2 +.DE +sets a trap for signal 2 (terminal +interrupt), and if this signal is received +will execute the commands +.DS + rm /tmp/ps$$; exit +.DE +\fIexit\fP is +another built-in command +that terminates execution of a shell script. +The \fIexit\fP is required; otherwise, +after the trap has been taken, +the shell will resume executing +the script +at the place where it was interrupted. +.LP +UNIX signals can be handled in one of three ways. +They can be ignored, in which case +the signal is never sent to the process. +They can be caught, in which case the process +must decide what action to take when the +signal is received. +Lastly, they can be left to cause +termination of the process without +it having to take any further action. +If a signal is being ignored +on entry to the shell script, for example, +by invoking it in the background (see 3.7) then \fItrap\fP +commands (and the signal) are ignored. +.LP +The use of \fItrap\fP is illustrated +by this modified version of the \fItouch\fP +command (Figure 4). +The cleanup action is to remove the file \fBjunk$$\fR\|. +.DS + #!/bin/sh + + flag= + trap\ \'rm\ \(mif\ junk$$;\ exit\'\ 1 2 3 15 + for i + do\ case\ $i\ in + \*(DC\(mic) flag=N ;; + \*(DC\*(ST) if\ test\ \(mif\ $i + \*(DC then cp\ $i\ junk$$;\ mv\ junk$$ $i + \*(DC elif\ test\ $flag + \*(DC then echo\ file\ \\'$i\\'\ does\ not\ exist + \*(DC else >$i + \*(DC fi + \*(DOesac + done +.DE +.sp +.ft B +.ce +Figure 4. The touch command +.ft +.sp +The \fItrap\fP command +appears before the creation +of the temporary file; +otherwise it would be +possible for the process +to die without removing +the file. +.LP +Since there is no signal 0 in UNIX +it is used by the shell to indicate the +commands to be executed on exit from the +shell script. +.LP +A script may, itself, elect to +ignore signals by specifying the null +string as the argument to trap. +The following fragment is taken from the +\fInohup\fP command. +.DS + trap \'\' 1 2 3 15 +.DE +which causes \fIhangup, interrupt, quit \fRand\fI kill\fR +to be ignored both by the +script and by invoked commands. +.LP +Traps may be reset by saying +.DS + trap 2 3 +.DE +which resets the traps for signals 2 and 3 to their default values. +A list of the current values of traps may be obtained +by writing +.DS + trap +.DE +.LP +The script \fIscan\fP (Figure 5) is an example +of the use of \fItrap\fP where there is no exit +in the trap command. +\fIscan\fP takes each directory +in the current directory, prompts +with its name, and then executes +commands typed at the terminal +until an end of file or an interrupt is received. +Interrupts are ignored while executing +the requested commands but cause +termination when \fIscan\fP is +waiting for input. +.DS + d=\`pwd\` + for\ i\ in\ \*(ST + do\ if\ test\ \(mid\ $d/$i + \*(DOthen\ cd\ $d/$i + \*(DO\*(THwhile\ echo\ "$i:" + \*(DO\*(TH\*(WHtrap\ exit\ 2 + \*(DO\*(TH\*(WHread\ x + \*(DO\*(THdo\ trap\ :\ 2;\ eval\ $x;\ done + \*(DOfi + done +.DE +.sp +.ft B +.ce +Figure 5. The scan command +.ft +.sp +\fIread x\fR is a built-in command that reads one line from the +standard input +and places the result in the variable \fBx\|.\fP +It returns a non-zero exit status if either +an end-of-file is read or an interrupt +is received. +.SH +3.8\ Command\ execution +.LP +To run a command (other than a built-in) the shell first creates +a new process using the system call \fIfork.\fP +The execution environment for the command +includes input, output and the states of signals, and +is established in the child process +before the command is executed. +The built-in command \fIexec\fP +is used in the rare cases when no fork +is required +and simply replaces the shell with a new command. +For example, a simple version of the \fInohup\fP +command looks like +.DS + trap \\'\\' 1 2 3 15 + exec $\*(ST +.DE +The \fItrap\fP turns off the signals specified +so that they are ignored by subsequently created commands +and \fIexec\fP replaces the shell by the command +specified. +.LP +Most forms of input output redirection have already been +described. +In the following \fIword\fP is only subject +to parameter and command substitution. +No file name generation or blank interpretation +takes place so that, for example, +.DS + echo \*(ZZ >\*(ST.c +.DE +will write its output into a file whose name is \fB\*(ST.c\|.\fP +Input output specifications are evaluated left to right +as they appear in the command. +.IP >\ \fIword\fP 12 +The standard output (file descriptor 1) +is sent to the file \fIword\fP which is +created if it does not already exist. +.IP \*(AP\ \fIword\fP 12 +The standard output is sent to file \fIword.\fP +If the file exists then output is appended +(by seeking to the end); +otherwise the file is created. +.IP <\ \fIword\fP 12 +The standard input (file descriptor 0) +is taken from the file \fIword.\fP +.IP \*(HE\ \fIword\fP 12 +The standard input is taken from the lines +of shell input that follow up to but not +including a line consisting only of \fIword.\fP +If \fIword\fP is quoted then no interpretation +of the document occurs. +If \fIword\fP is not quoted +then parameter and command substitution +occur and \fB\\\fP is used to quote +the characters \fB\\\fP \fB$\fP \fB\`\fP and the first character +of \fIword.\fP +In the latter case \fB\\newline\fP is ignored (c.f. quoted strings). +.IP >&\ \fIdigit\fP 12 +The file descriptor \fIdigit\fP is duplicated using the system +call \fIdup\fP (2) +and the result is used as the standard output. +.IP <&\ \fIdigit\fP 12 +The standard input is duplicated from file descriptor \fIdigit.\fP +.IP <&\(mi 12 +The standard input is closed. +.IP >&\(mi 12 +The standard output is closed. +.LP +Any of the above may be preceded by a digit in which +case the file descriptor created is that specified by the digit +instead of the default 0 or 1. +For example, +.DS + \*(ZZ 2>file +.DE +runs a command with message output (file descriptor 2) +directed to \fIfile.\fP +.DS + \*(ZZ 2>&1 +.DE +runs a command with its standard output and message output +merged. +(Strictly speaking file descriptor 2 is created +by duplicating file descriptor 1 but the effect is usually to +merge the two streams.) +.\" Removed by Perry Metzger, most of this is now obsolete +.\" +.\" .LP +.\" The environment for a command run in the background such as +.\" .DS +.\" list \*(ST.c \*(VT lpr & +.\" .DE +.\" is modified in two ways. +.\" Firstly, the default standard input +.\" for such a command is the empty file \fB/dev/null\|.\fR +.\" This prevents two processes (the shell and the command), +.\" which are running in parallel, from trying to +.\" read the same input. +.\" Chaos would ensue +.\" if this were not the case. +.\" For example, +.\" .DS +.\" ed file & +.\" .DE +.\" would allow both the editor and the shell +.\" to read from the same input at the same time. +.\" .LP +.\" The other modification to the environment of a background +.\" command is to turn off the QUIT and INTERRUPT signals +.\" so that they are ignored by the command. +.\" This allows these signals to be used +.\" at the terminal without causing background +.\" commands to terminate. +.\" For this reason the UNIX convention +.\" for a signal is that if it is set to 1 +.\" (ignored) then it is never changed +.\" even for a short time. +.\" Note that the shell command \fItrap\fP +.\" has no effect for an ignored signal. +.SH +3.9\ Invoking\ the\ shell +.LP +The following flags are interpreted by the shell +when it is invoked. +If the first character of argument zero is a minus, +then commands are read from the file \fB.profile\|.\fP +.IP \fB\(mic\fP\ \fIstring\fP +.br +If the \fB\(mic\fP flag is present then +commands are read from \fIstring\|.\fP +.IP \fB\(mis\fP +If the \fB\(mis\fP flag is present or if no +arguments remain +then commands are read from the standard input. +Shell output is written to +file descriptor 2. +.IP \fB\(mii\fP +If the \fB\(mii\fP flag is present or +if the shell input and output are attached to a terminal (as told by \fIgtty\fP) +then this shell is \fIinteractive.\fP +In this case TERMINATE is ignored (so that \fBkill 0\fP +does not kill an interactive shell) and INTERRUPT is caught and ignored +(so that \fBwait\fP is interruptable). +In all cases QUIT is ignored by the shell. +.SH +3.10\ Job\ Control +.LP +When a command or pipeline (also known as a \fIjob\fP) is running in +the foreground, entering the stop character (typically +\s-1CONTROL-Z\s0 but user settable with the \fIstty\fP(1) command) +will usually cause the job to stop. +.LP +The jobs associated with the current shell may be listed by entering +the \fIjobs\fP command. +Each job has an associated \fIjob number\fP. +Jobs that are stopped may be continued by entering +.DS + bg %\fIjobnumber\fP +.DE +and jobs may be moved to the foreground by entering +.DS + fg %\fIjobnumber\fP +.DE +If there is a sole job with a particular name (say only one instance +of \fIcc\fP running), \fIfg\fP and \fIbg\fP may also use name of the +command in place of the number, as in: +.DS + bg %cc +.DE +If no `\fB%\fP' clause is entered, most recently stopped job +(indicated with a `+' by the \fIjobs\fP command) will be assumed. +See the manual page for the shell for more details. +.SH +3.11\ Aliases +.LP +The \fIalias\fP command creates a so-called shell alias, which is an +abbreviation that macro-expands at run time into some other command. +For example: +.DS + alias ls="ls -F" +.DE +would cause the command sequence \fBls -F\fP to be executed whenever +the user types \fBls\fP into the shell. +Note that if the user types \fBls -a\fP, the shell will in fact +execute \fBls -F -a\fP. +The command \fBalias\fP on its own prints out all current aliases. +The \fIunalias\fP command, as in: +.DS + unalias ls +.DE +will remove an existing alias. +Aliases can shadow pre-existing commands, as in the example above. +They are typically used to override the interactive behavior of +commands in small ways, for example to always invoke some program with +a favorite option, and are almost never found in scripts. +.SH +3.12\ Command\ Line\ Editing\ and\ Recall +.LP +When working interactively with the shell, it is often tedious to +retype previously entered commands, especially if they are complicated. +The shell therefore maintains a so-called \fIhistory\fP, which is +stored in the file specified by the \fB\s-1HISTFILE\s0\fP environment +variable if it is set. +Users may view, edit, and re-enter previous lines of input using +a small subset of the commands of the \fIvi\fP(1) or +\fIemacs\fP(1)\(dg editors. +.FS +Technically, vi command editing is standardized by POSIX while emacs +is not. +However, all modern shells support both styles. +.FE +Emacs style editing may be selected by entering +.DS + set -o emacs +.DE +and vi style editing may be selected with +.DS + set -o vi +.DE +The details of how command line editing works are beyond the scope of +this document. +See the shell manual page for details. +.SH +Acknowledgements +.LP +The design of the shell is +based in part on the original UNIX shell +.[ +unix command language thompson +.] +and the PWB/UNIX shell, +.[ +pwb shell mashey unix +.] +some +features having been taken from both. +Similarities also exist with the +command interpreters +of the Cambridge Multiple Access System +.[ +cambridge multiple access system hartley +.] +and of CTSS. +.[ +ctss +.] +.LP +I would like to thank Dennis Ritchie +and John Mashey for many +discussions during the design of the shell. +I am also grateful to the members of the Computing Science Research Center +and to Joe Maranzano for their +comments on drafts of this document. +.SH +.[ +$LIST$ +.] diff --git a/bin/sh/USD.doc/t4 b/bin/sh/USD.doc/t4 new file mode 100644 index 0000000..7719d6c --- /dev/null +++ b/bin/sh/USD.doc/t4 @@ -0,0 +1,180 @@ +.\" $NetBSD: t4,v 1.3 2010/08/22 02:19:07 perry Exp $ +.\" +.\" Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" Redistributions of source code and documentation must retain the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer. +.\" +.\" 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. +.\" +.\" All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" +.\" This product includes software developed or owned by Caldera +.\" International, Inc. Neither the name of Caldera International, Inc. +.\" nor the names of other contributors may be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA +.\" INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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) RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" @(#)t4 8.1 (Berkeley) 8/14/93 +.\" +.bp +.SH +Appendix\ A\ -\ Grammar +.LP +Note: This grammar needs updating, it is obsolete. +.LP +.LD +\fIitem: word + input-output + name = value +.sp 0.7 +simple-command: item + simple-command item +.sp 0.7 +command: simple-command + \fB( \fIcommand-list \fB) + \fB{ \fIcommand-list \fB} + \fBfor \fIname \fBdo \fIcommand-list \fBdone + \fBfor \fIname \fBin \fIword \*(ZZ \fBdo \fIcommand-list \fBdone + \fBwhile \fIcommand-list \fBdo \fIcommand-list \fBdone + \fBuntil \fIcommand-list \fBdo \fIcommand-list \fBdone + \fBcase \fIword \fBin \fIcase-part \*(ZZ \fBesac + \fBif \fIcommand-list \fBthen \fIcommand-list \fIelse-part \fBfi +.sp 0.7 +\fIpipeline: command + pipeline \fB\*(VT\fI command +.sp 0.7 +andor: pipeline + andor \fB&&\fI pipeline + andor \fB\*(VT\*(VT\fI pipeline +.sp 0.7 +command-list: andor + command-list \fB;\fI + command-list \fB&\fI + command-list \fB;\fI andor + command-list \fB&\fI andor +.sp 0.7 +input-output: \fB> \fIfile + \fB< \fIfile + \fB\*(AP \fIword + \fB\*(HE \fIword +.sp 0.7 +file: word + \fB&\fI digit + \fB&\fI \(mi +.sp 0.7 +case-part: pattern\fB ) \fIcommand-list\fB ;; +.sp 0.7 +\fIpattern: word + pattern \fB\*(VT\fI word +.sp 0.7 +\fIelse-part: \fBelif \fIcommand-list\fB then\fI command-list else-part\fP + \fBelse \fIcommand-list\fI + empty +.sp 0.7 +empty: +.sp 0.7 +word: \fRa sequence of non-blank characters\fI +.sp 0.7 +name: \fRa sequence of letters, digits or underscores starting with a letter\fI +.sp 0.7 +digit: \fB0 1 2 3 4 5 6 7 8 9\fP +.DE +.LP +.bp +.SH +Appendix\ B\ -\ Meta-characters\ and\ Reserved\ Words +.LP +a) syntactic +.RS +.IP \fB\*(VT\fR 6 +pipe symbol +.IP \fB&&\fR 6 +`andf' symbol +.IP \fB\*(VT\*(VT\fR 6 +`orf' symbol +.IP \fB;\fP 8 +command separator +.IP \fB;;\fP 8 +case delimiter +.IP \fB&\fP 8 +background commands +.IP \fB(\ )\fP 8 +command grouping +.IP \fB<\fP 8 +input redirection +.IP \fB\*(HE\fP 8 +input from a here document +.IP \fB>\fP 8 +output creation +.IP \fB\*(AP\fP 8 +output append +.sp 2 +.RE +.LP +b) patterns +.RS +.IP \fB\*(ST\fP 8 +match any character(s) including none +.IP \fB?\fP 8 +match any single character +.IP \fB[...]\fP 8 +match any of the enclosed characters +.sp 2 +.RE +.LP +c) substitution +.RS +.IP \fB${...}\fP 8 +substitute shell variable +.IP \fB$(...)\fP 8 +substitute command output +.IP \fB\`...\`\fP 8 +substitute command output +.IP \fB$((...))\fP 8 +substitute arithmetic expression +.sp 2 +.RE +.LP +d) quoting +.RS +.IP \fB\e\fP 8 +quote the next character +.IP \fB\'...\'\fP 8 +quote the enclosed characters except for \' +.IP \fB"\&..."\fP 8 +quote the enclosed characters except +for \fB$ \` \e "\fP +.sp 2 +.RE +.LP +e) reserved words +.DS +.ft B +if then else elif fi +case in esac +for while until do done +! { } +.ft +.DE diff --git a/bin/sh/alias.c b/bin/sh/alias.c new file mode 100644 index 0000000..2848b52 --- /dev/null +++ b/bin/sh/alias.c @@ -0,0 +1,314 @@ +/* $NetBSD: alias.c,v 1.20 2018/12/03 06:40:26 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: alias.c,v 1.20 2018/12/03 06:40:26 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdlib.h> +#include "shell.h" +#include "input.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" /* XXX for argptr (should remove?) */ +#include "builtins.h" +#include "var.h" + +#define ATABSIZE 39 + +struct alias *atab[ATABSIZE]; + +STATIC void setalias(char *, char *); +STATIC int by_name(const void *, const void *); +STATIC void list_aliases(void); +STATIC int unalias(char *); +STATIC struct alias **freealias(struct alias **, int); +STATIC struct alias **hashalias(const char *); +STATIC size_t countaliases(void); + +STATIC +void +setalias(char *name, char *val) +{ + struct alias *ap, **app; + + (void) unalias(name); /* old one (if any) is now gone */ + app = hashalias(name); + + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->flag = 0; + ap->val = savestr(val); + ap->next = *app; + *app = ap; + INTON; +} + +STATIC struct alias ** +freealias(struct alias **app, int force) +{ + struct alias *ap = *app; + + if (ap == NULL) + return app; + + /* + * if the alias is currently in use (i.e. its + * buffer is being used by the input routine) we + * just null out the name instead of discarding it. + * If we encounter it later, when it is idle, + * we will finish freeing it then. + * + * Unless we want to simply free everything (INIT) + */ + if (ap->flag & ALIASINUSE && !force) { + *ap->name = '\0'; + return &ap->next; + } + + INTOFF; + *app = ap->next; + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); + INTON; + + return app; +} + +STATIC int +unalias(char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + while ((ap = *app) != NULL) { + if (equal(name, ap->name)) { + (void) freealias(app, 0); + return 0; + } + app = &ap->next; + } + + return 1; +} + +#ifdef mkinit +MKINIT void rmaliases(int); + +SHELLPROC { + rmaliases(1); +} +#endif + +void +rmaliases(int force) +{ + struct alias **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + while (*app) + app = freealias(app, force); + } + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap = *hashalias(name); + + while (ap != NULL) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return NULL; + return ap; + } + ap = ap->next; + } + + return NULL; +} + +const char * +alias_text(void *dummy __unused, const char *name) +{ + struct alias *ap; + + ap = lookupalias(name, 0); + if (ap == NULL) + return NULL; + return ap->val; +} + +STATIC int +by_name(const void *a, const void *b) +{ + + return strcmp( + (*(const struct alias * const *)a)->name, + (*(const struct alias * const *)b)->name); +} + +STATIC void +list_aliases(void) +{ + size_t i, j, n; + const struct alias **aliases; + const struct alias *ap; + + INTOFF; + n = countaliases(); + aliases = ckmalloc(n * sizeof aliases[0]); + + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap != NULL; ap = ap->next) + if (ap->name[0] != '\0') + aliases[j++] = ap; + if (j != n) + error("Alias count botch"); + INTON; + + qsort(aliases, n, sizeof aliases[0], by_name); + + for (i = 0; i < n; i++) { + out1fmt("alias %s=", aliases[i]->name); + print_quoted(aliases[i]->val); + out1c('\n'); + } + + ckfree(aliases); +} + +/* + * Count how many aliases are defined (skipping any + * that have been deleted, but don't know it yet). + * Use this opportunity to clean up any of those + * zombies that are no longer needed. + */ +STATIC size_t +countaliases(void) +{ + struct alias *ap, **app; + size_t n; + int i; + + n = 0; + for (i = 0; i < ATABSIZE; i++) + for (app = &atab[i]; (ap = *app) != NULL;) { + if (ap->name[0] != '\0') + n++; + else { + app = freealias(app, 0); + continue; + } + app = &ap->next; + } + + return n; +} + +int +aliascmd(int argc, char **argv) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + if (argc == 1) { + list_aliases(); + return 0; + } + + while ((n = *++argv) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + outfmt(out2, "alias: %s not found\n", n); + ret = 1; + } else { + out1fmt("alias %s=", n); + print_quoted(ap->val); + out1c('\n'); + } + } else { + *v++ = '\0'; + setalias(n, v); + } + } + + return ret; +} + +int +unaliascmd(int argc, char **argv) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(0); + return 0; + } + } + + (void)countaliases(); /* delete any dead ones */ + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return i; +} + +STATIC struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = *(const unsigned char *)p << 4; + while (*p) + hashval += *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/bin/sh/alias.h b/bin/sh/alias.h new file mode 100644 index 0000000..390df6d --- /dev/null +++ b/bin/sh/alias.h @@ -0,0 +1,48 @@ +/* $NetBSD: alias.h,v 1.9 2018/12/03 06:40:26 kre Exp $ */ + +/*- + * 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. + * + * @(#)alias.h 8.2 (Berkeley) 5/4/95 + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); +const char *alias_text(void *, const char *); +void rmaliases(int); diff --git a/bin/sh/arith_token.c b/bin/sh/arith_token.c new file mode 100644 index 0000000..cd91857 --- /dev/null +++ b/bin/sh/arith_token.c @@ -0,0 +1,262 @@ +/* $NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $ */ + +/*- + * Copyright (c) 2002 + * Herbert Xu. + * 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. + * + * From FreeBSD, from dash + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $"); +#endif /* not lint */ + +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "shell.h" +#include "arith_tokens.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" +#include "show.h" + +#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \ + ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +/* + * Scan next arithmetic token, return its type, + * leave its value (when applicable) in (global) a_t_val. + * + * Input text is in (global) arith_buf which is updated to + * refer to the next char after the token returned, except + * on error (ARITH_BAD returned) where arith_buf is not altered. + */ +int +arith_token(void) +{ + int token; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + token = *buf; + + if (isdigit(token)) { + /* + * Numbers all start with a digit, and nothing + * else does, the number ends wherever + * strtoimax() stops... + */ + a_t_val.val = strtoimax(buf, &end, 0); + if (is_in_name(*end)) { + token = *end; + while (is_in_name(*++end)) + continue; + error("arithmetic: unexpected '%c' " + "(out of range) in numeric constant: " + "%.*s", token, (int)(end - buf), buf); + } + arith_buf = end; + VTRACE(DBG_ARITH, ("Arith token ARITH_NUM=%jd\n", + a_t_val.val)); + return ARITH_NUM; + + } else if (is_name(token)) { + /* + * Variable names all start with an alpha (or '_') + * and nothing else does. They continue for the + * longest unbroken sequence of alphanumerics ( + _ ) + */ + arith_var_lno = arith_lno; + p = buf; + while (buf++, is_in_name(*buf)) + ; + a_t_val.name = stalloc(buf - p + 1); + memcpy(a_t_val.name, p, buf - p); + a_t_val.name[buf - p] = '\0'; + arith_buf = buf; + VTRACE(DBG_ARITH, ("Arith token ARITH_VAR=\"%s\"\n", + a_t_val.name)); + return ARITH_VAR; + + } else switch (token) { + /* + * everything else must be some kind of + * operator, white space, or an error. + */ + case '\n': + arith_lno++; + VTRACE(DBG_ARITH, ("Arith: newline\n")); + /* FALLTHROUGH */ + case ' ': + case '\t': + buf++; + continue; + + default: + error("arithmetic: unexpected '%c' (%#x) in expression", + token, token); + /* NOTREACHED */ + + case '=': + token = ARITH_ASS; + checkeq: + buf++; + checkeqcur: + if (*buf != '=') + goto out; + token += ARITH_ASS_GAP; + break; + + case '>': + switch (*++buf) { + case '=': + token = ARITH_GE; + break; + case '>': + token = ARITH_RSHIFT; + goto checkeq; + default: + token = ARITH_GT; + goto out; + } + break; + + case '<': + switch (*++buf) { + case '=': + token = ARITH_LE; + break; + case '<': + token = ARITH_LSHIFT; + goto checkeq; + default: + token = ARITH_LT; + goto out; + } + break; + + case '|': + if (*++buf != '|') { + token = ARITH_BOR; + goto checkeqcur; + } + token = ARITH_OR; + break; + + case '&': + if (*++buf != '&') { + token = ARITH_BAND; + goto checkeqcur; + } + token = ARITH_AND; + break; + + case '!': + if (*++buf != '=') { + token = ARITH_NOT; + goto out; + } + token = ARITH_NE; + break; + + case 0: + goto out; + + case '(': + token = ARITH_LPAREN; + break; + case ')': + token = ARITH_RPAREN; + break; + + case '*': + token = ARITH_MUL; + goto checkeq; + case '/': + token = ARITH_DIV; + goto checkeq; + case '%': + token = ARITH_REM; + goto checkeq; + + case '+': + if (buf[1] == '+') { + buf++; + token = ARITH_INCR; + break; + } + token = ARITH_ADD; + goto checkeq; + case '-': + if (buf[1] == '-') { + buf++; + token = ARITH_DECR; + break; + } + token = ARITH_SUB; + goto checkeq; + case '~': + token = ARITH_BNOT; + break; + case '^': + token = ARITH_BXOR; + goto checkeq; + + case '?': + token = ARITH_QMARK; + break; + case ':': + token = ARITH_COLON; + break; + case ',': + token = ARITH_COMMA; + break; + } + break; + } + buf++; + out: + arith_buf = buf; + VTRACE(DBG_ARITH, ("Arith token: %d\n", token)); + return token; +} diff --git a/bin/sh/arith_tokens.h b/bin/sh/arith_tokens.h new file mode 100644 index 0000000..f655aa9 --- /dev/null +++ b/bin/sh/arith_tokens.h @@ -0,0 +1,120 @@ +/* $NetBSD: arith_tokens.h,v 1.3 2017/07/24 13:21:14 kre Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. 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. + * + * From FreeBSD who obtained it from dash (modified both times.) + */ + +/* + * Tokens returned from arith_token() + * + * Caution, values are not arbitrary. + */ + +#define ARITH_BAD 0 + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#define ARITH_NUM 5 +#define ARITH_VAR 6 +#define ARITH_NOT 7 + +#define ARITH_BINOP_MIN 8 + +#define ARITH_LE 8 +#define ARITH_GE 9 +#define ARITH_LT 10 +#define ARITH_GT 11 +#define ARITH_EQ 12 /* Must be ARITH_ASS + ARITH_ASS_GAP */ + +#define ARITH_ASS_BASE 13 + +#define ARITH_REM 13 +#define ARITH_BAND 14 +#define ARITH_LSHIFT 15 +#define ARITH_RSHIFT 16 +#define ARITH_MUL 17 +#define ARITH_ADD 18 +#define ARITH_BOR 19 +#define ARITH_SUB 20 +#define ARITH_BXOR 21 +#define ARITH_DIV 22 + +#define ARITH_NE 23 + +#define ARITH_BINOP_MAX 24 + +#define ARITH_ASS_MIN ARITH_BINOP_MAX +#define ARITH_ASS_GAP (ARITH_ASS_MIN - ARITH_ASS_BASE) + +#define ARITH_REMASS (ARITH_ASS_GAP + ARITH_REM) +#define ARITH_BANDASS (ARITH_ASS_GAP + ARITH_BAND) +#define ARITH_LSHIFTASS (ARITH_ASS_GAP + ARITH_LSHIFT) +#define ARITH_RSHIFTASS (ARITH_ASS_GAP + ARITH_RSHIFT) +#define ARITH_MULASS (ARITH_ASS_GAP + ARITH_MUL) +#define ARITH_ADDASS (ARITH_ASS_GAP + ARITH_ADD) +#define ARITH_BORASS (ARITH_ASS_GAP + ARITH_BOR) +#define ARITH_SUBASS (ARITH_ASS_GAP + ARITH_SUB) +#define ARITH_BXORASS (ARITH_ASS_GAP + ARITH_BXOR) +#define ARITH_DIVASS (ARITH_ASS_GAP + ARITH_BXOR) + +#define ARITH_ASS_MAX 34 + +#define ARITH_LPAREN 34 +#define ARITH_RPAREN 35 +#define ARITH_BNOT 36 +#define ARITH_QMARK 37 +#define ARITH_COLON 38 +#define ARITH_INCR 39 +#define ARITH_DECR 40 +#define ARITH_COMMA 41 + +/* + * Globals shared between arith parser, and lexer + */ + +extern const char *arith_buf; + +union a_token_val { + intmax_t val; + char *name; +}; + +extern union a_token_val a_t_val; + +extern int arith_lno, arith_var_lno; + +int arith_token(void); diff --git a/bin/sh/arithmetic.c b/bin/sh/arithmetic.c new file mode 100644 index 0000000..f9c91a4 --- /dev/null +++ b/bin/sh/arithmetic.c @@ -0,0 +1,502 @@ +/* $NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. 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. + * + * From FreeBSD, who obtained it from dash, modified both times... + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $"); +#endif /* not lint */ + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "shell.h" +#include "arithmetic.h" +#include "arith_tokens.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" +#include "show.h" +#include "syntax.h" + +#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \ + ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union a_token_val a_t_val; + +static int last_token; + +int arith_lno, arith_var_lno; + +#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec + +static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { + ARITH_PRECEDENCE(ARITH_MUL, 0), + ARITH_PRECEDENCE(ARITH_DIV, 0), + ARITH_PRECEDENCE(ARITH_REM, 0), + ARITH_PRECEDENCE(ARITH_ADD, 1), + ARITH_PRECEDENCE(ARITH_SUB, 1), + ARITH_PRECEDENCE(ARITH_LSHIFT, 2), + ARITH_PRECEDENCE(ARITH_RSHIFT, 2), + ARITH_PRECEDENCE(ARITH_LT, 3), + ARITH_PRECEDENCE(ARITH_LE, 3), + ARITH_PRECEDENCE(ARITH_GT, 3), + ARITH_PRECEDENCE(ARITH_GE, 3), + ARITH_PRECEDENCE(ARITH_EQ, 4), + ARITH_PRECEDENCE(ARITH_NE, 4), + ARITH_PRECEDENCE(ARITH_BAND, 5), + ARITH_PRECEDENCE(ARITH_BXOR, 6), + ARITH_PRECEDENCE(ARITH_BOR, 7), +}; + +#define ARITH_MAX_PREC 8 + +int expcmd(int, char **); + +static void __dead +arith_err(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static intmax_t +arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + intmax_t result; + const int oln = line_number; + + VTRACE(DBG_ARITH, ("Arith var lookup(\"%s\") with lno=%d\n", varname, + arith_var_lno)); + + line_number = arith_var_lno; + str = lookupvar(varname); + line_number = oln; + + if (uflag && str == NULL) + arith_err("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoimax(str, &p, 0); + if (errno != 0 || *p != '\0') { + if (errno == 0) { + while (*p != '\0' && is_space(*p)) + p++; + if (*p == '\0') + return result; + } + arith_err("variable contains non-numeric value"); + } + return result; +} + +static inline int +arith_prec(int op) +{ + + return prec[op - ARITH_BINOP_MIN]; +} + +static inline int +higher_prec(int op1, int op2) +{ + + return arith_prec(op1) < arith_prec(op2); +} + +static intmax_t +do_binop(int op, intmax_t a, intmax_t b) +{ + + VTRACE(DBG_ARITH, ("Arith do binop %d (%jd, %jd)\n", op, a, b)); + switch (op) { + default: + arith_err("token error"); + case ARITH_REM: + case ARITH_DIV: + if (b == 0) + arith_err("division by zero"); + if (a == INTMAX_MIN && b == -1) + arith_err("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_LT: + return a < b; + case ARITH_LE: + return a <= b; + case ARITH_GT: + return a > b; + case ARITH_GE: + return a >= b; + case ARITH_EQ: + return a == b; + case ARITH_NE: + return a != b; + case ARITH_BAND: + return a & b; + case ARITH_BXOR: + return a ^ b; + case ARITH_BOR: + return a | b; + } +} + +static intmax_t assignment(int, int); +static intmax_t comma_list(int, int); + +static intmax_t +primary(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t result; + char sresult[DIGITS(result) + 1]; + + VTRACE(DBG_ARITH, ("Arith primary: token %d op %d%s\n", + token, op, noeval ? " noeval" : "")); + + switch (token) { + case ARITH_LPAREN: + result = comma_list(op, noeval); + if (last_token != ARITH_RPAREN) + arith_err("expecting ')'"); + last_token = arith_token(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + result = noeval ? val->val : arith_lookupvarint(val->name); + if (op == ARITH_INCR || op == ARITH_DECR) { + last_token = arith_token(); + if (noeval) + return val->val; + + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, + result + (op == ARITH_INCR ? 1 : -1)); + setvar(val->name, sresult, 0); + } else + last_token = op; + return result; + case ARITH_ADD: + *val = a_t_val; + return primary(op, val, arith_token(), noeval); + case ARITH_SUB: + *val = a_t_val; + return -primary(op, val, arith_token(), noeval); + case ARITH_NOT: + *val = a_t_val; + return !primary(op, val, arith_token(), noeval); + case ARITH_BNOT: + *val = a_t_val; + return ~primary(op, val, arith_token(), noeval); + case ARITH_INCR: + case ARITH_DECR: + if (op != ARITH_VAR) + arith_err("incr/decr require var name"); + last_token = arith_token(); + if (noeval) + return val->val; + result = arith_lookupvarint(a_t_val.name); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, + result += (token == ARITH_INCR ? 1 : -1)); + setvar(a_t_val.name, sresult, 0); + return result; + default: + arith_err("expecting primary"); + } + return 0; /* never reached */ +} + +static intmax_t +binop2(intmax_t a, int op, int precedence, int noeval) +{ + union a_token_val val; + intmax_t b; + int op2; + int token; + + VTRACE(DBG_ARITH, ("Arith: binop2 %jd op %d (P:%d)%s\n", + a, op, precedence, noeval ? " noeval" : "")); + + for (;;) { + token = arith_token(); + val = a_t_val; + + b = primary(token, &val, arith_token(), noeval); + + op2 = last_token; + if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX && + higher_prec(op2, op)) { + b = binop2(b, op2, arith_prec(op), noeval); + op2 = last_token; + } + + a = noeval ? b : do_binop(op, a, b); + + if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX || + arith_prec(op2) >= precedence) + return a; + + op = op2; + } +} + +static intmax_t +binop(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = primary(token, val, op, noeval); + + op = last_token; + if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX) + return a; + + return binop2(a, op, ARITH_MAX_PREC, noeval); +} + +static intmax_t +and(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = binop(token, val, op, noeval); + intmax_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + VTRACE(DBG_ARITH, ("Arith: AND %jd%s\n", a, noeval ? " noeval" : "")); + + token = arith_token(); + *val = a_t_val; + + b = and(token, val, arith_token(), noeval | !a); + + return a && b; +} + +static intmax_t +or(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = and(token, val, op, noeval); + intmax_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + VTRACE(DBG_ARITH, ("Arith: OR %jd%s\n", a, noeval ? " noeval" : "")); + + token = arith_token(); + *val = a_t_val; + + b = or(token, val, arith_token(), noeval | !!a); + + return a || b; +} + +static intmax_t +cond(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = or(token, val, op, noeval); + intmax_t b; + intmax_t c; + + if (last_token != ARITH_QMARK) + return a; + + VTRACE(DBG_ARITH, ("Arith: ?: %jd%s\n", a, noeval ? " noeval" : "")); + + b = assignment(arith_token(), noeval | !a); + + if (last_token != ARITH_COLON) + arith_err("expecting ':'"); + + token = arith_token(); + *val = a_t_val; + + c = cond(token, val, arith_token(), noeval | !!a); + + return a ? b : c; +} + +static intmax_t +assignment(int var, int noeval) +{ + union a_token_val val = a_t_val; + int op = arith_token(); + intmax_t result; + char sresult[DIGITS(result) + 1]; + + + if (var != ARITH_VAR) + return cond(var, &val, op, noeval); + + if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX)) + return cond(var, &val, op, noeval); + + VTRACE(DBG_ARITH, ("Arith: %s ASSIGN %d%s\n", val.name, op, + noeval ? " noeval" : "")); + + result = assignment(arith_token(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - ARITH_ASS_GAP, + arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +static intmax_t +comma_list(int token, int noeval) +{ + intmax_t result = assignment(token, noeval); + + while (last_token == ARITH_COMMA) { + VTRACE(DBG_ARITH, ("Arith: comma discarding %jd%s\n", result, + noeval ? " noeval" : "")); + result = assignment(arith_token(), noeval); + } + + return result; +} + +intmax_t +arith(const char *s, int lno) +{ + struct stackmark smark; + intmax_t result; + const char *p; + int nls = 0; + + setstackmark(&smark); + + arith_lno = lno; + + CTRACE(DBG_ARITH, ("Arith(\"%s\", %d) @%d\n", s, lno, arith_lno)); + + /* check if it is possible we might reference LINENO */ + p = s; + while ((p = strchr(p, 'L')) != NULL) { + if (p[1] == 'I' && p[2] == 'N') { + /* if it is possible, we need to correct airth_lno */ + p = s; + while ((p = strchr(p, '\n')) != NULL) + nls++, p++; + VTRACE(DBG_ARITH, ("Arith found %d newlines\n", nls)); + arith_lno -= nls; + break; + } + p++; + } + + arith_buf = arith_startbuf = s; + + result = comma_list(arith_token(), 0); + + if (last_token) + arith_err("expecting end of expression"); + + popstackmark(&smark); + + CTRACE(DBG_ARITH, ("Arith result=%jd\n", result)); + + return result; +} + +/* + * The let(1)/exp(1) builtin. + */ +int +expcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + intmax_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p, line_number); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/bin/sh/arithmetic.h b/bin/sh/arithmetic.h new file mode 100644 index 0000000..51808d3 --- /dev/null +++ b/bin/sh/arithmetic.h @@ -0,0 +1,42 @@ +/* $NetBSD: arithmetic.h,v 1.2 2017/06/07 05:08:32 kre Exp $ */ + +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * + * From FreeBSD, from dash + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +#define ARITH_FORMAT_STR "%" PRIdMAX + +intmax_t arith(const char *, int); diff --git a/bin/sh/bltin/bltin.h b/bin/sh/bltin/bltin.h new file mode 100644 index 0000000..b5669f1 --- /dev/null +++ b/bin/sh/bltin/bltin.h @@ -0,0 +1,105 @@ +/* $NetBSD: bltin.h,v 1.15 2017/06/26 22:09:16 kre Exp $ */ + +/*- + * 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. + * + * @(#)bltin.h 8.1 (Berkeley) 5/31/93 + */ + +/* + * This file is included by programs which are optionally built into the + * shell. + * + * We always define SHELL_BUILTIN, to allow other included headers to + * hide some of their symbols if appropriate. + * + * If SHELL is defined, we try to map the standard UNIX library routines + * to ash routines using defines. + */ + +#define SHELL_BUILTIN +#include "../shell.h" +#include "../mystring.h" +#ifdef SHELL +#include "../output.h" +#include "../error.h" +#include "../var.h" +#undef stdout +#undef stderr +#undef putc +#undef putchar +#undef fileno +#undef ferror +#define FILE struct output +#define stdout out1 +#define stderr out2 +#ifdef __GNUC__ +#define _RETURN_INT(x) ({(x); 0;}) /* map from void foo() to int bar() */ +#else +#define _RETURN_INT(x) ((x), 0) /* map from void foo() to int bar() */ +#endif +#define fprintf(...) _RETURN_INT(outfmt(__VA_ARGS__)) +#define printf(...) _RETURN_INT(out1fmt(__VA_ARGS__)) +#define putc(c, file) _RETURN_INT(outc(c, file)) +#define putchar(c) _RETURN_INT(out1c(c)) +#define fputs(...) _RETURN_INT(outstr(__VA_ARGS__)) +#define fflush(f) _RETURN_INT(flushout(f)) +#define fileno(f) ((f)->fd) +#define ferror(f) ((f)->flags & OUTPUT_ERR) +#define INITARGS(argv) +#define err sh_err +#define verr sh_verr +#define errx sh_errx +#define verrx sh_verrx +#define warn sh_warn +#define vwarn sh_vwarn +#define warnx sh_warnx +#define vwarnx sh_vwarnx +#define exit sh_exit +#define setprogname(s) +#define getprogname() commandname +#define setlocate(l,s) 0 + +#define getenv(p) bltinlookup((p),0) + +#else /* ! SHELL */ +#undef NULL +#include <stdio.h> +#undef main +#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else +#endif /* ! SHELL */ + +pointer stalloc(int); + +int echocmd(int, char **); + + +extern const char *commandname; diff --git a/bin/sh/bltin/echo.1 b/bin/sh/bltin/echo.1 new file mode 100644 index 0000000..13b3c6a --- /dev/null +++ b/bin/sh/bltin/echo.1 @@ -0,0 +1,109 @@ +.\" $NetBSD: echo.1,v 1.14 2017/07/03 21:33:24 wiz Exp $ +.\" +.\" 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. +.\" Copyright 1989 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. +.\" +.\" @(#)echo.1 8.1 (Berkeley) 5/31/93 +.\" +.Dd May 31, 1993 +.Dt ECHO 1 +.Os +.Sh NAME +.Nm echo +.Nd produce message in a shell script +.Sh SYNOPSIS +.Nm +.Op Fl n | Fl e +.Ar args ... +.Sh DESCRIPTION +.Nm +prints its arguments on the standard output, separated by spaces. +Unless the +.Fl n +option is present, a newline is output following the arguments. +The +.Fl e +option causes +.Nm +to treat the escape sequences specially, as described in the following +paragraph. +The +.Fl e +option is the default, and is provided solely for compatibility with +other systems. +Only one of the options +.Fl n +and +.Fl e +may be given. +.Pp +If any of the following sequences of characters is encountered during +output, the sequence is not output. Instead, the specified action is +performed: +.Bl -tag -width indent +.It Li \eb +A backspace character is output. +.It Li \ec +Subsequent output is suppressed. This is normally used at the end of the +last argument to suppress the trailing newline that +.Nm +would otherwise output. +.It Li \ef +Output a form feed. +.It Li \en +Output a newline character. +.It Li \er +Output a carriage return. +.It Li \et +Output a (horizontal) tab character. +.It Li \ev +Output a vertical tab. +.It Li \e0 Ns Ar digits +Output the character whose value is given by zero to three digits. +If there are zero digits, a nul character is output. +.It Li \e\e +Output a backslash. +.El +.Sh HINTS +Remember that backslash is special to the shell and needs to be escaped. +To output a message to standard error, say +.Pp +.D1 echo message >&2 +.Sh BUGS +The octal character escape mechanism +.Pq Li \e0 Ns Ar digits +differs from the +C language mechanism. +.Pp +There is no way to force +.Nm +to treat its arguments literally, rather than interpreting them as +options and escape sequences. diff --git a/bin/sh/bltin/echo.c b/bin/sh/bltin/echo.c new file mode 100644 index 0000000..440e01b --- /dev/null +++ b/bin/sh/bltin/echo.c @@ -0,0 +1,122 @@ +/* $NetBSD: echo.c,v 1.14 2008/10/12 01:40:37 dholland Exp $ */ + +/*- + * 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. + * + * @(#)echo.c 8.1 (Berkeley) 5/31/93 + */ + +/* + * Echo command. + * + * echo is steeped in tradition - several of them! + * netbsd has supported 'echo [-n | -e] args' in spite of -e not being + * documented anywhere. + * Posix requires that -n be supported, output from strings containing + * \ is implementation defined + * The Single Unix Spec requires that \ escapes be treated as if -e + * were set, but that -n not be treated as an option. + * (ksh supports 'echo [-eEn] args', but not -- so that it is actually + * impossible to actually output '-n') + * + * It is suggested that 'printf "%b" "string"' be used to get \ sequences + * expanded. printf is now a builtin of netbsd's sh and csh. + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: echo.c,v 1.14 2008/10/12 01:40:37 dholland Exp $"); + +#define main echocmd + +#include "bltin.h" + +int +main(int argc, char **argv) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; + int eflag = 0; + + ap = argv; + if (argc) + ap++; + + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag = 1; + ap++; + } else if (equal(p, "-e")) { + eflag = 1; + ap++; + } + } + + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; /* bell */ + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = 033; break; /* escape */ + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': + c = 0; + count = 3; + while (--count >= 0 && (unsigned)(*p - '0') < 8) + c = (c << 3) + (*p++ - '0'); + break; + default: + /* Output the '/' and char following */ + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + fflush(stdout); + if (ferror(stdout)) + return 1; + return 0; +} diff --git a/bin/sh/builtins.def b/bin/sh/builtins.def new file mode 100644 index 0000000..d702707 --- /dev/null +++ b/bin/sh/builtins.def @@ -0,0 +1,98 @@ +#!/bin/sh - +# $NetBSD: builtins.def,v 1.25 2017/05/15 20:00:36 kre Exp $ +# +# 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. +# +# @(#)builtins.def 8.4 (Berkeley) 5/4/95 + +# +# This file lists all the builtin commands. The first column is the name +# of a C routine. +# The -j flag specifies that this command is to be excluded from systems +# without job control. +# The -h flag specifies that this command is to be excluded from systems +# based on the SMALL compile-time symbol. +# The -s flag specifies that this is a posix 'special builtin' command. +# The -u flag specifies that this is a posix 'standard utility'. +# The rest of the line specifies the command name or names used to run +# the command. + +bltincmd -u command +bgcmd -j -u bg +breakcmd -s break -s continue +cdcmd -u cd chdir +dotcmd -s . +echocmd echo +evalcmd -s eval +execcmd -s exec +exitcmd -s exit +expcmd exp let +exportcmd -s export -s readonly +falsecmd -u false +histcmd -h -u fc +inputrc inputrc +fgcmd -j -u fg +fgcmd_percent -j -u % +getoptscmd -u getopts +hashcmd hash +jobidcmd jobid +jobscmd -u jobs +localcmd local +#ifndef TINY +printfcmd printf +#endif +pwdcmd -u pwd +readcmd -u read +returncmd -s return +setcmd -s set +fdflagscmd fdflags +setvarcmd setvar +shiftcmd -s shift +timescmd -s times +trapcmd -s trap +truecmd -s : -u true +typecmd type +umaskcmd -u umask +unaliascmd -u unalias +unsetcmd -s unset +waitcmd -u wait +aliascmd -u alias +ulimitcmd ulimit +testcmd test [ +killcmd -u kill # mandated by posix for 'kill %job' +wordexpcmd wordexp +#newgrp -u newgrp # optional command in posix + +#ifdef DEBUG +debugcmd debug +#endif + +#exprcmd expr diff --git a/bin/sh/cd.c b/bin/sh/cd.c new file mode 100644 index 0000000..2b1046d --- /dev/null +++ b/bin/sh/cd.c @@ -0,0 +1,463 @@ +/* $NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +/* + * The cd and pwd commands. + */ + +#include "shell.h" +#include "var.h" +#include "nodes.h" /* for jobs.h */ +#include "jobs.h" +#include "options.h" +#include "builtins.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "exec.h" +#include "redir.h" +#include "mystring.h" +#include "show.h" +#include "cd.h" + +STATIC int docd(const char *, int); +STATIC char *getcomponent(void); +STATIC void updatepwd(const char *); +STATIC void find_curdir(int noerror); + +char *curdir = NULL; /* current working directory */ +char *prevdir; /* previous working directory */ +STATIC char *cdcomppath; + +int +cdcmd(int argc, char **argv) +{ + const char *dest; + const char *path, *cp; + char *p; + char *d; + struct stat statb; + int print = cdprint; /* set -o cdprint to enable */ + + while (nextopt("P") != '\0') + ; + + /* + * Try (quite hard) to have 'curdir' defined, nothing has set + * it on entry to the shell, but we want 'cd fred; cd -' to work. + */ + getpwd(1); + dest = *argptr; + if (dest == NULL) { + dest = bltinlookup("HOME", 1); + if (dest == NULL) + error("HOME not set"); + } else if (argptr[1]) { + /* Do 'ksh' style substitution */ + if (!curdir) + error("PWD not set"); + p = strstr(curdir, dest); + if (!p) + error("bad substitution"); + d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1); + memcpy(d, curdir, p - curdir); + strcpy(d + (p - curdir), argptr[1]); + strcat(d, p + strlen(dest)); + dest = d; + print = 1; + } else if (dest[0] == '-' && dest[1] == '\0') { + dest = prevdir ? prevdir : curdir; + print = 1; + } + if (*dest == '\0') + dest = "."; + + cp = dest; + if (*cp == '.' && *++cp == '.') + cp++; + if (*cp == 0 || *cp == '/' || (path = bltinlookup("CDPATH", 1)) == NULL) + path = nullstr; + while ((p = padvance(&path, dest, 0)) != NULL) { + stunalloc(p); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { + int dopr = print; + + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + dopr = strcmp(p + 2, dest); + else + dopr = strcmp(p, dest); + } + if (docd(p, dopr) >= 0) + return 0; + + } + } + error("can't cd to %s", dest); + /* NOTREACHED */ +} + + +/* + * Actually do the chdir. In an interactive shell, print the + * directory name if "print" is nonzero. + */ + +STATIC int +docd(const char *dest, int print) +{ +#if 0 /* no "cd -L" (ever) so all this is just a waste of time ... */ + char *p; + char *q; + char *component; + struct stat statb; + int first; + int badstat; + + CTRACE(DBG_CMDS, ("docd(\"%s\", %d) called\n", dest, print)); + + /* + * Check each component of the path. If we find a symlink or + * something we can't stat, clear curdir to force a getcwd() + * next time we get the value of the current directory. + */ + badstat = 0; + cdcomppath = stalloc(strlen(dest) + 1); + scopy(dest, cdcomppath); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + cdcomppath++; + } + first = 1; + while ((q = getcomponent()) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + while (*q) + STPUTC(*q++, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } +#endif + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return -1; + } + updatepwd(NULL); /* only do cd -P, no "pretend" -L mode */ + INTON; + if (print && iflag == 1 && curdir) + out1fmt("%s\n", curdir); + return 0; +} + + +/* + * Get the next component of the path name pointed to by cdcomppath. + * This routine overwrites the string pointed to by cdcomppath. + */ + +STATIC char * +getcomponent(void) +{ + char *p; + char *start; + + if ((p = cdcomppath) == NULL) + return NULL; + start = cdcomppath; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + cdcomppath = NULL; + } else { + *p++ = '\0'; + cdcomppath = p; + } + return start; +} + + + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. We also call hashcd to let the routines in exec.c know + * that the current directory has changed. + */ + +STATIC void +updatepwd(const char *dir) +{ + char *new; + char *p; + + hashcd(); /* update command hash table */ + + /* + * If our argument is NULL, we don't know the current directory + * any more because we traversed a symbolic link or something + * we couldn't stat(). + */ + if (dir == NULL || curdir == NULL) { + if (prevdir) + ckfree(prevdir); + INTOFF; + prevdir = curdir; + curdir = NULL; + getpwd(1); + INTON; + if (curdir) { + setvar("OLDPWD", prevdir, VEXPORT); + setvar("PWD", curdir, VEXPORT); + } else + unsetvar("PWD", 0); + return; + } + cdcomppath = stalloc(strlen(dir) + 1); + scopy(dir, cdcomppath); + STARTSTACKSTR(new); + if (*dir != '/') { + p = curdir; + while (*p) + STPUTC(*p++, new); + if (p[-1] == '/') + STUNPUTC(new); + } + while ((p = getcomponent()) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + while (*p) + STPUTC(*p++, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + INTOFF; + if (prevdir) + ckfree(prevdir); + prevdir = curdir; + curdir = savestr(stackblock()); + setvar("OLDPWD", prevdir, VEXPORT); + setvar("PWD", curdir, VEXPORT); + INTON; +} + +/* + * Posix says the default should be 'pwd -L' (as below), however + * the 'cd' command (above) does something much nearer to the + * posix 'cd -P' (not the posix default of 'cd -L'). + * If 'cd' is changed to support -P/L then the default here + * needs to be revisited if the historic behaviour is to be kept. + */ + +int +pwdcmd(int argc, char **argv) +{ + int i; + char opt = 'L'; + + while ((i = nextopt("LP")) != '\0') + opt = i; + if (*argptr) + error("unexpected argument"); + + if (opt == 'L') + getpwd(0); + else + find_curdir(0); + + setvar("OLDPWD", prevdir, VEXPORT); + setvar("PWD", curdir, VEXPORT); + out1str(curdir); + out1c('\n'); + return 0; +} + + + +void +initpwd(void) +{ + getpwd(1); + if (curdir) + setvar("PWD", curdir, VEXPORT); + else + sh_warnx("Cannot determine current working directory"); +} + +#define MAXPWD 256 + +/* + * Find out what the current directory is. If we already know the current + * directory, this routine returns immediately. + */ +void +getpwd(int noerror) +{ + char *pwd; + struct stat stdot, stpwd; + static int first = 1; + + if (curdir) + return; + + if (first) { + first = 0; + pwd = getenv("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + curdir = savestr(pwd); + return; + } + } + + find_curdir(noerror); + + return; +} + +STATIC void +find_curdir(int noerror) +{ + int i; + char *pwd; + + /* + * Things are a bit complicated here; we could have just used + * getcwd, but traditionally getcwd is implemented using popen + * to /bin/pwd. This creates a problem for us, since we cannot + * keep track of the job if it is being ran behind our backs. + * So we re-implement getcwd(), and we suppress interrupts + * throughout the process. This is not completely safe, since + * the user can still break out of it by killing the pwd program. + * We still try to use getcwd for systems that we know have a + * c implementation of getcwd, that does not open a pipe to + * /bin/pwd. + */ +#if defined(__NetBSD__) || defined(__SVR4) + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) { + curdir = savestr(pwd); + stunalloc(pwd); + return; + } + stunalloc(pwd); + if (errno == ERANGE) + continue; + if (!noerror) + error("getcwd() failed: %s", strerror(errno)); + return; + } +#else + { + char *p; + int status; + struct job *jp; + int pip[2]; + + pwd = stalloc(MAXPWD); + INTOFF; + if (pipe(pip) < 0) + error("Pipe call failed"); + jp = makejob(NULL, 1); + if (forkshell(jp, NULL, FORK_NOJOB) == 0) { + (void) close(pip[0]); + movefd(pip[1], 1); + (void) execl("/bin/pwd", "pwd", (char *)0); + error("Cannot exec /bin/pwd"); + } + (void) close(pip[1]); + pip[1] = -1; + p = pwd; + while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0 + || (i == -1 && errno == EINTR)) { + if (i > 0) + p += i; + } + (void) close(pip[0]); + pip[0] = -1; + status = waitforjob(jp); + if (status != 0) + error((char *)0); + if (i < 0 || p == pwd || p[-1] != '\n') { + if (noerror) { + INTON; + return; + } + error("pwd command failed"); + } + p[-1] = '\0'; + INTON; + curdir = savestr(pwd); + stunalloc(pwd); + return; + } +#endif +} diff --git a/bin/sh/cd.h b/bin/sh/cd.h new file mode 100644 index 0000000..7600955 --- /dev/null +++ b/bin/sh/cd.h @@ -0,0 +1,34 @@ +/* $NetBSD: cd.h,v 1.6 2011/06/18 21:18:46 christos Exp $ */ + +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * 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. + * + */ + +void initpwd(void); +void getpwd(int); diff --git a/bin/sh/error.c b/bin/sh/error.c new file mode 100644 index 0000000..db42926 --- /dev/null +++ b/bin/sh/error.c @@ -0,0 +1,378 @@ +/* $NetBSD: error.c,v 1.42 2019/01/21 14:29:12 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: error.c,v 1.42 2019/01/21 14:29:12 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * Errors and exceptions. + */ + +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "shell.h" +#include "eval.h" /* for commandname */ +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "show.h" + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +int exception; +volatile int suppressint; +volatile int intpending; + + +static void exverror(int, const char *, va_list) __dead; + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + */ + +void +exraise(int e) +{ + CTRACE(DBG_ERRS, ("exraise(%d)\n", e)); + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received. (If the user specifies + * that SIGINT is to be trapped or ignored using the trap builtin, then + * this routine is not called.) Suppressint is nonzero when interrupts + * are held using the INTOFF macro. The call to _exit is necessary because + * there is a short period after a fork before the signal handlers are + * set to the appropriate value for the child. (The test for iflag is + * just defensive programming.) + */ + +void +onint(void) +{ + sigset_t nsigset; + + if (suppressint) { + intpending = 1; + return; + } + intpending = 0; + sigemptyset(&nsigset); + sigprocmask(SIG_SETMASK, &nsigset, NULL); + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + raise(SIGINT); + } + /* NOTREACHED */ +} + +static __printflike(2, 0) void +exvwarning(int sv_errno, const char *msg, va_list ap) +{ + /* Partially emulate line buffered output so that: + * printf '%d\n' 1 a 2 + * and + * printf '%d %d %d\n' 1 a 2 + * both generate sensible text when stdout and stderr are merged. + */ + if (output.buf != NULL && output.nextc != output.buf && + output.nextc[-1] == '\n') + flushout(&output); + if (commandname) + outfmt(&errout, "%s: ", commandname); + else + outfmt(&errout, "%s: ", getprogname()); + if (msg != NULL) { + doformat(&errout, msg, ap); + if (sv_errno >= 0) + outfmt(&errout, ": "); + } + if (sv_errno >= 0) + outfmt(&errout, "%s", strerror(sv_errno)); + out2c('\n'); + flushout(&errout); +} + +/* + * Exverror is called to raise the error exception. If the second argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static __printflike(2, 0) void +exverror(int cond, const char *msg, va_list ap) +{ + CLEAR_PENDING_INT; + INTOFF; + +#ifdef DEBUG + if (msg) { + CTRACE(DBG_ERRS, ("exverror(%d, \"", cond)); + CTRACEV(DBG_ERRS, (msg, ap)); + CTRACE(DBG_ERRS, ("\") pid=%d\n", getpid())); + } else + CTRACE(DBG_ERRS, ("exverror(%d, NULL) pid=%d\n", cond, + getpid())); +#endif + if (msg) + exvwarning(-1, msg, ap); + + flushall(); + exraise(cond); + /* NOTREACHED */ +} + + +void +error(const char *msg, ...) +{ + va_list ap; + + /* + * On error, we certainly never want exit(0)... + */ + if (exerrno == 0) + exerrno = 1; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + exverror(cond, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + +/* + * error/warning routines for external builtins + */ + +void +sh_exit(int rval) +{ + exerrno = rval & 255; + exraise(EXEXEC); +} + +void +sh_err(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(errno, fmt, ap); + va_end(ap); + sh_exit(status); +} + +void +sh_verr(int status, const char *fmt, va_list ap) +{ + exvwarning(errno, fmt, ap); + sh_exit(status); +} + +void +sh_errx(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(-1, fmt, ap); + va_end(ap); + sh_exit(status); +} + +void +sh_verrx(int status, const char *fmt, va_list ap) +{ + exvwarning(-1, fmt, ap); + sh_exit(status); +} + +void +sh_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(errno, fmt, ap); + va_end(ap); +} + +void +sh_vwarn(const char *fmt, va_list ap) +{ + exvwarning(errno, fmt, ap); +} + +void +sh_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(-1, fmt, ap); + va_end(ap); +} + +void +sh_vwarnx(const char *fmt, va_list ap) +{ + exvwarning(-1, fmt, ap); +} + + +/* + * Table of error messages. + */ + +struct errname { + short errcode; /* error number */ + short action; /* operation which encountered the error */ + const char *msg; /* text describing the error */ +}; + + +#define ALL (E_OPEN|E_CREAT|E_EXEC) + +STATIC const struct errname errormsg[] = { + { EINTR, ALL, "interrupted" }, + { EACCES, ALL, "permission denied" }, + { EIO, ALL, "I/O error" }, + { EEXIST, ALL, "file exists" }, + { ENOENT, E_OPEN, "no such file" }, + { ENOENT, E_CREAT,"directory nonexistent" }, + { ENOENT, E_EXEC, "not found" }, + { ENOTDIR, E_OPEN, "no such file" }, + { ENOTDIR, E_CREAT,"directory nonexistent" }, + { ENOTDIR, E_EXEC, "not found" }, + { EISDIR, ALL, "is a directory" }, +#ifdef EMFILE + { EMFILE, ALL, "too many open files" }, +#endif + { ENFILE, ALL, "file table overflow" }, + { ENOSPC, ALL, "file system full" }, +#ifdef EDQUOT + { EDQUOT, ALL, "disk quota exceeded" }, +#endif +#ifdef ENOSR + { ENOSR, ALL, "no streams resources" }, +#endif + { ENXIO, ALL, "no such device or address" }, + { EROFS, ALL, "read-only file system" }, + { ETXTBSY, ALL, "text busy" }, +#ifdef EAGAIN + { EAGAIN, E_EXEC, "not enough memory" }, +#endif + { ENOMEM, ALL, "not enough memory" }, +#ifdef ENOLINK + { ENOLINK, ALL, "remote access failed" }, +#endif +#ifdef EMULTIHOP + { EMULTIHOP, ALL, "remote access failed" }, +#endif +#ifdef ECOMM + { ECOMM, ALL, "remote access failed" }, +#endif +#ifdef ESTALE + { ESTALE, ALL, "remote access failed" }, +#endif +#ifdef ETIMEDOUT + { ETIMEDOUT, ALL, "remote access failed" }, +#endif +#ifdef ELOOP + { ELOOP, ALL, "symbolic link loop" }, +#endif +#ifdef ENAMETOOLONG + { ENAMETOOLONG, ALL, "file name too long" }, +#endif + { E2BIG, E_EXEC, "argument list too long" }, +#ifdef ELIBACC + { ELIBACC, E_EXEC, "shared library missing" }, +#endif + { 0, 0, NULL }, +}; + + +/* + * Return a string describing an error. The returned string may be a + * pointer to a static buffer that will be overwritten on the next call. + * Action describes the operation that got the error. + */ + +const char * +errmsg(int e, int action) +{ + struct errname const *ep; + static char buf[12]; + + for (ep = errormsg ; ep->errcode ; ep++) { + if (ep->errcode == e && (ep->action & action) != 0) + return ep->msg; + } + fmtstr(buf, sizeof buf, "error %d", e); + return buf; +} diff --git a/bin/sh/error.h b/bin/sh/error.h new file mode 100644 index 0000000..8dcf737 --- /dev/null +++ b/bin/sh/error.h @@ -0,0 +1,120 @@ +/* $NetBSD: error.h,v 1.21 2018/08/19 23:50:27 kre Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)error.h 8.2 (Berkeley) 5/4/95 + */ + +#include <stdarg.h> + +/* + * Types of operations (passed to the errmsg routine). + */ + +#define E_OPEN 01 /* opening a file */ +#define E_CREAT 02 /* creating a file */ +#define E_EXEC 04 /* executing a program */ + + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exeception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +#include <setjmp.h> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern int exception; +extern int exerrno; /* error for EXEXEC */ + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXSHELLPROC 2 /* execute a shell procedure */ +#define EXEXEC 3 /* command execution failed */ +#define EXEXIT 4 /* shell wants to exit(exitstatus) */ + + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +extern volatile int suppressint; +extern volatile int intpending; + +#define INTOFF suppressint++ +#define INTON do { if (--suppressint == 0 && intpending) onint(); } while (0) +#define FORCEINTON do { suppressint = 0; if (intpending) onint(); } while (0) +#define CLEAR_PENDING_INT (intpending = 0) +#define int_pending() intpending + +#if ! defined(SHELL_BUILTIN) +void exraise(int) __dead; +void onint(void); +void error(const char *, ...) __dead __printflike(1, 2); +void exerror(int, const char *, ...) __dead __printflike(2, 3); +const char *errmsg(int, int); +#endif /* ! SHELL_BUILTIN */ + +void sh_err(int, const char *, ...) __dead __printflike(2, 3); +void sh_verr(int, const char *, va_list) __dead __printflike(2, 0); +void sh_errx(int, const char *, ...) __dead __printflike(2, 3); +void sh_verrx(int, const char *, va_list) __dead __printflike(2, 0); +void sh_warn(const char *, ...) __printflike(1, 2); +void sh_vwarn(const char *, va_list) __printflike(1, 0); +void sh_warnx(const char *, ...) __printflike(1, 2); +void sh_vwarnx(const char *, va_list) __printflike(1, 0); + +void sh_exit(int) __dead; + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#if defined(BSD) && !defined(__SVR4) +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) +#endif diff --git a/bin/sh/eval.c b/bin/sh/eval.c new file mode 100644 index 0000000..2bda702 --- /dev/null +++ b/bin/sh/eval.c @@ -0,0 +1,1680 @@ +/* $NetBSD: eval.c,v 1.170 2019/01/21 14:18:59 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#else +__RCSID("$NetBSD: eval.c,v 1.170 2019/01/21 14:18:59 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdbool.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <unistd.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#include <sys/times.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/sysctl.h> + +/* + * 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" +#include "main.h" +#ifndef SMALL +#include "nodenames.h" +#include "myhistedit.h" +#endif + + +STATIC struct skipsave s_k_i_p; +#define evalskip (s_k_i_p.state) +#define skipcount (s_k_i_p.count) + +STATIC int loopnest; /* current loop nesting level */ +STATIC int funcnest; /* depth of function calls */ +STATIC int builtin_flags; /* evalcommand flags for builtins */ +/* + * Base function nesting level inside a dot command. Set to 0 initially + * and to (funcnest + 1) before every dot command to enable + * 1) detection of being in a file sourced by a dot command and + * 2) counting of function nesting in that file for the implementation + * of the return command. + * The value is reset to its previous value after the dot command. + */ +STATIC int dot_funcnest; + + +const char *commandname; +struct strlist *cmdenviron; +int exitstatus; /* exit status of last command */ +int back_exitstatus; /* exit status of backquoted command */ + + +STATIC void evalloop(union node *, int); +STATIC void evalfor(union node *, int); +STATIC void evalcase(union node *, int); +STATIC void evalsubshell(union node *, int); +STATIC void expredir(union node *); +STATIC void evalredir(union node *, int); +STATIC void evalpipe(union node *); +STATIC void evalcommand(union node *, int, struct backcmd *); +STATIC void prehash(union node *); + +STATIC char *find_dot_file(char *); + +/* + * Called to reset things after an exception. + */ + +#ifdef mkinit +INCLUDE "eval.h" + +RESET { + reset_eval(); +} + +SHELLPROC { + exitstatus = 0; +} +#endif + +void +reset_eval(void) +{ + evalskip = SKIPNONE; + dot_funcnest = 0; + loopnest = 0; + funcnest = 0; +} + +static int +sh_pipe(int fds[2]) +{ + int nfd; + + if (pipe(fds)) + return -1; + + if (fds[0] < 3) { + nfd = fcntl(fds[0], F_DUPFD, 3); + if (nfd != -1) { + close(fds[0]); + fds[0] = nfd; + } + } + + if (fds[1] < 3) { + nfd = fcntl(fds[1], F_DUPFD, 3); + if (nfd != -1) { + close(fds[1]); + fds[1] = nfd; + } + } + return 0; +} + + +/* + * The eval commmand. + */ + +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 (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags & EV_TESTED); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(char *s, int flag) +{ + union node *n; + struct stackmark smark; + int last; + int any; + + last = flag & EV_EXIT; + flag &= ~EV_EXIT; + + setstackmark(&smark); + setinputstring(s, 1, line_number); + + any = 0; /* to determine if exitstatus will have been set */ + while ((n = parsecmd(0)) != NEOF) { + XTRACE(DBG_EVAL, ("evalstring: "), showtree(n)); + if (n && nflag == 0) { + if (last && at_eof()) + evaltree(n, flag | EV_EXIT); + else + evaltree(n, flag); + any = 1; + if (evalskip) + break; + } + rststackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (last) + exraise(EXEXIT); +} + + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + bool do_etest; + int sflags = flags & ~EV_EXIT; + union node *next; + struct stackmark smark; + + do_etest = false; + if (n == NULL || nflag) { + VTRACE(DBG_EVAL, ("evaltree(%s) called\n", + n == NULL ? "NULL" : "-n")); + if (nflag == 0) + exitstatus = 0; + goto out2; + } + + setstackmark(&smark); + do { +#ifndef SMALL + displayhist = 1; /* show history substitutions done with fc */ +#endif + next = NULL; + CTRACE(DBG_EVAL, ("pid %d, evaltree(%p: %s(%d), %#x) called\n", + getpid(), n, NODETYPENAME(n->type), n->type, flags)); + if (n->type != NCMD && traps_invalid) + free_traps(); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, sflags); + if (nflag || evalskip) + goto out1; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (nflag || evalskip || exitstatus != 0) + goto out1; + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (nflag || evalskip || exitstatus == 0) + goto out1; + 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 (nflag || evalskip) + goto out1; + 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, sflags); + break; + case NFOR: + evalfor(n, sflags); + break; + case NCASE: + evalcase(n, sflags); + break; + case NDEFUN: + CTRACE(DBG_EVAL, ("Defining fn %s @%d%s\n", + n->narg.text, n->narg.lineno, + fnline1 ? " LINENO=1" : "")); + defun(n->narg.text, n->narg.next, n->narg.lineno); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + exitstatus = !exitstatus; + break; + case NDNOT: + evaltree(n->nnot.com, EV_TESTED); + if (exitstatus != 0) + exitstatus = 1; + break; + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, NULL); + do_etest = !(flags & EV_TESTED); + break; + default: +#ifdef NODETYPENAME + out1fmt("Node type = %d(%s)\n", + n->type, NODETYPENAME(n->type)); +#else + out1fmt("Node type = %d\n", n->type); +#endif + flushout(&output); + break; + } + n = next; + rststackmark(&smark); + } while(n != NULL); + out1: + popstackmark(&smark); + out2: + if (pendingsigs) + 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; + + CTRACE(DBG_EVAL, ("evalloop %s:", NODETYPENAME(n->type))); + VXTRACE(DBG_EVAL, (" "), showtree(n->nbinary.ch1)); + VXTRACE(DBG_EVAL, ("evalloop do: "), showtree(n->nbinary.ch2)); + VTRACE(DBG_EVAL, ("evalloop done\n")); + CTRACE(DBG_EVAL, ("\n")); + + for (;;) { + evaltree(n->nbinary.ch1, EV_TESTED); + if (nflag) + break; + if (evalskip) { + skipping: if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = SKIPNONE; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = SKIPNONE; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags & EV_TESTED); + status = exitstatus; + if (evalskip) + goto skipping; + } + loopnest--; + exitstatus = status; +} + + + +STATIC void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; + int status; + + status = nflag ? exitstatus : 0; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + if (evalskip) + goto out; + } + *arglist.lastp = NULL; + + loopnest++; + for (sp = arglist.list ; sp ; sp = sp->next) { + if (xflag) { + outxstr(expandstr(ps4val(), line_number)); + outxstr("for "); + outxstr(n->nfor.var); + outxc('='); + outxshstr(sp->text); + outxc('\n'); + flushout(outx); + } + + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags & EV_TESTED); + status = exitstatus; + if (nflag) + break; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = SKIPNONE; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = SKIPNONE; + break; + } + } + loopnest--; + exitstatus = status; + out: + popstackmark(&smark); +} + + + +STATIC void +evalcase(union node *n, int flags) +{ + union node *cp, *ncp; + union node *patp; + struct arglist arglist; + struct stackmark smark; + int status = 0; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + line_number = n->ncase.lineno; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { + line_number = patp->narg.lineno; + if (casematch(patp, arglist.list->text)) { + while (cp != NULL && evalskip == 0 && + nflag == 0) { + if (cp->type == NCLISTCONT) + ncp = cp->nclist.next; + else + ncp = NULL; + line_number = cp->nclist.lineno; + evaltree(cp->nclist.body, flags); + status = exitstatus; + cp = ncp; + } + goto out; + } + } + } + out: + exitstatus = status; + popstackmark(&smark); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +STATIC void +evalsubshell(union node *n, int flags) +{ + struct job *jp= NULL; + int backgnd = (n->type == NBACKGND); + + expredir(n->nredir.redirect); + if (xflag && n->nredir.redirect) { + union node *rn; + + outxstr(expandstr(ps4val(), line_number)); + outxstr("using redirections:"); + for (rn = n->nredir.redirect; rn; rn = rn->nfile.next) + (void) outredir(outx, rn, ' '); + outxstr(" do subshell ("/*)*/); + if (backgnd) + outxstr(/*(*/") &"); + outxc('\n'); + flushout(outx); + } + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd?FORK_BG:FORK_FG) == 0) { + INTON; + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, REDIR_KEEP); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (!backgnd) { + INTOFF; + exitstatus = waitforjob(jp); + INTON; + } else + exitstatus = 0; + + if (!backgnd && xflag && n->nredir.redirect) { + outxstr(expandstr(ps4val(), line_number)); + outxstr(/*(*/") done subshell\n"); + flushout(outx); + } +} + + + +/* + * 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; + + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR); + fixredir(redir, fn.list->text, 1); + } + break; + } + } +} + +/* + * Perform redirections for a compound command, and then do it (and restore) + */ +STATIC void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc * const savehandler = handler; + volatile int in_redirect = 1; + const char * volatile PS4 = NULL; + + expredir(n->nredir.redirect); + + if (xflag && n->nredir.redirect) { + union node *rn; + + outxstr(PS4 = expandstr(ps4val(), line_number)); + outxstr("using redirections:"); + for (rn = n->nredir.redirect; rn != NULL; rn = rn->nfile.next) + (void) outredir(outx, rn, ' '); + outxstr(" do {\n"); /* } */ + flushout(outx); + } + + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (PS4 != NULL) { + outxstr(PS4); + /* { */ outxstr("} failed\n"); + flushout(outx); + } + 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 | REDIR_KEEP); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; + + if (PS4 != NULL) { + outxstr(PS4); + /* { */ outxstr("} done\n"); + flushout(outx); + } +} + + +/* + * 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]; + + CTRACE(DBG_EVAL, ("evalpipe(%p) called\n", 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 (sh_pipe(pip) < 0) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, + n->npipe.backgnd ? FORK_BG : FORK_FG) == 0) { + INTON; + if (prevfd > 0) + movefd(prevfd, 0); + if (pip[1] >= 0) { + close(pip[0]); + movefd(pip[1], 1); + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp); + CTRACE(DBG_EVAL, ("evalpipe: job done exit status %d\n", + exitstatus)); + INTON; + } else + exitstatus = 0; + INTON; +} + + + +/* + * 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; /* unnecessary (because we fork) */ + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + + if (nflag || n == NULL) + goto out; + + setstackmark(&smark); + +#ifdef notyet + /* + * For now we disable executing builtins in the same + * context as the shell, because we are not keeping + * enough state to recover from changes that are + * supposed only to affect subshells. eg. echo "`cd /`" + */ + if (n->type == NCMD) { + exitstatus = oexitstatus; /* XXX o... no longer exists */ + evalcommand(n, EV_BACKCMD, result); + } else +#endif + { + INTOFF; + if (sh_pipe(pip) < 0) + error("Pipe call failed"); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + movefd(pip[1], 1); + eflag = 0; + evaltree(n, EV_EXIT); + /* NOTREACHED */ + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + INTON; + } + popstackmark(&smark); + out: + CTRACE(DBG_EVAL, ("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +const char * +syspath(void) +{ + static char *sys_path = NULL; + static int mib[] = {CTL_USER, USER_CS_PATH}; + static char def_path[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin"; + size_t len; + + if (sys_path == NULL) { + if (sysctl(mib, 2, 0, &len, 0, 0) != -1 && + (sys_path = ckmalloc(len + 5)) != NULL && + sysctl(mib, 2, sys_path + 5, &len, 0, 0) != -1) { + memcpy(sys_path, "PATH=", 5); + } else { + ckfree(sys_path); + /* something to keep things happy */ + sys_path = def_path; + } + } + return sys_path; +} + +static int +parse_command_args(int argc, char **argv, int *use_syspath) +{ + int sv_argc = argc; + char *cp, c; + + *use_syspath = 0; + + for (;;) { + argv++; + if (--argc == 0) + break; + cp = *argv; + if (*cp++ != '-') + break; + if (*cp == '-' && cp[1] == 0) { + argv++; + argc--; + break; + } + while ((c = *cp++)) { + switch (c) { + case 'p': + *use_syspath = 1; + break; + default: + /* run 'typecmd' for other options */ + return 0; + } + } + } + return sv_argc - argc; +} + +int vforked = 0; +extern char *trap[]; + +/* + * Execute a simple command. + */ + +STATIC void +evalcommand(union node *cmd, int flgs, struct backcmd *backcmd) +{ + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + volatile int flags = flgs; + char ** volatile argv; + volatile int argc; + char **envp; + int varflag; + struct strlist *sp; + volatile int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job * volatile jp; + struct jmploc jmploc; + struct jmploc *volatile savehandler = NULL; + const char *volatile savecmdname; + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct parsefile *volatile savetopfile; + volatile int e; + char * volatile lastarg; + const char * volatile path = pathval(); + volatile int temp_path; + const int savefuncline = funclinebase; + const int savefuncabs = funclineabs; + volatile int cmd_flags = 0; + + vforked = 0; + /* First expand the arguments. */ + CTRACE(DBG_EVAL, ("evalcommand(%p, %d) called [%s]\n", cmd, flags, + cmd->ncmd.args ? cmd->ncmd.args->narg.text : "")); + setstackmark(&smark); + back_exitstatus = 0; + + line_number = cmd->ncmd.lineno; + + arglist.lastp = &arglist.list; + varflag = 1; + /* Expand arguments, ignoring the initial 'name=value' ones */ + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) + continue; + varflag = 0; + line_number = argp->narg.lineno; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + *arglist.lastp = NULL; + + expredir(cmd->ncmd.redirect); + + /* Now do the initial 'name=value' ones we skipped above */ + varlist.lastp = &varlist.list; + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + line_number = argp->narg.lineno; + if (!isassignment(argp->narg.text)) + break; + expandarg(argp, &varlist, EXP_VARTILDE); + } + *varlist.lastp = NULL; + + argc = 0; + for (sp = arglist.list ; sp ; sp = sp->next) + argc++; + argv = stalloc(sizeof (char *) * (argc + 1)); + + for (sp = arglist.list ; sp ; sp = sp->next) { + VTRACE(DBG_EVAL, ("evalcommand arg: %s\n", sp->text)); + *argv++ = sp->text; + } + *argv = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[-1]; + argv -= argc; + + /* Print the command if xflag is set. */ + if (xflag) { + char sep = 0; + union node *rn; + + outxstr(expandstr(ps4val(), line_number)); + for (sp = varlist.list ; sp ; sp = sp->next) { + char *p; + + if (sep != 0) + outxc(sep); + + /* + * The "var=" part should not be quoted, regardless + * of the value, or it would not represent an + * assignment, but rather a command + */ + p = strchr(sp->text, '='); + if (p != NULL) { + *p = '\0'; /*XXX*/ + outxshstr(sp->text); + outxc('='); + *p++ = '='; /*XXX*/ + } else + p = sp->text; + outxshstr(p); + sep = ' '; + } + for (sp = arglist.list ; sp ; sp = sp->next) { + if (sep != 0) + outxc(sep); + outxshstr(sp->text); + sep = ' '; + } + for (rn = cmd->ncmd.redirect; rn; rn = rn->nfile.next) + if (outredir(outx, rn, sep)) + sep = ' '; + outxc('\n'); + flushout(outx); + } + + /* Now locate the command. */ + if (argc == 0) { + /* + * the empty command begins as a normal builtin, and + * remains that way while redirects are processed, then + * will become special before we get to doing the + * var assigns. + */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.bltin = bltincmd; + } else { + static const char PATH[] = "PATH="; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (sp = varlist.list; sp; sp = sp->next) + if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0) + path = sp->text + sizeof(PATH) - 1; + + do { + int argsused, use_syspath; + + find_command(argv[0], &cmdentry, cmd_flags, path); +#if 0 + /* + * This short circuits all of the processing that + * should be done (including processing the + * redirects), so just don't ... + * + * (eventually this whole #if'd block will vanish) + */ + if (cmdentry.cmdtype == CMDUNKNOWN) { + exitstatus = 127; + flushout(&errout); + goto out; + } +#endif + + /* implement the 'command' builtin here */ + if (cmdentry.cmdtype != CMDBUILTIN || + cmdentry.u.bltin != bltincmd) + break; + cmd_flags |= DO_NOFUNC; + argsused = parse_command_args(argc, argv, &use_syspath); + if (argsused == 0) { + /* use 'type' builtin to display info */ + cmdentry.u.bltin = typecmd; + break; + } + argc -= argsused; + argv += argsused; + if (use_syspath) + path = syspath() + 5; + } while (argc != 0); + if (cmdentry.cmdtype == CMDSPLBLTIN && cmd_flags & DO_NOFUNC) + /* posix mandates that 'command <splbltin>' act as if + <splbltin> was a normal builtin */ + cmdentry.cmdtype = CMDBUILTIN; + } + + /* + * When traps are invalid, we permit the following: + * trap + * command trap + * eval trap + * command eval trap + * eval command trap + * without zapping the traps completely, in all other cases we do. + * + * The test here permits eval "anything" but when evalstring() comes + * back here again, the "anything" will be validated. + * This means we can actually do: + * eval eval eval command eval eval command trap + * as long as we end up with just "trap" + * + * We permit "command" by allowing CMDBUILTIN as well as CMDSPLBLTIN + * + * trapcmd() takes care of doing free_traps() if it is needed there. + */ + if (traps_invalid && + ((cmdentry.cmdtype!=CMDSPLBLTIN && cmdentry.cmdtype!=CMDBUILTIN) || + (cmdentry.u.bltin != trapcmd && cmdentry.u.bltin != evalcmd))) + free_traps(); + + /* Fork off a child process if necessary. */ + if (cmd->ncmd.backgnd || (have_traps() && (flags & EV_EXIT) != 0) + || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && (flags & EV_EXIT) == 0) + || ((flags & EV_BACKCMD) != 0 && + ((cmdentry.cmdtype != CMDBUILTIN && cmdentry.cmdtype != CMDSPLBLTIN) + || cmdentry.u.bltin == dotcmd + || cmdentry.u.bltin == evalcmd))) { + INTOFF; + jp = makejob(cmd, 1); + mode = cmd->ncmd.backgnd; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (sh_pipe(pip) < 0) + error("Pipe call failed"); + } +#ifdef DO_SHAREDVFORK + /* It is essential that if DO_SHAREDVFORK is defined that the + * child's address space is actually shared with the parent as + * we rely on this. + */ + if (usefork == 0 && cmdentry.cmdtype == CMDNORMAL) { + pid_t pid; + int serrno; + + savelocalvars = localvars; + localvars = NULL; + vforked = 1; + VFORK_BLOCK + switch (pid = vfork()) { + case -1: + serrno = errno; + VTRACE(DBG_EVAL, ("vfork() failed, errno=%d\n", + serrno)); + INTON; + error("Cannot vfork (%s)", strerror(serrno)); + break; + case 0: + /* Make sure that exceptions only unwind to + * after the vfork(2) + */ + SHELL_FORKED(); + if (setjmp(jmploc.loc)) { + if (exception == EXSHELLPROC) { + /* + * We can't progress with the + * vfork, so, set vforked = 2 + * so the parent knows, + * and _exit(); + */ + vforked = 2; + _exit(0); + } else { + _exit(exception == EXEXIT ? + exitstatus : exerrno); + } + } + savehandler = handler; + handler = &jmploc; + listmklocal(varlist.list, + VDOEXPORT | VEXPORT | VNOFUNC); + forkchild(jp, cmd, mode, vforked); + break; + default: + VFORK_UNDO(); + /* restore from vfork(2) */ + handler = savehandler; + poplocalvars(); + localvars = savelocalvars; + if (vforked == 2) { + vforked = 0; + + (void)waitpid(pid, NULL, 0); + /* + * We need to progress in a + * normal fork fashion + */ + goto normal_fork; + } + /* + * Here the child has left home, + * getting on with its life, so + * so must we... + */ + vforked = 0; + forkparent(jp, cmd, mode, pid); + goto parent; + } + VFORK_END + } else { + normal_fork: +#endif + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + flags |= EV_EXIT; + FORCEINTON; +#ifdef DO_SHAREDVFORK + } +#endif + if (flags & EV_BACKCMD) { + if (!vforked) { + FORCEINTON; + } + close(pip[0]); + movefd(pip[1], 1); + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + switch (cmdentry.cmdtype) { + volatile int saved; + + case CMDFUNCTION: + VXTRACE(DBG_EVAL, ("Shell function%s: ",vforked?" VF":""), + trargs(argv)); + redirect(cmd->ncmd.redirect, saved = + !(flags & EV_EXIT) || have_traps() ? REDIR_PUSH : 0); + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + INTON; + if (setjmp(jmploc.loc)) { + if (exception == EXSHELLPROC) { + freeparam((volatile struct shparam *) + &saveparam); + } else { + freeparam(&shellparam); + shellparam = saveparam; + } + if (saved) + popredir();; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + funclinebase = savefuncline; + funclineabs = savefuncabs; + handler = savehandler; + longjmp(handler->loc, 1); + } + savehandler = handler; + handler = &jmploc; + if (cmdentry.u.func) { + if (cmdentry.lno_frel) + funclinebase = cmdentry.lineno - 1; + else + funclinebase = 0; + funclineabs = cmdentry.lineno; + + VTRACE(DBG_EVAL, + ("function: node: %d '%s' # %d%s; funclinebase=%d\n", + getfuncnode(cmdentry.u.func)->type, + NODETYPENAME(getfuncnode(cmdentry.u.func)->type), + cmdentry.lineno, cmdentry.lno_frel?" (=1)":"", + funclinebase)); + } + listmklocal(varlist.list, VDOEXPORT | VEXPORT); + /* stop shell blowing its stack */ + if (++funcnest > 1000) + error("too many nested function calls"); + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED|EV_EXIT)); + funcnest--; + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + funclinebase = savefuncline; + funclineabs = savefuncabs; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + if (saved) + popredir(); + INTON; + if (evalskip == SKIPFUNC) { + evalskip = SKIPNONE; + skipcount = 0; + } + if (flags & EV_EXIT) + exitshell(exitstatus); + break; + + case CMDSPLBLTIN: + VTRACE(DBG_EVAL, ("special ")); + case CMDBUILTIN: + VXTRACE(DBG_EVAL, ("builtin command [%d]%s: ", argc, + vforked ? " VF" : ""), trargs(argv)); + mode = (cmdentry.u.bltin == execcmd) ? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nleft = 0; + memout.nextc = memout.buf; + memout.bufsize = 64; + mode |= REDIR_BACKQ; + } + e = -1; + savecmdname = commandname; + savetopfile = getcurrentfile(); + savehandler = handler; + temp_path = 0; + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + + /* + * We need to ensure the command hash table isn't + * corrupted by temporary PATH assignments. + * However we must ensure the 'local' command works! + */ + if (path != pathval() && (cmdentry.u.bltin == hashcmd || + cmdentry.u.bltin == typecmd)) { + savelocalvars = localvars; + localvars = 0; + temp_path = 1; + mklocal(path - 5 /* PATH= */, 0); + } + redirect(cmd->ncmd.redirect, mode); + + /* + * the empty command is regarded as a normal + * builtin for the purposes of redirects, but + * is a special builtin for var assigns. + * (unless we are the "command" command.) + */ + if (argc == 0 && !(cmd_flags & DO_NOFUNC)) + cmdentry.cmdtype = CMDSPLBLTIN; + + /* exec is a special builtin, but needs this list... */ + cmdenviron = varlist.list; + /* we must check 'readonly' flag for all builtins */ + listsetvar(varlist.list, + cmdentry.cmdtype == CMDSPLBLTIN ? 0 : VNOSET); + commandname = argv[0]; + /* initialize nextopt */ + argptr = argv + 1; + optptr = NULL; + /* and getopt */ + optreset = 1; + optind = 1; + builtin_flags = flags; + exitstatus = cmdentry.u.bltin(argc, argv); + } else { + e = exception; + if (e == EXINT) + exitstatus = SIGINT + 128; + else if (e == EXEXEC) + exitstatus = exerrno; + else if (e != EXEXIT) + exitstatus = 2; + } + handler = savehandler; + flushall(); + out1 = &output; + out2 = &errout; + freestdout(); + if (temp_path) { + poplocalvars(); + localvars = savelocalvars; + } + cmdenviron = NULL; + if (e != EXSHELLPROC) { + commandname = savecmdname; + if (flags & EV_EXIT) + exitshell(exitstatus); + } + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.cmdtype == CMDSPLBLTIN) + exraise(e); + popfilesupto(savetopfile); + FORCEINTON; + } + if (cmdentry.u.bltin != execcmd) + popredir(); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.nextc - memout.buf; + memout.buf = NULL; + } + break; + + default: + VXTRACE(DBG_EVAL, ("normal command%s: ", vforked?" VF":""), + trargs(argv)); + redirect(cmd->ncmd.redirect, + (vforked ? REDIR_VFORK : 0) | REDIR_KEEP); + if (!vforked) + for (sp = varlist.list ; sp ; sp = sp->next) + setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index, vforked); + break; + } + goto out; + + parent: /* parent process gets here (if we forked) */ + + exitstatus = 0; /* if not altered just below */ + if (mode == FORK_FG) { /* argument to fork */ + exitstatus = waitforjob(jp); + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + FORCEINTON; + + out: + if (lastarg) + /* implement $_ for whatever use that really is */ + (void) setvarsafe("_", lastarg, VNOERROR); + popstackmark(&smark); +} + + +/* + * 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()); +} + +int +in_function(void) +{ + return funcnest; +} + +enum skipstate +current_skipstate(void) +{ + return evalskip; +} + +void +save_skipstate(struct skipsave *p) +{ + *p = s_k_i_p; +} + +void +restore_skipstate(const struct skipsave *p) +{ + s_k_i_p = *p; +} + +void +stop_skipping(void) +{ + evalskip = SKIPNONE; + skipcount = 0; +} + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given. + */ + +int +bltincmd(int argc, char **argv) +{ + /* + * Preserve exitstatus of a previous possible redirection + * as POSIX mandates + */ + return back_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) +{ + int n = argc > 1 ? number(argv[1]) : 1; + + if (n <= 0) + error("invalid count: %d", n); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +int +dotcmd(int argc, char **argv) +{ + exitstatus = 0; + + if (argc >= 2) { /* That's what SVR2 does */ + char *fullname; + /* + * dot_funcnest needs to be 0 when not in a dotcmd, so it + * cannot be restored with (funcnest + 1). + */ + int dot_funcnest_old; + struct stackmark smark; + + setstackmark(&smark); + fullname = find_dot_file(argv[1]); + setinputfile(fullname, 1); + commandname = fullname; + dot_funcnest_old = dot_funcnest; + dot_funcnest = funcnest + 1; + cmdloop(0); + dot_funcnest = dot_funcnest_old; + popfile(); + popstackmark(&smark); + } + return exitstatus; +} + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + +STATIC char * +find_dot_file(char *basename) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(basename, '/')) { + if (stat(basename, &statb) == 0) { + if (S_ISDIR(statb.st_mode)) + error("%s: is a directory", basename); + if (S_ISBLK(statb.st_mode)) + error("%s: is a block device", basename); + return basename; + } + } else while ((fullname = padvance(&path, basename, 1)) != NULL) { + if ((stat(fullname, &statb) == 0)) { + /* weird format is to ease future code... */ + if (S_ISDIR(statb.st_mode) || S_ISBLK(statb.st_mode)) + ; +#if notyet + else if (unreadable()) { + /* + * testing this via st_mode is ugly to get + * correct (and would ignore ACLs). + * better way is just to open the file. + * But doing that here would (currently) + * mean opening the file twice, which + * might not be safe. So, defer this + * test until code is restructures so + * we can return a fd. Then we also + * get to fix the mem leak just below... + */ + } +#endif + else { + /* + * Don't bother freeing here, since + * it will be freed by the caller. + * XXX no it won't - a bug for later. + */ + return fullname; + } + } + stunalloc(fullname); + } + + /* not found in the PATH */ + error("%s: not found", basename); + /* NOTREACHED */ +} + + + +/* + * The return command. + * + * Quoth the POSIX standard: + * The return utility shall cause the shell to stop executing the current + * function or dot script. If the shell is not currently executing + * a function or dot script, the results are unspecified. + * + * As for the unspecified part, there seems to be no de-facto standard: bash + * ignores the return with a warning, zsh ignores the return in interactive + * mode but seems to liken it to exit in a script. (checked May 2014) + * + * We choose to silently ignore the return. Older versions of this shell + * set evalskip to SKIPFILE causing the shell to (indirectly) exit. This + * had at least the problem of circumventing the check for stopped jobs, + * which would occur for exit or ^D. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : exitstatus; + + if ((dot_funcnest == 0 && funcnest) + || (dot_funcnest > 0 && funcnest - (dot_funcnest - 1) > 0)) { + evalskip = SKIPFUNC; + skipcount = 1; + } else if (dot_funcnest > 0) { + evalskip = SKIPFILE; + skipcount = 1; + } else { + /* XXX: should a warning be issued? */ + ret = 0; + } + + return ret; +} + + +int +falsecmd(int argc, char **argv) +{ + return 1; +} + + +int +truecmd(int argc, char **argv) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + if (argc > 1) { + struct strlist *sp; + + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (sp = cmdenviron; sp; sp = sp->next) + setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0, 0); + } + return 0; +} + +static int +conv_time(clock_t ticks, char *seconds, size_t l) +{ + static clock_t tpm = 0; + clock_t mins; + int i; + + if (!tpm) + tpm = sysconf(_SC_CLK_TCK) * 60; + + mins = ticks / tpm; + snprintf(seconds, l, "%.4f", (ticks - mins * tpm) * 60.0 / tpm ); + + if (seconds[0] == '6' && seconds[1] == '0') { + /* 59.99995 got rounded up... */ + mins++; + strlcpy(seconds, "0.0", l); + return mins; + } + + /* suppress trailing zeros */ + i = strlen(seconds) - 1; + for (; seconds[i] == '0' && seconds[i - 1] != '.'; i--) + seconds[i] = 0; + return mins; +} + +int +timescmd(int argc, char **argv) +{ + struct tms tms; + int u, s, cu, cs; + char us[8], ss[8], cus[8], css[8]; + + nextopt(""); + + times(&tms); + + u = conv_time(tms.tms_utime, us, sizeof(us)); + s = conv_time(tms.tms_stime, ss, sizeof(ss)); + cu = conv_time(tms.tms_cutime, cus, sizeof(cus)); + cs = conv_time(tms.tms_cstime, css, sizeof(css)); + + outfmt(out1, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n", + u, us, s, ss, cu, cus, cs, css); + + return 0; +} diff --git a/bin/sh/eval.h b/bin/sh/eval.h new file mode 100644 index 0000000..b18dd42 --- /dev/null +++ b/bin/sh/eval.h @@ -0,0 +1,87 @@ +/* $NetBSD: eval.h,v 1.22 2018/12/03 06:43:19 kre Exp $ */ + +/*- + * 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. + * + * @(#)eval.h 8.2 (Berkeley) 5/4/95 + */ + +extern const char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int back_exitstatus; /* exit status of backquoted command */ +extern struct strlist *cmdenviron; /* environment for builtin command */ + + +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +void evalstring(char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +const char *syspath(void); + +/* in_function returns nonzero if we are currently evaluating a function */ +int in_function(void); /* return non-zero, if evaluating a function */ + +/* reasons for skipping commands (see comment on breakcmd routine) */ +enum skipstate { + SKIPNONE = 0, /* not skipping */ + SKIPBREAK, /* break */ + SKIPCONT, /* continue */ + SKIPFUNC, /* return in a function */ + SKIPFILE /* return in a dot command */ +}; + +struct skipsave { + enum skipstate state; /* skipping or not */ + int count; /* when break or continue, how many */ +}; + +enum skipstate current_skipstate(void); +void save_skipstate(struct skipsave *); +void restore_skipstate(const struct skipsave *); +void stop_skipping(void); /* reset internal skipping state to SKIPNONE */ + +/* + * Only for use by reset() in init.c! + */ +void reset_eval(void); + +/* flags in argument to evaltree */ +#define EV_EXIT 0x01 /* exit after evaluating tree */ +#define EV_TESTED 0x02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 0x04 /* command executing within back quotes */ diff --git a/bin/sh/exec.c b/bin/sh/exec.c new file mode 100644 index 0000000..674beb8 --- /dev/null +++ b/bin/sh/exec.c @@ -0,0 +1,1183 @@ +/* $NetBSD: exec.c,v 1.53 2018/07/25 14:42:50 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#else +__RCSID("$NetBSD: exec.c,v 1.53 2018/07/25 14:42:50 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +/* + * 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 "init.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char fn_ln1; /* for functions, LINENO from 1 */ + int lineno; /* for functions abs LINENO of definition */ + char cmdname[ARB]; /* name of command */ +}; + + +STATIC struct tblentry *cmdtable[CMDTABLESIZE]; +STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */ +int exerrno = 0; /* Last exec error */ + + +STATIC void tryexec(char *, char **, char **, int); +STATIC void printentry(struct tblentry *, int); +STATIC void addcmdentry(char *, struct cmdentry *); +STATIC void clearcmdentry(int); +STATIC struct tblentry *cmdlookup(const char *, int); +STATIC void delete_cmd_entry(void); + +#ifndef BSD +STATIC void execinterp(char **, char **); +#endif + + +extern const char *const parsekwd[]; + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx, int vforked) +{ + char *cmdname; + int e; + + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp, vforked); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0], 1)) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp, vforked); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + switch (e) { + case EACCES: /* particularly this (unless no search perm) */ + /* + * should perhaps check if this EACCES is an exec() + * EACESS or a namei() EACESS - the latter should be 127 + * - but not today + */ + case EINVAL: /* also explicitly these */ + case ENOEXEC: + default: /* and anything else */ + exerrno = 126; + break; + + case ENOENT: /* these are the "pathname lookup failed" errors */ + case ELOOP: + case ENOTDIR: + case ENAMETOOLONG: + exerrno = 127; + break; + } + CTRACE(DBG_ERRS|DBG_CMDS|DBG_EVAL, + ("shellexec failed for %s, errno %d, vforked %d, suppressint %d\n", + argv[0], e, vforked, suppressint)); + exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC)); + /* NOTREACHED */ +} + + +STATIC void +tryexec(char *cmd, char **argv, char **envp, int vforked) +{ + int e; +#ifndef BSD + char *p; +#endif + +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + e = errno; + if (e == ENOEXEC) { + if (vforked) { + /* We are currently vfork(2)ed, so raise an + * exception, and evalcommand will try again + * with a normal fork(2). + */ + exraise(EXSHELLPROC); + } +#ifdef DEBUG + VTRACE(DBG_CMDS, ("execve(cmd=%s) returned ENOEXEC\n", cmd)); +#endif + initshellproc(); + setinputfile(cmd, 0); + commandname = arg0 = savestr(argv[0]); +#ifndef BSD + pgetc(); pungetc(); /* fill up input buffer */ + p = parsenextc; + if (parsenleft > 2 && p[0] == '#' && p[1] == '!') { + argv[0] = cmd; + execinterp(argv, envp); + } +#endif + setparam(argv + 1); + exraise(EXSHELLPROC); + } + errno = e; +} + + +#ifndef BSD +/* + * Execute an interpreter introduced by "#!", for systems where this + * feature has not been built into the kernel. If the interpreter is + * the shell, return (effectively ignoring the "#!"). If the execution + * of the interpreter fails, exit. + * + * This code peeks inside the input buffer in order to avoid actually + * reading any input. It would benefit from a rewrite. + */ + +#define NEWARGS 5 + +STATIC void +execinterp(char **argv, char **envp) +{ + int n; + char *inp; + char *outp; + char c; + char *p; + char **ap; + char *newargs[NEWARGS]; + int i; + char **ap2; + char **new; + + n = parsenleft - 2; + inp = parsenextc + 2; + ap = newargs; + for (;;) { + while (--n >= 0 && (*inp == ' ' || *inp == '\t')) + inp++; + if (n < 0) + goto bad; + if ((c = *inp++) == '\n') + break; + if (ap == &newargs[NEWARGS]) +bad: error("Bad #! line"); + STARTSTACKSTR(outp); + do { + STPUTC(c, outp); + } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n'); + STPUTC('\0', outp); + n++, inp--; + *ap++ = grabstackstr(outp); + } + if (ap == newargs + 1) { /* if no args, maybe no exec is needed */ + p = newargs[0]; + for (;;) { + if (equal(p, "sh") || equal(p, "ash")) { + return; + } + while (*p != '/') { + if (*p == '\0') + goto break2; + p++; + } + p++; + } +break2:; + } + i = (char *)ap - (char *)newargs; /* size in bytes */ + if (i == 0) + error("Bad #! line"); + for (ap2 = argv ; *ap2++ != NULL ; ); + new = ckmalloc(i + ((char *)ap2 - (char *)argv)); + ap = newargs, ap2 = new; + while ((i -= sizeof (char **)) >= 0) + *ap2++ = *ap++; + ap = argv; + while (*ap2++ = *ap++); + shellexec(new, envp, pathval(), 0); + /* NOTREACHED */ +} +#endif + + + +/* + * 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, int magic_percent) +{ + const char *p; + char *q; + const char *start; + int len; + + if (*path == NULL) + return NULL; + if (magic_percent) + magic_percent = '%'; + + start = *path; + for (p = start ; *p && *p != ':' && *p != magic_percent ; p++) + ; + len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ + while (stackblocksize() < len) + growstackblock(); + q = stackblock(); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + if (q[-1] != '/') + *q++ = '/'; + } + strcpy(q, name); + pathopt = NULL; + if (*p == magic_percent) { + pathopt = ++p; + while (*p && *p != ':') + p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return grabstackstr(q + strlen(name) + 1); +} + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + int allopt=0, bopt=0, fopt=0, ropt=0, sopt=0, uopt=0, verbose=0; + + while ((c = nextopt("bcfrsuv")) != '\0') + switch (c) { + case 'b': bopt = 1; break; + case 'c': uopt = 1; break; /* c == u */ + case 'f': fopt = 1; break; + case 'r': ropt = 1; break; + case 's': sopt = 1; break; + case 'u': uopt = 1; break; + case 'v': verbose = 1; break; + } + + if (ropt) + clearcmdentry(0); + + if (bopt == 0 && fopt == 0 && sopt == 0 && uopt == 0) + allopt = bopt = fopt = sopt = uopt = 1; + + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + switch (cmdp->cmdtype) { + case CMDNORMAL: + if (!uopt) + continue; + break; + case CMDBUILTIN: + if (!bopt) + continue; + break; + case CMDSPLBLTIN: + if (!sopt) + continue; + break; + case CMDFUNCTION: + if (!fopt) + continue; + break; + default: /* never happens */ + continue; + } + if (!allopt || verbose || + cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + + while ((name = *argptr++) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL) { + switch (cmdp->cmdtype) { + case CMDNORMAL: + if (!uopt) + continue; + delete_cmd_entry(); + break; + case CMDBUILTIN: + if (!bopt) + continue; + if (builtinloc >= 0) + delete_cmd_entry(); + break; + case CMDSPLBLTIN: + if (!sopt) + continue; + break; + case CMDFUNCTION: + if (!fopt) + continue; + break; + } + } + find_command(name, &entry, DO_ERR, pathval()); + if (verbose) { + if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */ + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + } + flushall(); + } + } + return 0; +} + +STATIC void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path; + char *name; + + switch (cmdp->cmdtype) { + case CMDNORMAL: + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname, 1); + stunalloc(name); + } while (--idx >= 0); + if (verbose) + out1fmt("Command from PATH[%d]: ", + cmdp->param.index); + out1str(name); + break; + case CMDSPLBLTIN: + if (verbose) + out1str("special "); + /* FALLTHROUGH */ + case CMDBUILTIN: + if (verbose) + out1str("builtin "); + out1fmt("%s", cmdp->cmdname); + break; + case CMDFUNCTION: + if (verbose) + out1str("function "); + out1fmt("%s", cmdp->cmdname); + if (verbose) { + struct procstat ps; + + INTOFF; + commandtext(&ps, getfuncnode(cmdp->param.func)); + INTON; + out1str("() { "); + out1str(ps.cmd); + out1str("; }"); + } + break; + default: + error("internal error: %s cmdtype %d", + cmdp->cmdname, cmdp->cmdtype); + } + if (cmdp->rehash) + out1c('*'); + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(char *name, struct cmdentry *entry, int act, const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + int prev; + char *fullname; + struct stat statb; + int e; + int (*bltin)(int,char **); + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + if (act & DO_ABS) { + while (stat(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + entry->cmdtype = CMDUNKNOWN; + entry->u.index = -1; + return; + } + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + return; + } + + if (path != pathval()) + act |= DO_ALTPATH; + + if (act & DO_ALTPATH && strstr(path, "%builtin") != NULL) + act |= DO_ALTBLTIN; + + /* If name is in the table, check answer will be ok */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + do { + switch (cmdp->cmdtype) { + case CMDNORMAL: + if (act & DO_ALTPATH) { + cmdp = NULL; + continue; + } + break; + case CMDFUNCTION: + if (act & DO_NOFUNC) { + cmdp = NULL; + continue; + } + break; + case CMDBUILTIN: + if ((act & DO_ALTBLTIN) || builtinloc >= 0) { + cmdp = NULL; + continue; + } + break; + } + /* if not invalidated by cd, we're done */ + if (cmdp->rehash == 0) + goto success; + } while (0); + } + + /* If %builtin not in path, check for builtin next */ + if ((act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc < 0) && + (bltin = find_builtin(name)) != 0) + goto builtin_success; + + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; + } + + e = ENOENT; + idx = -1; +loop: + while ((fullname = padvance(&path, name, 1)) != NULL) { + stunalloc(fullname); + idx++; + if (pathopt) { + if (prefix("builtin", pathopt)) { + if ((bltin = find_builtin(name)) == 0) + goto loop; + goto builtin_success; + } else if (prefix("func", pathopt)) { + /* handled below */ + } else { + /* ignore unimplemented options */ + goto loop; + } + } + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + goto loop; + VTRACE(DBG_CMDS, ("searchexec \"%s\": no change\n", + name)); + goto success; + } + while (stat(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + goto loop; + if (pathopt) { /* this is a %func directory */ + char *endname; + + if (act & DO_NOFUNC) + goto loop; + endname = fullname + strlen(fullname) + 1; + grabstackstr(endname); + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || + cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + ungrabstackstr(fullname, endname); + goto success; + } +#ifdef notdef + /* XXX this code stops root executing stuff, and is buggy + if you need a group from the group list. */ + 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 + VTRACE(DBG_CMDS, ("searchexec \"%s\" returns \"%s\"\n", name, + fullname)); + INTOFF; + if (act & DO_ALTPATH) { + /* + * this should be a grabstackstr() but is not needed: + * fullname is no longer needed for anything + stalloc(strlen(fullname) + 1); + */ + cmdp = &loc_cmd; + } else + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INTON; + goto success; + } + + /* We failed. If there was an entry for this command, delete it */ + if (cmdp) + delete_cmd_entry(); + if (act & DO_ERR) + outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC)); + entry->cmdtype = CMDUNKNOWN; + return; + +builtin_success: + INTOFF; + if (act & DO_ALTPATH) + cmdp = &loc_cmd; + else + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + /* DO_NOFUNC must have been set */ + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.bltin = bltin; + INTON; +success: + if (cmdp) { + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->lineno = cmdp->lineno; + entry->lno_frel = cmdp->fn_ln1; + entry->u = cmdp->param; + } else + entry->cmdtype = CMDUNKNOWN; +} + + + +/* + * Search the table of builtin commands. + */ + +int +(*find_builtin(char *name))(int, char **) +{ + const struct builtincmd *bp; + + for (bp = builtincmd ; bp->name ; bp++) { + if (*bp->name == *name + && (*name == '%' || equal(bp->name, name))) + return bp->builtin; + } + return 0; +} + +int +(*find_splbltin(char *name))(int, char **) +{ + const struct builtincmd *bp; + + for (bp = splbltincmd ; bp->name ; bp++) { + if (*bp->name == *name && equal(bp->name, name)) + return bp->builtin; + } + return 0; +} + +/* + * At shell startup put special builtins into hash table. + * ensures they are executed first (see posix). + * We stop functions being added with the same name + * (as they are impossible to call) + */ + +void +hash_special_builtins(void) +{ + const struct builtincmd *bp; + struct tblentry *cmdp; + + for (bp = splbltincmd ; bp->name ; bp++) { + cmdp = cmdlookup(bp->name, 1); + cmdp->cmdtype = CMDSPLBLTIN; + cmdp->param.bltin = bp->builtin; + } +} + + + +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ + +void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)) + cmdp->rehash = 1; + } + } +} + + + +/* + * Fix command hash table when PATH changed. + * 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) +{ + const char *old, *new; + int idx; + int firstchange; + int bltin; + + old = pathval(); + new = newval; + firstchange = 9999; /* assume no change */ + idx = 0; + bltin = -1; + for (;;) { + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') + break; + if (*new == '%' && bltin < 0 && prefix("builtin", new + 1)) + bltin = idx; + if (*new == ':') { + idx++; + } + new++, old++; + } + if (builtinloc < 0 && bltin >= 0) + builtinloc = bltin; /* zap builtins */ + if (builtinloc >= 0 && bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = bltin; +} + + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ + +STATIC void +clearcmdentry(int firstchange) +{ + 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 && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange)) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + INTON; +} + + +/* + * Delete all functions. + */ + +#ifdef mkinit +MKINIT void deletefuncs(void); +MKINIT void hash_special_builtins(void); + +INIT { + hash_special_builtins(); +} + +SHELLPROC { + deletefuncs(); +} +#endif + +void +deletefuncs(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 == CMDFUNCTION) { + *pp = cmdp->next; + freefunc(cmdp->param.func); + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + 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. + */ + +struct tblentry **lastcmdentry; + + +STATIC struct tblentry * +cmdlookup(const char *name, int add) +{ + int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = *p << 4; + while (*p) + hashval += *p++; + hashval &= 0x7FFF; + 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; + cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + cmdp->rehash = 0; + strcpy(cmdp->cmdname, name); + 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; +} + + + +#ifdef notdef +void +getcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp = cmdlookup(name, 0); + + if (cmdp) { + entry->u = cmdp->param; + entry->cmdtype = cmdp->cmdtype; + } else { + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + } +} +#endif + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ + +STATIC void +addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype != CMDSPLBLTIN) { + if (cmdp->cmdtype == CMDFUNCTION) + unreffunc(cmdp->param.func); + cmdp->cmdtype = entry->cmdtype; + cmdp->lineno = entry->lineno; + cmdp->fn_ln1 = entry->lno_frel; + cmdp->param = entry->u; + } + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(char *name, union node *func, int lineno) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.lineno = lineno; + entry.lno_frel = fnline1; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + */ + +int +unsetfunc(char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && + cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + } + return 0; +} + +/* + * Locate and print what a word is... + * also used for 'command -[v|V]' + */ + +int +typecmd(int argc, char **argv) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char * const *pp; + struct alias *ap; + int err = 0; + char *arg; + int c; + int V_flag = 0; + int v_flag = 0; + int p_flag = 0; + + while ((c = nextopt("vVp")) != 0) { + switch (c) { + case 'v': v_flag = 1; break; + case 'V': V_flag = 1; break; + case 'p': p_flag = 1; break; + } + } + + if (argv[0][0] != 'c' && v_flag | V_flag | p_flag) + error("usage: %s name...", argv[0]); + + if (v_flag && V_flag) + error("-v and -V cannot both be specified"); + + if (*argptr == NULL) + error("usage: %s%s name ...", argv[0], + argv[0][0] == 'c' ? " [-p] [-v|-V]" : ""); + + while ((arg = *argptr++)) { + if (!v_flag) + out1str(arg); + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *arg && equal(*pp, arg)) + break; + + if (*pp) { + if (v_flag) + out1fmt("%s\n", arg); + else + out1str(" is a shell keyword\n"); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(arg, 1)) != NULL) { + int ml = 0; + + if (!v_flag) { + out1str(" is an alias "); + if (strchr(ap->val, '\n')) { + out1str("(multiline)...\n"); + ml = 1; + } else + out1str("for: "); + } + out1fmt("%s\n", ap->val); + if (ml && *argptr != NULL) + out1c('\n'); + continue; + } + + /* Then check if it is a tracked alias */ + if (!p_flag && (cmdp = cmdlookup(arg, 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + cmdp = NULL; + /* Finally use brute force */ + find_command(arg, &entry, DO_ABS, + p_flag ? syspath() + 5 : pathval()); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(arg, '/') == NULL) { + const char *path; + char *name; + int j = entry.u.index; + + path = p_flag ? syspath() + 5 : pathval(); + + do { + name = padvance(&path, arg, 1); + stunalloc(name); + } while (--j >= 0); + if (!v_flag) + out1fmt(" is%s ", + cmdp ? " a tracked alias for" : ""); + out1fmt("%s\n", name); + } else { + if (access(arg, X_OK) == 0) { + if (!v_flag) + out1fmt(" is "); + out1fmt("%s\n", arg); + } else { + if (!v_flag) + out1fmt(": %s\n", + strerror(errno)); + else + err = 126; + } + } + break; + } + case CMDFUNCTION: + if (!v_flag) + out1str(" is a shell function\n"); + else + out1fmt("%s\n", arg); + break; + + case CMDBUILTIN: + if (!v_flag) + out1str(" is a shell builtin\n"); + else + out1fmt("%s\n", arg); + break; + + case CMDSPLBLTIN: + if (!v_flag) + out1str(" is a special shell builtin\n"); + else + out1fmt("%s\n", arg); + break; + + default: + if (!v_flag) + out1str(": not found\n"); + err = 127; + break; + } + } + return err; +} diff --git a/bin/sh/exec.h b/bin/sh/exec.h new file mode 100644 index 0000000..90beba1 --- /dev/null +++ b/bin/sh/exec.h @@ -0,0 +1,78 @@ +/* $NetBSD: exec.h,v 1.27 2018/06/22 11:04:55 kre Exp $ */ + +/*- + * 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. + * + * @(#)exec.h 8.3 (Berkeley) 6/8/95 + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ +#define CMDSPLBLTIN 3 /* command is a special shell builtin */ + + +struct cmdentry { + short cmdtype; + short lno_frel; /* for functions: Line numbers count from 1 */ + int lineno; /* for functions: Abs line number of defn */ + union param { + int index; + int (*bltin)(int, char**); + struct funcdef *func; + } u; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ + +extern const char *pathopt; /* set by padvance */ + +void shellexec(char **, char **, const char *, int, int) __dead; +char *padvance(const char **, const char *, int); +void find_command(char *, struct cmdentry *, int, const char *); +int (*find_builtin(char *))(int, char **); +int (*find_splbltin(char *))(int, char **); +void hashcd(void); +void changepath(const char *); +void deletefuncs(void); +void getcmdentry(char *, struct cmdentry *); +union node; +void defun(char *, union node *, int); +int unsetfunc(char *); +void hash_special_builtins(void); diff --git a/bin/sh/expand.c b/bin/sh/expand.c new file mode 100644 index 0000000..955185b --- /dev/null +++ b/bin/sh/expand.c @@ -0,0 +1,2125 @@ +/* $NetBSD: expand.c,v 1.129 2018/12/03 06:41:30 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#else +__RCSID("$NetBSD: expand.c,v 1.129 2018/12/03 06:41:30 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <unistd.h> +#include <pwd.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <wctype.h> +#include <wchar.h> + +/* + * 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 "arithmetic.h" +#include "parser.h" +#include "jobs.h" +#include "options.h" +#include "builtins.h" +#include "var.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" + +/* + * Structure specifying which parts of the string should be searched + * for IFS characters. + */ + +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int inquotes; /* search for nul bytes only */ +}; + + +char *expdest; /* output of current string */ +struct nodelist *argbackq; /* list of back quote expressions */ +struct ifsregion ifsfirst; /* first struct in list of ifs regions */ +struct ifsregion *ifslastp; /* last struct in list */ +struct arglist exparg; /* holds expanded arg list */ + +STATIC const char *argstr(const char *, int); +STATIC const char *exptilde(const char *, int); +STATIC void expbackq(union node *, int, int); +STATIC const char *expari(const char *); +STATIC int subevalvar(const char *, const char *, int, int, int); +STATIC int subevalvar_trim(const char *, int, int, int, int, int); +STATIC const char *evalvar(const char *, int); +STATIC int varisset(const char *, int); +STATIC void varvalue(const char *, int, int, int); +STATIC void recordregion(int, int, int); +STATIC void removerecordregions(int); +STATIC void ifsbreakup(char *, struct arglist *); +STATIC void ifsfree(void); +STATIC void expandmeta(struct strlist *, int); +STATIC void expmeta(char *, char *); +STATIC void addfname(char *); +STATIC struct strlist *expsort(struct strlist *); +STATIC struct strlist *msort(struct strlist *, int); +STATIC int patmatch(const char *, const char *, int); +STATIC char *cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); +STATIC void add_args(struct strlist *); +STATIC void rmescapes_nl(char *); + +#ifdef DEBUG +#define NULLTERM_4_TRACE(p) STACKSTRNUL(p) +#else +#define NULLTERM_4_TRACE(p) do { /* nothing */ } while (/*CONSTCOND*/0) +#endif + +#define IS_BORING(_ch) \ + ((_ch) == CTLQUOTEMARK || (_ch) == CTLQUOTEEND || (_ch) == CTLNONL) +#define SKIP_BORING(p) \ + do { \ + char _ch; \ + \ + while ((_ch = *(p)), IS_BORING(_ch)) \ + (p)++; \ + } while (0) + +/* + * Expand shell variables and backquotes inside a here document. + */ + +void +expandhere(union node *arg, int fd) +{ + + herefd = fd; + expandarg(arg, NULL, 0); + xwrite(fd, stackblock(), expdest - stackblock()); +} + + +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)); +} + +/* + * Perform variable substitution and command substitution on an argument, + * placing the resulting list of arguments in arglist. If EXP_FULL is true, + * perform splitting and file name expansion. When arglist is NULL, perform + * here document expansion. + */ + +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct strlist *sp; + char *p; + + CTRACE(DBG_EXPAND, ("expandarg(fl=%#x)\n", flag)); + if (fflag) /* no filename expandsion */ + flag &= ~EXP_GLOB; + + argbackq = arg->narg.backquote; + STARTSTACKSTR(expdest); + ifsfirst.next = NULL; + ifslastp = NULL; + line_number = arg->narg.lineno; + argstr(arg->narg.text, flag); + if (arglist == NULL) { + STACKSTRNUL(expdest); + CTRACE(DBG_EXPAND, ("expandarg: no arglist, done (%d) \"%s\"\n", + expdest - stackblock(), stackblock())); + return; /* here document expanded */ + } + STPUTC('\0', expdest); + CTRACE(DBG_EXPAND, ("expandarg: arglist got (%d) \"%s\"\n", + expdest - stackblock() - 1, stackblock())); + p = grabstackstr(expdest); + exparg.lastp = &exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_SPLIT) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + if (flag & EXP_GLOB) + expandmeta(exparg.list, flag); + else + add_args(exparg.list); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(p); + sp = stalloc(sizeof(*sp)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + + + +/* + * Perform variable and command substitution. + * If EXP_GLOB is set, output CTLESC characters to allow for further processing. + * If EXP_SPLIT is set, remember location of result for later, + * Otherwise treat $@ like $* since no splitting will be performed. + */ + +STATIC const char * +argstr(const char *p, int flag) +{ + char c; + const int quotes = flag & EXP_QNEEDED; /* do CTLESC */ + int firsteq = 1; + const char *ifs = NULL; + int ifs_split = EXP_IFS_SPLIT; + + if (flag & EXP_IFS_SPLIT) + ifs = ifsval(); + + CTRACE(DBG_EXPAND, ("argstr(\"%s\", %#x) quotes=%#x\n", p,flag,quotes)); + + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + switch (c = *p++) { + case '\0': + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr returning at \"\" " + "added \"%s\" to expdest\n", stackblock())); + return p - 1; + case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */ + case CTLENDARI: /* end of a $(( )) string */ + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr returning at \"%.6s\"..." + " after %2.2X; added \"%s\" to expdest\n", + p, (c&0xff), stackblock())); + return p; + case CTLQUOTEMARK: + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && p[1] & VSQUOTE && + p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_SPLIT) != 0) + STPUTC(c, expdest); + ifs_split = 0; + break; + case CTLNONL: + if (flag & EXP_NL) + STPUTC(c, expdest); + line_number++; + break; + case CTLCNL: + STPUTC('\n', expdest); /* no line_number++ */ + break; + case CTLQUOTEEND: + if ((flag & EXP_SPLIT) != 0) + STPUTC(c, expdest); + ifs_split = EXP_IFS_SPLIT; + break; + case CTLESC: + if (quotes) + STPUTC(c, expdest); + c = *p++; + STPUTC(c, expdest); + if (c == '\n') /* should not happen, but ... */ + line_number++; + break; + case CTLVAR: { +#ifdef DEBUG + unsigned int pos = expdest - stackblock(); + NULLTERM_4_TRACE(expdest); +#endif + p = evalvar(p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split)); + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr evalvar " + "added %zd \"%s\" to expdest\n", + (size_t)(expdest - (stackblock() + pos)), + stackblock() + pos)); + break; + } + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: { +#ifdef DEBUG + unsigned int pos = expdest - stackblock(); +#endif + expbackq(argbackq->n, c & CTLQUOTE, flag); + argbackq = argbackq->next; + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr expbackq added \"%s\" " + "to expdest\n", stackblock() + pos)); + break; + } + case CTLARI: { +#ifdef DEBUG + unsigned int pos = expdest - stackblock(); +#endif + p = expari(p); + NULLTERM_4_TRACE(expdest); + VTRACE(DBG_EXPAND, ("argstr expari " + "+ \"%s\" to expdest p=\"%.5s...\"\n", + stackblock() + pos, p)); + break; + } + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + STPUTC(c, expdest); + if (flag & EXP_VARTILDE && *p == '~') { + if (c == '=') { + if (firsteq) + firsteq = 0; + else + break; + } + p = exptilde(p, flag); + } + break; + default: + if (c == '\n') + line_number++; + STPUTC(c, expdest); + if (flag & ifs_split && strchr(ifs, c) != NULL) { + /* We need to get the output split here... */ + recordregion(expdest - stackblock() - 1, + expdest - stackblock(), 0); + } + break; + } + } +} + +STATIC const char * +exptilde(const char *p, int flag) +{ + char c; + const char *startp = p; + struct passwd *pw; + const char *home; + const int quotes = flag & EXP_QNEEDED; + char *user; + struct stackmark smark; +#ifdef DEBUG + unsigned int offs = expdest - stackblock(); +#endif + + setstackmark(&smark); + (void) grabstackstr(expdest); + user = stackblock(); /* we will just borrow top of stack */ + + while ((c = *++p) != '\0') { + switch(c) { + case CTLESC: /* any of these occurring */ + case CTLVAR: /* means ~ expansion */ + case CTLBACKQ: /* does not happen at all */ + case CTLBACKQ | CTLQUOTE: + case CTLARI: /* just leave original unchanged */ + case CTLENDARI: + case CTLQUOTEMARK: + case '\n': + popstackmark(&smark); + return (startp); + case CTLNONL: + continue; + case ':': + if (!posix || flag & EXP_VARTILDE) + goto done; + break; + case CTLENDVAR: + case '/': + goto done; + } + STPUTC(c, user); + } + done: + STACKSTRNUL(user); + user = stackblock(); /* to start of collected username */ + + CTRACE(DBG_EXPAND, ("exptilde, found \"~%s\"", user)); + if (*user == '\0') { + home = lookupvar("HOME"); + /* + * if HOME is unset, results are unspecified... + * we used to just leave the ~ unchanged, but + * (some) other shells do ... and this seems more useful. + */ + if (home == NULL && (pw = getpwuid(getuid())) != NULL) + home = pw->pw_dir; + } else if ((pw = getpwnam(user)) == NULL) { + /* + * If user does not exist, results are undefined. + * so we can abort() here if we want, but let's not! + */ + home = NULL; + } else + home = pw->pw_dir; + + VTRACE(DBG_EXPAND, (" ->\"%s\"", home ? home : "<<NULL>>")); + popstackmark(&smark); /* now expdest is valid again */ + + /* + * Posix XCU 2.6.1: The value of $HOME (for ~) or the initial + * working directory from getpwnam() for ~user + * Nothing there about "except if a null string". So do what it wants. + */ + if (home == NULL /* || *home == '\0' */) { + CTRACE(DBG_EXPAND, (": returning unused \"%s\"\n", startp)); + return startp; + } while ((c = *home++) != '\0') { + if (quotes && NEEDESC(c)) + STPUTC(CTLESC, expdest); + STPUTC(c, expdest); + } + CTRACE(DBG_EXPAND, (": added %d \"%.*s\" returning \"%s\"\n", + expdest - stackblock() - offs, expdest - stackblock() - offs, + stackblock() + offs, p)); + + return (p); +} + + +STATIC void +removerecordregions(int endoff) +{ + + VTRACE(DBG_EXPAND, ("removerecordregions(%d):", endoff)); + if (ifslastp == NULL) { + VTRACE(DBG_EXPAND, (" none\n", endoff)); + return; + } + + if (ifsfirst.endoff > endoff) { + VTRACE(DBG_EXPAND, (" first(%d)", ifsfirst.endoff)); + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + VTRACE(DBG_EXPAND,("->(%d,%d)",ifsfirst.begoff,endoff)); + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + VTRACE(DBG_EXPAND, ("\n")); + return; + } + + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + VTRACE(DBG_EXPAND, (" found(%d,%d)", ifslastp->begoff,ifslastp->endoff)); + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifslastp->next->next; + ckfree(ifslastp->next); + ifslastp->next = ifsp; + INTON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; + VTRACE(DBG_EXPAND, ("->(%d,%d)", ifslastp->begoff,ifslastp->endoff)); +} + + +/* + * Expand arithmetic expression. + * + * In this incarnation, we start at the beginning (yes, "Let's start at the + * very beginning. A very good place to start.") and collect the expression + * until the end - which means expanding anything contained within. + * + * Fortunately, argstr() just happens to do that for us... + */ +STATIC const char * +expari(const char *p) +{ + char *q, *start; + intmax_t result; + int adjustment; + int begoff; + int quoted; + struct stackmark smark; + + /* ifsfree(); */ + + /* + * SPACE_NEEDED is enough for all possible digits (rounded up) + * plus possible "-", and the terminating '\0', hence, plus 2 + * + * The calculation produces the number of bytes needed to + * represent the biggest possible value, in octal. We only + * generate decimal, which takes (often) less digits (never more) + * so this is safe, if occasionally slightly wasteful. + */ +#define SPACE_NEEDED ((int)((sizeof(intmax_t) * CHAR_BIT + 2) / 3 + 2)) + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + VTRACE(DBG_EXPAND, ("expari%s: \"%s\" begoff %d\n", + quoted ? "(quoted)" : "", p, begoff)); + + p = argstr(p, EXP_NL); /* expand $(( )) string */ + STPUTC('\0', expdest); + start = stackblock() + begoff; + + removerecordregions(begoff); /* nothing there is kept */ + rmescapes_nl(start); /* convert CRTNONL back into \n's */ + + setstackmark(&smark); + q = grabstackstr(expdest); /* keep the expression while eval'ing */ + result = arith(start, line_number); + popstackmark(&smark); /* return the stack to before grab */ + + start = stackblock() + begoff; /* block may have moved */ + adjustment = expdest - start; + STADJUST(-adjustment, expdest); /* remove the argstr() result */ + + CHECKSTRSPACE(SPACE_NEEDED, expdest); /* nb: stack block might move */ + fmtstr(expdest, SPACE_NEEDED, "%"PRIdMAX, result); + + for (q = expdest; *q++ != '\0'; ) /* find end of what we added */ + ; + + if (quoted == 0) /* allow weird splitting */ + recordregion(begoff, begoff + q - 1 - expdest, 0); + adjustment = q - expdest - 1; + STADJUST(adjustment, expdest); /* move expdest to end */ + VTRACE(DBG_EXPAND, ("expari: adding %d \"%s\", returning \"%.5s...\"\n", + adjustment, stackblock() + begoff, p)); + + return p; +} + + +/* + * Expand stuff in backwards quotes (these days, any command substitution). + */ + +STATIC void +expbackq(union node *cmd, int quoted, int flag) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; /* expdest may be reused by eval, use an alt */ + struct ifsregion saveifs, *savelastp; + struct nodelist *saveargbackq; + char lastc; + int startloc = dest - stackblock(); + int saveherefd; + const int quotes = flag & EXP_QNEEDED; + int nnl; + struct stackmark smark; + + VTRACE(DBG_EXPAND, ("expbackq( ..., q=%d flag=%#x) have %d\n", + quoted, flag, startloc)); + INTOFF; + saveifs = ifsfirst; + savelastp = ifslastp; + saveargbackq = argbackq; + saveherefd = herefd; + herefd = -1; + + setstackmark(&smark); /* preserve the stack */ + p = grabstackstr(dest); /* save what we have there currently */ + evalbackcmd(cmd, &in); /* evaluate the $( ) tree (using stack) */ + popstackmark(&smark); /* and return stack to when we entered */ + + ifsfirst = saveifs; + ifslastp = savelastp; + argbackq = saveargbackq; + herefd = saveherefd; + + p = in.buf; /* now extract the results */ + nnl = 0; /* dropping trailing \n's */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + INTON; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) + continue; + INTOFF; + VTRACE(DBG_EXPAND, ("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc != '\0') { + if (lastc == '\n') /* don't save \n yet */ + nnl++; /* it might be trailing */ + else { + /* + * We have something other than \n + * + * Before saving it, we need to insert + * any \n's that we have just skipped. + */ + + /* XXX + * this hack is just because our + * CHECKSTRSPACE() is lazy, and only + * ever grows the stack once, even + * if that does not allocate the space + * we requested. ie: safe for small + * requests, but not large ones. + * FIXME someday... + */ + if (nnl < 20) { + CHECKSTRSPACE(nnl + 2, dest); + while (nnl > 0) { + nnl--; + USTPUTC('\n', dest); + } + } else { + /* The slower, safer, way */ + while (nnl > 0) { + nnl--; + STPUTC('\n', dest); + } + CHECKSTRSPACE(2, dest); + } + if (quotes && quoted && NEEDESC(lastc)) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + } + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) + back_exitstatus = waitforjob(in.jp); + if (quoted == 0) + recordregion(startloc, dest - stackblock(), 0); + CTRACE(DBG_EXPAND, ("evalbackq: size=%d: \"%.*s\"\n", + (int)((dest - stackblock()) - startloc), + (int)((dest - stackblock()) - startloc), + stackblock() + startloc)); + + expdest = dest; /* all done, expdest is all ours again */ + INTON; +} + + +STATIC int +subevalvar(const char *p, const char *str, int subtype, int startloc, + int varflags) +{ + char *startp; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + + herefd = -1; + VTRACE(DBG_EXPAND, ("subevalvar(%d) \"%.20s\" ${%.*s} sloc=%d vf=%x\n", + subtype, p, p-str, str, startloc, varflags)); + argstr(p, subtype == VSASSIGN ? EXP_VARTILDE : EXP_TILDE); + STACKSTRNUL(expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; /* remove what argstr added */ + STADJUST(amount, expdest); + varflags &= ~VSNUL; /*XXX Huh? What's that achieve? */ + return 1; /* go back and eval var again */ + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(&errout, "%s\n", startp); + error(NULL); + } + error("%.*s: parameter %snot set", + (int)(p - str - 1), + str, (varflags & VSNUL) ? "null or " + : nullstr); + /* NOTREACHED */ + + default: + abort(); + } +} + +STATIC int +subevalvar_trim(const char *p, int strloc, int subtype, int startloc, + int varflags, int quotes) +{ + char *startp; + char *str; + char *loc = NULL; + char *q; + int c = 0; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + + herefd = -1; + switch (subtype) { + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + break; + default: + abort(); + break; + } + + VTRACE(DBG_EXPAND, + ("subevalvar_trim(\"%.9s\", STR@%d, SUBT=%d, start@%d, vf=%x, q=%x)\n", + p, strloc, subtype, startloc, varflags, quotes)); + + argstr(p, (varflags & (VSQUOTE|VSPATQ)) == VSQUOTE ? 0 : EXP_CASE); + STACKSTRNUL(expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; + str = stackblock() + strloc; + + switch (subtype) { + + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp, quotes)) + goto recordleft; + *loc = c; + if (quotes && *loc == CTLESC) + loc++; + } + return 0; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp, quotes)) + goto recordleft; + *loc = c; + loc--; + if (quotes && loc > startp && + *(loc - 1) == CTLESC) { + for (q = startp; q < loc; q++) + if (*q == CTLESC) + q++; + if (q > loc) + loc--; + } + } + return 0; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc, quotes)) + goto recordright; + loc--; + if (quotes && loc > startp && + *(loc - 1) == CTLESC) { + for (q = startp; q < loc; q++) + if (*q == CTLESC) + q++; + if (q > loc) + loc--; + } + } + return 0; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc, quotes)) + goto recordright; + if (quotes && *loc == CTLESC) + loc++; + } + return 0; + + default: + abort(); + } + + recordleft: + *loc = c; + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; + return 1; + + recordright: + amount = loc - expdest; + STADJUST(amount, expdest); + STPUTC('\0', expdest); + STADJUST(-1, expdest); + return 1; +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +STATIC const char * +evalvar(const char *p, int flag) +{ + int subtype; + int varflags; + const char *var; + char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int apply_ifs; + const int quotes = flag & EXP_QNEEDED; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = !is_name(*p); + p = strchr(p, '=') + 1; + + CTRACE(DBG_EXPAND, + ("evalvar \"%.*s\", flag=%#X quotes=%#X vf=%#X subtype=%X\n", + p - var - 1, var, flag, quotes, varflags, subtype)); + + again: /* jump here after setting a variable with ${var=text} */ + if (varflags & VSLINENO) { + if (line_num.flags & VUNSET) { + set = 0; + val = NULL; + } else { + set = 1; + special = p - var; + val = NULL; + } + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = lookupvar(var); + 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); + /* NOTREACHED */ + } + } + + if (!set && subtype != VSPLUS && special && *var == '@') + if (startloc > 0 && expdest[-1] == CTLQUOTEMARK) + expdest--, startloc--; + + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) { + /* + * The LINENO hack (expansion part) + */ + while (--special > 0) { +/* not needed, it is a number... + if (quotes && NEEDESC(*var)) + STPUTC(CTLESC, expdest); +*/ + STPUTC(*var++, expdest); + } + } else + varvalue(var, varflags&VSQUOTE, subtype, flag); + if (subtype == VSLENGTH) { + varlen = expdest - stackblock() - startloc; + STADJUST(-varlen, expdest); + } + } else { + + if (subtype == VSLENGTH) { + for (;*val; val++) + varlen++; + } else if (quotes && varflags & VSQUOTE) { + for (; (c = *val) != '\0'; val++) { + if (NEEDESC(c)) + STPUTC(CTLESC, expdest); + STPUTC(c, expdest); + } + } else { + while (*val) + STPUTC(*val++, expdest); + } + } + } + + + if (varflags & VSQUOTE) { + if (*var == '@' && shellparam.nparam != 1) + apply_ifs = 1; + else { + /* + * Mark so that we don't apply IFS if we recurse through + * here expanding $bar from "${foo-$bar}". + */ + flag |= EXP_IN_QUOTES; + apply_ifs = 0; + } + } else if (flag & EXP_IN_QUOTES) { + apply_ifs = 0; + } else + apply_ifs = 1; + + switch (subtype) { + case VSLENGTH: + expdest = cvtnum(varlen, expdest); + break; + + case VSNORMAL: + break; + + case VSPLUS: + set = !set; + /* FALLTHROUGH */ + case VSMINUS: + if (!set) { + argstr(p, flag | (apply_ifs ? EXP_IFS_SPLIT : 0)); + /* + * ${x-a b c} doesn't get split, but removing the + * 'apply_ifs = 0' apparently breaks ${1+"$@"}.. + * ${x-'a b' c} should generate 2 args. + */ + if (*p != CTLENDVAR) + /* We should have marked stuff already */ + apply_ifs = 0; + } + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) { + set = 1; /* allow argbackq to be advanced if needed */ + break; + } + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + if (subevalvar_trim(p, patloc, subtype, startloc, varflags, + quotes) == 0) { + int amount = (expdest - stackblock() - patloc) + 1; + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + apply_ifs = 1; + break; + + case VSASSIGN: + case VSQUESTION: + if (set) + break; + if (subevalvar(p, var, subtype, startloc, varflags)) { + /* if subevalvar() returns, it always returns 1 */ + + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + apply_ifs = 0; /* never executed */ + break; + + default: + abort(); + } + + if (apply_ifs) + recordregion(startloc, expdest - stackblock(), + varflags & VSQUOTE); + + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLNONL) + ; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (set) + 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 special parameter is set. + */ + +STATIC int +varisset(const char *name, int nulok) +{ + if (*name == '!') + return backgndpid != -1; + 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; + + /* + * handle overflow sensibly (the *ap tests should never fail) + */ + errno = 0; + num = strtol(name, &ap, 10); + if (errno != 0 || (*ap != '\0' && *ap != '=')) + return 0; + + if (num == 0) + ap = arg0; + else if (num > shellparam.nparam) + return 0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + + + +/* + * Add the value of a specialized variable to the stack string. + */ + +STATIC void +varvalue(const char *name, int quoted, int subtype, int flag) +{ + int num; + char *p; + int i; + int sep; + char **ap; +#ifdef DEBUG + char *start = expdest; +#endif + + VTRACE(DBG_EXPAND, ("varvalue(%c%s, sub=%d, fl=%#x)", *name, + quoted ? ", quoted" : "", subtype, flag)); + + if (subtype == VSLENGTH) /* no magic required ... */ + flag &= ~EXP_FULL; + +#define STRTODEST(p) \ + do {\ + if ((flag & EXP_QNEEDED) && quoted) { \ + while (*p) { \ + if (NEEDESC(*p)) \ + STPUTC(CTLESC, expdest); \ + STPUTC(*p++, expdest); \ + } \ + } else \ + while (*p) \ + STPUTC(*p++, expdest); \ + } while (0) + + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = exitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpid; + break; + case '-': + for (i = 0; i < option_flags; i++) { + if (optlist[optorder[i]].val) + STPUTC(optlist[optorder[i]].letter, expdest); + } + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); + return; + case '@': + if (flag & EXP_SPLIT && quoted) { + VTRACE(DBG_EXPAND, (": $@ split (%d)\n", + shellparam.nparam)); + /* GROSS HACK */ + if (shellparam.nparam == 0 && + expdest[-1] == CTLQUOTEMARK) + expdest--; + /* KCAH SSORG */ + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + STRTODEST(p); + if (*ap) + /* A NUL separates args inside "" */ + STPUTC('\0', expdest); + } + return; + } + /* fall through */ + case '*': + sep = ifsval()[0]; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + STRTODEST(p); + if (!*ap) + break; + if (sep) { + if (quoted && (flag & EXP_QNEEDED) && + NEEDESC(sep)) + STPUTC(CTLESC, expdest); + STPUTC(sep, expdest); + } else + if ((flag & (EXP_SPLIT|EXP_IN_QUOTES)) == EXP_SPLIT + && !quoted && **ap != '\0') + STPUTC('\0', expdest); + } + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); + return; + default: + if (is_digit(*name)) { + long lnum; + + errno = 0; + lnum = strtol(name, &p, 10); + if (errno != 0 || (*p != '\0' && *p != '=')) + return; + + if (lnum == 0) + p = arg0; + else if (lnum > 0 && lnum <= shellparam.nparam) + p = shellparam.p[lnum - 1]; + else + return; + STRTODEST(p); + } + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); + return; + } + /* + * only the specials with an int value arrive here + */ + VTRACE(DBG_EXPAND, ("(%d)", num)); + expdest = cvtnum(num, expdest); + VTRACE(DBG_EXPAND, (": %.*s\n", expdest-start, start)); +} + + + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ + +STATIC void +recordregion(int start, int end, int inquotes) +{ + struct ifsregion *ifsp; + + VTRACE(DBG_EXPAND, ("recordregion(%d,%d,%d)\n", start, end, inquotes)); + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + if (ifslastp->endoff == start + && ifslastp->inquotes == inquotes) { + /* extend previous area */ + ifslastp->endoff = end; + return; + } + ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); + ifslastp->next = ifsp; + } + ifslastp = ifsp; + ifslastp->next = NULL; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->inquotes = inquotes; +} + + + +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ +STATIC void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs; + const char *ifsspc; + int had_param_ch = 0; + + start = string; + + VTRACE(DBG_EXPAND, ("ifsbreakup(\"%s\")", string)); /* misses \0's */ + if (ifslastp == NULL) { + /* Return entire argument, IFS doesn't apply to any of it */ + VTRACE(DBG_EXPAND, ("no regions\n", string)); + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + return; + } + + ifs = ifsval(); + + for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { + p = string + ifsp->begoff; + VTRACE(DBG_EXPAND, (" !%.*s!(%d)", ifsp->endoff-ifsp->begoff, + p, ifsp->endoff-ifsp->begoff)); + while (p < string + ifsp->endoff) { + had_param_ch = 1; + q = p; + if (IS_BORING(*p)) { + p++; + continue; + } + if (*p == CTLESC) + p++; + if (ifsp->inquotes) { + /* Only NULs (should be from "$@") end args */ + if (*p != 0) { + p++; + continue; + } + ifsspc = NULL; + VTRACE(DBG_EXPAND, (" \\0 nxt:\"%s\" ", p)); + } else { + if (!strchr(ifs, *p)) { + p++; + continue; + } + had_param_ch = 0; + ifsspc = strchr(" \t\n", *p); + + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc != NULL) { + p++; + start = p; + continue; + } + } + + /* Save this argument... */ + *q = '\0'; + VTRACE(DBG_EXPAND, ("<%s>", start)); + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + + if (ifsspc != NULL) { + /* Ignore further trailing IFS whitespace */ + for (; p < string + ifsp->endoff; p++) { + q = p; + if (*p == CTLNONL) + continue; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL) { + p = q; + break; + } + if (strchr(" \t\n", *p) == NULL) { + p++; + break; + } + } + } + start = p; + } + } + + while (*start == CTLQUOTEEND) + start++; + + /* + * Save anything left as an argument. + * Traditionally we have treated 'IFS=':'; set -- x$IFS' as + * generating 2 arguments, the second of which is empty. + * Some recent clarification of the Posix spec say that it + * should only generate one.... + */ + if (had_param_ch || *start != 0) { + VTRACE(DBG_EXPAND, (" T<%s>", start)); + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + } + VTRACE(DBG_EXPAND, ("\n")); +} + +STATIC void +ifsfree(void) +{ + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + ifslastp = NULL; + ifsfirst.next = NULL; +} + + + +/* + * Expand shell metacharacters. At this point, the only control characters + * should be escapes. The results are stored in the list exparg. + */ + +char *expdir; + + +STATIC void +expandmeta(struct strlist *str, int flag) +{ + char *p; + struct strlist **savelastp; + struct strlist *sp; + char c; + /* TODO - EXP_REDIR */ + + while (str) { + p = str->text; + for (;;) { /* fast check for meta chars */ + if ((c = *p++) == '\0') + goto nometa; + if (c == '*' || c == '?' || c == '[' /* || c == '!' */) + break; + } + savelastp = exparg.lastp; + INTOFF; + if (expdir == NULL) { + int i = strlen(str->text); + expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(expdir, str->text); + ckfree(expdir); + expdir = NULL; + INTON; + if (exparg.lastp == savelastp) { + /* + * no matches + */ + nometa: + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + } else { + *exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + exparg.lastp = &sp->next; + } + str = str->next; + } +} + +STATIC void +add_args(struct strlist *str) +{ + while (str) { + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + str = str->next; + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +STATIC void +expmeta(char *enddir, char *name) +{ + char *p; + const char *cp; + char *q; + char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + + CTRACE(DBG_EXPAND|DBG_MATCH, ("expmeta(\"%s\")\n", name)); + metaflag = 0; + start = name; + for (p = name ; ; p++) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + while (IS_BORING(*q)) + q++; + if (*q == ']') { + q++; + metaflag = 1; + break; + } + if (*q == '[' && q[1] == ':') { + /* + * character class, look for :] ending + * also stop on ']' (end bracket expr) + * or '\0' or '/' (end pattern) + */ + while (*++q != '\0' && *q != ']' && + *q != '/') { + if (*q == CTLESC) { + if (*++q == '\0') + break; + if (*q == '/') + break; + } else if (*q == ':' && + q[1] == ']') + break; + } + if (*q == ':') { + /* + * stopped at ':]' + * still in [...] + * skip ":]" and continue; + */ + q += 2; + continue; + } + + /* done at end of pattern, not [...] */ + if (*q == '\0' || *q == '/') + break; + + /* found the ']', we have a [...] */ + metaflag = 1; + q++; /* skip ']' */ + break; + } + if (*q == CTLESC) + q++; + /* end of pattern cannot be escaped */ + if (*q == '/' || *q == '\0') + break; + q++; + } + } else if (*p == '\0') + break; + else if (IS_BORING(*p)) + continue; + else if (*p == CTLESC) + p++; + if (*p == '/') { + if (metaflag) + break; + start = p + 1; + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (IS_BORING(*p)) + continue; + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = p; + if (start != name) { + p = name; + while (p < start) { + while (IS_BORING(*p)) + p++; + if (*p == CTLESC) + p++; + *enddir++ = *p++; + } + } + if (enddir == expdir) { + cp = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + cp = "/"; + } else { + cp = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(cp)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname++ = '\0'; + } + matchdot = 0; + p = start; + while (IS_BORING(*p)) + p++; + 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, 0)) { + if (atend) { + scopy(dp->d_name, enddir); + addfname(expdir); + } else { + for (p = enddir, cp = dp->d_name; + (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(p, endname); + } + } + } + closedir(dirp); + if (! atend) + endname[-1] = '/'; +} + + +/* + * Add a file name to the list. + */ + +STATIC void +addfname(char *name) +{ + char *p; + struct strlist *sp; + + p = stalloc(strlen(name) + 1); + scopy(name, p); + sp = stalloc(sizeof(*sp)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; +} + + +/* + * Sort the results of file name expansion. It calculates the number of + * strings to sort and then calls msort (short for merge sort) to do the + * work. + */ + +STATIC struct strlist * +expsort(struct strlist *str) +{ + int len; + struct strlist *sp; + + len = 0; + for (sp = str ; sp ; sp = sp->next) + len++; + return msort(str, len); +} + + +STATIC struct strlist * +msort(struct strlist *list, int len) +{ + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; + + if (len <= 1) + return list; + half = len >> 1; + p = list; + for (n = half ; --n >= 0 ; ) { + q = p; + p = p->next; + } + q->next = NULL; /* terminate first half of list */ + q = msort(list, half); /* sort first half of list */ + p = msort(p, len - half); /* sort second half */ + lpp = &list; + for (;;) { + if (strcmp(p->text, q->text) < 0) { + *lpp = p; + lpp = &p->next; + if ((p = *lpp) == NULL) { + *lpp = q; + break; + } + } else { + *lpp = q; + lpp = &q->next; + if ((q = *lpp) == NULL) { + *lpp = p; + break; + } + } + } + return list; +} + + +/* + * 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]; + char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || nameend == p) /* not a valid class */ + return 0; + + if (!is_alpha(*p) || strspn(p, /* '_' is a local extension */ + "0123456789" "_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != (size_t)(nameend - p)) + return 0; + + *end = nameend + 2; /* committed to it being a char class */ + if ((size_t)(nameend - p) >= sizeof(name)) /* but too long */ + return 0; /* so no match */ + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + 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, int squoted) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + VTRACE(DBG_MATCH, ("patmatch(P=\"%s\", W=\"%s\"%s): ", + pattern, string, squoted ? ", SQ" : "")); + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (squoted && *q == CTLESC) { + if (q[1] == '\0') + q++; + } + if (*q != '\0') + goto backtrack; + VTRACE(DBG_MATCH, ("match\n")); + return 1; + case CTLESC: + if (squoted && *q == CTLESC) + q++; + if (*p == '\0' && *q == '\0') { + VTRACE(DBG_MATCH, ("match-\\\n")); + return 1; + } + if (*q++ != *p++) + goto backtrack; + break; + case '\\': + if (squoted && *q == CTLESC) + q++; + if (*q++ != *p++) + goto backtrack; + break; + case CTLQUOTEMARK: + case CTLQUOTEEND: + case CTLNONL: + continue; + case '?': + if (squoted && *q == CTLESC) + q++; + if (*q++ == '\0') { + VTRACE(DBG_MATCH, ("?fail\n")); + return 0; + } + break; + case '*': + c = *p; + while (c == CTLQUOTEMARK || c == '*') + c = *++p; + if (c != CTLESC && !IS_BORING(c) && + c != '?' && c != '*' && c != '[') { + while (*q != c) { + if (squoted && *q == CTLESC && + q[1] == c) + break; + if (*q == '\0') { + VTRACE(DBG_MATCH, ("*fail\n")); + return 0; + } + if (squoted && *q == CTLESC) + q++; + q++; + } + } + if (c == CTLESC && p[1] == '\0') { + VTRACE(DBG_MATCH, ("match+\\\n")); + 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, *endp; + int invert, found; + unsigned char chr; + + /* + * First quick check to see if there is a + * possible matching ']' - if not, then this + * is not a char class, and the '[' is just + * a literal '['. + * + * This check will not detect all non classes, but + * that's OK - It just means that we execute the + * harder code sometimes when it it cannot succeed. + */ + endp = p; + if (*endp == '!' || *endp == '^') + endp++; + for (;;) { + while (IS_BORING(*endp)) + endp++; + if (*endp == '\0') + goto dft; /* no matching ] */ + if (*endp++ == ']') + break; + } + /* end shortcut */ + + invert = 0; + savep = p, saveq = q; + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (*q == '\0') { + VTRACE(DBG_MATCH, ("[]fail\n")); + return 0; + } + if (squoted && *q == CTLESC) + q++; + chr = (unsigned char)*q++; + c = *p++; + do { + if (IS_BORING(c)) + continue; + if (c == '\0') { + p = savep, q = saveq; + c = '['; + goto dft; + } + if (c == '[' && *p == ':') { + found |= match_charclass(p, chr, &end); + if (end != NULL) { + p = end; + continue; + } + } + if (c == CTLESC || c == '\\') + c = *p++; + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + if (*p == CTLESC || *p == '\\') + p++; + 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 (squoted && *q == CTLESC) + q++; + 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) { + VTRACE(DBG_MATCH, ("BTP fail\n")); + return 0; + } + if (*bt_q == '\0') { + VTRACE(DBG_MATCH, ("BTQ fail\n")); + return 0; + } + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC or CTLNONL characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && !IS_BORING(*p)) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (IS_BORING(*p)) { + p++; + continue; + } + if (*p == CTLCNL) { + p++; + *q++ = '\n'; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + +/* + * and a special version for dealing with expressions to be parsed + * by the arithmetic evaluator. That needs to be able to count \n's + * even ones that were \newline elided \n's, so we have to put the + * latter back into the string - just being careful to put them only + * at a place where white space can reasonably occur in the string + * -- then the \n we insert will just be white space, and ignored + * for all purposes except line counting. + */ + +void +rmescapes_nl(char *str) +{ + char *p, *q; + int nls = 0, holdnl = 0, holdlast; + + p = str; + while (*p != CTLESC && !IS_BORING(*p)) { + if (*p++ == '\0') + return; + } + if (p > str) /* must reprocess char before stopper (if any) */ + --p; /* so we do not place a \n badly */ + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLNONL) { + p++; + nls++; + continue; + } + if (*p == CTLCNL) { + p++; + *q++ = '\n'; + continue; + } + if (*p == CTLESC) + p++; + + holdlast = holdnl; + holdnl = is_in_name(*p); /* letters, digits, _ */ + if (q == str || is_space(q[-1]) || (*p != '=' && q[-1] != *p)) { + if (nls > 0 && holdnl != holdlast) { + while (nls > 0) + *q++ = '\n', nls--; + } + } + *q++ = *p++; + } + while (--nls >= 0) + *q++ = '\n'; + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, char *val) +{ + struct stackmark smark; + int result; + char *p; + + CTRACE(DBG_MATCH, ("casematch(P=\"%s\", W=\"%s\")\n", + pattern->narg.text, val)); + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val, 0); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). Assumes result buffer is on the stack + */ + +STATIC char * +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + sizeof temp - 1; + + if (neg) + num = -num; + + *p = '\0'; + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0 && p > temp + 1); + + if (neg) + *--p = '-'; + + while (*p) + STPUTC(*p++, buf); + return buf; +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%d", argc - 1); + out1c('\0'); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%zu", len); + out1c('\0'); + for (i = 1; i < argc; i++) { + out1str(argv[i]); + out1c('\0'); + } + return (0); +} diff --git a/bin/sh/expand.h b/bin/sh/expand.h new file mode 100644 index 0000000..d435fd8 --- /dev/null +++ b/bin/sh/expand.h @@ -0,0 +1,71 @@ +/* $NetBSD: expand.h,v 1.24 2018/11/18 17:23:37 kre Exp $ */ + +/*- + * 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. + * + * @(#)expand.h 8.2 (Berkeley) 5/4/95 + */ + +#include <inttypes.h> + +struct strlist { + struct strlist *next; + char *text; +}; + + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* + * expandarg() flags + */ +#define EXP_SPLIT 0x1 /* perform word splitting */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_IFS_SPLIT 0x20 /* need to record arguments for ifs breakup */ +#define EXP_IN_QUOTES 0x40 /* don't set EXP_IFS_SPLIT again */ +#define EXP_GLOB 0x80 /* perform filename globbing */ +#define EXP_NL 0x100 /* keep CRTNONL in output */ + +#define EXP_FULL (EXP_SPLIT | EXP_GLOB) +#define EXP_QNEEDED (EXP_GLOB | EXP_CASE | EXP_REDIR) + +union node; + +void expandhere(union node *, int); +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, char *); diff --git a/bin/sh/funcs/cmv b/bin/sh/funcs/cmv new file mode 100644 index 0000000..0e3eef6 --- /dev/null +++ b/bin/sh/funcs/cmv @@ -0,0 +1,43 @@ +# $NetBSD: cmv,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)cmv 8.2 (Berkeley) 5/4/95 + +# Conditional move--don't replace an existing file. + +cmv() { + if test $# != 2 + then echo "cmv: arg count" + return 2 + fi + if test -f "$2" -o -w "$2" + then echo "$2 exists" + return 2 + fi + /bin/mv "$1" "$2" +} diff --git a/bin/sh/funcs/dirs b/bin/sh/funcs/dirs new file mode 100644 index 0000000..ef2ae0a --- /dev/null +++ b/bin/sh/funcs/dirs @@ -0,0 +1,67 @@ +# $NetBSD: dirs,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)dirs 8.2 (Berkeley) 5/4/95 + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/sh/funcs/kill b/bin/sh/funcs/kill new file mode 100644 index 0000000..70f2b27 --- /dev/null +++ b/bin/sh/funcs/kill @@ -0,0 +1,43 @@ +# $NetBSD: kill,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)kill 8.2 (Berkeley) 5/4/95 + +# Convert job names to process ids and then run /bin/kill. + +kill() { + local args x + args= + for x in "$@" + do case $x in + %*) x=`jobid "$x"` ;; + esac + args="$args $x" + done + /bin/kill $args +} diff --git a/bin/sh/funcs/login b/bin/sh/funcs/login new file mode 100644 index 0000000..a2fe60e --- /dev/null +++ b/bin/sh/funcs/login @@ -0,0 +1,32 @@ +# $NetBSD: login,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)login 8.2 (Berkeley) 5/4/95 + +# replaces the login builtin in the BSD shell +login () exec login "$@" diff --git a/bin/sh/funcs/newgrp b/bin/sh/funcs/newgrp new file mode 100644 index 0000000..1ad46a3 --- /dev/null +++ b/bin/sh/funcs/newgrp @@ -0,0 +1,31 @@ +# $NetBSD: newgrp,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)newgrp 8.2 (Berkeley) 5/4/95 + +newgrp() exec newgrp "$@" diff --git a/bin/sh/funcs/popd b/bin/sh/funcs/popd new file mode 100644 index 0000000..836741c --- /dev/null +++ b/bin/sh/funcs/popd @@ -0,0 +1,67 @@ +# $NetBSD: popd,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)popd 8.2 (Berkeley) 5/4/95 + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/sh/funcs/pushd b/bin/sh/funcs/pushd new file mode 100644 index 0000000..c6ec1af --- /dev/null +++ b/bin/sh/funcs/pushd @@ -0,0 +1,67 @@ +# $NetBSD: pushd,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)pushd 8.2 (Berkeley) 5/4/95 + +# pushd, popd, and dirs --- written by Chris Bertin +# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris +# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW + +pushd () { + SAVE=`pwd` + if [ "$1" = "" ] + then if [ "$DSTACK" = "" ] + then echo "pushd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 || return + shift 1 + DSTACK="$*" + else cd $1 > /dev/null || return + fi + DSTACK="$SAVE $DSTACK" + dirs +} + +popd () { + if [ "$DSTACK" = "" ] + then echo "popd: directory stack empty." + return 1 + fi + set $DSTACK + cd $1 + shift + DSTACK=$* + dirs +} + +dirs () { + echo "`pwd` $DSTACK" + return 0 +} diff --git a/bin/sh/funcs/suspend b/bin/sh/funcs/suspend new file mode 100644 index 0000000..7643f43 --- /dev/null +++ b/bin/sh/funcs/suspend @@ -0,0 +1,35 @@ +# $NetBSD: suspend,v 1.8 2016/02/29 23:50:59 christos Exp $ +# 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. +# +# 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. +# +# @(#)suspend 8.2 (Berkeley) 5/4/95 + +suspend() { + local - + set +j + kill -TSTP 0 +} diff --git a/bin/sh/histedit.c b/bin/sh/histedit.c new file mode 100644 index 0000000..5de3adc --- /dev/null +++ b/bin/sh/histedit.c @@ -0,0 +1,576 @@ +/* $NetBSD: histedit.c,v 1.53 2018/07/13 22:43:44 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: histedit.c,v 1.53 2018/07/13 22:43:44 kre Exp $"); +#endif +#endif /* not lint */ + +#include <sys/param.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Editline and history functions (and glue). + */ +#include "shell.h" +#include "parser.h" +#include "var.h" +#include "options.h" +#include "builtins.h" +#include "main.h" +#include "output.h" +#include "mystring.h" +#include "myhistedit.h" +#include "error.h" +#include "alias.h" +#ifndef SMALL +#include "eval.h" +#include "memalloc.h" + +#define MAXHISTLOOPS 4 /* max recursions through fc */ +#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ + +History *hist; /* history cookie */ +EditLine *el; /* editline cookie */ +int displayhist; +static FILE *el_in, *el_out; +unsigned char _el_fn_complete(EditLine *, int); + +STATIC const char *fc_replace(const char *, char *, char *); + +#ifdef DEBUG +extern FILE *tracefile; +#endif + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + FILE *el_err; + +#define editing (Eflag || Vflag) + + if (iflag == 1) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) + sethistsize(histsizeval()); + else + out2str("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term, *shname; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_out == NULL) + goto bad; + el_err = el_out; +#if DEBUG + if (tracefile) + el_err = tracefile; +#endif + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + shname = arg0; + if (shname[0] == '-') + shname++; + el = el_init(shname, el_in, el_out, el_err); + if (el != NULL) { + if (hist) + el_set(el, EL_HIST, history, hist); + + set_prompt_lit(lookupvar("PSlit")); + el_set(el, EL_SIGNAL, 1); + el_set(el, EL_ALIAS_TEXT, alias_text, NULL); + el_set(el, EL_ADDFN, "rl-complete", + "ReadLine compatible completion function", + _el_fn_complete); + } else { +bad: + out2str("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_BIND, "^I", + tabcomplete ? "rl-complete" : "ed-insert", NULL); + el_source(el, lookupvar("EDITRC")); + } + } else { + INTOFF; + if (el) { /* no editing if not interactive */ + el_end(el); + el = NULL; + } + if (hist) { + history_end(hist); + hist = NULL; + } + INTON; + } +} + +void +set_prompt_lit(const char *lit_ch) +{ + wchar_t wc; + + if (!(iflag && editing && el)) + return; + + if (lit_ch == NULL) { + el_set(el, EL_PROMPT, getprompt); + return; + } + + mbtowc(&wc, NULL, 1); /* state init */ + + if (mbtowc(&wc, lit_ch, strlen(lit_ch)) <= 0) + el_set(el, EL_PROMPT, getprompt); + else + el_set(el, EL_PROMPT_ESC, getprompt, (int)wc); +} + +void +set_editrc(const char *fname) +{ + if (iflag && editing && el) + el_source(el, fname); +} + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || *hs == '\0' || *hs == '-' || + (histsize = number(hs)) < 0) + histsize = 100; + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setterm(const char *term) +{ + if (el != NULL && term != NULL) + if (el_set(el, EL_TERMINAL, term) != 0) { + outfmt(out2, "sh: Can't set terminal type %s\n", term); + outfmt(out2, "sh: Using dumb terminal settings.\n"); + } +} + +int +inputrc(int argc, char **argv) +{ + if (argc != 2) { + out2str("usage: inputrc file\n"); + return 1; + } + if (el != NULL) { + if (el_source(el, argv[1])) { + out2str("inputrc: failed\n"); + return 1; + } else + return 0; + } else { + out2str("sh: inputrc ignored, not editing\n"); + return 1; + } +} + +/* + * This command is provided since POSIX decided to standardize + * the Korn shell fc command. Oh well... + */ +int +histcmd(volatile int argc, char ** volatile argv) +{ + int ch; + const char * volatile editor = NULL; + HistEvent he; + volatile int lflg = 0, nflg = 0, rflg = 0, sflg = 0; + int i, retval; + const char *firststr, *laststr; + int first, last, direction; + char * volatile pat = NULL, * volatile repl; /* ksh "fc old=new" crap */ + static int active = 0; + struct jmploc jmploc; + struct jmploc *volatile savehandler; + char editfile[MAXPATHLEN + 1]; + FILE * volatile efp; +#ifdef __GNUC__ + repl = NULL; /* XXX gcc4 */ + efp = NULL; /* XXX gcc4 */ +#endif + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + optreset = 1; optind = 1; /* initialize getopt */ + while (not_fcnumber(argv[optind]) && + (ch = getopt(argc, argv, ":e:lnrs")) != -1) + switch ((char)ch) { + case 'e': + editor = optionarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + case ':': + error("option -%c expects argument", optopt); + /* NOTREACHED */ + case '?': + default: + error("unknown option: -%c", optopt); + /* NOTREACHED */ + } + argc -= optind, argv += optind; + + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile[0] = '\0'; + /* + * Catch interrupts to reset active counter and + * cleanup temp files. + */ + savehandler = handler; + if (setjmp(jmploc.loc)) { + active = 0; + if (*editfile) + unlink(editfile); + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + if (++active > MAXHISTLOOPS) { + active = 0; + displayhist = 0; + error("called recursively too many times"); + } + /* + * Set editor. + */ + if (sflg == 0) { + if (editor == NULL && + (editor = bltinlookup("FCEDIT", 1)) == NULL && + (editor = bltinlookup("EDITOR", 1)) == NULL) + editor = DEFEDITOR; + if (editor[0] == '-' && editor[1] == '\0') { + sflg = 1; /* no edit */ + editor = NULL; + } + } + } + + /* + * If executing, parse [old=new] now + */ + if (lflg == 0 && argc > 0 && + ((repl = strchr(argv[0], '=')) != NULL)) { + pat = argv[0]; + *repl++ = '\0'; + argc--, argv++; + } + + /* + * If -s is specified, accept only one operand + */ + if (sflg && argc >= 2) + error("too many args"); + + /* + * determine [first] and [last] + */ + switch (argc) { + case 0: + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + break; + case 1: + firststr = argv[0]; + laststr = lflg ? "-1" : argv[0]; + break; + case 2: + firststr = argv[0]; + laststr = argv[1]; + break; + default: + error("too many args"); + /* NOTREACHED */ + } + /* + * Turn into event numbers. + */ + first = str_to_event(firststr, 0); + last = str_to_event(laststr, 1); + + if (rflg) { + i = last; + last = first; + first = i; + } + /* + * XXX - this should not depend on the event numbers + * always increasing. Add sequence numbers or offset + * to the history element in next (diskbased) release. + */ + direction = first < last ? H_PREV : H_NEXT; + + /* + * If editing, grab a temp file. + */ + if (editor) { + int fd; + INTOFF; /* easier */ + snprintf(editfile, sizeof(editfile), "%s_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfile)) < 0) + error("can't create temporary file %s", editfile); + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("can't allocate stdio buffer for temp"); + } + } + + /* + * Loop through selected history events. If listing or executing, + * do it now. Otherwise, put into temp file and call the editor + * after. + * + * The history interface needs rethinking, as the following + * convolutions will demonstrate. + */ + history(hist, &he, H_FIRST); + retval = history(hist, &he, H_NEXT_EVENT, first); + for (;retval != -1; retval = history(hist, &he, direction)) { + if (lflg) { + if (!nflg) + out1fmt("%5d ", he.num); + out1str(he.str); + } else { + const char *s = pat ? + fc_replace(he.str, pat, repl) : he.str; + + if (sflg) { + if (displayhist) { + out2str(s); + } + + evalstring(strcpy(stalloc(strlen(s) + 1), s), 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + history(hist, &he, H_ENTER, s); + } + + break; + } else + fputs(s, efp); + } + /* + * At end? (if we were to lose last, we'd sure be + * messed up). + */ + if (he.num == last) + break; + } + if (editor) { + char *editcmd; + size_t cmdlen; + + fclose(efp); + cmdlen = strlen(editor) + strlen(editfile) + 2; + editcmd = stalloc(cmdlen); + snprintf(editcmd, cmdlen, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + INTON; + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + return 0; +} + +STATIC const char * +fc_replace(const char *s, char *p, char *r) +{ + char *dest; + int plen = strlen(p); + + STARTSTACKSTR(dest); + while (*s) { + if (*s == *p && strncmp(s, p, plen) == 0) { + while (*r) + STPUTC(*r++, dest); + s += plen; + *p = '\0'; /* so no more matches */ + } else + STPUTC(*s++, dest); + } + STACKSTRNUL(dest); + dest = grabstackstr(dest); + + return (dest); +} + +int +not_fcnumber(char *s) +{ + if (s == NULL) + return 0; + if (*s == '-') + s++; + return (!is_number(s)); +} + +int +str_to_event(const char *str, int last) +{ + HistEvent he; + const char *s = str; + int relative = 0; + int i, retval; + + retval = history(hist, &he, H_FIRST); + switch (*s) { + case '-': + relative = 1; + /*FALLTHROUGH*/ + case '+': + s++; + } + if (is_number(s)) { + i = number(s); + if (relative) { + while (retval != -1 && i--) { + retval = history(hist, &he, H_NEXT); + } + if (retval == -1) + retval = history(hist, &he, H_LAST); + } else { + retval = history(hist, &he, H_NEXT_EVENT, i); + if (retval == -1) { + /* + * the notion of first and last is + * backwards to that of the history package + */ + retval = history(hist, &he, + last ? H_FIRST : H_LAST); + } + } + if (retval == -1) + error("history number %s not found (internal error)", + str); + } else { + /* + * pattern + */ + retval = history(hist, &he, H_PREV_STR, str); + if (retval == -1) + error("history pattern not found: %s", str); + } + return (he.num); +} +#else +int +histcmd(int argc, char **argv) +{ + error("not compiled with history support"); + /* NOTREACHED */ +} +int +inputrc(int argc, char **argv) +{ + error("not compiled with history support"); + /* NOTREACHED */ +} +#endif diff --git a/bin/sh/init.h b/bin/sh/init.h new file mode 100644 index 0000000..60d924e --- /dev/null +++ b/bin/sh/init.h @@ -0,0 +1,39 @@ +/* $NetBSD: init.h,v 1.10 2003/08/07 09:05:32 agc Exp $ */ + +/*- + * 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. + * + * @(#)init.h 8.2 (Berkeley) 5/4/95 + */ + +void init(void); +void reset(void); +void initshellproc(void); diff --git a/bin/sh/input.c b/bin/sh/input.c new file mode 100644 index 0000000..dc686f5 --- /dev/null +++ b/bin/sh/input.c @@ -0,0 +1,695 @@ +/* $NetBSD: input.c,v 1.69 2019/01/16 07:14:17 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#else +__RCSID("$NetBSD: input.c,v 1.69 2019/01/16 07:14:17 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +/* + * This file implements the input routines used by the parser. + */ + +#include "shell.h" +#include "redir.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "options.h" +#include "memalloc.h" +#include "error.h" +#include "alias.h" +#include "parser.h" +#include "myhistedit.h" +#include "show.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +MKINIT +struct strpush { + struct strpush *prev; /* preceding string on stack */ + const char *prevstring; + int prevnleft; + int prevlleft; + struct alias *ap; /* if push was associated with an alias */ +}; + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + +MKINIT +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of chars left in this buffer */ + const char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + + +int plinno = 1; /* input line number */ +int parsenleft; /* copy of parsefile->nleft */ +MKINIT int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +MKINIT struct parsefile basepf; /* top level input file */ +MKINIT char basebuf[BUFSIZ]; /* buffer for top level input file */ +struct parsefile *parsefile = &basepf; /* current input file */ +int init_editline = 0; /* editline library initialized? */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +STATIC void pushfile(void); +static int preadfd(void); + +#ifdef mkinit +INCLUDE <stdio.h> +INCLUDE "input.h" +INCLUDE "error.h" + +INIT { + basepf.nextc = basepf.buf = basebuf; +} + +RESET { + if (exception != EXSHELLPROC) + parselleft = parsenleft = 0; /* clear input buffer */ + popallfiles(); +} + +SHELLPROC { + popallfiles(); +} +#endif + + +#if 0 /* this is unused */ +/* + * Read a line from the script. + */ + +char * +pfgets(char *line, int len) +{ + char *p = line; + int nleft = len; + int c; + + while (--nleft > 0) { + c = pgetc_macro(); + if (c == PFAKE) /* consecutive PFAKEs is impossible */ + c = pgetc_macro(); + if (c == PEOF) { + if (p == line) + return NULL; + break; + } + *p++ = c; + if (c == '\n') { + plinno++; + break; + } + } + *p = '\0'; + return line; +} +#endif + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + int c; + + c = pgetc_macro(); + if (c == PFAKE) + c = pgetc_macro(); + return c; +} + + +static int +preadfd(void) +{ + int nr; + char *buf = parsefile->buf; + parsenextc = buf; + + retry: +#ifndef SMALL + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) + rl_cp = el_gets(el, &el_len); + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ - 8) + nr = BUFSIZ - 8; + memcpy(buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = 0; + } + + } else +#endif + nr = read(parsefile->fd, buf, BUFSIZ - 8); + + + if (nr <= 0) { + if (nr < 0) { + if (errno == EINTR) + goto retry; + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2str("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + nr = -1; + } + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If there is more stuff in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *p, *q; + int more; +#ifndef SMALL + int something; +#endif + char savec; + + while (parsefile->strpush) { + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return PFAKE; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + flushout(&output); + flushout(&errout); + + again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + /* p = (not const char *)parsenextc; */ + p = parsefile->buf + (parsenextc - parsefile->buf); + q = p; + + /* delete nul characters */ +#ifndef SMALL + something = 0; +#endif + for (more = 1; more;) { + switch (*p) { + case '\0': + p++; /* Skip nul */ + goto check; + + case '\t': + case ' ': + break; + + case '\n': + parsenleft = q - parsenextc; + more = 0; /* Stop processing here */ + break; + + default: +#ifndef SMALL + something = 1; +#endif + break; + } + + *q++ = *p++; + check: + if (--parselleft <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + *q = '\0'; + more = 0; + } + } + + savec = *q; + *q = '\0'; + +#ifndef SMALL + if (parsefile->fd == 0 && hist && (something || whichprompt == 2)) { + HistEvent he; + + INTOFF; + history(hist, &he, whichprompt != 2 ? H_ENTER : H_APPEND, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Test whether we have reached EOF on input stream. + * Return true only if certain (without attempting a read). + * + * Note the similarity to the opening section of preadbuffer() + */ +int +at_eof(void) +{ + struct strpush *sp = parsefile->strpush; + + if (parsenleft > 0) /* more chars are in the buffer */ + return 0; + + while (sp != NULL) { + /* + * If any pushed string has any remaining data, + * then we are not at EOF (simulating popstring()) + */ + if (sp->prevnleft > 0) + return 0; + sp = sp->prev; + } + + /* + * If we reached real EOF and pushed it back, + * or if we are just processing a string (not reading a file) + * then there is no more. Note that if a file pushes a + * string, the file's ->buf remains present. + */ + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + + /* + * In other cases, there might be more + */ + return 0; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(const char *s, int len, struct alias *ap) +{ + struct strpush *sp; + + VTRACE(DBG_INPUT, + ("pushstring(\"%.*s\", %d)%s%s%s had: nl=%d ll=%d \"%.*s\"\n", + len, s, len, ap ? " for alias:'" : "", + ap ? ap->name : "", ap ? "'" : "", + parsenleft, parselleft, parsenleft, parsenextc)); + + INTOFF; + if (parsefile->strpush) { + sp = ckmalloc(sizeof (struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; + sp->prevlleft = parselleft; + sp->ap = ap; + if (ap) + ap->flag |= ALIASINUSE; + parsenextc = s; + parsenleft = len; + INTON; +} + +void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + int alen; + + if ((alen = strlen(sp->ap->val)) > 0 && + (sp->ap->val[alen - 1] == ' ' || + sp->ap->val[alen - 1] == '\t')) + checkkwd |= CHKALIAS; + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; + + VTRACE(DBG_INPUT, ("popstring()%s%s%s nl=%d ll=%d \"%.*s\"\n", + sp->ap ? " from alias:'" : "", sp->ap ? sp->ap->name : "", + sp->ap ? "'" : "", parsenleft, parselleft, parsenleft, parsenextc)); + + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +void +setinputfile(const char *fname, int push) +{ + unsigned char magic[4]; + int fd; + int fd2; + struct stat sb; + + CTRACE(DBG_INPUT,("setinputfile(\"%s\", %spush)\n",fname,push?"":"no")); + + INTOFF; + if ((fd = open(fname, O_RDONLY)) < 0) + error("Can't open %s", fname); + + /* Since the message "Syntax error: "(" unexpected" is not very + * helpful, we check if the file starts with the ELF magic to + * avoid that message. The first lseek tries to make sure that + * we can later rewind the file. + */ + if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode) && + lseek(fd, 0, SEEK_SET) == 0) { + if (read(fd, magic, 4) == 4) { + if (memcmp(magic, "\177ELF", 4) == 0) { + (void)close(fd); + error("Cannot execute ELF binary %s", fname); + } + } + if (lseek(fd, 0, SEEK_SET) != 0) { + (void)close(fd); + error("Cannot rewind the file %s", fname); + } + } + + fd2 = to_upper_fd(fd); /* closes fd, returns higher equiv */ + if (fd2 == fd) { + (void) close(fd); + error("Out of file descriptors"); + } + + setinputfd(fd2, push); + INTON; +} + +/* + * When a shell fd needs to be altered (when the user wants to use + * the same fd - rare, but happens - we need to locate the ref to + * the fd, and update it. This happens via a callback. + * This is the callback func for fd's used for shell input + */ +static void +input_fd_swap(int from, int to) +{ + struct parsefile *pf; + + pf = parsefile; + while (pf != NULL) { /* don't need to stop at basepf */ + if (pf->fd == from) + pf->fd = to; + pf = pf->prev; + } +} + +/* + * Like setinputfile, but takes an open file descriptor. Call this with + * interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + VTRACE(DBG_INPUT, ("setinputfd(%d, %spush)\n", fd, push?"":"no")); + + register_sh_fd(fd, input_fd_swap); + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + if (push) + pushfile(); + if (parsefile->fd > 0) + sh_close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ); + parselleft = parsenleft = 0; + plinno = 1; + + CTRACE(DBG_INPUT, ("setinputfd(%d, %spush) done; plinno=1\n", fd, + push ? "" : "no")); +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(char *string, int push, int line1) +{ + + INTOFF; + if (push) /* XXX: always, as it happens */ + pushfile(); + parsenextc = string; + parselleft = parsenleft = strlen(string); + plinno = line1; + + CTRACE(DBG_INPUT, + ("setinputstring(\"%.20s%s\" (%d), %spush, @ %d)\n", string, + (parsenleft > 20 ? "..." : ""), parsenleft, push?"":"no", line1)); + INTON; +} + + + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ + +STATIC void +pushfile(void) +{ + struct parsefile *pf; + + VTRACE(DBG_INPUT, + ("pushfile(): fd=%d buf=%p nl=%d ll=%d \"%.*s\" plinno=%d\n", + parsefile->fd, parsefile->buf, parsenleft, parselleft, + parsenleft, parsenextc, plinno)); + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + pf->buf = NULL; + parsefile = pf; +} + + +void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + sh_close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + + VTRACE(DBG_INPUT, + ("popfile(): fd=%d buf=%p nl=%d ll=%d \"%.*s\" plinno:%d->%d\n", + parsefile->fd, parsefile->buf, parsenleft, parselleft, + parsenleft, parsenextc, plinno, parsefile->linno)); + + plinno = parsefile->linno; + INTON; +} + +/* + * Return current file (to go back to it later using popfilesupto()). + */ + +struct parsefile * +getcurrentfile(void) +{ + return parsefile; +} + + +/* + * Pop files until the given file is on top again. Useful for regular + * builtins that read shell commands from files or strings. + * If the given file is not an active file, an error is raised. + */ + +void +popfilesupto(struct parsefile *file) +{ + while (parsefile != file && parsefile != &basepf) + popfile(); + if (parsefile != file) + error("popfilesupto() misused"); +} + + +/* + * Return to top level. + */ + +void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + * + * Takes one arg, vfork, which tells it to not modify its global vars + * as it is still running in the parent. + * + * This code is (probably) unnecessary as the 'close on exec' flag is + * set and should be enough. In the vfork case it is definitely wrong + * to close the fds as another fork() may be done later to feed data + * from a 'here' document into a pipe and we don't want to close the + * pipe! + */ + +void +closescript(int vforked) +{ + if (vforked) + return; + popallfiles(); + if (parsefile->fd > 0) { + sh_close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/bin/sh/input.h b/bin/sh/input.h new file mode 100644 index 0000000..c40d4aa --- /dev/null +++ b/bin/sh/input.h @@ -0,0 +1,69 @@ +/* $NetBSD: input.h,v 1.21 2018/08/19 23:50:27 kre Exp $ */ + +/*- + * 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. + * + * @(#)input.h 8.2 (Berkeley) 5/4/95 + */ + +/* PEOF (the end of file marker) is defined in syntax.h */ + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ +extern int plinno; +extern int parsenleft; /* number of characters left in input buffer */ +extern const char *parsenextc; /* next character in input buffer */ +extern int init_editline; /* 0 == not setup, 1 == OK, -1 == failed */ +extern int whichprompt; /* 1 ==> PS1, 2 ==> PS2 */ + +struct alias; +struct parsefile; + +char *pfgets(char *, int); +int pgetc(void); +int preadbuffer(void); +int at_eof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void popstring(void); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(char *, int, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(int); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c new file mode 100644 index 0000000..d066bb8 --- /dev/null +++ b/bin/sh/jobs.c @@ -0,0 +1,1812 @@ +/* $NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> +#include <sys/types.h> +#include <sys/param.h> +#ifdef BSD +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> +#endif +#include <sys/ioctl.h> + +#include "shell.h" +#if JOBS +#if OLD_TTY_DRIVER +#include "sgtty.h" +#else +#include <termios.h> +#endif +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "show.h" +#include "main.h" +#include "parser.h" +#include "nodes.h" +#include "jobs.h" +#include "var.h" +#include "options.h" +#include "builtins.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" + + +#ifndef WCONTINUED +#define WCONTINUED 0 /* So we can compile on old systems */ +#endif +#ifndef WIFCONTINUED +#define WIFCONTINUED(x) (0) /* ditto */ +#endif + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static int jobs_invalid; /* set in child */ +MKINIT pid_t backgndpid = -1; /* pid of last background process */ +#if JOBS +int initialpgrp; /* pgrp of shell on invocation */ +static int curjob = -1; /* current job */ +#endif +static int ttyfd = -1; + +STATIC void restartjob(struct job *); +STATIC void freejob(struct job *); +STATIC struct job *getjob(const char *, int); +STATIC int dowait(int, struct job *, struct job **); +#define WBLOCK 1 +#define WNOFREE 2 +#define WSILENT 4 +STATIC int jobstatus(const struct job *, int); +STATIC int waitproc(int, struct job *, int *); +STATIC void cmdtxt(union node *); +STATIC void cmdlist(union node *, int); +STATIC void cmdputs(const char *); +inline static void cmdputi(int); + +#ifdef SYSV +STATIC int onsigchild(void); +#endif + +#ifdef OLD_TTY_DRIVER +static pid_t tcgetpgrp(int fd); +static int tcsetpgrp(int fd, pid_t pgrp); + +static pid_t +tcgetpgrp(int fd) +{ + pid_t pgrp; + if (ioctl(fd, TIOCGPGRP, (char *)&pgrp) == -1) + return -1; + else + return pgrp; +} + +static int +tcsetpgrp(int fd, pid_tpgrp) +{ + return ioctl(fd, TIOCSPGRP, (char *)&pgrp); +} +#endif + +static void +ttyfd_change(int from, int to) +{ + if (ttyfd == from) + ttyfd = to; +} + +/* + * Turn job control on and off. + * + * Note: This code assumes that the third arg to ioctl is a character + * pointer, which is true on Berkeley systems but not System V. Since + * System V doesn't have job control yet, this isn't a problem now. + */ + +MKINIT int jobctl; + +void +setjobctl(int on) +{ +#ifdef OLD_TTY_DRIVER + int ldisc; +#endif + + if (on == jobctl || rootshell == 0) + return; + if (on) { +#if defined(FIOCLEX) || defined(FD_CLOEXEC) + int i; + + if (ttyfd != -1) + sh_close(ttyfd); + if ((ttyfd = open("/dev/tty", O_RDWR)) == -1) { + for (i = 0; i < 3; i++) { + if (isatty(i) && (ttyfd = dup(i)) != -1) + break; + } + if (i == 3) + goto out; + } + ttyfd = to_upper_fd(ttyfd); /* Move to a high fd */ + register_sh_fd(ttyfd, ttyfd_change); +#else + out2str("sh: Need FIOCLEX or FD_CLOEXEC to support job control"); + goto out; +#endif + do { /* while we are in the background */ + if ((initialpgrp = tcgetpgrp(ttyfd)) < 0) { + out: + out2str("sh: can't access tty; job control turned off\n"); + mflag = 0; + return; + } + if (initialpgrp == -1) + initialpgrp = getpgrp(); + else if (initialpgrp != getpgrp()) { + killpg(0, SIGTTIN); + continue; + } + } while (0); + +#ifdef OLD_TTY_DRIVER + if (ioctl(ttyfd, TIOCGETD, (char *)&ldisc) < 0 + || ldisc != NTTYDISC) { + out2str("sh: need new tty driver to run job control; job control turned off\n"); + mflag = 0; + return; + } +#endif + setsignal(SIGTSTP, 0); + setsignal(SIGTTOU, 0); + setsignal(SIGTTIN, 0); + if (getpgrp() != rootpid && setpgid(0, rootpid) == -1) + error("Cannot set process group (%s) at %d", + strerror(errno), __LINE__); + if (tcsetpgrp(ttyfd, rootpid) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } else { /* turning job control off */ + if (getpgrp() != initialpgrp && setpgid(0, initialpgrp) == -1) + error("Cannot set process group (%s) at %d", + strerror(errno), __LINE__); + if (tcsetpgrp(ttyfd, initialpgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + sh_close(ttyfd); + ttyfd = -1; + setsignal(SIGTSTP, 0); + setsignal(SIGTTOU, 0); + setsignal(SIGTTIN, 0); + } + jobctl = on; +} + + +#ifdef mkinit +INCLUDE <stdlib.h> + +SHELLPROC { + backgndpid = -1; +#if JOBS + jobctl = 0; +#endif +} + +#endif + + + +#if JOBS +static int +do_fgcmd(const char *arg_ptr) +{ + struct job *jp; + int i; + int status; + + if (jobs_invalid) + error("No current jobs"); + jp = getjob(arg_ptr, 0); + if (jp->jobctl == 0) + error("job not created under job control"); + out1fmt("%s", jp->ps[0].cmd); + for (i = 1; i < jp->nprocs; i++) + out1fmt(" | %s", jp->ps[i].cmd ); + out1c('\n'); + flushall(); + + for (i = 0; i < jp->nprocs; i++) + if (tcsetpgrp(ttyfd, jp->ps[i].pid) != -1) + break; + + if (i >= jp->nprocs) { + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + restartjob(jp); + INTOFF; + status = waitforjob(jp); + INTON; + return status; +} + +int +fgcmd(int argc, char **argv) +{ + nextopt(""); + return do_fgcmd(*argptr); +} + +int +fgcmd_percent(int argc, char **argv) +{ + nextopt(""); + return do_fgcmd(*argv); +} + +static void +set_curjob(struct job *jp, int mode) +{ + struct job *jp1, *jp2; + int i, ji; + + ji = jp - jobtab; + + /* first remove from list */ + if (ji == curjob) + curjob = jp->prev_job; + else { + for (i = 0; i < njobs; i++) { + if (jobtab[i].prev_job != ji) + continue; + jobtab[i].prev_job = jp->prev_job; + break; + } + } + + /* Then re-insert in correct position */ + switch (mode) { + case 0: /* job being deleted */ + jp->prev_job = -1; + break; + case 1: /* newly created job or backgrounded job, + put after all stopped jobs. */ + if (curjob != -1 && jobtab[curjob].state == JOBSTOPPED) { + for (jp1 = jobtab + curjob; ; jp1 = jp2) { + if (jp1->prev_job == -1) + break; + jp2 = jobtab + jp1->prev_job; + if (jp2->state != JOBSTOPPED) + break; + } + jp->prev_job = jp1->prev_job; + jp1->prev_job = ji; + break; + } + /* FALLTHROUGH */ + case 2: /* newly stopped job - becomes curjob */ + jp->prev_job = curjob; + curjob = ji; + break; + } +} + +int +bgcmd(int argc, char **argv) +{ + struct job *jp; + int i; + + nextopt(""); + if (jobs_invalid) + error("No current jobs"); + do { + jp = getjob(*argptr, 0); + if (jp->jobctl == 0) + error("job not created under job control"); + set_curjob(jp, 1); + out1fmt("[%ld] %s", (long)(jp - jobtab + 1), jp->ps[0].cmd); + for (i = 1; i < jp->nprocs; i++) + out1fmt(" | %s", jp->ps[i].cmd ); + out1c('\n'); + flushall(); + restartjob(jp); + } while (*argptr && *++argptr); + return 0; +} + + +STATIC void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + INTOFF; + for (i = 0; i < jp->nprocs; i++) + if (killpg(jp->ps[i].pid, SIGCONT) != -1) + break; + if (i >= jp->nprocs) + error("Cannot continue job (%s)", strerror(errno)); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + VTRACE(DBG_JOBS, ( + "restartjob: [%zu] pid %d status change" + " from %#x (stopped) to -1 (running)\n", + (size_t)(jp-jobtab+1), ps->pid, ps->status)); + ps->status = -1; + jp->state = JOBRUNNING; + } + } + INTON; +} +#endif + +inline static void +cmdputi(int n) +{ + char str[20]; + + fmtstr(str, sizeof str, "%d", n); + cmdputs(str); +} + +static void +showjob(struct output *out, struct job *jp, int mode) +{ + int procno; + int st; + struct procstat *ps; + int col; + char s[64]; + +#if JOBS + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + outfmt(out, "%ld\n", (long)jp->ps->pid); + return; + } +#endif + + procno = jp->nprocs; + if (!procno) + return; + + if (mode & SHOW_PID) + mode |= SHOW_MULTILINE; + + if ((procno > 1 && !(mode & SHOW_MULTILINE)) + || (mode & SHOW_SIGNALLED)) { + /* See if we have more than one status to report */ + ps = jp->ps; + st = ps->status; + do { + int st1 = ps->status; + if (st1 != st) + /* yes - need multi-line output */ + mode |= SHOW_MULTILINE; + if (st1 == -1 || !(mode & SHOW_SIGNALLED) || WIFEXITED(st1)) + continue; + if (WIFSTOPPED(st1) || ((st1 = WTERMSIG(st1) & 0x7f) + && st1 != SIGINT && st1 != SIGPIPE)) + mode |= SHOW_ISSIG; + + } while (ps++, --procno); + procno = jp->nprocs; + } + + if (mode & SHOW_SIGNALLED && !(mode & SHOW_ISSIG)) { + if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) { + VTRACE(DBG_JOBS, ("showjob: freeing job %d\n", + jp - jobtab + 1)); + freejob(jp); + } + return; + } + + for (ps = jp->ps; --procno >= 0; ps++) { /* for each process */ + if (ps == jp->ps) + fmtstr(s, 16, "[%ld] %c ", + (long)(jp - jobtab + 1), +#if JOBS + jp - jobtab == curjob ? + '+' : + curjob != -1 && + jp - jobtab == jobtab[curjob].prev_job ? + '-' : +#endif + ' '); + else + fmtstr(s, 16, " " ); + col = strlen(s); + if (mode & SHOW_PID) { + fmtstr(s + col, 16, "%ld ", (long)ps->pid); + col += strlen(s + col); + } + if (ps->status == -1) { + scopy("Running", s + col); + } else if (WIFEXITED(ps->status)) { + st = WEXITSTATUS(ps->status); + if (st) + fmtstr(s + col, 16, "Done(%d)", st); + else + fmtstr(s + col, 16, "Done"); + } else { +#if JOBS + if (WIFSTOPPED(ps->status)) + st = WSTOPSIG(ps->status); + else /* WIFSIGNALED(ps->status) */ +#endif + st = WTERMSIG(ps->status); + scopyn(strsignal(st), s + col, 32); + if (WCOREDUMP(ps->status)) { + col += strlen(s + col); + scopyn(" (core dumped)", s + col, 64 - col); + } + } + col += strlen(s + col); + outstr(s, out); + do { + outc(' ', out); + col++; + } while (col < 30); + outstr(ps->cmd, out); + if (mode & SHOW_MULTILINE) { + if (procno > 0) { + outc(' ', out); + outc('|', out); + } + } else { + while (--procno >= 0) + outfmt(out, " | %s", (++ps)->cmd ); + } + outc('\n', out); + } + flushout(out); + jp->flags &= ~JOBCHANGED; + if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) + freejob(jp); +} + +int +jobscmd(int argc, char **argv) +{ + int mode, m; + + mode = 0; + while ((m = nextopt("lp"))) + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + if (!iflag) + mode |= SHOW_NO_FREE; + if (*argptr) + do + showjob(out1, getjob(*argptr,0), mode); + while (*++argptr); + else + showjobs(out1, mode); + return 0; +} + + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(struct output *out, int mode) +{ + int jobno; + struct job *jp; + int silent = 0, gotpid; + + CTRACE(DBG_JOBS, ("showjobs(%x) called\n", mode)); + + /* If not even one one job changed, there is nothing to do */ + gotpid = dowait(WSILENT, NULL, NULL); + while (dowait(WSILENT, NULL, NULL) > 0) + continue; +#ifdef JOBS + /* + * Check if we are not in our foreground group, and if not + * put us in it. + */ + if (mflag && gotpid != -1 && tcgetpgrp(ttyfd) != getpid()) { + if (tcsetpgrp(ttyfd, getpid()) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + VTRACE(DBG_JOBS|DBG_INPUT, ("repaired tty process group\n")); + silent = 1; + } +#endif + + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (!jp->used) + continue; + if (jp->nprocs == 0) { + if (!jobs_invalid) + freejob(jp); + continue; + } + if ((mode & SHOW_CHANGED) && !(jp->flags & JOBCHANGED)) + continue; + if (silent && (jp->flags & JOBCHANGED)) { + jp->flags &= ~JOBCHANGED; + continue; + } + showjob(out, jp, mode); + } +} + +/* + * Mark a job structure as unused. + */ + +STATIC void +freejob(struct job *jp) +{ + INTOFF; + if (jp->ps != &jp->ps0) { + ckfree(jp->ps); + jp->ps = &jp->ps0; + } + jp->nprocs = 0; + jp->used = 0; +#if JOBS + set_curjob(jp, 0); +#endif + INTON; +} + +/* + * Extract the status of a completed job (for $?) + */ +STATIC int +jobstatus(const struct job *jp, int raw) +{ + int status = 0; + int retval; + + if ((jp->flags & JPIPEFAIL) && jp->nprocs) { + int i; + + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status != 0) + status = jp->ps[i].status; + } else + status = jp->ps[jp->nprocs ? jp->nprocs - 1 : 0].status; + + if (raw) + return status; + + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + retval = WSTOPSIG(status) + 128; +#endif + else { + /* XXX: limits number of signals */ + retval = WTERMSIG(status) + 128; + } + + return retval; +} + + + +int +waitcmd(int argc, char **argv) +{ + struct job *job, *last; + int retval; + struct job *jp; + int i; + int any = 0; + int found; + char *pid = NULL, *fpid; + char **arg; + char idstring[20]; + + while ((i = nextopt("np:")) != '\0') { + switch (i) { + case 'n': + any = 1; + break; + case 'p': + if (pid) + error("more than one -p unsupported"); + pid = optionarg; + break; + } + } + + if (pid != NULL) { + if (!validname(pid, '\0', NULL)) + error("invalid name: -p '%s'", pid); + if (unsetvar(pid, 0)) + error("%s readonly", pid); + } + + /* + * If we have forked, and not yet created any new jobs, then + * we have no children, whatever jobtab claims, + * so simply return in that case. + * + * The return code is 127 if we had any pid args (none are found) + * or if we had -n (nothing exited), but 0 for plain old "wait". + */ + if (jobs_invalid) { + CTRACE(DBG_WAIT, ("builtin wait%s%s in child, invalid jobtab\n", + any ? " -n" : "", *argptr ? " pid..." : "")); + return (any || *argptr) ? 127 : 0; + } + + /* clear stray flags left from previous waitcmd */ + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + jp->flags &= ~JOBWANTED; + jp->ref = NULL; + } + + CTRACE(DBG_WAIT, + ("builtin wait%s%s\n", any ? " -n" : "", *argptr ? " pid..." : "")); + + /* + * First, validate the jobnum args, count how many refer to + * (different) running jobs, and if we had -n, and found that one has + * already finished, we return that one. Otherwise remember + * which ones we are looking for (JOBWANTED). + */ + found = 0; + last = NULL; + for (arg = argptr; *arg; arg++) { + last = jp = getjob(*arg, 1); + if (!jp) + continue; + if (jp->ref == NULL) + jp->ref = *arg; + if (any && jp->state == JOBDONE) { + /* + * We just want any of them, and this one is + * ready for consumption, bon apetit ... + */ + retval = jobstatus(jp, 0); + if (pid) + setvar(pid, *arg, 0); + if (!iflag) + freejob(jp); + CTRACE(DBG_WAIT, ("wait -n found %s already done: %d\n", *arg, retval)); + return retval; + } + if (!(jp->flags & JOBWANTED)) { + /* + * It is possible to list the same job several + * times - the obvious "wait 1 1 1" or + * "wait %% %2 102" where job 2 is current and pid 102 + * However many times it is requested, it is found once. + */ + found++; + jp->flags |= JOBWANTED; + } + job = jp; + } + + VTRACE(DBG_WAIT, ("wait %s%s%sfound %d candidates (last %s)\n", + any ? "-n " : "", *argptr ? *argptr : "", + argptr[0] && argptr[1] ? "... " : " ", found, + job ? (job->ref ? job->ref : "<no-arg>") : "none")); + + /* + * If we were given a list of jobnums: + * and none of those exist, then we're done. + */ + if (*argptr && found == 0) + return 127; + + /* + * Otherwise we need to wait for something to complete + * When it does, we check and see if it is one of the + * jobs we're waiting on, and if so, we clean it up. + * If we had -n, then we're done, otherwise we do it all again + * until all we had listed are done, of if there were no + * jobnum args, all are done. + */ + + retval = any || *argptr ? 127 : 0; + fpid = NULL; + for (;;) { + VTRACE(DBG_WAIT, ("wait waiting (%d remain): ", found)); + for (jp = jobtab, i = njobs; --i >= 0; jp++) { + if (jp->used && jp->flags & JOBWANTED && + jp->state == JOBDONE) + break; + if (jp->used && jp->state == JOBRUNNING) + break; + } + if (i < 0) { + CTRACE(DBG_WAIT, ("nothing running (ret: %d) fpid %s\n", + retval, fpid ? fpid : "unset")); + if (pid && fpid) + setvar(pid, fpid, 0); + return retval; + } + VTRACE(DBG_WAIT, ("found @%d/%d state: %d\n", njobs-i, njobs, + jp->state)); + + /* + * There is at least 1 job running, so we can + * safely wait() for something to exit. + */ + if (jp->state == JOBRUNNING) { + job = NULL; + if ((i = dowait(WBLOCK|WNOFREE, NULL, &job)) == -1) + return 128 + lastsig(); + + /* + * one of the job's processes exited, + * but there are more + */ + if (job->state == JOBRUNNING) + continue; + } else + job = jp; /* we want this, and it is done */ + + if (job->flags & JOBWANTED || (*argptr == 0 && any)) { + int rv; + + job->flags &= ~JOBWANTED; /* got it */ + rv = jobstatus(job, 0); + VTRACE(DBG_WAIT, ( + "wanted %d (%s) done: st=%d", i, + job->ref ? job->ref : "", rv)); + if (any || job == last) { + retval = rv; + fpid = job->ref; + + VTRACE(DBG_WAIT, (" save")); + if (pid) { + /* + * don't need fpid unless we are going + * to return it. + */ + if (fpid == NULL) { + /* + * this only happens with "wait -n" + * (that is, no pid args) + */ + snprintf(idstring, sizeof idstring, + "%d", job->ps[ job->nprocs ? + job->nprocs-1 : + 0 ].pid); + fpid = idstring; + } + VTRACE(DBG_WAIT, (" (for %s)", fpid)); + } + } + + if (job->state == JOBDONE) { + VTRACE(DBG_WAIT, (" free")); + freejob(job); + } + + if (any || (found > 0 && --found == 0)) { + if (pid && fpid) + setvar(pid, fpid, 0); + VTRACE(DBG_WAIT, (" return %d\n", retval)); + return retval; + } + VTRACE(DBG_WAIT, ("\n")); + continue; + } + + /* this is to handle "wait" (no args) */ + if (found == 0 && job->state == JOBDONE) { + VTRACE(DBG_JOBS|DBG_WAIT, ("Cleanup: %d\n", i)); + freejob(job); + } + } +} + + +int +jobidcmd(int argc, char **argv) +{ + struct job *jp; + int i; + int pg = 0, onep = 0, job = 0; + + while ((i = nextopt("gjp"))) { + switch (i) { + case 'g': pg = 1; break; + case 'j': job = 1; break; + case 'p': onep = 1; break; + } + } + CTRACE(DBG_JOBS, ("jobidcmd%s%s%s%s %s\n", pg ? " -g" : "", + onep ? " -p" : "", job ? " -j" : "", jobs_invalid ? " [inv]" : "", + *argptr ? *argptr : "<implicit %%>")); + if (pg + onep + job > 1) + error("-g -j and -p options cannot be combined"); + + if (argptr[0] && argptr[1]) + error("usage: jobid [-g|-p|-r] jobid"); + + jp = getjob(*argptr, 0); + if (job) { + out1fmt("%%%zu\n", (size_t)(jp - jobtab + 1)); + return 0; + } + if (pg) { + if (jp->pgrp != 0) { + out1fmt("%ld\n", (long)jp->pgrp); + return 0; + } + return 1; + } + if (onep) { + i = jp->nprocs - 1; + if (i < 0) + return 1; + out1fmt("%ld\n", (long)jp->ps[i].pid); + return 0; + } + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%ld", (long)jp->ps[i].pid); + out1c(++i < jp->nprocs ? ' ' : '\n'); + } + return 0; +} + +int +getjobpgrp(const char *name) +{ + struct job *jp; + + if (jobs_invalid) + error("No such job: %s", name); + jp = getjob(name, 1); + if (jp == 0) + return 0; + return -jp->ps[0].pid; +} + +/* + * Convert a job name to a job structure. + */ + +STATIC struct job * +getjob(const char *name, int noerror) +{ + int jobno = -1; + struct job *jp; + int pid; + int i; + const char *err_msg = "No such job: %s"; + + if (name == NULL) { +#if JOBS + jobno = curjob; +#endif + err_msg = "No current job"; + } else if (name[0] == '%') { + if (is_number(name + 1)) { + jobno = number(name + 1) - 1; + } else if (!name[1] || !name[2]) { + switch (name[1]) { +#if JOBS + case 0: + case '+': + case '%': + jobno = curjob; + err_msg = "No current job"; + break; + case '-': + jobno = curjob; + if (jobno != -1) + jobno = jobtab[jobno].prev_job; + err_msg = "No previous job"; + break; +#endif + default: + goto check_pattern; + } + } else { + struct job *found; + check_pattern: + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (!jp->used || jp->nprocs <= 0) + continue; + if ((name[1] == '?' + && strstr(jp->ps[0].cmd, name + 2)) + || prefix(name + 1, jp->ps[0].cmd)) { + if (found) { + err_msg = "%s: ambiguous"; + found = 0; + break; + } + found = jp; + } + } + if (found) + return found; + } + + } else if (is_number(name)) { + pid = number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + + if (jobno >= 0 && jobno < njobs) { + jp = jobtab + jobno; + if (jp->used) + return jp; + } + if (!noerror) + error(err_msg, name); + return 0; +} + + + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node, int nprocs) +{ + int i; + struct job *jp; + + if (jobs_invalid) { + for (i = njobs, jp = jobtab ; --i >= 0 ; jp++) { + if (jp->used) + freejob(jp); + } + jobs_invalid = 0; + } + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; njobs++) { + jobtab[njobs].used = 0; + jobtab[njobs].prev_job = -1; + } + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = JOBRUNNING; + jp->used = 1; + jp->flags = pipefail ? JPIPEFAIL : 0; + jp->nprocs = 0; + jp->pgrp = 0; +#if JOBS + jp->jobctl = jobctl; + set_curjob(jp, 1); +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + VTRACE(DBG_JOBS, ("makejob(%p, %d)%s returns %%%d\n", (void *)node, + nprocs, (jp->flags&JPIPEFAIL)?" PF":"", jp - jobtab + 1)); + return jp; +} + + +/* + * Fork off a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + */ + +int +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + int serrno; + + CTRACE(DBG_JOBS, ("forkshell(%%%d, %p, %d) called\n", + jp - jobtab, n, mode)); + + switch ((pid = fork())) { + case -1: + serrno = errno; + VTRACE(DBG_JOBS, ("Fork failed, errno=%d\n", serrno)); + INTON; + error("Cannot fork (%s)", strerror(serrno)); + break; + case 0: + SHELL_FORKED(); + forkchild(jp, n, mode, 0); + return 0; + default: + return forkparent(jp, n, mode, pid); + } +} + +int +forkparent(struct job *jp, union node *n, int mode, pid_t pid) +{ + int pgrp; + + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + jp->pgrp = pgrp; + /* This can fail because we are doing it in the child also */ + (void)setpgid(pid, pgrp); + } + if (mode == FORK_BG) + backgndpid = pid; /* set $! */ + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd[0] = 0; + if (/* iflag && rootshell && */ n) + commandtext(ps, n); + } + CTRACE(DBG_JOBS, ("In parent shell: child = %d (mode %d)\n",pid,mode)); + return pid; +} + +void +forkchild(struct job *jp, union node *n, int mode, int vforked) +{ + int wasroot; + int pgrp; + const char *devnull = _PATH_DEVNULL; + const char *nullerr = "Can't open %s"; + + wasroot = rootshell; + CTRACE(DBG_JOBS, ("Child shell %d %sforked from %d (mode %d)\n", + getpid(), vforked?"v":"", getppid(), mode)); + + if (!vforked) { + rootshell = 0; + handler = &main_handler; + } + + closescript(vforked); + clear_traps(vforked); +#if JOBS + if (!vforked) + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the parent also */ + (void)setpgid(0, pgrp); + if (mode == FORK_FG) { + if (tcsetpgrp(ttyfd, pgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + setsignal(SIGTSTP, vforked); + setsignal(SIGTTOU, vforked); + } else if (mode == FORK_BG) { + ignoresig(SIGINT, vforked); + ignoresig(SIGQUIT, vforked); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(devnull, O_RDONLY) != 0) + error(nullerr, devnull); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT, vforked); + ignoresig(SIGQUIT, vforked); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(devnull, O_RDONLY) != 0) + error(nullerr, devnull); + } + } +#endif + if (wasroot && iflag) { + setsignal(SIGINT, vforked); + setsignal(SIGQUIT, vforked); + setsignal(SIGTERM, vforked); + } + + if (!vforked) + jobs_invalid = 1; +} + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * forground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + */ + +int +waitforjob(struct job *jp) +{ +#if JOBS + int mypgrp = getpgrp(); +#endif + int status; + int st; + + INTOFF; + VTRACE(DBG_JOBS, ("waitforjob(%%%d) called\n", jp - jobtab + 1)); + while (jp->state == JOBRUNNING) { + dowait(WBLOCK, jp, NULL); + } +#if JOBS + if (jp->jobctl) { + if (tcsetpgrp(ttyfd, mypgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + if (jp->state == JOBSTOPPED && curjob != jp - jobtab) + set_curjob(jp, 2); +#endif + status = jobstatus(jp, 1); + + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + + VTRACE(DBG_JOBS, ("waitforjob: job %d, nproc %d, status %d, st %x\n", + jp - jobtab + 1, jp->nprocs, status, st)); +#if JOBS + if (jp->jobctl) { + /* + * This is truly gross. + * If we're doing job control, then we did a TIOCSPGRP which + * caused us (the shell) to no longer be in the controlling + * session -- so we wouldn't have seen any ^C/SIGINT. So, we + * intuit from the subprocess exit status whether a SIGINT + * occurred, and if so interrupt ourselves. Yuck. - mycroft + */ + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + raise(SIGINT); + } +#endif + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + INTON; + return st; +} + + + +/* + * Wait for a process to terminate. + */ + +STATIC int +dowait(int flags, struct job *job, struct job **changed) +{ + int pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + int done; + int stopped; + + VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called\n", flags)); + + if (changed != NULL) + *changed = NULL; + + do { + pid = waitproc(flags & WBLOCK, job, &status); + VTRACE(DBG_JOBS|DBG_PROCS, ("wait returns pid %d, status %#x\n", + pid, status)); + } while (pid == -1 && errno == EINTR && pendingsigs == 0); + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && + (sp->status==-1 || WIFSTOPPED(sp->status))) { + VTRACE(DBG_JOBS | DBG_PROCS, + ("Job %d: changing status of proc %d from %#x to %#x\n", + jp - jobtab + 1, pid, + sp->status, status)); + if (WIFCONTINUED(status)) { + if (sp->status != -1) + jp->flags |= JOBCHANGED; + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + if (changed != NULL) + *changed = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done ? JOBDONE : JOBSTOPPED; + + if (jp->state != state) { + VTRACE(DBG_JOBS, + ("Job %d: changing state from %d to %d\n", + jp - jobtab + 1, jp->state, state)); + jp->state = state; +#if JOBS + if (done) + set_curjob(jp, 0); +#endif + } + } + } + } + + if (thisjob && + (thisjob->state != JOBRUNNING || thisjob->flags & JOBCHANGED)) { + int mode = 0; + + if (!rootshell || !iflag) + mode = SHOW_SIGNALLED; + if ((job == thisjob && (flags & WNOFREE) == 0) || + job != thisjob) + mode = SHOW_SIGNALLED | SHOW_NO_FREE; + if (mode && (flags & WSILENT) == 0) + showjob(out2, thisjob, mode); + else { + VTRACE(DBG_JOBS, + ("Not printing status, rootshell=%d, job=%p\n", + rootshell, job)); + thisjob->flags |= JOBCHANGED; + } + } + + INTON; + return pid; +} + + + +/* + * Do a wait system call. If job control is compiled in, we accept + * stopped processes. If block is zero, we return a value of zero + * rather than blocking. + * + * System V doesn't have a non-blocking wait system call. It does + * have a SIGCLD signal that is sent to a process when one of its + * children dies. The obvious way to use SIGCLD would be to install + * a handler for SIGCLD which simply bumped a counter when a SIGCLD + * was received, and have waitproc bump another counter when it got + * the status of a process. Waitproc would then know that a wait + * system call would not block if the two counters were different. + * This approach doesn't work because if a process has children that + * have not been waited for, System V will send it a SIGCLD when it + * installs a signal handler for SIGCLD. What this means is that when + * a child exits, the shell will be sent SIGCLD signals continuously + * until is runs out of stack space, unless it does a wait call before + * restoring the signal handler. The code below takes advantage of + * this (mis)feature by installing a signal handler for SIGCLD and + * then checking to see whether it was called. If there are any + * children to be waited for, it will be. + * + * If neither SYSV nor BSD is defined, we don't implement nonblocking + * waits at all. In this case, the user will not be informed when + * a background process until the next time she runs a real program + * (as opposed to running a builtin command or just typing return), + * and the jobs command may give out of date information. + */ + +#ifdef SYSV +STATIC int gotsigchild; + +STATIC int onsigchild() { + gotsigchild = 1; +} +#endif + + +STATIC int +waitproc(int block, struct job *jp, int *status) +{ +#ifdef BSD + int flags = 0; + +#if JOBS + if (mflag || (jp != NULL && jp->jobctl)) + flags |= WUNTRACED | WCONTINUED; +#endif + if (block == 0) + flags |= WNOHANG; + VTRACE(DBG_WAIT, ("waitproc: doing waitpid(flags=%#x)\n", flags)); + return waitpid(-1, status, flags); +#else +#ifdef SYSV + int (*save)(); + + if (block == 0) { + gotsigchild = 0; + save = signal(SIGCLD, onsigchild); + signal(SIGCLD, save); + if (gotsigchild == 0) + return 0; + } + return wait(status); +#else + if (block == 0) + return 0; + return wait(status); +#endif +#endif +} + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning || jobs_invalid) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command). + */ + +STATIC char *cmdnextc; +STATIC int cmdnleft; + +void +commandtext(struct procstat *ps, union node *n) +{ + int len; + + cmdnextc = ps->cmd; + if (iflag || mflag || sizeof(ps->cmd) <= 60) + len = sizeof(ps->cmd); + else if (sizeof ps->cmd <= 400) + len = 50; + else if (sizeof ps->cmd <= 800) + len = 80; + else + len = sizeof(ps->cmd) / 10; + cmdnleft = len; + cmdtxt(n); + if (cmdnleft <= 0) { + char *p = ps->cmd + len - 4; + p[0] = '.'; + p[1] = '.'; + p[2] = '.'; + p[3] = 0; + } else + *cmdnextc = '\0'; + + VTRACE(DBG_JOBS, + ("commandtext: ps->cmd %p, end %p, left %d\n\t\"%s\"\n", + ps->cmd, cmdnextc, cmdnleft, ps->cmd)); +} + + +STATIC void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + const char *p; + int i; + + if (n == NULL || cmdnleft <= 0) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NDNOT: + cmdputs("! "); + /* FALLTHROUGH */ + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + if (n->npipe.backgnd) + cmdputs(" &"); + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + if (n->nif.elsepart) { + cmdputs("; else "); + cmdtxt(n->nif.elsepart); + } + cmdputs("; fi"); + break; + case NWHILE: + cmdputs("while "); + goto until; + case NUNTIL: + cmdputs("until "); + until: + cmdtxt(n->nbinary.ch1); + cmdputs("; do "); + cmdtxt(n->nbinary.ch2); + cmdputs("; done"); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + cmdputs("; do "); + cmdtxt(n->nfor.body); + cmdputs("; done"); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in "); + for (np = n->ncase.cases; np; np = np->nclist.next) { + cmdtxt(np->nclist.pattern); + cmdputs(") "); + cmdtxt(np->nclist.body); + switch (n->type) { /* switch (not if) for later */ + case NCLISTCONT: + cmdputs(";& "); + break; + default: + cmdputs(";; "); + break; + } + } + cmdputs("esac"); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() { ... }"); + break; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + if (n->ncmd.backgnd) + cmdputs(" &"); + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + p = ">"; i = 1; goto redir; + case NCLOBBER: + p = ">|"; i = 1; goto redir; + case NAPPEND: + p = ">>"; i = 1; goto redir; + case NTOFD: + p = ">&"; i = 1; goto redir; + case NFROM: + p = "<"; i = 0; goto redir; + case NFROMFD: + p = "<&"; i = 0; goto redir; + case NFROMTO: + p = "<>"; i = 0; goto redir; + redir: + if (n->nfile.fd != i) + cmdputi(n->nfile.fd); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd < 0) + cmdputs("-"); + else + cmdputi(n->ndup.dupfd); + } else { + cmdtxt(n->nfile.fname); + } + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + +STATIC void +cmdlist(union node *np, int sep) +{ + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(" "); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(" "); + } +} + + +STATIC void +cmdputs(const char *s) +{ + const char *p, *str = 0; + char c, cc[2] = " "; + char *nextc; + int nleft; + int subtype = 0; + int quoted = 0; + static char vstype[16][4] = { "", "}", "-", "+", "?", "=", + "#", "##", "%", "%%", "}" }; + + p = s; + nextc = cmdnextc; + nleft = cmdnleft; + while (nleft > 0 && (c = *p++) != 0) { + switch (c) { + case CTLNONL: + c = '\0'; + break; + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if (subtype & VSLINENO) { /* undo LINENO hack */ + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#LINENO"; /*}*/ + else + str = "${LINENO"; /*}*/ + while (is_digit(*p)) + p++; + } else if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; /*}*/ + else + str = "${"; /*}*/ + if (!(subtype & VSQUOTE) != !(quoted & 1)) { + quoted ^= 1; + c = '"'; + } else { + c = *str++; + } + break; + case CTLENDVAR: /*{*/ + c = '}'; + if (quoted & 1) + str = "\""; + quoted >>= 1; + subtype = 0; + break; + case CTLBACKQ: + c = '$'; + str = "(...)"; + break; + case CTLBACKQ+CTLQUOTE: + c = '"'; + str = "$(...)\""; + break; + case CTLARI: + c = '$'; + if (*p == ' ') + p++; + str = "(("; /*))*/ + break; + case CTLENDARI: /*((*/ + c = ')'; + str = ")"; + break; + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case CTLQUOTEEND: + quoted >>= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + c = *str++; /*{*/ + if (c != '}') + quoted <<= 1; + else if (*p == CTLENDVAR) + c = *str++; + subtype = 0; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + if (c != '\0') do { /* c == 0 implies nothing in str */ + *nextc++ = c; + } while (--nleft > 0 && str && (c = *str++)); + str = 0; + } + if ((quoted & 1) && nleft) { + *nextc++ = '"'; + nleft--; + } + cmdnleft = nleft; + cmdnextc = nextc; +} diff --git a/bin/sh/jobs.h b/bin/sh/jobs.h new file mode 100644 index 0000000..a587e9e --- /dev/null +++ b/bin/sh/jobs.h @@ -0,0 +1,105 @@ +/* $NetBSD: jobs.h,v 1.23 2018/09/11 03:30:40 kre Exp $ */ + +/*- + * 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. + * + * @(#)jobs.h 8.2 (Berkeley) 5/4/95 + */ + +#include "output.h" + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_MULTILINE 0x02 /* one line per process */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ +#define SHOW_SIGNALLED 0x10 /* only if stopped/exited on signal */ +#define SHOW_ISSIG 0x20 /* job was signalled */ +#define SHOW_NO_FREE 0x40 /* do not free job */ + + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ +#define MAXCMDTEXT 200 + +struct procstat { + pid_t pid; /* process id */ + int status; /* last process status from wait() */ + char cmd[MAXCMDTEXT];/* text of command being run */ +}; + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + void *ref; /* temporary reference, used variously */ + int nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; +#define JOBRUNNING 0 /* at least one proc running */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + char used; /* true if this entry is in used */ + char flags; +#define JOBCHANGED 1 /* set if status has changed */ +#define JOBWANTED 2 /* set if this is a job being sought */ +#define JPIPEFAIL 4 /* set if -o pipefail when job created */ +#if JOBS + char jobctl; /* job running under job control */ + int prev_job; /* previous job index */ +#endif +}; + +extern pid_t backgndpid; /* pid of last background process */ +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(struct output *, int); +struct job *makejob(union node *, int); +int forkshell(struct job *, union node *, int); +void forkchild(struct job *, union node *, int, int); +int forkparent(struct job *, union node *, int, pid_t); +int waitforjob(struct job *); +int stoppedjobs(void); +void commandtext(struct procstat *, union node *); +int getjobpgrp(const char *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/bin/sh/machdep.h b/bin/sh/machdep.h new file mode 100644 index 0000000..14e803b --- /dev/null +++ b/bin/sh/machdep.h @@ -0,0 +1,47 @@ +/* $NetBSD: machdep.h,v 1.11 2003/08/07 09:05:33 agc Exp $ */ + +/*- + * 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. + * + * @(#)machdep.h 8.2 (Berkeley) 5/4/95 + */ + +/* + * Most machines require the value returned from malloc to be aligned + * in some way. The following macro will get this right on many machines. + */ + +#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1) +/* + * It appears that grabstackstr() will barf with such alignments + * because stalloc() will return a string allocated in a new stackblock. + */ +#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE) diff --git a/bin/sh/mail.c b/bin/sh/mail.c new file mode 100644 index 0000000..484b50f --- /dev/null +++ b/bin/sh/mail.c @@ -0,0 +1,144 @@ +/* $NetBSD: mail.c,v 1.18 2017/06/04 20:28:13 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: mail.c,v 1.18 2017/06/04 20:28:13 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> + +#include "shell.h" +#include "exec.h" /* defines padvance() */ +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mail.h" + + +#define MAXMBOXES 10 + + +STATIC int nmboxes; /* number of mailboxes */ +STATIC off_t mailsize[MAXMBOXES]; /* sizes of mailboxes */ + + + +/* + * Print appropriate message(s) if mail has arrived. If the argument is + * nozero, then the value of MAIL has changed, so we just update the + * values. + */ + +void +chkmail(int silent) +{ + int i; + const char *mpath; + char *p; + char *q; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = mpathset() ? mpathval() : mailval(); + for (i = 0 ; i < nmboxes ; i++) { + p = padvance(&mpath, nullstr, 1); + if (p == NULL) + break; + stunalloc(p); + if (*p == '\0') + continue; + for (q = p ; *q ; q++) + ; + if (q[-1] != '/') + abort(); + q[-1] = '\0'; /* delete trailing '/' */ +#ifdef notdef /* this is what the System V shell claims to do (it lies) */ + if (stat(p, &statb) < 0) + statb.st_mtime = 0; + if (statb.st_mtime > mailtime[i] && ! silent) { + out2str(pathopt ? pathopt : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_mtime; +#else /* this is what it should do */ + if (stat(p, &statb) < 0) + statb.st_size = 0; + if (statb.st_size > mailsize[i] && ! silent) { + const char *pp; + + if ((pp = pathopt) != NULL) { + int len = 0; + + while (*pp && *pp != ':') + len++, pp++; + if (len == 0) { + CHECKSTRSPACE(16, p); + strcat(p, ": file changed"); + } else { + while (stackblocksize() <= len) + growstackblock(); + p = stackblock(); + memcpy(p, pathopt, len); + p[len] = '\0'; + } + pp = p; + } else + pp = "you have mail"; + + out2str(pp); + out2c('\n'); + } + mailsize[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/bin/sh/mail.h b/bin/sh/mail.h new file mode 100644 index 0000000..9ea7c21 --- /dev/null +++ b/bin/sh/mail.h @@ -0,0 +1,37 @@ +/* $NetBSD: mail.h,v 1.10 2003/08/07 09:05:34 agc Exp $ */ + +/*- + * 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. + * + * @(#)mail.h 8.2 (Berkeley) 5/4/95 + */ + +void chkmail(int); diff --git a/bin/sh/main.c b/bin/sh/main.c new file mode 100644 index 0000000..a023611 --- /dev/null +++ b/bin/sh/main.c @@ -0,0 +1,393 @@ +/* $NetBSD: main.c,v 1.80 2019/01/19 14:20:22 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.7 (Berkeley) 7/19/95"; +#else +__RCSID("$NetBSD: main.c,v 1.80 2019/01/19 14:20:22 kre Exp $"); +#endif +#endif /* not lint */ + +#include <errno.h> +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <locale.h> +#include <fcntl.h> + + +#include "shell.h" +#include "main.h" +#include "mail.h" +#include "options.h" +#include "builtins.h" +#include "output.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "trap.h" +#include "var.h" +#include "show.h" +#include "memalloc.h" +#include "error.h" +#include "init.h" +#include "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" + +#define PROFILE 0 + +int rootpid; +int rootshell; +struct jmploc main_handler; +int max_user_fd; +#if PROFILE +short profile_buf[16384]; +extern int etext(); +#endif + +STATIC void read_profile(const char *); + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ + +int +main(int argc, char **argv) +{ + struct stackmark smark; + volatile int state; + char *shinit; + uid_t uid; + gid_t gid; + + uid = getuid(); + gid = getgid(); + + max_user_fd = fcntl(0, F_MAXFD); + if (max_user_fd < 2) + max_user_fd = 2; + + setlocale(LC_ALL, ""); + + posix = getenv("POSIXLY_CORRECT") != NULL; +#if PROFILE + monitor(4, etext, profile_buf, sizeof profile_buf, 50); +#endif + state = 0; + if (setjmp(main_handler.loc)) { + /* + * When a shell procedure is executed, we raise the + * exception EXSHELLPROC to clean up before executing + * the shell procedure. + */ + switch (exception) { + case EXSHELLPROC: + rootpid = getpid(); + rootshell = 1; + minusc = NULL; + state = 3; + break; + + case EXEXEC: + exitstatus = exerrno; + break; + + case EXERROR: + exitstatus = 2; + break; + + default: + break; + } + + if (exception != EXSHELLPROC) { + if (state == 0 || iflag == 0 || ! rootshell || + exception == EXEXIT) + exitshell(exitstatus); + } + reset(); + if (exception == EXINT) { + out2c('\n'); + flushout(&errout); + } + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (state == 1) + goto state1; + else if (state == 2) + goto state2; + else if (state == 3) + goto state3; + else + goto state4; + } + handler = &main_handler; +#ifdef DEBUG +#if DEBUG >= 2 + debug = 1; /* this may be reset by procargs() later */ +#endif + opentrace(); + trputs("Shell args: "); trargs(argv); +#if DEBUG >= 3 + set_debug(((DEBUG)==3 ? "_@" : "++"), 1); +#endif +#endif + rootpid = getpid(); + rootshell = 1; + init(); + initpwd(); + setstackmark(&smark); + procargs(argc, argv); + + /* + * Limit bogus system(3) or popen(3) calls in setuid binaries, + * by requiring the -p flag + */ + if (!pflag && (uid != geteuid() || gid != getegid())) { + setuid(uid); + setgid(gid); + /* PS1 might need to be changed accordingly. */ + choose_ps1(); + } + + if (argv[0] && argv[0][0] == '-') { + state = 1; + read_profile("/etc/profile"); + state1: + state = 2; + read_profile(".profile"); + } + state2: + state = 3; + if ((iflag || !posix) && + getuid() == geteuid() && getgid() == getegid()) { + struct stackmark env_smark; + + setstackmark(&env_smark); + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + state = 3; + read_profile(expandenv(shinit)); + } + popstackmark(&env_smark); + } + state3: + state = 4; + line_number = 1; /* undo anything from profile files */ + + if (sflag == 0 || minusc) { + static int sigs[] = { + SIGINT, SIGQUIT, SIGHUP, +#ifdef SIGTSTP + SIGTSTP, +#endif + SIGPIPE + }; +#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0])) + size_t i; + + for (i = 0; i < SIGSSIZE; i++) + setsignal(sigs[i], 0); + } + + if (minusc) + evalstring(minusc, sflag ? 0 : EV_EXIT); + + if (sflag || minusc == NULL) { + state4: /* XXX ??? - why isn't this before the "if" statement */ + cmdloop(1); + } +#if PROFILE + monitor(0); +#endif + line_number = plinno; + exitshell(exitstatus); + /* NOTREACHED */ +} + + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +void +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + enum skipstate skip; + + CTRACE(DBG_ALWAYS, ("cmdloop(%d) called\n", top)); + setstackmark(&smark); + for (;;) { + if (pendingsigs) + dotrap(); + inter = 0; + if (iflag == 1 && top) { + inter = 1; + showjobs(out2, SHOW_CHANGED); + chkmail(0); + flushout(&errout); + nflag = 0; + } + n = parsecmd(inter); + VXTRACE(DBG_PARSE|DBG_EVAL|DBG_CMDS,("cmdloop: "),showtree(n)); + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (nflag) + break; + if (!stoppedjobs()) { + if (!iflag || !Iflag) + break; + out2str("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (n != NULL && nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + rststackmark(&smark); + + /* + * Any SKIP* can occur here! SKIP(FUNC|BREAK|CONT) occur when + * a dotcmd is in a loop or a function body and appropriate + * built-ins occurs in file scope in the sourced file. Values + * other than SKIPFILE are reset by the appropriate eval*() + * that contained the dotcmd() call. + */ + skip = current_skipstate(); + if (skip != SKIPNONE) { + if (skip == SKIPFILE) + stop_skipping(); + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +STATIC void +read_profile(const char *name) +{ + int fd; + int xflag_set = 0; + int vflag_set = 0; + + if (*name == '\0') + return; + + INTOFF; + if ((fd = open(name, O_RDONLY)) >= 0) + setinputfd(fd, 1); + INTON; + if (fd < 0) + return; + /* -q turns off -x and -v just when executing init files */ + if (qflag) { + if (xflag) + xflag = 0, xflag_set = 1; + if (vflag) + vflag = 0, vflag_set = 1; + } + cmdloop(0); + if (qflag) { + if (xflag_set) + xflag = 1; + if (vflag_set) + vflag = 1; + } + popfile(); +} + + + +/* + * Read a file containing shell functions. + */ + +void +readcmdfile(char *name) +{ + int fd; + + INTOFF; + if ((fd = open(name, O_RDONLY)) >= 0) + setinputfd(fd, 1); + else + error("Can't open %s", name); + INTON; + cmdloop(0); + popfile(); +} + + + +int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argc > 1) + exitshell(number(argv[1])); + else + exitshell_savedstatus(); + /* NOTREACHED */ +} diff --git a/bin/sh/main.h b/bin/sh/main.h new file mode 100644 index 0000000..db1d576 --- /dev/null +++ b/bin/sh/main.h @@ -0,0 +1,42 @@ +/* $NetBSD: main.h,v 1.12 2018/12/03 02:38:30 kre Exp $ */ + +/*- + * 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. + * + * @(#)main.h 8.2 (Berkeley) 5/4/95 + */ + +extern int rootpid; /* pid of main shell */ +extern int rootshell; /* true if we aren't a child of the main shell */ +extern struct jmploc main_handler; /* top level exception handler */ + +void readcmdfile(char *); +void cmdloop(int); diff --git a/bin/sh/memalloc.c b/bin/sh/memalloc.c new file mode 100644 index 0000000..da8ff3c --- /dev/null +++ b/bin/sh/memalloc.c @@ -0,0 +1,334 @@ +/* $NetBSD: memalloc.c,v 1.32 2018/08/22 20:08:54 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: memalloc.c,v 1.32 2018/08/22 20:08:54 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "machdep.h" +#include "mystring.h" + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + p = malloc(nbytes); + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + p = realloc(p, nbytes); + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + + p = ckmalloc(strlen(s) + 1); + scopy(s, p); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 504 was chosen because the Ultrix malloc handles that size + * well. + */ + +#define MINSIZE 504 /* minimum size of a block */ + +struct stack_block { + struct stack_block *prev; + char space[MINSIZE]; +}; + +struct stack_block stackbase; +struct stack_block *stackp = &stackbase; +struct stackmark *markp; +char *stacknxt = stackbase.space; +int stacknleft = MINSIZE; +int sstrnleft; +int herefd = -1; + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = SHELL_ALIGN(nbytes); + if (nbytes > stacknleft) { + int blocksize; + struct stack_block *sp; + + blocksize = nbytes; + if (blocksize < MINSIZE) + blocksize = MINSIZE; + INTOFF; + sp = ckmalloc(sizeof(struct stack_block) - MINSIZE + blocksize); + sp->prev = stackp; + stacknxt = sp->space; + stacknleft = blocksize; + stackp = sp; + INTON; + } + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(2, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +/* save the current status of the sh stack */ +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + mark->sstrnleft = sstrnleft; + mark->marknext = markp; + markp = mark; +} + +/* reset the stack mark, and remove it from the list of marks */ +void +popstackmark(struct stackmark *mark) +{ + markp = mark->marknext; /* delete mark from the list */ + rststackmark(mark); /* and reset stack */ +} + +/* reset the shell stack to its state recorded in the stack mark */ +void +rststackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + while (stackp != mark->stackp) { + /* delete any recently allocated mem blocks */ + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrnleft = mark->sstrnleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +void +growstackblock(void) +{ + int newlen = SHELL_ALIGN(stacknleft * 2 + 100); + + INTOFF; + if (stacknxt == stackp->space && stackp != &stackbase) { + struct stack_block *oldstackp; + struct stackmark *xmark; + struct stack_block *sp; + + oldstackp = stackp; + sp = stackp; + stackp = sp->prev; + sp = ckrealloc((pointer)sp, + sizeof(struct stack_block) - MINSIZE + newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = sp->space; + sstrnleft += newlen - stacknleft; + stacknleft = newlen; + + /* + * Stack marks pointing to the start of the old block + * must be relocated to point to the new block + */ + xmark = markp; + while (xmark != NULL && xmark->stackp == oldstackp) { + xmark->stackp = stackp; + xmark->stacknxt = stacknxt; + xmark->sstrnleft += stacknleft - xmark->stacknleft; + xmark->stacknleft = stacknleft; + xmark = xmark->marknext; + } + } else { + char *oldspace = stacknxt; + int oldlen = stacknleft; + char *p = stalloc(newlen); + + (void)memcpy(p, oldspace, oldlen); + stacknxt = p; /* free the space */ + stacknleft += newlen; /* we just allocated */ + } + INTON; +} + +void +grabstackblock(int len) +{ + len = SHELL_ALIGN(len); + stacknxt += len; + stacknleft -= len; +} + +/* + * The following routines are somewhat easier to use than the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +char * +growstackstr(void) +{ + int len = stackblocksize(); + if (herefd >= 0 && len >= 1024) { + xwrite(herefd, stackblock(), len); + sstrnleft = len - 1; + return stackblock(); + } + growstackblock(); + sstrnleft = stackblocksize() - len - 1; + return stackblock() + len; +} + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(void) +{ + int len = stackblocksize() - sstrnleft; + growstackblock(); + sstrnleft = stackblocksize() - len; + return stackblock() + len; +} + +/* + * Note that this only works to release stack space for reuse + * if nothing else has allocated space on the stack since the grabstackstr() + * + * "s" is the start of the area to be released, and "p" represents the end + * of the string we have stored beyond there and are now releasing. + * (ie: "p" should be the same as in the call to grabstackstr()). + * + * stunalloc(s) and ungrabstackstr(s, p) are almost interchangable after + * a grabstackstr(), however the latter also returns string space so we + * can just continue with STPUTC() etc without needing a new STARTSTACKSTR(s) + */ +void +ungrabstackstr(char *s, char *p) +{ +#ifdef DEBUG + if (s < stacknxt || stacknxt + stacknleft < s) + abort(); +#endif + stacknleft += stacknxt - s; + stacknxt = s; + sstrnleft = stacknleft - (p - s); +} diff --git a/bin/sh/memalloc.h b/bin/sh/memalloc.h new file mode 100644 index 0000000..ed6669e --- /dev/null +++ b/bin/sh/memalloc.h @@ -0,0 +1,79 @@ +/* $NetBSD: memalloc.h,v 1.18 2018/08/22 20:08:54 kre Exp $ */ + +/*- + * 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. + * + * @(#)memalloc.h 8.2 (Berkeley) 5/4/95 + */ + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; + int sstrnleft; + struct stackmark *marknext; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern int sstrnleft; +extern int herefd; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +void rststackmark(struct stackmark *); +void growstackblock(void); +void grabstackblock(int); +char *growstackstr(void); +char *makestrspace(void); +void ungrabstackstr(char *, char *); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define STARTSTACKSTR(p) p = stackblock(), sstrnleft = stackblocksize() +#define STPUTC(c, p) (--sstrnleft >= 0? (*p++ = (c)) : (p = growstackstr(), *p++ = (c))) +#define CHECKSTRSPACE(n, p) { if (sstrnleft < n) p = makestrspace(); } +#define USTPUTC(c, p) (--sstrnleft, *p++ = (c)) +#define STACKSTRNUL(p) (sstrnleft == 0? (p = growstackstr(), sstrnleft++, *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (++sstrnleft, --p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount), sstrnleft -= (amount)) +#define grabstackstr(p) stalloc((p) - stackblock()) + +#define ckfree(p) free((pointer)(p)) diff --git a/bin/sh/miscbltin.c b/bin/sh/miscbltin.c new file mode 100644 index 0000000..3988532 --- /dev/null +++ b/bin/sh/miscbltin.c @@ -0,0 +1,458 @@ +/* $NetBSD: miscbltin.c,v 1.44 2017/05/13 15:03:34 gson Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: miscbltin.c,v 1.44 2017/05/13 15:03:34 gson Exp $"); +#endif +#endif /* not lint */ + +/* + * Miscelaneous builtins. + */ + +#include <sys/types.h> /* quad_t */ +#include <sys/param.h> /* BSD4_4 */ +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "builtins.h" +#include "mystring.h" + +#undef rflag + + + +/* + * The read builtin. + * Backslahes escape the next char unless -r is specified. + * + * This uses unbuffered input, which may be avoidable in some cases. + * + * Note that if IFS=' :' then read x y should work so that: + * 'a b' x='a', y='b' + * ' a b ' x='a', y='b' + * ':b' x='', y='b' + * ':' x='', y='' + * '::' x='', y='' + * ': :' x='', y='' + * ':::' x='', y='::' + * ':b c:' x='', y='b c:' + */ + +int +readcmd(int argc, char **argv) +{ + char **ap; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + + rflag = 0; + prompt = NULL; + while ((i = nextopt("p:r")) != '\0') { + if (i == 'p') + prompt = optionarg; + else + rflag = 1; + } + + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + + if (*(ap = argptr) == NULL) + error("arg count"); + + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + status = 0; + startword = 2; + STARTSTACKSTR(p); + for (;;) { + if (read(0, &c, 1) != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + if (c == '\\' && !rflag) { + if (read(0, &c, 1) != 1) { + status = 1; + break; + } + if (c != '\n') + STPUTC(c, p); + continue; + } + if (c == '\n') + break; + if (strchr(ifs, c)) + is_ifs = strchr(" \t\n", c) ? 1 : 2; + else + is_ifs = 0; + + if (startword != 0) { + if (is_ifs == 1) { + /* Ignore leading IFS whitespace */ + if (saveall) + STPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) + STPUTC(c, p); + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + STPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + STPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + } + STACKSTRNUL(p); + + /* Remove trailing IFS chars */ + for (; stackblock() <= --p; *p = 0) { + if (!strchr(ifs, *p)) + break; + if (strchr(" \t\n", *p)) + /* Always remove whitespace */ + continue; + if (saveall > 1) + /* Don't remove non-whitespace unless it was naked */ + break; + } + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, nullstr, 0); + return status; +} + + + +int +umaskcmd(int argc, char **argv) +{ + char *ap; + int mask; + int i; + int symbolic_mode = 0; + + while ((i = nextopt("S")) != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char u[4], g[4], o[4]; + + i = 0; + if ((mask & S_IRUSR) == 0) + u[i++] = 'r'; + if ((mask & S_IWUSR) == 0) + u[i++] = 'w'; + if ((mask & S_IXUSR) == 0) + u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if ((mask & S_IRGRP) == 0) + g[i++] = 'r'; + if ((mask & S_IWGRP) == 0) + g[i++] = 'w'; + if ((mask & S_IXGRP) == 0) + g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if ((mask & S_IROTH) == 0) + o[i++] = 'r'; + if ((mask & S_IWOTH) == 0) + o[i++] = 'w'; + if ((mask & S_IXOTH) == 0) + o[i++] = 'x'; + o[i] = '\0'; + + out1fmt("u=%s,g=%s,o=%s\n", u, g, o); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (isdigit((unsigned char)*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", argv[1]); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + + INTOFF; + if ((set = setmode(ap)) != 0) { + mask = getmode(set, ~mask & 0777); + ckfree(set); + } + INTON; + if (!set) + error("Cannot set mode `%s' (%s)", ap, + strerror(errno)); + + umask(~mask & 0777); + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + const char *unit; + int cmd; + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file", "blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "coredump", "blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "memory", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory","kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NTHR + { "thread", "threads", RLIMIT_NTHR, 1, 'r' }, +#endif +#ifdef RLIMIT_NPROC + { "process", "processes", RLIMIT_NPROC, 1, 'p' }, +#endif +#ifdef RLIMIT_NOFILE + { "nofiles", "descriptors", RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "vmemory", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "sbsize", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif + { NULL, NULL, 0, 0, '\0' } +}; + +int +ulimitcmd(int argc, char **argv) +{ + int c; + rlim_t val = 0; + enum { SOFT = 0x1, HARD = 0x2 } + how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSabtfdscmlrpnv")) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) + error("internal error (%c)", what); + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + error("too many arguments"); + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else { + val = (rlim_t) 0; + + while ((c = *p++) >= '0' && c <= '9') + val = (val * 10) + (long)(c - '0'); + if (c) + error("bad number"); + val *= l->factor; + } + } + if (all) { + for (l = limits; l->name; l++) { + getrlimit(l->cmd, &limit); + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + + out1fmt("%-13s (-%c %-11s) ", l->name, l->option, + l->unit); + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else + { + val /= l->factor; +#ifdef BSD4_4 + out1fmt("%lld\n", (long long) val); +#else + out1fmt("%ld\n", (long) val); +#endif + } + } + return 0; + } + + if (getrlimit(l->cmd, &limit) == -1) + error("error getting limit (%s)", strerror(errno)); + if (set) { + if (how & HARD) + limit.rlim_max = val; + if (how & SOFT) + limit.rlim_cur = val; + if (setrlimit(l->cmd, &limit) < 0) + error("error setting limit (%s)", strerror(errno)); + } else { + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else + { + val /= l->factor; +#ifdef BSD4_4 + out1fmt("%lld\n", (long long) val); +#else + out1fmt("%ld\n", (long) val); +#endif + } + } + return 0; +} diff --git a/bin/sh/miscbltin.h b/bin/sh/miscbltin.h new file mode 100644 index 0000000..4c12c82 --- /dev/null +++ b/bin/sh/miscbltin.h @@ -0,0 +1,31 @@ +/* $NetBSD: miscbltin.h,v 1.3 2003/08/21 17:57:53 christos Exp $ */ + +/* + * Copyright (c) 1997 Christos Zoulas. All rights reserved. + * + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); diff --git a/bin/sh/mkbuiltins b/bin/sh/mkbuiltins new file mode 100644 index 0000000..2ebf7ac --- /dev/null +++ b/bin/sh/mkbuiltins @@ -0,0 +1,136 @@ +#!/bin/sh - +# $NetBSD: mkbuiltins,v 1.22 2009/10/06 19:56:58 apb Exp $ +# +# 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. +# +# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95 + +havehist=1 +if [ x"$1" = x"-h" ]; then + havehist=0 + shift +fi + +shell=$1 +builtins=$2 +objdir=$3 + +havejobs=0 +if grep '^#define JOBS[ ]*1' ${shell} > /dev/null +then + havejobs=1 +fi + +exec <$builtins 3> ${objdir}/builtins.c 4> ${objdir}/builtins.h + +echo '/* + * This file was generated by the mkbuiltins program. + */ + +#include "shell.h" +#include "builtins.h" + +const struct builtincmd builtincmd[] = { +' >&3 + +echo '/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> + +struct builtincmd { + const char *name; + int (*builtin)(int, char **); +}; + +extern const struct builtincmd builtincmd[]; +extern const struct builtincmd splbltincmd[]; + +' >&4 + +specials= + +while read line +do + set -- $line + [ -z "$1" ] && continue + case "$1" in + \#if*|\#def*|\#end*) + echo $line >&3 + echo $line >&4 + continue + ;; + \#*) + continue + ;; + esac + + func=$1 + shift + [ x"$1" = x'-j' ] && { + [ $havejobs = 0 ] && continue + shift + } + [ x"$1" = x'-h' ] && { + [ $havehist = 0 ] && continue + shift + } + echo 'int '"$func"'(int, char **);' >&4 + while + [ $# != 0 ] && [ x"$1" != x'#' ] + do + [ x"$1" = x'-s' ] && { + specials="$specials $2 $func" + shift 2 + continue; + } + [ x"$1" = x'-u' ] && shift + echo ' { "'$1'", '"$func"' },' >&3 + shift + done +done + +echo ' { 0, 0 },' >&3 +echo '};' >&3 +echo >&3 +echo 'const struct builtincmd splbltincmd[] = {' >&3 + +set -- $specials +while + [ $# != 0 ] +do + echo ' { "'$1'", '"$2"' },' >&3 + shift 2 +done + +echo ' { 0, 0 },' >&3 +echo "};" >&3 diff --git a/bin/sh/mkinit.sh b/bin/sh/mkinit.sh new file mode 100755 index 0000000..da4f46f --- /dev/null +++ b/bin/sh/mkinit.sh @@ -0,0 +1,224 @@ +#! /bin/sh +# $NetBSD: mkinit.sh,v 1.10 2018/12/05 09:20:18 kre Exp $ + +# Copyright (c) 2003 The NetBSD Foundation, Inc. +# All rights reserved. +# +# This code is derived from software contributed to The NetBSD Foundation +# by David Laight. +# +# 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. +# +# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + +srcs="$*" + +# use of echo in this script is broken + +# some echo versions will expand \n in the args, which breaks C +# Note: this script is a HOST_PROG ... it must run in the +# build host's environment, with its shell. + +# Fortunately, use of echo here is also trivially simplistic, +# we can easily replace all uses with ... + +echo() +{ + printf '%s\n' "$1" +} + +# CAUTION: for anyone modifying this script.... use printf +# rather than echo to output anything at all... then +# you will avoid being bitten by the simplicity of this function. +# This was done this way rather than wholesale replacement +# to avoid unnecessary code churn. + + +nl=' +' +openparen='(' + +# shells have bugs (including older NetBSD sh) in how \ is +# used in pattern matching. So work out what the shell +# running this script expects. We could also just use a +# literal \ in the pattern, which would need to be quoted +# of course, but then we'd run into a whole host of potential +# other shell bugs (both with the quoting in the pattern, and +# with the matching that follows if that works as inended). +# Far easier, and more reliable, is to just work out what works, +# and then use it, which more or less mandates using a variable... +backslash='\\' +var='abc\' # dummy test case. +if [ "$var" = "${var%$backslash}" ] +then + # buggy sh, try the broken way + backslash='\' + if [ "$var" = "${var%$backslash}" ] + then + printf >&2 "$0: %s\n" 'No pattern match with \ (broken shell)' + exit 1 + fi +fi +# We know we can detect the presence of a trailing \, which is all we need. +# Now to confirm we will not generate false matches. +var='abc' +if [ "$var" != "${var%$backslash}" ] +then + printf >&2 "$0: %s\n" 'Bogus pattern match with \ (broken shell)' + exit 1 +fi +unset var + +includes=' "shell.h" "mystring.h" "init.h" ' +defines= +decles= +event_init= +event_reset= +event_shellproc= + +for src in $srcs; do + exec <$src + decnl="$nl" + while IFS=; read -r line; do + [ "$line" = x ] + case "$line " in + INIT["{ "]* ) event=init;; + RESET["{ "]* ) event=reset;; + SHELLPROC["{ "]* ) event=shellproc;; + INCLUDE[\ \ ]* ) + IFS=' ' + set -- $line + # ignore duplicates + [ "${includes}" != "${includes% $2 *}" ] && continue + includes="$includes$2 " + continue + ;; + MKINIT\ ) + # struct declaration + decles="$decles$nl" + while + read -r line + decles="${decles}${line}${nl}" + [ "$line" != "};" ] + do + : + done + decnl="$nl" + continue + ;; + MKINIT["{ "]* ) + # strip initialiser + def=${line#MKINIT} + comment="${def#*;}" + def="${def%;$comment}" + def="${def%%=*}" + def="${def% }" + decles="${decles}${decnl}extern${def};${comment}${nl}" + decnl= + continue + ;; + \#define[\ \ ]* ) + IFS=' ' + set -- $line + # Ignore those with arguments + [ "$2" = "${2##*$openparen}" ] || continue + # and multiline definitions + [ "$line" = "${line%$backslash}" ] || continue + defines="${defines}#undef $2${nl}${line}${nl}" + continue + ;; + * ) continue;; + esac + # code for events + ev="${nl} /* from $src: */${nl} {${nl}" + # Indent the text by an extra <tab> + while + read -r line + [ "$line" != "}" ] + do + case "$line" in + ('') ;; + ('#'*) ;; + (*) line=" $line";; + esac + ev="${ev}${line}${nl}" + done + ev="${ev} }${nl}" + eval event_$event=\"\$event_$event\$ev\" + done +done + +exec >init.c.tmp + +echo "/*" +echo " * This file was generated by the mkinit program." +echo " */" +echo + +IFS=' ' +for f in $includes; do + echo "#include $f" +done + +echo +echo +echo +echo "$defines" +echo +echo "$decles" +echo +echo +echo "/*" +echo " * Initialization code." +echo " */" +echo +echo "void" +echo "init(void)" +echo "{" +echo "${event_init}" +echo "}" +echo +echo +echo +echo "/*" +echo " * This routine is called when an error or an interrupt occurs in an" +echo " * interactive shell and control is returned to the main command loop." +echo " */" +echo +echo "void" +echo "reset(void)" +echo "{" +echo "${event_reset}" +echo "}" +echo +echo +echo +echo "/*" +echo " * This routine is called to initialize the shell to run a shell procedure." +echo " */" +echo +echo "void" +echo "initshellproc(void)" +echo "{" +echo "${event_shellproc}" +echo "}" + +exec >&- +mv init.c.tmp init.c diff --git a/bin/sh/mknodenames.sh b/bin/sh/mknodenames.sh new file mode 100755 index 0000000..1d03200 --- /dev/null +++ b/bin/sh/mknodenames.sh @@ -0,0 +1,69 @@ +#! /bin/sh + +# $NetBSD: mknodenames.sh,v 1.6 2018/08/18 03:09:37 kre Exp $ + +# Use this script however you like, but it would be amazing if +# it has any purpose other than as part of building the shell... + +if [ -z "$1" ]; then + echo "Usage: $0 nodes.h" 1>&2 + exit 1 +fi + +NODES=$1 + +test -t 1 && test -z "$2" && exec > nodenames.h + +echo "\ +/* + * Automatically generated by $0 + * DO NOT EDIT. Do Not 'cvs add'. + */ +" +echo "#ifndef NODENAMES_H_INCLUDED" +echo "#define NODENAMES_H_INCLUDED" +echo +echo "#ifdef DEBUG" + +MAX=$(awk < "$NODES" ' + /#define/ { + if ($3 > MAX) MAX = $3 + } + END { print MAX } +') + +echo +echo '#ifdef DEFINE_NODENAMES' +echo "STATIC const char * const NodeNames[${MAX} + 1] = {" + +grep '^#define' "$NODES" | sort -k3n | while read define name number opt_comment +do + : ${next:=0} + while [ "$number" -gt "$next" ] + do + echo ' "???",' + next=$(( next + 1)) + done + echo ' "'"$name"'",' + next=$(( number + 1 )) +done + +echo "};" +echo '#else' +echo "extern const char * const NodeNames[${MAX} + 1];" +echo '#endif' +echo +echo '#define NODETYPENAME(type) \' +echo ' ((unsigned)(type) <= '"${MAX}"' ? NodeNames[(type)] : "??OOR??")' +echo +echo '#define NODETYPE(type) NODETYPENAME(type), (type)' +echo '#define PRIdsNT "s(%d)"' +echo +echo '#else /* DEBUG */' +echo +echo '#define NODETYPE(type) (type)' +echo '#define PRIdsNT "d"' +echo +echo '#endif /* DEBUG */' +echo +echo '#endif /* !NODENAMES_H_INCLUDED */' diff --git a/bin/sh/mknodes.sh b/bin/sh/mknodes.sh new file mode 100755 index 0000000..0b1ab80 --- /dev/null +++ b/bin/sh/mknodes.sh @@ -0,0 +1,242 @@ +#! /bin/sh +# $NetBSD: mknodes.sh,v 1.4 2019/01/19 13:08:50 kre Exp $ + +# Copyright (c) 2003 The NetBSD Foundation, Inc. +# All rights reserved. +# +# This code is derived from software contributed to The NetBSD Foundation +# by David Laight. +# +# 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. +# +# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + +nodetypes=$1 +nodes_pat=$2 +objdir="$3" + +exec <$nodetypes +exec >$objdir/nodes.h.tmp + +echo "/*" +echo " * This file was generated by mknodes.sh" +echo " */" +echo + +tagno=0 +while IFS=; read -r line; do + line="${line%%#*}" + IFS=' ' + set -- $line + IFS= + [ -z "$2" ] && continue + case "$line" in + [" "]* ) + IFS=' ' + [ $field = 0 ] && struct_list="$struct_list $struct" + eval field_${struct}_$field=\"\$*\" + eval numfld_$struct=\$field + field=$(($field + 1)) + ;; + * ) + define=$1 + struct=$2 + echo "#define $define $tagno" + tagno=$(($tagno + 1)) + eval define_$struct=\"\$define_$struct \$define\" + struct_define="$struct_define $struct" + field=0 + ;; + esac +done + +echo + +IFS=' ' +for struct in $struct_list; do + echo + echo + echo "struct $struct {" + field=0 + while + eval line=\"\$field_${struct}_$field\" + field=$(($field + 1)) + [ -n "$line" ] + do + IFS=' ' + set -- $line + name=$1 + case "$name" in + type) if [ -n "$typetype" ] && [ "$typetype" != "$2" ] + then + echo >&2 "Conflicting type fields: node" \ + "$struct has $2, others $typetype" + exit 1 + fi + if [ $field -ne 1 ] + then + echo >&2 "Node $struct has type as field" \ + "$field (should only be first)" + exit 1 + fi + typetype=$2 + ;; + *) + if [ $field -eq 1 ] + then + echo >&2 "Node $struct does not have" \ + "type as first field" + exit 1 + fi + ;; + esac + case $2 in + nodeptr ) type="union node *";; + nodelist ) type="struct nodelist *";; + string ) type="char *";; + int*_t | uint*_t | int ) type="$2 ";; + * ) name=; shift 2; type="$*";; + esac + echo " $type$name;" + done + echo "};" +done + +echo +echo +echo "union node {" +echo " $typetype type;" +for struct in $struct_list; do + echo " struct $struct $struct;" +done +echo "};" +echo +echo +echo "struct nodelist {" +echo " struct nodelist *next;" +echo " union node *n;" +echo "};" +echo +echo +echo 'struct funcdef;' +echo 'struct funcdef *copyfunc(union node *);' +echo 'union node *getfuncnode(struct funcdef *);' +echo 'void reffunc(struct funcdef *);' +echo 'void unreffunc(struct funcdef *);' +echo 'void freefunc(struct funcdef *);' + +mv $objdir/nodes.h.tmp $objdir/nodes.h || exit 1 + +exec <$nodes_pat +exec >$objdir/nodes.c.tmp + +echo "/*" +echo " * This file was generated by mknodes.sh" +echo " */" +echo + +while IFS=; read -r line; do + IFS=' ' + set -- $line + IFS= + case "$1" in + '%SIZES' ) + echo "static const short nodesize[$tagno] = {" + IFS=' ' + for struct in $struct_define; do + echo " SHELL_ALIGN(sizeof (struct $struct))," + done + echo "};" + ;; + '%CALCSIZE' ) + echo " if (n == NULL)" + echo " return;" + echo " res->bsize += nodesize[n->type];" + echo " switch (n->type) {" + IFS=' ' + for struct in $struct_list; do + eval defines=\"\$define_$struct\" + for define in $defines; do + echo " case $define:" + done + eval field=\$numfld_$struct + while + [ $field != 0 ] + do + eval line=\"\$field_${struct}_$field\" + field=$(($field - 1)) + IFS=' ' + set -- $line + name=$1 + cl=", res)" + case $2 in + nodeptr ) fn=calcsize;; + nodelist ) fn=sizenodelist;; + string ) fn="res->ssize += strlen" + cl=") + 1";; + * ) continue;; + esac + echo " ${fn}(n->$struct.$name${cl};" + done + echo " break;" + done + echo " };" + ;; + '%COPY' ) + echo " if (n == NULL)" + echo " return NULL;" + echo " new = st->block;" + echo " st->block = (char *) st->block + nodesize[n->type];" + echo " switch (n->type) {" + IFS=' ' + for struct in $struct_list; do + eval defines=\"\$define_$struct\" + for define in $defines; do + echo " case $define:" + done + eval field=\$numfld_$struct + while + [ $field != 0 ] + do + eval line=\"\$field_${struct}_$field\" + field=$(($field - 1)) + IFS=' ' + set -- $line + name=$1 + case $2 in + nodeptr ) fn="copynode(";; + nodelist ) fn="copynodelist(";; + string ) fn="nodesavestr(";; + int*_t| uint*_t | int ) fn=;; + * ) continue;; + esac + f="$struct.$name" + echo " new->$f = ${fn}n->$f${fn:+, st)};" + done + echo " break;" + done + echo " };" + echo " new->type = n->type;" + ;; + * ) echo "$line";; + esac +done + +mv $objdir/nodes.c.tmp $objdir/nodes.c || exit 1 diff --git a/bin/sh/mkoptions.sh b/bin/sh/mkoptions.sh new file mode 100644 index 0000000..aecb6df --- /dev/null +++ b/bin/sh/mkoptions.sh @@ -0,0 +1,198 @@ +#! /bin/sh + +# $NetBSD: mkoptions.sh,v 1.5 2017/11/15 09:21:19 kre Exp $ + +# +# It would be more sensible to generate 2 .h files, one which +# is for everyone to use, defines the "variables" and (perhaps) generates +# the externs (though they could just be explicit in options.h) +# and one just for options.c which generates the initialisation. +# +# But then I'd have to deal with making the Makefile handle that properly... +# (this is simpler there, and it just means a bit more sh compile time.) + +set -f +IFS=' ' # blank, tab (no newline) + +IF="$1" +OF="${3+$3/}$2" + +E_FILE=$(${MKTEMP:-mktemp} -t MKO.E.$$) +O_FILE=$(${MKTEMP:-mktemp} -t MKO.O.$$) +trap 'rm -f "${E_FILE}" "${O_FILE}"' EXIT + +exec 5> "${E_FILE}" +exec 6> "${O_FILE}" + +{ + printf '/*\n * File automatically generated by %s.\n' "$0" + printf ' * Do not edit, do not add to cvs.\n' + printf ' */\n\n' + + printf '#ifdef DEFINE_OPTIONS\n' + printf 'struct optent optlist[] = {\n' +} >"${OF}" + +FIRST=true + +${SED:-sed} <"${IF}" \ + -e '/^$/d' \ + -e '/^#/d' \ + -e '/^[ ]*\//d' \ + -e '/^[ ]*\*/d' \ + -e '/^[ ]*;/d' | +sort -b -k2,2f -k2,2 < "${IF}" | +while read line +do + # Look for comments in various styles, and ignore them + # (these should generally be already removed by sed above) + + case "${line}" in + '') continue;; + /*) continue;; + \**) continue;; + \;*) continue;; + \#*) continue;; + esac + + case "${line}" in + *'#if'*) + COND="${line#*#}" + COND="#${COND%%#*}" + ;; + *) + COND= + ;; + esac + set -- ${line%%[ ]#*} + + var="$1" name="$2" + + case "${var}" in + ('' | [!A-Za-z_]* | *[!A-Za-z0-9_]*) + printf >&2 "Bad var name: '%s'\\n" "${var}" + # exit 1 + continue # just ignore it for now + esac + + case "${name}" in + ?) set -- ${var} '' $name $3 $4; name= ;; + esac + + chr="$3" set="$4" dflt="$5" + + case "${chr}" in + -) chr= set= dflt="$4";; + ''|?) ;; + *) printf >&2 'flag "%s": Not a character\n' "${chr}"; continue;; + esac + + # options must have some kind of name, or they are useless... + test -z "${name}${chr}" && continue + + case "${set}" in + -) set= ;; + [01] | [Oo][Nn] | [Oo][Ff][Ff]) dflt="${set}"; set= ;; + ''|?) ;; + *) printf >&2 'set "%s": Not a character\n' "${set}"; continue;; + esac + + case "${dflt}" in + '') ;; + [Oo][Nn]) dflt=1;; + [Oo][Ff][Ff]) dflt=0;; + [01]) ;; + *) printf >&2 'default "%s" invalid, use 0 off 1 on\n'; continue;; + esac + + # validation complete, now to generate output + + if [ -n "${COND}" ] + then + printf '%s\n' "${COND}" >&4 + printf '%s\n' "${COND}" >&5 + printf '%s\n' "${COND}" >&6 + fi + + printf '\t_SH_OPT_%s,\n' "${var}" >&5 + + if [ -n "${name}" ] + then + printf ' { "%s", ' "${name}" >&4 + else + printf ' { 0, ' >&4 + fi + + if [ -n "${chr}" ] + then + printf "'%s', " "${chr}" >&4 + else + chr= + printf '0, ' >&4 + fi + + if [ -n "${set}" ] + then + printf "'%s', 0, " "${set}" >&4 + else + printf '0, 0, ' >&4 + fi + + if [ -n "${dflt}" ] + then + printf '%s },\n' "${dflt}" >&4 + else + printf '0 },\n' >&4 + fi + + printf '#define %s\toptlist[_SH_OPT_%s].val\n' "${var}" "${var}" >&6 + + if [ -n "${COND}" ] + then + printf '#endif\n' >&4 + printf '#endif\n' >&5 + printf '#endif\n' >&6 + fi + + test -z "${chr}" && continue + + printf '%s _SH_OPT_%s %s\n' "${chr}" "${var}" "${COND}" + +done 4>>"${OF}" | sort -t' ' -k1,1f -k1,1 | while read chr index COND +do + if $FIRST + then + printf ' { 0, 0, 0, 0, 0 }\n};\n' + printf '#endif\n\n' + + printf 'enum shell_opt_names {\n' + cat "${E_FILE}" + printf '};\n\n' + + printf '#ifdef DEFINE_OPTIONS\n' + printf 'const unsigned char optorder[] = {\n' + FIRST=false + fi + [ -n "${COND}" ] && printf '%s\n' "${COND}" + printf '\t%s,\n' "${index}" + [ -n "${COND}" ] && printf '#endif\n' + +done >>"${OF}" + +{ + printf '};\n\n' + printf '#define NOPTS (sizeof optlist / sizeof optlist[0] - 1)\n' + printf 'int sizeof_optlist = sizeof optlist;\n\n' + printf \ + 'const int option_flags = (sizeof optorder / sizeof optorder[0]);\n' + printf '\n#else\n\n' + printf 'extern struct optent optlist[];\n' + printf 'extern int sizeof_optlist;\n' + printf 'extern const unsigned char optorder[];\n' + printf 'extern const int option_flags;\n' + printf '\n#endif\n\n' + + cat "${O_FILE}" +} >> "${OF}" + +exit 0 diff --git a/bin/sh/mktokens b/bin/sh/mktokens new file mode 100644 index 0000000..a8f81c4 --- /dev/null +++ b/bin/sh/mktokens @@ -0,0 +1,99 @@ +#!/bin/sh - +# $NetBSD: mktokens,v 1.14 2017/07/26 03:46:54 kre Exp $ +# +# 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. +# +# @(#)mktokens 8.1 (Berkeley) 5/31/93 + +: ${AWK:=awk} +: ${SED:=sed} + +# The following is a list of tokens. The second column is nonzero if the +# token marks the end of a list. The third column is the name to print in +# error messages. + +# Note that all the keyword tokens come after TWORD, and all the others +# come before it. We rely upon that relationship - keep it! + +cat > /tmp/ka$$ <<\! +TEOF 1 end of file +TNL 0 newline +TSEMI 0 ";" +TBACKGND 0 "&" +TAND 0 "&&" +TOR 0 "||" +TPIPE 0 "|" +TLP 0 "(" +TRP 1 ")" +TENDCASE 1 ";;" +TCASEFALL 1 ";&" +TENDBQUOTE 1 "`" +TREDIR 0 redirection +TWORD 0 word +TIF 0 "if" +TTHEN 1 "then" +TELSE 1 "else" +TELIF 1 "elif" +TFI 1 "fi" +TWHILE 0 "while" +TUNTIL 0 "until" +TFOR 0 "for" +TDO 1 "do" +TDONE 1 "done" +TBEGIN 0 "{" +TEND 1 "}" +TCASE 0 "case" +TESAC 1 "esac" +TNOT 0 "!" +! +nl=`wc -l /tmp/ka$$` +exec > token.h +${AWK} '{print "#define " $1 " " NR-1}' /tmp/ka$$ +echo ' +/* Array indicating which tokens mark the end of a list */ +const char tokendlist[] = {' +${AWK} '{print "\t" $2 ","}' /tmp/ka$$ +echo '}; + +const char *const tokname[] = {' +${SED} -e 's/"/\\"/g' \ + -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \ + /tmp/ka$$ +echo '}; +' +${SED} 's/"//g' /tmp/ka$$ | ${AWK} ' +/TWORD/{print "#define KWDOFFSET " NR; print ""; + print "const char *const parsekwd[] = {"} +/TIF/,/neverfound/{print " \"" $3 "\","}' +echo ' 0 +};' + +rm /tmp/ka$$ diff --git a/bin/sh/myhistedit.h b/bin/sh/myhistedit.h new file mode 100644 index 0000000..855c1bc --- /dev/null +++ b/bin/sh/myhistedit.h @@ -0,0 +1,48 @@ +/* $NetBSD: myhistedit.h,v 1.13 2017/06/28 13:46:06 kre Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. + * + * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistsize(const char *); +void setterm(const char *); +int inputrc(int, char **); +void set_editrc(const char *); +void set_prompt_lit(const char *); +int not_fcnumber(char *); +int str_to_event(const char *, int); + diff --git a/bin/sh/mystring.c b/bin/sh/mystring.c new file mode 100644 index 0000000..9b6b40b --- /dev/null +++ b/bin/sh/mystring.c @@ -0,0 +1,140 @@ +/* $NetBSD: mystring.c,v 1.18 2018/07/13 22:43:44 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: mystring.c,v 1.18 2018/07/13 22:43:44 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * scopy(from, to) Copy a string. + * scopyn(from, to, n) Like scopy, but checks for overflow. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +const char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + +/* + * scopy - #defined in mystring.h + */ + + +/* + * scopyn - copy a string from "from" to "to", truncating the string + * if necessary. "To" is always nul terminated, even if + * truncation is performed. "Size" is the size of "to". + */ + +void +scopyn(const char *from, char *to, int size) +{ + + while (--size > 0) { + if ((*to++ = *from++) == '\0') + return; + } + *to = '\0'; +} + + +/* + * prefix -- see if pfx is a prefix of string. + */ + +int +prefix(const char *pfx, const char *string) +{ + while (*pfx) { + if (*pfx++ != *string++) + return 0; + } + return 1; +} + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + char *ep = NULL; + intmax_t n; + + if (!is_digit(*s) || ((n = strtoimax(s, &ep, 10)), + (ep == NULL || ep == s || *ep != '\0'))) + error("Illegal number: '%s'", s); + if (n < INT_MIN || n > INT_MAX) + error("Number out of range: %s", s); + return (int)n; +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + do { + if (! is_digit(*p)) + return 0; + } while (*++p != '\0'); + return 1; +} diff --git a/bin/sh/mystring.h b/bin/sh/mystring.h new file mode 100644 index 0000000..08a73e9 --- /dev/null +++ b/bin/sh/mystring.h @@ -0,0 +1,45 @@ +/* $NetBSD: mystring.h,v 1.11 2003/08/07 09:05:35 agc Exp $ */ + +/*- + * 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. + * + * @(#)mystring.h 8.2 (Berkeley) 5/4/95 + */ + +#include <string.h> + +void scopyn(const char *, char *, int); +int prefix(const char *, const char *); +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) +#define scopy(s1, s2) ((void)strcpy(s2, s1)) diff --git a/bin/sh/nodes.c.pat b/bin/sh/nodes.c.pat new file mode 100644 index 0000000..599597c --- /dev/null +++ b/bin/sh/nodes.c.pat @@ -0,0 +1,210 @@ +/* $NetBSD: nodes.c.pat,v 1.14 2018/06/22 11:04:55 kre Exp $ */ + +/*- + * 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. + * + * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95 + */ + +#include <stdlib.h> +#include <stddef.h> + +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "machdep.h" +#include "mystring.h" + + +/* used to accumulate sizes of nodes */ +struct nodesize { + int bsize; /* size of structures in function */ + int ssize; /* size of strings in node */ +}; + +/* provides resources for node copies */ +struct nodecopystate { + pointer block; /* block to allocate function from */ + char *string; /* block to allocate strings from */ +}; + + +%SIZES + + + +STATIC void calcsize(union node *, struct nodesize *); +STATIC void sizenodelist(struct nodelist *, struct nodesize *); +STATIC union node *copynode(union node *, struct nodecopystate *); +STATIC struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *); +STATIC char *nodesavestr(char *, struct nodecopystate *); + +struct funcdef { + unsigned int refcount; + union node n; /* must be last */ +}; + + +/* + * Make a copy of a parse tree. + */ + +struct funcdef * +copyfunc(union node *n) +{ + struct nodesize sz; + struct nodecopystate st; + struct funcdef *fn; + + if (n == NULL) + return NULL; + sz.bsize = offsetof(struct funcdef, n); + sz.ssize = 0; + calcsize(n, &sz); + fn = ckmalloc(sz.bsize + sz.ssize); + fn->refcount = 1; + st.block = (char *)fn + offsetof(struct funcdef, n); + st.string = (char *)fn + sz.bsize; + copynode(n, &st); + return fn; +} + +union node * +getfuncnode(struct funcdef *fn) +{ + if (fn == NULL) + return NULL; + return &fn->n; +} + + +STATIC void +calcsize(union node *n, struct nodesize *res) +{ + %CALCSIZE +} + + + +STATIC void +sizenodelist(struct nodelist *lp, struct nodesize *res) +{ + while (lp) { + res->bsize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n, res); + lp = lp->next; + } +} + + + +STATIC union node * +copynode(union node *n, struct nodecopystate *st) +{ + union node *new; + + %COPY + return new; +} + + +STATIC struct nodelist * +copynodelist(struct nodelist *lp, struct nodecopystate *st) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = st->block; + st->block = (char *)st->block + + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n, st); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +STATIC char * +nodesavestr(char *s, struct nodecopystate *st) +{ + register char *p = s; + register char *q = st->string; + char *rtn = st->string; + + while ((*q++ = *p++) != 0) + continue; + st->string = q; + return rtn; +} + + + +/* + * Handle making a reference to a function, and releasing it. + * Free the func code when there are no remaining references. + */ + +void +reffunc(struct funcdef *fn) +{ + if (fn != NULL) + fn->refcount++; +} + +void +unreffunc(struct funcdef *fn) +{ + if (fn != NULL) { + if (--fn->refcount > 0) + return; + ckfree(fn); + } +} + +/* + * this is used when we need to free the func, regardless of refcount + * which only happens when re-initing the shell for a SHELLPROC + */ +void +freefunc(struct funcdef *fn) +{ + if (fn != NULL) + ckfree(fn); +} diff --git a/bin/sh/nodetypes b/bin/sh/nodetypes new file mode 100644 index 0000000..dc5ee4a --- /dev/null +++ b/bin/sh/nodetypes @@ -0,0 +1,149 @@ +# $NetBSD: nodetypes,v 1.18 2017/06/08 13:12:17 kre Exp $ +# 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. +# +# @(#)nodetypes 8.2 (Berkeley) 5/4/95 + +# This file describes the nodes used in parse trees. Unindented lines +# contain a node type followed by a structure tag. Subsequent indented +# lines specify the fields of the structure. Several node types can share +# the same structure, in which case the fields of the structure should be +# specified only once. +# +# A field of a structure is described by the name of the field followed +# by a type. The currently implemented types are: +# nodeptr - a pointer to a node +# nodelist - a pointer to a list of nodes +# string - a pointer to a nul terminated string +# int - an integer +# other - any type that can be copied by assignment +# temp - a field that doesn't have to be copied when the node is copied +# The last two types should be followed by the text of a C declaration for +# the field. + +NSEMI nbinary # two commands separated by a semicolon + type int + ch1 nodeptr # the first child + ch2 nodeptr # the second child + +NCMD ncmd # a simple command + type int + backgnd int # set to run command in background + args nodeptr # the arguments + redirect nodeptr # list of file redirections + lineno int + +NPIPE npipe # a pipeline + type int + backgnd int # set to run pipeline in background + cmdlist nodelist # the commands in the pipeline + +NREDIR nredir # redirection (of a complex command) + type int + n nodeptr # the command + redirect nodeptr # list of file redirections + +NBACKGND nredir # run command in background +NSUBSHELL nredir # run command in a subshell + +NAND nbinary # the && operator +NOR nbinary # the || operator + +NIF nif # the if statement. Elif clauses are handled + type int # using multiple if nodes. + test nodeptr # if test + ifpart nodeptr # then ifpart + elsepart nodeptr # else elsepart + +NWHILE nbinary # the while statement. First child is the test +NUNTIL nbinary # the until statement + +NFOR nfor # the for statement + type int + args nodeptr # for var in args + body nodeptr # do body; done + var string # the for variable + +NCASE ncase # a case statement + type int + expr nodeptr # the word to switch on + cases nodeptr # the list of cases (NCLIST nodes) + lineno int + +NCLISTCONT nclist # a case terminated by ';&' (fall through) +NCLIST nclist # a case + type int + next nodeptr # the next case in list + pattern nodeptr # list of patterns for this case + body nodeptr # code to execute for this case + lineno int + + +NDEFUN narg # define a function. The "next" field contains + # the body of the function. + +NARG narg # represents a word + type int + next nodeptr # next word in list + text string # the text of the word + backquote nodelist # list of commands in back quotes + lineno int + +NTO nfile # fd> fname +NCLOBBER nfile # fd>| fname +NFROM nfile # fd< fname +NFROMTO nfile # fd<> fname +NAPPEND nfile # fd>> fname + type int + next nodeptr # next redirection in list + fd int # file descriptor being redirected + fname nodeptr # file name, in a NARG node + expfname temp char *expfname # actual file name + +NTOFD ndup # fd<&dupfd +NFROMFD ndup # fd>&dupfd + type int + next nodeptr # next redirection in list + fd int # file descriptor being redirected + dupfd int # file descriptor to duplicate + vname nodeptr # file name if fd>&$var + + +NHERE nhere # fd<<\! +NXHERE nhere # fd<<! + type int + next nodeptr # next redirection in list + fd int # file descriptor being redirected + doc nodeptr # input to command (NARG node) + +NNOT nnot # ! command (actually pipeline) +NDNOT nnot # ! ! pipeline (optimisation) + type int + com nodeptr diff --git a/bin/sh/option.list b/bin/sh/option.list new file mode 100644 index 0000000..3be683a --- /dev/null +++ b/bin/sh/option.list @@ -0,0 +1,79 @@ +/* $NetBSD: option.list,v 1.9 2018/11/23 20:40:06 kre Exp $ */ + +/* + * define the shell's settable options + * + * new options can be defined by adding them here, + * but they do nothing until code to implement them + * is added (using the "var name" field) + */ + +/* + * format is up to 5 columns... (followed by anything) + * end of line comments can be introduced by ' #' (space/tab hash) to eol. + * + * The columns are: + * 1. internal shell "var name" (required) + * 2. option long name + * if a single char, then no long name, and remaining + * columns shift left (this becomes the short name) + * 3. option short name (single character name) + * if '-' or absent then no short name + * if neither long nor short name, line is ignored + * 4. option set short name (name of option equiv class) + * if '-' or absent then no class + * 5. default value of option + * if absent, default is 0 + * only 0 or 1 possible (0==off 1==on) ("on" and "off" can be used) + * + * Data may be followed by any C preprocessor #if expression (incl the #if..) + * (including #ifdef #ifndef) to conditionalise output for that option. + * The #if expression continues until \n or next following '#' + */ + +// the POSIX defined options +aflag allexport a # export all variables +eflag errexit e # exit on command error ($? != 0) +mflag monitor m # enable job control +Cflag noclobber C # do not overwrite files when using > +nflag noexec n # do not execue commands +fflag noglob f # no pathname expansion +uflag nounset u # expanding unset var is an error +vflag verbose v # echo commands as read +xflag xtrace x # trace command execution + +// the long name (ignoreeof) is standard, the I flag is not +Iflag ignoreeof I # do not exit interactive shell on EOF + +// defined but not really implemented by the shell (yet) - they do nothing +bflag notify b # [U] report bg job completion +nolog nolog # [U] no func definitions in history +// 'h' is standard, long name (trackall) is not +hflag trackall h # [U] locate cmds in funcs during defn + +// 's' is standard for command line, not as 'set' option, nor 'stdin' name +sflag stdin s # read from standard input +// minusc c # command line option only. +// -- o # handled differently... + +// non-standard options -- 'i' is just a state, not an option in standard. +iflag interactive i # interactive shell +cdprint cdprint # always print result of a cd +usefork fork F # use fork(2) instead of vfork(2) +pflag nopriv p # preserve privs if set[ug]id +posix posix # be closer to POSIX compat +qflag quietprofile q # disable -v/-x in startup files +fnline1 local_lineno L on # number lines in funcs starting at 1 +promptcmds promptcmds # allow $( ) in PS1 (et al). +pipefail pipefail # pipe exit status +Xflag xlock X #ifndef SMALL # sticky stderr for -x (implies -x) + +// editline/history related options ("vi" is standard, 'V' and others are not) +// only one of vi/emacs can be set, hence the "set" definition, value +// of that can be any char (not used for a different set) +Vflag vi V V # enable vi style editing +Eflag emacs E V # enable emacs style editing +tabcomplete tabcomplete # make <tab> cause filename expansion + +// internal debug option (not usually included in the shell) +debug debug #ifdef DEBUG # enable internal shell debugging diff --git a/bin/sh/options.c b/bin/sh/options.c new file mode 100644 index 0000000..d2c3e68 --- /dev/null +++ b/bin/sh/options.c @@ -0,0 +1,631 @@ +/* $NetBSD: options.c,v 1.53 2018/07/13 22:43:44 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: options.c,v 1.53 2018/07/13 22:43:44 kre Exp $"); +#endif +#endif /* not lint */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#define DEFINE_OPTIONS +#include "options.h" +#undef DEFINE_OPTIONS +#include "builtins.h" +#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 "syntax.h" +#ifndef SMALL +#include "myhistedit.h" +#endif +#include "show.h" + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *optionarg; /* set by nextopt (like getopt) */ +char *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 int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + size_t i; + int psx; + + argptr = argv; + if (argc > 0) + argptr++; + + psx = posix; /* save what we set it to earlier */ + /* + * option values are mostly boolean 0:off 1:on + * we use 2 (just in this routine) to mean "unknown yet" + */ + for (i = 0; i < NOPTS; i++) + optlist[i].val = 2; + posix = psx; /* restore before processing -o ... */ + + options(1); + + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag == 2 && sflag == 1 && isatty(0) && isatty(2)) + iflag = 1; + if (iflag == 1 && sflag == 2) + iflag = 2; + if (mflag == 2) + mflag = iflag; +#ifndef DO_SHAREDVFORK + if (usefork == 2) + usefork = 1; +#endif +#if DEBUG >= 2 + if (debug == 2) + debug = 1; +#endif + /* + * Any options not dealt with as special cases just above, + * and which were not set on the command line, are set to + * their expected default values (mostly "off") + * + * then as each option is initialised, save its setting now + * as its "default" value for future use ("set -o default"). + */ + for (i = 0; i < NOPTS; i++) { + if (optlist[i].val == 2) + optlist[i].val = optlist[i].dflt; + optlist[i].dflt = optlist[i].val; + } + + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + commandname = argv[0]; + arg0 = *argptr++; + setinputfile(arg0, 0); + commandname = arg0; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (minusc != NULL) { + if (argptr == NULL || *argptr == NULL) + error("Bad -c option"); + minusc = *argptr++; + if (*argptr != 0) + 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(iflag); +#ifndef SMALL + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + */ + +STATIC void +options(int cmdline) +{ + static char empty[] = ""; + char *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) { + if (!cmdline) { + /* "-" means turn off -x and -v */ + if (p[0] == '\0') + xflag = vflag = 0; + /* "--" means reset params */ + else if (*argptr == NULL) + setparam(argptr); + } + break; /* "-" or "--" terminates options */ + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (val == 1 && c == 'c' && cmdline) { + /* command is after shell args*/ + minusc = empty; + } else if (c == 'o') { + if (*p != '\0') + minus_o(p, val + (cmdline ? val : 0)); + else if (*argptr) + minus_o(*argptr++, + val + (cmdline ? val : 0)); + else if (!cmdline) + minus_o(NULL, val); + else + error("arg for %co missing", "+-"[val]); + break; +#ifdef DEBUG + } else if (c == 'D') { + if (*p) { + set_debug(p, val); + break; + } else if (*argptr) + set_debug(*argptr++, val); + else + set_debug("*$", val); +#endif + } else { + setoption(c, val); + } + } + } +} + +static void +set_opt_val(size_t i, int val) +{ + size_t j; + int flag; + + if (val && (flag = optlist[i].opt_set)) { + /* some options (eg vi/emacs) are mutually exclusive */ + for (j = 0; j < NOPTS; j++) + if (optlist[j].opt_set == flag) + optlist[j].val = 0; + } +#ifndef SMALL + if (i == _SH_OPT_Xflag) + xtracefdsetup(val); +#endif + optlist[i].val = val; +#ifdef DEBUG + if (&optlist[i].val == &debug) + opentrace(); /* different "trace" than the -x one... */ +#endif +} + +STATIC void +minus_o(char *name, int val) +{ + size_t i; + const char *sep = ": "; + + if (name == NULL) { + if (val) { + out1str("Current option settings"); + for (i = 0; i < NOPTS; i++) { + if (optlist[i].name == NULL) { + out1fmt("%s%c%c", sep, + "+-"[optlist[i].val], + optlist[i].letter); + sep = ", "; + } + } + out1c('\n'); + for (i = 0; i < NOPTS; i++) { + if (optlist[i].name) + out1fmt("%-19s %s\n", optlist[i].name, + optlist[i].val ? "on" : "off"); + } + } else { + out1str("set -o default"); + for (i = 0; i < NOPTS; i++) { + if (optlist[i].val == optlist[i].dflt) + continue; + if (optlist[i].name) + out1fmt(" %co %s", + "+-"[optlist[i].val], optlist[i].name); + else + out1fmt(" %c%c", "+-"[optlist[i].val], + optlist[i].letter); + } + out1c('\n'); + } + } else { + if (val == 1 && equal(name, "default")) { /* special case */ + for (i = 0; i < NOPTS; i++) + set_opt_val(i, optlist[i].dflt); + return; + } + if (val) + val = 1; + for (i = 0; i < NOPTS; i++) + if (optlist[i].name && equal(name, optlist[i].name)) { + set_opt_val(i, val); +#ifndef SMALL + if (i == _SH_OPT_Xflag) + set_opt_val(_SH_OPT_xflag, val); +#endif + return; + } + error("Illegal option %co %s", "+-"[val], name); + } +} + + +STATIC void +setoption(int flag, int val) +{ + size_t i; + + for (i = 0; i < NOPTS; i++) + if (optlist[i].letter == flag) { + set_opt_val(i, val); +#ifndef SMALL + if (i == _SH_OPT_Xflag) + set_opt_val(_SH_OPT_xflag, val); +#endif + return; + } + error("Illegal option %c%c", "+-"[val], flag); + /* NOTREACHED */ +} + + + +#ifdef mkinit +INCLUDE "options.h" + +SHELLPROC { + int i; + + for (i = 0; optlist[i].name; i++) + optlist[i].val = 0; + optschanged(); + +} +#endif + + +/* + * Set the shell parameters. + */ + +void +setparam(char **argv) +{ + char **newparam; + char **ap; + int nparam; + + for (nparam = 0 ; argv[nparam] ; nparam++) + continue; + ap = newparam = ckmalloc((nparam + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(volatile struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int n; + char **ap1, **ap2; + + if (argc > 2) + error("Usage: shift [n]"); + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + error("can't shift that many"); + INTOFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p ; --n >= 0 ; ap1++) { + if (shellparam.malloc) + ckfree(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL) + continue; + shellparam.optnext = NULL; + INTON; + return 0; +} + + + +/* + * The set command builtin. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvars(0, 0, 1, 0); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + /* + * This is just to detect the case where OPTIND=1 + * is executed. Any other string assigned to OPTIND + * is OK, but is not a reset. No errors, so cannot use number() + */ + if (is_digit(*value) && strtol(value, NULL, 10) == 1) { + shellparam.optnext = NULL; + 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; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + else if (argc == 3) + optbase = shellparam.p; + else + optbase = &argv[3]; + + if (shellparam.reset == 1) { + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +STATIC int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, char **optpptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[12]; + + if ((p = *optpptr) == 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'; + err |= setvarsafe("OPTARG", s, 0); + } else { + outfmt(&errout, "Illegal option -%c\n", c); + (void) unsetvar("OPTARG", 0); + } + c = '?'; + goto bad; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + c = ':'; + } else { + outfmt(&errout, "No arg for -%c option\n", c); + (void) unsetvar("OPTARG", 0); + c = '?'; + } + goto bad; + } + + if (p == **optnext) + (*optnext)++; + err |= setvarsafe("OPTARG", p, 0); + p = NULL; + } else + err |= setvarsafe("OPTARG", "", 0); + ind = *optnext - optfirst + 1; + goto out; + +bad: + ind = 1; + *optnext = NULL; + p = NULL; +out: + *optpptr = p; + 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; + *optpptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * XXX - should get rid of. have all builtins use getopt(3). the + * library getopt must have the BSD extension static variable "optreset" + * otherwise it can't be used within the shell safely. + * + * 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 + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = 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); + optionarg = p; + p = NULL; + } + optptr = p; + return c; +} diff --git a/bin/sh/options.h b/bin/sh/options.h new file mode 100644 index 0000000..4857d18 --- /dev/null +++ b/bin/sh/options.h @@ -0,0 +1,72 @@ +/* $NetBSD: options.h,v 1.27 2017/05/28 00:38:01 kre Exp $ */ + +/*- + * 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. + * + * @(#)options.h 8.2 (Berkeley) 5/4/95 + */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + unsigned char reset; /* if getopts has been reset */ + char **p; /* parameter list */ + char **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + +/* + * Note that option default values can be changed at shell startup + * depending upon the environment in which the shell is running. + */ +struct optent { + const char *name; /* for set -o <name> */ + const char letter; /* set [+/-]<letter> and $- */ + const char opt_set; /* mutually exclusive option set */ + unsigned char val; /* value of <letter>flag */ + unsigned char dflt; /* default value of flag */ +}; + +#include "optinit.h" + +extern char *minusc; /* argument to -c option */ +extern char *arg0; /* $0 */ +extern struct shparam shellparam; /* $@ */ +extern char **argptr; /* argument list for builtin commands */ +extern char *optionarg; /* set by nextopt */ +extern char *optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void setparam(char **); +void freeparam(volatile struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/bin/sh/output.c b/bin/sh/output.c new file mode 100644 index 0000000..99bd913 --- /dev/null +++ b/bin/sh/output.c @@ -0,0 +1,755 @@ +/* $NetBSD: output.c,v 1.40 2017/11/21 03:42:39 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: output.c,v 1.40 2017/11/21 03:42:39 kre Exp $"); +#endif +#endif /* not lint */ + +/* + * Shell output routines. We use our own output routines because: + * When a builtin command is interrupted we have to discard + * any pending output. + * When a builtin command appears in back quotes, we want to + * save the output of the command in a region obtained + * via malloc, rather than doing a fork and reading the + * output of the command via a pipe. + * Our output routines may be smaller than the stdio routines. + */ + +#include <sys/types.h> /* quad_t */ +#include <sys/param.h> /* BSD4_4 */ +#include <sys/ioctl.h> + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "redir.h" +#include "options.h" +#include "show.h" + + +#define OUTBUFSIZ BUFSIZ +#define BLOCK_OUT -2 /* output to a fixed block of memory */ +#define MEM_OUT -3 /* output to dynamically allocated memory */ + +#ifdef SMALL +#define CHAIN +#else +#define CHAIN ,NULL +#endif + + + /* nextc nleft bufsize buf fd flags chain */ +struct output output = {NULL, 0, OUTBUFSIZ, NULL, 1, 0 CHAIN }; +struct output errout = {NULL, 0, 100, NULL, 2, 0 CHAIN }; +struct output memout = {NULL, 0, 0, NULL, MEM_OUT, 0 CHAIN }; +struct output *out1 = &output; +struct output *out2 = &errout; +#ifndef SMALL +struct output *outx = &errout; +struct output *outxtop = NULL; +#endif + + +#ifdef mkinit + +INCLUDE "output.h" +INCLUDE "memalloc.h" + +RESET { + out1 = &output; + out2 = &errout; + if (memout.buf != NULL) { + ckfree(memout.buf); + memout.buf = NULL; + } +} + +#endif + + +#ifdef notdef /* no longer used */ +/* + * Set up an output file to write to memory rather than a file. + */ + +void +open_mem(char *block, int length, struct output *file) +{ + file->nextc = block; + file->nleft = --length; + file->fd = BLOCK_OUT; + file->flags = 0; +} +#endif + + +void +out1str(const char *p) +{ + outstr(p, out1); +} + + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +#ifndef SMALL +void +outxstr(const char *p) +{ + outstr(p, outx); +} +#endif + + +void +outstr(const char *p, struct output *file) +{ + char c = 0; + + while (*p) + outc((c = *p++), file); + if (file == out2 || (file == outx && c == '\n')) + flushout(file); +} + + +void +out2shstr(const char *p) +{ + outshstr(p, out2); +} + +#ifndef SMALL +void +outxshstr(const char *p) +{ + outshstr(p, outx); +} +#endif + +/* + * ' is in this list, not because it does not require quoting + * (which applies to all the others) but because '' quoting cannot + * be used to quote it. + */ +static const char norm_chars [] = \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/+-=_,.'"; + +static int +inquote(const char *p) +{ + size_t l = strspn(p, norm_chars); + char *s = strchr(p, '\''); + + return s == NULL ? p[l] != '\0' : s - p > (off_t)l; +} + +void +outshstr(const char *p, struct output *file) +{ + int need_q; + int inq; + char c; + + if (strchr(p, '\'') != NULL && p[1] != '\0') { + /* + * string contains ' in it, and is not only the ' + * see if " quoting will work + */ + size_t i = strcspn(p, "\\\"$`"); + + if (p[i] == '\0') { + /* + * string contains no $ ` \ or " chars, perfect... + * + * We know it contains ' so needs quoting, so + * this is easy... + */ + outc('"', file); + outstr(p, file); + outc('"', file); + return; + } + } + + need_q = p[0] == 0 || p[strspn(p, norm_chars)] != 0; + + /* + * Don't emit ' unless something needs quoting before closing ' + */ + if (need_q && (p[0] == 0 || inquote(p) != 0)) { + outc('\'', file); + inq = 1; + } else + inq = 0; + + while ((c = *p++) != '\0') { + if (c != '\'') { + outc(c, file); + continue; + } + + if (inq) + outc('\'', file); /* inq = 0, implicit */ + outc('\\', file); + outc(c, file); + if (need_q && *p != '\0') { + if ((inq = inquote(p)) != 0) + outc('\'', file); + } else + inq = 0; + } + + if (inq) + outc('\'', file); + + if (file == out2) + flushout(file); +} + + +char out_junk[16]; + + +void +emptyoutbuf(struct output *dest) +{ + int offset; + + if (dest->fd == BLOCK_OUT) { + dest->nextc = out_junk; + dest->nleft = sizeof out_junk; + dest->flags |= OUTPUT_ERR; + } else if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->nleft = dest->bufsize; + INTON; + VTRACE(DBG_OUTPUT, ("emptyoutbuf now %d @%p for fd %d\n", + dest->nleft, dest->buf, dest->fd)); + } else if (dest->fd == MEM_OUT) { + offset = dest->bufsize; + INTOFF; + dest->bufsize <<= 1; + dest->buf = ckrealloc(dest->buf, dest->bufsize); + dest->nleft = dest->bufsize - offset; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } + dest->nleft--; +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + VTRACE(DBG_OUTPUT, ("flushout fd=%d %zd to write\n", dest->fd, + (size_t)(dest->nextc - dest->buf))); + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; + dest->nleft = dest->bufsize; +} + + +void +freestdout(void) +{ + INTOFF; + if (output.buf) { + ckfree(output.buf); + output.buf = NULL; + output.nleft = 0; + } + INTON; +} + + +void +outfmt(struct output *file, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(file, fmt, ap); + va_end(ap); +} + + +void +out1fmt(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out1, fmt, ap); + va_end(ap); +} + +#ifdef DEBUG +void +debugprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} +#endif + +void +fmtstr(char *outbuf, size_t length, const char *fmt, ...) +{ + va_list ap; + struct output strout; + + va_start(ap, fmt); + strout.nextc = outbuf; + strout.nleft = length; + strout.fd = BLOCK_OUT; + strout.flags = 0; + doformat(&strout, fmt, ap); + outc('\0', &strout); + if (strout.flags & OUTPUT_ERR) + outbuf[length - 1] = '\0'; + va_end(ap); +} + +/* + * Formatted output. This routine handles a subset of the printf formats: + * - Formats supported: d, u, o, p, X, s, and c. + * - The x format is also accepted but is treated like X. + * - The l, ll and q modifiers are accepted. + * - The - and # flags are accepted; # only works with the o format. + * - Width and precision may be specified with any format except c. + * - An * may be given for the width or precision. + * - The obsolete practice of preceding the width with a zero to get + * zero padding is not supported; use the precision field. + * - A % may be printed by writing %% in the format string. + */ + +#define TEMPSIZE 24 + +#ifdef BSD4_4 +#define HAVE_VASPRINTF 1 +#endif + +void +doformat(struct output *dest, const char *f, va_list ap) +{ +#if HAVE_VASPRINTF + char *s; + + vasprintf(&s, f, ap); + if (s == NULL) + error("Could not allocate formatted output buffer"); + outstr(s, dest); + free(s); +#else /* !HAVE_VASPRINTF */ + static const char digit[] = "0123456789ABCDEF"; + char c; + char temp[TEMPSIZE]; + int flushleft; + int sharp; + int width; + int prec; + int islong; + int isquad; + char *p; + int sign; +#ifdef BSD4_4 + quad_t l; + u_quad_t num; +#else + long l; + u_long num; +#endif + unsigned base; + int len; + int size; + int pad; + + while ((c = *f++) != '\0') { + if (c != '%') { + outc(c, dest); + continue; + } + flushleft = 0; + sharp = 0; + width = 0; + prec = -1; + islong = 0; + isquad = 0; + for (;;) { + if (*f == '-') + flushleft++; + else if (*f == '#') + sharp++; + else + break; + f++; + } + if (*f == '*') { + width = va_arg(ap, int); + f++; + } else { + while (is_digit(*f)) { + width = 10 * width + digit_val(*f++); + } + } + if (*f == '.') { + if (*++f == '*') { + prec = va_arg(ap, int); + f++; + } else { + prec = 0; + while (is_digit(*f)) { + prec = 10 * prec + digit_val(*f++); + } + } + } + if (*f == 'l') { + f++; + if (*f == 'l') { + isquad++; + f++; + } else + islong++; + } else if (*f == 'q') { + isquad++; + f++; + } + switch (*f) { + case 'd': +#ifdef BSD4_4 + if (isquad) + l = va_arg(ap, quad_t); + else +#endif + if (islong) + l = va_arg(ap, long); + else + l = va_arg(ap, int); + sign = 0; + num = l; + if (l < 0) { + num = -l; + sign = 1; + } + base = 10; + goto number; + case 'u': + base = 10; + goto uns_number; + case 'o': + base = 8; + goto uns_number; + case 'p': + outc('0', dest); + outc('x', dest); + /*FALLTHROUGH*/ + case 'x': + /* we don't implement 'x'; treat like 'X' */ + case 'X': + base = 16; +uns_number: /* an unsigned number */ + sign = 0; +#ifdef BSD4_4 + if (isquad) + num = va_arg(ap, u_quad_t); + else +#endif + if (islong) + num = va_arg(ap, unsigned long); + else + num = va_arg(ap, unsigned int); +number: /* process a number */ + p = temp + TEMPSIZE - 1; + *p = '\0'; + while (num) { + *--p = digit[num % base]; + num /= base; + } + len = (temp + TEMPSIZE - 1) - p; + if (prec < 0) + prec = 1; + if (sharp && *f == 'o' && prec <= len) + prec = len + 1; + pad = 0; + if (width) { + size = len; + if (size < prec) + size = prec; + size += sign; + pad = width - size; + if (flushleft == 0) { + while (--pad >= 0) + outc(' ', dest); + } + } + if (sign) + outc('-', dest); + prec -= len; + while (--prec >= 0) + outc('0', dest); + while (*p) + outc(*p++, dest); + while (--pad >= 0) + outc(' ', dest); + break; + case 's': + p = va_arg(ap, char *); + pad = 0; + if (width) { + len = strlen(p); + if (prec >= 0 && len > prec) + len = prec; + pad = width - len; + if (flushleft == 0) { + while (--pad >= 0) + outc(' ', dest); + } + } + prec++; + while (--prec != 0 && *p) + outc(*p++, dest); + while (--pad >= 0) + outc(' ', dest); + break; + case 'c': + c = va_arg(ap, int); + outc(c, dest); + break; + default: + outc(*f, dest); + break; + } + f++; + } +#endif /* !HAVE_VASPRINTF */ +} + + + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + while (n > 0) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } + return nbytes; +} + +#ifndef SMALL +static void +xtrace_fd_swap(int from, int to) +{ + struct output *o = outxtop; + + while (o != NULL) { + if (o->fd == from) + o->fd = to; + o = o->chain; + } +} + +/* + * the -X flag is to be set or reset (not necessarily changed) + * Do what is needed to make tracing go to where it should + * + * Note: Xflag has not yet been altered, "on" indicates what it will become + */ + +void +xtracefdsetup(int on) +{ + if (!on) { + flushout(outx); + + if (Xflag != 1) /* Was already +X */ + return; /* so nothing to do */ + + outx = out2; + CTRACE(DBG_OUTPUT, ("Tracing to stderr\n")); + return; + } + + if (Xflag == 1) { /* was already set */ + /* + * This is a change of output file only + * Just close the current one, and reuse the struct output + */ + if (!(outx->flags & OUTPUT_CLONE)) + sh_close(outx->fd); + } else if (outxtop == NULL) { + /* + * -X is just turning on, for the forst time, + * need a new output struct to become outx + */ + xtrace_clone(1); + } else + outx = outxtop; + + if (outx != out2) { + outx->flags &= ~OUTPUT_CLONE; + outx->fd = to_upper_fd(dup(out2->fd)); + register_sh_fd(outx->fd, xtrace_fd_swap); + } + + CTRACE(DBG_OUTPUT, ("Tracing now to fd %d (from %d)\n", + outx->fd, out2->fd)); +} + +void +xtrace_clone(int new) +{ + struct output *o; + + CTRACE(DBG_OUTPUT, + ("xtrace_clone(%d): %sfd=%d buf=%p nleft=%d flags=%x ", + new, (outx == out2 ? "out2: " : ""), + outx->fd, outx->buf, outx->nleft, outx->flags)); + + flushout(outx); + + if (!new && outxtop == NULL && Xflag == 0) { + /* following stderr, nothing to save */ + CTRACE(DBG_OUTPUT, ("+X\n")); + return; + } + + o = ckmalloc(sizeof(*o)); + o->fd = outx->fd; + o->flags = OUTPUT_CLONE; + o->bufsize = outx->bufsize; + o->nleft = 0; + o->buf = NULL; + o->nextc = NULL; + o->chain = outxtop; + outx = o; + outxtop = o; + + CTRACE(DBG_OUTPUT, ("-> fd=%d flags=%x[CLONE]\n", outx->fd, o->flags)); +} + +void +xtrace_pop(void) +{ + struct output *o; + + CTRACE(DBG_OUTPUT, ("trace_pop: fd=%d buf=%p nleft=%d flags=%x ", + outx->fd, outx->buf, outx->nleft, outx->flags)); + + flushout(outx); + + if (outxtop == NULL) { + /* + * No -X has been used, so nothing much to do + */ + CTRACE(DBG_OUTPUT, ("+X\n")); + return; + } + + o = outxtop; + outx = o->chain; + if (outx == NULL) + outx = &errout; + outxtop = o->chain; + if (o != &errout) { + if (o->fd >= 0 && !(o->flags & OUTPUT_CLONE)) + sh_close(o->fd); + if (o->buf) + ckfree(o->buf); + ckfree(o); + } + + CTRACE(DBG_OUTPUT, ("-> fd=%d buf=%p nleft=%d flags=%x\n", + outx->fd, outx->buf, outx->nleft, outx->flags)); +} +#endif /* SMALL */ diff --git a/bin/sh/output.h b/bin/sh/output.h new file mode 100644 index 0000000..a19df43 --- /dev/null +++ b/bin/sh/output.h @@ -0,0 +1,109 @@ +/* $NetBSD: output.h,v 1.27 2017/11/21 03:42:39 kre Exp $ */ + +/*- + * 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. + * + * @(#)output.h 8.2 (Berkeley) 5/4/95 + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> + +struct output { + char *nextc; + int nleft; + int bufsize; + char *buf; + short fd; + short flags; +#ifndef SMALL + struct output *chain; +#endif +}; + +/* flags for ->flags */ +#define OUTPUT_ERR 0x01 /* error occurred on output */ +#define OUTPUT_CLONE 0x02 /* this is a clone of another */ + +extern struct output output; +extern struct output errout; +extern struct output memout; +extern struct output *out1; +extern struct output *out2; +#ifdef SMALL +#define outx out2 +#else +extern struct output *outx; +#endif + +void open_mem(char *, int, struct output *); +void out1str(const char *); +void out2str(const char *); +void outstr(const char *, struct output *); +void out2shstr(const char *); +#ifdef SMALL +#define outxstr out2str +#define outxshstr out2shstr +#else +void outxstr(const char *); +void outxshstr(const char *); +#endif +void outshstr(const char *, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +void outfmt(struct output *, const char *, ...) __printflike(2, 3); +void out1fmt(const char *, ...) __printflike(1, 2); +#ifdef DEBUG +void debugprintf(const char *, ...) __printflike(1, 2); +#endif +void fmtstr(char *, size_t, const char *, ...) __printflike(3, 4); +void doformat(struct output *, const char *, va_list) __printflike(2, 0); +int xwrite(int, char *, int); +#ifdef SMALL +#define xtracefdsetup(x) do { break; } while (0) +#define xtrace_clone(x) do { break; } while (0) +#define xtrace_pop() do { break; } while (0) +#else +void xtracefdsetup(int); +void xtrace_clone(int); +void xtrace_pop(void); +#endif + +#define outc(c, file) (--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1) +#define out2c(c) outc(c, out2) +#define outxc(c) outc(c, outx) + +#define OUTPUT_INCL +#endif diff --git a/bin/sh/parser.c b/bin/sh/parser.c new file mode 100644 index 0000000..6b153aa --- /dev/null +++ b/bin/sh/parser.c @@ -0,0 +1,2756 @@ +/* $NetBSD: parser.c,v 1.164 2019/01/22 14:32:17 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#else +__RCSID("$NetBSD: parser.c,v 1.164 2019/01/22 14:32:17 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#include "eval.h" /* defines commandname */ +#include "syntax.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "show.h" +#ifndef SMALL +#include "myhistedit.h" +#endif +#ifdef DEBUG +#include "nodenames.h" +#endif + +/* + * Shell command parser. + */ + +/* values returned by readtoken */ +#include "token.h" + +#define OPENBRACE '{' +#define CLOSEBRACE '}' + +struct HereDoc { + struct HereDoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ + int startline; /* line number where << seen */ +}; + +MKINIT struct parse_state parse_state; +union parse_state_p psp = { .c_current_parser = &parse_state }; + +static const struct parse_state init_parse_state = { /* all 0's ... */ + .ps_heredoclist = NULL, + .ps_parsebackquote = 0, + .ps_doprompt = 0, + .ps_needprompt = 0, + .ps_lasttoken = 0, + .ps_tokpushback = 0, + .ps_wordtext = NULL, + .ps_checkkwd = 0, + .ps_redirnode = NULL, + .ps_heredoc = NULL, + .ps_quoteflag = 0, + .ps_startlinno = 0, + .ps_funclinno = 0, + .ps_elided_nl = 0, +}; + +STATIC union node *list(int); +STATIC union node *andor(void); +STATIC union node *pipeline(void); +STATIC union node *command(void); +STATIC union node *simplecmd(union node **, union node *); +STATIC union node *makeword(int); +STATIC void parsefname(void); +STATIC int slurp_heredoc(char *const, const int, const int); +STATIC void readheredocs(void); +STATIC int peektoken(void); +STATIC int readtoken(void); +STATIC int xxreadtoken(void); +STATIC int readtoken1(int, char const *, int); +STATIC int noexpand(char *); +STATIC void linebreak(void); +STATIC void consumetoken(int); +STATIC void synexpect(int, const char *) __dead; +STATIC void synerror(const char *) __dead; +STATIC void setprompt(int); +STATIC int pgetc_linecont(void); + +static const char EOFhere[] = "EOF reading here (<<) document"; + +#ifdef DEBUG +int parsing = 0; +#endif + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + union node *n; + +#ifdef DEBUG + parsing++; +#endif + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); +#ifdef DEBUG + parsing--; +#endif + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + +#ifdef DEBUG + parsing++; +#endif + tokpushback++; + n = list(1); +#ifdef DEBUG + parsing--; +#endif + if (heredoclist) + error("%d: Here document (<<%s) expected but not present", + heredoclist->startline, heredoclist->eofmark); + return n; +} + + +STATIC union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + CTRACE(DBG_PARSE, ("list(%d): entered @%d\n",nlflag,plinno)); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (nlflag == 0 && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2->type == NCMD || n2->type == NPIPE) + n2->ncmd.backgnd = 1; + else if (n2->type == NREDIR) + n2->type = NBACKGND; + else { + n3 = stalloc(sizeof(struct nredir)); + n3->type = NBACKGND; + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + } + + if (ntop == NULL) + ntop = n2; + else if (n1 == NULL) { + n1 = stalloc(sizeof(struct nbinary)); + n1->type = NSEMI; + n1->nbinary.ch1 = ntop; + n1->nbinary.ch2 = n2; + ntop = n1; + } else { + n3 = stalloc(sizeof(struct nbinary)); + n3->type = NSEMI; + n3->nbinary.ch1 = n1->nbinary.ch2; + n3->nbinary.ch2 = n2; + n1->nbinary.ch2 = n3; + n1 = n3; + } + + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + readheredocs(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) + return ntop; + else + tokpushback++; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1, 0); + tokpushback++; + return ntop; + } + } +} + +STATIC union node * +andor(void) +{ + union node *n1, *n2, *n3; + int t; + + CTRACE(DBG_PARSE, ("andor: entered @%d\n", plinno)); + + n1 = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n1; + } + n2 = pipeline(); + n3 = stalloc(sizeof(struct nbinary)); + n3->type = t; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } +} + +STATIC union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate; + + CTRACE(DBG_PARSE, ("pipeline: entered @%d\n", plinno)); + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + while (readtoken() == TNOT) { + CTRACE(DBG_PARSE, ("pipeline: TNOT recognized\n")); +#ifndef BOGUS_NOT_COMMAND + if (posix && negate) + synerror("2nd \"!\" unexpected"); +#endif + negate++; + } + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = stalloc(sizeof(struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = stalloc(sizeof(struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = stalloc(sizeof(struct nodelist)); + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + CTRACE(DBG_PARSE, ("%snegate pipeline\n", + (negate&1) ? "" : "double ")); + n2 = stalloc(sizeof(struct nnot)); + n2->type = (negate & 1) ? NNOT : NDNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +STATIC union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + int t; +#ifdef BOGUS_NOT_COMMAND + int negate = 0; +#endif + + CTRACE(DBG_PARSE, ("command: entered @%d\n", plinno)); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + redir = NULL; + n1 = NULL; + rpp = &redir; + + /* Check for redirection which may precede command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + +#ifdef BOGUS_NOT_COMMAND /* only in pileline() */ + while (readtoken() == TNOT) { + CTRACE(DBG_PARSE, ("command: TNOT (bogus) recognized\n")); + negate++; + } + tokpushback++; +#endif + + switch (readtoken()) { + case TIF: + n1 = stalloc(sizeof(struct nif)); + n1->type = NIF; + n1->nif.test = list(0); + consumetoken(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = stalloc(sizeof(struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + n2->nif.test = list(0); + consumetoken(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + consumetoken(TFI); + checkkwd = CHKKWD | CHKALIAS; + break; + case TWHILE: + case TUNTIL: + n1 = stalloc(sizeof(struct nbinary)); + n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; + n1->nbinary.ch1 = list(0); + consumetoken(TDO); + n1->nbinary.ch2 = list(0); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + break; + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = stalloc(sizeof(struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + linebreak(); + if (lasttoken==TWORD && !quoteflag && equal(wordtext,"in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makeword(startlinno); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(TSEMI, 0); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + + n2 = stalloc(sizeof(struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n2->narg.lineno = startlinno; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) == TDO) + t = TDONE; + else if (t == TBEGIN) + t = TEND; + else + synexpect(TDO, 0); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = stalloc(sizeof(struct ncase)); + n1->type = NCASE; + n1->ncase.lineno = startlinno - elided_nl; + consumetoken(TWORD); + n1->ncase.expr = makeword(startlinno); + linebreak(); + if (lasttoken != TWORD || !equal(wordtext, "in")) + synexpect(-1, "in"); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD; + readtoken(); + /* + * Both ksh and bash accept 'case x in esac' + * so configure scripts started taking advantage of this. + * The page: http://pubs.opengroup.org/onlinepubs/\ + * 009695399/utilities/xcu_chap02.html contradicts itself, + * as to if this is legal; the "Case Conditional Format" + * paragraph shows one case is required, but the "Grammar" + * section shows a grammar that explicitly allows the no + * case option. + * + * The standard also says (section 2.10): + * This formal syntax shall take precedence over the + * preceding text syntax description. + * ie: the "Grammar" section wins. The text is just + * a rough guide (introduction to the common case.) + */ + while (lasttoken != TESAC) { + *cpp = cp = stalloc(sizeof(struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + if (lasttoken < TWORD) + synexpect(TWORD, 0); + *app = ap = makeword(startlinno); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + if (lasttoken != TRP) + synexpect(TRP, 0); + cp->nclist.lineno = startlinno; + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t != TENDCASE && t != TCASEFALL) { + synexpect(TENDCASE, 0); + } else { + if (t == TCASEFALL) + cp->type = NCLISTCONT; + checkkwd = CHKNL | CHKKWD; + readtoken(); + } + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = stalloc(sizeof(struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + if (n1->nredir.n == NULL) + synexpect(-1, 0); + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + break; + case TBEGIN: + n1 = list(0); + if (posix && n1 == NULL) + synexpect(-1, 0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TNL: + case TEOF: + case TRP: + case TENDCASE: + case TCASEFALL: + /* + * simple commands must have something in them, + * either a word (which at this point includes a=b) + * or a redirection. If we reached the end of the + * command (which one of these tokens indicates) + * when we are just starting, and have not had a + * redirect, then ... + * + * nb: it is still possible to end up with empty + * simple commands, if the "command" is a var + * expansion that produces nothing: + * X= ; $X && $X + * --> && + * That is OK and is handled after word expansions. + */ + if (!redir) + synexpect(-1, 0); + /* + * continue to build a node containing the redirect. + * the tokpushback means that our ending token will be + * read again in simplecmd, causing it to terminate, + * so only the redirect(s) will be contained in the + * returned n1 + */ + /* FALLTHROUGH */ + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + goto checkneg; + default: + synexpect(-1, 0); + /* NOTREACHED */ + } + + /* Now check for redirection which may follow command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (n1 == NULL || n1->type != NSUBSHELL) { + n2 = stalloc(sizeof(struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + checkneg: +#ifdef BOGUS_NOT_COMMAND + if (negate) { + VTRACE(DBG_PARSE, ("bogus %snegate command\n", + (negate&1) ? "" : "double ")); + n2 = stalloc(sizeof(struct nnot)); + n2->type = (negate & 1) ? NNOT : NDNOT; + n2->nnot.com = n1; + return n2; + } + else +#endif + return n1; +} + + +STATIC union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node *n = NULL; + int line = 0; + int savecheckkwd; +#ifdef BOGUS_NOT_COMMAND + union node *n2; + int negate = 0; +#endif + + CTRACE(DBG_PARSE, ("simple command with%s redir already @%d\n", + redir ? "" : "out", plinno)); + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == 0) + rpp = &redir; + + args = NULL; + app = &args; + +#ifdef BOGUS_NOT_COMMAND /* pipelines get negated, commands do not */ + while (readtoken() == TNOT) { + VTRACE(DBG_PARSE, ("simplcmd: bogus TNOT recognized\n")); + negate++; + } + tokpushback++; +#endif + + savecheckkwd = CHKALIAS; + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + if (line == 0) + line = startlinno; + n = makeword(startlinno); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + if (line == 0) + line = startlinno; + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && redir == 0) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + rmescapes(n->narg.text); + if (strchr(n->narg.text, '/')) + synerror("Bad function name"); + VTRACE(DBG_PARSE, ("Function '%s' seen @%d\n", + n->narg.text, plinno)); + n->type = NDEFUN; + n->narg.lineno = plinno - elided_nl; + n->narg.next = command(); + funclinno = 0; + goto checkneg; + } else { + tokpushback++; + break; + } + } + + if (args == NULL && redir == NULL) + synexpect(-1, 0); + *app = NULL; + *rpp = NULL; + n = stalloc(sizeof(struct ncmd)); + n->type = NCMD; + n->ncmd.lineno = line - elided_nl; + n->ncmd.backgnd = 0; + n->ncmd.args = args; + n->ncmd.redirect = redir; + n->ncmd.lineno = startlinno; + + checkneg: +#ifdef BOGUS_NOT_COMMAND + if (negate) { + VTRACE(DBG_PARSE, ("bogus %snegate simplecmd\n", + (negate&1) ? "" : "double ")); + n2 = stalloc(sizeof(struct nnot)); + n2->type = (negate & 1) ? NNOT : NDNOT; + n2->nnot.com = n; + return n2; + } + else +#endif + return n; +} + +STATIC union node * +makeword(int lno) +{ + union node *n; + + n = stalloc(sizeof(struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + n->narg.lineno = lno; + return n; +} + +void +fixredir(union node *n, const char *text, int err) +{ + + VTRACE(DBG_PARSE, ("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_number(text)) + n->ndup.dupfd = number(text); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makeword(startlinno - elided_nl); + } +} + + +STATIC void +parsefname(void) +{ + union node *n = redirnode; + + if (readtoken() != TWORD) + synexpect(-1, 0); + if (n->type == NHERE) { + struct HereDoc *here = heredoc; + struct HereDoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + VTRACE(DBG_PARSE, ("Here document %d @%d\n", n->type, plinno)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + + /* + * this test is not really necessary, we are not + * required to expand wordtext, but there's no reason + * it cannot be $$ or something like that - that would + * not mean the pid, but literally two '$' characters. + * There is no need for limits on what the word can be. + * However, it needs to stay literal as entered, not + * have $ converted to CTLVAR or something, which as + * the parser is, at the minute, is impossible to prevent. + * So, leave it like this until the rest of the parser is fixed. + */ + if (!noexpand(wordtext)) + synerror("Illegal eof marker for << redirection"); + + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next) + continue; + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makeword(startlinno - elided_nl); + } +} + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + * The new value of c is returned. + */ + +static int +checkend(int c, char * const eofmark, const int striptabs) +{ + + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == PEOF) { + if (*eofmark == '\0') + return (c); + synerror(EOFhere); + } + if (c == *eofmark) { + int c2; + char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + if ((c2 == PEOF || c2 == '\n') && *q == '\0') { + c = PEOF; + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + } else { + pungetc(); + pushstring(eofmark + 1, q - (eofmark + 1), NULL); + } + } else if (c == '\n' && *eofmark == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } + return (c); +} + + +/* + * Input any here documents. + */ + +STATIC int +slurp_heredoc(char *const eofmark, const int striptabs, const int sq) +{ + int c; + char *out; + int lines = plinno; + + c = pgetc(); + + /* + * If we hit EOF on the input, and the eofmark is a null string ('') + * we consider this empty line to be the eofmark, and exit without err. + */ + if (c == PEOF && *eofmark != '\0') + synerror(EOFhere); + + STARTSTACKSTR(out); + + while ((c = checkend(c, eofmark, striptabs)) != PEOF) { + do { + if (sq) { + /* + * in single quoted mode (eofmark quoted) + * all we look for is \n so we can check + * for the epfmark - everything saved literally. + */ + STPUTC(c, out); + if (c == '\n') { + plinno++; + break; + } + continue; + } + /* + * In double quoted (non-quoted eofmark) + * we must handle \ followed by \n here + * otherwise we can mismatch the end mark. + * All other uses of \ will be handled later + * when the here doc is expanded. + * + * This also makes sure \\ followed by \n does + * not suppress the newline (the \ quotes itself) + */ + if (c == '\\') { /* A backslash */ + STPUTC(c, out); + c = pgetc(); /* followed by */ + if (c == '\n') { /* a newline? */ + STPUTC(c, out); + plinno++; + continue; /* don't break */ + } + } + STPUTC(c, out); /* keep the char */ + if (c == '\n') { /* at end of line */ + plinno++; + break; /* look for eofmark */ + } + } while ((c = pgetc()) != PEOF); + + /* + * If we have read a line, and reached EOF, without + * finding the eofmark, whether the EOF comes before + * or immediately after the \n, that is an error. + */ + if (c == PEOF || (c = pgetc()) == PEOF) + synerror(EOFhere); + } + STPUTC('\0', out); + + c = out - stackblock(); + out = stackblock(); + grabstackblock(c); + wordtext = out; + + VTRACE(DBG_PARSE, + ("Slurped a %d line %sheredoc (to '%s')%s: len %d, \"%.*s%s\" @%d\n", + plinno - lines, sq ? "quoted " : "", eofmark, + striptabs ? " tab stripped" : "", c, (c > 16 ? 16 : c), + wordtext, (c > 16 ? "..." : ""), plinno)); + + return (plinno - lines); +} + +static char * +insert_elided_nl(char *str) +{ + while (elided_nl > 0) { + STPUTC(CTLNONL, str); + elided_nl--; + } + return str; +} + +STATIC void +readheredocs(void) +{ + struct HereDoc *here; + union node *n; + int line, l; + + line = 0; /*XXX - gcc! obviously unneeded */ + if (heredoclist) + line = heredoclist->startline + 1; + l = 0; + while (heredoclist) { + line += l; + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + + l = slurp_heredoc(here->eofmark, here->striptabs, + here->here->nhere.type == NHERE); + + here->here->nhere.doc = n = makeword(line); + + if (here->here->nhere.type == NHERE) + continue; + + /* + * Now "parse" here docs that have unquoted eofmarkers. + */ + setinputstring(wordtext, 1, line); + VTRACE(DBG_PARSE, ("Reprocessing %d line here doc from %d\n", + l, line)); + readtoken1(pgetc(), DQSYNTAX, 1); + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + popfile(); + } +} + +STATIC int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +STATIC int +readtoken(void) +{ + int t; +#ifdef DEBUG + int alreadyseen = tokpushback; + int savecheckkwd = checkkwd; +#endif + struct alias *ap; + + top: + t = xxreadtoken(); + + if (checkkwd & CHKNL) { + while (t == TNL) { + readheredocs(); + t = xxreadtoken(); + } + } + + /* + * check for keywords and aliases + */ + if (t == TWORD && !quoteflag) { + const char *const *pp; + + if (checkkwd & CHKKWD) + for (pp = parsekwd; *pp; pp++) { + if (**pp == *wordtext && equal(*pp, wordtext)) { + lasttoken = t = pp - + parsekwd + KWDOFFSET; + VTRACE(DBG_PARSE, + ("keyword %s recognized @%d\n", + tokname[t], plinno)); + goto out; + } + } + + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + VTRACE(DBG_PARSE, + ("alias '%s' recognized -> <:%s:>\n", + wordtext, ap->val)); + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } + out: + if (t != TNOT) + checkkwd = 0; + + VTRACE(DBG_PARSE, ("%stoken %s %s @%d (chkkwd %x->%x)\n", + alreadyseen ? "reread " : "", tokname[t], + t == TWORD ? wordtext : "", plinno, savecheckkwd, checkkwd)); + return (t); +} + + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ + +#define RETURN(token) return lasttoken = (token) + +STATIC int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + CTRACE(DBG_LEXER, + ("xxreadtoken() returns %s (%d) again\n", + tokname[lasttoken], lasttoken)); + return lasttoken; + } + if (needprompt) { + setprompt(2); + needprompt = 0; + } + elided_nl = 0; + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + CTRACE(DBG_LEXER, ("xxreadtoken() sees '%c' (%#.2x) ", + c&0xFF, c&0x1FF)); + switch (c) { + case ' ': case '\t': case PFAKE: + CTRACE(DBG_LEXER, (" ignored\n")); + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF) + continue; + CTRACE(DBG_LEXER, + ("skipped comment to (not incl) \\n\n")); + pungetc(); + continue; + + case '\n': + plinno++; + CTRACE(DBG_LEXER, ("newline now @%d\n", plinno)); + needprompt = doprompt; + RETURN(TNL); + case PEOF: + CTRACE(DBG_LEXER, ("EOF -> TEOF (return)\n")); + RETURN(TEOF); + + case '&': + if (pgetc_linecont() == '&') { + CTRACE(DBG_LEXER, + ("and another -> TAND (return)\n")); + RETURN(TAND); + } + pungetc(); + CTRACE(DBG_LEXER, (" -> TBACKGND (return)\n")); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') { + CTRACE(DBG_LEXER, + ("and another -> TOR (return)\n")); + RETURN(TOR); + } + pungetc(); + CTRACE(DBG_LEXER, (" -> TPIPE (return)\n")); + RETURN(TPIPE); + case ';': + switch (pgetc_linecont()) { + case ';': + CTRACE(DBG_LEXER, + ("and another -> TENDCASE (return)\n")); + RETURN(TENDCASE); + case '&': + CTRACE(DBG_LEXER, + ("and '&' -> TCASEFALL (return)\n")); + RETURN(TCASEFALL); + default: + pungetc(); + CTRACE(DBG_LEXER, (" -> TSEMI (return)\n")); + RETURN(TSEMI); + } + case '(': + CTRACE(DBG_LEXER, (" -> TLP (return)\n")); + RETURN(TLP); + case ')': + CTRACE(DBG_LEXER, (" -> TRP (return)\n")); + RETURN(TRP); + + case '\\': + switch (pgetc()) { + case '\n': + startlinno = ++plinno; + CTRACE(DBG_LEXER, ("\\\n ignored, now @%d\n", + plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + case PEOF: + CTRACE(DBG_LEXER, + ("then EOF -> TEOF (return) '\\' dropped\n")); + RETURN(TEOF); + default: + CTRACE(DBG_LEXER, ("not \\\n or EOF: ")); + pungetc(); + break; + } + /* FALLTHROUGH */ + default: + CTRACE(DBG_LEXER, ("getting a word\n")); + return readtoken1(c, BASESYNTAX, 0); + } + } +#undef RETURN +} + + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +/* + * We used to remember only the current syntax, variable nesting level, + * double quote state for each var nesting level, and arith nesting + * level (unrelated to var nesting) and one prev syntax when in arith + * syntax. This worked for simple cases, but can't handle arith inside + * var expansion inside arith inside var with some quoted and some not. + * + * Inspired by FreeBSD's implementation (though it was the obvious way) + * though implemented differently, we now have a stack that keeps track + * of what we are doing now, and what we were doing previously. + * Every time something changes, which will eventually end and should + * revert to the previous state, we push this stack, and then pop it + * again later (that is every ${} with an operator (to parse the word + * or pattern that follows) ${x} and $x are too simple to need it) + * $(( )) $( ) and "...". Always. Really, always! + * + * The stack is implemented as one static (on the C stack) base block + * containing LEVELS_PER_BLOCK (8) stack entries, which should be + * enough for the vast majority of cases. For torture tests, we + * malloc more blocks as needed. All accesses through the inline + * functions below. + */ + +/* + * varnest & arinest will typically be 0 or 1 + * (varnest can increment in usages like ${x=${y}} but probably + * does not really need to) + * parenlevel allows balancing parens inside a $(( )), it is reset + * at each new nesting level ( $(( ( x + 3 ${unset-)} )) does not work. + * quoted is special - we need to know 2 things ... are we inside "..." + * (even if inherited from some previous nesting level) and was there + * an opening '"' at this level (so the next will be closing). + * "..." can span nesting levels, but cannot be opened in one and + * closed in a different one. + * To handle this, "quoted" has two fields, the bottom 4 (really 2) + * bits are 0, 1, or 2, for un, single, and double quoted (single quoted + * is really so special that this setting is not very important) + * and 0x10 that indicates that an opening quote has been seen. + * The bottom 4 bits are inherited, the 0x10 bit is not. + */ +struct tokenstate { + const char *ts_syntax; + unsigned short ts_parenlevel; /* counters */ + unsigned short ts_varnest; /* 64000 levels should be enough! */ + unsigned short ts_arinest; + unsigned short ts_quoted; /* 1 -> single, 2 -> double */ + unsigned short ts_magicq; /* heredoc or word expand */ +}; + +#define NQ 0x00 /* Unquoted */ +#define SQ 0x01 /* Single Quotes */ +#define DQ 0x02 /* Double Quotes (or equivalent) */ +#define CQ 0x03 /* C style Single Quotes */ +#define QF 0x0F /* Mask to extract previous values */ +#define QS 0x10 /* Quoting started at this level in stack */ + +#define LEVELS_PER_BLOCK 8 +#define VSS struct statestack + +struct statestack { + VSS *prev; /* previous block in list */ + int cur; /* which of our tokenstates is current */ + struct tokenstate tokenstate[LEVELS_PER_BLOCK]; +}; + +static inline struct tokenstate * +currentstate(VSS *stack) +{ + return &stack->tokenstate[stack->cur]; +} + +#ifdef notdef +static inline struct tokenstate * +prevstate(VSS *stack) +{ + if (stack->cur != 0) + return &stack->tokenstate[stack->cur - 1]; + if (stack->prev == NULL) /* cannot drop below base */ + return &stack->tokenstate[0]; + return &stack->prev->tokenstate[LEVELS_PER_BLOCK - 1]; +} +#endif + +static inline VSS * +bump_state_level(VSS *stack) +{ + struct tokenstate *os, *ts; + + os = currentstate(stack); + + if (++stack->cur >= LEVELS_PER_BLOCK) { + VSS *ss; + + ss = (VSS *)ckmalloc(sizeof (struct statestack)); + ss->cur = 0; + ss->prev = stack; + stack = ss; + } + + ts = currentstate(stack); + + ts->ts_parenlevel = 0; /* parens inside never match outside */ + + ts->ts_quoted = os->ts_quoted & QF; /* these are default settings */ + ts->ts_varnest = os->ts_varnest; + ts->ts_arinest = os->ts_arinest; /* when appropriate */ + ts->ts_syntax = os->ts_syntax; /* they will be altered */ + ts->ts_magicq = os->ts_magicq; + + return stack; +} + +static inline VSS * +drop_state_level(VSS *stack) +{ + if (stack->cur == 0) { + VSS *ss; + + ss = stack; + stack = ss->prev; + if (stack == NULL) + return ss; + ckfree(ss); + } + --stack->cur; + return stack; +} + +static inline void +cleanup_state_stack(VSS *stack) +{ + while (stack->prev != NULL) { + stack->cur = 0; + stack = drop_state_level(stack); + } +} + +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +/* + * The following macros all assume the existance of a local var "stack" + * which contains a pointer to the current struct stackstate + */ + +/* + * These are macros rather than inline funcs to avoid code churn as much + * as possible - they replace macros of the same name used previously. + */ +#define ISDBLQUOTE() (currentstate(stack)->ts_quoted & QS) +#define SETDBLQUOTE() (currentstate(stack)->ts_quoted = QS | DQ) +#ifdef notdef +#define CLRDBLQUOTE() (currentstate(stack)->ts_quoted = \ + stack->cur != 0 || stack->prev ? \ + prevstate(stack)->ts_quoted & QF : 0) +#endif + +/* + * This set are just to avoid excess typing and line lengths... + * The ones that "look like" var names must be implemented to be lvalues + */ +#define syntax (currentstate(stack)->ts_syntax) +#define parenlevel (currentstate(stack)->ts_parenlevel) +#define varnest (currentstate(stack)->ts_varnest) +#define arinest (currentstate(stack)->ts_arinest) +#define quoted (currentstate(stack)->ts_quoted) +#define magicq (currentstate(stack)->ts_magicq) +#define TS_PUSH() (stack = bump_state_level(stack)) +#define TS_POP() (stack = drop_state_level(stack)) + +/* + * Called to parse command substitutions. oldstyle is true if the command + * is enclosed inside `` (otherwise it was enclosed in "$( )") + * + * Internally nlpp is a pointer to the head of the linked + * list of commands (passed by reference), and savelen is the number of + * characters on the top of the stack which must be preserved. + */ +static char * +parsebackq(VSS *const stack, char * const in, + struct nodelist **const pbqlist, const int oldstyle) +{ + struct nodelist **nlpp; + const int savepbq = parsebackquote; + union node *n; + char *out; + char *str = NULL; + char *volatile sstr = str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + struct parsefile *const savetopfile = getcurrentfile(); + const int savelen = in - stackblock(); + int saveprompt; + int lno; + + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (sstr) + ckfree(__UNVOLATILE(sstr)); + cleanup_state_stack(stack); + parsebackquote = 0; + handler = savehandler; + CTRACE(DBG_LEXER, ("parsebackq() err (%d), unwinding\n", + exception)); + longjmp(handler->loc, 1); + } + INTOFF; + sstr = str = NULL; + if (savelen > 0) { + sstr = str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + INTON; + if (oldstyle) { + /* + * We must read until the closing backquote, giving special + * treatment to some slashes, and then push the string and + * reread it as input, interpreting it normally. + */ + int pc; + int psavelen; + char *pstr; + int line1 = plinno; + + VTRACE(DBG_PARSE|DBG_LEXER, + ("parsebackq: repackaging `` as $( )")); + /* + * Because the entire `...` is read here, we don't + * need to bother the state stack. That will be used + * (as appropriate) when the processed string is re-read. + */ + STARTSTACKSTR(out); +#ifdef DEBUG + for (psavelen = 0;;psavelen++) { /* } */ +#else + for (;;) { +#endif + if (needprompt) { + setprompt(2); + needprompt = 0; + } + pc = pgetc(); + VTRACE(DBG_LEXER, + ("parsebackq() got '%c'(%#.2x) in `` %s", pc&0xFF, + pc&0x1FF, pc == '`' ? "terminator\n" : "")); + if (pc == '`') + break; + switch (pc) { + case '\\': + pc = pgetc(); + VTRACE(DBG_LEXER, ("then '%c'(%#.2x) ", + pc&0xFF, pc&0x1FF)); +#ifdef DEBUG + psavelen++; +#endif + if (pc == '\n') { /* keep \ \n for later */ + plinno++; + VTRACE(DBG_LEXER, ("@%d ", plinno)); + needprompt = doprompt; + } + if (pc != '\\' && pc != '`' && pc != '$' + && (!ISDBLQUOTE() || pc != '"')) { + VTRACE(DBG_LEXER, ("keep '\\' ")); + STPUTC('\\', out); + } + break; + + case '\n': + plinno++; + VTRACE(DBG_LEXER, ("@%d ", plinno)); + needprompt = doprompt; + break; + + case PEOF: + startlinno = line1; + VTRACE(DBG_LEXER, ("EOF\n", plinno)); + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + VTRACE(DBG_LEXER, (".\n", plinno)); + STPUTC(pc, out); + } + STPUTC('\0', out); + VTRACE(DBG_LEXER, ("parsebackq() ``:")); + VTRACE(DBG_PARSE|DBG_LEXER, (" read %d", psavelen)); + psavelen = out - stackblock(); + VTRACE(DBG_PARSE|DBG_LEXER, (" produced %d\n", psavelen)); + if (psavelen > 0) { + pstr = grabstackstr(out); + CTRACE(DBG_LEXER, + ("parsebackq() reprocessing as $(%s)\n", pstr)); + setinputstring(pstr, 1, line1); + } + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = stalloc(sizeof(struct nodelist)); + (*nlpp)->next = NULL; + parsebackquote = oldstyle; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } else + saveprompt = 0; + + lno = -plinno; + CTRACE(DBG_LEXER, ("parsebackq() parsing embedded command list\n")); + n = list(0); + CTRACE(DBG_LEXER, ("parsebackq() parsed $() (%d -> %d)\n", -lno, + lno + plinno)); + lno += plinno; + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1, 0); + doprompt = saveprompt; + } else + consumetoken(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + CTRACE(DBG_LEXER, ("parsebackq() back to previous input\n")); + popfile(); + tokpushback = 0; + } + + while (stackblocksize() <= savelen) + growstackblock(); + STARTSTACKSTR(out); + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + INTOFF; + ckfree(str); + sstr = str = NULL; + INTON; + } + parsebackquote = savepbq; + handler = savehandler; + if (arinest || ISDBLQUOTE()) { + STPUTC(CTLBACKQ | CTLQUOTE, out); + while (--lno >= 0) + STPUTC(CTLNONL, out); + } else + STPUTC(CTLBACKQ, out); + + return out; +} + +/* + * Parse a redirection operator. The parameter "out" points to a string + * specifying the fd to be redirected. It is guaranteed to be either "" + * or a numeric string (for now anyway). The parameter "c" contains the + * first character of the redirection operator. + * + * Note the string "out" is on the stack, which we are about to clobber, + * so process it first... + */ + +static void +parseredir(const char *out, int c) +{ + union node *np; + int fd; + + fd = (*out == '\0') ? -1 : number(out); + + np = stalloc(sizeof(struct nfile)); + VTRACE(DBG_LEXER, ("parseredir after '%s%c' ", out, c)); + if (c == '>') { + if (fd < 0) + fd = 1; + c = pgetc_linecont(); + VTRACE(DBG_LEXER, ("is '%c'(%#.2x) ", c&0xFF, c&0x1FF)); + if (c == '>') + np->type = NAPPEND; + else if (c == '|') + np->type = NCLOBBER; + else if (c == '&') + np->type = NTOFD; + else { + np->type = NTO; + VTRACE(DBG_LEXER, ("unwanted ", c)); + pungetc(); + } + } else { /* c == '<' */ + if (fd < 0) + fd = 0; + c = pgetc_linecont(); + VTRACE(DBG_LEXER, ("is '%c'(%#.2x) ", c&0xFF, c&0x1FF)); + switch (c) { + case '<': + /* if sizes differ, just discard the old one */ + if (sizeof (struct nfile) != sizeof (struct nhere)) + np = stalloc(sizeof(struct nhere)); + np->type = NHERE; + np->nhere.fd = 0; + heredoc = stalloc(sizeof(struct HereDoc)); + heredoc->here = np; + heredoc->startline = plinno; + if ((c = pgetc_linecont()) == '-') { + CTRACE(DBG_LEXER, ("and '%c'(%#.2x) ", + c & 0xFF, c & 0x1FF)); + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + break; + + case '&': + np->type = NFROMFD; + break; + + case '>': + np->type = NFROMTO; + break; + + default: + np->type = NFROM; + VTRACE(DBG_LEXER, ("unwanted('%c'0#.2x)", c&0xFF, + c&0x1FF)); + pungetc(); + break; + } + } + np->nfile.fd = fd; + + VTRACE(DBG_LEXER, (" ->%"PRIdsNT" fd=%d\n", NODETYPENAME(np->type),fd)); + + redirnode = np; /* this is the "value" of TRENODE */ +} + +/* + * Called to parse a backslash escape sequence inside $'...'. + * The backslash has already been read. + */ +static char * +readcstyleesc(char *out) +{ + int c, vc, i, n; + unsigned int v; + + c = pgetc(); + VTRACE(DBG_LEXER, ("CSTR(\\%c)(\\%#x)", c&0xFF, c&0x1FF)); + switch (c) { + case '\0': + case PEOF: + synerror("Unterminated quoted string"); + case '\n': + plinno++; + VTRACE(DBG_LEXER, ("@%d ", plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + return out; + + case '\\': + case '\'': + case '"': + v = c; + break; + + case 'a': v = '\a'; break; + case 'b': v = '\b'; break; + case 'e': v = '\033'; break; + case 'f': v = '\f'; break; + case 'n': v = '\n'; break; + case 'r': v = '\r'; break; + case 't': v = '\t'; break; + case 'v': v = '\v'; break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + v = c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + } else + pungetc(); + } else + pungetc(); + break; + + case 'c': + c = pgetc(); + if (c < 0x3f || c > 0x7a || c == 0x60) + synerror("Bad \\c escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad \\c\\ escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + + case 'x': + n = 2; + goto hexval; + case 'u': + n = 4; + goto hexval; + case 'U': + n = 8; + hexval: + v = 0; + for (i = 0; i < n; i++) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else { + pungetc(); + break; + } + } + if (n > 2 && v > 127) { + if (v >= 0xd800 && v <= 0xdfff) + synerror("Invalid \\u escape sequence"); + + /* XXX should we use iconv here. What locale? */ + CHECKSTRSPACE(4, out); + + if (v <= 0x7ff) { + USTPUTC(0xc0 | v >> 6, out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0xffff) { + USTPUTC(0xe0 | v >> 12, out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0x10ffff) { + USTPUTC(0xf0 | v >> 18, out); + USTPUTC(0x80 | ((v >> 12) & 0x3f), out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } + if (v > 127) + v = '?'; + } + break; + default: + synerror("Unknown $'' escape sequence"); + } + vc = (char)v; + VTRACE(DBG_LEXER, ("->%u(%#x)['%c']", v, v, vc&0xFF)); + + /* + * If we managed to create a \n from a \ sequence (no matter how) + * then we replace it with the magic CRTCNL control char, which + * will turn into a \n again later, but in the meantime, never + * causes LINENO increments. + */ + if (vc == '\n') { + VTRACE(DBG_LEXER, ("CTLCNL.")); + USTPUTC(CTLCNL, out); + return out; + } + + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (vc == '\0') { + CTRACE(DBG_LEXER, ("\\0: skip to '", v, v, vc&0xFF)); + while ((c = pgetc()) != '\'') { + if (c == '\\') + c = pgetc(); + if (c == PEOF) + synerror("Unterminated quoted string"); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } + } + pungetc(); + return out; + } + CVTRACE(DBG_LEXER, NEEDESC(vc), ("CTLESC-")); + VTRACE(DBG_LEXER, ("'%c'(%#.2x)", vc&0xFF, vc&0x1FF)); + if (NEEDESC(vc)) + USTPUTC(CTLESC, out); + USTPUTC(vc, out); + return out; +} + +/* + * The lowest level basic tokenizer. + * + * The next input byte (character) is in firstc, syn says which + * syntax tables we are to use (basic, single or double quoted, or arith) + * and magicq (used with sqsyntax and dqsyntax only) indicates that the + * quote character itself is not special (used parsing here docs and similar) + * + * The result is the type of the next token (its value, when there is one, + * is saved in the relevant global var - must fix that someday!) which is + * also saved for re-reading ("lasttoken"). + * + * Overall, this routine does far more parsing than it is supposed to. + * That will also need fixing, someday... + */ +STATIC int +readtoken1(int firstc, char const *syn, int oneword) +{ + int c; + char * out; + int len; + struct nodelist *bqlist; + int quotef; + VSS static_stack; + VSS *stack = &static_stack; + + stack->prev = NULL; + stack->cur = 0; + + syntax = syn; + +#ifdef DEBUG +#define SYNTAX ( syntax == BASESYNTAX ? "BASE" : \ + syntax == DQSYNTAX ? "DQ" : \ + syntax == SQSYNTAX ? "SQ" : \ + syntax == ARISYNTAX ? "ARI" : \ + "???" ) +#endif + + startlinno = plinno; + varnest = 0; + quoted = 0; + if (syntax == DQSYNTAX) + SETDBLQUOTE(); + quotef = 0; + bqlist = NULL; + arinest = 0; + parenlevel = 0; + elided_nl = 0; + magicq = oneword; + + CTRACE(DBG_LEXER, ("readtoken1(%c) syntax=%s %s%s(quoted=%x)\n", + firstc&0xFF, SYNTAX, magicq ? "magic quotes" : "", + ISDBLQUOTE()?" ISDBLQUOTE":"", quoted)); + + STARTSTACKSTR(out); + + for (c = firstc ;; c = pgetc_macro()) { /* until of token */ + if (syntax == ARISYNTAX) + out = insert_elided_nl(out); + CHECKSTRSPACE(6, out); /* permit 6 calls to USTPUTC */ + switch (syntax[c]) { + case CFAKE: + VTRACE(DBG_LEXER, ("CFAKE")); + if (syntax == BASESYNTAX && varnest == 0) + break; + VTRACE(DBG_LEXER, (",")); + continue; + case CNL: /* '\n' */ + VTRACE(DBG_LEXER, ("CNL")); + if (syntax == BASESYNTAX && varnest == 0) + break; /* exit loop */ + USTPUTC(c, out); + plinno++; + VTRACE(DBG_LEXER, ("@%d,", plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + + case CSBACK: /* single quoted backslash */ + if ((quoted & QF) == CQ) { + out = readcstyleesc(out); + continue; + } + VTRACE(DBG_LEXER, ("ESC:")); + USTPUTC(CTLESC, out); + /* FALLTHROUGH */ + case CWORD: + VTRACE(DBG_LEXER, ("'%c'", c)); + USTPUTC(c, out); + continue; + + case CCTL: + CVTRACE(DBG_LEXER, !magicq || ISDBLQUOTE(), + ("%s%sESC:",!magicq?"!m":"",ISDBLQUOTE()?"DQ":"")); + if (!magicq || ISDBLQUOTE()) + USTPUTC(CTLESC, out); + VTRACE(DBG_LEXER, ("'%c'", c)); + USTPUTC(c, out); + continue; + case CBACK: /* backslash */ + c = pgetc(); + VTRACE(DBG_LEXER, ("\\'%c'(%#.2x)", c&0xFF, c&0x1FF)); + if (c == PEOF) { + VTRACE(DBG_LEXER, ("EOF, keep \\ ")); + USTPUTC('\\', out); + pungetc(); + continue; + } + if (c == '\n') { + plinno++; + elided_nl++; + VTRACE(DBG_LEXER, ("eli \\n (%d) @%d ", + elided_nl, plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + CVTRACE(DBG_LEXER, quotef==0, (" QF=1 ")); + quotef = 1; /* current token is quoted */ + if (quoted && c != '\\' && c != '`' && + c != '$' && (c != '"' || magicq)) { + /* + * retain the \ (which we *know* needs CTLESC) + * when in "..." and the following char is + * not one of the magic few.) + * Otherwise the \ has done its work, and + * is dropped. + */ + VTRACE(DBG_LEXER, ("ESC:'\\'")); + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } + CVTRACE(DBG_LEXER, NEEDESC(c) || !magicq, + ("%sESC:", NEEDESC(c) ? "+" : "m")); + VTRACE(DBG_LEXER, ("'%c'(%#.2x)", c&0xFF, c&0x1FF)); + if (NEEDESC(c)) + USTPUTC(CTLESC, out); + else if (!magicq) { + USTPUTC(CTLESC, out); + USTPUTC(c, out); + continue; + } + USTPUTC(c, out); + continue; + case CSQUOTE: + if (syntax != SQSYNTAX) { + CVTRACE(DBG_LEXER, !magicq, (" CQM ")); + if (!magicq) + USTPUTC(CTLQUOTEMARK, out); + CVTRACE(DBG_LEXER, quotef==0, (" QF=1 ")); + quotef = 1; + TS_PUSH(); + syntax = SQSYNTAX; + quoted = SQ; + VTRACE(DBG_LEXER, (" TS_PUSH(SQ)")); + continue; + } + if (magicq && arinest == 0 && varnest == 0) { + /* Ignore inside quoted here document */ + VTRACE(DBG_LEXER, ("<<'>>")); + USTPUTC(c, out); + continue; + } + /* End of single quotes... */ + TS_POP(); + VTRACE(DBG_LEXER, ("SQ TS_POP->%s ", SYNTAX)); + CVTRACE(DBG_LEXER, syntax == BASESYNTAX, (" CQE ")); + if (syntax == BASESYNTAX) + USTPUTC(CTLQUOTEEND, out); + continue; + case CDQUOTE: + if (magicq && arinest == 0 /* && varnest == 0 */) { + VTRACE(DBG_LEXER, ("<<\">>")); + /* Ignore inside here document */ + USTPUTC(c, out); + continue; + } + CVTRACE(DBG_LEXER, quotef==0, (" QF=1 ")); + quotef = 1; + if (arinest) { + if (ISDBLQUOTE()) { + VTRACE(DBG_LEXER, + (" CQE ari(%d", arinest)); + USTPUTC(CTLQUOTEEND, out); + TS_POP(); + VTRACE(DBG_LEXER, ("%d)TS_POP->%s ", + arinest, SYNTAX)); + } else { + VTRACE(DBG_LEXER, + (" ari(%d) %s TS_PUSH->DQ CQM ", + arinest, SYNTAX)); + TS_PUSH(); + syntax = DQSYNTAX; + SETDBLQUOTE(); + USTPUTC(CTLQUOTEMARK, out); + } + continue; + } + CVTRACE(DBG_LEXER, magicq, (" MQignDQ ")); + if (magicq) + continue; + if (ISDBLQUOTE()) { + TS_POP(); + VTRACE(DBG_LEXER, + (" DQ TS_POP->%s CQE ", SYNTAX)); + USTPUTC(CTLQUOTEEND, out); + } else { + VTRACE(DBG_LEXER, + (" %s TS_POP->DQ CQM ", SYNTAX)); + TS_PUSH(); + syntax = DQSYNTAX; + SETDBLQUOTE(); + USTPUTC(CTLQUOTEMARK, out); + } + continue; + case CVAR: /* '$' */ + VTRACE(DBG_LEXER, ("'$'...")); + out = insert_elided_nl(out); + PARSESUB(); /* parse substitution */ + continue; + case CENDVAR: /* CLOSEBRACE */ + if (varnest > 0 && !ISDBLQUOTE()) { + VTRACE(DBG_LEXER, ("vn=%d !DQ", varnest)); + TS_POP(); + VTRACE(DBG_LEXER, (" TS_POP->%s CEV ", SYNTAX)); + USTPUTC(CTLENDVAR, out); + } else { + VTRACE(DBG_LEXER, ("'%c'", c)); + USTPUTC(c, out); + } + out = insert_elided_nl(out); + continue; + case CLP: /* '(' in arithmetic */ + parenlevel++; + VTRACE(DBG_LEXER, ("'('(%d)", parenlevel)); + USTPUTC(c, out); + continue;; + case CRP: /* ')' in arithmetic */ + if (parenlevel > 0) { + USTPUTC(c, out); + --parenlevel; + VTRACE(DBG_LEXER, ("')'(%d)", parenlevel)); + } else { + VTRACE(DBG_LEXER, ("')'(%d)", parenlevel)); + if (pgetc_linecont() == /*(*/ ')') { + out = insert_elided_nl(out); + if (--arinest == 0) { + TS_POP(); + USTPUTC(CTLENDARI, out); + } else + USTPUTC(/*(*/ ')', out); + } else { + break; /* to synerror() just below */ +#if 0 /* the old way, causes weird errors on bad input */ + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(/*(*/ ')', out); +#endif + } + } + continue; + case CBQUOTE: /* '`' */ + VTRACE(DBG_LEXER, ("'`' -> parsebackq()\n")); + out = parsebackq(stack, out, &bqlist, 1); + VTRACE(DBG_LEXER, ("parsebackq() -> readtoken1: ")); + continue; + case CEOF: /* --> c == PEOF */ + VTRACE(DBG_LEXER, ("EOF ")); + break; /* will exit loop */ + default: + VTRACE(DBG_LEXER, ("['%c'(%#.2x)]", c&0xFF, c&0x1FF)); + if (varnest == 0 && !ISDBLQUOTE()) + break; /* exit loop */ + USTPUTC(c, out); + VTRACE(DBG_LEXER, (",")); + continue; + } + VTRACE(DBG_LEXER, (" END TOKEN\n", c&0xFF, c&0x1FF)); + break; /* break from switch -> break from for loop too */ + } + + if (syntax == ARISYNTAX) { + cleanup_state_stack(stack); + synerror(/*((*/ "Missing '))'"); + } + if (syntax != BASESYNTAX && /* ! parsebackquote && */ !magicq) { + cleanup_state_stack(stack); + synerror("Unterminated quoted string"); + } + if (varnest != 0) { + cleanup_state_stack(stack); + startlinno = plinno; + /* { */ + synerror("Missing '}'"); + } + + STPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + + if (!magicq) { + if ((c == '<' || c == '>') + && quotef == 0 && (*out == '\0' || is_number(out))) { + parseredir(out, c); + cleanup_state_stack(stack); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + + VTRACE(DBG_PARSE|DBG_LEXER, + ("readtoken1 %sword \"%s\", completed%s (%d) left %d enl\n", + (quotef ? "quoted " : ""), out, (bqlist ? " with cmdsubs" : ""), + len, elided_nl)); + + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + cleanup_state_stack(stack); + return lasttoken = TWORD; +/* end of readtoken routine */ + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + + c = pgetc_linecont(); + VTRACE(DBG_LEXER, ("\"$%c\"(%#.2x)", c&0xFF, c&0x1FF)); + if (c == '(' /*)*/) { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(' /*')'*/ ) { + VTRACE(DBG_LEXER, ("\"$((\" ARITH ")); + out = insert_elided_nl(out); + PARSEARITH(); + } else { + VTRACE(DBG_LEXER, ("\"$(\" CSUB->parsebackq()\n")); + out = insert_elided_nl(out); + pungetc(); + out = parsebackq(stack, out, &bqlist, 0); + VTRACE(DBG_LEXER, ("parseback()->readtoken1(): ")); + } + } else if (c == OPENBRACE || is_name(c) || is_special(c)) { + VTRACE(DBG_LEXER, (" $EXP:CTLVAR ")); + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == OPENBRACE) { + c = pgetc_linecont(); + if (c == '#') { + if ((c = pgetc_linecont()) == CLOSEBRACE) + c = '#'; + else if (is_name(c) || isdigit(c)) + subtype = VSLENGTH; + else if (is_special(c)) { + /* + * ${#} is $# - the number of sh params + * ${##} is the length of ${#} + * ${###} is ${#} with as much nothing + * as possible removed from start + * ${##1} is ${#} with leading 1 gone + * ${##\#} is ${#} with leading # gone + * + * this stuff is UGLY! + */ + if (pgetc_linecont() == CLOSEBRACE) { + pungetc(); + subtype = VSLENGTH; + } else { + static char cbuf[2]; + + pungetc(); /* would like 2 */ + cbuf[0] = c; /* so ... */ + cbuf[1] = '\0'; + pushstring(cbuf, 1, NULL); + c = '#'; /* ${#:...} */ + subtype = 0; /* .. or similar */ + } + } else { + pungetc(); + c = '#'; + subtype = 0; + } + } + else + subtype = 0; + VTRACE(DBG_LEXER, ("${ st=%d ", subtype)); + } + if (is_name(c)) { + p = out; + do { + VTRACE(DBG_LEXER, ("%c", c)); + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_in_name(c)); + +#if 0 + if (out - p == 6 && strncmp(p, "LINENO", 6) == 0) { + int i; + int linno; + char buf[10]; + + /* + * The "LINENO hack" + * + * Replace the variable name with the + * current line number. + */ + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + snprintf(buf, sizeof(buf), "%d", linno); + STADJUST(-6, out); + for (i = 0; buf[i] != '\0'; i++) + STPUTC(buf[i], out); + flags |= VSLINENO; + } +#endif + } else if (is_digit(c)) { + do { + VTRACE(DBG_LEXER, ("%c", c)); + STPUTC(c, out); + c = pgetc_linecont(); + } while (subtype != VSNORMAL && is_digit(c)); + } + else if (is_special(c)) { + VTRACE(DBG_LEXER, ("\"$%c", c)); + USTPUTC(c, out); + c = pgetc_linecont(); + } + else { + VTRACE(DBG_LEXER, ("\"$%c(%#.2x)??\n", c&0xFF,c&0x1FF)); + badsub: + cleanup_state_stack(stack); + synerror("Bad substitution"); + } + + STPUTC('=', out); + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) + goto badsub; + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc_linecont(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else { + if (subtype == VSLENGTH && c != /*{*/ '}') + synerror("no modifiers allowed with ${#var}"); + pungetc(); + } + if (quoted || arinest) + flags |= VSQUOTE; + if (subtype >= VSTRIMLEFT && subtype <= VSTRIMRIGHTMAX) + flags |= VSPATQ; + VTRACE(DBG_LEXER, (" st%d:%x", subtype, flags)); + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + TS_PUSH(); + varnest++; + arinest = 0; + if (subtype > VSASSIGN) { /* # ## % %% */ + syntax = BASESYNTAX; + quoted = 0; + magicq = 0; + } + VTRACE(DBG_LEXER, (" TS_PUSH->%s vn=%d%s ", + SYNTAX, varnest, quoted ? " Q" : "")); + } + } else if (c == '\'' && syntax == BASESYNTAX) { + USTPUTC(CTLQUOTEMARK, out); + VTRACE(DBG_LEXER, (" CSTR \"$'\" CQM ")); + CVTRACE(DBG_LEXER, quotef==0, ("QF=1 ")); + quotef = 1; + TS_PUSH(); + syntax = SQSYNTAX; + quoted = CQ; + VTRACE(DBG_LEXER, ("%s->TS_PUSH()->SQ ", SYNTAX)); + } else { + VTRACE(DBG_LEXER, ("$unk -> '$' (pushback '%c'%#.2x)", + c & 0xFF, c & 0x1FF)); + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + +#if 0 + if (syntax == ARISYNTAX) { + /* + * we collapse embedded arithmetic expansion to + * parentheses, which should be equivalent + * + * XXX It isn't, must fix, soonish... + */ + USTPUTC('(' /*)*/, out); + USTPUTC('(' /*)*/, out); + /* + * Need 2 of them because there will (should be) + * two closing ))'s to follow later. + */ + parenlevel += 2; + } else +#endif + { + VTRACE(DBG_LEXER, (" CTLARI%c ", ISDBLQUOTE()?'"':'_')); + USTPUTC(CTLARI, out); + if (ISDBLQUOTE()) + USTPUTC('"',out); + else + USTPUTC(' ',out); + + VTRACE(DBG_LEXER, ("%s->TS_PUSH->ARI(1)", SYNTAX)); + TS_PUSH(); + syntax = ARISYNTAX; + arinest = 1; + varnest = 0; + magicq = 1; + } + goto parsearith_return; +} + +} /* end of readtoken */ + + + + +#ifdef mkinit +INCLUDE "parser.h" + +RESET { + psp.v_current_parser = &parse_state; + + parse_state.ps_tokpushback = 0; + parse_state.ps_checkkwd = 0; + parse_state.ps_heredoclist = NULL; +} +#endif + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +STATIC int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if (c == CTLQUOTEMARK || c == CTLQUOTEEND) + continue; + if (c == CTLESC) + p++; + else if (BASESYNTAX[(int)c] == CCTL) + return 0; + } + return 1; +} + + +/* + * Return true if the argument is a legal variable name (a letter or + * underscore followed by zero or more letters, underscores, and digits). + */ + +int +goodname(const char *name) +{ + const char *p; + + p = name; + if (! is_name(*p)) + return 0; + while (*++p) { + if (! is_in_name(*p)) + return 0; + } + return 1; +} + +int +isassignment(const char *p) +{ + if (!is_name(*p)) + return 0; + while (*++p != '=') + if (*p == '\0' || !is_in_name(*p)) + return 0; + return 1; +} + +/* + * skip past any \n's, and leave lasttoken set to whatever follows + */ +STATIC void +linebreak(void) +{ + while (readtoken() == TNL) + ; +} + +/* + * The next token must be "token" -- check, then move past it + */ +STATIC void +consumetoken(int token) +{ + if (readtoken() != token) { + VTRACE(DBG_PARSE, ("consumetoken(%d): expecting %s got %s", + token, tokname[token], tokname[lasttoken])); + CVTRACE(DBG_PARSE, (lasttoken==TWORD), (" \"%s\"", wordtext)); + VTRACE(DBG_PARSE, ("\n")); + synexpect(token, NULL); + } +} + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ + +STATIC void +synexpect(int token, const char *text) +{ + char msg[64]; + char *p; + + if (lasttoken == TWORD) { + size_t len = strlen(wordtext); + + if (len <= 13) + fmtstr(msg, 34, "Word \"%.13s\" unexpected", wordtext); + else + fmtstr(msg, 34, + "Word \"%.10s...\" unexpected", wordtext); + } else + fmtstr(msg, 34, "%s unexpected", tokname[lasttoken]); + + p = strchr(msg, '\0'); + if (text) + fmtstr(p, 30, " (expecting \"%.10s\")", text); + else if (token >= 0) + fmtstr(p, 30, " (expecting %s)", tokname[token]); + + synerror(msg); + /* NOTREACHED */ +} + + +STATIC void +synerror(const char *msg) +{ + error("%d: Syntax error: %s", startlinno, msg); + /* NOTREACHED */ +} + +STATIC void +setprompt(int which) +{ + whichprompt = which; + +#ifndef SMALL + if (!el) +#endif + out2str(getprompt(NULL)); +} + +/* + * handle getting the next character, while ignoring \ \n + * (which is a little tricky as we only have one char of pushback + * and we need that one elsewhere). + */ +STATIC int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + elided_nl++; + VTRACE(DBG_LEXER, ("\"\\n\"drop(el=%d@%d)", + elided_nl, plinno)); + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +const char * +getprompt(void *unused) +{ + char *p; + const char *cp; + int wp; + + if (!doprompt) + return ""; + + VTRACE(DBG_PARSE|DBG_EXPAND, ("getprompt %d\n", whichprompt)); + + switch (wp = whichprompt) { + case 0: + return ""; + case 1: + p = ps1val(); + break; + case 2: + p = ps2val(); + break; + default: + return "<internal prompt error>"; + } + if (p == NULL) + return ""; + + VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt <<%s>>\n", p)); + + cp = expandstr(p, plinno); + whichprompt = wp; /* history depends on it not changing */ + + VTRACE(DBG_PARSE|DBG_EXPAND, ("prompt -> <<%s>>\n", cp)); + + return cp; +} + +/* + * Expand a string ... used for expanding prompts (PS1...) + * + * Never return NULL, always some string (return input string if invalid) + * + * The internal routine does the work, leaving the result on the + * stack (or in a static string, or even the input string) and + * handles parser recursion, and cleanup after an error while parsing. + * + * The visible interface copies the result off the stack (if it is there), + * and handles stack management, leaving the stack in the exact same + * state it was when expandstr() was called (so it can be used part way + * through building a stack data structure - as in when PS2 is being + * expanded half way through reading a "command line") + * + * on error, expandonstack() cleans up the parser state, but then + * simply jumps out through expandstr() withut doing any stack cleanup, + * which is OK, as the error handler must deal with that anyway. + * + * The split into two funcs is to avoid problems with setjmp/longjmp + * and local variables which could otherwise be optimised into bizarre + * behaviour. + */ +static const char * +expandonstack(char *ps, int cmdsub, int lineno) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + struct parsefile *const savetopfile = getcurrentfile(); + const int save_x = xflag; + struct parse_state new_state = init_parse_state; + struct parse_state *const saveparser = psp.v_current_parser; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + + psp.v_current_parser = &new_state; + setinputstring(ps, 1, lineno); + + readtoken1(pgetc(), DQSYNTAX, 1); + if (backquotelist != NULL) { + if (!cmdsub) + result = ps; + else if (!promptcmds) + result = "-o promptcmds not set: "; + } + if (result == NULL) { + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.lineno = lineno; + n.narg.backquote = backquotelist; + + xflag = 0; /* we might be expanding PS4 ... */ + expandarg(&n, NULL, 0); + result = stackblock(); + } + } else { + psp.v_current_parser = saveparser; + xflag = save_x; + popfilesupto(savetopfile); + handler = savehandler; + + if (exception == EXEXIT) + longjmp(handler->loc, 1); + if (exception == EXINT) + exraise(SIGINT); + return ps; + } + psp.v_current_parser = saveparser; + xflag = save_x; + popfilesupto(savetopfile); + handler = savehandler; + + + if (result == NULL) + result = ps; + + return result; +} + +const char * +expandstr(char *ps, int lineno) +{ + const char *result = NULL; + struct stackmark smark; + static char *buffer = NULL; /* storage for prompt, never freed */ + static size_t bufferlen = 0; + + setstackmark(&smark); + /* + * At this point we anticipate that there may be a string + * growing on the stack, but we have no idea how big it is. + * However we know that it cannot be bigger than the current + * allocated stack block, so simply reserve the whole thing, + * then we can use the stack without barfing all over what + * is there already... (the stack mark undoes this later.) + */ + (void) stalloc(stackblocksize()); + + result = expandonstack(ps, 1, lineno); + + if (__predict_true(result == stackblock())) { + size_t len = strlen(result) + 1; + + /* + * the result (usual case) is on the stack, which we + * are just about to discard (popstackmark()) so we + * need to move it somewhere safe first. + */ + + if (__predict_false(len > bufferlen)) { + char *new; + size_t newlen = bufferlen; + + if (__predict_false(len > (SIZE_MAX >> 4))) { + result = "huge prompt: "; + goto getout; + } + + if (newlen == 0) + newlen = 32; + while (newlen <= len) + newlen <<= 1; + + new = (char *)realloc(buffer, newlen); + + if (__predict_false(new == NULL)) { + /* + * this should rarely (if ever) happen + * but we must do something when it does... + */ + result = "No mem for prompt: "; + goto getout; + } else { + buffer = new; + bufferlen = newlen; + } + } + (void)memcpy(buffer, result, len); + result = buffer; + } + + getout:; + popstackmark(&smark); + + return result; +} + +/* + * and a simpler version, which does no $( ) expansions, for + * use during shell startup when we know we are not parsing, + * and so the stack is not in use - we can do what we like, + * and do not need to clean up (that's handled externally). + * + * Simply return the result, even if it is on the stack + */ +const char * +expandenv(char *arg) +{ + return expandonstack(arg, 0, 0); +} diff --git a/bin/sh/parser.h b/bin/sh/parser.h new file mode 100644 index 0000000..7545b4f --- /dev/null +++ b/bin/sh/parser.h @@ -0,0 +1,169 @@ +/* $NetBSD: parser.h,v 1.27 2018/12/11 13:31:20 kre Exp $ */ + +/*- + * 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. + * + * @(#)parser.h 8.3 (Berkeley) 5/4/95 + */ + +/* control characters in argument strings */ +#define CTL_FIRST '\201' /* first 'special' character */ +#define CTLESC '\201' /* escape next character */ +#define CTLVAR '\202' /* variable defn */ +#define CTLENDVAR '\203' +#define CTLBACKQ '\204' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\205' */ +#define CTLARI '\206' /* arithmetic expression */ +#define CTLENDARI '\207' +#define CTLQUOTEMARK '\210' +#define CTLQUOTEEND '\211' /* only inside ${...} */ +#define CTLNONL '\212' /* The \n in a deleted \ \n sequence */ + /* pure concidence that (CTLNONL & 0x7f) == '\n' */ +#define CTLCNL '\213' /* A $'\n' - newline not counted */ +#define CTL_LAST '\213' /* last 'special' character */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSLINENO 0x20 /* expansion of $LINENO, the line number + follows immediately */ +#define VSPATQ 0x40 /* ensure correct pattern quoting in ${x#pat} */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMLEFT 0x6 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */ +#define VSTRIMRIGHT 0x8 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ +#define VSLENGTH 0xa /* ${#var} */ + +union node *parsecmd(int); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +const char *getprompt(void *); +const char *expandstr(char *, int); +const char *expandenv(char *); + +struct HereDoc; +union node; +struct nodelist; + +struct parse_state { + struct HereDoc *ps_heredoclist; /* list of here documents to read */ + int ps_parsebackquote; /* nonzero inside backquotes */ + int ps_doprompt; /* if set, prompt the user */ + int ps_needprompt; /* true if interactive at line start */ + int ps_lasttoken; /* last token read */ + int ps_tokpushback; /* last token pushed back */ + char *ps_wordtext; /* text of last word returned by readtoken */ + int ps_checkkwd; /* word expansion flags, see below */ + struct nodelist *ps_backquotelist; /* list of cmdsubs to process */ + union node *ps_redirnode; /* node for current redirect */ + struct HereDoc *ps_heredoc; /* current heredoc << beign parsed */ + int ps_quoteflag; /* set if (part) of token was quoted */ + int ps_startlinno; /* line # where last token started */ + int ps_funclinno; /* line # of the current function */ + int ps_elided_nl; /* count of \ \n pairs we have seen */ +}; + +/* + * The parser references the elements of struct parse_state quite + * frequently - they used to be simple globals, so one memory ref + * per access, adding an indirect through global ptr would not be + * nice. The following gross hack allows most of that cost to be + * avoided, by allowing the compiler to understand that the global + * pointer is in fact constant in any function, and so its value can + * be cached, rather than needing to be fetched every time in case + * some other called function has changed it. + * + * The rule to make this work is that any function that wants + * to alter the global must restore it before it returns (and thus + * must have an error trap handler). That means that the struct + * used for the new parser state can be a local in that function's + * stack frame, it never needs to be malloc'd. + */ + +union parse_state_p { + struct parse_state *const c_current_parser; + struct parse_state * v_current_parser; +}; + +extern union parse_state_p psp; + +#define current_parser (psp.c_current_parser) + +/* + * Perhaps one day emulate "static" by moving most of these definitions into + * parser.c ... (only checkkwd & tokpushback are used outside parser.c, + * and only in init.c as a RESET activity) + */ +#define tokpushback (current_parser->ps_tokpushback) +#define checkkwd (current_parser->ps_checkkwd) + +#define noalias (current_parser->ps_noalias) +#define heredoclist (current_parser->ps_heredoclist) +#define parsebackquote (current_parser->ps_parsebackquote) +#define doprompt (current_parser->ps_doprompt) +#define needprompt (current_parser->ps_needprompt) +#define lasttoken (current_parser->ps_lasttoken) +#define wordtext (current_parser->ps_wordtext) +#define backquotelist (current_parser->ps_backquotelist) +#define redirnode (current_parser->ps_redirnode) +#define heredoc (current_parser->ps_heredoc) +#define quoteflag (current_parser->ps_quoteflag) +#define startlinno (current_parser->ps_startlinno) +#define funclinno (current_parser->ps_funclinno) +#define elided_nl (current_parser->ps_elided_nl) + +/* + * Values that can be set in checkkwd + */ +#define CHKKWD 0x01 /* turn word into keyword (if it is) */ +#define CHKNL 0x02 /* ignore leading \n's */ +#define CHKALIAS 0x04 /* lookup words as aliases and ... */ + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. + */ +#define NEOF ((union node *)&psp) + +#ifdef DEBUG +extern int parsing; +#endif diff --git a/bin/sh/redir.c b/bin/sh/redir.c new file mode 100644 index 0000000..751cce7 --- /dev/null +++ b/bin/sh/redir.c @@ -0,0 +1,982 @@ +/* $NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/param.h> /* PIPE_BUF */ +#include <sys/stat.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * Code for dealing with input/output redirection. + */ + +#include "main.h" +#include "builtins.h" +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "options.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "mystring.h" +#include "error.h" +#include "show.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif + + +MKINIT +struct renamelist { + struct renamelist *next; + int orig; + int into; +}; + +MKINIT +struct redirtab { + struct redirtab *next; + struct renamelist *renamed; +}; + + +MKINIT struct redirtab *redirlist; + +/* + * We keep track of whether or not fd0 has been redirected. This is for + * background commands, where we want to redirect fd0 to /dev/null only + * if it hasn't already been redirected. + */ +STATIC int fd0_redirected = 0; + +/* + * And also where to put internal use fds that should be out of the + * way of user defined fds (normally) + */ +STATIC int big_sh_fd = 0; + +STATIC const struct renamelist *is_renamed(const struct renamelist *, int); +STATIC void fd_rename(struct redirtab *, int, int); +STATIC void free_rl(struct redirtab *, int); +STATIC void openredirect(union node *, char[10], int); +STATIC int openhere(const union node *); +STATIC int copyfd(int, int, int); +STATIC void find_big_fd(void); + + +struct shell_fds { /* keep track of internal shell fds */ + struct shell_fds *nxt; + void (*cb)(int, int); + int fd; +}; + +STATIC struct shell_fds *sh_fd_list; + +STATIC void renumber_sh_fd(struct shell_fds *); +STATIC struct shell_fds *sh_fd(int); + +STATIC const struct renamelist * +is_renamed(const struct renamelist *rl, int fd) +{ + while (rl != NULL) { + if (rl->orig == fd) + return rl; + rl = rl->next; + } + return NULL; +} + +STATIC void +free_rl(struct redirtab *rt, int reset) +{ + struct renamelist *rl, *rn = rt->renamed; + + while ((rl = rn) != NULL) { + rn = rl->next; + if (rl->orig == 0) + fd0_redirected--; + VTRACE(DBG_REDIR, ("popredir %d%s: %s", + rl->orig, rl->orig==0 ? " (STDIN)" : "", + reset ? "" : "no reset\n")); + if (reset) { + if (rl->into < 0) { + VTRACE(DBG_REDIR, ("closed\n")); + close(rl->orig); + } else { + VTRACE(DBG_REDIR, ("from %d\n", rl->into)); + movefd(rl->into, rl->orig); + } + } + ckfree(rl); + } + rt->renamed = NULL; +} + +STATIC void +fd_rename(struct redirtab *rt, int from, int to) +{ + /* XXX someday keep a short list (8..10) of freed renamelists XXX */ + struct renamelist *rl = ckmalloc(sizeof(struct renamelist)); + + rl->next = rt->renamed; + rt->renamed = rl; + + rl->orig = from; + rl->into = to; +} + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. + */ + +void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv = NULL; + int i; + int fd; + char memory[10]; /* file descriptors to write to memory */ + + CTRACE(DBG_REDIR, ("redirect(F=0x%x):%s\n", flags, redir?"":" NONE")); + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + /* + * We don't have to worry about REDIR_VFORK here, as + * flags & REDIR_PUSH is never true if REDIR_VFORK is set. + */ + sv = ckmalloc(sizeof (struct redirtab)); + sv->renamed = NULL; + sv->next = redirlist; + redirlist = sv; + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + VTRACE(DBG_REDIR, ("redir %d (max=%d) ", fd, max_user_fd)); + if (fd > max_user_fd) + max_user_fd = fd; + renumber_sh_fd(sh_fd(fd)); + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) { + /* redirect from/to same file descriptor */ + /* make sure it stays open */ + if (fcntl(fd, F_SETFD, 0) < 0) + error("fd %d: %s", fd, strerror(errno)); + VTRACE(DBG_REDIR, ("!cloexec\n")); + continue; + } + + if ((flags & REDIR_PUSH) && !is_renamed(sv->renamed, fd)) { + INTOFF; + if (big_sh_fd < 10) + find_big_fd(); + if ((i = fcntl(fd, F_DUPFD, big_sh_fd)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + case EMFILE: + case EINVAL: + find_big_fd(); + i = fcntl(fd, F_DUPFD, big_sh_fd); + if (i >= 0) + break; + /* FALLTHRU */ + default: + i = errno; + INTON; /* XXX not needed here ? */ + error("%d: %s", fd, strerror(i)); + /* NOTREACHED */ + } + } + if (i >= 0) + (void)fcntl(i, F_SETFD, FD_CLOEXEC); + fd_rename(sv, fd, i); + VTRACE(DBG_REDIR, ("saved as %d ", i)); + INTON; + } + VTRACE(DBG_REDIR, ("%s\n", fd == 0 ? "STDIN" : "")); + if (fd == 0) + fd0_redirected++; + openredirect(n, memory, flags); + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; +} + + +STATIC void +openredirect(union node *redir, char memory[10], int flags) +{ + struct stat sb; + int fd = redir->nfile.fd; + char *fname; + int f; + int eflags, cloexec; + + /* + * We suppress interrupts so that we won't leave open file + * descriptors around. This may not be such a good idea because + * an open of a device or a fifo can block indefinitely. + */ + INTOFF; + if (fd < 10) + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if (flags & REDIR_VFORK) + eflags = O_NONBLOCK; + else + eflags = 0; + if ((f = open(fname, O_RDONLY|eflags)) < 0) + goto eopen; + VTRACE(DBG_REDIR, ("openredirect(< '%s') -> %d [%#x]", + fname, f, eflags)); + if (eflags) + (void)fcntl(f, F_SETFL, fcntl(f, F_GETFL, 0) & ~eflags); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + goto ecreate; + VTRACE(DBG_REDIR, ("openredirect(<> '%s') -> %d", fname, f)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY)) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, + 0666)) < 0) + goto ecreate; + } else if (fstat(f, &sb) == -1) { + int serrno = errno; + close(f); + errno = serrno; + goto ecreate; + } else if (S_ISREG(sb.st_mode)) { + close(f); + errno = EEXIST; + goto ecreate; + } + VTRACE(DBG_REDIR, ("openredirect(>| '%s') -> %d", + fname, f)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + goto ecreate; + VTRACE(DBG_REDIR, ("openredirect(> '%s') -> %d", fname, f)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + goto ecreate; + VTRACE(DBG_REDIR, ("openredirect(>> '%s') -> %d", fname, f)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (fd < 10 && redir->ndup.dupfd < 10 && + memory[redir->ndup.dupfd]) + memory[fd] = 1; + else if (copyfd(redir->ndup.dupfd, fd, + (flags & REDIR_KEEP) == 0) < 0) + error("Redirect (from %d to %d) failed: %s", + redir->ndup.dupfd, fd, strerror(errno)); + VTRACE(DBG_REDIR, ("openredirect: %d%c&%d\n", fd, + "<>"[redir->nfile.type==NTOFD], redir->ndup.dupfd)); + } else { + (void) close(fd); + VTRACE(DBG_REDIR, ("openredirect: %d%c&-\n", fd, + "<>"[redir->nfile.type==NTOFD])); + } + INTON; + return; + case NHERE: + case NXHERE: + VTRACE(DBG_REDIR, ("openredirect: %d<<...", fd)); + f = openhere(redir); + break; + default: + abort(); + } + + cloexec = fd > 2 && (flags & REDIR_KEEP) == 0 && !posix; + if (f != fd) { + VTRACE(DBG_REDIR, (" -> %d", fd)); + if (copyfd(f, fd, cloexec) < 0) { + int e = errno; + + close(f); + error("redirect reassignment (fd %d) failed: %s", fd, + strerror(e)); + } + close(f); + } else if (cloexec) + (void)fcntl(f, F_SETFD, FD_CLOEXEC); + VTRACE(DBG_REDIR, ("%s\n", cloexec ? " cloexec" : "")); + + INTON; + return; + ecreate: + exerrno = 1; + error("cannot create %s: %s", fname, errmsg(errno, E_CREAT)); + eopen: + exerrno = 1; + error("cannot open %s: %s", fname, errmsg(errno, E_OPEN)); +} + + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ + +STATIC int +openhere(const union node *redir) +{ + int pip[2]; + int len = 0; + + if (pipe(pip) < 0) + error("Pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPESIZE) { + xwrite(pip[1], redir->nhere.doc->narg.text, len); + goto out; + } + } + VTRACE(DBG_REDIR, (" forking [%d,%d]\n", pip[0], pip[1])); + if (forkshell(NULL, NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + xwrite(pip[1], redir->nhere.doc->narg.text, len); + else + expandhere(redir->nhere.doc, pip[1]); + _exit(0); + } + VTRACE(DBG_REDIR, ("openhere (closing %d)", pip[1])); + out: + close(pip[1]); + VTRACE(DBG_REDIR, (" (pipe fd=%d)", pip[0])); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + + INTOFF; + free_rl(rp, 1); + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* + * Undo all redirections. Called on error or interrupt. + */ + +#ifdef mkinit + +INCLUDE "redir.h" + +RESET { + while (redirlist) + popredir(); +} + +SHELLPROC { + clearredir(0); +} + +#endif + +/* Return true if fd 0 has already been redirected at least once. */ +int +fd0_redirected_p(void) +{ + return fd0_redirected != 0; +} + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(int vforked) +{ + struct redirtab *rp; + struct renamelist *rl; + + for (rp = redirlist ; rp ; rp = rp->next) { + if (!vforked) + free_rl(rp, 0); + else for (rl = rp->renamed; rl; rl = rl->next) + if (rl->into >= 0) + close(rl->into); + } +} + + + +/* + * Copy a file descriptor to be == to. + * cloexec indicates if we want close-on-exec or not. + * Returns -1 if any error occurs. + */ + +STATIC int +copyfd(int from, int to, int cloexec) +{ + int newfd; + + if (cloexec && to > 2) + newfd = dup3(from, to, O_CLOEXEC); + else + newfd = dup2(from, to); + + return newfd; +} + +/* + * rename fd from to be fd to (closing from). + * close-on-exec is never set on 'to' (unless + * from==to and it was set on from) - ie: a no-op + * returns to (or errors() if an error occurs). + * + * This is mostly used for rearranging the + * results from pipe(). + */ +int +movefd(int from, int to) +{ + if (from == to) + return to; + + (void) close(to); + if (copyfd(from, to, 0) != to) { + int e = errno; + + (void) close(from); + error("Unable to make fd %d: %s", to, strerror(e)); + } + (void) close(from); + + return to; +} + +STATIC void +find_big_fd(void) +{ + int i, fd; + static int last_start = 3; /* aim to keep sh fd's under 20 */ + + if (last_start < 10) + last_start++; + + for (i = (1 << last_start); i >= 10; i >>= 1) { + if ((fd = fcntl(0, F_DUPFD, i - 1)) >= 0) { + close(fd); + break; + } + } + + fd = (i / 5) * 4; + if (fd < 10) + fd = 10; + + big_sh_fd = fd; +} + +/* + * If possible, move file descriptor fd out of the way + * of expected user fd values. Returns the new fd + * (which may be the input fd if things do not go well.) + * Always set close-on-exec on the result, and close + * the input fd unless it is to be our result. + */ +int +to_upper_fd(int fd) +{ + int i; + + VTRACE(DBG_REDIR|DBG_OUTPUT, ("to_upper_fd(%d)", fd)); + if (big_sh_fd < 10) + find_big_fd(); + do { + i = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd); + if (i >= 0) { + if (fd != i) + close(fd); + VTRACE(DBG_REDIR|DBG_OUTPUT, ("-> %d\n", i)); + return i; + } + if (errno != EMFILE && errno != EINVAL) + break; + find_big_fd(); + } while (big_sh_fd > 10); + + /* + * If we wanted to move this fd to some random high number + * we certainly do not intend to pass it through exec, even + * if the reassignment failed. + */ + (void)fcntl(fd, F_SETFD, FD_CLOEXEC); + VTRACE(DBG_REDIR|DBG_OUTPUT, (" fails ->%d\n", fd)); + return fd; +} + +void +register_sh_fd(int fd, void (*cb)(int, int)) +{ + struct shell_fds *fp; + + fp = ckmalloc(sizeof (struct shell_fds)); + if (fp != NULL) { + fp->nxt = sh_fd_list; + sh_fd_list = fp; + + fp->fd = fd; + fp->cb = cb; + } +} + +void +sh_close(int fd) +{ + struct shell_fds **fpp, *fp; + + fpp = &sh_fd_list; + while ((fp = *fpp) != NULL) { + if (fp->fd == fd) { + *fpp = fp->nxt; + ckfree(fp); + break; + } + fpp = &fp->nxt; + } + (void)close(fd); +} + +STATIC struct shell_fds * +sh_fd(int fd) +{ + struct shell_fds *fp; + + for (fp = sh_fd_list; fp != NULL; fp = fp->nxt) + if (fp->fd == fd) + return fp; + return NULL; +} + +STATIC void +renumber_sh_fd(struct shell_fds *fp) +{ + int to; + + if (fp == NULL) + return; + +#ifndef F_DUPFD_CLOEXEC +#define F_DUPFD_CLOEXEC F_DUPFD +#define CLOEXEC(fd) (fcntl((fd), F_SETFD, fcntl((fd),F_GETFD) | FD_CLOEXEC)) +#else +#define CLOEXEC(fd) +#endif + + /* + * if we have had a collision, and the sh fd was a "big" one + * try moving the sh fd base to a higher number (if possible) + * so future sh fds are less likely to be in the user's sights + * (incl this one when moved) + */ + if (fp->fd >= big_sh_fd) + find_big_fd(); + + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd/2); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, fp->fd + 1); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 10); + if (to == -1) + to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 3); + if (to == -1) + error("insufficient file descriptors available"); + CLOEXEC(to); + + if (fp->fd == to) /* impossible? */ + return; + + (*fp->cb)(fp->fd, to); + (void)close(fp->fd); + fp->fd = to; +} + +static const struct flgnames { + const char *name; + uint16_t minch; + uint32_t value; +} nv[] = { +#ifdef O_APPEND + { "append", 2, O_APPEND }, +#endif +#ifdef O_ASYNC + { "async", 2, O_ASYNC }, +#endif +#ifdef O_SYNC + { "sync", 2, O_SYNC }, +#endif +#ifdef O_NONBLOCK + { "nonblock", 3, O_NONBLOCK }, +#endif +#ifdef O_FSYNC + { "fsync", 2, O_FSYNC }, +#endif +#ifdef O_DSYNC + { "dsync", 2, O_DSYNC }, +#endif +#ifdef O_RSYNC + { "rsync", 2, O_RSYNC }, +#endif +#ifdef O_ALT_IO + { "altio", 2, O_ALT_IO }, +#endif +#ifdef O_DIRECT + { "direct", 2, O_DIRECT }, +#endif +#ifdef O_NOSIGPIPE + { "nosigpipe", 3, O_NOSIGPIPE }, +#endif +#ifdef O_CLOEXEC + { "cloexec", 2, O_CLOEXEC }, +#endif + { 0, 0, 0 } +}; +#define ALLFLAGS (O_APPEND|O_ASYNC|O_SYNC|O_NONBLOCK|O_DSYNC|O_RSYNC|\ + O_ALT_IO|O_DIRECT|O_NOSIGPIPE|O_CLOEXEC) + +static int +getflags(int fd, int p) +{ + int c, f; + + if (sh_fd(fd) != NULL) { + if (!p) + return -1; + error("Can't get status for fd=%d (%s)", fd, + "Bad file descriptor"); /*XXX*/ + } + + if ((c = fcntl(fd, F_GETFD)) == -1) { + if (!p) + return -1; + error("Can't get status for fd=%d (%s)", fd, strerror(errno)); + } + if ((f = fcntl(fd, F_GETFL)) == -1) { + if (!p) + return -1; + error("Can't get flags for fd=%d (%s)", fd, strerror(errno)); + } + if (c & FD_CLOEXEC) + f |= O_CLOEXEC; + return f & ALLFLAGS; +} + +static void +printone(int fd, int p, int verbose, int pfd) +{ + int f = getflags(fd, p); + const struct flgnames *fn; + + if (f == -1) + return; + + if (pfd) + outfmt(out1, "%d: ", fd); + for (fn = nv; fn->name; fn++) { + if (f & fn->value) { + outfmt(out1, "%s%s", verbose ? "+" : "", fn->name); + f &= ~fn->value; + } else if (verbose) + outfmt(out1, "-%s", fn->name); + else + continue; + if (f || (verbose && fn[1].name)) + outfmt(out1, ","); + } + if (verbose && f) /* f should be normally be 0 */ + outfmt(out1, " +%#x", f); + outfmt(out1, "\n"); +} + +static void +parseflags(char *s, int *p, int *n) +{ + int *v, *w; + const struct flgnames *fn; + size_t len; + + *p = 0; + *n = 0; + for (s = strtok(s, ","); s; s = strtok(NULL, ",")) { + switch (*s++) { + case '+': + v = p; + w = n; + break; + case '-': + v = n; + w = p; + break; + default: + error("Missing +/- indicator before flag %s", s-1); + } + + len = strlen(s); + for (fn = nv; fn->name; fn++) + if (len >= fn->minch && strncmp(s,fn->name,len) == 0) { + *v |= fn->value; + *w &=~ fn->value; + break; + } + if (fn->name == 0) + error("Bad flag `%s'", s); + } +} + +static void +setone(int fd, int pos, int neg, int verbose) +{ + int f = getflags(fd, 1); + int n, cloexec; + + if (f == -1) + return; + + cloexec = -1; + if ((pos & O_CLOEXEC) && !(f & O_CLOEXEC)) + cloexec = FD_CLOEXEC; + if ((neg & O_CLOEXEC) && (f & O_CLOEXEC)) + cloexec = 0; + + if (cloexec != -1 && fcntl(fd, F_SETFD, cloexec) == -1) + error("Can't set status for fd=%d (%s)", fd, strerror(errno)); + + pos &= ~O_CLOEXEC; + neg &= ~O_CLOEXEC; + f &= ~O_CLOEXEC; + n = f; + n |= pos; + n &= ~neg; + if (n != f && fcntl(fd, F_SETFL, n) == -1) + error("Can't set flags for fd=%d (%s)", fd, strerror(errno)); + if (verbose) + printone(fd, 1, verbose, 1); +} + +int +fdflagscmd(int argc, char *argv[]) +{ + char *num; + int verbose = 0, ch, pos = 0, neg = 0; + char *setflags = NULL; + + optreset = 1; optind = 1; /* initialize getopt */ + while ((ch = getopt(argc, argv, ":vs:")) != -1) + switch ((char)ch) { + case 'v': + verbose = 1; + break; + case 's': + if (setflags) + goto msg; + setflags = optarg; + break; + case '?': + default: + msg: + error("Usage: fdflags [-v] [-s <flags> fd] [fd...]"); + /* NOTREACHED */ + } + + argc -= optind, argv += optind; + + if (setflags) + parseflags(setflags, &pos, &neg); + + if (argc == 0) { + int i; + + if (setflags) + goto msg; + + for (i = 0; i <= max_user_fd; i++) + printone(i, 0, verbose, 1); + return 0; + } + + while ((num = *argv++) != NULL) { + int fd = number(num); + + while (num[0] == '0' && num[1] != '\0') /* skip 0's */ + num++; + if (strlen(num) > 5) + error("%s too big to be a file descriptor", num); + + if (setflags) + setone(fd, pos, neg, verbose); + else + printone(fd, 1, verbose, argc > 1); + } + return 0; +} + +#undef MAX /* in case we inherited them from somewhere */ +#undef MIN + +#define MIN(a,b) (/*CONSTCOND*/((a)<=(b)) ? (a) : (b)) +#define MAX(a,b) (/*CONSTCOND*/((a)>=(b)) ? (a) : (b)) + + /* now make the compiler work for us... */ +#define MIN_REDIR MIN(MIN(MIN(MIN(NTO,NFROM), MIN(NTOFD,NFROMFD)), \ + MIN(MIN(NCLOBBER,NAPPEND), MIN(NHERE,NXHERE))), NFROMTO) +#define MAX_REDIR MAX(MAX(MAX(MAX(NTO,NFROM), MAX(NTOFD,NFROMFD)), \ + MAX(MAX(NCLOBBER,NAPPEND), MAX(NHERE,NXHERE))), NFROMTO) + +static const char *redir_sym[MAX_REDIR - MIN_REDIR + 1] = { + [NTO - MIN_REDIR]= ">", + [NFROM - MIN_REDIR]= "<", + [NTOFD - MIN_REDIR]= ">&", + [NFROMFD - MIN_REDIR]= "<&", + [NCLOBBER - MIN_REDIR]= ">|", + [NAPPEND - MIN_REDIR]= ">>", + [NHERE - MIN_REDIR]= "<<", + [NXHERE - MIN_REDIR]= "<<", + [NFROMTO - MIN_REDIR]= "<>", +}; + +int +outredir(struct output *out, union node *n, int sep) +{ + if (n == NULL) + return 0; + if (n->type < MIN_REDIR || n->type > MAX_REDIR || + redir_sym[n->type - MIN_REDIR] == NULL) + return 0; + + if (sep) + outc(sep, out); + + /* + * ugly, but all redir node types have "fd" in same slot... + * (and code other places assumes it as well) + */ + if ((redir_sym[n->type - MIN_REDIR][0] == '<' && n->nfile.fd != 0) || + (redir_sym[n->type - MIN_REDIR][0] == '>' && n->nfile.fd != 1)) + outfmt(out, "%d", n->nfile.fd); + + outstr(redir_sym[n->type - MIN_REDIR], out); + + switch (n->type) { + case NHERE: + outstr("'...'", out); + break; + case NXHERE: + outstr("...", out); + break; + case NTOFD: + case NFROMFD: + if (n->ndup.dupfd < 0) + outc('-', out); + else + outfmt(out, "%d", n->ndup.dupfd); + break; + default: + outstr(n->nfile.expfname, out); + break; + } + return 1; +} diff --git a/bin/sh/redir.h b/bin/sh/redir.h new file mode 100644 index 0000000..b186bb4 --- /dev/null +++ b/bin/sh/redir.h @@ -0,0 +1,55 @@ +/* $NetBSD: redir.h,v 1.24 2017/06/30 23:01:21 kre Exp $ */ + +/*- + * 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. + * + * @(#)redir.h 8.2 (Berkeley) 5/4/95 + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 0x01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 0x02 /* save the command output in memory */ +#define REDIR_VFORK 0x04 /* running under vfork(2), be careful */ +#define REDIR_KEEP 0x08 /* don't close-on-exec */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(int); +int movefd(int, int); +int to_upper_fd(int); +void register_sh_fd(int, void (*)(int, int)); +void sh_close(int); +struct output; +int outredir(struct output *, union node *, int); + +int max_user_fd; /* highest fd used by user */ diff --git a/bin/sh/sh.1 b/bin/sh/sh.1 new file mode 100644 index 0000000..4630689 --- /dev/null +++ b/bin/sh/sh.1 @@ -0,0 +1,4549 @@ +.\" $NetBSD: sh.1,v 1.217 2019/01/21 14:09:24 kre Exp $ +.\" 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. +.\" +.\" @(#)sh.1 8.6 (Berkeley) 5/4/95 +.\" +.Dd December 12, 2018 +.Dt SH 1 +.\" everything except c o and s (keep them ordered) +.ds flags abCEeFfhIiLmnpquVvXx +.Os +.Sh NAME +.Nm sh +.Nd command interpreter (shell) +.Sh SYNOPSIS +.Nm +.Bk -words +.Op Fl \*[flags] +.Op Cm +\*[flags] +.Ek +.Bk -words +.Op Fl o Ar option_name +.Op Cm +o Ar option_name +.Ek +.Bk -words +.Op Ar command_file Op Ar argument ... +.Ek +.Nm +.Fl c +.Bk -words +.Op Fl s +.Op Fl \*[flags] +.Op Cm +\*[flags] +.Ek +.Bk -words +.Op Fl o Ar option_name +.Op Cm +o Ar option_name +.Ek +.Bk -words +.Ar command_string +.Op Ar command_name Op Ar argument ... +.Ek +.Nm +.Fl s +.Bk -words +.Op Fl \*[flags] +.Op Cm +\*[flags] +.Ek +.Bk -words +.Op Fl o Ar option_name +.Op Cm +o Ar option_name +.Ek +.Bk -words +.Op Ar argument ... +.Ek +.Sh DESCRIPTION +.Nm +is the standard command interpreter for the system. +The current version of +.Nm +is in the process of being changed to conform more closely to the +POSIX 1003.2 and 1003.2a specifications for the shell. +This version has many +features which make it appear similar in some respects to the Korn shell, +but it is not a Korn shell clone (see +.Xr ksh 1 ) . +This man page is not intended +to be a tutorial or a complete specification of the shell. +.Ss Overview +The shell is a command that reads lines from either a file or the +terminal, interprets them, and generally executes other commands. +A shell is the program that is running when a user logs into the system. +(Users can select which shell is executed for them at login with the +.Xr chsh 1 +command). +The shell implements a language that has flow control +constructs, a macro facility that provides a variety of features in +addition to data storage, along with built in history and line editing +capabilities. +It incorporates many features to aid interactive use and +has the advantage that the interpretative language is common to both +interactive and non-interactive use (shell scripts). +That is, commands +can be typed directly to the running shell or can be put into a file and +the file can be executed directly by the shell. +.Ss Invocation +If no arguments are present and if the standard input, +and standard error output, of the shell +are connected to a terminal (or terminals, or if the +.Fl i +flag is set), +and the +.Fl c +option is not present, the shell is considered an interactive shell. +An interactive shell generally prompts before each command and handles +programming and command errors differently (as described below). +When first starting, +the shell inspects argument 0, and if it begins with a dash +.Sq - , +the shell is also considered +a login shell. +This is normally done automatically by the system +when the user first logs in. +A login shell first reads commands +from the files +.Pa /etc/profile +and +.Pa .profile +in the user's home directory +.Pq \&$HOME , +if they exist. +If the environment variable +.Ev ENV +is set on entry to a shell, +or is set in the +.Pa .profile +of a login shell, +and either the shell is interactive, or the +.Cm posix +option is not set, +the shell then performs parameter and arithmetic +expansion on the value of +.Ev ENV , +(these are described later) +and then reads commands from the file name that results. +If +.Ev ENV +contains a command substitution, or one of the +other expansions fails, or if there are no expansions +to expand, the value of +.Ev ENV +is used as the file name. +.Pp +Therefore, a user should place commands that are to be executed only at +login time in the +.Pa .profile +file, and commands that are executed for every shell inside the +.Ev ENV +file. +To set the +.Ev ENV +variable to some file, place the following line in your +.Pa .profile +of your home directory +.Pp +.Dl ENV=$HOME/.shinit; export ENV +.Pp +substituting for +.Dq .shinit +any filename you wish. +Since the +.Ev ENV +file can be read for every invocation of the shell, including shell scripts +and non-interactive shells, the following paradigm is useful for +restricting commands in the +.Ev ENV +file to interactive invocations. +Place commands within the +.Dq Ic case +and +.Dq Ic esac +below (these commands are described later): +.Bd -literal -offset indent +case $- in *i*) + # commands for interactive use only + ... +esac +.Ed +.Pp +If command line arguments besides the options have been specified, and +neither +.Fl c +nor +.Fl s +was given, then the shell treats the first argument +as the name of a file from which to read commands (a shell script). +This also becomes +.Li $0 +and the remaining arguments are set as the +positional parameters of the shell +.Li ( $1 , $2 , +etc). +Otherwise, if +.Fl c +was given, then the first argument, which must exist, +is taken to be a string of +.Nm +commands to execute. +Then if any additional arguments follow the command string, +those arguments become +.Li $0 , $1 , +\&... +Otherwise, if additional arguments were given +(which implies that +.Fl s +was set) +those arguments become +.Li $1 , $2 , +\&... +If +.Li $0 +has not been set by the preceding processing, it +will be set to +.Va argv\^ Ns [ 0 ] +as passed to the shell, which will +usually be the name of the shell itself. +If +.Fl s +was given, or if neither +.Fl c +nor any additional (non-option) arguments were present, +the shell reads commands from its standard input. +.\" +.\" +.Ss Argument List Processing +.\" +Currently, all of the single letter options that can meaningfully +be set using the +.Ic set +built-in, have a corresponding name +that can be used as an argument to the +.Fl o +option. +The +.Ic set Fl o +name is provided next to the single letter option in +the description below. +Some options have only a long name, they are described after +the flag options, they are used with +.Fl o +or +.Cm +o +only, either on the command line, or with the +.Ic set +built-in command. +Other options described are for the command line only. +Specifying a dash +.Dq Cm \- +turns the option on, while using a plus +.Dq Cm + +disables the option. +The following options can be set from the command line and, +unless otherwise stated, with the +.Ic set +built-in (described later). +.\" +.\" strlen("quietprofile") == strlen("local_lineno"): pick the latter +.\" to give the indent as the _ in local_lineno, and the fi ligature in +.\" quietprofile combine to make "local_lineno' slightly wider when printed +.\" (in italics) in a variable width font. +.Bl -tag -width ".Fl L Em local_lineno" -offset indent +.\" +.It Fl a Em allexport +Automatically export any variable to which a value is assigned +while this flag is set, unless the variable has been marked as +not for export. +.It Fl b Em notify +Enable asynchronous notification of background job completion. +(Not implemented.) +.It Fl C Em noclobber +Don't overwrite existing files with +.Dq > . +.It Fl c +Read commands from the +.Ar command_string +operand instead of, or in addition to, from the standard input. +Special parameter +.Dv 0 \" $0 (comments like this for searching sources only) +will be set from the +.Ar command_name +operand if given, and the positional parameters +.Dv ( 1 , 2 , +etc.) +set from the remaining argument operands, if any. +.Fl c +is only available at invocation, it cannot be +.Ic set , +and there is no form using +.Dq Cm \&+ . +.It Fl E Em emacs +Enable the built-in emacs style +command line editor (disables +.Fl V +if it has been set). +(See the +.Sx Command Line Editing +section below.) +.It Fl e Em errexit +If not interactive, exit immediately if any untested command fails. +If interactive, and an untested command fails, +cease all processing of the current command and return to +prompt for a new command. +The exit status of a command is considered to be +explicitly tested if the command is used to control an +.Ic if , +.Ic elif , +.Ic while , +or +.Ic until , +or if the command is the left hand operand of an +.Dq && +or +.Dq || +operator, +or if it is a pipeline (or simple command) preceded by the +.Dq \&! +operator. +With pipelines, only the status of the entire pipeline +(indicated by the last command it contains) +is tested when +.Fl e +is set to determine if the shell should exit. +.It Fl F Em fork +Cause the shell to always use +.Xr fork 2 +instead of attempting +.Xr vfork 2 +when it needs to create a new process. +This should normally have no visible effect, +but can slow execution. +The +.Nm +can be compiled to always use +.Xr fork 2 +in which case altering the +.Fl F +flag has no effect. +.It Fl f Em noglob +Disable pathname expansion. +.It Fl h Em trackall +Functions defined while this option is set will have paths bound to +commands to be executed by the function at the time of the definition. +When off when a function is defined, +the file system is searched for commands each time the function is invoked. +(Not implemented.) +.It Fl I Em ignoreeof +Ignore EOFs from input when interactive. +(After a large number of consecutive EOFs the shell will exit anyway.) +.It Fl i Em interactive +Force the shell to behave interactively. +.It Fl L Em local_lineno +When set, before a function is defined, +causes the variable +.Dv LINENO +when used within the function, +to refer to the line number defined such that +first line of the function is line 1. +When reset, +.Dv LINENO +in a function refers to the line number within the file +within which the definition of the function occurs. +This option defaults to +.Dq on +in this shell. +For more details see the section +.Sx LINENO +below. +.It Fl m Em monitor +Turn on job control (set automatically when interactive). +.It Fl n Em noexec +Read and parse commands, but do not execute them. +This is useful for checking the syntax of shell scripts. +If +.Fl n +becomes set in an interactive shell, it will automatically be +cleared just before the next time the command line prompt +.Pq Ev PS1 +is written. +.It Fl p Em nopriv +Do not attempt to reset effective UID if it does not match UID. +This is not set by default to help avoid incorrect usage by setuid +root programs via +.Xr system 3 +or +.Xr popen 3 . +.It Fl q Em quietprofile +If the +.Fl v +or +.Fl x +options have been set, do not apply them when reading +initialization files, these being +.Pa /etc/profile , +.Pa .profile , +and the file specified by the +.Ev ENV +environment variable. +.It Fl s Em stdin +Read commands from standard input (set automatically if +neither +.Fl c +nor file arguments are present). +If after processing a command_string with the +.Fl c +option, the shell has not exited, and the +.Fl s +option is set, it will continue reading more commands from standard input. +This option has no effect when set or reset after the shell has +already started reading from the command_file, or from standard input. +Note that the +.Fl s +flag being set does not cause the shell to be interactive. +.It Fl u Em nounset +Write a message to standard error when attempting to obtain a +value from a variable that is not set, +and if the shell is not interactive, exit immediately. +For interactive shells, instead return immediately to the command prompt +and read the next command. +Note that expansions (described later, see +.Sx Word Expansions +below) using the +.Sq \&+ , +.Sq \&\- , +.Sq \&= , +or +.Sq \&? +operators test if the variable is set, before attempting to +obtain its value, and hence are unaffected by +.Fl u . +.It Fl V Em vi +Enable the built-in +.Xr vi 1 +command line editor (disables +.Fl E +if it has been set). +(See the +.Sx Command Line Editing +section below.) +.It Fl v Em verbose +The shell writes its input to standard error as it is read. +Useful for debugging. +.It Fl X Em xlock +Cause output from the +.Ic xtrace +.Pq Fl x +option to be sent to standard error as it exists when the +.Fl X +option is enabled (regardless of its previous state.) +For example: +.Bd -literal -compact + set -X 2>/tmp/trace-file +.Ed +will arrange for tracing output to be sent to the file named, +instead of wherever it was previously being sent, +until the X option is set again, or cleared. +.Pp +Each change (set or clear) to +.Fl X +is also performed upon +.Fl x , +but not the converse. +.It Fl x Em xtrace +Write each command to standard error (preceded by the expanded value of +.Li $PS4 ) +before it is executed. +Unless +.Fl X +is set, +.Dq "standard error" +means that which existed immediately before any redirections to +be applied to the command are performed. +Useful for debugging. +.It "\ \ " Em cdprint +Make an interactive shell always print the new directory name when +changed by the +.Ic cd +command. +In a non-interactive shell this option has no effect. +.It "\ \ " Em nolog +Prevent the entry of function definitions into the command history (see +.Ic fc +in the +.Sx Built-ins +section.) +(Not implemented.) +.It "\ \ " Em pipefail +If set when a pipeline is created, +the way the exit status of the pipeline is determined +is altered. +See +.Sx Pipelines +below for the details. +.It "\ \ " Em posix +Enables closer adherence to the POSIX shell standard. +This option will default set at shell startup if the +environment variable +.Ev POSIXLY_CORRECT +is present. +That can be overridden (set or reset) by the +.Fl o +option on the command line. +Currently this option controls whether (!posix) or not (posix) +the file given by the +.Ev ENV +variable is read at startup by a non-interactive shell. +It also controls whether file descriptors greater than 2 +opened using the +.Ic exec +built-in command are passed on to utilities executed +.Dq ( yes +in posix mode), +whether a colon (:) terminates the user name in tilde (~) expansions +other than in assignment statements +.Dq ( no +in posix mode), +the format of the output of the +.Ic kill Fl l +command, where posix mode causes the names of the signals +be separated by either a single space or newline, and where otherwise +sufficient spaces are inserted to generate nice looking columns, +and whether the shell treats +an empty brace-list compound statement as a syntax error +(expected by POSIX) or permits it. +Such statements +.Dq "{\ }" +can be useful when defining dummy functions. +Lastly, in posix mode, only one +.Dq \&! +is permitted before a pipeline. +.It "\ \ " Em promptcmds +Allows command substitutions (as well as parameter +and arithmetic expansions, which are always performed) +upon the prompt strings +.Ev PS1 , +.Ev PS2 , +and +.Ev PS4 +each time, before they are output. +This option should not be set until after the prompts +have been set (or verified) to avoid accidentally importing +unwanted command substitutions from the environment. +.It "\ \ " Em tabcomplete +Enables filename completion in the command line editor. +Typing a tab character will extend the current input word to match a +filename. +If more than one filename matches it is only extended to be the common prefix. +Typing a second tab character will list all the matching names. +One of the editing modes, either +.Fl E +or +.Fl V , +must be enabled for this to work. +.El +.Ss Lexical Structure +The shell reads input in terms of lines from a file and breaks it up into +words at whitespace (blanks and tabs), and at certain sequences of +characters that are special to the shell called +.Dq operators . +There are two types of operators: control operators and redirection +operators (their meaning is discussed later). +The following is a list of operators: +.Bl -ohang -offset indent +.It "Control operators:" +.Dl & && \&( \&) \&; ;; ;& \&| || <newline> +.It "Redirection operators:" +.Dl < > >| << >> <& >& <<- <> +.El +.Ss Quoting +Quoting is used to remove the special meaning of certain characters or +words to the shell, such as operators, whitespace, or keywords. +There are four types of quoting: +matched single quotes, +matched double quotes, +backslash, +and +dollar preceding matched single quotes (enhanced C style strings.) +.Ss Backslash +An unquoted backslash preserves the literal meaning of the following +character, with the exception of +.Aq newline . +An unquoted backslash preceding a +.Aq newline +is treated as a line continuation, the two characters are simply removed. +.Ss Single Quotes +Enclosing characters in single quotes preserves the literal meaning of all +the characters (except single quotes, making it impossible to put +single quotes in a single-quoted string). +.Ss Double Quotes +Enclosing characters within double quotes preserves the literal +meaning of all characters except dollar sign +.Pq Li \&$ , +backquote +.Pq Li \&` , +and backslash +.Pq Li \e . +The backslash inside double quotes is historically weird, and serves to +quote only the following characters (and these not in all contexts): +.Dl $ ` \*q \e <newline> , +where a backslash newline is a line continuation as above. +Otherwise it remains literal. +.\" +.\" +.Ss Dollar Single Quotes ( Li \&$'...' ) +.\" +.Bd -filled -offset indent +.Bf Em +Note: this form of quoting is still somewhat experimental, +and yet to be included in the POSIX standard. +This implementation is based upon the current proposals for +standardization, and is subject to change should the eventual +adopted text differ. +.Ef +.Ed +.Pp +Enclosing characters in a matched pair of single quotes, with the +first immediately preceded by an unquoted dollar sign +.Pq Li \&$ +provides a quoting mechanism similar to single quotes, except +that within the sequence of characters, any backslash +.Pq Li \e , +is an escape character, which causes the following character to +be treated specially. +Only a subset of the characters that can occur in the string +are defined after a backslash, others are reserved for future +definition, and currently generate a syntax error if used. +The escape sequences are modeled after the similar sequences +in strings in the C programming language, with some extensions. +.Pp +The following characters are treated literally when following +the escape character (backslash): +.Dl \e \&' \(dq +The sequence +.Dq Li \e\e +allows the escape character (backslash) to appear in the string literally. +.Dq Li \e' +allows a single quote character into the string, such an +escaped single quote does not terminate the quoted string. +.Dq Li \e\(dq +is for compatibility with C strings, the double quote has +no special meaning in a shell C-style string, +and does not need to be escaped, but may be. +.Pp +A newline following the escape character is treated as a line continuation, +like the same sequence in a double quoted string, +or when not quoted \(en +the two characters, the backslash escape and the newline, +are removed from the input string. +.Pp +The following characters, when escaped, are converted in a +manner similar to the way they would be in a string in the C language: +.Dl a b e f n r t v +An escaped +.Sq a +generates an alert (or +.Sq BEL ) +character, that is, control-G, or 0x07. +In a similar way, +.Sq b +is backspace (0x08), +.Sq e +(an extension to C) is escape (0x1B), +.Sq f +is form feed (0x0C), +.Sq n +is newline (or line feed, 0x0A), +.Sq r +is return (0x0D), +.Sq t +is horizontal tab (0x09), +and +.Sq v +is vertical tab (0x13). +.Pp +In addition to those there are 5 forms that need additional +data, which is obtained from the subsequent characters. +An escape +.Pq Li \e +followed by one, two or three, octal digits +.Po So 0 Sc Ns \&.. Ns So 7 Sc Ns Pc +is processed to form an 8 bit character value. +If only one or two digits are present, the following +character must be something other than an octal digit. +It is safest to always use all 3 digits, with leading +zeros if needed. +If all three digits are present, the first must be one of +.So 0 Sc Ns \&.. Ns So 3 Sc . +.Pp +An escape followed by +.Sq x +(lower case only) can be followed by one or two +hexadecimal digits +.Po So 0 Sc Ns \&.. Ns So 9 Sc , So A Sc Ns \&.. Ns So F Sc , or So a Sc Ns \&.. Ns So f Sc . Pc +As with octal, if only one hex digit is present, the following +character must be something other than a hex digit, +so always giving 2 hex digits is best. +However, unlike octal, it is unspecified in the standard +how many hex digits can be consumed. +This +.Nm +takes at most two, but other shells will continue consuming +characters as long as they remain valid hex digits. +Consequently, users should ensure that the character +following the hex escape sequence is something other than +a hex digit. +One way to achieve this is to end the +.Li $'...' +string immediately +after the final hex digit, and then, immediately start +another, so +.Dl \&$'\ex33'$'4...' +always gives the character with value 0x33 +.Pq Sq 3 , +followed by the character +.Sq 4 , +whereas +.Dl \&$'\ex334' +in some other shells would be the hex value 0x334 (10, or more, bits). +.Pp +There are two escape sequences beginning with +.Sq Li \eu +or +.Sq Li \eU . +The former is followed by from 1 to 4 hex digits, the latter by +from 1 to 8 hex digits. +Leading zeros can be used to pad the sequences to the maximum +permitted length, to avoid any possible ambiguity problem with +the following character, and because there are some shells that +insist on exactly 4 (or 8) hex digits. +These sequences are evaluated to form the value of a Unicode code +point, which is then encoded into UTF-8 form, and entered into the +string. +(The code point should be converted to the appropriate +code point value for the corresponding character in the character +set given by the current locale, or perhaps the locale in use +when the shell was started, but is not... currently.) +Not all values that are possible to write are valid, values that +specify (known) invalid Unicode code points will be rejected, or +simply produce +.Sq \&? . +.Pp +Lastly, as another addition to what is available in C, the escape +character (backslash), followed by +.Sq c +(lower case only) followed by one additional character, which must +be an alphabetic character (a letter), or one of the following: +.Dl \&@ \&[ \&\e \&] \&^ \&_ \&? +Other than +.Sq Li \ec? +the value obtained is the least significant 5 bits of the +ASCII value of the character following the +.Sq Li \ec +escape sequence. +That is what is commonly known as the +.Dq control +character obtained from the given character. +The escape sequence +.Sq Li \ec? +yields the ASCII DEL character (0x7F). +Note that to obtain the ASCII FS character (0x1C) this way, +.Pq "that is control-\e" +the trailing +.Sq Li \e +must be escaped itself, and so for this one case, the full +escape sequence is +.Dq Li \ec\e\e . +The sequence +.Dq Li \ec\e Ns Ar X\^ +where +.Sq Ar X\^ +is some character other than +.Sq Li \e +is reserved for future use, its meaning is unspecified. +In this +.Nm +an error is generated. +.Pp +If any of the preceding escape sequences generate the value +.Sq \e0 +(a NUL character) that character, and all that follow in the same +.Li $'...' +string, are omitted from the resulting word. +.Pp +After the +.Li $'...' +string has had any included escape sequences +converted, it is treated as if it had been a single quoted string. +.\" +.\" +.Ss Reserved Words +.\" +Reserved words are words that have special meaning to the +shell and are recognized at the beginning of a line and +after a control operator. +The following are reserved words: +.Bl -column while while while while -offset indent +.It Ic \&! Ta Ic \&{ Ta Ic \&} Ta Ic case +.It Ic do Ta Ic done Ta Ic elif Ta Ic else +.It Ic esac Ta Ic fi Ta Ic for Ta Ic if +.It Ic in Ta Ic then Ta Ic until Ta Ic while +.El +.Pp +Their meanings are discussed later. +.\" +.\" +.Ss Aliases +.\" +An alias is a name and corresponding value set using the +.Ic alias +built-in command. +Whenever a reserved word (see above) may occur, +and after checking for reserved words, the shell +checks the word to see if it matches an alias. +If it does, it replaces it in the input stream with its value. +For example, if there is an alias called +.Dq lf +with the value +.Dq "ls -F" , +then the input: +.Pp +.Dl lf foobar Aq return +.Pp +would become +.Pp +.Dl ls -F foobar Aq return +.Pp +Aliases provide a convenient way for naive users to create shorthands for +commands without having to learn how to create functions with arguments. +They can also be used to create lexically obscure code. +This use is strongly discouraged. +.\" +.Ss Commands +.\" +The shell interprets the words it reads according to a language, the +specification of which is outside the scope of this man page (refer to the +BNF in the POSIX 1003.2 document). +Essentially though, a line is read and if the first +word of the line (or after a control operator) is not a reserved word, +then the shell has recognized a simple command. +Otherwise, a complex +command or some other special construct may have been recognized. +.\" +.\" +.Ss Simple Commands +.\" +If a simple command has been recognized, the shell performs +the following actions: +.Bl -enum -offset indent +.It +Leading words of the form +.Dq Ar name Ns Li = Ns Ar value +are stripped off, the value is expanded, as described below, +and the results are assigned to the environment of the simple command. +Redirection operators and their arguments (as described below) are +stripped off and saved for processing in step 3 below. +.It +The remaining words are expanded as described in the +.Sx Word Expansions +section below. +The first remaining word is considered the command name and the +command is located. +Any remaining words are considered the arguments of the command. +If no command name resulted, then the +.Dq Ar name Ns Li = Ns Ar value +variable assignments recognized in item 1 affect the current shell. +.It +Redirections are performed, from first to last, in the order given, +as described in the next section. +.El +.\" +.\" +.Ss Redirections +.\" +Redirections are used to change where a command reads its input or sends +its output. +In general, redirections open, close, or duplicate an +existing reference to a file. +The overall format used for redirection is: +.Pp +.Dl Oo Ar n Oc Ns Va redir-op Ar file +.Pp +where +.Va redir-op +is one of the redirection operators mentioned previously. +The following is a list of the possible redirections. +The +.Op Ar n +is an optional number, as in +.Sq Li 3 +(not +.Li [3] ) , +that refers to a file descriptor. +If present it must occur immediately before the redirection +operator, with no intervening white space, and becomes a +part of that operator. +.Bl -tag -width aaabsfiles -offset indent +.It Oo Ar n Oc Ns Ic > Ar file +Redirect standard output (or +.Ar n ) +to +.Ar file . +.It Oo Ar n Oc Ns Ic >| Ar file +The same, but override the +.Fl C +option. +.It Oo Ar n Oc Ns Ic >> Ar file +Append standard output (or +.Ar n ) +to +.Ar file . +.It Oo Ar n Oc Ns Ic < Ar file +Redirect standard input (or +.Ar n ) +from +.Ar file . +.It Oo Ar n1 Oc Ns Ic <& Ns Ar n2 +Duplicate standard input (or +.Ar n1 ) +from file descriptor +.Ar n2 . +.Ar n2 +is expanded if not a digit string, the result must be a number. +.It Oo Ar n Oc Ns Ic <&- +Close standard input (or +.Ar n ) . +.It Oo Ar n1 Oc Ns Ic >& Ns Ar n2 +Duplicate standard output (or +.Ar n1 ) +to +.Ar n2 . +.It Oo Ar n Oc Ns Ic >&- +Close standard output (or +.Ar n ) . +.It Oo Ar n Oc Ns Ic <> Ar file +Open +.Ar file +for reading and writing on standard input (or +.Ar n ) . +.El +.Pp +The following redirection is often called a +.Dq here-document . +.Bd -unfilled -offset indent +.Oo Ar n Oc Ns Ic << Ar delimiter +.Li \&... here-doc-text ... +.Ar delimiter +.Ed +.Pp +The +.Dq here-doc-text +starts immediately after the next unquoted newline character following +the here-document redirection operator. +If there is more than one here-document redirection on the same +line, then the text for the first (from left to right) is read +first, and subsequent here-doc-text for later here-document redirections +follows immediately after, until all such redirections have been +processed. +.Pp +All the text on successive lines up to the delimiter, +which must appear on a line by itself, with nothing other +than an immediately following newline, is +saved away and made available to the command on standard input, or file +descriptor n if it is specified. +If the delimiter as specified on the initial line is +quoted, then the here-doc-text is treated literally; otherwise, the text is +treated much like a double quoted string, except that +.Sq Li \(dq +characters have no special meaning, and are not escaped by +.Sq Li \&\e , +and is subjected to parameter expansion, command substitution, and arithmetic +expansion as described in the +.Sx Word Expansions +section below. +If the operator is +.Ic <<- +instead of +.Ic << , +then leading tabs in all lines in the here-doc-text, including before the +end delimiter, are stripped. +If the delimiter is not quoted, lines in here-doc-text that end with +an unquoted +.Li \e +are joined to the following line, the +.Li \e +and following +newline are simply removed while reading the here-document, +which thus guarantees +that neither of those lines can be the end delimiter. +.Pp +It is a syntax error for the end of the input file (or string) to be +reached before the delimiter is encountered. +.\" +.\" +.Ss Search and Execution +.\" +There are three types of commands: shell functions, built-in commands, and +normal programs \(em and the command is searched for (by name) in that order. +A command that contains a slash +.Sq \&/ +in its name is always a normal program. +They each are executed in a different way. +.Pp +When a shell function is executed, all of the shell positional parameters +(note: excluding +.Li 0 , \" $0 +which is a special, not positional, parameter, and remains unchanged) +are set to the arguments of the shell function. +The variables which are explicitly placed in the environment of +the command (by placing assignments to them before the function name) are +made local to the function and are set to the values given, +and exported for the benefit of programs executed with the function. +Then the command given in the function definition is executed. +The positional parameters, and local variables, are restored to +their original values when the command completes. +This all occurs within the current shell, and the function +can alter variables, or other settings, of the shell, but +not the positional parameters nor their related special parameters. +.Pp +Shell built-ins are executed internally to the shell, without spawning a +new process. +.Pp +Otherwise, if the command name doesn't match a function or built-in, the +command is searched for as a normal program in the file system (as +described in the next section). +When a normal program is executed, the shell runs the program, +passing the arguments and the environment to the program. +If the program is not a normal executable file, and if it does +not begin with the +.Dq magic number +whose ASCII representation is +.Dq Li "#!" , +so +.Xr execve 2 +returns +.Er ENOEXEC +then) the shell will interpret the program in a sub-shell. +The child shell will reinitialize itself in this case, +so that the effect will be as if a +new shell had been invoked to handle the ad-hoc shell script, except that +the location of hashed commands located in the parent shell will be +remembered by the child. +.Pp +Note that previous versions of this document and the source code itself +misleadingly and sporadically refer to a shell script without a magic +number as a +.Dq shell procedure . +.\" +.\" +.Ss Path Search +.\" +When locating a command, the shell first looks to see if it has a shell +function by that name. +Then it looks for a built-in command by that name. +If a built-in command is not found, one of two things happen: +.Bl -enum +.It +Command names containing a slash are simply executed without performing +any searches. +.It +Otherwise, the shell searches each entry in +.Ev PATH +in turn for the command. +The value of the +.Ev PATH +variable should be a series of entries separated by colons. +Each entry consists of a directory name. +The current directory may be indicated +implicitly by an empty directory name, or explicitly by a single period. +If a directory searched contains an executable file with the same +name as the command given, +the search terminates, and that program is executed. +.El +.Ss Command Exit Status +Each command has an exit status that can influence the behavior +of other shell commands. +The paradigm is that a command exits +with zero in normal cases, or to indicate success, +and non-zero for failure, +error, or a false indication. +The man page for each command +should indicate the various exit codes and what they mean. +Additionally, the built-in commands return exit codes, as does +an executed shell function. +.Pp +If a command consists entirely of variable assignments then the +exit status of the command is that of the last command substitution +if any, otherwise 0. +.Pp +If redirections are present, and any fail to be correctly performed, +any command present is not executed, and an exit status of 2 +is returned. +.Ss Complex Commands +Complex commands are combinations of simple commands with control +operators or reserved words, together creating a larger complex command. +Overall, a shell program is a: +.Bl -tag -width XpipelineX +.It list +Which is a sequence of one or more AND-OR lists. +.It "AND-OR list" +is a sequence of one or more pipelines. +.It pipeline +is a sequence of one or more commands. +.It command +is one of a simple command, a compound command, or a function definition. +.It "simple command" +has been explained above, and is the basic building block. +.It "compound command" +provides mechanisms to group lists to achieve different effects. +.It "function definition" +allows new simple commands to be created as groupings of existing commands. +.El +.Pp +Unless otherwise stated, the exit status of a list +is that of the last simple command executed by the list. +.\" +.\" +.Ss Pipelines +.\" +A pipeline is a sequence of one or more commands separated +by the control operator +.Sq Ic \(ba , +and optionally preceded by the +.Dq Ic \&! +reserved word. +Note that +.Sq Ic \(ba +is an operator, and so is recognized anywhere it appears unquoted, +it does not require surrounding white space or other syntax elements. +On the other hand +.Dq Ic \&! +being a reserved word, must be separated from adjacent words by +white space (or other operators, perhaps redirects) and is only +recognized as the reserved word when it appears in a command word +position (such as at the beginning of a pipeline.) +.Pp +The standard output of all but +the last command in the sequence is connected to the standard input +of the next command. +The standard output of the last +command is inherited from the shell, as usual, +as is the standard input of the first command. +.Pp +The format for a pipeline is: +.Pp +.Dl [!] command1 Op Li \&| command2 No ... +.Pp +The standard output of command1 is connected to the standard input of +command2. +The standard input, standard output, or both of each command is +considered to be assigned by the pipeline before any redirection specified +by redirection operators that are part of the command are performed. +.Pp +If the pipeline is not in the background (discussed later), the shell +waits for all commands to complete. +.Pp +The commands in a pipeline can either be simple commands, +or one of the compound commands described below. +The simplest case of a pipeline is a single simple command. +.Pp +If the +.Ic pipefail +option was set when a pipeline was started, +the pipeline status is the status of +the last (lexically last, i.e.: rightmost) command in the +pipeline to exit with non-zero exit status, or zero, if, +and only if, all commands in the pipeline exited with a status of zero. +If the +.Ic pipefail +option was not set, which is the default state, +the pipeline status is the exit +status of the last (rightmost) command in the pipeline, +and the exit status of any other commands in the pipeline is ignored. +.Pp +If the reserved word +.Dq Ic \&! +precedes the pipeline, the exit status +becomes the logical NOT of the pipeline status as determined above. +That is, if the pipeline status is zero, the exit status is 1; +if the pipeline status is other than zero, the exit status is zero. +If there is no +.Dq Ic \&! +reserved word, the pipeline status becomes the exit status. +.Pp +Because pipeline assignment of standard input or standard output or both +takes place before redirection, it can be modified by redirection. +For example: +.Pp +.Dl $ command1 2>&1 \&| command2 +.Pp +sends both the standard output and standard error of command1 +to the standard input of command2. +.Pp +Note that unlike some other shells, each process in the pipeline is a +child of the invoking shell (unless it is a shell built-in, in which case +it executes in the current shell \(em but any effect it has on the +environment is wiped). +.Pp +A pipeline is a simple case of an AND-OR-list (described below.) +A +.Li \&; +or +.Aq newline +terminator causes the preceding pipeline, or more generally, +the preceding AND-OR-list to be executed sequentially; +that is, the shell executes the commands, and waits for them +to finish before proceeding to following commands. +An +.Li \&& +terminator causes asynchronous (background) execution +of the preceding AND-OR-list (see the next paragraph below). +The exit status of an asynchronous AND-OR-list is zero. +The actual status of the commands, +after they have completed, +can be obtained using the +.Ic wait +built-in command described later. +.\" +.\" +.Ss Background Commands \(em Ic \&& +.\" +If a command, pipeline, or AND-OR-list +is terminated by the control operator ampersand +.Pq Li \&& , +the +shell executes the command asynchronously \(em that is, the shell does not +wait for the command to finish before executing the next command. +.Pp +The format for running a command in background is: +.Pp +.Dl command1 & Op Li command2 & No ... +.Pp +If the shell is not interactive, the standard input of an asynchronous +command is set to +.Pa /dev/null . +The process identifier of the most recent command started in the +background can be obtained from the value of the special parameter +.Dq Dv \&! \" $! +(see +.Sx Special Parameters ) +provided it is accessed before the next asynchronous command is started. +.\" +.\" +.Ss Lists \(em Generally Speaking +.\" +A list is a sequence of one or more commands separated by newlines, +semicolons, or ampersands, and optionally terminated by one of these three +characters. +A shell program, which includes the commands given to an +interactive shell, is a list. +Each command in such a list is executed when it is fully parsed. +Another use of a list is as a complete-command, +which is parsed in its entirety, and then later the commands in +the list are executed only if there were no parsing errors. +.Pp +The commands in a list are executed in the order they are written. +If command is followed by an ampersand, the shell starts the +command and immediately proceeds to the next command; otherwise it waits +for the command to terminate before proceeding to the next one. +A newline is equivalent to a +.Sq Li \&; +when no other operator is present, and the command being input +could syntactically correctly be terminated at the point where +the newline is encountered, otherwise it is just whitespace. +.\" +.\" +.Ss AND-OR Lists (Short-Circuit List Operators) +.\" +.Dq Li \&&& +and +.Dq Li \&|| +are AND-OR list operators. +After executing the commands that precede the +.Dq Li \&&& +the subsequent command is executed +if and only if the exit status of the preceding command(s) is zero. +.Dq Li \&|| +is similar, but executes the subsequent command if and only if the exit status +of the preceding command is nonzero. +If a command is not executed, the exit status remains unchanged +and the following AND-OR list operator (if any) uses that status. +.Dq Li \&&& +and +.Dq Li \&|| +both have the same priority. +Note that these operators are left-associative, so +.Dl true || echo bar && echo baz +writes +.Dq baz +and nothing else. +This is not the way it works in C. +.\" +.\" +.Ss Flow-Control Constructs \(em Ic if , while , until , for , case +.\" +These commands are instances of compound commands. +The syntax of the +.Ic if +command is +.Bd -literal -offset indent +.Ic if Ar list +.Ic then Ar list +.Ic [ elif Ar list +.Ic then Ar list ] No ... +.Ic [ else Ar list ] +.Ic fi +.Ed +.Pp +The first list is executed, and if the exit status of that list is zero, +the list following the +.Ic then +is executed. +Otherwise the list after an +.Ic elif +(if any) is executed and the process repeats. +When no more +.Ic elif +reserved words, and accompanying lists, appear, +the list after the +.Ic else +reserved word, if any, is executed. +.Pp +The syntax of the +.Ic while +command is +.Bd -literal -offset indent +.Ic while Ar list +.Ic do Ar list +.Ic done +.Ed +.Pp +The two lists are executed repeatedly while the exit status of the +first list is zero. +The +.Ic until +command is similar, but has the word +.Ic until +in place of +.Ic while , +which causes it to repeat until the exit status of the first list is zero. +.Pp +The syntax of the +.Ic for +command is +.Bd -literal -offset indent +.Ic for Ar variable Op Ic in Ar word No ... +.Ic do Ar list +.Ic done +.Ed +.Pp +The words are expanded, or +.Li \*q$@\*q +if +.Ic in +(and the following words) is not present, +and then the list is executed repeatedly with the +variable set to each word in turn. +If +.Ic in +appears after the variable, but no words are +present, the list is not executed, and the exit status is zero. +.Ic do +and +.Ic done +may be replaced with +.Sq Ic \&{ +and +.Sq Ic \&} , +but doing so is non-standard and not recommended. +.Pp +The syntax of the +.Ic break +and +.Ic continue +commands is +.Bd -literal -offset indent +.Ic break Op Ar num +.Ic continue Op Ar num +.Ed +.Pp +.Ic break +terminates the +.Ar num +innermost +.Ic for , while , +or +.Ic until +loops. +.Ic continue +breaks execution of the +.Ar num\^ Ns -1 +innermost +.Ic for , while , +or +.Ic until +loops, and then continues with the next iteration of the enclosing loop. +These are implemented as special built-in commands. +The parameter +.Ar num , +if given, must be an unsigned positive integer (greater than zero). +If not given, 1 is used. +.Pp +The syntax of the +.Ic case +command is +.Bd -literal -offset indent +.Ic case Ar word Ic in +.Oo Ic \&( Oc Ar pattern Ns Ic \&) Oo Ar list Oc Ic \&;& +.Oo Ic \&( Oc Ar pattern Ns Ic \&) Oo Ar list Oc Ic \&;; +.No \&... +.Ic esac +.Ed +.Pp +The pattern can actually be one or more patterns (see +.Sx Shell Patterns +described later), separated by +.Dq \(or +characters. +.Pp +.Ar word +is expanded and matched against each +.Ar pattern +in turn, +from first to last, +with each pattern being expanded just before the match is attempted. +When a match is found, pattern comparisons cease, and the associated +.Ar list , +if given, +is evaluated. +If the list is terminated with +.Dq Ic \&;& +execution then falls through to the following list, if any, +without evaluating its pattern, or attempting a match. +When a list terminated with +.Dq Ic \&;; +has been executed, or when +.Ic esac +is reached, execution of the +.Ic case +statement is complete. +The exit status is that of the last command executed +from the last list evaluated, if any, or zero otherwise. +.\" +.\" +.Ss Grouping Commands Together +.\" +Commands may be grouped by writing either +.Dl Ic \&( Ns Ar list Ns Ic \&) +or +.Dl Ic \&{ Ar list Ns Ic \&; \&} +These also form compound commands. +.Pp +Note that while parentheses are operators, and do not require +any extra syntax, braces are reserved words, so the opening brace +must be followed by white space (or some other operator), and the +closing brace must occur in a position where a new command word might +otherwise appear. +.Pp +The first of these executes the commands in a sub-shell. +Built-in commands grouped into a +.Li \&( Ns Ar list Ns \&) +will not affect the current shell. +The second form does not fork another shell so is slightly more efficient, +and allows for commands which do affect the current shell. +Grouping commands together this way allows you to redirect +their output as though they were one program: +.Bd -literal -offset indent +{ echo -n \*qhello \*q ; echo \*qworld\*q ; } > greeting +.Ed +.Pp +Note that +.Dq Ic } +must follow a control operator (here, +.Dq Ic \&; ) +so that it is recognized as a reserved word and not as another command argument. +.\" +.\" +.Ss Functions +.\" +The syntax of a function definition is +.Pp +.Dl Ar name Ns Ic \&() Ar command Op Ar redirect No ... +.Pp +A function definition is an executable statement; when executed it +installs a function named name and returns an exit status of zero. +The command is normally a list enclosed between +.Dq { +and +.Dq } . +The standard syntax also allows the command to be any of the other +compound commands, including a sub-shell, all of which are supported. +As an extension, this shell also allows a simple command +(or even another function definition) to be +used, though users should be aware this is non-standard syntax. +This means that +.Dl l() ls \*q$@\*q +works to make +.Dq l +an alternative name for the +.Ic ls +command. +.Pp +If the optional redirect, (see +.Sx Redirections ) , +which may be of any of the normal forms, +is given, it is applied each time the +function is called. +This means that a simple +.Dq Hello World +function might be written (in the extended syntax) as: +.Bd -literal -offset indent +hello() cat <<EOF +Hello World! +EOF +.Ed +.Pp +To be correctly standards conforming this should be re-written as: +.Bd -literal -offset indent +hello() { cat; } <<EOF +Hello World! +EOF +.Ed +.Pp +Note the distinction between those forms, and +.Bd -literal -offset indent +hello() { cat <<EOF +Hello World! +EOF +\&} +.Ed +.Pp +which reads and processes the here-document +each time the shell executes the function, and which applies +that input only to the cat command, not to any other commands +that might appear in the function. +.Pp +Variables may be declared to be local to a function by using the +.Ic local +command. +This should usually appear as the first statement of a function, +though +.Ic local +is an executable command which can be used anywhere in a function. +See +.Sx Built-ins +below for its definition. +.Pp +The function completes after having executed +.Ar command +with exit status set to the status returned by +.Ar command . +If +.Ar command +is a compound-command +it can use the +.Ic return +command (see +.Sx Built-ins +below) +to finish before completing all of +.Ar command . +.Ss Variables and Parameters +The shell maintains a set of parameters. +A parameter denoted by a name is called a variable. +When starting up, the shell turns all the environment +variables into shell variables, and exports them. +New variables can be set using the form +.Pp +.Dl Ar name Ns Li = Ns Ar value +.Pp +Variables set by the user must have a name consisting solely of +alphabetics, numerics, and underscores \(em the first of which must not be +numeric. +A parameter can also be denoted by a number or a special +character as explained below. +.Ss Positional Parameters +A positional parameter is a parameter denoted by a number (n > 0). +The shell sets these initially to the values of its command line arguments +that follow the name of the shell script. +The +.Ic set +built-in can also be used to set or reset them, and +.Ic shift +can be used to manipulate the list. +.Pp +To refer to the 10th (and later) positional parameters, +the form +.Li \&${ Ns Ar n Ns Li \&} +must be used. +Without the braces, a digit following +.Dq $ +can only refer to one of the first 9 positional parameters, +or the special parameter +.Dv 0 . \" $0 +The word +.Dq Li $10 +is treated identically to +.Dq Li ${1}0 . +.\" +.\" +.Ss Special Parameters +.\" +A special parameter is a parameter denoted by one of the following special +characters. +The value of the parameter is listed next to its character. +.Bl -tag -width thinhyphena +.It Dv * +Expands to the positional parameters, starting from one. +When the +expansion occurs within a double-quoted string it expands to a single +field with the value of each parameter separated by the first character of +the +.Ev IFS +variable, or by a +.Aq space +if +.Ev IFS +is unset. +.It Dv @ \" $@ +Expands to the positional parameters, starting from one. +When the expansion occurs within double quotes, each positional +parameter expands as a separate argument. +If there are no positional parameters, the +expansion of @ generates zero arguments, even when +.Dv $@ +is double-quoted. +What this basically means, for example, is +if +.Li $1 +is +.Dq abc +and +.Li $2 +is +.Dq def\ ghi , +then +.Li \*q$@\*q +expands to +the two arguments: +.Pp +.Sm off +.Dl \*q abc \*q \ \*q def\ ghi \*q +.Sm on +.It Dv # +Expands to the number of positional parameters. +.It Dv \&? +Expands to the exit status of the most recent pipeline. +.It Dv \- No (hyphen, or minus) +Expands to the current option flags (the single-letter +option names concatenated into a string) as specified on +invocation, by the set built-in command, or implicitly +by the shell. +.It Dv $ +Expands to the process ID of the invoked shell. +A sub-shell retains the same value of +.Dv $ +as its parent. +.It Dv \&! +Expands to the process ID of the most recent background +command executed from the current shell. +For a pipeline, the process ID is that of the last command in the pipeline. +If no background commands have yet been started by the shell, then +.Dq Dv \&! +will be unset. +Once set, the value of +.Dq Dv \&! +will be retained until another background command is started. +.It Dv 0 No (zero) \" $0 +Expands to the name of the shell or shell script. +.El +.\" +.\" +.Ss Word Expansions +.\" +This section describes the various expansions that are performed on words. +Not all expansions are performed on every word, as explained later. +.Pp +Tilde expansions, parameter expansions, command substitutions, arithmetic +expansions, and quote removals that occur within a single word expand to a +single field. +It is only field splitting or pathname expansion that can +create multiple fields from a single word. +The single exception to this +rule is the expansion of the special parameter +.Dv @ \" $@ +within double quotes, as was described above. +.Pp +The order of word expansion is: +.Bl -enum +.It +Tilde Expansion, Parameter Expansion, Command Substitution, +Arithmetic Expansion (these all occur at the same time). +.It +Field Splitting is performed on fields +generated by step (1) unless the +.Ev IFS +variable is null. +.It +Pathname Expansion (unless set +.Fl f +is in effect). +.It +Quote Removal. +.El +.Pp +The $ character is used to introduce parameter expansion, command +substitution, or arithmetic evaluation. +.Ss Tilde Expansion (substituting a user's home directory) +A word beginning with an unquoted tilde character (~) is +subjected to tilde expansion. +Provided all of the subsequent characters in the word are unquoted +up to an unquoted slash (/) +or when in an assignment or not in posix mode, an unquoted colon (:), +or if neither of those appear, the end of the word, +they are treated as a user name +and are replaced with the pathname of the named user's home directory. +If the user name is missing (as in +.Pa ~/foobar ) , +the tilde is replaced with the value of the +.Dv HOME +variable (the current user's home directory). +.Pp +In variable assignments, +an unquoted tilde immediately after the assignment operator (=), and +each unquoted tilde immediately after an unquoted colon in the value +to be assigned is also subject to tilde expansion as just stated. +.\" +.\" +.Ss Parameter Expansion +.\" +The format for parameter expansion is as follows: +.Pp +.Dl ${ Ns Ar expression Ns Li } +.Pp +where +.Ar expression +consists of all characters until the matching +.Sq Li } . +Any +.Sq Li } +escaped by a backslash or within a quoted string, and characters in +embedded arithmetic expansions, command substitutions, and variable +expansions, are not examined in determining the matching +.Sq Li } . +.Pp +The simplest form for parameter expansion is: +.Pp +.Dl ${ Ns Ar parameter Ns Li } +.Pp +The value, if any, of +.Ar parameter +is substituted. +.Pp +The parameter name or symbol can be enclosed in braces, +which are optional in this simple case, +except for positional parameters with more than one digit or +when parameter is followed by a character that could be interpreted as +part of the name. +If a parameter expansion occurs inside double quotes: +.Bl -enum +.It +pathname expansion is not performed on the results of the expansion; +.It +field splitting is not performed on the results of the +expansion, with the exception of the special rules for +.Dv @ . \" $@ +.El +.Pp +In addition, a parameter expansion where braces are used, +can be modified by using one of the following formats. +If the +.Sq Ic \&: +is omitted in the following modifiers, then the test in the expansion +applies only to unset parameters, not null ones. +.Bl -tag -width aaparameterwordaaaaa +.It Li ${ Ns Ar parameter Ns Ic :- Ns Ar word Ns Li } +.Sy Use Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is substituted; otherwise, the value of +.Ar parameter +is substituted. +.It Li ${ Ns Ar parameter Ns Ic := Ns Ar word Ns Li } +.Sy Assign Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is assigned to +.Ar parameter . +In all cases, the final value of +.Ar parameter +is substituted. +Only variables, not positional parameters or special +parameters, can be assigned in this way. +.It Li ${ Ns Ar parameter Ns Ic :? Ns Oo Ar word\^ Oc Ns Li } +.Sy Indicate Error if Null or Unset. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +(or a message indicating it is unset if +.Ar word +is omitted) +is written to standard error and a non-interactive shell exits with +a nonzero exit status. +An interactive shell will not exit, but any associated command(s) will +not be executed. +If the +.Ar parameter +is set, its value is substituted. +.It Li ${ Ns Ar parameter Ns Ic :+ Ns Ar word Ns Li } +.Sy Use Alternative Value. +If +.Ar parameter +is unset or null, null is substituted; +otherwise, the expansion of +.Ar word +is substituted. +The value of +.Ar parameter +.Em is not used +in this expansion. +.It Li ${ Ns Ic # Ns Ar parameter Ns Li } +.Sy String Length. +The length in characters of the value of +.Ar parameter . +.El +.Pp +The following four varieties of parameter expansion provide for substring +processing. +In each case, pattern matching notation (see +.Sx Shell Patterns ) , +rather than regular expression notation, is used to evaluate the patterns. +If parameter is +.Dv * +or +.Dv @ , \" $@ +the result of the expansion is unspecified. +Enclosing the full parameter expansion string in double quotes does not +cause the following four varieties of pattern characters to be quoted, +whereas quoting characters within the braces has this effect. +.Bl -tag -width aaparameterwordaaaaa +.It Li ${ Ns Ar parameter Ns Ic % Ns Ar word Ns Li } +.Sy Remove Smallest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The parameter expansion then results in +.Ar parameter , +with the +smallest portion of the suffix matched by the pattern deleted. +If the +.Ar word +is to start with a +.Sq Li \&% +character, it must be quoted. +.It Li ${ Ns Ar parameter Ns Ic %% Ns Ar word Ns Li } +.Sy Remove Largest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The parameter expansion then results in +.Ar parameter , +with the largest +portion of the suffix matched by the pattern deleted. +The +.Dq Ic %% +pattern operator only produces different results from the +.Dq Ic \&% +operator when the pattern contains at least one unquoted +.Sq Li \&* . +.It Li ${ Ns Ar parameter Ns Ic \&# Ns Ar word Ns Li } +.Sy Remove Smallest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The parameter expansion then results in +.Ar parameter , +with the +smallest portion of the prefix matched by the pattern deleted. +If the +.Ar word +is to start with a +.Sq Li \&# +character, it must be quoted. +.It Li ${ Ns Ar parameter Ns Ic \&## Ns Ar word Ns Li } +.Sy Remove Largest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The parameter expansion then results in +.Ar parameter , +with the largest +portion of the prefix matched by the pattern deleted. +This has the same relationship with the +.Dq Ic \&# +pattern operator as +.Dq Ic %% +has with +.Dq Ic \&% . +.El +.\" +.\" +.Ss Command Substitution +.\" +Command substitution allows the output of a command to be substituted in +place of the command (and surrounding syntax). +Command substitution occurs when a word contains +a command list enclosed as follows: +.Pp +.Dl Ic $( Ns Ar list Ns Ic \&) +.Pp +or the older +.Pq Dq backquoted +version, which is best avoided: +.Pp +.Dl Ic \&` Ns Ar list Ns Ns Ic \&` +.Pp +See the section +.Sx Complex Commands +above for the definition of +.Ic list . +.Pp +The shell expands the command substitution by executing the +.Ar list +in a sub-shell environment and replacing the command substitution with the +standard output of the +.Ar list +after removing any sequence of one or more +.Ao newline Ac Ns s +from the end of the substitution. +(Embedded +.Ao newline Ac Ns s +before +the end of the output are not removed; however, during field splitting, +they may be used to separate fields +.Pq "as spaces usually are" +depending on the value of +.Ev IFS +and any quoting that is in effect.) +.Pp +Note that if a command substitution includes commands +to be run in the background, +the sub-shell running those commands +will only wait for them to complete if an appropriate +.Ic wait +command is included in the command list. +However, the shell in which the result of the command substitution +will be used will wait for both the sub-shell to exit and for the +file descriptor that was initially standard output for the command +substitution sub-shell to be closed. +In some circumstances this might not happen until all processes +started by the command substitution have finished. +.\" While the exit of the sub-shell closes its standard output, +.\" any background process left running may still +.\" have that file descriptor open. +.\" This includes yet another sub-shell which might have been +.\" (almost invisibly) created to wait for some other command to complete, +.\" and even where the background command has had its +.\" standard output redirected or closed, +.\" the waiting sub-shell may still have it open. +.\" Thus there is no guarantee that the result of a command substitution +.\" will be available in the shell which is to use it before all of +.\" the commands started by the command substitution have completed, +.\" though careful coding can often avoid delays beyond the termination +.\" of the command substitution sub-shell. +.\" .Pp +.\" For example, assuming a script were to contain the following +.\" code (which could be done better other ways, attempting +.\" this kind of trickery is not recommended): +.\" .Bd -literal -offset indent +.\" if [ "$( printf "Ready? " >&2; read ans; printf "${ans}"; +.\" { sleep 120 >/dev/null && kill $$ >/dev/null 2>&1; }& )" = go ] +.\" then +.\" printf Working... +.\" # more code +.\" fi +.\" .Ed +.\" .Pp +.\" the +.\" .Dq Working... +.\" output will not be printed, and code that follows will never be executed. +.\" Nor will anything later in the script +.\" .Po +.\" unless +.\" .Dv SIGTERM +.\" is trapped or ignored +.\" .Pc . +.\" .Pp +.\" The intent is to prompt the user, wait for the user to +.\" answer, then if the answer was +.\" .Dq go +.\" do the appropriate work, but set a 2 minute time limit +.\" on completing that work. +.\" If the work is not done by then, kill the shell doing it. +.\" .Pp +.\" It will usually not work as written, as while the 2 minute +.\" .Sq sleep +.\" has its standard output redirected, as does the +.\" .Sq kill +.\" that follows (which also redirects standard error, +.\" so the user would not see an error if the work were +.\" completed and there was no parent process left to kill); +.\" the sub-shell waiting for the +.\" .Sq sleep +.\" to finish successfully, so it can start the +.\" .Sq kill , +.\" does not. +.\" It waits, with standard output still open, +.\" for the 2 minutes until the sleep is done, +.\" even though the kill is not going to need that file descriptor, +.\" and there is nothing else which could. +.\" The command substitution does not complete until after +.\" the kill has executed and the background sub-shell +.\" finishes \(en at which time the shell running it is +.\" presumably dead. +.\" .Pp +.\" Rewriting the background sub-shell part of that as +.\" .Dl "{ sleep 120 && kill $$ 2>&1; } >/dev/null &" +.\" would allow this +.\" .Nm +.\" to perform as expected, but that is not guaranteed, +.\" not all shells would behave as planned. +.\" It is advised to avoid starting background processes +.\" from within a command substitution. +.\" +.\" +.Ss Arithmetic Expansion +.\" +Arithmetic expansion provides a mechanism for evaluating an arithmetic +expression and substituting its value. +The format for arithmetic expansion is as follows: +.Pp +.Dl Ic $(( Ns Ar expression Ns Ic \&)) +.Pp +The expression in an arithmetic expansion is treated as if it were in +double quotes, except that a double quote character inside the expression +is just a normal character (it quotes nothing.) +The shell expands all tokens in the expression for parameter expansion, +command substitution, and quote removal (the only quoting character is +the backslash +.Sq \&\e , +and only when followed by another +.Sq \&\e , +a dollar sign +.Sq \&$ , +a backquote +.Sq \&` +or a newline.) +.Pp +Next, the shell evaluates the expanded result as an arithmetic expression +and substitutes the calculated value of that expression. +.Pp +Arithmetic expressions use a syntax similar to that +of the C language, and are evaluated using the +.Ql intmax_t +data type (this is an extension to POSIX, which requires only +.Ql long +arithmetic.) +Shell variables may be referenced by name inside an arithmetic +expression, without needing a +.Dq \&$ +sign. +Variables that are not set, or which have an empty (null string) value, +used this way evaluate as zero (that is, +.Dq x +in arithmetic, as an R-Value, is evaluated as +.Dq ${x:-0} ) +unless the +.Nm +.Fl u +flag is set, in which case a reference to an unset variable is an error. +Note that unset variables used in the ${var} form expand to a null +string, which might result in syntax errors. +Referencing the value of a variable which is not numeric is an error. +.Pp +All of the C expression operators applicable to integers are supported, +and operate as they would in a C expression. +Use white space, or parentheses, to disambiguate confusing syntax, +otherwise, as in C, the longest sequence of consecutive characters +which make a valid token (operator, variable name, or number) is taken +to be that token, even if the token designated cannot be used +and a different interpretation could produce a successful parse. +This means, as an example, that +.Dq a+++++b +is parsed as the gibberish sequence +.Dq "a ++ ++ + b" , +rather than as the valid alternative +.Dq "a ++ + ++ b" . +Similarly, separate the +.Sq \&, +operator from numbers with white space to avoid the possibility +of confusion with the decimal indicator in some locales (though +fractional, or floating-point, numbers are not supported in this +implementation.) +.Pp +It should not be necessary to state that the C operators which +operate on, or produce, pointer types, are not supported. +Those include unary +.Dq \&* +and +.Dq \&& +and the struct and array referencing binary operators: +.Dq \&. , +.Dq \&-> +and +.Dq \&[ . +.\" +.\" +.Ss White Space Splitting (Field Splitting) +.\" +After parameter expansion, command substitution, and +arithmetic expansion the shell scans the results of +expansions and substitutions that did not occur in double quotes, +and +.Dq Li $@ +even if it did, +for field splitting and multiple fields can result. +.Pp +The shell treats each character of the +.Ev IFS +as a delimiter and uses the delimiters to split the results of parameter +expansion and command substitution into fields. +.Pp +Non-whitespace characters in +.Ev IFS +are treated strictly as parameter separators. +So adjacent non-whitespace +.Ev IFS +characters will produce empty parameters. +On the other hand, any sequence of whitespace +characters that occur in +.Ev IFS +(known as +.Ev IFS +whitespace) +can occur, leading and trailing +.Ev IFS +whitespace, and any +.Ev IFS +whitespace surrounding a non whitespace +.Ev IFS +delimiter, is removed. +Any sequence of +.Ev IFS +whitespace characters without a non-whitespace +.Ev IFS +delimiter acts as a single field separator. +.Pp +If +.Ev IFS +is unset it is assumed to contain space, tab, and newline, +all of which are +.Ev IFS +whitespace characters. +If +.Ev IFS +is set to a null string, there are no delimiters, +and no field splitting occurs. +.Ss Pathname Expansion (File Name Generation) +Unless the +.Fl f +flag is set, file name generation is performed after word splitting is +complete. +Each word is viewed as a series of patterns, separated by slashes. +The process of expansion replaces the word with the names of all +existing files whose names can be formed by replacing each pattern with a +string that matches the specified pattern. +There are two restrictions on +this: first, a pattern cannot match a string containing a slash, and +second, a pattern cannot match a string starting with a period unless the +first character of the pattern is a period. +The next section describes the +patterns used for both Pathname Expansion and the +.Ic case +command. +.Ss Shell Patterns +A pattern consists of normal characters, which match themselves, +and meta-characters. +The meta-characters are +.Dq \&! , +.Dq * , +.Dq \&? , +and +.Dq \&[ . +These characters lose their special meanings if they are quoted. +When command or variable substitution is performed +and the dollar sign or backquotes are not double-quoted, +the value of the variable or the output of +the command is scanned for these characters and they are turned into +meta-characters. +.Pp +An asterisk +.Pq Dq * +matches any string of characters. +A question mark +.Pq Dq \&? +matches any single character. +A left bracket +.Pq Dq \&[ +introduces a character class. +The end of the character class is indicated by a right bracket +.Pq Dq \&] ; +if this +.Dq \&] +is missing then the +.Dq \&[ +matches a +.Dq \&[ +rather than introducing a character class. +A character class matches any of the characters between the square brackets. +A named class of characters (see +.Xr wctype 3 ) +may be specified by surrounding the name with +.Pq Dq [: +and +.Pq Dq :] . +For example, +.Pq Dq [[:alpha:]] +is a shell pattern that matches a single letter. +A range of characters may be specified using a minus sign +.Pq Dq \(mi . +The character class may be complemented +by making an exclamation mark +.Pq Dq \&! +the first character of the character class. +.Pp +To include a +.Dq \&] +in a character class, make it the first character listed (after the +.Dq \&! , +if any). +To include a +.Dq \(mi , +make it the first (after !) or last character listed. +If both +.Dq \&] +and +.Dq \(mi +are to be included, the +.Dq \&] +must be first (after !) +and the +.Dq \(mi +last, in the character class. +.\" +.\" +.Ss Built-ins +.\" +This section lists the built-in commands which are built-in because they +need to perform some operation that can't be performed by a separate +process. +Or just because they traditionally are. +In addition to these, there are several other commands that may +be built in for efficiency (e.g. +.Xr printf 1 , +.Xr echo 1 , +.Xr test 1 , +etc). +.Bl -tag -width 5n +.It Ic \&: Op Ar arg ... +A null command that returns a 0 (true) exit value. +Any arguments or redirects are evaluated, then ignored. +.\" +.It Ic \&. Ar file +The dot command reads and executes the commands from the specified +.Ar file +in the current shell environment. +The file does not need to be executable and is looked up from the directories +listed in the +.Ev PATH +variable if its name does not contain a directory separator +.Pq Sq / . +The +.Ic return +command (see below) +can be used for a premature return from the sourced file. +.Pp +The POSIX standard has been unclear on how loop control keywords (break +and continue) behave across a dot command boundary. +This implementation allows them to control loops surrounding the dot command, +but obviously such behavior should not be relied on. +It is now permitted by the standard, but not required. +.\" +.It Ic alias Op Ar name Ns Op Li = Ns Ar string ... +If +.Ar name Ns Li = Ns Ar string +is specified, the shell defines the alias +.Ar name +with value +.Ar string . +If just +.Ar name +is specified, the value of the alias +.Ar name +is printed. +With no arguments, the +.Ic alias +built-in prints the +names and values of all defined aliases (see +.Ic unalias ) . +.\" +.It Ic bg Op Ar job ... +Continue the specified jobs (or the current job if no +jobs are given) in the background. +.\" +.It Ic command Oo Fl pVv Oc Ar command Op Ar arg ... +Execute the specified command but ignore shell functions when searching +for it. +(This is useful when you +have a shell function with the same name as a command.) +.Bl -tag -width 5n +.It Fl p +search for command using a +.Ev PATH +that guarantees to find all the standard utilities. +.It Fl V +Do not execute the command but +search for the command and print the resolution of the +command search. +This is the same as the +.Ic type +built-in. +.It Fl v +Do not execute the command but +search for the command and print the absolute pathname +of utilities, the name for built-ins or the expansion of aliases. +.El +.\" +.It Ic cd Oo Fl P Oc Op Ar directory Op Ar replace +Switch to the specified directory (default +.Ev $HOME ) . +If +.Ar replace +is specified, then the new directory name is generated by replacing +the first occurrence of the string +.Ar directory +in the current directory name with +.Ar replace . +Otherwise if +.Ar directory +is +.Sq Li - , +then the current working directory is changed to the previous current +working directory as set in +.Ev OLDPWD . +Otherwise if an entry for +.Ev CDPATH +appears in the environment of the +.Ic cd +command or the shell variable +.Ev CDPATH +is set and the directory name does not begin with a slash, +and its first (or only) component isn't dot or dot dot, +then the directories listed in +.Ev CDPATH +will be searched for the specified directory. +The format of +.Ev CDPATH +is the same as that of +.Ev PATH . +.Pp +The +.Fl P +option instructs the shell to update +.Ev PWD +with the specified physical directory path and change to that directory. +This is the default. +.Pp +When the directory changes, the variable +.Ev OLDPWD +is set to the working directory before the change. +.Pp +Some shells also support a +.Fl L +option, which instructs the shell to update +.Ev PWD +with the logical path and to change the current directory +accordingly. +This is not supported. +.Pp +In an interactive shell, the +.Ic cd +command will print out the name of the +directory that it actually switched to if this is different from the name +that the user gave, +or always if the +.Cm cdprint +option is set. +The destination may be different either because the +.Ev CDPATH +mechanism was used +.\" or because a symbolic link was crossed. +or if the +.Ar replace +argument was used. +.\" +.It Ic eval Ar string ... +Concatenate all the arguments with spaces. +Then re-parse and execute the command. +.\" +.It Ic exec Op Ar command Op Ar arg ... +Unless +.Ar command +is omitted, the shell process is replaced with the +specified program (which must be a real program, not a shell built-in or +function). +Any redirections on the +.Ic exec +command are marked as permanent, so that they are not undone when the +.Ic exec +command finishes. +When the +.Cm posix +option is not set, +file descriptors created via such redirections are marked close-on-exec +(see +.Xr open 2 +.Dv O_CLOEXEC +or +.Xr fcntl 2 +.Dv F_SETFD / +.Dv FD_CLOEXEC ) , +unless the descriptors refer to the standard input, +output, or error (file descriptors 0, 1, 2). +Traditionally Bourne-like shells +(except +.Xr ksh 1 ) , +made those file descriptors available to exec'ed processes. +To be assured the close-on-exec setting is off, +redirect the descriptor to (or from) itself, +either when invoking a command for which the descriptor is wanted open, +or by using +.Ic exec +(perhaps the same +.Ic exec +as opened it, after the open) +to leave the descriptor open in the shell +and pass it to all commands invoked subsequently. +Alternatively, see the +.Ic fdflags +command below, which can set, or clear, this, and other, +file descriptor flags. +.\" +.It Ic exit Op Ar exitstatus +Terminate the shell process. +If +.Ar exitstatus +is given it is used as the exit status of the shell; otherwise the +exit status of the preceding command (the current value of $?) is used. +.\" +.It Ic export Oo Fl nx Oc Ar name Ns Oo =value Oc ... +.It Ic export Oo Fl x Oc Oo Fl p Oo Ar name ... Oc Oc +.It Ic export Fl q Oo Fl x Oc Ar name ... +With no options, +but one or more names, +the specified names are exported so that they will appear in the +environment of subsequent commands. +With +.Fl n +the specified names are un-exported. +Variables can also be un-exported using the +.Ic unset +built in command. +With +.Fl x +(exclude) the specified names are marked not to be exported, +and any that had been exported, will be un-exported. +Later attempts to export the variable will be refused. +Note this does not prevent explicitly exporting a variable +to a single command, script or function by preceding that +command invocation by a variable assignment to that variable, +provided the variable is not also read-only. +That is +.Bd -literal -offset indent +export -x FOO # FOO will now not be exported by default +export FOO # this command will fail (non-fatally) +FOO=some_value my_command +.Ed +.Pp +still passes the value +.Pq Li FOO=some_value +to +.Li my_command +through the environment. +.Pp +The shell allows the value of a variable to be set at the +same time it is exported (or unexported, etc) by writing +.Pp +.Dl export [-nx] name=value +.Pp +With no arguments the export command lists the names of all +set exported variables, +or if +.Fl x +was given, all set variables marked not for export. +With the +.Fl p +option specified, the output will be formatted suitably for +non-interactive use, and unset variables are included. +When +.Fl p +is given, variable names, but not values, may also be +given, in which case output is limited to the variables named. +.Pp +With +.Fl q +and a list of variable names, the +.Ic export +command will exit with status 0 if all the named +variables have been marked for export, or 1 if +any are not so marked. +If +.Fl x +is also given, +the test is instead for variables marked not to be exported. +.Pp +Other than with +.Fl q , +the +.Ic export +built-in exits with status 0, +unless an attempt is made to export a variable which has +been marked as unavailable for export, +in which cases it exits with status 1. +In all cases if +an invalid option, or option combination, is given, +or an invalid variable name is present, +.Ic export +will write a message to the standard error output, +and exit with a non-zero status. +A non-interactive shell will terminate. +.Pp +Note that there is no restriction upon exporting, +or un-exporting, read-only variables. +The no-export flag can be reset by unsetting the variable +and creating it again \(en provided the variable is not also read-only. +.\" +.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last +.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last +.It Ic fc Fl s Oo Ar old=new Oc Op Ar first +The +.Ic fc +built-in lists, or edits and re-executes, commands previously entered +to an interactive shell. +.Bl -tag -width 5n +.It Fl e Ar editor +Use the editor named by +.Ar editor +to edit the commands. +The +.Ar editor +string is a command name, subject to search via the +.Ev PATH +variable. +The value in the +.Ev FCEDIT +variable is used as a default when +.Fl e +is not specified. +If +.Ev FCEDIT +is null or unset, the value of the +.Ev EDITOR +variable is used. +If +.Ev EDITOR +is null or unset, +.Xr ed 1 +is used as the editor. +.It Fl l No (ell) +List the commands rather than invoking an editor on them. +The commands are written in the sequence indicated by +the first and last operands, as affected by +.Fl r , +with each command preceded by the command number. +.It Fl n +Suppress command numbers when listing with +.Fl l . +.It Fl r +Reverse the order of the commands listed (with +.Fl l ) +or edited (with neither +.Fl l +nor +.Fl s ) . +.It Fl s +Re-execute the command without invoking an editor. +.It Ar first +.It Ar last +Select the commands to list or edit. +The number of previous commands that +can be accessed are determined by the value of the +.Ev HISTSIZE +variable. +The value of +.Ar first +or +.Ar last +or both are one of the following: +.Bl -tag -width 5n +.It Oo Cm + Oc Ns Ar number +A positive number representing a command number; command numbers can be +displayed with the +.Fl l +option. +.It Cm \- Ns Ar number +A negative decimal number representing the command that was executed +number of commands previously. +For example, \-1 is the immediately previous command. +.El +.It Ar string +A string indicating the most recently entered command that begins with +that string. +If the +.Ar old Ns Li = Ns Ar new +operand is not also specified with +.Fl s , +the string form of the first operand cannot contain an embedded equal sign. +.El +.Pp +The following environment variables affect the execution of +.Ic fc : +.Bl -tag -width HISTSIZE +.It Ev FCEDIT +Name of the editor to use. +.It Ev HISTSIZE +The number of previous commands that are accessible. +.El +.\" +.It Ic fg Op Ar job +Move the specified job or the current job to the foreground. +A foreground job can interact with the user via standard input, +and receive signals from the terminal. +.\" +.It Ic fdflags Oo Fl v Oc Op Ar fd ... +.It Ic fdflags Oo Fl v Oc Fl s Ar flags fd Op ... +Get or set file descriptor flags. +The +.Fl v +argument enables verbose printing, printing flags that are also off, and +the flags of the file descriptor being set after setting. +The +.Fl s +flag interprets the +.Ar flags +argument as a comma separated list of file descriptor flags, each preceded +with a +.Dq \(pl +or a +.Dq \(mi +indicating to set or clear the respective flag. +Valid flags are: +.Cm append , +.Cm async , +.Cm sync , +.Cm nonblock , +.Cm fsync , +.Cm dsync , +.Cm rsync , +.Cm direct , +.Cm nosigpipe , +and +.Cm cloexec . +Unique abbreviations of these names, of at least 2 characters, +may be used on input. +See +.Xr fcntl 2 +and +.Xr open 2 +for more information. +.\" +.It Ic getopts Ar optstring var +The POSIX +.Ic getopts +command, not to be confused with the +Bell Labs\[en]derived +.Xr getopt 1 . +.Pp +The first argument should be a series of letters, each of which may be +optionally followed by a colon to indicate that the option requires an +argument. +The variable specified is set to the parsed option. +.Pp +The +.Ic getopts +command deprecates the older +.Xr getopt 1 +utility due to its handling of arguments containing whitespace. +.Pp +The +.Ic getopts +built-in may be used to obtain options and their arguments +from a list of parameters. +When invoked, +.Ic getopts +places the value of the next option from the option string in the list in +the shell variable specified by +.Ar var +and its index in the shell variable +.Ev OPTIND . +When the shell is invoked, +.Ev OPTIND +is initialized to 1. +For each option that requires an argument, the +.Ic getopts +built-in will place it in the shell variable +.Ev OPTARG . +If an option is not allowed for in the +.Ar optstring , +then +.Ev OPTARG +will be unset. +.Pp +.Ar optstring +is a string of recognized option letters (see +.Xr getopt 3 ) . +If a letter is followed by a colon, the option is expected to have an +argument which may or may not be separated from it by whitespace. +If an option character is not found where expected, +.Ic getopts +will set the variable +.Ar var +to a +.Sq Li \&? ; +.Ic getopts +will then unset +.Ev OPTARG +and write output to standard error. +By specifying a colon as the first character of +.Ar optstring +all errors will be ignored. +.Pp +A nonzero value is returned when the last option is reached. +If there are no remaining arguments, +.Ic getopts +will set +.Ar var +to the special option, +.Dq Li \-\- , +otherwise, it will set +.Ar var +to +.Sq Li \&? . +.Pp +The following code fragment shows how one might process the arguments +for a command that can take the options +.Fl a +and +.Fl b , +and the option +.Fl c , +which requires an argument. +.Bd -literal -offset indent +while getopts abc: f +do + case $f in + a | b) flag=$f;; + c) carg=$OPTARG;; + \e?) echo $USAGE; exit 1;; + esac +done +shift $((OPTIND - 1)) +.Ed +.Pp +This code will accept any of the following as equivalent: +.Bd -literal -offset indent +cmd \-acarg file file +cmd \-a \-c arg file file +cmd \-carg -a file file +cmd \-a \-carg \-\- file file +.Ed +.\" +.It Ic hash Oo Fl rv Oc Op Ar command ... +The shell maintains a hash table which remembers the +locations of commands. +With no arguments whatsoever, +the +.Ic hash +command prints out the contents of this table. +Entries which have not been looked at since the last +.Ic cd +command are marked with an asterisk; it is possible for these entries +to be invalid. +.Pp +With arguments, the +.Ic hash +command removes the specified commands from the hash table (unless +they are functions) and then locates them. +With the +.Fl v +option, hash prints the locations of the commands as it finds them. +The +.Fl r +option causes the hash command to delete all the entries in the hash table +except for functions. +.\" +.It Ic inputrc Ar file +Read the +.Ar file +to set key bindings as defined by +.Xr editrc 5 . +.\" +.It Ic jobid Oo Fl g Ns \&| Ns Fl j Ns \&| Ns Fl p Oc Op Ar job +With no flags, print the process identifiers of the processes in the job. +If the +.Ar job +argument is omitted, the current job is used. +Any of the ways to select a job may be used for +.Ar job , +including the +.Sq Li \&% +forms, or the process id of the job leader +.Po +.Dq Li \&$! +if the job was created in the background. +.Pc +.Pp +If one of the flags is given, then instead of the list of +process identifiers, the +.Ic jobid +command prints: +.Bl -tag -width ".Fl g" +.It Fl g +the process group, if one was created for this job, +or nothing otherwise (the job is in the same process +group as the shell.) +.It Fl j +the job identifier (using +.Dq Li \&% Ns Ar n +notation, where +.Ar n +is a number) is printed. +.It Fl p +only the process id of the process group leader is printed. +.El +.Pp +These flags are mutually exclusive. +.Pp +.Ic jobid +exits with status 2 if there is an argument error, +status 1, if with +.Fl g +the job had no separate process group, +or with +.Fl p +there is no process group leader (should not happen), +and otherwise exits with status 0. +.\" +.It Ic jobs Oo Fl l Ns \&| Ns Fl p Oc Op Ar job ... +Without +.Ar job +arguments, +this command lists out all the background processes +which are children of the current shell process. +With +.Ar job +arguments, the listed jobs are shown instead. +Without flags, the output contains the job +identifier (see +.Sx Job Control +below), an indicator character if the job is the current or previous job, +the current status of the job (running, suspended, or terminated successfully, +unsuccessfully, or by a signal) +and a (usually abbreviated) command string. +.Pp +With the +.Fl l +flag the output is in a longer form, with the process identifiers +of each process (run from the top level, as in a pipeline), and the +status of each process, rather than the job status. +.Pp +With the +.Fl p +flag, the output contains only the process identifier of the lead +process. +.Pp +In an interactive shell, each job shown as completed in the output +from the jobs command is implicitly waited for, and is removed from +the jobs table, never to be seen again. +In an interactive shell, when a background job terminates, the +.Ic jobs +command (with that job as an argument) is implicitly run just +before outputting the next PS1 command prompt, after the job +terminated. +This indicates that the job finished, shows its status, +and cleans up the job table entry for that job. +Non-interactive shells need to execute +.Ic wait +commands to clean up terminated background jobs. +.\" +.It Ic local Oo Fl INx Oc Oo Ar variable | \- Oc ... +Define local variables for a function. +Local variables have their attributes, and values, +as they were before the +.Ic local +declaration, restored when the function terminates. +.Pp +With the +.Fl N +flag, variables made local, are unset initially inside +the function. +Unless the +.Fl x +flag is also given, such variables are also unexported. +The +.Fl I +flag, which is the default in this shell, causes +the initial value and exported attribute +of local variables +to be inherited from the variable +with the same name in the surrounding +scope, if there is one. +If there is not, the variable is initially unset, +and not exported. +The +.Fl N +and +.Fl I +flags are mutually exclusive, if both are given, the last specified applies. +The read-only and unexportable attributes are always +inherited, if a variable with the same name already exists. +.Pp +The +.Fl x +flag (lower case) causes the local variable to be exported, +while the function runs, unless it has the unexportable attribute. +This can also be accomplished by using the +.Ic export +command, giving the same +.Ar variable +names, after the +.Ic local +command. +.Pp +Making an existing read-only variable local is possible, +but pointless. +If an attempt is made to assign an initial value to such +a variable, the +.Ic local +command fails, as does any later attempted assignment. +If the +.Ic readonly +command is applied to a variable that has been declared local, +the variable cannot be (further) modified within the function, +or any other functions it calls, however when the function returns, +the previous status (and value) of the variable is returned. +.Pp +Values may be given to local variables on the +.Ic local +command line in a similar fashion as used for +.Ic export +and +.Ic readonly . +These values are assigned immediately after the initialization +described above. +Note that any variable references on the command line will have +been expanded before +.Ic local +is executed, so expressions like +.Pp +.Dl "local -N X=${X}" +.Pp +are well defined, first $X is expanded, and then the command run is +.Pp +.Dl "local -N X=old-value-of-X" +.Pp +After arranging to preserve the old value and attributes, of +.Dv X +.Dq ( old-value-of X ) +.Ic local +unsets +.Dv X , +unexports it, and then assigns the +.Dq old-value-of-X +to +.Ev X . +.Pp +The shell uses dynamic scoping, so that if you make the variable +.Dv x +local to +function +.Dv f , +which then calls function +.Dv g , +references to the variable +.Dv x +made inside +.Dv g +will refer to the variable +.Dv x +declared inside +.Dv f , +not to the global variable named +.Dv x . +.Pp +Another way to view this, is as if the shell just has one flat, global, +namespace, in which all variables exist. +The +.Ic local +command conceptually copies the variable(s) named to unnamed temporary +variables, and when the function ends, copies them back again. +All references to the variables reference the same global variables, +but while the function is active, after the +.Ic local +command has run, the values and attributes of the variables might +be altered, and later, when the function completes, be restored. +.Pp +Note that the positional parameters +.Dv 1 , \" $1 +.Dv 2 , \" $2 +\&... (see +.Sx Positional Parameters ) , +and the special parameters +.Dv \&# , \" $# +.Dv \&* \" $* +and +.Dv \&@ \" $@ +(see +.Sx Special Parameters ) , +are always made local in all functions, and are reset inside the +function to represent the options and arguments passed to the function. +Note that +.Li $0 +however retains the value it had outside the function, +as do all the other special parameters. +.Pp +The only special parameter that can optionally be made local is +.Dq Li \- . +Making +.Dq Li \- +local causes any shell options that are changed via the set command inside the +function to be restored to their original values when the function +returns. +If +.Fl X +option is altered after +.Dq Li \- +has been made local, then when the function returns, the previous +destination for +.Cm xtrace +output (as of the time of the +.Ic local +command) will also be restored. +If any of the shell's magic variables +(those which return a value which may vary without +the variable being explicitly altered, +e.g.: +.Dv SECONDS +or +.Dv HOSTNAME ) +are made local in a function, +they will lose their special properties when set +within the function, including by the +.Ic local +command itself +(if not to be set in the function, there is little point +in making a variable local) +but those properties will be restored when the function returns. +.Pp +It is an error to use +.Ic local +outside the scope of a function definition. +When used inside a function, it exits with status 0, +unless an undefined option is used, or an attempt is made to +assign a value to a read-only variable. +.Pp +Note that either +.Fl I +or +.Fl N +should always be used, or variables made local should always +be given a value, or explicitly unset, as the default behavior +(inheriting the earlier value, or starting unset after +.Ic local ) +differs amongst shell implementations. +Using +.Dq Li local \&\- +is an extension not implemented by most shells. +.Pp +See the section +.Sx LINENO +below for details of the effects of making the variable +.Dv LINENO +local. +.\" +.It Ic pwd Op Fl \&LP +Print the current directory. +If +.Fl L +is specified the cached value (initially set from +.Ev PWD ) +is checked to see if it refers to the current directory; if it does +the value is printed. +Otherwise the current directory name is found using +.Xr getcwd 3 . +The environment variable +.Ev PWD +is set to the printed value. +.Pp +The default is +.Ic pwd +.Fl L , +but note that the built-in +.Ic cd +command doesn't support the +.Fl L +option and will cache (almost) the absolute path. +If +.Ic cd +is changed (as unlikely as that is), +.Ic pwd +may be changed to default to +.Ic pwd +.Fl P . +.Pp +If the current directory is renamed and replaced by a symlink to the +same directory, or the initial +.Ev PWD +value followed a symbolic link, then the cached value may not +be the absolute path. +.Pp +The built-in command may differ from the program of the same name because +the program will use +.Ev PWD +and the built-in uses a separately cached value. +.\" +.It Ic read Oo Fl p Ar prompt Oc Oo Fl r Oc Ar variable Op Ar ... +The +.Ar prompt +is printed if the +.Fl p +option is specified and the standard input is a terminal. +Then a line is read from the standard input. +The trailing newline is deleted from the +line and the line is split as described in the field splitting section of the +.Sx Word Expansions +section above, and the pieces are assigned to the variables in order. +If there are more pieces than variables, the remaining pieces +(along with the characters in +.Ev IFS +that separated them) are assigned to the last variable. +If there are more variables than pieces, +the remaining variables are assigned the null string. +The +.Ic read +built-in will indicate success unless EOF is encountered on input, in +which case failure is returned. +.Pp +By default, unless the +.Fl r +option is specified, the backslash +.Dq \e +acts as an escape character, causing the following character to be treated +literally. +If a backslash is followed by a newline, the backslash and the +newline will be deleted. +.\" +.It Ic readonly Ar name Ns Oo =value Oc ... +.It Ic readonly Oo Fl p Oo Ar name ... Oc Oc +.It Ic readonly Fl q Ar name ... +With no options, +the specified names are marked as read only, so that they cannot be +subsequently modified or unset. +The shell allows the value of a variable +to be set at the same time it is marked read only by writing +.Pp +.Dl readonly name=value +.Pp +With no arguments the +.Ic readonly +command lists the names of all set read only variables. +With the +.Fl p +option specified, +the output will be formatted suitably for non-interactive use, +and unset variables are included. +When the +.Fl p +option is given, +a list of variable names (without values) may also be specified, +in which case output is limited to the named variables. +.Pp +With the +.Fl q +option, the +.Ic readonly +command tests the read-only status of the variables listed +and exits with status 0 if all named variables are read-only, +or with status 1 if any are not read-only. +.Pp +Other than as specified for +.Fl q +the +.Ic readonly +command normally exits with status 0. +In all cases, if an unknown option, or an invalid option combination, +or an invalid variable name, is given; +or a variable which was already read-only is attempted to be set; +the exit status will not be zero, a diagnostic +message will be written to the standard error output, +and a non-interactive shell will terminate. +.\" +.It Ic return Op Ar n +Stop executing the current function or a dot command with return value of +.Ar n +or the value of the last executed command, if not specified. +For portability, +.Ar n +should be in the range from 0 to 255. +.Pp +The POSIX standard says that the results of +.Ic return +outside a function or a dot command are unspecified. +This implementation treats such a return as a no-op with a return value of 0 +(success, true). +Use the +.Ic exit +command instead, if you want to return from a script or exit +your shell. +.\" +.It Ic set Oo { Fl options | Cm +options | Cm \-- } Oc Ar arg ... +The +.Ic set +command performs four different functions. +.Pp +With no arguments, it lists the values of all shell variables. +.Pp +With a single option of either +.Dq Fl o +or +.Dq Cm +o +.Ic set +outputs the current values of the options. +In the +.Fl o +form, all options are listed, with their current values. +In the +.Cm +o +form, the shell outputs a string that can later be used +as a command to reset all options to their current values. +.Pp +If options are given, it sets the specified option +flags, or clears them as described in the +.Sx Argument List Processing +section. +In addition to the options listed there, +when the +.Dq "option name" +given to +.Ic set Fl o +is +.Cm default +all of the options are reset to the values they had immediately +after +.Nm +initialization, before any startup scripts, or other input, had been processed. +While this may be of use to users or scripts, its primary purpose +is for use in the output of +.Dq Ic set Cm +o , +to avoid that command needing to list every available option. +There is no +.Cm +o default . +.Pp +The fourth use of the +.Ic set +command is to set the values of the shell's +positional parameters to the specified arguments. +To change the positional +parameters without changing any options, use +.Dq -\|- +as the first argument to +.Ic set . +If no following arguments are present, the +.Ic set +command +will clear all the positional parameters (equivalent to executing +.Dq Li shift $# . ) +Otherwise the following arguments become +.Li \&$1 , +.Li \&$2 , +\&..., +and +.Li \&$# +is set to the number of arguments present. +.\" +.It Ic setvar Ar variable Ar value +Assigns +.Ar value +to +.Ar variable . +(In general it is better to write +.Li variable=value +rather than using +.Ic setvar . +.Ic setvar +is intended to be used in +functions that assign values to variables whose names are passed as +parameters.) +.\" +.It Ic shift Op Ar n +Shift the positional parameters +.Ar n +times. +If +.Ar n +is omitted, 1 is assumed. +Each +.Ic shift +sets the value of +.Li $1 +to the previous value of +.Li $2 , +the value of +.Li $2 +to the previous value of +.Li $3 , +and so on, decreasing +the value of +.Li $# +by one. +The shift count must be less than or equal to the number of +positional parameters ( +.Dq Li $# ) +before the shift. +.\" +.It Ic times +Prints two lines to standard output. +Each line contains two accumulated time values, expressed +in minutes and seconds (including fractions of a second.) +The first value gives the user time consumed, the second the system time. +.Pp +The first output line gives the CPU and system times consumed by the +shell itself. +The second line gives the accumulated times for children of this +shell (and their descendants) which have exited, and then been +successfully waited for by the relevant parent. +See +.Xr times 3 +for more information. +.Pp +.Ic times +has no parameters, and exits with an exit status of 0 unless +an attempt is made to give it an option. +.\" +.It Ic trap Ar action signal ... +.It Ic trap \- +.It Ic trap Op Fl l +.It Ic trap Oo Fl p Oc Ar signal ... +.It Ic trap Ar N signal ... +.Pp +Cause the shell to parse and execute action when any of the specified +signals are received. +The signals are specified by signal number or as the name of the signal. +If +.Ar signal +is +.Li 0 \" $0 +or its equivalent, +.Li EXIT , +the action is executed when the shell exits. +The +.Ar action +may be a null (empty) string, +which causes the specified signals to be ignored. +With +.Ar action +set to +.Sq Li - +the specified signals are set to their default actions. +If the first +.Ar signal +is specified in its numeric form, then +.Ar action +can be omitted to achieve the same effect. +This archaic, +but still standard, +form should not be relied upon, use the explicit +.Sq Li - +action. +If no signals are specified with an action of +.Sq Li - , +all signals are reset. +.Pp +When the shell forks off a sub-shell, it resets trapped (but not ignored) +signals to the default action. +On non-interactive shells, the +.Ic trap +command has no effect on signals that were +ignored on entry to the shell. +On interactive shells, the +.Ic trap +command will catch or reset signals ignored on entry. +.Pp +Issuing +.Ic trap +with option +.Fl l +will print a list of valid signal names. +.Ic trap +without any arguments causes it to write a list of signals and their +associated non-default actions to the standard output in a format +that is suitable as an input to the shell that achieves the same +trapping results. +With the +.Fl p +flag, trap prints the same information for the signals specified, +or if none are given, for all signals, including those where the +action is the default. +These variants of the trap command may be executed in a sub-shell +.Pq "such as in a command substitution" , +provided they appear as the sole, or first, command in that sub-shell, +in which case the state of traps from the parent of that +sub-shell is reported. +.Pp +Examples: +.Pp +.Dl trap +.Pp +List trapped signals and their corresponding actions. +.Pp +.Dl trap -l +.Pp +Print a list of valid signals. +.Pp +.Dl trap '' INT QUIT tstp 30 +.Pp +Ignore signals INT QUIT TSTP USR1. +.Pp +.Dl trap date INT +.Pp +Run the +.Dq date +command (print the date) upon receiving signal INT. +.Pp +.Dl trap HUP INT +.Pp +Run the +.Dq HUP +command, or function, upon receiving signal INT. +.Pp +.Dl trap 1 2 +.Pp +Reset the actions for signals 1 (HUP) and 2 (INT) to their defaults. +.Bd -literal -offset indent +traps=$(trap -p) + # more commands ... +trap 'action' SIG + # more commands ... +eval "$traps" +.Ed +.Pp +Save the trap status, execute commands, changing some traps, +and then reset all traps to their values at the start of the sequence. +The +.Fl p +option is required in the first command here, +or any signals that were previously +untrapped (in their default states) +and which were altered during the intermediate code, +would not be reset by the final +.Ic eval . +.\" +.It Ic type Op Ar name ... +Interpret each +.Ar name +as a command and print the resolution of the command search. +Possible resolutions are: +shell keyword, alias, shell built-in, +command, tracked alias and not found. +For aliases the alias expansion is +printed; for commands and tracked aliases the complete pathname of the +command is printed. +.\" +.It Ic ulimit Oo Fl H Ns \*(Ba Ns Fl S Oc Op Fl a \*(Ba Fl btfdscmlrpnv Op Ar value +Inquire about or set the hard or soft limits on processes or set new +limits. +The choice between hard limit (which no process is allowed to +violate, and which may not be raised once it has been lowered) and soft +limit (which causes processes to be signaled but not necessarily killed, +and which may be raised) is made with these flags: +.Bl -tag -width Fl +.It Fl H +set or inquire about hard limits +.It Fl S +set or inquire about soft limits. +.El +.Pp +If neither +.Fl H +nor +.Fl S +is specified, the soft limit is displayed or both limits are set. +If both are specified, the last one wins. +.Pp +The limit to be interrogated or set, then, is chosen by specifying +any one of these flags: +.Bl -tag -width Fl +.It Fl a +show all the current limits +.It Fl b +the socket buffer size of a process (bytes) +.It Fl c +the largest core dump size that can be produced +(512-byte blocks) +.It Fl d +the data segment size of a process (kilobytes) +.It Fl f +the largest file that can be created +(512-byte blocks) +.It Fl l +how much memory a process can lock with +.Xr mlock 2 +(kilobytes) +.It Fl m +the total physical memory that can be +in use by a process (kilobytes) +.It Fl n +the number of files a process can have open at once +.It Fl p +the number of processes this user can +have at one time +.It Fl r +the number of threads this user can +have at one time +.It Fl s +the stack size of a process (kilobytes) +.It Fl t +CPU time (seconds) +.It Fl v +how large a process address space can be +.El +.Pp +If none of these is specified, it is the limit on file size that is shown +or set. +If value is specified, the limit is set to that number; otherwise +the current limit is displayed. +.Pp +Limits of an arbitrary process can be displayed or set using the +.Xr sysctl 8 +utility. +.It Ic umask Oo Fl S Oc Op Ar mask +Set the value of umask (see +.Xr umask 2 ) +to the specified octal value. +If the argument is omitted, the umask value is printed. +With +.Fl S +a symbolic form is used instead of an octal number. +.It Ic unalias Oo Fl a Oc Op Ar name +If +.Ar name +is specified, the shell removes that alias. +If +.Fl a +is specified, all aliases are removed. +.It Ic unset Oo Fl efvx Oc Ar name ... +If +.Fl v +is specified, the specified variables are unset and unexported. +Readonly variables cannot be unset. +If +.Fl f +is specified, the specified functions are undefined. +If +.Fl e +is given, the specified variables are unexported, but otherwise unchanged, +alternatively, if +.Fl x +is given, the exported status of the variable will be retained, +even after it is unset. +.Pp +If no flags are provided +.Fl v +is assumed. +If +.Fl f +is given with one of the other flags, +then the named variables will be unset, or unexported, and functions +of the same names will be undefined. +The +.Fl e +and +.Fl x +flags both imply +.Fl v . +If +.Fl e +is given, the +.Fl x +flag is ignored. +.Pp +The exit status is 0, unless an attempt was made to unset +a readonly variable, in which case the exit status is 1. +It is not an error to unset (or undefine) a variable (or function) +that is not currently set (or defined.) +.\" +.It Ic wait Oo Fl n Oc Oo Fl p Ar var Oc Op Ar job ... +Wait for the specified jobs to complete +and return the exit status of the last job in the parameter list, +or 127 if that job is not a current child of the shell. +.Pp +If no +.Ar job +arguments are given, wait for all jobs to +complete and then return an exit status of zero +(including when there were no jobs, and so nothing exited.) +.Pp +With the +.Fl n +option, wait instead for any one of the given +.Ar job Ns s, +or if none are given, any job, to complete, and +return the exit status of that job. +If none of the given +.Ar job +arguments is a current child of the shell, +or if no +.Ar job +arguments are given and the shell has no unwaited for children, +then the exit status will be 127. +.Pp +The +.Fl p Ar var +option allows the process (or job) identifier of the +job for which the exit status is returned to be obtained. +The variable named (which must not be readonly) will be +unset initially, then if a job has exited and its status is +being returned, set to the identifier from the +arg list (if given) of that job, +or the lead process identifier of the job to exit when used with +.Fl n +and no job arguments. +Note that +.Fl p +with neither +.Fl n +nor +.Ar job +arguments is useless, as in that case no job status is +returned, the variable named is simply unset. +.Pp +If the wait is interrupted by a signal, +its exit status will be greater than 128, +and +.Ar var , +if given, will remain unset. +.Pp +Once waited upon, by specific process number or job-id, +or by a +.Ic wait +with no arguments, +knowledge of the child is removed from the system, +and it cannot be waited upon again. +.Pp +Note than when a list of jobs are given, more that +one argument might refer to the same job. +In that case, if the final argument represents a job +that is also given earlier in the list, it is not +defined whether the status returned will be the +exit status of the job, or 127 indicating that +the child no longer existed when the wait command +reached the later argument in the list. +In this +.Nm +the exit status will be that from the job. +.Nm +waits for each job exactly once, regardless of +how many times (or how many different ways) it +is listed in the arguments to +.Ic wait . +That is +.Bd -literal -offset indent -compact +wait 100 100 100 +.Ed +is identical to +.Bd -literal -offset indent -compact +wait 100 +.Ed +.El +.\" +.\" +.Ss Job Control +.\" +Each process (or set of processes) started by +.Nm +is created as a +.Dq job +and added to the jobs table. +When enabled by the +.Fl m +option +.Pq aka Fl o Cm monitor +when the job is created, +.Nm +places each job (if run from the top level shell) +into a process group of its own, which allows control +of the process(es), and its/their descendants, as a unit. +When the +.Fl m +option is off, or when started from a sub-shell environment, +jobs share the same process group as the parent shell. +The +.Fl m +option is enabled by default in interactive shells with +a terminal as standard input and standard error. +.Pp +Jobs with separate process groups may be stopped, and then later +resumed in the foreground (with access to the terminal) +or in the background (where attempting to read from the +terminal will result in the job stopping.) +A list of current jobs can be obtained using the +.Ic jobs +built-in command. +Jobs are identified using either the process identifier +of the lead process of the job (the value available in +the special parameter +.Dq Dv \&! +if the job is started in the background), or using percent +notation. +Each job is given a +.Dq job number +which is a small integer, starting from 1, and can be +referenced as +.Dq Li \&% Ns Ar n +where +.Ar n +is that number. +Note that this applies to jobs both with and without their own process groups. +Job numbers are shown in the output from the +.Ic jobs +command enclosed in brackets +.Po +.Sq Li \&[ +and +.Sq Li \&] +.Pc . +Whenever the job table becomes empty, the numbers begin at one again. +In addition, there is the concept of a current, and a previous job, +identified by +.Dq Li \&%+ +.Po +or +.Dq Li \&%% +or even just +.Dq Li \&% +.Pc , +and a previous job, identified by +.Dq Li \&%\- . +Whenever a background job is started, +or a job is resumed in the background, +it becomes the current job. +The job that was the current job +(prepare for a big surprise here, drum roll..., wait for it...\&) +becomes the previous job. +When the current job terminates, the previous job is +promoted to be the current job. +In addition the form +.Dq Li \&% Ns Ar string\^ +finds the job for which the command starts with +.Ar string +and the form +.Dq Li \&%? Ns Ar string\^ +finds the job which contains the +.Ar string +in its command somewhere. +Both forms require the result to be unambiguous. +For this purpose the +.Dq command +is that shown in the output from the +.Ic jobs +command, not the original command line. +.Pp +The +.Ic bg , +.Ic fg , +.Ic jobid , +.Ic jobs , +.Ic kill , +and +.Ic wait +commands all accept job identifiers as arguments, in addition to +process identifiers (larger integers). +See the +.Sx Built-ins +section above, and +.Xr kill 1 , +for more details of those commands. +In addition, a job identifier +(using one of the +.Dq \&% forms ) +issued as a command, without arguments, is interpreted as +if it had been given as the argument to the +.Ic fg +command. +.Pp +To cause a foreground process to stop, enter the terminal's +.Ic stop +character (usually control-Z). +To cause a background process to stop, send it a +.Dv STOP +signal, using the kill command. +A useful function to define is +.Bd -literal -offset indent +stop() { kill -s STOP "${@:-%%}"; } +.Ed +.Pp +The +.Ic fg +command resumes a stopped job, placing it in the foreground, +and +.Ic bg +resumes a stopped job in the background. +The +.Ic jobid +command provides information about process identifiers, job identifiers, +and the process group identifier, for a job. +.Pp +Whenever a sub-shell is created, the jobs table becomes invalid +(the sub-shell has no children.) +However, to enable uses like +.Bd -literal -offset indent +PID=$(jobid -p %1) +.Ed +.Pp +the table is only actually cleared in a sub-shell when needed to +create the first job there (built-in commands run in the foreground +do not create jobs.) +Note that in this environment, there is no useful current job +.Dq ( Li \&%% +actually refers to the sub-shell itself, but is not accessible) +but the job which is the current job in the parent can be accessed as +.Dq Li \&%\- . +.\" +.\" +.Ss Command Line Editing +.\" +When +.Nm +is being used interactively from a terminal, the current command +and the command history (see +.Ic fc +in the +.Sx Built-ins +section) +can be edited using emacs-mode or vi-mode command-line editing. +The command +.Ql set -o emacs +(or +.Fl E +option) +enables emacs-mode editing. +The command +.Ql set -o vi +(or +.Fl V +option) +enables vi-mode editing and places the current shell process into +vi insert mode. +(See the +.Sx Argument List Processing +section above.) +.Pp +The vi-mode uses commands similar to a subset of those described in the +.Xr vi 1 +man page. +With vi-mode +enabled, +.Nm sh +can be switched between insert mode and command mode. +It's similar to +.Ic vi : +pressing the +.Aq ESC +key will throw you into vi command mode. +Pressing the +.Aq return +key while in command mode will pass the line to the shell. +.Pp +The emacs-mode uses commands similar to a subset available in the +.Ic emacs +editor. +With emacs-mode enabled, special keys can be used to modify the text +in the buffer using the control key. +.Pp +.Nm +uses the +.Xr editline 3 +library. +See +.Xr editline 7 +for a list of the possible command bindings, +and the default settings in emacs and vi modes. +Also see +.Xr editrc 5 +for the commands that can be given to configure +.Xr editline 7 +in the file named by the +.Ev EDITRC +parameter, +or a file used with the +.Ic inputrc +built-in command, +or using +.Xr editline 7 Ap s +configuration command line. +.Pp +When command line editing is enabled, the +.Xr editline 7 +functions control printing of the +.Ev PS1 +and +.Ev PS2 +prompts when required. +As, in this mode, the command line editor needs to +keep track of what characters are in what position on +the command line, care needs to be taken when setting +the prompts. +Normal printing characters are handled automatically, +however mode setting sequences, which do not actually display +on the terminal, need to be identified to +.Xr editline 7 . +This is done, when needed, by choosing a character that +is not needed anywhere in the prompt, including in the mode +setting sequences, any single character is acceptable, +and assigning it to the shell parameter +.Dv PSlit . +Then that character should be used, in pairs, in the +prompt string. +Between each pair of +.Dv PSlit +characters are mode setting sequences, which affect the printing +attributes of the following (normal) characters of the prompt, +but do not themselves appear visibly, nor change the terminal's +cursor position. +.Pp +Each such sequence, that is +.Dv PSlit +character, mode setting character sequence, and another +.Dv PSlit +character, must currently be followed by at least one following +normal prompt character, or it will be ignored. +That is, a +.Dv PSlit +character cannot be the final character of +.Ev PS1 +or +.Ev PS2 , +nor may two +.Dv PSlit +delimited sequences appear adjacent to each other. +Each sequence can contain as many mode altering sequences as are +required however. +Only the first character from +.Dv PSlit +will be used. +When set +.Dv PSlit +should usually be set to a string containing just one +character, then it can simply be embedded in +.Ev PS1 +(or +.Ev PS2 ) +as in +.Pp +.D1 Li PS1=\*q${PSlit} Ns Ar mset\^ Ns Li ${PSlit}XYZ${PSlit} Ns Ar mclr\^ Ns Li ${PSlit}ABC\*q +.Pp +The prompt visible will be +.Dq XYZABC +with the +.Dq XYZ +part shown according as defined by the mode setting characters +.Ar mset , +and then cleared again by +.Ar mclr . +See +.Xr tput 1 +for one method to generate appropriate mode sequences. +Note that both parts, XYZ and ABC, must each contain at least one +character. +.Pp +If +.Dv PSlit +is unset, which is its initial state, or set to a null string, +no literal character will be defined, +and all characters of the prompt strings will be assumed +to be visible characters (which includes spaces etc.) +To allow smooth use of prompts, without needing redefinition, when +.Xr editline 7 +is disabled, the character chosen should be one which will be +ignored by the terminal if received, as when +.Xr editline 7 +is not in use, the prompt strings are simply written to the terminal. +For example, setting: +.\" XXX: PS1 line is too long for -offset indent +.Bd -literal -offset left + PSlit="$(printf\ '\e1')" + PS1="${PSlit}$(tput\ bold\ blink)${PSlit}\e$${PSlit}$(tput\ sgr0)${PSlit}\ " +.Ed +.Pp +will arrange for the primary prompt to be a bold blinking dollar sign, +if supported by the current terminal, followed by an (ordinary) space, +and, as the SOH (control-A) character +.Pq Sq \e1 +will not normally affect +a terminal, this same prompt will usually work with +.Xr editline 7 +enabled or disabled. +.Sh ENVIRONMENT +.Bl -tag -width MAILCHECK +.It Ev CDPATH +The search path used with the +.Ic cd +built-in. +.It Ev EDITRC +Gives the name of the file containing commands for +.Xr editline 7 . +See +.Xr editrc 5 +for possible content and format. +The file is processed, when in interactive mode with +command line editing enabled, whenever +.Ev EDITRC +is set (even with no actual value change,) +and if command line editing changes from disabled to enabled, +or the editor style used is changed. +(See the +.Fl E +and +.Fl V +options of the +.Ic set +built-in command, described in +.Sx Built-ins +above, which are documented further above in +.Sx Argument List Processing . ) +If unset +.Dq $HOME/.editrc +is used. +.It Ev ENV +Names the file sourced at startup by the shell. +Unused by this shell after initialization, +but is usually passed through the environment to +descendant shells. +.It Ev EUSER +Set to the login name of the effective user id running the shell, +as returned by +.Bd -compact -literal -offset indent +getpwuid(geteuid())->pw_name +.Ed +.Po +See +.Xr getpwuid 3 +and +.Xr geteuid 2 +for more details. +.Pc +This is obtained each time +.Ev EUSER +is expanded, so changes to the shell's execution identity +cause updates without further action. +If unset, it returns nothing. +If set it loses its special properties, and is simply a variable. +.It Ev HISTSIZE +The number of lines in the history buffer for the shell. +.It Ev HOME +Set automatically by +.Xr login 1 +from the user's login directory in the password file +.Pq Xr passwd 5 . +This environment variable also functions as the default argument for the +.Ic cd +built-in. +.It Ev HOSTNAME +Set to the current hostname of the system, as returned by +.Xr gethostname 3 . +This is obtained each time +.Ev HOSTNAME +is expanded, so changes to the system's name are reflected +without further action. +If unset, it returns nothing. +If set it loses its special properties, and is simply a variable. +.It Ev IFS +Input Field Separators. +This is normally set to +.Aq space , +.Aq tab , +and +.Aq newline . +See the +.Sx White Space Splitting +section for more details. +.It Ev LANG +The string used to specify localization information that allows users +to work with different culture-specific and language conventions. +See +.Xr nls 7 . +.It Dv LINENO +The current line number in the script or function. +See the section +.Sx LINENO +below for more details. +.It Ev MAIL +The name of a mail file, that will be checked for the arrival of new mail. +Overridden by +.Ev MAILPATH . +The check occurs just before +.Ev PS1 +is written, immediately after reporting jobs which have changed status, +in interactive shells only. +New mail is considered to have arrived if the monitored file +has increased in size since the last check. +.\" .It Ev MAILCHECK +.\" The frequency in seconds that the shell checks for the arrival of mail +.\" in the files specified by the +.\" .Ev MAILPATH +.\" or the +.\" .Ev MAIL +.\" file. +.\" If set to 0, the check will occur at each prompt. +.It Ev MAILPATH +A colon +.Dq \&: +separated list of file names, for the shell to check for incoming mail. +This environment setting overrides the +.Ev MAIL +setting. +There is a maximum of 10 mailboxes that can be monitored at once. +.It Ev PATH +The default search path for executables. +See the +.Sx Path Search +section above. +.It Ev POSIXLY_CORRECT +If set in the environment upon initialization of the shell, +then the shell option +.Ic posix +will be set. +.Po +See the description of the +.Ic set +command in the +.Sx Built-ins +section. +.Pc +After initialization it is unused by the shell, +but is usually passed through the environment to +descendant processes, including other instances of the shell, +which may interpret it in a similar way. +.It Ev PPID +The process identified of the parent process of the +current shell. +This value is set at shell startup, ignoring +any value in the environment, and then made readonly. +.It Ev PS1 +The primary prompt string, which defaults to +.Dq Li "$ " , +unless you are the superuser, in which case it defaults to +.Dq Li "# " . +This string is subject to parameter, arithmetic, and if +enabled by setting the +.Ic promptcmds +option, command substitution before being output. +During execution of commands used by command substitution, +execution tracing, the +.Ic xtrace +.Ic ( set Fl x ) +option is temporarily disabled. +If +.Ic promptcmds +is not set and the prompt string uses command substitution, +the prompt used will be an appropriate error string. +For other expansion errors, a message will be output, +and the unexpanded string will then be used as the prompt. +.It Ev PS2 +The secondary prompt string, which defaults to +.Dq Li "> " . +After expansion (as for +.Ev PS1 ) +it is written whenever more input is required to complete the +current command. +.It Ev PS4 +Output, after expansion like +.Ev PS1 , +before each line when execution trace +.Ic ( set Fl x ) +is enabled. +.Ev PS4 +defaults to +.Dq Li "+ " . +.It Ev PSc +Initialized by the shell, ignoring any value from the environment, +to a single character string, either +.Sq \&# +or +.Sq \&$ , +depending upon whether the current user is the superuser or not. +This is intended for use when building a custom +.Ev PS1 . +.It Ev PSlit +Defines the character which may be embedded in pairs, in +.Ev PS1 +or +.Ev PS2 +to indicate to +.Xr editline 7 +that the characters between each pair of occurrences of the +.Dv PSlit +character will not appear in the visible prompt, and will not +cause the terminal's cursor to change position, but rather set terminal +attributes for the following prompt character(s) at least one of +which must be present. +See +.Sx Command Line Editing +above for more information. +.It Ev RANDOM +Returns a different pseudo-random integer, +in the range [0,32767] each time it is accessed. +.Ev RANDOM +can be assigned an integer value to seed the PRNG. +If the value assigned is a constant, then the +sequence of values produces on subsequent references of +.Ev RANDOM +will repeat after the next time the same constant is assigned. +Note, this is not guaranteed to remain constant from one version +of the shell to another \(en the PRNG algorithm, or seeding +method is subject to change. +If +.Ev RANDOM +is assigned an empty value (null string) then the next time +.Ev RANDOM +is accessed, it will be seeded from a more genuinely random source. +The sequence of pseudo-random numbers generated will not be able to +be generated again (except by luck, whether good or bad, depends!) +This is also how the initial seed is generated, if none has been +assigned before +.Ev RANDOM +is first accessed after shell initialization. +Should the error message +.Dq "RANDOM initialisation failed" +appear on standard error, it indicates that the source +of good random numbers was not available, and +.Ev RANDOM +has instead been seeded with a more predictable value. +The following sequence of random numbers will +not be as unpredictable as they otherwise would be. +.It Ev SECONDS +Returns the number of seconds since the current shell was started. +If unset, it remains unset, and returns nothing, unless set again. +If set, it loses its special properties, and becomes a normal variable. +.It Ev START_TIME +Initialized by the shell to the number of seconds since the Epoch +(see +.Xr localtime 3 ) +when the shell was started. +The value of +.Dl $(( Ns Ev START_TIME + Ev SECONDS Ns )) +represents the current time, if +.Ev START_TIME +has not been modified, and +.Ev SECONDS +has not been set or unset. +.It Ev TERM +The default terminal setting for the shell. +This is inherited by +children of the shell, and is used in the history editing modes. +.\" This is explicitly last, not in sort order - please leave! +.It Ev ToD +When referenced, uses the value of +.Ev ToD_FORMAT +(or +.Dq \&%T +if +.Ev ToD_FORMAT +is unset) as the format argument to +.Xr strftime 3 +to encode the current time of day, in the time zone +defined by +.Ev TZ +if set, or current local time if not, and returns the result. +If unset +.Ev ToD +returns nothing. +If set, it loses its special properties, and becomes a normal variable. +.It Ev ToD_FORMAT +Can be set to the +.Xr strftime 3 +format string to be used when expanding +.Ev ToD . +Initially unset. +.It Ev TZ +If set, gives the time zone +(see +.Xr localtime 3 , +.Xr environ 7 ) +to use when formatting +.Ev ToD +and if exported, other utilities that deal with times. +If unset, the system's local wall clock time zone is used. +.It Ev NETBSD_SHELL +Unlike the variables previously mentioned, +this variable is somewhat strange, +in that it cannot be set, +inherited from the environment, +modified, or exported from the shell. +If set, by the shell, it indicates that the shell is the +.Ic sh +defined by this manual page, and gives its version information. +It can also give information in additional space separated words, +after the version string. +If the shell was built as part of a reproducible build, +the relevant date that was used for that build will be included. +Finally, any non-standard compilation options, +which may affect features available, +that were used when building the shell will be listed. +.Ev NETBSD_SHELL +behaves like any other variable that has the read-only +and un-exportable attributes set. +.El +.Ss Dv LINENO +.Dv LINENO +is in many respects a normal shell variable, containing an +integer value. and can be expanded using any of the forms +mentioned above which can be used for any other variable. +.Pp +.Dv LINENO +can be exported, made readonly, or unset, as with any other +variable, with similar effects. +Note that while being readonly prevents later attempts to +set, or unset, +.Dv LINENO , +it does not prevent its value changing. +References to +.Dv LINENO +.Pq "when not unset" +always obtain the current line number. +However, +.Dv LINENO +should normally not ever be set or unset. +In this shell setting +.Dv LINENO +reverses the effect of an earlier +.Ic unset , +but does not otherwise affect the value obtained. +If unset, +.Dv LINENO +should not normally be set again, doing so is not portable. +If +.Dv LINENO +is set or unset, different shells act differently. +The value of +.Dv LINENO +is never imported from the environment when the shell is +started, though if present there, as with any other variable, +.Dv LINENO +will be exported by this shell. +.Pp +.Dv LINENO +is set automatically by the shell to be the number of the source +line on which it occurs. +When exported, +.Dv LINENO +is exported with its value set to the line number it would have +had had it been referenced on the command line of the command to +which it is exported. +Line numbers are counted from 1, which is the first line the shell +reads from any particular file. +For this shell, standard input, including in an interactive shell, +the user's terminal, is just another file and lines are counted +there as well. +However note that not all shells count interactive +lines this way, it is not wise to rely upon +.Dv LINENO +having a useful value, except in a script, or a function. +.Pp +The role of +.Dv LINENO +in functions is less clear. +In some shells, +.Dv LINENO +continues to refer to the line number in the script which defines +the function, +in others lines count from one within the function, always (and +resume counting normally once the function definition is complete) +and others count in functions from one if the function is defined +interactively, but otherwise just reference the line number in the +script in which the function is defined. +This shell gives the user the option to choose. +If the +.Fl L +flag (the +.Ic local_lineno +option, see +.Sx Argument List Processing ) +is set, when the function is defined, then the function +defaults to counting lines with one being the first line of the +function. +When the +.Fl L +flag is not set, the shell counts lines in a function definition +in the same continuous sequence as the lines that surround the +function definition. +Further, if +.Dv LINENO +is made local +(see +.Sx Built-ins +above) +inside the function, the function can decide which +behavior it prefers. +If +.Dv LINENO +is made local and inherited, and not given a value, as in +.Dl local Fl I Dv LINENO +then from that point in the function, +.Dv LINENO +will give the line number as if lines are counted in sequence +with the lines that surround the function definition (and +any other function definitions in which this is nested.) +If +.Dv LINENO +is made local, and in that same command, given a value, as +.Dl local Oo Fl I Ns | Ns Fl N Oc Dv LINENO Ns = Ns Ar value +then +.Dv LINENO +will give the line number as if lines are counted from one +from the beginning of the function. +The value nominally assigned in this case is irrelevant, and ignored. +For completeness, if lineno is made local and unset, as in +.Dl local Fl N Dv LINENO +then +.Dv LINENO +is simply unset inside the function, and gives no value at all. +.Pp +Now for some technical details. +The line on which +.Dv LINENO +occurs in a parameter expansion, is the line that contains the +.Sq \&$ +that begins the expansion of +.Dv LINENO . +In the case of nested expansions, that +.Sq \&$ +is the one that actually has +.Dv LINENO +as its parameter. +In an arithmetic expansion, where no +.Sq \&$ +is used to evaluate +.Dv LINENO +but +.Dv LINENO +is simply referenced as a variable, then the value is the +line number of the line that contains the +.Sq L +of +.Dv LINENO . +For functions line one of the function definition (when relevant) +is the line that contains the first character of the +function name in the definition. +When exported, the line number of the command is the line number +where the first character of the word which becomes the command name occurs. +.Pp +When the shell opens a new file, for any reason, +it counts lines from one in that file, +and then resumes its original counting once it resumes reading the +previous input stream. +When handling a string passed to +.Ic eval +the line number starts at the line on which the string starts, +and then if the string contains internal newline characters, +those characters increase the line number. +This means that references to +.Dv LINENO +in such a case can produce values larger than would be +produced by a reference on the line after the +.Ic eval . +.Sh FILES +.Bl -item +.It +.Pa $HOME/.profile +.It +.Pa /etc/profile +.El +.Sh EXIT STATUS +Errors that are detected by the shell, such as a syntax error, will cause the +shell to exit with a non-zero exit status. +If the shell is not an +interactive shell, the execution of the shell file will be aborted. +Otherwise +the shell will return the exit status of the last command executed, or +if the exit built-in is used with a numeric argument, it will return the +argument. +.Sh SEE ALSO +.Xr csh 1 , +.Xr echo 1 , +.Xr getopt 1 , +.Xr ksh 1 , +.Xr login 1 , +.Xr printf 1 , +.Xr test 1 , +.Xr editline 3 , +.Xr getopt 3 , +.\" .Xr profile 4 , +.Xr editrc 5 , +.Xr passwd 5 , +.Xr editline 7 , +.Xr environ 7 , +.Xr nls 7 , +.Xr sysctl 8 +.Sh HISTORY +A +.Nm +command appeared in +.At v1 . +It was replaced in +.At v7 +with a version that introduced the basis of the current syntax. +That was, however, unmaintainable so we wrote this one. +.Sh BUGS +Setuid shell scripts should be avoided at all costs, as they are a +significant security risk. +.Pp +The characters generated by filename completion should probably be quoted +to ensure that the filename is still valid after the input line has been +processed. +.Pp +Job control of compound statements (loops, etc) is a complete mess. +.Pp +Many, many, more. +(But less than there were...) diff --git a/bin/sh/shell.h b/bin/sh/shell.h new file mode 100644 index 0000000..318d56e --- /dev/null +++ b/bin/sh/shell.h @@ -0,0 +1,224 @@ +/* $NetBSD: shell.h,v 1.29 2019/01/22 13:48:28 kre Exp $ */ + +/*- + * 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. + * + * @(#)shell.h 8.2 (Berkeley) 5/4/95 + */ + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define BSD if you are running 4.2 BSD or later. + * define SYSV if you are running under System V. + * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) + * define DEBUG=2 to compile in and enable debugging. + * define DEBUG=3 for DEBUG==2 + enable most standard debug output + * define DEBUG=4 for DEBUG==2 + enable absolutely everything + * define DO_SHAREDVFORK to indicate that vfork(2) shares its address + * with its parent. + * define BOGUS_NOT_COMMAND to allow ! reserved words in weird places + * (traditional ash behaviour.) + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + +#ifndef SHELL_H +#define SHELL_H +#include <sys/param.h> + +#define JOBS 1 +#ifndef BSD +#define BSD 1 +#endif + +#ifndef DO_SHAREDVFORK +#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104000000 +#define DO_SHAREDVFORK +#endif +#endif + +typedef void *pointer; +#ifndef NULL +#define NULL (void *)0 +#endif +#ifndef STATIC +#define STATIC /* empty */ +#endif +#define MKINIT /* empty */ + +#include <sys/cdefs.h> + +extern const char nullstr[1]; /* null string */ + +#ifdef SMALL +#undef DEBUG +#endif + +#ifdef DEBUG + +extern uint64_t DFlags; +extern int ShNest; + +/* + * This is selected as there are 26 letters in ascii - not that that + * matters for anything, just makes it easier to assign a different + * command letter to each debug option. We currently use only 18 + * so this could be reduced, but that is of no real benefit. It can also + * be increased, but that both limits the maximum value tha can be + * used with DBG_EXTRAS(), and causes problems with verbose option naming. + */ +#define DBG_VBOSE_SHIFT 27 +#define DBG_EXTRAS(n) ((DBG_VBOSE_SHIFT * 2) + (n)) + +/* + * Macros to enable tracing, so the mainainer can control + * just how much debug output is dumped to the trace file + * + * In the X forms, "xtra" can be any legal C statement(s) without (bare) commas + * executed if the relevant debug flag is enabled, after any tracing output. + */ +#define CTRACE(when, param) do { \ + if ((DFlags & (when)) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define CCTRACE(when,cond,param) do { \ + if ((cond) && (DFlags & (when)) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define CTRACEV(when, param) do { \ + if ((DFlags & (when)) != 0) \ + tracev param; \ + } while (/*CONSTCOND*/ 0) + +#define XTRACE(when, param, xtra) do { \ + if ((DFlags & (when)) != 0) { \ + trace param; \ + xtra; \ + } \ + } while (/*CONSTCOND*/ 0) + +#define VTRACE(when, param) do { \ + if ((DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define CVTRACE(when,cond,param) do { \ + if ((cond) && (DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) \ + trace param; \ + } while (/*CONSTCOND*/ 0) + +#define VTRACEV(when, param) do { \ + if ((DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) \ + tracev param; \ + } while (/*CONSTCOND*/ 0) + +#define VXTRACE(when, param, xtra) do { \ + if ((DFlags & \ + (when)<<DBG_VBOSE_SHIFT) != 0) {\ + trace param; \ + xtra; \ + } \ + } while (/*CONSTCOND*/ 0) + +#define SHELL_FORKED() ShNest++ +#define VFORK_BLOCK { const int _ShNest = ShNest; +#define VFORK_END } +#define VFORK_UNDO() ShNest = _ShNest + +#define DBG_ALWAYS (1LL << 0) +#define DBG_PARSE (1LL << 1) /* r (read commands) */ +#define DBG_EVAL (1LL << 2) /* e */ +#define DBG_EXPAND (1LL << 3) /* x */ +#define DBG_JOBS (1LL << 4) /* j */ +#define DBG_PROCS (1LL << 5) /* p */ +#define DBG_REDIR (1LL << 6) /* f (fds) */ +#define DBG_CMDS (1LL << 7) /* c */ +#define DBG_ERRS (1LL << 8) /* z (?) */ +#define DBG_WAIT (1LL << 9) /* w */ +#define DBG_TRAP (1LL << 10) /* t */ +#define DBG_VARS (1LL << 11) /* v */ +#define DBG_INPUT (1LL << 12) /* i */ +#define DBG_OUTPUT (1LL << 13) /* o */ +#define DBG_MEM (1LL << 14) /* m */ +#define DBG_ARITH (1LL << 15) /* a */ +#define DBG_HISTORY (1LL << 16) /* h */ +#define DBG_SIG (1LL << 17) /* s */ +#define DBG_MATCH (1LL << 18) /* g (glob) */ +#define DBG_LEXER (1LL << 19) /* l */ + +/* + * reserved extras: b=builtins y=alias + * still free: d k n q u + */ + + /* use VTRACE(DBG_ALWAYS, (...)) to test this one */ +#define DBG_VERBOSE (1LL << DBG_VBOSE_SHIFT) + + /* DBG_EXTRAS 0 .. 9 (max) only - non-alpha options (no VTRACE !!) */ +#define DBG_U0 (1LL << DBG_EXTRAS(0)) /* 0 - ad-hoc extra flags */ +#define DBG_U1 (1LL << DBG_EXTRAS(1)) /* 1 - for short term */ +#define DBG_U2 (1LL << DBG_EXTRAS(2)) /* 2 - extra tracing */ +#define DBG_U3 (1LL << DBG_EXTRAS(3)) /* 3 - when needed */ + /* 4, 5, & 6 currently free */ +#define DBG_LINE (1LL << DBG_EXTRAS(7)) /* @ ($LINENO) */ +#define DBG_PID (1LL << DBG_EXTRAS(8)) /* $ ($$) */ +#define DBG_NEST (1LL << DBG_EXTRAS(9)) /* ^ */ + +/* 26 lower case, 26 upper case, always, verbose, and 10 extras: 64 bits */ + +extern void set_debug(const char *, int); + +#else /* DEBUG */ + +#define CTRACE(when, param) /* conditional normal trace */ +#define CCTRACE(when, cond, param) /* more conditional normal trace */ +#define CTRACEV(when, param) /* conditional varargs trace */ +#define XTRACE(when, param, extra) /* conditional trace, plus more */ +#define VTRACE(when, param) /* conditional verbose trace */ +#define CVTRACE(when, cond, param) /* more conditional verbose trace */ +#define VTRACEV(when, param) /* conditional verbose varargs trace */ +#define VXTRACE(when, param, extra) /* cond verbose trace, plus more */ + +#define SHELL_FORKED() +#define VFORK_BLOCK +#define VFORK_END +#define VFORK_UNDO() + +#endif /* DEBUG */ + +#endif /* SHELL_H */ diff --git a/bin/sh/show.c b/bin/sh/show.c new file mode 100644 index 0000000..fa9f5aa --- /dev/null +++ b/bin/sh/show.c @@ -0,0 +1,1175 @@ +/* $NetBSD: show.c,v 1.52 2019/01/22 13:48:28 kre Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 2017 The NetBSD Foundation, Inc. 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: show.c,v 1.52 2019/01/22 13:48:28 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/uio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.h" +#include "options.h" +#include "redir.h" +#include "error.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "builtins.h" + +#define DEFINE_NODENAMES +#include "nodenames.h" /* does almost nothing if !defined(DEBUG) */ + +#define TR_STD_WIDTH 60 /* tend to fold lines wider than this */ +#define TR_IOVECS 10 /* number of lines or trace (max) / write */ + +typedef struct traceinfo { + int tfd; /* file descriptor for open trace file */ + int nxtiov; /* the buffer we should be writing to */ + char lastc; /* the last non-white character output */ + uint8_t supr; /* char classes to suppress after \n */ + pid_t pid; /* process id of process that opened that file */ + size_t llen; /* number of chars in current output line */ + size_t blen; /* chars used in current buffer being filled */ + char * tracefile; /* name of the tracefile */ + struct iovec lines[TR_IOVECS]; /* filled, flling, pending buffers */ +} TFILE; + +/* These are auto turned off when non white space is printed */ +#define SUP_NL 0x01 /* don't print \n */ +#define SUP_SP 0x03 /* suppress spaces */ +#define SUP_WSP 0x04 /* suppress all white space */ + +#ifdef DEBUG /* from here to end of file ... */ + +TFILE tracedata, *tracetfile; +FILE *tracefile; /* just for histedit */ + +uint64_t DFlags; /* currently enabled debug flags */ +int ShNest; /* depth of shell (internal) nesting */ + +static void shtree(union node *, int, int, int, TFILE *); +static void shcmd(union node *, TFILE *); +static void shsubsh(union node *, TFILE *); +static void shredir(union node *, TFILE *, int); +static void sharg(union node *, TFILE *); +static void indent(int, TFILE *); +static void trstring(const char *); +static void trace_putc(char, TFILE *); +static void trace_puts(const char *, TFILE *); +static void trace_flush(TFILE *, int); +static char *trace_id(TFILE *); +static void trace_fd_swap(int, int); + +inline static int trlinelen(TFILE *); + + +/* + * These functions are the externally visible interface + * (but only for a DEBUG shell.) + */ + +void +opentrace(void) +{ + char *s; + int fd; + int i; + pid_t pid; + + if (debug != 1) { + /* leave fd open because libedit might be using it */ + if (tracefile) + fflush(tracefile); + if (tracetfile) + trace_flush(tracetfile, 1); + return; + } +#if DBG_PID == 1 /* using old shell.h, old tracing method */ + DFlags = DBG_PID; /* just force DBG_PID on, and leave it ... */ +#endif + pid = getpid(); + if (asprintf(&s, "trace.%jd", (intmax_t)pid) <= 0) { + debug = 0; + error("Cannot asprintf tracefilename"); + }; + + fd = open(s, O_WRONLY|O_APPEND|O_CREAT, 0666); + if (fd == -1) { + debug = 0; + error("Can't open tracefile: %s (%s)\n", s, strerror(errno)); + } + fd = to_upper_fd(fd); + if (fd <= 2) { + (void) close(fd); + debug = 0; + error("Attempt to use fd %d as tracefile thwarted\n", fd); + } + register_sh_fd(fd, trace_fd_swap); + + /* + * This stuff is just so histedit has a FILE * to use + */ + if (tracefile) + (void) fclose(tracefile); /* also closes tfd */ + tracefile = fdopen(fd, "a"); /* don't care if it is NULL */ + if (tracefile) /* except here... */ + setlinebuf(tracefile); + + /* + * Now the real tracing setup + */ + if (tracedata.tfd > 0 && tracedata.tfd != fd) + (void) close(tracedata.tfd); /* usually done by fclose() */ + + tracedata.tfd = fd; + tracedata.pid = pid; + tracedata.nxtiov = 0; + tracedata.blen = 0; + tracedata.llen = 0; + tracedata.lastc = '\0'; + tracedata.supr = SUP_NL | SUP_WSP; + +#define replace(f, v) do { \ + if (tracedata.f != NULL) \ + free(tracedata.f); \ + tracedata.f = v; \ + } while (/*CONSTCOND*/ 0) + + replace(tracefile, s); + + for (i = 0; i < TR_IOVECS; i++) { + replace(lines[i].iov_base, NULL); + tracedata.lines[i].iov_len = 0; + } + +#undef replace + + tracetfile = &tracedata; + + trace_puts("\nTracing started.\n", tracetfile); +} + +void +trace(const char *fmt, ...) +{ + va_list va; + char *s; + + if (debug != 1 || !tracetfile) + return; + va_start(va, fmt); + (void) vasprintf(&s, fmt, va); + va_end(va); + + trace_puts(s, tracetfile); + free(s); + if (tracetfile->llen == 0) + trace_flush(tracetfile, 0); +} + +void +tracev(const char *fmt, va_list va) +{ + va_list ap; + char *s; + + if (debug != 1 || !tracetfile) + return; + va_copy(ap, va); + (void) vasprintf(&s, fmt, ap); + va_end(ap); + + trace_puts(s, tracetfile); + free(s); + if (tracetfile->llen == 0) + trace_flush(tracetfile, 0); +} + + +void +trputs(const char *s) +{ + if (debug != 1 || !tracetfile) + return; + trace_puts(s, tracetfile); +} + +void +trputc(int c) +{ + if (debug != 1 || !tracetfile) + return; + trace_putc(c, tracetfile); +} + +void +showtree(union node *n) +{ + TFILE *fp; + + if ((fp = tracetfile) == NULL) + return; + + trace_puts("showtree(", fp); + if (n == NULL) + trace_puts("NULL", fp); + else if (n == NEOF) + trace_puts("NEOF", fp); + else + trace("%p", n); + trace_puts(") called\n", fp); + if (n != NULL && n != NEOF) + shtree(n, 1, 1, 1, fp); +} + +void +trargs(char **ap) +{ + if (debug != 1 || !tracetfile) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + trace_putc(' ', tracetfile); + } + trace_putc('\n', tracetfile); +} + +void +trargstr(union node *n) +{ + sharg(n, tracetfile); +} + + +/* + * Beyond here we just have the implementation of all of that + */ + + +inline static int +trlinelen(TFILE * fp) +{ + return fp->llen; +} + +static void +shtree(union node *n, int ind, int ilvl, int nl, TFILE *fp) +{ + struct nodelist *lp; + const char *s; + + if (n == NULL) { + if (nl) + trace_putc('\n', fp); + return; + } + + indent(ind, fp); + switch (n->type) { + case NSEMI: + s = NULL; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, 0, ilvl, 0, fp); + if (s != NULL) + trace_puts(s, fp); + if (trlinelen(fp) >= TR_STD_WIDTH) { + trace_putc('\n', fp); + indent(ind < 0 ? 2 : ind + 1, fp); + } else if (s == NULL) { + if (fp->lastc != '&') + trace_puts("; ", fp); + else + trace_putc(' ', fp); + } + shtree(n->nbinary.ch2, 0, ilvl, nl, fp); + break; + case NCMD: + shcmd(n, fp); + if (n->ncmd.backgnd) + trace_puts(" &", fp); + if (nl && trlinelen(fp) > 0) + trace_putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shtree(lp->n, 0, ilvl, 0, fp); + if (lp->next) { + trace_puts(" |", fp); + if (trlinelen(fp) >= TR_STD_WIDTH) { + trace_putc('\n', fp); + indent((ind < 0 ? ilvl : ind) + 1, fp); + } else + trace_putc(' ', fp); + } + } + if (n->npipe.backgnd) + trace_puts(" &", fp); + if (nl || trlinelen(fp) >= TR_STD_WIDTH) + trace_putc('\n', fp); + break; + case NBACKGND: + case NSUBSHELL: + shsubsh(n, fp); + if (n->type == NBACKGND) + trace_puts(" &", fp); + if (nl && trlinelen(fp) > 0) + trace_putc('\n', fp); + break; + case NDEFUN: + trace_puts(n->narg.text, fp); + trace_puts("() {\n", fp); + indent(ind, fp); + shtree(n->narg.next, (ind < 0 ? ilvl : ind) + 1, ilvl+1, 1, fp); + indent(ind, fp); + trace_puts("}\n", fp); + break; + case NDNOT: + trace_puts("! ", fp); + /* FALLTHROUGH */ + case NNOT: + trace_puts("! ", fp); + shtree(n->nnot.com, -1, ilvl, nl, fp); + break; + case NREDIR: + shtree(n->nredir.n, -1, ilvl, 0, fp); + shredir(n->nredir.redirect, fp, n->nredir.n == NULL); + if (nl) + trace_putc('\n', fp); + break; + + case NIF: + itsif: + trace_puts("if ", fp); + shtree(n->nif.test, -1, ilvl, 0, fp); + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_puts(" ;", fp); + } else + indent(ilvl, fp); + trace_puts(" then ", fp); + if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) + indent(ilvl+1, fp); + shtree(n->nif.ifpart, -1, ilvl + 1, 0, fp); + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_puts(" ;", fp); + } else + indent(ilvl, fp); + if (n->nif.elsepart && n->nif.elsepart->type == NIF) { + if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) + indent(ilvl, fp); + n = n->nif.elsepart; + trace_puts(" el", fp); + goto itsif; + } + if (n->nif.elsepart) { + if (nl || trlinelen(fp) > TR_STD_WIDTH - 24) + indent(ilvl+1, fp); + trace_puts(" else ", fp); + shtree(n->nif.elsepart, -1, ilvl + 1, 0, fp); + if (fp->lastc != '&') + trace_puts(" ;", fp); + } + trace_puts(" fi", fp); + if (nl) + trace_putc('\n', fp); + break; + + case NWHILE: + trace_puts("while ", fp); + goto aloop; + case NUNTIL: + trace_puts("until ", fp); + aloop: + shtree(n->nbinary.ch1, -1, ilvl, 0, fp); + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_puts(" ;", fp); + } else + trace_putc('\n', fp); + trace_puts(" do ", fp); + shtree(n->nbinary.ch1, -1, ilvl + 1, 1, fp); + trace_puts(" done ", fp); + if (nl) + trace_putc('\n', fp); + break; + + case NFOR: + trace_puts("for ", fp); + trace_puts(n->nfor.var, fp); + if (n->nfor.args) { + union node *argp; + + trace_puts(" in ", fp); + for (argp = n->nfor.args; argp; argp=argp->narg.next) { + sharg(argp, fp); + trace_putc(' ', fp); + } + if (trlinelen(fp) > 0 && trlinelen(fp) < TR_STD_WIDTH) { + if (fp->lastc != '&') + trace_putc(';', fp); + } else + trace_putc('\n', fp); + } + trace_puts(" do ", fp); + shtree(n->nfor.body, -1, ilvl + 1, 0, fp); + if (fp->lastc != '&') + trace_putc(';', fp); + trace_puts(" done", fp); + if (nl) + trace_putc('\n', fp); + break; + + case NCASE: + trace_puts("case ", fp); + sharg(n->ncase.expr, fp); + trace_puts(" in", fp); + if (nl) + trace_putc('\n', fp); + { + union node *cp; + + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + union node *patp; + + if (nl || trlinelen(fp) > TR_STD_WIDTH - 16) + indent(ilvl, fp); + else + trace_putc(' ', fp); + trace_putc('(', fp); + patp = cp->nclist.pattern; + while (patp != NULL) { + trace_putc(' ', fp); + sharg(patp, fp); + trace_putc(' ', fp); + if ((patp = patp->narg.next) != NULL) + trace_putc('|', fp); + } + trace_putc(')', fp); + if (nl) + indent(ilvl + 1, fp); + else + trace_putc(' ', fp); + shtree(cp->nclist.body, -1, ilvl+2, 0, fp); + if (cp->type == NCLISTCONT) + trace_puts(" ;&", fp); + else + trace_puts(" ;;", fp); + if (nl) + trace_putc('\n', fp); + } + } + if (nl) { + trace_putc('\n', fp); + indent(ind, fp); + } else + trace_putc(' ', fp); + trace_puts("esac", fp); + if (nl) + trace_putc('\n', fp); + break; + + default: { + char *str; + + asprintf(&str, "<node type %d [%s]>", n->type, + NODETYPENAME(n->type)); + trace_puts(str, fp); + free(str); + if (nl) + trace_putc('\n', fp); + } + break; + } +} + + +static void +shcmd(union node *cmd, TFILE *fp) +{ + union node *np; + int first; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + trace_putc(' ', fp); + sharg(np, fp); + first = 0; + } + shredir(cmd->ncmd.redirect, fp, first); +} + +static void +shsubsh(union node *cmd, TFILE *fp) +{ + trace_puts(" ( ", fp); + shtree(cmd->nredir.n, -1, 3, 0, fp); + trace_puts(" ) ", fp); + shredir(cmd->ncmd.redirect, fp, 1); +} + +static void +shredir(union node *np, TFILE *fp, int first) +{ + const char *s; + int dftfd; + char buf[106]; + + for ( ; np ; np = np->nfile.next) { + if (! first) + trace_putc(' ', fp); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NXHERE: /* FALLTHROUGH */ + case NHERE: s = "<<"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) { + sprintf(buf, "%d", np->nfile.fd); + trace_puts(buf, fp); + } + trace_puts(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + if (np->ndup.vname) + sharg(np->ndup.vname, fp); + else { + if (np->ndup.dupfd < 0) + trace_puts("-", fp); + else { + sprintf(buf, "%d", np->ndup.dupfd); + trace_puts(buf, fp); + } + } + } else + if (np->nfile.type == NHERE || np->nfile.type == NXHERE) { + if (np->nfile.type == NHERE) + trace_putc('\\', fp); + trace_puts("!!!\n", fp); + s = np->nhere.doc->narg.text; + if (strlen(s) > 100) { + memmove(buf, s, 100); + buf[100] = '\0'; + strcat(buf, " ...\n"); + s = buf; + } + trace_puts(s, fp); + trace_puts("!!! ", fp); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + +static void +sharg(union node *arg, TFILE *fp) +{ + char *p, *s; + struct nodelist *bqlist; + int subtype = 0; + int quoted = 0; + + if (arg->type != NARG) { + asprintf(&s, "<node type %d> ! NARG\n", arg->type); + trace_puts(s, fp); + abort(); /* no need to free s, better not to */ + } + + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + trace_putc('\\', fp); + trace_putc(*++p, fp); + break; + + case CTLNONL: + trace_putc('\\', fp); + trace_putc('\n', fp); + break; + + case CTLVAR: + subtype = *++p; + if (!quoted != !(subtype & VSQUOTE)) + trace_putc('"', fp); + trace_putc('$', fp); + trace_putc('{', fp); /*}*/ + if ((subtype & VSTYPE) == VSLENGTH) + trace_putc('#', fp); + if (subtype & VSLINENO) + trace_puts("LINENO=", fp); + + while (*++p != '=') + trace_putc(*p, fp); + + if (subtype & VSNUL) + trace_putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + /* { */ + trace_putc('}', fp); + if (!quoted != !(subtype & VSQUOTE)) + trace_putc('"', fp); + break; + case VSMINUS: + trace_putc('-', fp); + break; + case VSPLUS: + trace_putc('+', fp); + break; + case VSQUESTION: + trace_putc('?', fp); + break; + case VSASSIGN: + trace_putc('=', fp); + break; + case VSTRIMLEFTMAX: + trace_putc('#', fp); + /* FALLTHROUGH */ + case VSTRIMLEFT: + trace_putc('#', fp); + break; + case VSTRIMRIGHTMAX: + trace_putc('%', fp); + /* FALLTHROUGH */ + case VSTRIMRIGHT: + trace_putc('%', fp); + break; + case VSLENGTH: + break; + default: { + char str[32]; + + snprintf(str, sizeof str, + "<subtype %d>", subtype); + trace_puts(str, fp); + } + break; + } + break; + case CTLENDVAR: + /* { */ + trace_putc('}', fp); + if (!quoted != !(subtype & VSQUOTE)) + trace_putc('"', fp); + subtype = 0; + break; + + case CTLBACKQ|CTLQUOTE: + if (!quoted) + trace_putc('"', fp); + /* FALLTHRU */ + case CTLBACKQ: + trace_putc('$', fp); + trace_putc('(', fp); + if (bqlist) { + shtree(bqlist->n, -1, 3, 0, fp); + bqlist = bqlist->next; + } else + trace_puts("???", fp); + trace_putc(')', fp); + if (!quoted && *p == (CTLBACKQ|CTLQUOTE)) + trace_putc('"', fp); + break; + + case CTLQUOTEMARK: + if (subtype != 0 || !quoted) { + trace_putc('"', fp); + quoted++; + } + break; + case CTLQUOTEEND: + trace_putc('"', fp); + quoted--; + break; + case CTLARI: + if (*p == ' ') + p++; + trace_puts("$(( ", fp); + break; + case CTLENDARI: + trace_puts(" ))", fp); + break; + + default: + if (*p == '$') + trace_putc('\\', fp); + trace_putc(*p, fp); + break; + } + } + if (quoted) + trace_putc('"', fp); +} + + +static void +indent(int amount, TFILE *fp) +{ + int i; + + if (amount <= 0) + return; + + amount <<= 2; /* indent slots -> chars */ + + i = trlinelen(fp); + fp->supr = SUP_NL; + if (i > amount) { + trace_putc('\n', fp); + i = 0; + } + fp->supr = 0; + for (; i < amount - 7 ; i++) { + trace_putc('\t', fp); + i |= 7; + } + while (i < amount) { + trace_putc(' ', fp); + i++; + } + fp->supr = SUP_WSP; +} + +static void +trace_putc(char c, TFILE *fp) +{ + char *p; + + if (c == '\0') + return; + if (debug == 0 || fp == NULL) + return; + + if (fp->llen == 0) { + if (fp->blen != 0) + abort(); + + if ((fp->supr & SUP_NL) && c == '\n') + return; + if ((fp->supr & (SUP_WSP|SUP_SP)) && c == ' ') + return; + if ((fp->supr & SUP_WSP) && c == '\t') + return; + + if (fp->nxtiov >= TR_IOVECS - 1) /* should be rare */ + trace_flush(fp, 0); + + p = trace_id(fp); + if (p != NULL) { + fp->lines[fp->nxtiov].iov_base = p; + fp->lines[fp->nxtiov].iov_len = strlen(p); + fp->nxtiov++; + } + } else if (fp->blen && fp->blen >= fp->lines[fp->nxtiov].iov_len) { + fp->blen = 0; + if (++fp->nxtiov >= TR_IOVECS) + trace_flush(fp, 0); + } + + if (fp->lines[fp->nxtiov].iov_len == 0) { + p = (char *)malloc(2 * TR_STD_WIDTH); + if (p == NULL) { + trace_flush(fp, 1); + debug = 0; + return; + } + *p = '\0'; + fp->lines[fp->nxtiov].iov_base = p; + fp->lines[fp->nxtiov].iov_len = 2 * TR_STD_WIDTH; + fp->blen = 0; + } + + p = (char *)fp->lines[fp->nxtiov].iov_base + fp->blen++; + *p++ = c; + *p = 0; + + if (c != ' ' && c != '\t' && c != '\n') { + fp->lastc = c; + fp->supr = 0; + } + + if (c == '\n') { + fp->lines[fp->nxtiov++].iov_len = fp->blen; + fp->blen = 0; + fp->llen = 0; + fp->supr |= SUP_NL; + return; + } + + if (c == '\t') + fp->llen |= 7; + fp->llen++; +} + +void +trace_flush(TFILE *fp, int all) +{ + int niov, i; + ssize_t written; + + niov = fp->nxtiov; + if (all && fp->blen > 0) { + fp->lines[niov].iov_len = fp->blen; + fp->blen = 0; + fp->llen = 0; + niov++; + } + if (niov == 0) + return; + if (fp->blen > 0 && --niov == 0) + return; + written = writev(fp->tfd, fp->lines, niov); + for (i = 0; i < niov; i++) { + free(fp->lines[i].iov_base); + fp->lines[i].iov_base = NULL; + fp->lines[i].iov_len = 0; + } + if (written == -1) { + if (fp->blen > 0) { + free(fp->lines[niov].iov_base); + fp->lines[niov].iov_base = NULL; + fp->lines[niov].iov_len = 0; + } + debug = 0; + fp->blen = 0; + fp->llen = 0; + return; + } + if (fp->blen > 0) { + fp->lines[0].iov_base = fp->lines[niov].iov_base; + fp->lines[0].iov_len = fp->lines[niov].iov_len; + fp->lines[niov].iov_base = NULL; + fp->lines[niov].iov_len = 0; + } + fp->nxtiov = 0; +} + +void +trace_puts(const char *s, TFILE *fp) +{ + char c; + + while ((c = *s++) != '\0') + trace_putc(c, fp); +} + +inline static char * +trace_id(TFILE *tf) +{ + int i; + char indent[16]; + char *p; + int lno; + char c; + + if (DFlags & DBG_NEST) { + if ((unsigned)ShNest >= sizeof indent - 1) { + (void) snprintf(indent, sizeof indent, + "### %*d ###", (int)(sizeof indent) - 9, ShNest); + p = strchr(indent, '\0'); + } else { + p = indent; + for (i = 0; i < 6; i++) + *p++ = (i < ShNest) ? '#' : ' '; + while (i++ < ShNest && p < &indent[sizeof indent - 1]) + *p++ = '#'; + *p = '\0'; + } + } else + indent[0] = '\0'; + + /* + * If we are in the parser, then plinno is the current line + * number being processed (parser line no). + * If we are elsewhere, then line_number gives the source + * line of whatever we are currently doing (close enough.) + */ + if (parsing) + lno = plinno; + else + lno = line_number; + + c = ((i = getpid()) == tf->pid) ? ':' : '='; + + if (DFlags & DBG_PID) { + if (DFlags & DBG_LINE) + (void) asprintf(&p, "%5d%c%s\t%4d%c@\t", i, c, + indent, lno, parsing?'-':'+'); + else + (void) asprintf(&p, "%5d%c%s\t", i, c, indent); + return p; + } else if (DFlags & DBG_NEST) { + if (DFlags & DBG_LINE) + (void) asprintf(&p, "%c%s\t%4d%c@\t", c, indent, lno, + parsing?'-':'+'); + else + (void) asprintf(&p, "%c%s\t", c, indent); + return p; + } else if (DFlags & DBG_LINE) { + (void) asprintf(&p, "%c%4d%c@\t", c, lno, parsing?'-':'+'); + return p; + } + return NULL; +} + +/* + * Used only from trargs(), which itself is used only to print + * arg lists (argv[]) either that passed into this shell, or + * the arg list about to be given to some other command (incl + * builtin, and function) as their argv[]. If any of the CTL* + * chars seem to appear, they really should be just treated as data, + * not special... But this is just debug, so, who cares! + */ +static void +trstring(const char *s) +{ + const char *p; + char c; + TFILE *fp; + + if (debug != 1 || !tracetfile) + return; + fp = tracetfile; + trace_putc('"', fp); + for (p = s ; *p ; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; +backslash: trace_putc('\\', fp); + trace_putc(c, fp); + break; + default: + if (*p >= ' ' && *p <= '~') + trace_putc(*p, fp); + else { + trace_putc('\\', fp); + trace_putc(*p >> 6 & 03, fp); + trace_putc(*p >> 3 & 07, fp); + trace_putc(*p & 07, fp); + } + break; + } + } + trace_putc('"', fp); +} + +/* + * deal with the user "accidentally" picking our fd to use. + */ +static void +trace_fd_swap(int from, int to) +{ + if (tracetfile == NULL || from == to) + return; + + tracetfile->tfd = to; + + /* + * This is just so histedit has a stdio FILE* to use. + */ + if (tracefile) + fclose(tracefile); + tracefile = fdopen(to, "a"); + if (tracefile) + setlinebuf(tracefile); +} + + +static struct debug_flag { + char label; + uint64_t flag; +} debug_flags[] = { + { 'a', DBG_ARITH }, /* arithmetic ( $(( )) ) */ + { 'c', DBG_CMDS }, /* command searching, ... */ + { 'e', DBG_EVAL }, /* evaluation of the parse tree */ + { 'f', DBG_REDIR }, /* file descriptors & redirections */ + { 'g', DBG_MATCH }, /* pattern matching (glob) */ + { 'h', DBG_HISTORY }, /* history & cmd line editing */ + { 'i', DBG_INPUT }, /* shell input routines */ + { 'j', DBG_JOBS }, /* job control, structures */ + { 'l', DBG_LEXER }, /* lexical analysis */ + { 'm', DBG_MEM }, /* memory management */ + { 'o', DBG_OUTPUT }, /* output routines */ + { 'p', DBG_PROCS }, /* process management, fork, ... */ + { 'r', DBG_PARSE }, /* parser, lexer, ... tree building */ + { 's', DBG_SIG }, /* signals and everything related */ + { 't', DBG_TRAP }, /* traps & signals */ + { 'v', DBG_VARS }, /* variables and parameters */ + { 'w', DBG_WAIT }, /* waits for processes to finish */ + { 'x', DBG_EXPAND }, /* word expansion ${} $() $(( )) */ + { 'z', DBG_ERRS }, /* error control, jumps, cleanup */ + + { '0', DBG_U0 }, /* ad-hoc temp debug flag #0 */ + { '1', DBG_U1 }, /* ad-hoc temp debug flag #1 */ + { '2', DBG_U2 }, /* ad-hoc temp debug flag #2 */ + { '3', DBG_U3 }, /* ad-hoc temp debug flag #3 */ + + { '@', DBG_LINE }, /* prefix trace lines with line# */ + { '$', DBG_PID }, /* prefix trace lines with sh pid */ + { '^', DBG_NEST }, /* show shell nesting level */ + + /* alpha options only - but not DBG_LEXER */ + { '_', DBG_PARSE | DBG_EVAL | DBG_EXPAND | DBG_JOBS | DBG_SIG | + DBG_PROCS | DBG_REDIR | DBG_CMDS | DBG_ERRS | + DBG_WAIT | DBG_TRAP | DBG_VARS | DBG_MEM | DBG_MATCH | + DBG_INPUT | DBG_OUTPUT | DBG_ARITH | DBG_HISTORY }, + + /* { '*', DBG_ALLVERBOSE }, is handled in the code */ + + { '#', DBG_U0 | DBG_U1 | DBG_U2 | DBG_U3 }, + + { 0, 0 } +}; + +void +set_debug(const char * flags, int on) +{ + char f; + struct debug_flag *df; + int verbose; + + while ((f = *flags++) != '\0') { + verbose = 0; + if (is_upper(f)) { + verbose = 1; + f += 'a' - 'A'; + } + if (f == '*') + f = '_', verbose = 1; + if (f == '+') { + if (*flags == '+') + flags++, verbose=1; + } + + /* + * Note: turning on any debug option also enables DBG_ALWAYS + * turning on any verbose option also enables DBG_VERBOSE + * Once enabled, those flags cannot be disabled. + * (tracing can still be turned off with "set +o debug") + */ + for (df = debug_flags; df->label != '\0'; df++) { + if (f == '+' || df->label == f) { + if (on) { + DFlags |= DBG_ALWAYS | df->flag; + if (verbose) + DFlags |= DBG_VERBOSE | + (df->flag << DBG_VBOSE_SHIFT); + } else { + DFlags &= ~(df->flag<<DBG_VBOSE_SHIFT); + if (!verbose) + DFlags &= ~df->flag; + } + } + } + } +} + + +int +debugcmd(int argc, char **argv) +{ + if (argc == 1) { + struct debug_flag *df; + + out1fmt("Debug: %sabled. Flags: ", debug ? "en" : "dis"); + for (df = debug_flags; df->label != '\0'; df++) { + if (df->flag & (df->flag - 1)) + continue; + if (is_alpha(df->label) && + (df->flag << DBG_VBOSE_SHIFT) & DFlags) + out1c(df->label - ('a' - 'A')); + else if (df->flag & DFlags) + out1c(df->label); + } + out1c('\n'); + return 0; + } + + while (*++argv) { + if (**argv == '-') + set_debug(*argv + 1, 1); + else if (**argv == '+') + set_debug(*argv + 1, 0); + else + return 1; + } + return 0; +} + +#endif /* DEBUG */ diff --git a/bin/sh/show.h b/bin/sh/show.h new file mode 100644 index 0000000..2c48873 --- /dev/null +++ b/bin/sh/show.h @@ -0,0 +1,46 @@ +/* $NetBSD: show.h,v 1.11 2017/06/30 23:00:40 kre Exp $ */ + +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * 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. + * + * @(#)show.h 1.1 (Berkeley) 5/4/95 + */ + +#include <stdarg.h> + +#ifdef DEBUG +union node; +void showtree(union node *); +void trace(const char *, ...); +void tracev(const char *, va_list); +void trargs(char **); +void trargstr(union node *); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/bin/sh/syntax.c b/bin/sh/syntax.c new file mode 100644 index 0000000..7b460d4 --- /dev/null +++ b/bin/sh/syntax.c @@ -0,0 +1,111 @@ +/* $NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $ */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $"); + +#include <limits.h> +#include "shell.h" +#include "syntax.h" +#include "parser.h" + +#if CWORD != 0 +#error initialisation assumes 'CWORD' is zero +#endif + +#define ndx(ch) (ch + 2 - CHAR_MIN) +#define set(ch, val) [ndx(ch)] = val, +#define set_range(s, e, val) [ndx(s) ... ndx(e)] = val, + +/* syntax table used when not in quotes */ +const char basesyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\\', CBACK) + set('\'', CSQUOTE) + set('"', CDQUOTE) + set('`', CBQUOTE) + set('$', CVAR) + set('}', CENDVAR) + set('<', CSPCL) + set('>', CSPCL) + set('(', CSPCL) + set(')', CSPCL) + set(';', CSPCL) + set('&', CSPCL) + set('|', CSPCL) + set(' ', CSPCL) + set('\t', CSPCL) +}; + +/* syntax table used when in double quotes */ +const char dqsyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\\', CBACK) + set('"', CDQUOTE) + set('`', CBQUOTE) + set('$', CVAR) + set('}', CENDVAR) + /* ':/' for tilde expansion, '-]' for [a\-x] pattern ranges */ + set('!', CCTL) + set('*', CCTL) + set('?', CCTL) + set('[', CCTL) + set('=', CCTL) + set('~', CCTL) + set(':', CCTL) + set('/', CCTL) + set('-', CCTL) + set(']', CCTL) +}; + +/* syntax table used when in single quotes */ +const char sqsyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\'', CSQUOTE) + set('\\', CSBACK) + /* ':/' for tilde expansion, '-]' for [a\-x] pattern ranges */ + set('!', CCTL) + set('*', CCTL) + set('?', CCTL) + set('[', CCTL) + set('=', CCTL) + set('~', CCTL) + set(':', CCTL) + set('/', CCTL) + set('-', CCTL) + set(']', CCTL) +}; + +/* syntax table used when in arithmetic */ +const char arisyntax[258] = { CFAKE, CEOF, + set_range(CTL_FIRST, CTL_LAST, CCTL) + set('\n', CNL) + set('\\', CBACK) + set('`', CBQUOTE) + set('\'', CSQUOTE) + set('"', CDQUOTE) + set('$', CVAR) + set('}', CENDVAR) + set('(', CLP) + set(')', CRP) +}; + +/* character classification table */ +const char is_type[258] = { 0, 0, + set_range('0', '9', ISDIGIT) + set_range('a', 'z', ISLOWER) + set_range('A', 'Z', ISUPPER) + set('_', ISUNDER) + set('#', ISSPECL) + set('?', ISSPECL) + set('$', ISSPECL) + set('!', ISSPECL) + set('-', ISSPECL) + set('*', ISSPECL) + set('@', ISSPECL) + set(' ', ISSPACE) + set('\t', ISSPACE) + set('\n', ISSPACE) +}; diff --git a/bin/sh/syntax.h b/bin/sh/syntax.h new file mode 100644 index 0000000..ad064b8 --- /dev/null +++ b/bin/sh/syntax.h @@ -0,0 +1,98 @@ +/* $NetBSD: syntax.h,v 1.11 2018/12/03 06:40:26 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#include <ctype.h> + +/* Syntax classes */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ +#define CBQUOTE 5 /* backwards single quote */ +#define CVAR 6 /* a dollar sign */ +#define CENDVAR 7 /* a '}' character */ +#define CLP 8 /* a left paren in arithmetic */ +#define CRP 9 /* a right paren in arithmetic */ +#define CEOF 10 /* end of file */ +#define CSPCL 11 /* these terminate a word */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSBACK 13 /* a backslash in a single quote syntax */ +#define CFAKE 14 /* a delimiter that does not exist */ + /* + * note CSBACK == (CCTL|1) + * the code does not rely upon that, but keeping it allows a + * smart enough compiler to optimise some tests + */ + +/* Syntax classes for is_ functions */ +#define ISDIGIT 01 /* a digit */ +#define ISUPPER 02 /* an upper case letter */ +#define ISLOWER 04 /* a lower case letter */ +#define ISUNDER 010 /* an underscore */ +#define ISSPECL 020 /* the name of a special parameter */ +#define ISSPACE 040 /* a white space character */ + +#define PEOF (CHAR_MIN - 1) +#define PFAKE (CHAR_MIN - 2) +#define SYNBASE (-PFAKE) + + +#define BASESYNTAX (basesyntax + SYNBASE) +#define DQSYNTAX (dqsyntax + SYNBASE) +#define SQSYNTAX (sqsyntax + SYNBASE) +#define ARISYNTAX (arisyntax + SYNBASE) + +/* These defines assume that the digits are contiguous (which is guaranteed) */ +#define is_digit(c) ((unsigned)((c) - '0') <= 9) +#define sh_ctype(c) (is_type+SYNBASE)[(int)(c)] +#define is_upper(c) (sh_ctype(c) & ISUPPER) +#define is_lower(c) (sh_ctype(c) & ISLOWER) +#define is_alpha(c) (sh_ctype(c) & (ISUPPER|ISLOWER)) +#define is_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER)) +#define is_in_name(c) (sh_ctype(c) & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT)) +#define is_special(c) (sh_ctype(c) & (ISSPECL|ISDIGIT)) +#define is_space(c) (sh_ctype(c) & ISSPACE) +#define digit_val(c) ((c) - '0') + +/* true if the arg char needs CTLESC to protect it */ +#define NEEDESC(c) (SQSYNTAX[(int)(c)] == CCTL || \ + SQSYNTAX[(int)(c)] == CSBACK) + +extern const char basesyntax[]; +extern const char dqsyntax[]; +extern const char sqsyntax[]; +extern const char arisyntax[]; +extern const char is_type[]; diff --git a/bin/sh/trap.c b/bin/sh/trap.c new file mode 100644 index 0000000..cb641fd --- /dev/null +++ b/bin/sh/trap.c @@ -0,0 +1,837 @@ +/* $NetBSD: trap.c,v 1.51 2019/01/18 06:28:09 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#else +__RCSID("$NetBSD: trap.c,v 1.51 2019/01/18 06:28:09 kre Exp $"); +#endif +#endif /* not lint */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <termios.h> + +#undef CEOF /* from <termios.h> but concflicts with sh use */ + +#include <sys/ioctl.h> +#include <sys/resource.h> + +#include "shell.h" +#include "main.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" +#include "jobs.h" +#include "show.h" +#include "options.h" +#include "builtins.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "trap.h" +#include "mystring.h" +#include "var.h" + + +/* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permenantly */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +MKINIT char sigmode[NSIG]; /* current value of signal */ +static volatile sig_atomic_t gotsig[NSIG];/* indicates specified signal received */ +volatile sig_atomic_t pendingsigs; /* indicates some signal received */ + +int traps_invalid; /* in a subshell, but trap[] not yet cleared */ +static char * volatile trap[NSIG]; /* trap handler commands */ +static int in_dotrap; +static int last_trapsig; + +static int exiting; /* exitshell() has been done */ +static int exiting_status; /* the status to use for exit() */ + +static int getsigaction(int, sig_t *); +STATIC const char *trap_signame(int); +void printsignals(struct output *, int); + +/* + * return the signal number described by `p' (as a number or a name) + * or -1 if it isn't one + */ + +static int +signame_to_signum(const char *p) +{ + int i; + + if (is_number(p)) + return number(p); + + if (strcasecmp(p, "exit") == 0 ) + return 0; + + i = signalnumber(p); + if (i == 0) + i = -1; + return i; +} + +/* + * return the name of a signal used by the "trap" command + */ +STATIC const char * +trap_signame(int signo) +{ + static char nbuf[12]; + const char *p; + + if (signo == 0) + return "EXIT"; + p = signalname(signo); + if (p != NULL) + return p; + (void)snprintf(nbuf, sizeof nbuf, "%d", signo); + return nbuf; +} + +#ifdef SMALL +/* + * Print a list of valid signal names + */ +void +printsignals(struct output *out, int len) +{ + int n; + + if (len != 0) + outc(' ', out); + for (n = 1; n < NSIG; n++) { + outfmt(out, "%s", trap_signame(n)); + if ((n == NSIG/2) || n == (NSIG - 1)) + outstr("\n", out); + else + outc(' ', out); + } +} +#else /* !SMALL */ +/* + * Print the names of all the signals (neatly) to fp + * "len" gives the number of chars already printed to + * the current output line (in kill.c, always 0) + */ +void +printsignals(struct output *out, int len) +{ + int sig; + int nl, pad; + const char *name; + int termwidth = 80; + + if ((name = bltinlookup("COLUMNS", 1)) != NULL) + termwidth = (int)strtol(name, NULL, 10); + else if (isatty(1)) { + struct winsize win; + + if (ioctl(1, TIOCGWINSZ, &win) == 0 && win.ws_col > 0) + termwidth = win.ws_col; + } + + if (posix) + pad = 1; + else + pad = (len | 7) + 1 - len; + + for (sig = 0; (sig = signalnext(sig)) != 0; ) { + name = signalname(sig); + if (name == NULL) + continue; + + nl = strlen(name); + + if (len > 0 && nl + len + pad >= termwidth) { + outc('\n', out); + len = 0; + pad = 0; + } else if (pad > 0 && len != 0) + outfmt(out, "%*s", pad, ""); + else + pad = 0; + + len += nl + pad; + if (!posix) + pad = (nl | 7) + 1 - nl; + else + pad = 1; + + outstr(name, out); + } + if (len != 0) + outc('\n', out); +} +#endif /* SMALL */ + +/* + * The trap builtin. + */ + +int +trapcmd(int argc, char **argv) +{ + char *action; + char **ap; + int signo; + int errs = 0; + int printonly = 0; + + ap = argv + 1; + + CTRACE(DBG_TRAP, ("trapcmd: ")); + if (argc == 2 && strcmp(*ap, "-l") == 0) { + CTRACE(DBG_TRAP, ("-l\n")); + out1str("EXIT"); + printsignals(out1, 4); + return 0; + } + if (argc == 2 && strcmp(*ap, "-") == 0) { + CTRACE(DBG_TRAP, ("-\n")); + for (signo = 0; signo < NSIG; signo++) { + if (trap[signo] == NULL) + continue; + INTOFF; + ckfree(trap[signo]); + trap[signo] = NULL; + if (signo != 0) + setsignal(signo, 0); + INTON; + } + traps_invalid = 0; + return 0; + } + if (argc >= 2 && strcmp(*ap, "-p") == 0) { + CTRACE(DBG_TRAP, ("-p ")); + printonly = 1; + ap++; + argc--; + } + + if (argc > 1 && strcmp(*ap, "--") == 0) { + argc--; + ap++; + } + + if (argc <= 1) { + int count; + + CTRACE(DBG_TRAP, ("*all*\n")); + if (printonly) { + for (count = 0, signo = 0 ; signo < NSIG ; signo++) + if (trap[signo] == NULL) { + if (count == 0) + out1str("trap -- -"); + out1fmt(" %s", trap_signame(signo)); + /* oh! unlucky 13 */ + if (++count >= 13) { + out1str("\n"); + count = 0; + } + } + if (count) + out1str("\n"); + } + + for (count = 0, signo = 0 ; signo < NSIG ; signo++) + if (trap[signo] != NULL && trap[signo][0] == '\0') { + if (count == 0) + out1str("trap -- ''"); + out1fmt(" %s", trap_signame(signo)); + /* + * the prefix is 10 bytes, with 4 byte + * signal names (common) we have room in + * the 70 bytes left on a normal line for + * 70/(4+1) signals, that's 14, but to + * allow for the occasional longer sig name + * we output one less... + */ + if (++count >= 13) { + out1str("\n"); + count = 0; + } + } + if (count) + out1str("\n"); + + for (signo = 0 ; signo < NSIG ; signo++) + if (trap[signo] != NULL && trap[signo][0] != '\0') { + out1str("trap -- "); + print_quoted(trap[signo]); + out1fmt(" %s\n", trap_signame(signo)); + } + + return 0; + } + CTRACE(DBG_TRAP, ("\n")); + + action = NULL; + + if (!printonly && traps_invalid) + free_traps(); + + if (!printonly && !is_number(*ap)) { + if ((*ap)[0] == '-' && (*ap)[1] == '\0') + ap++; /* reset to default */ + else + action = *ap++; /* can be '' for "ignore" */ + argc--; + } + + if (argc < 2) { /* there must be at least 1 condition */ + out2str("Usage: trap [-l]\n" + " trap -p [condition ...]\n" + " trap action condition ...\n" + " trap N condition ...\n"); + return 2; + } + + + while (*ap) { + signo = signame_to_signum(*ap); + + if (signo < 0 || signo >= NSIG) { + /* This is not a fatal error, so sayeth posix */ + outfmt(out2, "trap: '%s' bad condition\n", *ap); + errs = 1; + ap++; + continue; + } + ap++; + + if (printonly) { + out1str("trap -- "); + if (trap[signo] == NULL) + out1str("-"); + else + print_quoted(trap[signo]); + out1fmt(" %s\n", trap_signame(signo)); + continue; + } + + INTOFF; + if (action) + action = savestr(action); + + VTRACE(DBG_TRAP, ("trap for %d from %s%s%s to %s%s%s\n", signo, + trap[signo] ? "'" : "", trap[signo] ? trap[signo] : "-", + trap[signo] ? "'" : "", action ? "'" : "", + action ? action : "-", action ? "'" : "")); + + if (trap[signo]) + ckfree(trap[signo]); + + trap[signo] = action; + + if (signo != 0) + setsignal(signo, 0); + INTON; + } + return errs; +} + + + +/* + * Clear traps on a fork or vfork. + * Takes one arg vfork, to tell it to not be destructive of + * the parents variables. + */ +void +clear_traps(int vforked) +{ + char * volatile *tp; + + VTRACE(DBG_TRAP, ("clear_traps(%d)\n", vforked)); + if (!vforked) + traps_invalid = 1; + + for (tp = &trap[1] ; tp < &trap[NSIG] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + setsignal(tp - trap, vforked == 1); + INTON; + } + } + if (vforked == 2) + free_traps(); +} + +void +free_traps(void) +{ + char * volatile *tp; + + VTRACE(DBG_TRAP, ("free_traps%s\n", traps_invalid ? "(invalid)" : "")); + INTOFF; + for (tp = trap ; tp < &trap[NSIG] ; tp++) + if (*tp && **tp) { + ckfree(*tp); + *tp = NULL; + } + traps_invalid = 0; + INTON; +} + +/* + * See if there are any defined traps + */ +int +have_traps(void) +{ + char * volatile *tp; + + if (traps_invalid) + return 0; + + for (tp = trap ; tp < &trap[NSIG] ; tp++) + if (*tp && **tp) /* trap not NULL or SIG_IGN */ + return 1; + return 0; +} + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ +void +setsignal(int signo, int vforked) +{ + int action; + sig_t sigact = SIG_DFL, sig; + char *t, tsig; + + if (traps_invalid || (t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + + VTRACE(DBG_TRAP, ("setsignal(%d%s) -> %d", signo, + vforked ? ", VF" : "", action)); + if (rootshell && !vforked && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + /* + * Never let users futz with SIGCHLD + * instead we will give them pseudo SIGCHLD's + * when background jobs complete. + */ + if (signo == SIGCHLD) + action = S_DFL; + + VTRACE(DBG_TRAP, (" -> %d", action)); + + t = &sigmode[signo]; + tsig = *t; + if (tsig == 0) { + /* + * current setting unknown + */ + if (!getsigaction(signo, &sigact)) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + VTRACE(DBG_TRAP, (" getsigaction (%d)\n", errno)); + return; + } + VTRACE(DBG_TRAP, (" [%s]%s%s", sigact==SIG_IGN ? "IGN" : + sigact==SIG_DFL ? "DFL" : "caught", + iflag ? "i" : "", mflag ? "m" : "")); + + if (sigact == SIG_IGN) { + /* + * POSIX 3.14.13 states that non-interactive shells + * should ignore trap commands for signals that were + * ignored upon entry, and leaves the behavior + * unspecified for interactive shells. On interactive + * shells, or if job control is on, and we have a job + * control related signal, we allow the trap to work. + * + * This change allows us to be POSIX compliant, and + * at the same time override the default behavior if + * we need to by setting the interactive flag. + */ + if ((mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) || iflag) { + tsig = S_IGN; + } else + tsig = S_HARD_IGN; + } else { + tsig = S_RESET; /* force to be set */ + } + } + VTRACE(DBG_TRAP, (" tsig=%d\n", tsig)); + + if (tsig == S_HARD_IGN || tsig == action) + return; + + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + + sig = signal(signo, sigact); + + if (sig != SIG_ERR) { + sigset_t ss; + + if (!vforked) + *t = action; + + if (action == S_CATCH) + (void)siginterrupt(signo, 1); + /* + * If our parent accidentally blocked signals for + * us make sure we unblock them + */ + (void)sigemptyset(&ss); + (void)sigaddset(&ss, signo); + (void)sigprocmask(SIG_UNBLOCK, &ss, NULL); + } + return; +} + +/* + * Return the current setting for sig w/o changing it. + */ +static int +getsigaction(int signo, sig_t *sigact) +{ + struct sigaction sa; + + if (sigaction(signo, (struct sigaction *)0, &sa) == -1) + return 0; + *sigact = (sig_t) sa.sa_handler; + return 1; +} + +/* + * Ignore a signal. + */ + +void +ignoresig(int signo, int vforked) +{ + if (sigmode[signo] == 0) + setsignal(signo, vforked); + + VTRACE(DBG_TRAP, ("ignoresig(%d%s)\n", signo, vforked ? ", VF" : "")); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + if (!vforked) + sigmode[signo] = S_IGN; + } +} + +char * +child_trap(void) +{ + char * p; + + p = trap[SIGCHLD]; + + if (traps_invalid || (p != NULL && *p == '\0')) + p = NULL; + + return p; +} + + +#ifdef mkinit +INCLUDE <signal.h> +INCLUDE "trap.h" +INCLUDE "shell.h" +INCLUDE "show.h" + +SHELLPROC { + char *sm; + + INTOFF; + clear_traps(2); + for (sm = sigmode ; sm < sigmode + NSIG ; sm++) { + if (*sm == S_IGN) { + *sm = S_HARD_IGN; + VTRACE(DBG_TRAP, ("SHELLPROC: %d -> hard_ign\n", + (sm - sigmode))); + } + } + INTON; +} +#endif + + + +/* + * Signal handler. + */ + +void +onsig(int signo) +{ + CTRACE(DBG_SIG, ("Signal %d, had: pending %d, gotsig[%d]=%d\n", + signo, pendingsigs, signo, gotsig[signo])); + + /* This should not be needed. + signal(signo, onsig); + */ + + if (signo == SIGINT && (traps_invalid || trap[SIGINT] == NULL)) { + onint(); + return; + } + + /* + * if the signal will do nothing, no point reporting it + */ + if (!traps_invalid && trap[signo] != NULL && trap[signo][0] != '\0' && + signo != SIGCHLD) { + gotsig[signo] = 1; + pendingsigs++; + } +} + + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ + +void +dotrap(void) +{ + int i; + char *tr; + int savestatus; + struct skipsave saveskip; + + in_dotrap++; + + CTRACE(DBG_TRAP, ("dotrap[%d]: %d pending, traps %sinvalid\n", + in_dotrap, pendingsigs, traps_invalid ? "" : "not ")); + for (;;) { + pendingsigs = 0; + for (i = 1 ; ; i++) { + if (i >= NSIG) + return; + if (gotsig[i]) + break; + } + gotsig[i] = 0; + + if (traps_invalid) + continue; + + tr = trap[i]; + + CTRACE(DBG_TRAP|DBG_SIG, ("dotrap %d: %s%s%s\n", i, + tr ? "\"" : "", tr ? tr : "NULL", tr ? "\"" : "")); + + if (tr != NULL) { + last_trapsig = i; + save_skipstate(&saveskip); + savestatus = exitstatus; + + tr = savestr(tr); /* trap code may free trap[i] */ + evalstring(tr, 0); + ckfree(tr); + + if (current_skipstate() == SKIPNONE || + saveskip.state != SKIPNONE) { + restore_skipstate(&saveskip); + exitstatus = savestatus; + } + } + } + + in_dotrap--; +} + +int +lastsig(void) +{ + int i; + + for (i = NSIG; --i > 0; ) + if (gotsig[i]) + return i; + return SIGINT; /* XXX */ +} + +/* + * Controls whether the shell is interactive or not. + */ + + +void +setinteractive(int on) +{ + static int is_interactive; + + if (on == is_interactive) + return; + setsignal(SIGINT, 0); + setsignal(SIGQUIT, 0); + setsignal(SIGTERM, 0); + is_interactive = on; +} + + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + CTRACE(DBG_ERRS|DBG_PROCS|DBG_CMDS|DBG_TRAP, + ("pid %d: exitshell(%d)\n", getpid(), status)); + + exiting = 1; + exiting_status = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc; + char *p; + volatile int sig = 0; + int s; + sigset_t sigs; + + CTRACE(DBG_ERRS|DBG_PROCS|DBG_CMDS|DBG_TRAP, + ("pid %d: exitshell_savedstatus()%s $?=%d xs=%d dt=%d ts=%d\n", + getpid(), exiting ? " exiting" : "", exitstatus, + exiting_status, in_dotrap, last_trapsig)); + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_status = sig + 128; + } else + exiting_status = exitstatus; + } + exitstatus = exiting_status; + + if (!setjmp(loc.loc)) { + handler = &loc; + + if (!traps_invalid && (p = trap[0]) != NULL && *p != '\0') { + reset_eval(); + trap[0] = NULL; + VTRACE(DBG_TRAP, ("exit trap: \"%s\"\n", p)); + evalstring(p, 0); + } + } + + INTOFF; /* we're done, no more interrupts. */ + + if (!setjmp(loc.loc)) { + handler = &loc; /* probably unnecessary */ + flushall(); +#if JOBS + setjobctl(0); +#endif + } + + if ((s = sig) != 0 && s != SIGSTOP && s != SIGTSTP && s != SIGTTIN && + s != SIGTTOU) { + struct rlimit nocore; + + /* + * if the signal is of the core dump variety, don't... + */ + nocore.rlim_cur = nocore.rlim_max = 0; + (void) setrlimit(RLIMIT_CORE, &nocore); + + signal(s, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, s); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + + kill(getpid(), s); + } + _exit(exiting_status); + /* NOTREACHED */ +} diff --git a/bin/sh/trap.h b/bin/sh/trap.h new file mode 100644 index 0000000..7ea3ef1 --- /dev/null +++ b/bin/sh/trap.h @@ -0,0 +1,52 @@ +/* $NetBSD: trap.h,v 1.25 2018/12/03 10:53:29 martin Exp $ */ + +/*- + * 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. + * + * @(#)trap.h 8.3 (Berkeley) 6/5/95 + */ + +extern volatile sig_atomic_t pendingsigs; + +extern int traps_invalid; + +void clear_traps(int); +void free_traps(void); +int have_traps(void); +void setsignal(int, int); +void ignoresig(int, int); +void onsig(int); +void dotrap(void); +char *child_trap(void); +void setinteractive(int); +void exitshell(int) __dead; +void exitshell_savedstatus(void) __dead; +int lastsig(void); diff --git a/bin/sh/var.c b/bin/sh/var.c new file mode 100644 index 0000000..378598c --- /dev/null +++ b/bin/sh/var.c @@ -0,0 +1,1587 @@ +/* $NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $ */ + +/*- + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <paths.h> +#include <limits.h> +#include <time.h> +#include <pwd.h> +#include <fcntl.h> +#include <inttypes.h> + +/* + * Shell variables. + */ + +#include "shell.h" +#include "output.h" +#include "expand.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" /* defines cmdenviron */ +#include "exec.h" +#include "syntax.h" +#include "options.h" +#include "builtins.h" +#include "mail.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "parser.h" +#include "show.h" +#include "machdep.h" +#ifndef SMALL +#include "myhistedit.h" +#endif + +#ifdef SMALL +#define VTABSIZE 39 +#else +#define VTABSIZE 517 +#endif + + +struct varinit { + struct var *var; + int flags; + const char *text; + union var_func_union v_u; +}; +#define func v_u.set_func +#define rfunc v_u.ref_func + +char *get_lineno(struct var *); + +#ifndef SMALL +char *get_tod(struct var *); +char *get_hostname(struct var *); +char *get_seconds(struct var *); +char *get_euser(struct var *); +char *get_random(struct var *); +#endif + +struct localvar *localvars; + +#ifndef SMALL +struct var vhistsize; +struct var vterm; +struct var editrc; +struct var ps_lit; +#endif +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps1; +struct var vps2; +struct var vps4; +struct var vvers; +struct var voptind; +struct var line_num; +#ifndef SMALL +struct var tod; +struct var host_name; +struct var seconds; +struct var euname; +struct var random_num; + +intmax_t sh_start_time; +#endif + +struct var line_num; +int line_number; +int funclinebase = 0; +int funclineabs = 0; + +char ifs_default[] = " \t\n"; + +const struct varinit varinit[] = { +#ifndef SMALL + { &vhistsize, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTSIZE=", + { .set_func= sethistsize } }, +#endif + { &vifs, VSTRFIXED|VTEXTFIXED, "IFS= \t\n", + { NULL } }, + { &vmail, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL=", + { NULL } }, + { &vmpath, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH=", + { NULL } }, + { &vvers, VSTRFIXED|VTEXTFIXED|VNOEXPORT, "NETBSD_SHELL=", + { NULL } }, + { &vpath, VSTRFIXED|VTEXTFIXED, "PATH=" _PATH_DEFPATH, + { .set_func= changepath } }, + /* + * vps1 depends on uid + */ + { &vps2, VSTRFIXED|VTEXTFIXED, "PS2=> ", + { NULL } }, + { &vps4, VSTRFIXED|VTEXTFIXED, "PS4=+ ", + { NULL } }, +#ifndef SMALL + { &vterm, VSTRFIXED|VTEXTFIXED|VUNSET, "TERM=", + { .set_func= setterm } }, + { &editrc, VSTRFIXED|VTEXTFIXED|VUNSET, "EDITRC=", + { .set_func= set_editrc } }, + { &ps_lit, VSTRFIXED|VTEXTFIXED|VUNSET, "PSlit=", + { .set_func= set_prompt_lit } }, +#endif + { &voptind, VSTRFIXED|VTEXTFIXED|VNOFUNC, "OPTIND=1", + { .set_func= getoptsreset } }, + { &line_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "LINENO=1", + { .ref_func= get_lineno } }, +#ifndef SMALL + { &tod, VSTRFIXED|VTEXTFIXED|VFUNCREF, "ToD=", + { .ref_func= get_tod } }, + { &host_name, VSTRFIXED|VTEXTFIXED|VFUNCREF, "HOSTNAME=", + { .ref_func= get_hostname } }, + { &seconds, VSTRFIXED|VTEXTFIXED|VFUNCREF, "SECONDS=", + { .ref_func= get_seconds } }, + { &euname, VSTRFIXED|VTEXTFIXED|VFUNCREF, "EUSER=", + { .ref_func= get_euser } }, + { &random_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "RANDOM=", + { .ref_func= get_random } }, +#endif + { NULL, 0, NULL, + { NULL } } +}; + +struct var *vartab[VTABSIZE]; + +STATIC int strequal(const char *, const char *); +STATIC struct var *find_var(const char *, struct var ***, int *); +STATIC void showvar(struct var *, const char *, const char *, int); +static void export_usage(const char *) __dead; + +/* + * Initialize the varable symbol tables and import the environment + */ + +#ifdef mkinit +INCLUDE <stdio.h> +INCLUDE <unistd.h> +INCLUDE <time.h> +INCLUDE "var.h" +INCLUDE "version.h" +MKINIT char **environ; +INIT { + char **envp; + char buf[64]; + +#ifndef SMALL + sh_start_time = (intmax_t)time((time_t *)0); +#endif + /* + * Set up our default variables and their values. + */ + initvar(); + + /* + * Import variables from the environment, which will + * override anything initialised just previously. + */ + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + + /* + * Set variables which override anything read from environment. + * + * PPID is readonly + * Always default IFS + * POSIX: "Whenever the shell is invoked, OPTIND shall + * be initialized to 1." + * PSc indicates the root/non-root status of this shell. + * START_TIME belongs only to this shell. + * NETBSD_SHELL is a constant (readonly), and is never exported + * LINENO is simply magic... + */ + snprintf(buf, sizeof(buf), "%d", (int)getppid()); + setvar("PPID", buf, VREADONLY); + setvar("IFS", ifs_default, VTEXTFIXED); + setvar("OPTIND", "1", VTEXTFIXED); + setvar("PSc", (geteuid() == 0 ? "#" : "$"), VTEXTFIXED); + +#ifndef SMALL + snprintf(buf, sizeof(buf), "%jd", sh_start_time); + setvar("START_TIME", buf, VTEXTFIXED); +#endif + + setvar("NETBSD_SHELL", NETBSD_SHELL +#ifdef BUILD_DATE + " BUILD:" BUILD_DATE +#endif +#ifdef DEBUG + " DEBUG" +#endif +#if !defined(JOBS) || JOBS == 0 + " -JOBS" +#endif +#ifndef DO_SHAREDVFORK + " -VFORK" +#endif +#ifdef SMALL + " SMALL" +#endif +#ifdef TINY + " TINY" +#endif +#ifdef OLD_TTY_DRIVER + " OLD_TTY" +#endif +#ifdef SYSV + " SYSV" +#endif +#ifndef BSD + " -BSD" +#endif +#ifdef BOGUS_NOT_COMMAND + " BOGUS_NOT" +#endif + , VTEXTFIXED|VREADONLY|VNOEXPORT); + + setvar("LINENO", "1", VTEXTFIXED); +} +#endif + + +/* + * This routine initializes the builtin variables. It is called when the + * shell is initialized and again when a shell procedure is spawned. + */ + +void +initvar(void) +{ + const struct varinit *ip; + struct var *vp; + struct var **vpp; + + for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { + if (find_var(ip->text, &vpp, &vp->name_len) != NULL) + continue; + vp->next = *vpp; + *vpp = vp; + vp->text = strdup(ip->text); + vp->flags = (ip->flags & ~VTEXTFIXED) | VSTRFIXED; + vp->v_u = ip->v_u; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.flags = VSTRFIXED; + vps1.text = NULL; + choose_ps1(); + } +} + +void +choose_ps1(void) +{ + uid_t u = geteuid(); + + if ((vps1.flags & (VTEXTFIXED|VSTACK)) == 0) + free(vps1.text); + vps1.text = strdup(u != 0 ? "PS1=$ " : "PS1=# "); + vps1.flags &= ~(VTEXTFIXED|VSTACK); + + /* + * Update PSc whenever we feel the need to update PS1 + */ + setvarsafe("PSc", (u == 0 ? "#" : "$"), 0); +} + +/* + * Validate a string as a valid variable name + * nb: not parameter - special params and such are "invalid" here. + * Name terminated by either \0 or the term param (usually '=' or '\0'). + * + * If not NULL, the length of the (intended) name is returned via len + */ + +int +validname(const char *name, int term, int *len) +{ + const char *p = name; + int ok = 1; + + if (p == NULL || *p == '\0' || *p == term) { + if (len != NULL) + *len = 0; + return 0; + } + + if (!is_name(*p)) + ok = 0; + p++; + for (;;) { + if (*p == '\0' || *p == term) + break; + if (!is_in_name(*p)) + ok = 0; + p++; + } + if (len != NULL) + *len = p - name; + + return ok; +} + +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + struct jmploc jmploc; + struct jmploc * const savehandler = handler; + int volatile err = 0; + + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + return err; +} + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + * + * This always copies name and val when setting a variable, so + * the source strings can be from anywhere, and are no longer needed + * after this function returns. The VTEXTFIXED and VSTACK flags should + * not be used (but just in case they were, clear them.) + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + const char *q; + char *d; + int len; + int namelen; + char *nameeq; + + p = name; + + if (!validname(p, '=', &namelen)) + error("%.*s: bad variable name", namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + } else { + len += strlen(val); + } + d = nameeq = ckmalloc(len); + q = name; + while (--namelen >= 0) + *d++ = *q++; + *d++ = '='; + *d = '\0'; + if (val) + scopy(val, d); + setvareq(nameeq, flags & ~(VTEXTFIXED | VSTACK)); +} + + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. The flags (VTEXTFIXED or VSTACK) can be used to + * indicate the source of the string (if neither is set, the string will + * eventually be free()d when a replacement value is assigned.) + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + VTRACE(DBG_VARS, ("setvareq([%s],%#x) aflag=%d ", s, flags, aflag)); + if (aflag && !(flags & VNOEXPORT)) + flags |= VEXPORT; + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + VTRACE(DBG_VARS, ("was [%s] fl:%#x\n", vp->text, + vp->flags)); + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + if (flags & VNOERROR) + return; + error("%.*s: is read only", vp->name_len, vp->text); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + + INTOFF; + + if (vp->func && !(vp->flags & VFUNCREF) && !(flags & VNOFUNC)) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + /* + * if we set a magic var, the magic dissipates, + * unless it is very special indeed. + */ + if (vp->rfunc && (vp->flags & (VFUNCREF|VSPECIAL)) == VFUNCREF) + vp->rfunc = NULL; + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + if (flags & VNOEXPORT) + vp->flags &= ~VEXPORT; + if (flags & VDOEXPORT) + vp->flags &= ~VNOEXPORT; + if (vp->flags & VNOEXPORT) + flags &= ~VEXPORT; + vp->flags |= flags & ~(VNOFUNC | VDOEXPORT); + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + */ + if (vp == &vmpath || (vp == &vmail && ! mpathset())) + chkmail(1); + + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + VTRACE(DBG_VARS, ("new noset\n")); + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags & ~(VNOFUNC|VFUNCREF|VDOEXPORT); + vp->text = s; + vp->name_len = nlen; + vp->func = NULL; + vp->next = *vpp; + *vpp = vp; + + VTRACE(DBG_VARS, ("new [%s] (%d) %#x\n", s, nlen, vp->flags)); +} + + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct strlist *list, int flags) +{ + struct strlist *lp; + + INTOFF; + for (lp = list ; lp ; lp = lp->next) { + setvareq(savestr(lp->text), flags); + } + INTON; +} + +void +listmklocal(struct strlist *list, int flags) +{ + struct strlist *lp; + + for (lp = list ; lp ; lp = lp->next) + mklocal(lp->text, flags); +} + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +char * +lookupvar(const char *name) +{ + struct var *v; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET) + return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; + return v->text + v->name_len + 1; +} + + + +/* + * Search the environment of a builtin command. If the second argument + * is nonzero, return the value of a variable even if it hasn't been + * exported. + */ + +char * +bltinlookup(const char *name, int doall) +{ + struct strlist *sp; + struct var *v; + + for (sp = cmdenviron ; sp ; sp = sp->next) { + if (strequal(sp->text, name)) + return strchr(sp->text, '=') + 1; + } + + v = find_var(name, NULL, NULL); + + if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT))) + return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; + return v->text + v->name_len + 1; +} + + + +/* + * Generate a list of exported variables. This routine is used to construct + * the third argument to execve when executing a program. + */ + +char ** +environment(void) +{ + int nenv; + struct var **vpp; + struct var *vp; + char **env; + char **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) + nenv++; + } + CTRACE(DBG_VARS, ("environment: %d vars to export\n", nenv)); + ep = env = stalloc((nenv + 1) * sizeof *env); + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) { + if (vp->rfunc && (vp->flags & VFUNCREF)) + *ep++ = (*vp->rfunc)(vp); + else + *ep++ = vp->text; + VTRACE(DBG_VARS, ("environment: %s\n", ep[-1])); + } + } + *ep = NULL; + return env; +} + + +/* + * Called when a shell procedure is invoked to clear out nonexported + * variables. It is also necessary to reallocate variables of with + * VSTACK set since these are currently allocated on the stack. + */ + +#ifdef mkinit +void shprocvar(void); + +SHELLPROC { + shprocvar(); +} +#endif + +void +shprocvar(void) +{ + struct var **vpp; + struct var *vp, **prev; + + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (prev = vpp ; (vp = *prev) != NULL ; ) { + if ((vp->flags & VEXPORT) == 0) { + *prev = vp->next; + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + if ((vp->flags & VSTRFIXED) == 0) + ckfree(vp); + } else { + if (vp->flags & VSTACK) { + vp->text = savestr(vp->text); + vp->flags &=~ VSTACK; + } + prev = &vp->next; + } + } + } + initvar(); +} + + + +/* + * Command to list all variables which are set. Currently this command + * is invoked from the set command when the set command is called without + * any variables. + */ + +void +print_quoted(const char *p) +{ + const char *q; + + if (p[0] == '\0') { + out1fmt("''"); + return; + } + if (strcspn(p, "|&;<>()$`\\\"' \t\n*?[]#~=%") == strlen(p)) { + out1fmt("%s", p); + return; + } + while (*p) { + if (*p == '\'') { + out1fmt("\\'"); + p++; + continue; + } + q = strchr(p, '\''); + if (!q) { + out1fmt("'%s'", p ); + return; + } + out1fmt("'%.*s'", (int)(q - p), p ); + p = q; + } +} + +static int +sort_var(const void *v_v1, const void *v_v2) +{ + const struct var * const *v1 = v_v1; + const struct var * const *v2 = v_v2; + char *t1 = (*v1)->text, *t2 = (*v2)->text; + + if (*t1 == *t2) { + char *p, *s; + + STARTSTACKSTR(p); + + /* + * note: if lengths are equal, strings must be different + * so we don't care which string we pick for the \0 in + * that case. + */ + if ((strchr(t1, '=') - t1) <= (strchr(t2, '=') - t2)) { + s = t1; + t1 = p; + } else { + s = t2; + t2 = p; + } + + while (*s && *s != '=') { + STPUTC(*s, p); + s++; + } + STPUTC('\0', p); + } + + return strcoll(t1, t2); +} + +/* + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... + */ + +STATIC void +showvar(struct var *vp, const char *cmd, const char *xtra, int show_value) +{ + const char *p; + + if (cmd) + out1fmt("%s ", cmd); + if (xtra) + out1fmt("%s ", xtra); + p = vp->text; + if (vp->rfunc && (vp->flags & VFUNCREF) != 0) { + p = (*vp->rfunc)(vp); + if (p == NULL) + p = vp->text; + } + for ( ; *p != '=' ; p++) + out1c(*p); + if (!(vp->flags & VUNSET) && show_value) { + out1fmt("="); + print_quoted(++p); + } + out1c('\n'); +} + +int +showvars(const char *cmd, int flag, int show_value, const char *xtra) +{ + struct var **vpp; + struct var *vp; + + static struct var **list; /* static in case we are interrupted */ + static int list_len; + int count = 0; + + if (!list) { + list_len = 32; + list = ckmalloc(list_len * sizeof *list); + } + + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (flag && !(vp->flags & flag)) + continue; + if (vp->flags & VUNSET && !(show_value & 2)) + continue; + if (count >= list_len) { + list = ckrealloc(list, + (list_len << 1) * sizeof *list); + list_len <<= 1; + } + list[count++] = vp; + } + } + + qsort(list, count, sizeof *list, sort_var); + + for (vpp = list; count--; vpp++) + showvar(*vpp, cmd, xtra, show_value); + + /* no free(list), will be used again next time ... */ + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +static void __dead +export_usage(const char *cmd) +{ +#ifdef SMALL + if (*cmd == 'r') + error("Usage: %s [ -p | var[=val]... ]", cmd); + else + error("Usage: %s [ -p | [-n] var[=val]... ]", cmd); +#else + if (*cmd == 'r') + error("Usage: %s [-p [var...] | -q var... | var[=val]... ]", cmd); + else + error( + "Usage: %s [ -px [var...] | -q[x] var... | [-n|x] var[=val]... ]", + cmd); +#endif +} + +int +exportcmd(int argc, char **argv) +{ + struct var *vp; + char *name; + const char *p = argv[0]; + int flag = p[0] == 'r'? VREADONLY : VEXPORT; + int pflg = 0; + int nflg = 0; +#ifndef SMALL + int xflg = 0; + int qflg = 0; +#endif + int res; + int c; + int f; + +#ifdef SMALL +#define EXPORT_OPTS "np" +#else +#define EXPORT_OPTS "npqx" +#endif + + while ((c = nextopt(EXPORT_OPTS)) != '\0') { + +#undef EXPORT_OPTS + + switch (c) { + case 'n': + if (pflg || flag == VREADONLY +#ifndef SMALL + || qflg || xflg +#endif + ) + export_usage(p); + nflg = 1; + break; + case 'p': + if (nflg +#ifndef SMALL + || qflg +#endif + ) + export_usage(p); + pflg = 3; + break; +#ifndef SMALL + case 'q': + if (nflg || pflg) + export_usage(p); + qflg = 1; + break; + case 'x': + if (nflg || flag == VREADONLY) + export_usage(p); + flag = VNOEXPORT; + xflg = 1; + break; +#endif + } + } + + if ((nflg +#ifndef SMALL + || qflg +#endif + ) && *argptr == NULL) + export_usage(p); + +#ifndef SMALL + if (pflg && *argptr != NULL) { + while ((name = *argptr++) != NULL) { + int len; + + vp = find_var(name, NULL, &len); + if (name[len] == '=') + export_usage(p); + if (!goodname(name)) + error("%s: bad variable name", name); + + if (vp && vp->flags & flag) + showvar(vp, p, xflg ? "-x" : NULL, 1); + } + return 0; + } +#endif + + if (pflg || *argptr == NULL) + return showvars( pflg ? p : 0, flag, pflg, +#ifndef SMALL + pflg && xflg ? "-x" : +#endif + NULL ); + + res = 0; +#ifndef SMALL + if (qflg) { + while ((name = *argptr++) != NULL) { + int len; + + vp = find_var(name, NULL, &len); + if (name[len] == '=') + export_usage(p); + if (!goodname(name)) + error("%s: bad variable name", name); + + if (vp == NULL || !(vp->flags & flag)) + res = 1; + } + return res; + } +#endif + + while ((name = *argptr++) != NULL) { + int len; + + f = flag; + + vp = find_var(name, NULL, &len); + p = name + len; + if (*p++ != '=') + p = NULL; + + if (vp != NULL) { + if (nflg) + vp->flags &= ~flag; + else if (flag&VEXPORT && vp->flags&VNOEXPORT) { + /* note we go ahead and do any assignment */ + sh_warnx("%.*s: not available for export", + len, name); + res = 1; + } else { + if (flag == VNOEXPORT) + vp->flags &= ~VEXPORT; + + /* if not NULL will be done in setvar below */ + if (p == NULL) + vp->flags |= flag; + } + if (p == NULL) + continue; + } else if (nflg && p == NULL && !goodname(name)) + error("%s: bad variable name", name); + + if (!nflg || p != NULL) + setvar(name, p, f); + } + return res; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc, char **argv) +{ + char *name; + int c; + int flags = 0; /*XXX perhaps VUNSET from a -o option value */ + + if (! in_function()) + error("Not in a function"); + + /* upper case options, as bash stole all the good ones ... */ + while ((c = nextopt("INx")) != '\0') + switch (c) { + case 'I': flags &= ~VUNSET; break; + case 'N': flags |= VUNSET; break; + case 'x': flags |= VEXPORT; break; + } + + while ((name = *argptr++) != NULL) { + mklocal(name, flags); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, its + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ + +void +mklocal(const char *name, int flags) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + char *p; + p = ckmalloc(sizeof_optlist); + lvp->text = memcpy(p, optlist, sizeof_optlist); + lvp->rfunc = NULL; + vp = NULL; + xtrace_clone(0); + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + flags &= ~VNOEXPORT; + if (strchr(name, '=')) + setvareq(savestr(name), + VSTRFIXED | (flags & ~VUNSET)); + else + setvar(name, NULL, VSTRFIXED|flags); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + lvp->rfunc = NULL; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + lvp->v_u = vp->v_u; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (flags & (VDOEXPORT | VUNSET)) + vp->flags &= ~VNOEXPORT; + if (vp->flags & VNOEXPORT && + (flags & (VEXPORT|VDOEXPORT|VUNSET)) == VEXPORT) + flags &= ~VEXPORT; + if (flags & (VNOEXPORT | VUNSET)) + vp->flags &= ~VEXPORT; + flags &= ~VNOEXPORT; + if (name[vp->name_len] == '=') + setvareq(savestr(name), flags & ~VUNSET); + else if (flags & VUNSET) + unsetvar(name, 0); + else + vp->flags |= flags & (VUNSET|VEXPORT); + + if (vp == &line_num) { + if (name[vp->name_len] == '=') + funclinebase = funclineabs -1; + else + funclinebase = 0; + } + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + VTRACE(DBG_VARS, ("poplocalvar %s\n", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof_optlist); + ckfree(lvp->text); + xtrace_pop(); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + (void)unsetvar(vp->text, 0); + } else { + if (lvp->func && (lvp->flags & (VNOFUNC|VFUNCREF)) == 0) + (*lvp->func)(lvp->text + vp->name_len + 1); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + vp->v_u = lvp->v_u; + } + ckfree(lvp); + } +} + + +int +setvarcmd(int argc, char **argv) +{ + if (argc <= 2) + return unsetcmd(argc, argv); + else if (argc == 3) + setvar(argv[1], argv[2], 0); + else + error("List assignment not implemented"); + return 0; +} + + +/* + * The unset builtin command. We unset the function before we unset the + * variable to allow a function to be unset when there is a readonly variable + * with the same name. + */ + +int +unsetcmd(int argc, char **argv) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int flg_x = 0; + int ret = 0; + + while ((i = nextopt("efvx")) != '\0') { + switch (i) { + case 'f': + flg_func = 1; + break; + case 'e': + case 'x': + flg_x = (2 >> (i == 'e')); + /* FALLTHROUGH */ + case 'v': + flg_var = 1; + break; + } + } + + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap, flg_x); + } + return ret; +} + + +/* + * Unset the specified variable. + */ + +int +unsetvar(const char *s, int unexport) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return 0; + + if (vp->flags & VREADONLY && !(unexport & 1)) + return 1; + + INTOFF; + if (unexport & 1) { + vp->flags &= ~VEXPORT; + } else { + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, nullstr, 0); + if (!(unexport & 2)) + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags&(VEXPORT|VSTRFIXED|VREADONLY|VNOEXPORT)) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + } + INTON; + return 0; +} + + +/* + * Returns true if the two strings specify the same varable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +STATIC int +strequal(const char *p, const char *q) +{ + while (*p == *q++) { + if (*p++ == '=') + return 1; + } + if (*p == '=' && *(q - 1) == '\0') + return 1; + return 0; +} + +/* + * Search for a variable. + * 'name' may be terminated by '=' or a NUL. + * vppp is set to the pointer to vp, or the list head if vp isn't found + * lenp is set to the number of characters in 'name' + */ + +STATIC struct var * +find_var(const char *name, struct var ***vppp, int *lenp) +{ + unsigned int hashval; + int len; + struct var *vp, **vpp; + const char *p = name; + + hashval = 0; + while (*p && *p != '=') + hashval = 2 * hashval + (unsigned char)*p++; + + len = p - name; + if (lenp) + *lenp = len; + + vpp = &vartab[hashval % VTABSIZE]; + if (vppp) + *vppp = vpp; + + for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { + if (vp->name_len != len) + continue; + if (memcmp(vp->text, name, len) != 0) + continue; + if (vppp) + *vppp = vpp; + return vp; + } + return NULL; +} + +/* + * The following are the functions that create the values for + * shell variables that are dynamically produced when needed. + * + * The output strings cannot be malloc'd as there is nothing to + * free them - callers assume these are ordinary variables where + * the value returned is vp->text + * + * Each function needs its own storage space, as the results are + * used to create processes' environment, and (if exported) all + * the values will (might) be needed simultaneously. + * + * It is not a problem if a var is updated while nominally in use + * somewhere, all these are intended to be dynamic, the value they + * return is not guaranteed, an updated vaue is just as good. + * + * So, malloc a single buffer for the result of each function, + * grow, and even shrink, it as needed, but once we have one that + * is a suitable size for the actual usage, simply hold it forever. + * + * For a SMALL shell we implement only LINENO, none of the others, + * and give it just a fixed length static buffer for its result. + */ + +#ifndef SMALL + +struct space_reserved { /* record of space allocated for results */ + char *b; + int len; +}; + +/* rough (over-)estimate of the number of bytes needed to hold a number */ +static int +digits_in(intmax_t number) +{ + int res = 0; + + if (number & ~((1LL << 62) - 1)) + res = 64; /* enough for 2^200 and a bit more */ + else if (number & ~((1LL << 32) - 1)) + res = 20; /* enough for 2^64 */ + else if (number & ~((1 << 23) - 1)) + res = 10; /* enough for 2^32 */ + else + res = 8; /* enough for 2^23 or smaller */ + + return res; +} + +static int +make_space(struct space_reserved *m, int bytes) +{ + void *p; + + if (m->len >= bytes && m->len <= (bytes<<2)) + return 1; + + bytes = SHELL_ALIGN(bytes); + /* not ckrealloc() - we want failure, not error() here */ + p = realloc(m->b, bytes); + if (p == NULL) /* what we had should still be there */ + return 0; + + m->b = p; + m->len = bytes; + m->b[bytes - 1] = '\0'; + + return 1; +} +#endif + +char * +get_lineno(struct var *vp) +{ +#ifdef SMALL +#define length (8 + 10) /* 10 digits is enough for a 32 bit line num */ + static char result[length]; +#else + static struct space_reserved buf; +#define result buf.b +#define length buf.len +#endif + int ln = line_number; + + if (vp->flags & VUNSET) + return NULL; + + ln -= funclinebase; + +#ifndef SMALL + if (!make_space(&buf, vp->name_len + 2 + digits_in(ln))) + return vp->text; +#endif + + snprintf(result, length, "%.*s=%d", vp->name_len, vp->text, ln); + return result; +} +#undef result +#undef length + +#ifndef SMALL + +char * +get_hostname(struct var *vp) +{ + static struct space_reserved buf; + + if (vp->flags & VUNSET) + return NULL; + + if (!make_space(&buf, vp->name_len + 2 + 256)) + return vp->text; + + memcpy(buf.b, vp->text, vp->name_len + 1); /* include '=' */ + (void)gethostname(buf.b + vp->name_len + 1, + buf.len - vp->name_len - 3); + return buf.b; +} + +char * +get_tod(struct var *vp) +{ + static struct space_reserved buf; /* space for answers */ + static struct space_reserved tzs; /* remember TZ last used */ + static timezone_t last_zone; /* timezone data for tzs zone */ + const char *fmt; + char *tz; + time_t now; + struct tm tm_now, *tmp; + timezone_t zone = NULL; + static char t_err[] = "time error"; + int len; + + if (vp->flags & VUNSET) + return NULL; + + fmt = lookupvar("ToD_FORMAT"); + if (fmt == NULL) + fmt="%T"; + tz = lookupvar("TZ"); + (void)time(&now); + + if (tz != NULL) { + if (tzs.b == NULL || strcmp(tzs.b, tz) != 0) { + if (make_space(&tzs, strlen(tz) + 1)) { + INTOFF; + strcpy(tzs.b, tz); + if (last_zone) + tzfree(last_zone); + last_zone = zone = tzalloc(tz); + INTON; + } else + zone = tzalloc(tz); + } else + zone = last_zone; + + tmp = localtime_rz(zone, &now, &tm_now); + } else + tmp = localtime_r(&now, &tm_now); + + len = (strlen(fmt) * 4) + vp->name_len + 2; + while (make_space(&buf, len)) { + memcpy(buf.b, vp->text, vp->name_len+1); + if (tmp == NULL) { + if (buf.len >= vp->name_len+2+(int)(sizeof t_err - 1)) { + strcpy(buf.b + vp->name_len + 1, t_err); + if (zone && zone != last_zone) + tzfree(zone); + return buf.b; + } + len = vp->name_len + 4 + sizeof t_err - 1; + continue; + } + if (strftime_z(zone, buf.b + vp->name_len + 1, + buf.len - vp->name_len - 2, fmt, tmp)) { + if (zone && zone != last_zone) + tzfree(zone); + return buf.b; + } + if (len >= 4096) /* Let's be reasonable */ + break; + len <<= 1; + } + if (zone && zone != last_zone) + tzfree(zone); + return vp->text; +} + +char * +get_seconds(struct var *vp) +{ + static struct space_reserved buf; + intmax_t secs; + + if (vp->flags & VUNSET) + return NULL; + + secs = (intmax_t)time((time_t *)0) - sh_start_time; + if (!make_space(&buf, vp->name_len + 2 + digits_in(secs))) + return vp->text; + + snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, secs); + return buf.b; +} + +char * +get_euser(struct var *vp) +{ + static struct space_reserved buf; + static uid_t lastuid = 0; + uid_t euid; + struct passwd *pw; + + if (vp->flags & VUNSET) + return NULL; + + euid = geteuid(); + if (buf.b != NULL && lastuid == euid) + return buf.b; + + pw = getpwuid(euid); + if (pw == NULL) + return vp->text; + + if (make_space(&buf, vp->name_len + 2 + strlen(pw->pw_name))) { + lastuid = euid; + snprintf(buf.b, buf.len, "%.*s=%s", vp->name_len, vp->text, + pw->pw_name); + return buf.b; + } + + return vp->text; +} + +char * +get_random(struct var *vp) +{ + static struct space_reserved buf; + static intmax_t random_val = 0; + +#ifdef USE_LRAND48 +#define random lrand48 +#define srandom srand48 +#endif + + if (vp->flags & VUNSET) + return NULL; + + if (vp->text != buf.b) { + /* + * Either initialisation, or a new seed has been set + */ + if (vp->text[vp->name_len + 1] == '\0') { + int fd; + + /* + * initialisation (without pre-seeding), + * or explictly requesting a truly random seed. + */ + fd = open("/dev/urandom", 0); + if (fd == -1) { + out2str("RANDOM initialisation failed\n"); + random_val = (getpid()<<3) ^ time((time_t *)0); + } else { + int n; + + do { + n = read(fd,&random_val,sizeof random_val); + } while (n != sizeof random_val); + close(fd); + } + } else + /* good enough for today */ + random_val = strtoimax(vp->text+vp->name_len+1,NULL,0); + + srandom((long)random_val); + } + +#if 0 + random_val = (random_val + 1) & 0x7FFF; /* 15 bit "random" numbers */ +#else + random_val = (random() >> 5) & 0x7FFF; +#endif + + if (!make_space(&buf, vp->name_len + 2 + digits_in(random_val))) + return vp->text; + + snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, + random_val); + + if (buf.b != vp->text && (vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free(vp->text); + vp->flags |= VTEXTFIXED; + vp->text = buf.b; + + return vp->text; +#undef random +#undef srandom +} + +#endif /* SMALL */ diff --git a/bin/sh/var.h b/bin/sh/var.h new file mode 100644 index 0000000..43bef4b --- /dev/null +++ b/bin/sh/var.h @@ -0,0 +1,151 @@ +/* $NetBSD: var.h,v 1.38 2018/12/04 14:03:30 kre Exp $ */ + +/*- + * 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. + * + * @(#)var.h 8.2 (Berkeley) 5/4/95 + */ + +#ifndef VUNSET /* double include protection */ +/* + * Shell variables. + */ + +/* flags */ +#define VUNSET 0x0001 /* the variable is not set */ +#define VEXPORT 0x0002 /* variable is exported */ +#define VREADONLY 0x0004 /* variable cannot be modified */ +#define VNOEXPORT 0x0008 /* variable may not be exported */ + +#define VSTRFIXED 0x0010 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x0020 /* text is statically allocated */ +#define VSTACK 0x0040 /* text is allocated on the stack */ +#define VNOFUNC 0x0100 /* don't call the callback function */ +#define VFUNCREF 0x0200 /* the function is called on ref, not set */ + +#define VSPECIAL 0x1000 /* magic properties not lost when set */ +#define VDOEXPORT 0x2000 /* obey VEXPORT even if VNOEXPORT */ +#define VNOSET 0x4000 /* do not set variable - just readonly test */ +#define VNOERROR 0x8000 /* be quiet if set fails (no error msg) */ + +struct var; + +union var_func_union { /* function to be called when: */ + void (*set_func)(const char *); /* variable gets set/unset */ + char*(*ref_func)(struct var *); /* variable is referenced */ +}; + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + char *text; /* name=value */ + int name_len; /* length of name */ + union var_func_union v_u; /* function to apply (sometimes) */ +}; + + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + char *text; /* saved text */ + union var_func_union v_u; /* saved function */ +}; + + +extern struct localvar *localvars; + +extern struct var vifs; +extern char ifs_default[]; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var line_num; +#ifndef SMALL +extern struct var editrc; +extern struct var vterm; +extern struct var vtermcap; +extern struct var vhistsize; +extern struct var ps_lit; +extern struct var euname; +extern struct var random_num; +extern intmax_t sh_start_time; +#endif + +extern int line_number; +extern int funclinebase; +extern int funclineabs; + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ + +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define ifsval() (ifsset() ? (vifs.text + 4) : ifs_default) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define optindval() (voptind.text + 7) +#ifndef SMALL +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct strlist; +void listsetvar(struct strlist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +char **environment(void); +void shprocvar(void); +int showvars(const char *, int, int, const char *); +void mklocal(const char *, int); +void listmklocal(struct strlist *, int); +void poplocalvars(void); +int unsetvar(const char *, int); +void choose_ps1(void); +int setvarsafe(const char *, const char *, int); +void print_quoted(const char *); +int validname(const char *, int, int *); + +#endif diff --git a/bin/sh/version.h b/bin/sh/version.h new file mode 100644 index 0000000..59069e4 --- /dev/null +++ b/bin/sh/version.h @@ -0,0 +1,36 @@ +/* $NetBSD: version.h,v 1.3 2018/12/12 12:16:42 kre Exp $ */ + +/*- + * Copyright (c) 2014 The NetBSD Foundation, Inc. + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +/* + * This value should be modified rarely - only when significant changes + * to the shell (which does not mean a bug fixed, or some new feature added) + * have been made. The most likely reason to change this value would be + * when a new (shell only) release is to be exported. This should not be + * updated just because a new NetBSD release is to include this code. + */ +#define NETBSD_SHELL "20181212" |