diff options
Diffstat (limited to 'usr/src/lib/libshell/common')
161 files changed, 79566 insertions, 0 deletions
diff --git a/usr/src/lib/libshell/common/COMPATIBILITY b/usr/src/lib/libshell/common/COMPATIBILITY new file mode 100644 index 0000000000..8f20d44374 --- /dev/null +++ b/usr/src/lib/libshell/common/COMPATIBILITY @@ -0,0 +1,129 @@ + + KSH-93 VS. KSH-88 + + +The following is a list of known incompatibilities between ksh-93 and ksh-88. +I have not include cases that are clearly bugs in ksh-88. I also have +omitted features that are completely upward compatible. + +1. Functions, defined with name() with ksh-93 are compatible with + the POSIX standard, not with ksh-88. No local variables are + permitted, and there is no separate scope. Functions defined + with the function name syntax, maintain compatibility. + This also affects function traces. + +2. ! is now a reserved word. As a result, any command by that + name will no longer work with ksh-93. + +3. The -x attribute of alias and typeset -f is no longer + effective and the ENV file is only read for interactive + shells. You need to use FPATH to make function definitions + visible to scripts. + +4. A built-in command named command has been added which is + always found before the PATH search. Any script which uses + this name as the name of a command (or function) will not + be compatible. + +5. The output format for some built-ins has changed. In particular + the output format for set, typeset and alias now have single + quotes around values that have special characters. The output + for trap without arguments has a format that can be used as input. + +6. With ksh-88, a dollar sign ($') followed by a single quote was + interpreted literally. Now it is an ANSI-C string. You + must quote the dollar sign to get the previous behavior. + Also, a $ in front of a " indicates that the string needs + to be translated for locales other than C or POSIX. The $ + is ignored in the C and POSIX locale. + +7. With ksh-88, tilde expansion did not take place inside ${...}. + with ksh-93, ${foo-~} will cause tilde expansion if foo is + not set. You need to escape the ~ for the previous behavior. + +8. Some changes in the tokenizing rules where made that might + cause some scripts with previously ambiguous use of quoting + to produce syntax errors. + +9. Programs that rely on specific exit values for the shell, + (rather than 0 or non-zero) may not be compatible. The + exit status for many shell failures has been changed. + +10. Built-ins in ksh-88 were always executed before looking for + the command in the PATH variable. This is no longer true. + Thus, with ksh-93, if you have the current directory first + in your PATH, and you have a program named test in your + directory, it will be executed when you type test; the + built-in version will be run at the point /bin is found + in your PATH. + +11. Some undocumented combinations of argument passing to ksh + builtins no longer works since ksh-93 is getopts conforming + with respect to its built-ins. For example, typeset -8i + previously would work as a synonym for typeset -i8. + +12. Command substitution and arithmetic expansion are now performed + on PS1, PS3, and ENV when they are expanded. Thus, ` and $( + as part of the value of these variables must be preceded by a \ + to preserve their previous behavior. + +13. The ERRNO variable has been dropped. + +14. If the file name following a redirection symbol contain pattern + characters they will only be expanded for interactive shells. + +15. The arguments to a dot script will be restored when it completes. + +16. The list of tracked aliases is not displayed with alias unless + the -t option is specified. + +17. The POSIX standard requires that test "$arg" have exit status + of 0, if and only if $arg is null. However, since this breaks + programs that use test -t, ksh93 treats an explicit test -t + as if the user had entered test -t 1. + +18. The ^T directive of emacs mode has been changed to work the + way it does in gnu-emacs. + +19. ksh-88 allowed unbalanced parenthes within ${name op val} whereas + ksh-93 does not. Thus, ${foo-(} needs to be written as ${foo-\(} + which works with both versions. + +20. kill -l in ksh-93 lists only the signal names, not their numerical + values. + +21. Local variables defined by typeset are statically scoped in + ksh93. In ksh88 they were dynamically scoped although this + behavior was never documented. + +22. The value of the variable given to getopts is set to ? when + the end-of-options is reached to conform to the POSIX standard. + +23. Since the POSIX standard requires that octal constants be + recongnized, doing arithmetic on typeset -Z variables can + yield different results that with ksh88. Most of these + differences were eliminated in ksh93o. + +24. Starting after ksh93l, If you run ksh name, where name does + not contain a /, the current directory will be searched + before doing a path search on name as required by the POSIX + shell standard. + +25. In ksh93, cd - will output the directory that it changes + to on standard output as required by X/Open. With ksh88, + this only happened for interactive shells. + +26. As an undocumented feature of ksh-88, a leading 0 to an + assignment of an integer variable caused that variable + to be treated as unsigned. This behavior was removed + starting in ksh93p. + +27. The getopts builtin in ksh93 requires that optstring contain + a leading + to allow options to begin with a +. + +28. In emacs/gmacs mode, control-v will not display the version when + the stty lnext character is set to control-v or is unset. + The sequence escape control-v will display the shell version. + +I am interested in expanding this list so please let me know if you +uncover any others. diff --git a/usr/src/lib/libshell/common/DESIGN b/usr/src/lib/libshell/common/DESIGN new file mode 100644 index 0000000000..8e35e3fc55 --- /dev/null +++ b/usr/src/lib/libshell/common/DESIGN @@ -0,0 +1,163 @@ +Here is an overview of the source code organization for ksh93. + +Directory layout: + + The directory include contains header files for ksh93. + The files nval.h and shell.h are intended to be public + headers and can be used to add runtime builtin command. + The remainder are private. + + The directory data contains readonly data files for ksh93. + + The directory edit contains the code for command line + editing and history. + + The fun directory contains some shell function such as + pushd, popd, and dirs. + + The directory features contains files that are used to generate + header files in the FEATURE directory. Most of these files + are in a format that is processed by iffe. + + The directory bltins contains code for most of the built-in + commands. Additional built-in commands are part of libcmd. + + The directory sh contains most of the code for ksh93. + + The directory tests contains a number of regression tests. + In most cases, when a bug gets fixed, a test is added to + one of these files. The regression tests can be run by + going to this directory and running + SHELL=shell_path shell_path shtests + where shell_path is an absolute pathname for the shell to + be tested. + + The top level directory contains the nmake Makefile, a README, + and several documentation files. The RELEASE file contains + the list of bug fixes and new features since the original + ksh93 release. The file COMPATIBILITY is a list of all + known incompatibilities with ksh88. + + The bash_pre_rc.sh is a startup script used when emulating + bash if the shell is compiled with SHOPT_BASH and the shell + is invoked as bash. The bash emulation is not complete. + +Include directory: + 1. argnod.h contains the type definitions for command + nodes, io nodes, argument nodes, and for positional + parameters.a It defines the prototypes for + all the positional parameters functions. + 2. builtins.h contains prototypes for builtins as well + as symbolic constants that refer to the name-pairs + that are associated with some of the built-ins. + It also contains prototypes for many of the strings. + 3. defs.h is the catch all for all definitions that + don't fit elsewhere and it includes several other + headers. It defines a strucuture that contains ksh + global data, sh, and a structure that contains per + function data, sh.st. + 4. edit.h contains definitions that are common to both + vi and emacs edit modes. + 5. env.h contains interfaces for creating and modifying + environment variables. + 6. fault.h contains prototypes for signal related + functions and trap and fault handling. + 7. fcin.h contains macro and function definitions for + reading from a file or string. + 8. history.h contains macros and functions definitions + related to history file processing. + 9. jobs.h contains the definitions relating to job + processing and control. + 10. lexstates.h contains the states associated with + lexical processing. + 11. name.h contains the internal definitions related + to name-value pair processing. + 12. national.h contains a few I18N definitions, mostly + obsolete. + 13. nval.h is the public interface to the name-value + pair library that is documented with nval.3. + 14. path.h contains the interface for pathname processing + and pathname searching. + 15. shell.h is the public interface for shell functions + that are documented int shell.3. + 16. shlex.h contains the lexical token definitions and + interfaces for lexical analysis. + 17. shnodes.h contains the definition of the structures + for each of the parse nodes and flags for the attributes. + 18. shtable.h contains some interfaces and functions for + table lookup. + 19. streval.h contains the interface to the arithmetic + functions. + 20. terminal.h is a header file that includes the appropriate + terminal include. + 21. test.h contains the definitions for the test and [[...]] + commands. + 22. timeout.h contains the define constant for the maximum + shell timeout. + 23. ulimit.h includes the appropriate resource header. + 24. variables.h contains symbolic constants for the built-in + shell variables. + +sh directory: + 1. args.c contains functions for parsing shell options + and for processing positional parameters. + 2. arith.c contains callback functions for the streval.c + library and the interface to shell arithmetic. + 3. array.c contains the code for indexed and associative + arrays. + 4. bash.h contains code used when compiling with SHOPT_BASH + to add bash specific features such as shopt. + 5. defs.c contains the data definitions for global symbols. + 6. deparse.c contains code to generate shell script from + a parse tree. + 7. env.c contains code to add and delete environment variables + to an environment list. + 8. expand.c contains code for file name expansion and + file name generation. + 9. fault.c contains code for signal processing, trap + handling and termination. + 10. fcin.c contains code for reading and writing a character + at a time from a file or string. + 11. init.c contains initialization code and callbacks + for get and set functions for built-in variables. + 12. io.o contains code for redirections and managing file + descriptors and file streams. + 13. jobs.c contains the code for job management. + 14. lex.c contains the code for the lexical analyzer. + 15. macro.c contains code for the $ macro expansions, including + here-documents. + 16. main.c contains the calls to initialization, profile + processing and the main evaluation loop as well as + mail processing. + 17. name.c contains the name-value pair routines that are + built on the hash library in libast. + 18. nvdisc.c contains code related to name-value pair disciplines. + 19. nvtree.c contains code for compound variables and for + walking the namespace. + 20. parse.c contains the code for the shell parser. + 21. path.c contains the code for pathname lookup and + some path functions. It also contains the code + that executes commands and scripts. + 22. pmain.c is just a calls sh_main() so that all of the + rest of the shell can be in a shared library. + 23. shcomp.c contains the main program to the shell + compiler. This program parses a script and creates + a file that the shell can read containing the parse tree. + 24. streval.c is an C arithmetic evaluator. + 25. string.c contains some string related functions. + 26. subshell.c contains the code to save and restore + environments so that subshells can run without creating + a new process. + 27. suid_exec.c contains the program from running execute + only and/or setuid/setgid scripts. + 28. tdump.c contains the code to dump a parse tree into + a file. + 29. timers.c contains code for multiple event timeouts. + 30. trestore contians the code for restoring the parse + tree from the file created by tdump. + 31. userinit.c contains a dummy userinit() function. + This is now obsolete with the new version of sh_main(). + 32. waitevent.c contains the sh_waitnotify function so + that builtins can handle processing events when the + shell is waiting for input or for process completion. + 33. xec.c is the main shell executuion loop. diff --git a/usr/src/lib/libshell/common/OBSOLETE b/usr/src/lib/libshell/common/OBSOLETE new file mode 100644 index 0000000000..c29cb8bb63 --- /dev/null +++ b/usr/src/lib/libshell/common/OBSOLETE @@ -0,0 +1,152 @@ +.sp 3 +.tl ''Ksh Features That Are Obsolete in Ksh93'' +.sp 2 +.AL 1 +.LI +Using a pair of grave accents \^\fB\(ga\fR ... \fB\(ga\fR\^ +for command substition. Use \fB$(\fR ... \fB)\fR instead. +.LI +.B FCEDIT +is an obsolete name for +the default editor name for the +.B hist +command. +.B FCEDIT +is not used when +.B HISTEDIT +is set. Use +.B HISTEDIT +instead. +.LI +The newtest (\fB[[\fR ... \fB]]\fR) operator +\fB\-a\fP \fIfile\fP +is obsolete. Use +\fB\-e\fP instead. +.LI +The newtest (\fB[[\fR ... \fB]]\fR) operator +.BR = , +as used in +\fIstring\fP \fB=\fP \fIpattern\fP +is obsolete. Use +\fB==\fP instead. +.LI +The following obsolete arithmetic comparisons are also permitted: +.in +5 +.VL 20 +.LI "\fIexp1\fP \fB\-eq\fP \fIexp2\fP" +True, if +.I exp1 +is equal to +.IR exp2 . +.LI "\fIexp1\fP \fB\-ne\fP \fIexp2\fP" +True, if +.I exp1 +is not equal to +.IR exp2 . +.LI "\fIexp1\fP \fB\-lt\fP \fIexp2\fP" +True, if +.I exp1 +is less than +.IR exp2 . +.LI "\fIexp1\fP \fB\-gt\fP \fIexp2\fP" +True, if +.I exp1 +is greater than +.IR exp2 . +.LI "\fIexp1\fP \fB\-le\fP \fIexp2\fP" +True, if +.I exp1 +is less than or equal to +.IR exp2 . +.LI "\fIexp1\fP \fB\-ge\fP \fIexp2\fP" +True, if +.I exp1 +is greater than or equal to +.IR exp2 . +.LE \" End .VL +.in -5 +.LI +Using test -t or [ -t ] without specifying the file unit number. +.LI +The +.B \-k +option to the \fBset\fR builtin is obsolete. It causes +.I all\^ +variable assignment arguments are placed in the environment, +even if they occur after the command name. +The following +first prints +.B "a=b c" +and then +.BR c : +There is no alternative. +.LI +The obsolete +.B \-xf +option of the +.B typeset +command allows a function to be exported +to scripts that are executed without a separate +invocation of the shell. +Functions that need to be defined across separate +invocations of the shell should +be placed in a directory and the +.B FPATH +variable should contains the name of this directory. +They may also +be specified in the +.B ENV +file with the +.B \-xf +option of +.BR typeset . +.LI +The shell environment variable +.B FCEDIT +is obsolete. Use +.B HISTEDIT +instead. +.LI +In the +.B \-s +option +(to \fBfc\fR or \fBhist\fR command???) +( +and in obsolete versions, the editor name +.B \- +) +is used to skip the editing phase and +to re-execute the command. +.LI +The +.B \-t +option to \fBalias\fR builtin is is obsolete. It +is used to set and list tracked aliases. +There is no replacement. +.LI +The shell command line option +.B \-t +is obsolete. This option cause the shell to exit after reading +and executing one command. The is no replacement (although ending +\&"command" with the exit builtin should have the same effect). +.LI +As an obsolete feature of the "set" builtin, +if the first +.I arg\^ +is +.B \- +then the +.B \-x +and +.B \-v +options are turned off and the next +.I arg +is treated as the first argument. +Using +.B \+ +rather than +.B \- +causes these options to be turned off. +These options can also be used upon invocation of the shell. +.LE + diff --git a/usr/src/lib/libshell/common/PROMO.mm b/usr/src/lib/libshell/common/PROMO.mm new file mode 100644 index 0000000000..332ff3a855 --- /dev/null +++ b/usr/src/lib/libshell/common/PROMO.mm @@ -0,0 +1,141 @@ +.H 1 ksh93 +KSH-93 is the most recent version of the KornShell Language +described in +"The KornShell Command and Programming Language," +by Morris Bolsky and David Korn of AT&T Bell Laboratories, ISBN 0-13-182700-6. +The KornShell is a shell programming language, +which is upward compatible with "sh" (the Bourne Shell), +and is intended to conform to the IEEE P1003.2/ISO 9945.2 Shell and +Utilities standard. +KSH-93 provides an enhanced programming environment in +addition to the major command-entry features of the BSD +shell "csh". With KSH-93, medium-sized programming tasks can be +performed at shell-level without a significant loss in performance. +In addition, "sh" scripts can be run on KSH-93 without modification. +.P +The code should conform to the IEEE POSIX 1003.1 standard and to the +proposed ANSI-C standard so that it should be portable to all +such systems. Like the previous version, KSH-88, +it is designed to accept eight bit character sets +transparently, thereby making it internationally compatible. +It can support multi-byte characters sets with some characteristics +of the character set given at run time. +.P +KSH-93 provides the following features, many of which were also inherent +in KSH-88: +.BL +.LI +Enhanced Command Re-entry Capability: The KSH-93 history +function records commands entered at any shell level and stores +them, up to a user-specified limit, even after you log off. +This allows you to re-enter long commands with a few keystrokes +- even those commands you entered yesterday. +The history file allows for eight bit characters in +commands and supports essentially unlimited size histories. +.LI +In-line Editing: In "sh", the only way to fix mistyped +commands is to backspace or retype the line. KSH-93 allows you +to edit a command line using a choice of EMACS-TC or "vi" +functions. +You can use the in-line editors to complete filenames as +you type them. +You may also use this editing feature when entering +command lines from your history file. +A user can capture keystrokes and rebind keys to customize the +editing interface. +.LI +Extended I/O Capabilities: KSH-93 provides several I/O +capabilities not available in "sh", including the ability to: +.BL +.LI +specify a file descriptor for input and output +.LI +start up and run co-processes +.LI +produce a prompt at the terminal before a read +.LI +easily format and interpret responses to a menu +.LI +echo lines exactly as output without escape processing +.LI +format output using printf formats. +.LI +read and echo lines ending in "\e". +.LE +.LI +Improved performance: KSH-93 executes many scripts faster +than the System V Bourne shell. A major reason for this is +that many of the standard utilities are built-in. +To reduce the time to initiate a command, KSH-93 allows +commands to be added as built-ins at run time +on systems that support dynamic loading such as System V Release 4. +.LI +Arithmetic: KSH-93 allows you to do integer arithmetic in any +base from two to sixty-four. You can also do double +precision floating point arithmetic. +Almost the complete set of C language operators are available +with the same syntax and precedence. +Arithmetic expressions can be used to as an argument expansion +or as a separate command. +In addition there is an arithmetic for command that works +like the for statement in C. +.LI +Arrays: KSH-93 supports both indexed and associative arrays. +The subscript for an indexed array is an arithmetic expression, +whereas, the subscript for an associative array is a string. +.LI +Shell Functions and Aliases: Two mechanisms - functions and +aliases - can be used to assign a user-selected identifier to +an existing command or shell script. +Functions allow local variables and provide scoping +for exception handling. +Functions can be searched for and loaded on first reference the +way scripts are. +.LI +Substring Capabilities: KSH-93 allows you to create a +substring of any given string either by specifying the starting +offset and length, or by stripping off leading +or trailing substrings during parameter substitution. +You can also specify attributes, such as upper and lower case, +field width, and justification to shell variables. +.LI +More pattern matching capabilities: KSH-93 allows you to specify +extended regular expressions for file and string matches. +.LI +KSH-93 uses a hierarchal name space for variables. +Compound variables can be defined and variables can +be passed by reference. In addition, each variable +can have one or more disciplines associated with +it to intercept assignments and references. +.LI +Improved debugging: KSH-93 can generate line numbers on execution +traces. Also, I/O redirections are now traced. +There is a DEBUG trap that gets evaluated after each command +so that errors can be localized. +.LI +Job Control: On systems that support job control, including +System V Release 4, KSH-93 +provides a job-control mechanism almost identical to that of +the BSD "csh", version 4.1. +This feature allows you +to stop and restart programs, and to move programs between the +foreground and the background. +.LI +Added security: +KSH-93 can execute scripts which do not have read permission +and scripts which have the setuid and/or setgid set when +invoked by name, rather than as an argument to the shell. +It is possible to log or control the execution of setuid and/or +setgid scripts. +The noclobber option prevents you from accidentally erasing +a file by redirecting to an existing file. +.LI +KSH-93 can be extended by adding built-in commands at run time. +In addition, KSH-93 can be used as a library that can +be embedded into an application to allow scripting. +.LE +Documentation for KSH-93 consists of an "Introduction to KSH-93", +"Compatibility with the Bourne Shell" and a manual page and a +README file. In addition, the "New KornShell Command and Programming +Language," book is available from Prentice Hall. + diff --git a/usr/src/lib/libshell/common/README b/usr/src/lib/libshell/common/README new file mode 100644 index 0000000000..3c3899fda2 --- /dev/null +++ b/usr/src/lib/libshell/common/README @@ -0,0 +1,227 @@ +This directory, and its subdirectories contain the source code +for ksh-93; the language described in the second addition of +the book, "The KornShell Command and Programming Language," by +Morris Bolsky and David Korn which is published by Prentice Hall. +ksh-93 has been compiled and run on several machines with several +operating systems. The end of this file contains a partial list of +operating systems and machines that ksh-93 has been known to run on. + +The layout of files for ksh-93 has changed somewhat since ksh-88, +the last major release. Most of the source code for ksh remains in +the sh directory. However, the shell editing and history routines +are in the edit sub-directory. The code for shell built-ins is +in the bltins directory. The data directory contains read-only +data tables and messages that are used by the shell. The include +files remain in the include directory and the shlib directory +is gone. The features directory replaces the older install +directory. The method for generating systems specific feature +information has changed substantially. + +The Makefile file contains several compilation options that can be set +before compiling ksh. Options are of the form SHOPT_option and become +#define inside the code. These options are set to their recommended +value and some of these may disappear as options in future releases. +A value of 0, or no value represents off, 1 represents on. +Note that == is needed, not =, because these are nmake state variables +and changing their value will cause all modules that could be affected +by this change to be recompiled. +The options have the following defaults and meanings: + ACCT off Shell accounting. + ACCTFILE off Enable per user accounting info + APPEND on Allows var+=val string and array append. + BASH off Bash compatibility mode. It is not fully implemented + and is experimental. + BRACEPAT on C-shell type abc{d,e}f style file generation + CMDLIB_BLTIN off Makes all commands in libcmd.a builtins. The + SH_CMDLIB_DIR nmake state variable can be used to + specify a directory. + CMDLIB_DIR off Sets CMDLIB_BLTIN=1 and provides a default value + of "/opt/ast/bin" for SH_CMDLIB_DIR. + COMPOUND_ARRAY + on Allows all components of compound variables except the + first to be any string by enclosing in [...]. It also + allows components other than the last to be arrays. + This is experimental and only partially complete. + CRNL off <cr><nl> treated as <nl> in shell grammar. + DYNAMIC on Dynamic loading of builtins. (Requires dlopen() interface.) + ECHOPRINT off Make echo equivalent to print. + ESH on Compile with emacs command line editing. The original + emacs line editor code was provided by Mike Veach at IH. + FILESCAN on Experimental option that allows fast reading of files + using while < file;do ...; done and allowing fields in + each line to be accessed as positional parameters. + FS_3D off For use with 3-D file system. Enabled automatically for + sytems with dynamic linking. + KIA off Allow generation of shell cross reference database with -I. + MULTIBYTE on Multibyte character handling. Requires mblen() and + mbctowc(). + NAMESPACE on Allows namespaces. This is experimental, incomplete + and undocumented. + OLDTERMIO off Use either termios or termio at runtime. + OO on Experimental object oriented extension. This option + should disappear soon. + OPTIMIZE on Optimize loop invariants for with for and while loops. + P_SUID off If set, all real uids, greater than or equal to this + value will require the -p flag to run suid/sgid scripts. + PFSH off Compile with support for profile shell. + RAWONLY off Turn on if the vi line mode doesn't work right unless + you do a set -o viraw. + SEVENBIT off Strip the eigth bit from characters. + SPAWN off Use spawn as combined fork/exec. May improve speed on + some systems. + SUID_EXEC on Execute /etc/suid_exec for setuid, setgid script. + TIMEOUT off Set this to the number of seconds for timing out and + exiting the shell when you don't enter a command. If + non-zero, TMOUT can not be set larger than this value. + VSH on Compile with vi command line editing. The original vi + line editor code was provided by Pat Sullivan at CB. + +The following compile options are set automatically by the feature testing: + DEVFD Set when /dev/fd is a directory that names open files. + SHELLMAGIC + Set on systems that recognize script beginning with #! specially. + VPIX Set on systems the have /usr/bin/vpix program for running MS-DOS. + + +In most instances, you will generate ksh from a higher level directory +which also generates libcmd and libast libraries on which ksh depends. +However, it is possible to generate ksh, with by running make -f ksh.mk +in this directory. The ksh.mk file was generated from the nmake Makefile. +If you do not have make or nmake, but do have a Version 7 UNIX compatible +shell, then you can run the script mamexec < Mamfile to build ksh. +If you have nmake, version 2.3 or later, you can use it without the -f ksh.mk. +In either case, ksh relies on libraries libast and libcmd which must be +built first. The binary for ksh becomes the file named ./ksh which can +be copied to where ever you install it. + +If you use old make or the Mamfile, and you system has dynamic shared +libraries, then you should define the variables mam_cc_static and +mam_cc_dynanamic as the compiler options that request static linking +and dynamic linking respectively. This will decrease the number of +shared libraries that ksh need and cut startup time substantially. + +The makefile should also generate shcomp, a program that will precompile +a script. ksh93 is able to recognize files in this format and process +them as scripts. You can use shcomp to send out scripts when you +don't want to give away the original script source. + +It is advisable that you put the line PWD=$HOME;export PWD into the +/etc/profile file to reduce initialization time for ksh. + +To be able to run setuid/setgid shell scripts, or scripts without read +permission, the SUID_EXEC compile option must be on, and ksh must be installed +in the /bin directory, the /usr/bin directory, the /usr/lbin directory, +or the /usr/local/bin directory and the name must end in sh. The program +suid_exec must be installed in the /etc directory, must be owned by root, +and must be a suid program. If you must install ksh in some other directory +and want to be able to run setuid/setgid and execute only scripts, then +you will have to change the source code file sh/suid_exec.c explicitly. +If you do not have ksh in one of these secure locations, /bin/sh will +be invoked with the -p options and will fail when you execute a setuid/setgid +and/or execute only script. Note, that ksh does not read the .profile +or $ENV file when it the real and effective user/group id's are not +equal. + +The tests sub-directory contains a number of regression tests for ksh. +To run all these tests with the shell you just built, go to the tests +directory and run the command + SHELL=../ksh shtests + +The file PROMO.mm is an advertisement that extolls the virtues of ksh. +The file sh.1 contains the troff (man) description of this Shell. +The file nval.3 contains the troff (man) description of the name-value +pair library that is needed for writing built-ins that need to +access shell variables. + +The file sh.memo contains a draft troff (mm) memo describing ksh. The +file RELEASE88 contains the changes made for ksh88. The file RELEASE93 +contains the changes made in this release since ksh-88. The file +RELEASE contains bug fixes made in this release since ksh-88. The file +COMPATIBILITY contains a list of incompatibilities with ksh-88. The +file bltins.mm is a draft troff (mm) memo describing how to write +built-in commands that can be loaded at run time. + +Most of the work for internationalization has been done with ksh93. +The file ksh.msg is a generated file that contains error messages +that need to be translated. In addition, the function translate() +in sh/init.c has to be completed to interface with the dictionary +lookup. The translate function takes two argument, the string +that is to be translated and a type which is + 0 when a library string needs translation. + 1 when one of the error messages in ksh.msg needs translation. + 2 when a string in a script needs translation. You use a $ in front + of a double quoted string in a script to indicate that it + needs translation. The -D option for ksh builds the dictionary. +The translate routine needs to return the translated message. +For dictionaries that need to use a numeric key, it should be +possible to use the strhash() function to generate numbers to +go along with each of the messages and to use this number both +when generating the dictionary and when converting strings. +If you encounter error messages of type 1 that are not be translated via +this translate() function send mail to the address below. + +Please report any problems or suggestions to: + +dgk@research.att.com + + +ksh93 has been compiled and alpha tested on the following. An asterisk +signifies that ksh has been installed as /bin/sh on this machine. + +* Sun OS 4.1.[123] on sparc. + Sun OS 4.1.1 on sun. + Solaris 2.[1-9] on sparc. + Solaris 2.[4-8] on X86. + HP/UX 8 on HP-9000/730. + HP/UX 9 on HP-9000/730. + HP/UX 10 on HP-9000/857. + HP/UX 11 on pa-risc. + System V Release 3 on Counterpoint C19 + System V Release 4 on AT&T Intel 486. + System V Release 4 on NCR 4850 Intel 486. + IRIX Release 4.0.? System V on SGI-MIPS. + IRIX Release 5.1 System V on SGI-MIPS. + IRIX Release 6.[1-5] System V on SGI-MIPS. + System V Release 3.2 on 3B2. + UTS 5.2.6 on Amdahl 3090,5990,580. + System V Release 3.2 on i386. + SMP_DC.OSx olivetti dcosx MIServer-S 2/128. + SMP_DC.OSx Pyramid dcosx MIServer-S 2/160 r3000. + 4.3BSD on Vax 8650. + AIX release 2 on RS6000. + AIX 3.2 on RS6000. + Linux 1.X on Intel + Linux 2.X on Intel + Linux 2.X on Alpha + Linux 2.X on Alpha + Linux 2.X on OS/390 + Linux 2.X on sparc + Linux 2.4 on intel itanium 64 + Linux Slackware on sparc64 +* Linux ARM on i-PAQ + OSF1 on DEC alpha. + OSF4 on DEC alpha. + UMIPS 4.52 on mips. + BSD-i [2-4] on X86. + OpenBSD on X86 + NetBSD on X86 + FreeBSD on X86 + NeXT on Intel X86. + NeXT on HP. +* Windows NT using UWIN on X86 +* Windows NT using UWIN on alpha + Windows NT using Cygwin on X86 + Windows NT with NutCracker libraries. + Windows NT with Portage libraries. + Windows 3.1 using custom C library. + OpenEdition on MVS + Darwin OS X on PPC + MVS on OS 390 + SCO Openserver 3.2 on X86 + Unixware 7 on X86 + +Good luck!! + +David Korn +dgk@research.att.com + diff --git a/usr/src/lib/libshell/common/RELEASE b/usr/src/lib/libshell/common/RELEASE new file mode 100644 index 0000000000..034ca7033d --- /dev/null +++ b/usr/src/lib/libshell/common/RELEASE @@ -0,0 +1,1455 @@ +07-04-18 --- Release ksh93s+ --- +07-04-18 A small memory leak with each redirection of a non-builtin has + been fixed. +07-03-08 A bug in which set +o output command line options has been fixed. +07-03-08 A bug in which an error in read (for example, an invalid variable + name), could leave the terminal in raw mode has been fixed. +07-03-06 A bug in which read could core dump when specified with an array + variable with a subscript that is an arithmetic expression has + been fixed. +07-03-06 Several serious bugs with the restricted shell were reported and + fixed. +07-03-02 If a job is stopped, and subsequently restarted with a CONT + signal and exits normally, ksh93 was incorrectly exiting with + the exit status of the stop signal number. +07-02-26 M-^L added to emacs mode to clear the screen. +07-02-26 A bug in which setting a variable readonly in a subshell would + cause an unset error when the subshell completed has been fixed. +07-02-19 The format with printf uses the new = flag to center the output. +07-02-19 A bug in which ksh93 did not allow multibyte characters in + identifier names has been fixed. +07-02-19 A bug introduced in ksh93 that causes global compound variable + definitions inside functions to exit with "no parent" has been fixed. +07-02-19 A bug in which using compound commands in process redirection + arguments would give syntax errors <(...) and >(...) has been fixed. +07-01-29 A bug which caused the shell to core dump which can occur when a + built-in exits without closing files that it opens has been fixed. +07-01-26 A bug in which ~(E) in patterns containing \ that are not inside () + has been fixed. + +06-12-29 --- Release ksh93s --- +06-12-29 A bug in which the value of IFS could be changed after a command + substitution has been fixed. +06-12-22 /dev/(tcp|udp|sctp)/HOST/SEVRICE now handles IPv6 addresses on + systems that provide getaddrinfo(3). +06-12-19 A -v option was added to read. With this option the value of + the first variable name argument will become the default value + when read from a terminal device. +06-11-20 A bug in which "${foo[@]:1}}" expands a null argument (instead of + no argument), when foo[0] is not empty has been fixed. +06-11-16 The discipline functions have been modified to allow each subscript + to act independently. Currently the discipline function will not + be called when called from a discipline function of the same variable. +06-11-14 A bug which could cause a core dump if a file descriptor for + an internal file was closed from with a subshell has been fixed. +06-10-30 The redirections <# pattern, and <## pattern have been added. + Both seek forward to the beginning of the next line that contains + the pattern. The <## form copies the skipped portion to standard + output. +06-10-26 On systems that support stream control transport, the virtual file + name /dev/sctp/host/port can now be used to establish connections. +06-10-26 The printf modifier # when used with d produces units in thousands + with a single letter suffix added. The modifier # when used with + the i specification provides units of 1024 with a two letter suffix. +06-10-24 The value of $! is now set to the process id of a job put + into the background with the bg command as required by POSIX. +06-10-23 A bug in which the value of $! was affected by a background + job started from a subshell has been fixed. +06-10-23 A bug in ${var:offset:len} in multibyte locales has been fixed. +06-10-15 The remaining math functions from C99 were added for any system + that supports them. +06-10-13 The klockwork.com software detected a few coding errors that + have been fixed. +06-10-12 A bug when skipping over `...` with ${x:=`...`} when x is set + has been fixed. +06-10-11 A bug in process floating constants produced by the %a format + of printf has been fixed. +06-10-06 A bug in which IFS was not being restored correctly in some + cases after a subshell has been fixed. +06-10-06 A bug in which pipefail was not detecting some failures in + pipelines with 3 or more states has been fixed. +06-10-03 A bug in the processing of >(...) with builtins which could + cause the builtin to hang has been fixed. +06-10-03 A bug in the for loop optimizer which causes >(...) process + substitution to be ignored has been fixed. +06-09-17 The -a option was added to typeset for indexed arrays. This + is only needed when using the ([subscript]=value ...) form. +06-09-06 The showme option was added. Each simple command not beginning + with a redirection and not occurring with in the while, until, if, + select condition can be preceded by a semi-colon which will + be ignored when showme is off. When showme is on, any command + preceded by a colon will be traced but not executed. +06-08-16 As a new feature, a leading ~(N) on a pattern has no effect + except when used for file expansion. In this case if not + matches are found, the pattern is replaced by nothing rather + than itself. +06-08-11 A bug in the expansion of ${.sh.match[i]:${#.shmatch[i]}} has + been fixed. +06-08-10 The read builtin options -n and -N have been modified to treat + the size as characters rather than bytes unless storing into a + binary (typeset -B) variable. +06-07-27 When the here document operator << is followed directly by a # + rather than a -, the first line of the here-document determines + how much whitespace is removed for each line. +06-07-26 A bug in the C-shell history (enabled with set -H) in which the + history event !$ was not processed has been fixed. +06-07-21 A bug on some systems in which assigning PATH on a command line + would not take effect has been fixed. +06-07-20 Add ksh93 and rksh93 as allowable names for ksh binaries. +06-07-20 Removed the SHOPT_OO compilation option which was only partially + implemented. +06-07-20 The ability to use egrep, grep, and fgrep expressions within + shell patterns has been documented. +06-07-17 A bug with arithmetic command expressions for locales in which + the comma is a thousands separator has been fixed. +06-07-13 The default HISTSIZE was increased from 128 to 512. +06-07-13 A multibyte problem with locales that use shift codes has been fixed. +06-06-23 A number of bug fixes for command, file, and variable completion + have been mode. +06-06-20 Floating point division by zero now yields the constant Inf or -Inf + and floating functions with invalid arguments yield NaN. +06-06-20 The floating point constants Inf and NaN can be used in arithmetic + expressions. +06-06-20 The functions isinf(), isnan(), tanhl() have been added for + arithmetic expressions. +06-06-13 Internal change to use ordering for variables instead of hashing + to speed up prefix matching. +06-06-13 A window between fork/exec in which a signal could get lost + and cause a program to hang has been eliminated +06-06-13 A bug in edit completion with quoted strings has been fixed. +06-06-07 The restricted options can now be enabled by set as well as on + the command line. Once set, it can not be disabled. +06-06-04 Modified built-in binding so that for systems for which /bin + and /usr/bin are the same, a builtin bound to /bin will get + selected when either /bin or /usr/bin is scanned. +06-06-04 Added literal-next character processing for emacs/gmacs mode. + This change is not compatible with earlier versions of ksh93 + and ksh88 when the stty lnext is control-v. The sequence + escape-control-v will display the shell version. +06-05-31 Modified emacs and vi mode so that entering a TAB after a partial + TAB completion, generates a listing of possible completions. + After the second TAB, a number followed by a TAB will perform + the completion with the corresponding item. +06-05-19 Modified arithmetic so that conversions to strings default to + the maximum number of precision digits. +06-05-16 Bug fixes for multibyte locales. +06-05-10 The =~ operator was added to [[...]] and [[ string ~= ERE ]] + is equivalent to [[ string == ~(E)ERE ]]. +06-04-25 A bug in the vi edit mode which could cause the shell to core dump + when switching from emacs mode. +06-04-17 A bug in which using LANG or LC_ in assignment lists with builtins + did not restore the localed correctly has been fixed. +06-04-04 A bug in which discipline functions could not be added to variables + whose names started with .sh has been fixed. +06-03-28 The -s option to typeset was added to modify -i to indicate short + integers. +06-03-28 A bug in which variables assignment lists before functions + defined with function name were not passed on the functions + invoked by this function has been fixed. +06-03-28 A bug in which name references defined within a function defined + with function name could not be used with compound variables has + been fixed. +06-03-27 A bug in which read <&p (print >&p) would cause the coprocess input + (output) pipe to close before reading from (after writing to) + it has been fixed. +06-02-28 A bug in which stopping a job created with the hist builtin command + would create a job that could not be restarted has been fixed. + +06-01-24 --- Release ksh93r --- +06-01-24 A bug in which running commands with standard output closed would + not work as expected has been fixed. +06-01-23 A bug in which print -u<n> could fail when file descriptor <n> was + open for writing has been fixed. +06-01-19 The ?: arithmetic operator fixed to work when the operation after + the colon was an assignment. +05-12-24 A bug which could lead to a core dump when elements of a compound + variable were array elements, i.e. foo=(bar=(1 2)), has been fixed. +05-12-13 An arithmetic bug in which x+=y+=z was not working has been fixed. +05-12-13 An arithmetic bug in which x||y was returning x when x was non-zero + rather than 1 has been fixed. +05-12-07 The aliases for integer and float have been changed to use attributes + -li and -lE to handle long long and long double types. +05-12-07 The histexpand (-H) option has been added which allows C-shell + style history expansions using the history character !. +05-12-07 The multiline option was added which changes that way the edit + modes handle lines longer than the window width. Instead of + horizontal scrolling, multiple lines on the screen are used. +05-12-05 The whence builtin now returns an absolute pathname when the + command is found in the current directory. +05-11-29 A bug which caused ksh -c '[[ ! ((' to core dump rather than + report a syntax error has been fixed. +05-11-29 A bug when reading fixed length records into typeset -b variables + which caused a zero byte to terminate the value has been fixed. +05-11-22 The ability to seek to an offset within a file has been added + with the new I/O redirection operators, <# and >#. Currently, + these redirection operators must be followed by ((expr)) + but in a future release, it should be able to used to seek forward + to the specified shell pattern. In addition $(n<#) expands to the + current byte offset for file descriptor n. +05-11-22 The .sh.match array variable is now set after each [[ ... ]] + pattern match. Previously it was only set for substring matches. +05-10-17 A bug in which the library path variable could be prefixed + with a directory when a .path file was not encountered in + the directory of the executable has been fixed. +05-09-15 A for/while loop optimizer bug in which $OPTIND was not + correctly expanded has been fixed. +05-09-05 A bug in which a history command that invoked a history + command could go into an infinite loop has been fixed. +05-08-31 In the case that IFS contains to adjacent new-lines so that + new-line is not treated as a space delimiter, only a single + new-line is deleted at the end of a command substitution. +05-08-19 When a tilde substitution expands to the / directory and is + followed by a /, it is replaced by the empty string. +05-08-16 A bug in which n<&m did not synchronize m has been fixed. +05-08-16 A bug in which process substitution ( <() and >() ) was not + working within for and while loops has been fixed. +05-07-24 A bug in which the pattern ~(E)(foo|bar) was treated as a syntax + error has been fixed. +05-07-24 A bug in completion with <n>=, where n was the one of the + previous selection choices has been fixed. +05-07-21 A bug with multibyte input when no edit mode was specified which + caused the input line to shift left/right has been fixed. +05-06-24 A race condition which could cause the exit status to get lost + on some fast systems has been fixed. +05-06-21 A bug in which nested patterns of the form {m,n}(pat) would cause + syntax errors has been fixed. +05-06-21 A bug in the macro expander has been fixed which could cause a + syntax error for an expansion of the form ${x-$(...)} when + x is set and the command substitution contained certain strings. +05-06-08 On systems for which echo does not do System V style \ expansions, + the -e option was added to enable these expansion. +05-06-08 A bug in which ${var op pattern} to not work when inside an + arithmetic expression has been fixed. +05-05-23 An extension to shell patterns that allows matching of nested + groups while skipping over quoted strings has been added. +05-05-18 A bug in which the line number for errors was not correct for + functions loaded from FPATH has been fixed. +05-04-18 A bug in which the exit status $? is not set when a trap triggered + by the [[...]] command is executed has been fixed. +05-04-08 Redirection operators can be directly preceded with {varname} + with no intervening space, where varname is a variable name which + allows the shell to select a file descriptor > 10 and store it + into varname. +05-04-08 SHOPT_CMDLIB_BLTIN=1 now includes <cmdlist.h> generated table. +05-04-07 [[ -o ?option ]] is true if "option" is a supported option. +05-04-05 A bug in handling file completion with spaces in the names + has been fixed. +05-03-25 The SIGWINCH signal is caught by default to keeps the LINES and + COLUMNS variables in sync with the actual window size. +05-03-25 Building ksh with SHOPT_REMOTE=1 causes ksh to set --rc if stdin is + a socket (presumably part of a remote shell invocation.) +05-03-25 Building ksh with SHOPT_SYSRC=1 causes interactive ksh to source + /etc/ksh.kshrc (if it exists) before sourcing the $ENV file. +05-03-25 {first..last[..incr][%fmt]} sequences added to brace expansions + when braceexpand is enabled. +05-03-03 A bug where a SIGCHLD interrupt could cause a fifo open to fail has + been fixed. +05-02-25 A bug in which a builtin command run in the background could + keep a file descriptor open which could cause a foreground + process to hang has been fixed. +05-02-24 A bug where builtin library commands (e.g., date and TZ) failed to + detect environment variable changes has been fixed. +05-02-22 The read builtin and word splitting are now consistent with respect + to IFS -- both treat IFS as field delimiters. +05-02-22 The read builtin no longer strips off trailing delimiters that + are not space characters when there are fewer variables than fields. +05-02-17 A builtin bug on systems where dlsym(libcmd) returns link-time + bindings has been fixed. +05-02-12 A bug in which the lib_init() function for .paths BUILTIN_LIB + libraries was not called has been fixed. +05-02-06 A bug on some systems in which moving the write end of a co-process + to a numbered file descriptor could cause it to close has been fixed. +05-02-06 A bug in the vi-edit mode in which the character under the cursor + was not deleted in some cases with the d% directive has been fixed. +05-02-06 A bug where external builtin stdout/stderr redirection corrupted + stdout has been fixed. +05-02-04 A bug where times formatting assumed CLK_TCK==60 has been fixed. + +05-01-11 --- Release ksh93q --- +05-01-11 A bug in the integral divide by zero check has been fixed. +05-01-11 The -l option has been added to read /etc/profile and + $HOME/.profile, if they exist, before the first command. +05-01-11 An argument parsing bug that caused `kill -s x -- n' to fail has + been fixed. +05-01-11 The .paths file, introduced in ksh93m, which can appear in + any directory in PATH, now allows a line of the form 'BUILTIN_LIB=.' + When a command is searched for this directory, and the full path + matches the path of the built-in version of the command (listed + by the 'builtin' command) then the built-in version of the command + is used. When ksh is built with SHOPT_CMDLIB_DIR=1 then all libcmd + functions become builtins with the '/opt/ast/bin/' directory prefix. +05-01-10 A bug in which a nameref to a compound name caused a core dump has + been fixed. +05-01-09 A bug in which some SIGCHLD interrupts (from child processes exiting) + caused a fatal print/echo error diagnostic has been fixed. +04-12-24 A bug in which some SIGCHLD interrupts (from child processes exiting) + corrupted the internal process/job list, sometimes causing the shell + to hang, has been fixed. +04-12-01 A bug in which typeset -Fn truncated less than n digits for large + numbers has been fixed. +04-11-25 A bug in which standard error could be closed after a redirection + to /dev/stderr has been fixed. +04-11-17 A bug in which an expansion of the form ${array[@]:3} could expand + to ${array[0]} when ${array[3]} was not set has been fixed. +04-10-22 The -E or -orc command line option reads ${ENV-$HOME/.kshrc} file. +04-10-22 `-o foo' equivalent to `+o nofoo', `-o nobar' equivalent to `+o bar'. + `--foo' equivalent to `-o foo', `--nofoo' equivalent to `+o foo' +04-10-05 The .paths file, introduced in ksh93m, which can appear in + any directory in PATH, now allows a line of the form + 'BUILTIN_LIB=libname'. When a command is searched for this directory, + the shared library named by libname will first be searched for a + built-in version of the command. +04-09-03 <<< here documents now handle quotes in the word token correctly. +04-08-08 The maximum size for read -n and and read -N was increased from + 4095 to 32M. +04-08-04 printf %q was modified so that if an no operand was supplied, no + no output would be generated rather than a quoted empty string. +04-08-01 The -n and -N options of the read builtin has been modified + when reading variables with the binary attribute so that the + data is stored directly rather than through assignment. +04-08-01 The shcomp command has been modified to process alias commands + under some conditions. +04-07-31 The .sh.match variable added in ksh93l, now works like other + indexed arrays. +04-07-08 A loop optimizer bug which occurs when typeset is used in + a for or while loop inside a function has been fixed. +04-06-24 The number of subexpressions in a pattern was increased to 64 + from the current number of 20. +04-06-17 The -t option to read was modified to allow seconds to be + specified as any arithmetic expression rather than just + an integral number of seconds, for example even -t 'sin(.5)' + is now valid. +04-06-16 Two small memory leak problems were fixed. +04-06-15 A bug in ${var/pattern/"string"} which occurred when string + contained pattern matching characters has been fixed. +04-05-08 printf $'%d\n' produced an erroneous error message and has + been fixed. +04-05-24 A bug in which an associative array without any elements could + cause a core dump when a script with an associative array with + the same name was declared in a script invoked by name has + been fixed. +04-05-11 A bug in which an exec statement could close the script that + is being processed in a script that is run by name causing + a failure has been fixed. +04-04-28 If the first character of assignment to an integer variable was 0, + the variable had been treated as unsigned. This behavior was + undocumented and has been removed. +04-04-05 A bug in which the positioning of standard input could be incorrect + after reading from standard input from a subshell has been fixed. +04-03-30 A bug in the for loop optimizer which in rare cases could cause + memory corruption has been fixed. +04-03-29 The preset alias source='command .' has been added. +04-03-29 A bug introduced in ksh93p on some systems in which invoked by + name with #! on the first line would not get the signals handling + initialized correctly has been fixed. +04-03-29 A bug introduced in ksh93p in which a HUP signal received by + a shell that is a session group leader was not passed down to + its children has been fixed. + +04-02-28 --- Release ksh93p --- +04-02-28 The ability to apply an append discipline to any variable has + been added. +04-02-14 A bug in which the exportall option (set -a) would cause incorrect + results for arrays has been fixed. +04-02-02 A bug in which an exported array would pass more than + the first element to a script invoked by name has been fixed. +04-02-02 A bug on some systems in which name=value pairs preceding a script + invoked by name was not getting passed to the script has been fixed. +04-01-20 A bug in which an unset discipline function could cause a core + dump on some systems has been fixed. +04-01-12 A bug in which a continue or break called outside a loop from + inside a function defined with name() syntax could affect + the invoking function has been fixed. +04-01-08 If a command name begins with ~, only filename completion will be + attempted rather than pathname completion using the builtin editors. +04-01-08 A bug in the vi edit mode in which the wrong repeat count on + multiple word replacements with the . directive has been fixed. +04-01-06 Backspace characters are now handled correctly in prompt strings. +04-01-06 The getopts builtin has been modified to accept numerical + arguments of size long long on systems that support this. +04-01-06 A bug in which unsetting all elements of an associative array + would cause it to be treated as an indexed array has been fixed. +03-12-15 A bug in which a quoted string ending with an unescaped $ would + delete the ending $ in certain cases has been fixed. +03-12-05 A bug in which the shell could hang when set -x tracing a command + when an invalid multibyte character is encountered has been fixed. +03-12-05 On some systems, if the KEYBD trap is set, then commands that use + the meta key were not processed until return was hit. This + has been fixed. +03-12-05 A problem which occurred when the login shell was not a group + leader that could cause it to fail has been fixed. +03-12-05 A problem in which a shell could core dump after receiving a signal + that should cause it to terminate while it was in the process + of acquiring more space has been fixed. +03-12-05 If ENV is not specified, the shell will default to $HOME/.kshrc + for interactive shells. +03-11-21 A bug introduced in ksh93o in which the DEBUG trap could get + disabled after it triggered has been fixed. +03-11-04 A bug in which using arithmetic prefix operators ++ or -- on a + non-lvalue could cause a core dump has been fixed. +03-11-04 A bug in which leading zeros were stripped from variable + expansions within arithmetic computation to avoid being treated + as octal constants when they should not have, has been fixed. +03-10-08 A bug introduced in ksh93o in which a large here document inside + a function definition could get corrupted has been fixed. +03-09-22 A bug in which the .get discipline function was not being + called when a string variable was implicitly referenced from + within a numerical expression has been fixed. +03-09-22 A bug in which a script without a leading #! could get executed + by /bin/sh rather than the current shell on some systems has + been fixed. +03-09-12 To improve conformance with ksh88, leading zeros will be ignored + when getting the numerical value of a string variable so that + they will not be treated as octal constants. +03-09-03 The builtin kill command now processes obsolete invocations + such as kill -1 -pid. +03-09-02 The restriction on modifying FPATH in a restricted shell (sh -r) + has been documented. +03-09-02 The restricted shell (sh -r) has been modified to disallow + executing command -p. +03-08-07 A bug in which the KEYBD trap was not being invoked when + characters with the 8th bit set has been fixed. +03-08-02 A parser bug introduced in ksh93o which caused the character + after () in a Posix function definition to be skipped + when reading from standard input has been fixed. +03-08-01 A bug in which "${foo#pattern}(x)" treated (x) as if it were + part of the pattern has been fixed. +03-08-01 The command -x option has been modified so that any trailing + arguments that do expand to a single word will be included + on each invocation, so that commands like command -x mv * dir + work as expected. + +03-07-20 --- Release ksh93o+ --- +03-07-20 A bug in which could cause memory corruption when a posix + function invoked another one has been fixed. +03-07-15 A bug in which a file descriptor>2 could be closed before + executing a script has been fixed. +03-07-15 A parsing error for <() and >() process substitutions inside + command substitution has been fixed. +03-07-15 A parsing error for patterns of the form {...}(...) when + used inside ${...} has been fixed. +03-07-15 An error in which expanding an indexed array inside a compound + variable could cause a core dump has been fixed. +03-07-15 A bug in which under on rare occassions a job completion interrupt + could cause to core dump has been fixed. +03-06-26 A bug in which process substitution embedded within command + substitution would generate a syntax error has been fixed. +03-96-23 A bug in which ${@:offset:len} could core dump when there + were no arguments has been fixed. +03-96-23 A bug in which ${X[@]:offset:len} could core dump when X + was unset has been fixed. +03-06-22 The -x option was added to the command builtin. If this + option is on, and the number of arguments would exceed ARG_MAX, + the command will be invoked multiple times with a subset of + the arguments. For example, with alias grep='command -x grep, + any number of arguments can be specified. +03-06-14 A bug in which could cause a core dump on some systems with + vi and emacs editors with the MULTIBYTE option has been fixed. +03-06-06 A bug in which the shell could core dump when a script was + run from its directory, and the script name a symlink to a file + beginning with .., has been fixed. +03-06-05 A bug in which the shell could core dump when a child process + that it is unaware of terminates while it is calling malloc() + has been fixed. +03-06-02 An option named globstar (set -G) has been added. When enabled, + during pathname expansion, any component that consists only of ** is + matches all files and any number of directory levels. +03-05-30 A bug in which the PATH search could give incorrect results when + run from directory foo and PATH contained .:foo:xxx has been fixed. +03-05-29 Some changes were made to the code that displays the prompt in edit + mode to better handle escape sequences in the prompt. +03-05-27 I added = to the list of characters that mark the beginning of + a word for edit completion so that filenames in assignments + can be completed. +03-05-20 A bug in which read -N could hang on some systems when reading + from a terminal or a pipe has been fixed. +03-05-19 A bug in which the output of uname from a command substitution + would go to the standard output of the invoking command when + uname was invoked with a non-standard option has been fixed. +03-05-19 A job control bug which would cause the shell to exit because + it hadn't take back the terminal has been fixed. The bug + could occur when running a function that contained a pipeline + whose last element was a function. +03-05-19 A job control timing bug introduced in ksh93o on some systems + which could cause a pipeline to hang if the first component + completed quickly has been fixed. +03-05-13 The read builtin has been modified so that the builtin editors + will not overwrite output from a previous incomplete line. +03-05-13 A bug in which the name of an identifier could have the string + .sh. prefixed to it after expanding a variable whose name begins + with .sh. has been fixed. +03-05-13 A bug in the expansion of $var for compound variables in which + some elements would not be output when the name was a prefix + of another name in the compound variable has been fixed. +03-05-08 The last item in the ksh93o release on 03-01-02 has been + altered slightly to preserve the leading 0's when the + preceding character is a digit. Thus, with typeset -LZ3 x=10, + $(( 1$x)) will be 1010 whereas $(( $x) will be 10. +03-04-25 A bug in which if x is a name reference, then nameref y=x.foo + did not follow x has been fixed. + +03-03-18 --- Release ksh93o --- +03-03-18 A -N unary operator was added to test and [[...]] which returns + true if the file exists and the file has been modified since it + was last read. +03-03-18 The TIMEFORMAT variable was added to control the format for + the time compound command. The formatting description is + described in the man page. +03-03-06 A -N n option was added to read which causes exactly n bytes + to be read unlike -n n which causes at most n bytes to be read. +03-03-03 Three new shell variables were added. The variable .sh.file + stores the full pathname of the file that the current command + was found in. The variable .sh.fun names the current function + that is running. The variable .sh.subshell contains the depth + of the current subshell or command substitution. +03-03-03 When the DEBUG trap is executed, the current command line after + expansions is placed in the variable .sh.command. The trap + is also now triggered before each iteration of a for, select, + and case command and before each assignment and redirection. +03-02-28 Function definitions are no longer stored in the history file so + that set -o nolog no longer has any meaning. +03-02-28 All function definitions can be displayed with typeset -f not + just those stored in the history file. In addition, typeset +f + displays the function name followed by a comment containing the + line number and the path name for the file that defined this function. +03-02-28 A bug in which the value of $LINENO was not correct when executing + command contained inside mult-line command substitutions has been + fixed. +03-02-19 Since some existing ksh88 scripts use the undocumented and + unintended ability to insert a : in front of the % and # parameter + expansion operators, ksh93 was modified to accept :% as equivalent + to % and :# as equivalent to # with ${name op word}. +03-02-14 A bug which could cause a core dump when reading from standard + error when standard error was a pty has been fixed. +03-02-14 The shell arithmetic was modified to use long double on systems + that provide this data type. +03-02-09 A bug in which a function located in the first directory in FPATH + would not be found when the last component of PATH was . and the + current directory was one of the directories in PATH has been fixed. +03-02-07 The trap and kill builtin commands now accept a leading SIG prefix + on the signal names as documented. +03-02-05 A bug in the expansion of ${var/$pattern}, when pattern contained + \[ has been fixed. +03-02-05 A bug in which .sh.match[n], n>0, was not being set for substring + matches with % and %% has been fixed. +03-01-15 A bug in which getopts did not work for numerical arguments specified + as n#var in the getopts string has been fixed. +03-01-09 A bug in which using ${.sh.match} multiple times could lead to + a memory exception has been fixed. +03-01-06 A bug in the expansion of ${var/pattern/$string} in the case that + $string contains \digit has been fixed. +03-01-02 A -P option was added for systems such as Solaris 8 that support + profile shell. +03-01-02 For backward compatibility with ksh88, arithmetic expansion + with ((...)) and let has been modified so that if x is a zero-filled + variable, $x will not be treated as an octal constant. + +02-12-05 --- Release ksh93n+ --- +02-11-30 A bug that can show up in evaluating arithmetic statements that + are in an autoloaded function when the function is autoload from + another function has been fixed. +02-11-30 An optimization bug in which an expansion of the form ${!name.@}, + which occurred inside a for or a while loop, when name is a name + reference, has been fixed. +02-11-18 A bug in which modifying array variables in a subshell could leave + side effects in the parent shell environment has been fixed. +02-11-18 A memory leak when unsetting an associative array has been fixed. +02-11-14 The code to display compound objects was rewritten to make + it easier for runtime extensions to reuse this code. +02-11-14 A change was made to allow runtime builtins to be notified when + a signal is received so that cleanup can be performed. +02-10-31 User applications can now trap the ALRM signal. Previously, + the ALRM signal was used internally and could not be used + by applications. +02-10-31 A bug in which signals received while reading from a coprocess + for which traps were set was not handled correctly has been fixed. +02-10-31 A bug in which a file opened with exec inside a subshell could + be closed before the subshell completed has been fixed. +02-10-21 A bug in which setting PATH or FPATH inside a function might not + take effect has been fixed. +02-10-21 A bug which could cause a core dump when a local SECONDS variable + is defined in a function has been fixed. +02-10-15 A bug in which the associate array name operator ${!array[@]} + could return the same name multiple times has been fixed. +02-10-15 A bug in which the zero'th element of an associative array was + not getting set when an assignment was made without a subscript + specified has been fixed. + +02-09-30 --- Release ksh93n --- +02-09-30 The maximum indexed array size was increased to 16Megs. +02-09-30 A bug which could cause a core dump when changing attributes + of associative array has been fixed. +02-09-30 A bug in which exporting an array variable would not export the + 0-th element has been fixed. +02-09-30 A bug in which an array assignment of the form a=($a ...) would unset + 'a' before the right hand side was evaluated has been fixed. +02-09-27 A bug in which the error message for ${var?message} when var was + null or unset did not contain the variable name var has been fixed. +02-09-27 A bug in which closing file descriptors 0 through 2 could + cause a subsequent here document to fail has been fixed. +02-09-14 A bug in whence which occurs when the specified name contained + a / has been fixed. +02-09-14 A bug in the parser for strings of the form name$((expr))=value + has been fixed. +02-09-14 A for loop optimization bug in which the number of elements in + an array was treated as an invariant has been fixed. +02-09-09 A bug in which redirection or closing of a file descriptor between + 3 and 9 could cause a subsequent here document to fail has been + fixed. +02-09-09 A bug in which a background job was not removed from the job list + when a subshell completed has been fixed, for example (prog&). +02-09-03 A bug in which an assignment of the form name=(integer x=3) + could be interpretted as an array assignment rather than a + compound variable assignment has been fixed. +02-08-19 A command completion bug which occurred on file systems that + are case insensitive has been fixed. +02-08-19 A bug which could lead to an exception on some systems (for + example FREEBSD) which occurred when setting PATH has been fixed. +02-08-11 A bug in arithmetic rounding in which a value input as a decimal + string would output as a rounded version of the string has + been fixed. +02-08-11 A bug in which the last character could be deleted from shell + traces and from whence when called from a multibyte locale + has been fixed. +02-08-01 A bug which could cause a core dump to occur when a shell script + is executed while a coprocess is running that has closed the + output pipe has been fixed. +02-08-01 A bug in which command completion in multibyte mode could + corrupt memory for long command lines has been fixed. + +02-06-17 --- Release ksh93n- --- +02-06-17 A bug in which user defined macros could cause a core dump in + with MULTIBYE mode has been fixed. +02-06-17 A bug in which printf format specifiers of the form %2$s were causing + a core dump has been fixed. +02-06-17 A bug in which setting stty to noecho mode did not prevent the + echoing of characters by ksh when emacs or viraw mode + was enabled has been fixed. +02-06-17 A bug in which background job completion could cause the sleep + builtin to terminate prematurely has been fixed. +02-06-17 A bug in which the shell could core dump if getopts was called + when the OPTIND variable contained a negative value has been fixed. +02-06-10 The edit mode prompt has been modified to handle escape sequences. +02-06-10 A bug which occurred for interactive shells in which the builtin + cat command was used in command substitution on a file whose + size was larger than PIPE_BUF has been fixed. +02-06-10 A bug in which the trap on ERR was not being processed when + set inside a function has been fixed. +02-06-07 A bug in which function definitions could cause the history count + to be decremented by one (and even become negative) has been fixed. +02-06-05 A bug in read in which share mode could be enabled has been fixed. +02-05-28 A bug which could occur when the last command of a script was + a case statement and the action selected ended in ;& instead of ;; + has been fixed. +02-05-23 A bug with unary + introduced in ksh93k has been fixed. +02-05-07 A bug in substitutions of the form ${var/pattern/string} in which + a backslash was inserted in the replacement string when it contained + a special pattern character has been fixed. +02-05-01 A bug in the emacs edit mode which occurred in versions compiled + for multibyte character sets which occurred when a repeated search + was requested after a long line had been returned for the previous + search has been fixed. +02-04-02 vi and emacs edit modes were modified so that tab completion is + disabled when invoked from the read built-in. + +02-03-26 --- Release ksh93m+ --- +02-03-26 A bug in which \ was not handled correctly when used in file + expansion has been fixed. +02-02-18 A bug in which lines beginning with a # were deleted from here + documents when the here-document delimiter was followed by + a comment has been fixed. +02-12-06 An optimization bug in which ${!x[@]) was treated as invariant in + a for loop has been fixed. +02-02-06 A bug in which the ERR trap is not cleared for a script invoked + by name from within a function has been fixed. +02-01-08 A bug in which a shell script executed from within a subshell + could cause this script to have an invalid pointer leading + to a memory fault has been fixed. +02-01-07 Added here documents of the form <<< word (as per zsh) which + is equivalent to << delim\nword\ndelim. +02-01-07 A bug in which the first word of a compound assignment, + x=(word ...), was treated as a reserved word has been fixed. +02-01-07 A bug in the handling of \ when noglob was enabled and a + substitution of the form ${word op pattern} occurred in the + same word has been fixed. +02-01-07 A compilation option, CMDLIB_BLTIN in the file OPTION, has + been added. When this options is set, all commands implemented + in libcmd become shell builtin commands by default. +02-01-07 A bug in which builtin foo, where foo is already a builtin + would result in the builtin foo getting removed has been fixed. +02-01-07 A bug which the shell executed a command found in the current + directory when PATH have no valid directories has been fixed. +01-11-28 The value of $? was not being set when called with exit. +01-11-28 If the last command was of the form (...) and a trap on EXIT or + ERR was set, and the command inside () modified the trap, then + the original trap wasn't executed. +01-11-26 The value for 0 is now preceded by the base number when + the base was not 10. +01-11-26 The default has compilation mode has been changes so that + viraw mode will always be on. + +01-10-31 --- Release ksh93m --- +01-10-31 A for loop optimizer bug for subshells contained withing for + loops has been fixed. +01-10-16 typeset without arguments no longer outputs variable names + that do not have any attributes that are set. +01-10-16 A bug introduced in ksh93l in which assignments specified with + the exec built-in were not being expanded properly has been + fixed. +01-10-11 An optimization bug in which ${!x) was treated as invariant in + a for loop has been fixed. +01-10-11 Unsigned integer variables in bases other than 10 are printed now + expand in that base with the base prefix. +01-10-10 A number of typos in the self generating man pages for shell + built-ins have been fixed. +01-10-04 The self generated man pages for hist and fc were not working + correctly and have been fixed. +01-10-03 Yet another optimizer bug in which shell patterns were + treated as invariants has been fixed. +01-09-27 Two bugs relating to multibyte history searches and to find + have been fixed. +01-09-27 A bug introduced in ksh93k in which the PATH searching was + not restored after running a command with an assignment list + has been fixed. +01-09-26 A bug in which a zero filled field was treated as octal when + converted to integer has been fixed. +01-09-26 Yet another bug in the optimization of for loops related to + recursive functions with break or continue statements has been fixed. +01-09-25 The exponentiation operator ** was added to the shell arithmetic + evaluation. It has higher precedence than * and is left + associative. +01-09-25 The code was modified to use the ast multibyte macros + and functions for handing multibyte locales. +01-09-25 The expansion ${parameter:offset:length} now handles negative + offsets which cause offsets to be measured from the end. +01-09-25 Some spelling errors in the documentation were corrected. +01-09-24 The /dev/tcp/host/port and /dev/udp/host/port now allow + the ports to be specified by service name. +01-09-24 The change staring with ksh93g in which the the appropriate + library path variable is prepended with a corresponding library + directory has been modified. With the new method, only the + library path defined in the file named .paths in the directory + where the executable is found will be modified. See the + man page for more details. +01-09-23 The .fpath file (see ksh93h) is no longer looked for in each + directory on the path to locate function directories. The + file named .paths is used instead. +01-09-23 A bug in which IFS was not being restored after being changed in + a subshell has been fixed. +01-09-16 With the vi and emacs edit modes, after a list of command + or functions is generated with = or M-= respectively, + any element from the list can be pasted on the command line + by preceding the = or M-= with a numeric parameter specifying + the position on the list. +01-09-16 A bug in ksh93l caused command completion not to find aliases + and functions. Command listing from the edit mode was presented + in reverse order. This has been fixed. +01-09-13 Another bug in the optimization of for loops related to subshells + when traps were set has been fixed. +01-09-07 A change in ksh93l caused brace expansion to stop working + and this has been fixed. +01-09-04 A bug introduced in ksh93k in which an arithmetic statement + within a function that used name references did not follow the + reference has been fixed. +01-09-04 A bug introduced in ksh93l in which export -p did not prefix + each export with the word export has been fixed. +01-08-29 A bug in multibyte input which occurred when a partial multibyte + character was received has been fixed. +01-08-29 A bug introduced in ksh93l which could cause a core dump + when an assignment list containing PATH is specified inside + command substitution has been fixed. +01-08-09 Another bug in the optimization of for loops in ksh93l caused + errors in recursive functions using local variables that + contained for loops has been fixed. +01-07-27 A bug in which IFS would be unset after a command substitution + inside a here document has been fixed. +01-07-26 To conform to the POSIX standard, if you invoked ksh name, + and name does not contain a /, it will first try to run + one in the current directory whether it is executable or not + before doing a path search for an executable script. Earlier + versions first checked for an executable script using the + PATH variable. +01-07-23 A bug in which unset -f invoked in a subshell could unset a + function defined in the parent has been fixed. +01-07-16 A bug in the optimization of for loops in ksh93l caused + name references to be treated as invariants has been fixed. +01-07-09 A bug in which a discipline function applied to a local variable + could cause a shell exception has been fixed. Discipline + functions can only be specified for global variables. + +01-06-18 --- Release ksh93l --- +01-06-18 A bug in assigning integers larger than can be represented as + long integers to floating point variables has been fixed. +01-06-18 A bug in the handling of unsigned integers (typeset -ui) has + been fixed. +01-06-04 The evaluation of the PS1 prompt no longer effects the value + of the $? variable. +01-06-01 A small memory leak from subshells has been fixed. +01-05-22 A bug in which attributes for variables that did not have + values would be lost after a subshell has been fixed. +01-05-22 The %R format has been added to convert a shell pattern into + an extended regular expression. +01-05-22 The escape sequences \e, \cX, \C[.collating-element.], and + \x{hex} have been added to ASCII-C strings and to printf format + strings. +01-05-20 Patterns of the form {n}(pattern) and {m,n}(pattern) are now + recognized. The first form matches exactly n of pattern whereas, + the second form matches from m to n instances of pattern. +01-05-20 The shell allows *-(pattern), +-(pattern), ?-(pattern), + {m,n}-(pattern}, and @-(pattern) to cause the minimal + match of pattern to be selected whenever possible rather + than the maximal (greedy) match. +01-05-20 The character class [:word:] has been added to patterns. + The word class is the union of [:alnum:] and the character _. +01-05-20 Inside (...) pattern groups, the \ character is now treated + specially even when in an enclosing character class. The + sequences, \w, \d, \s are equivalent to the character classes + word, digit, and space respectively. The sequences \W, \D, + and \S are their complement sets. +01-05-20 The shell now recognizes pattern groups of the form + ~(options:pattern) where options or :pattern can be omitted. + Options use the letters + and - to enable and disable options + respectively. The option letters g (greedy), i (ignore case) + are used to cause maximal matching and to cause case + insensitive matching respectively. If :pattern is also + specified, these options are only in effect while this + pattern is being processed. Otherwise, these options remain + in effect until the end of the pattern group that they are contained + in or until another ~(...) is encountered. These pattern groups + are not counted with respect to group numbering. +01-05-14 When edit completion, expansion, or listing occurs in the + middle of a quoted string, the leading quote is ignored when + performing the completion, expansion, or listing. +01-05-14 A small memory leak from subshells has been fixed. +01-05-10 A bug in which open files were not restored after a subshell + that had used exec to replace a file has been fixed. +01-05-10 Redirection to a null file name now generates an error message. +01-05-09 The shell now rejects some invalid parameter substitutions that + were previously processed in undefined ways. +01-05-09 A bug in which the output of select was not flushed before the + read when input did not come from the terminal has been fixed. +01-05-08 A bug in which job ids would not be freed for interactive shells + when subshells ran built-ins in the background has been fixed. +01-05-08 The FPATH variable now requires an explicit . to cause the + current directory to be treated as a function directory. +01-05-08 A bug in read -n when echo mode was disabled has been fixed. +01-05-07 A bug in which function definitions could be listed as part + of the history has been fixed. +01-04-30 This release uses a new and often much faster pattern matcher than + earlier releases. +01-04-30 An optimizer now eliminates invariant parameter expansions from + for while and until loops. +01-04-30 The variable .sh.match is set after each pattern match (# % or /) + in a variable substitution. The variable .sh.match is an + indexed array with element 0 being the complete match. + The array is only valid until the next subsequent pattern + match or until the value of the variable changes which ever + comes first. +01-04-30 A self generating man page has been added to shcomp. Also, + shcomp now stops compiling when it finds an exit or exec + command and copies the remainder so that it can be used + for standard input. +01-04-30 The shcomp command was modified so that it can work in an + EBCIDIC environment and that binary scripts are portable + across environments. +01-04-30 A bug in the handling of a trailing : in PATH has been fixed. +01-04-30 A bug in which the builtin version of a command would get invoked + even though the full pathname for the command was specified + has been fixed. +01-04-30 A bug in which read would loose the last character when + reading the last line of a file that did not contain a new-line + character has been fixed. +01-04-23 A bug on some systems in which in vi mode the end of file + character and end of line character could be swapped has + been fixed. +01-04-23 A bug on some systems in which invoking a shell script that + did not have execute permission could set the exit value to + 127 rather than 126 has been fixed. +01-04-20 A bug in which read -n from a pipe would block if fewer than + n characters was received has been fixed. +01-04-09 A bug in which invalid patterns, for example, ) by itself, + was not treated as a string has been fixed so that if i=')', + then [[ $i == $i ]] is true. +01-04-09 The shell arithmetic now interprets C character constants. +01-04-09 A bug in which a non-zero return from a function defined + with the function reserved word did not trigger the ERR + trap or exit with set -e has been fixed. +01-04-02 A bug on some systems, in which characters above 127 were + not displayed correctly in vi or emacs edit mode has been fixed. +01-04-02 A bug on some systems, introduced in the 'k' point release, in + which the erase character in viraw mode was moving the cursor + to the left without erasing the character has been fixed. +01-04-02 On some systems the wcwith() function was returning a wrong + value for characters and caused characters to be displayed + incorrectly from the shell edit modes. A work around for + this problem has been added. +01-03-26 A bug in which valid scripts could produce syntax errors + when run with locales that considered characters such as "'" + to be space characters has been fixed. +01-03-20 A bug in which an syntax error in an arithmetic expression + entered interactively could cause the shell to go into + an infinite loop outputting the error message has been fixed. +01-03-10 ksh93 accepts -l as a synonym for -L in test on systems for + which /bin/test -l tests for symbolic links. +01-03-10 A bug in parsing scripts in which { and } are used in place of + in and esac in case statements embedded in compound commands + has been fixed. Use of { and } for in and esac is obsolete. +01-03-06 A bug in which an argument of the form foo=bar was not + being passed correctly to a traced function whose name + was foo has been fixed. +01-03-02 Using $(trap -p name) did not print the name of the current + trap setting for trap name. +01-02-26 Exported floating point variables gave incorrect results + when passing them to ksh88. This has been fixed. +01-02-25 A race condition in which a coprocess which completed too quickly + would not allow subsequent coprocesses to start has been fixed. +01-02-25 The 'g' format specifier is now handled by printf. It had + inadvertently been omitted. +01-02-20 The + was not being displayed during an execution trace + with the += assignment operator. +01-02-19 The error message which occurs when the interpreter name + defined on the #! line does not exist is more informative. +01-02-19 A bug in which $0 would not be set correctly when a + script with #! was invoked by full pathname from the + directory of the script has been fixed. +01-02-19 A shell script did not always pick up tty mode changes + made by external commands such as stty which could + effect the behavior of read. +01-02-19 The -u, -g, and -k unary tests did not give the correct + results when used with negation and this has been fixed. + +01-02-05 --- Release ksh93k+ --- +01-02-05 The sequence \<newline> inside $'...' was not incrementing + the line count and this has been fixed. +01-02-05 Modified expansion of "${@-}" so that if no arguments are set + it results in null string rather than nothing. +01-02-02 memory leak problem with local variables in functions fixed. +01-01-25 allow arithmetic expressions with float%int and treat them + as ((int)float)%int rather than as an error. +01-01-19 read -n1 was not working and has been fixed. +01-01-17 ksh now handles the case in which a here document in command + substitution $() is terminated by the trailing ). Previously, + a new-line was needed at the end of the delimiter word. +01-01-02 A bug in which a KEYBD trap would cause a multi-line token + to be processed incorrectly has been fixed. +00-12-10 Arithmetic integer constants can now have L and U suffices. +00-12-10 A bug in the processing of arithmetic expressions with compound + variables when the -n option is on has been fixed. +00-12-08 A bug in M-f and M-b from emacs mode has been fixed. This + bug only occurs when ksh93 is compiled without MULTIBYTE enabled. +00-11-29 A bug in which jobs -p would yield 0 for background + jobs run in a script has been fixed. +00-11-21 A bug in integer arrays in which the number of elements is + incorrect when the ++ operator is applied to a non-existing + element has been fixed. For example, integer x; ((x[3]++)). +00-11-20 A timing bug in which the shell could reset the terminal + group to the wrong value in the case that the a new process + changes the terminal group during startup has been fixed. + +00-10-27 --- Release ksh93k --- +00-10-27 Using tab for completion now works only when applied + after a non-blank character at the end of the current line. + In other case a tab is inserted. +00-10-27 A bug in the emacs edit mode for ^X^E has been fixed. + The ^X^E sequence is supposed to invoke the full editor + on the current command. +00-10-18 A bug in which expansions of the form ${var//pattern/string} + did not work correctly when pattern was '/' or "/" has + been fixed. +00-10-18 The output format for indexed arrays in compound variables + has been modified so that it can be used as input. +00-10-18 Assignments with name references (typeset -n) will now + implicitly unreference an existing name reference. +00-10-17 A bug the += append operator when a single array element + is appended to a variable that is not an array has been fixed. +00-10-16 A bug in which the SIGCONT signal was being sent to + each process will kill -0 or kill -n 0 has been fixed. +00-10-12 The arithmetic evaluation portion has been rewritten to + perform a number of optimizations. +00-10-10 A bug in which name prefix matching ${!name.*} was not + checking name to see if it was a name reference has been fixed. +00-09-26 A bug in the multibyte version in which the width of for + non-printing characters was not correct has been fixed. +00-09-12 Made changes to get multibyte editing work on UWIN for windows +00-09-12 A bug in which multibyte characters would be displayed incorrectly + has been fixed. +00-08-08 Removed build dependency on iswprint() and iswalph(). +00-07-20 In some cases the read builtin would read more than a single + line from a pipe on standard input and therefore leave the seek + position in the wrong location. +00-07-05 If the directory / is on the path, a / will not be inserted + between the directory and the file name during path searching + to avoid searching // for systems that treat this specially. +00-06-26 A bug in which on rare occasions wait could return before all + jobs have completed has been fixed. +00-06-21 A bug in which backspace did not work correctly during the + R replace directive in vi-mode has been fixed. +00-06-12 Added variable name completion/expansion/listing to the set of + completions. Variable name completions begin with $ or "$ followed + by a letter. +00-05-09 --- Release ksh93j --- +00-05-09 Modified command substitution to avoid using /tmp files when + run on read-only file systems. +00-04-17 Modified printf to handle '%..Xc' and '%..Xs' options where X + is not an alpha character. Previous versions core dumped with this. +00-04-10 Changes to multibyte editing code were made to use standard + ISO C functions rather than methods devised before the standard. +00-04-09 Add %H options to printf to output strings with <"'&\t> properly + converted for use in HTML and XML documents. +00-04-07 Modified getopts builtin to handle \f...\f in usage string + by invoking specified function. +00-04-04 Added self generating man pages for bg, fc, fg, disown, jobs, + hist, let, ., and ulimit. +00-03-30 The append operator += has been added and can be used + for all assignments, strings, arrays, and compound variables. +00-03-30 Code was modified in several places to support automatic + generation of C locale dictionaries. +00-03-28 A bug in which the set and trap commands invoked with --name + type arguments would terminate the invoking script has + been fixed. +00-03-27 A bug in which the library path variable was not updated + correctly on some systems as described in the 'g' point + release has been fixed. +00-03-07 printf now returns a non-zero exit status when one of + its arguments cannot be converted to the given type. +00-03-05 The return value and error message for a command that + was found on the path but was not executable was set + incorrectly. +00-03-05 A prototype for ioctl() was removed from the vi edit mode. + +00-01-28 --- Release ksh93i --- +00-01-28 Most of the built-in commands and ksh itself are now + self documenting. Running command --man will produce + screen output. Running command --html produces the + man page in html format. +00-01-28 The getopts builtin can process command description + strings to produce man pages. +00-01-28 A bug in which a script could terminate when getopts + encountered an error when invoked inside a function + has been fixed. +00-01-28 When a symbolic link was specified as the name of + the script to invoke by name, the value of $0 was + set to the real file name rather than the link name + in some cases and this has been fixed. +00-01-28 A bug in which the precision given as an argument + to printf was not working has been fixed. + +99-03-31 --- Release ksh93h --- +99-03-31 The PATH search algorithm has been modified to look + for a file named .fpath in each bin directory and if + found, to search for functions in this directory if + it cannot find the command in that directory. +99-03-31 When performing pathname expansion, the shell checks + to see whether each directory it reads is case sensitive + or not, and performs the matching accordingly. +99-03-31 The %T format for printing formatted date/time. +99-03-31 The emacs and vi modes now handle arrow keys when + they use standard ANSI escape sequences. +99-03-31 The TAB key can be used for completion in emacs and viraw mode. +99-03-31 A bug in setting .sh.editchar during the KEYBD trap + for the MULTIBYTE option was fixed in release ksh93h. +99-03-31 A bug in shcomp for compilation of unary operators with [[...]] + has been fixed. +99-03-31 A bug in which the value of $? was changed when executing + a keyboard trap has been fixed. +99-03-31 The handling of SIGCHLD has been changed so that the + trap is not triggered while executing trap commands + to avoid recursive trap calls. +99-03-31 A bug in which a local variable in a function declared readonly + would generated an error when the function went out of + scope has been fixed. +99-03-31 A bug in which \<new_line> entered from the keyboard + with the KEYBD trap enabled has been fixed. +99-03-31 The error message for a misplaced ((, for example print ((3), + was often garbled and has been fixed. +99-03-31 A bug in the KEYBD trap in which escape sequences of the form + <ESC>[#~ were not being handled as a unit has been fixed. +99-03-31 A bug in which ksh would consider expressions like [[ (a) ]] + as syntax errors has been fixed. +99-03-31 A function defined as foo() without a function body + was not reported as a syntax error. +99-03-31 A bug in which ksh could run out of file descriptors when + a stream was repeatedly opened with exec and read from + has been fixed. + +98-04-30 --- Release ksh93g --- +98-04-30 The pipefail option has been added. With pipefail + enabled, a pipeline will not complete until all + commands are complete, and the return value will + be that of the last command to fail, or zero if + all complete successfully. +98-04-30 The name-value pair library uses the cdt library rather + than the hash library. This change should be transparent + to applications. +98-04-30 On the U/WIN version for Window 95 and Windows NT, + when a directory beginning with a letter followed by + a colon is given to cd, it is assumed to be an absolute + directory +98-04-30 When an executable is found on a given path, + the appropriate library path variable is prepended + with a corresponding library directory. +98-04-30 A bug in which a name reference could be created to + itself and later cause the shell to get into an infinite + loop has been fixed. +98-04-30 A bug in shcomp relating to compound variables was fixed. +98-04-30 A bug introduced in ksh93e in which leading 0's in -Z + fields caused the value to be treated as octal for arithmetic + evaluation has been fixed. +98-04-30 A bug when a name reference with a shorter name than + the variable it references was the subject of a compound + assignment has been fixed. +98-04-30 A bug which in which assignment to array variables in + a subshell could effect the parent shell has been + fixed. +98-04-30 read name?prompt was putting a 0 byte at the end of the + prompt on standard error. +98-04-30 A bug in [[ string1 > string2 ]] when ksh was run with -x + has been fixed. +98-04-30 A bug in which the escape character was not processed + correctly inside {...} when brace expansion is enabled + has been fixed, for example {\$foo}. +98-04-30 A bug in line continuation in here-documents has been + fixed. +98-04-30 The default base when not specified with typeset -i is + 10 in accordance with the documentation. Previously, + the value was determined by the first assignment. +98-04-30 A parsing bug in which a # preceded alphanumeric + characters inside a command substitution caused + a syntax error to be reported has been fixed. +98-04-30 A bug in which a decimal constant represented as 10#ddd + where ddd was more than five digits generated a syntax + error has been fixed. +98-04-30 A bug in here document expansion in which ${...} expansions + were split across buffer boundaries has been fixed. +98-04-30 The sh_fun() function now takes third argument which + is an argument list for the invoked discipline function + or built-in. +98-04-30 A callback function can be installed which will give + notification of file duplications and file closes. +98-04-30 When ksh is compiled on systems that do not use fork() + current option settings where not propagated to sub-shells. + +97-06-30 --- Release ksh93f --- +97-06-30 Hostnames in addition to host addresses can be given in + /dev/tcp/host/port virtual file names. +97-06-30 File name completion and expansion now quotes special + characters in file names from both emacs and vi edit modes. +97-06-30 An empty for list behave like a for list with null expansions. + It produces a warning message with sh -n. +97-06-30 The code has been modified to work with EBCDIC as well as ASCII. +97-06-30 A bug which would cause the secondary prompt to be + displayed when a user entered a literal carriage + return has been fixed. +97-06-30 A bug which caused ksh read -s name to core dump was + fixed. +97-06-30 A bug with the expansion of \} and \] inside double + quoted strings that also contained variable expansions + has been fixed +97-06-30 Changes in the ksh93e point release caused autoload + functions invoked from within command substitution + to fail. This has been fixed. +97-06-30 A bug in the processing of here-documents that could + prevent variable substitution to occur after $(...) command + substitution for long here documents has been fixed. +97-06-30 A bug caused by a race condition that could cause SIGTERM + to be ignored by a child process has been fixed. +97-06-30 A bug which prevented the startup of a coprocess immediately + after killing a running coprocess has been fixed. +97-06-30 ulimit foobar, where foobar is not an arithmetic + expression, now gives an error message as it did with ksh88 + instead of setting the file size limit to 0. +97-06-30 A bug which could cause an interactive shell to terminate when + the last process of a pipeline was a POSIX function was fixed. +97-06-30 A bug which could cause command substitution of a shell script + to core dump has been fixed. +97-06-30 A security hole was fixed in suid_exec. +97-06-30 Arithmetic functions such as pow() that take more than + one argument, did not work if arguments other than the + first contained parenthesized sub-expression. +97-06-30 The error message from a script containing an incomplete + arithmetic expression has been corrected. +97-06-30 A bug which caused a core dump on some machines when + the value of a name reference contained a positional + parameter and the name reference was not defined inside + a function has been fixed. +97-06-30 Arithmetic expressions now correctly handle hexadecimal + constants. +97-06-30 A bug in which integer variables could be expanded + with a leading 10# when declared with typeset -i + multiple times has been corrected. +97-06-30 A bug in which IFS wasn't correctly restored when + set within command substitution has been fixed. +97-06-30 The _ character is now considered as part of a word + with the M-f and M-b emacs directives as it was in ksh88. +97-06-30 A bug in brace pattern expansions that caused expressions + such as {foo\,bar,bam} to expand incorrectly have been fixed. + + +96-07-31 --- Release ksh93e --- +96-07-31 The math functions, atan2, hypot, fmod, and pow were added. +96-07-31 When a shared library is loaded, if the function lib_init() + is defined in the library, it is invoked the first time that + the library is loaded with builtin -f library. +96-07-31 The k-shell information abstraction database option, KIA, + has been revamped. +96-07-31 Empty command substitutions of the form $() now work. + whence -v foo now gives the correct result after calling + builtin -d foo. +96-07-31 A bug in right to left arithmetic assignment for which + the arithmetic expression (( y = x = 1.5 )) did not + yield 1 for y when x was declared typeset -i was fixed. +96-07-31 printf has been fixed to handle format containing \0 + and/or \0145 correctly. In addition, characters following + %b in the format string are no longer displayed when + the operand contains \c. +96-07-31 A bug in printf that could cause the %E format to + produce unnormalized results has been fixed. +96-07-31 A bug which causes some arithmetic expressions to be + incorrectly evaluated as integer expressions rather + that floating point has been fixed. +96-07-31 Functions defined inside a subshell no longer remain + defined when the subshell completes. +96-07-31 The error message from sh -c ';echo foo' has been + corrected. +96-07-31 The format for umask -S has been changed to agree + with the specification in the POSIX standard. +96-07-31 A bug that caused side effects in subscript evaluation + when tracing was enabled for subscripts using ++ or -- + has been fixed. +96-07-31 To conform to the Posix standard getopts has been changed + so that the option char is set to ? when it returns with + a non-zero exit status. +96-07-31 The handling of \} inside ${name...} has been fixed so + that the \ quotes the }. +96-07-31 A bug that caused the read builtin to resume execution + after processing a trap has been fixed. +96-07-31 [[ -s file ]] has been fixed so that if file is open + by ksh, it is flushed first. +96-07-31 In some cases attributes and sizes for non exported + variables weren't being reset before running a script. +96-07-31 The value of TMOUT was affected by changes make to + it in a subshell. +96-07-31 The jobs command did not reflect changes make by + sending the CONT signal to a command. +96-07-31 The error message for ksh -o unknown was incorrect. +96-07-31 Functions invoked as name=value name, did not use + values from the calling scope when evaluating value. +96-07-31 A bug in which the shell would reexecute previously + executed code when a shell script or coprocess was + run in the background has been fixed. +96-07-31 A bug in which an empty here-document would leave + a file descriptor open has been fixed. +96-07-31 A bug in which $(set -A array ...) would leave a + side effect has been fixed. +96-07-31 A discipline function for a global variable defined + within a function defined with the function keyword, + incorrectly created a local variable of the same name + and applied the discipline to it. + +95-08-28 --- Release ksh93d --- +95-08-28 The \ character was not handled correctly in replacement + patterns with ${x/pattern/replace}. +95-08-28 A bug with read in which the line did not end with + a new-line has been fixed. +95-08-28 A bug in file name generation which sometimes + appended a . for filenames that ended in / has + been fixed. +95-08-28 If a process is waited for after a status has + been returned by a previous wait, wait now + returns 127. +95-08-28 A bug with hist (fc) -e which prevented a command + to re-executed after it had been edited has been fixed. +95-08-28 A bug which prevented quoting from removing the meaning + of unary test operators has been fixed. +95-08-28 A bug with typeahead and KEYBOARD traps with the + MULTIBYTE option set has been fixed. +95-08-28 Builtin functions can take a third argument which is + a void*. +95-08-28 The nv_scan() function can restrict the scope of a walk + to the top scope. + +95-04-31 --- Release ksh93c --- +95-04-31 The expansion of "$@" was incorrect when $1 was the null + string. +95-04-31 A bug which could incorrectly report a syntax error in + a backquoted expression when a $ was preceded by \\ + has been fixed. +95-04-31 A bug which prevented the shell from exiting after + reporting an error when failing to open a script + has been fixed. +95-04-31 A bug that could lead to memory corruption when a + large here document that required parameter or command + substitution was expanded has been fixed. +95-04-31 A bug that could cause a core dump on some systems + after ksh detected an error when reading a function + has been fixed. +95-04-31 A bug which could cause a coprocess to hang when + reading from a process that has terminated has been fixed. +95-04-31 A bug which caused a script to terminate when set -e + was on and the first command of and && or || list + failed has been fixed. +95-04-31 A bug with here documents inside $(...) when the delimiter + word is an identifier has been fixed. +95-04-31 A bug which caused $0 to display the wrong value when + a script was invoked as an argument to the . command + and the eval command has been fixed. +95-04-31 A bug that could cause the built-in sleep to hang + has been fixed. +95-04-31 A bug introduces in 12/28/93b which caused the backslash + to be removed when it was followed by digit inside double + quotes in some instances has been fixed. +95-04-31 A bug which could cause a core dump if ksh was invoked with + standard input closed has been fixed. +95-04-31 A bug which could cause a core dump if typeset -A was + specified for an existing variable has been fixed. +95-04-31 Variables that were unset but had attributes such as readonly + and export were not listed with readonly, export and typeset. +95-04-31 Several problems with signals have been fixed. +95-04-31 A bug which prevented ulimit -t from working has been fixed. + Also, a bug in which failed ulimits could cause a core dump + has also been fixed. +95-04-31 A bug in expansion of the form ${name/#pattern/string} and + ${name/%pattern/string} has been fixed. +95-04-31 A bug which caused read -r on a line that contained only + blanks to get a non-null value has been fixed. +95-04-31 A bug introduced in the 'a' point release in which + ${x='\\'} expanded to \ when x was unset has been fixed. +95-04-31 A bug which prevented a trap on EXIT from being executed + when the last command in a script was a function invocation + has been fixed. +95-04-31 A bug which caused an interactive shell ignore input when + standard error was redirected to a file with exec, + and then restored with exec 2>&1 has been fixed. +95-04-31 An interactive shell turns on monitor mode even when + standard error has been redirected to a file. +95-04-31 A bug which could cause standard input to be incorrectly + positioned for the last command of a script has been fixed. +95-04-31 A bug in the edit modes which allowed walking back in + the history file for more than HISTSIZE commands has + beed fixed. +95-04-31 A bug which could cause a core dump if variable TMPDIR was + changed between two command substitutions has been fixed. +95-04-31. A bug which prevented a trap on EXIT from being cleared + has been fixed. +95-04-31 A bug fixed for the v directive in vi MULTIBYTE has been + fixed. +95-04-31 Code to for IFS handling of multibyte characters has + been added. +95-04-31 The displaying of multibyte strings in export, readonly, + typeset, and execution traces has been fixed. +95-04-31 Variables inside functions are now statically scoped. + The previous behavior was never documented. +95-04-31 Variables inside functions are now statically scoped. + The previous behavior was never documented. +95-04-31 A few changes have been made to the name-value library + that affect built-ins that use disciplines. The + changes allow disciplines to be shared by variables + and should make it possible to add new disciplines + without recompilation. +95-04-31 The name-value library interface has undergone significant + change for this revision. See the new nval.3 man page. + +94-12-31 --- Release ksh93b --- +94-12-31 Variables inside functions are now statically scoped. + The previous behavior was never documented. +94-12-31 If IFS contains two consecutive identical characters belonging + to the [:space:] class, then this character is treated as + a non-space delimiter so that each instance will delimit + a field. For example, IFS=$'\t\t' will cause two consecutive + tabs to delimit a null field. +94-12-31 The getopts command has a -a name option that specifies a + name that will be used for usage messages. +94-12-31 A bug which caused unset RANDOM to dump core has been + fixed. +94-12-31 A bug which prevented return for terminating a profile + or ENV file has been fixed. +94-12-31 A bug which prevented standard input from being + directed to /dev/null for background jobs when + monitor mode was turned off has been fixed. +94-12-31 Statements of the form typeset -options var[expr]=value + did not perform substitutions on expr as expected. +94-12-31 A bug which prevented the shell from sending a HUP + signal to some background jobs that were not disowned + has been fixed. +94-12-31 A bug which allowed a script to trap signals that are + ignored at the time that the shell was invoked by exec + has been fixed. +94-12-31 A bug which could cause a core dump when a discipline + function was unset within a discipline was fixed. +94-12-31 The typeset builtin now accepts a first argument of + + or - for compatibility with ksh88. +94-12-31 For compatibility with ksh88, the results of expansions + of command arguments will treat the extended character + match characters ()|& as ordinary characters. +94-12-31 A bug which caused read to fail on a file that was + open for read/write with <> when the first operation + was print or printf has been fixed. +94-12-31 When a job is suspended, it is put on the top of + the job list as required by the POSIX standard. +94-12-31 The value of OPTARG when an option that required + an argument but didn't have one was incorrect in the + case the the option string began with a :. +94-12-31 A bug which caused the terminal to get into a bad + state with some KEYBD traps in vi-mode has been fixed. +94-12-31 A bug which caused an invalid trap to cause a script + to terminate, rather than just return an error, has + been fixed. +94-12-31 Backreferencing sub-expressions in patterns and replacement + strings now works. +94-12-31 A bug in chmod which caused the -R option to fail has + been fixed. +94-12-31 More signal names have been added for Solaris + +94-06-30 --- Release ksh93a --- +94-06-30 An expansion bug which causes portions of a word after + a $((...)) expansion that contains a nested $var expansion + to be lost has been fixed. +94-06-30 A bug that caused a core dump when a script that did not + have PWD set and did a cd inside command substitution + has been fixed. +94-06-30 A bug which caused a core dump on some machines when + the LANG variable was assigned to has been fixed. +94-06-30 A bug which incorrectly handled set disciplines that + performed arithmetic evaluation when the discipline + was called from the arithmetic evaluator has been fixed. +94-06-30 A bug caused by an EXIT trap inside a function that + was executed in a subshell was fixed. +94-06-30 If foo is a function, and not a program, then command foo + now reports that foo isn't found rather than invoking foo. +94-06-30 The previous version incorrectly listed -A as an + invocation option. The -A option is only for set. +94-06-30 A bug was fixed which caused ksh to loop when execution trace + was enabled and the PS4 prompt required command substitution. +94-06-30 A bug which could cause the job control switch character + to be disabled when a script that enabled monitor mode + terminated was fixed. +94-06-30 A bug in the macro expansion global replacement operator //, + when the pattern began with a [ or +( has been fixed. +94-06-30 A bug which prevented ~ expansion from occurring when + it was terminated with a colon inside an assignment + has been fixed. +94-06-30 A bug in the dot command which prevented autoload functions + from working has been fixed. +94-06-30 A bug which caused a variable to be unset if the + its value were expanded inside a set discipline has + been fixed. +94-06-30 Whence -a now longer reports that a defined function + is undefined. +94-06-30 A bug on some systems in which $0 would be incorrect + in scripts invoked by name has been fixed. +94-06-30 Here documents with an empty body now work. +94-06-30 A bug which disabled argument passing and resetting + of options for a script invoked by name inside a + function has been fixed. +94-06-30 A bug in which an EXIT trap set the caller of a function + would be executed if a command called inside a function + was not found has been fixed. +94-06-30 A bug which allowed a script to trap signals that are + ignored at the time that the shell was invoked has + been fixed. +94-06-30 A bug which caused 2<&1- when applied to a shell built-in + to leave standard input closed has been fixed. +94-06-30 A bug which caused the shell to incorrectly parse + $() command substitutions with nested case statements + has been fixed. + diff --git a/usr/src/lib/libshell/common/RELEASE88 b/usr/src/lib/libshell/common/RELEASE88 new file mode 100644 index 0000000000..2466e39781 --- /dev/null +++ b/usr/src/lib/libshell/common/RELEASE88 @@ -0,0 +1,422 @@ +This is a list of changes that have been made since the 11/16/88 version +of ksh. + +1. New features in 12/28/93 + a. Associative arrays. The new version of ksh supports both + associate arrays and the older indexed arrays with the same + array syntax. A new -A option of typeset is used to declare + an array to be associative. As with indexed arrays, $name is + equivalent to ${name[0]}. The prefix operator ! was added + to the parameter expansion syntax to expand to the list of + indices. For example, ${!name[@]} expands to the list of array + indices for variable name. + + b. Several additions have been made to shell arithmetic: + 1. The shell now performs floating point arithmetic. The + typeset options -F and -E have been added for floating + point and scientific notation respectively. + 2. The prefix and postfix ++ and -- operators. + 3. The comma and ?: operators. + 4. The math library functions. + 5. An arithmetic for statement of the form + for ((expr1; expr2; expr3)) + do ... + done + 6. Integer arithmetic extended up to base 64. + + c. Some additions to the macro expansion syntax have been made + to specify substrings and sub-arrays: + 1. ${name:expr} expands to the substring of ${name} starting at + the character position defined by arithmetic expression expr. + 2. ${name:expr1:expr2} expands to the substring of ${name} starting + at expr1 and consisting of at most expr2 characters. + 3. ${name[@]:expr} expands to the values of ${name[@]} starting at + the element defined by arithmetic expression expr. + 4. ${name[@]:expr1:expr2} expands to at most expr2 values of + ${name} starting at expr1. + 5. ${@:expr} expands the positional parameters starting at expr. + 6. ${@:expr1:expr2} expands to at most expr2 positional parameters + starting at expr1. + 7. ${!name} expands to the name of the variable named by name. + It will expand to name unless name is reference variable. + 8. ${!name[sub]} expands to the name of the subscript of the + given variable. If sub is @ or * the list of subscripts + is generated. + 9. ${!prefix*} and ${!prefix@} expand to the list of variable + names beginning with prefix. + 10. The substring operators, # and % can be now be applied + with aggregates (@ or *) and are applied to each. + 11. ${name/pattern/string} expands to the value of name with + the first occurrence of pattern replaced by string. + With aggregates (@ or *) this operation is applied to each. + 12. ${name/#pattern/string} Same as above but the pattern + to be replaced must match at the beginning. + 13. ${name/%pattern/string} Same as above but the pattern + to be replaced must match at the end. + 14. ${name//pattern/string} expands to the value of name with + the each occurrence of pattern replaced by string. + With aggregates (@ or *) this operation is applied to each. + + d. The name space for variables has been extended. The character '.' + can be used at the beginning of a name, and to separate identifiers + within a name. However, to create a name of the form, foo.bar, + the variable foo must exist. The namespace starting with .sh + is reserved for shell implementation variables. Exported + variable cannot contain a '.'. + + e. Compound assignments. The assignment syntax, varname=value, + has been extended to allow assignments of the form + varname=(assignment_list). As elsewhere in the shell + spaces or tabs are optional around the parentheses, and + no space is permitted between the varname and the =. The + assignment_list can be one of the following: + 1. A list of words. In this case each word is expanded as + in a for list and the resulting items become elements + of the indexed array varname. + 2. A list of subscript assignments in the form + [subscript]=value. In this, these elements become + elements of the associative array varname. + 3. A list of assignments; simple or compound. In this + case, each assignment is made to varname.name, where + name is the name of the enclosed assignment. + 4. Assignments in the form of readonly or typeset + statements. In this case each assignment is made as + in 3 above, and the attributes are given to the + corresponding variable. + In case 3 and 4 above, the value of "$varname" after + the above assignment is (assignment_list), where the + assignment_list produced would reproduce all of the + variables under varname.*. + + f. Function names of the form variable.action (called discipline + functions) can be defined where variable is any valid variable + name and action is get, set, or unset. The function variable.get + is invoked each time the variable is referenced. The set + discipline is invoked each time the variable is assigned to. + The unset discipline is invoked when a variable is unset. + The new variables .sh.name, .sh.subscript, and .sh.value are + defined inside the function body. Other shell extensions + may have their own set of discipline functions. + + g. The compound command !, which negates the return value of the + following pipeline, has been added. + + h. On systems that support dynamic loading with dlopen(), it is + now possible to add built-in commands at runtime with the + a builtin command named builtin. + + i. The following builtins have been added: + 1. command name [ ... ] + 2. sleep [decimal-seconds] + 3. builtin [-ds] [-f file] [name...] + 4. getconf name [pathname] + 5. disown [job...] + + j. An addition format for literal strings, $'....' can + be used where ever literal strings are valid. The string + inside the single quotes will be converted using the ANSI-C + escape conventions. Additionally, the escape sequence \E + expands to the escape character (default \033) whenever ANSI-C + escape sequences are recognized. + + k. A typeset -n option has been added which causes the value of a + variable to be treated as a reference to another variable so that + variables can be indirectly named. For example, if $1 contains + the name of a variable, then typeset -n foo=$1 causes the variable + foo to be synonymous with the variable whose name is $1. A builtin + alias, nameref='typeset -n' has been added to aid mnemonics. + Reference names cannot contain a '.'. Whenever that portion of + a variable up to the first '.' matches a reference name, the + reference value is substituted. For example, with nameref foo=.top, + then ${foo.bar} is equivalent to ${.top.bar}. When used as the + index of a for or select loop, each assignment causes a + new name reference to occur. + + l. The KEYBD trap has been added which is triggered when a key + or escape sequence is typed while reading from the keyboard + in an edit mode. This, combined with some new variables + makes it possible to program your key bindings in ksh. + + m. New variables have been added: + 1. FIGNORE defines a set of file names to be ignored in each + directory when performing pathname expansion, replacing + the rule that requires that a leading . be matched explicitly. + 2. Variable sh.edchar contains the value of the keyboard character + that has been entered when processing a KEYBD trap. If the value + is changed as part of the trap action, then the new value replaces + the key or keys that caused the trap. + 3. Variable sh.edcol is set to the character position of the cursor + within the input buffer during a KEYBD trap. + 4. Variable sh.edmode is set to the escape character when in vi + insert mode. + 5. Variable sh.edtext is set to the contents of the input buffer + during a KEYBD trap. + 6. HISTEDIT is checked before FCEDIT. FCEDIT is obsolete. + 7. HISTCMD is the number of the current command in the history + file. + 8. Variable .sh.version is set to the version string for + this shell. + 9. Variable .sh.name is set to the name of the variable + that that was referenced or assigned to when executing a get + or set discipline function. + 10. Variable .sh.subscript is set to the subscript for the variable + that was referenced or assign to when executing a get or + set discipline function. + 11. Variable .sh.value is set to the new value for the variable + that was assigned to when executing the set discipline function. + + n. New invocation and set -o options have been added: + 1. set -o notify (or set -b) causes background completion messages + to be displayed as soon as the job completes. + 2. There is a compile time option named KIA which enables + creation of a relational database for commands, variables + and functions defined and referenced by a script. The + option -I <filename>, causes the database to be generated + in <filename>. The database format can be queried via + the cql command. + o. ksh93 can read and evaluate pre-compiled scripts generated by + a separate program called shcomp. + p. More work on internationalization has been added: + 1. The decimal point character is processed per locale + 2. A $ can be placed in front of each string to indicate + that the string needs translation but is otherwise ignored. + This means that if a message catalog of all $"..." strings + is generated, then a program such as print $"hello world" + could display "bonjour monde" in the french locale. + q. Backreferences have been added to pattern matching. The sequence + \d, where d is a digit from 1-9, matches the same string as + the d-th previous parenthesis group. Backreferences + can be used within patterns, and within replacement strings + with any of the ${name/...} operators. + +2. Changes made in 12/28/93 + a. The output format of many commands has changed as follows: + 1. System error messages are displayed whenever a failure + is caused by a system call. + 2. The exit status has changed in many cases: + a. USAGE messages cause an exit status of 2. + b. Commands not found cause exit - 127. + c. Command found, but not executable - 126. + d. Terminated because of signal - 256+sig + 3. The output of values from built-ins that contain special + characters are quoted in a manner that then can be re-input. + 4. The trace output puts quotes around the output so that it + can be reused as input. + 5. The output for trap is in a format that can be reinput the + the shell to restore the traps. + 6. kill -l lists the signal names without numbers as + required by the POSIX standard. + + b. The following changes have been made to shell functions: + 1. The semantics of functions declared with name() has changed + to conform with the IEEE-POSIX 1003.2 standard. In particular, + these functions are executed in a dot script environment rather + than a separated function environment so that there are no + local variables and no scoping for traps. + 2. Functions declared as function name, preserve the old ksh + semantics can be also used as the first argument to the dot (.) + command to have them executed in a dot script environment. + + c. The command search rules have changed as follows: + 1. Special built-ins (those with a dagger in front of them) are + executed first. + 2. Functions are executed next. + 3. Other built-ins that do not require an executable version + (for example cd and read) come next. + 4. If the command name contains a slash, the pathname corresponding + to the command name is executed. + 5. If name corresponds to a previously encountered pathname + on the PATH variable, the corresponding command is executed. + 6. If the command name does not contain a slash, then the PATH + variable is used to find an executable by that name. If + the directory that the command is found is also contained in + the FPATH variable, then the command treated as a function. + If the shell has a built-in version of the command corresponding + to this command, then the built-in version of this command + is executed. Otherwise, the shell remembers that pathname + corresponding to this command name and executes this pathname. + 7. If the name is not found on PATH, then the directories in + FPATH are searched. If found, then the command is executed + as a function. + + d. Built-in commands options now conform to the IEEE-POSIX 1003.2 + conventions with some additions. In particular, + name -? + will now print a Usage line for name, except for true, false, + colon, login, newgrp, echo, [, and command. + + e. Tilde expansion is now performed as part of the word expansions. + The effect of this is that if word begins with ~ in ${name op word}, + it will be expanded unless escaped. + + f. Pathname expansion is no longer performed on redirection words + unless the shell is interactive. + + g. Changes to shell and options: + 1. The -n option has been enhanced to produce more warning and + portability messages. + 2. The -C option is equivalent to -o noclobber. Files are + created with O_EXCL when -C is on. + + h. The following changes have been made to [[...]]: + 1. A string by itself is equivalent to -n string. + 2. -e has been added as equivalent to -a. + 3. == has been added as equivalent =. + 4. -a and = are now considered obsolete. + 5. Arithmetic comparisons are now considered obsolete. + + i. kill has been changed as follows: + 1. Signal names can be upper case or lower case. + 2. Numerical arguments to kill -l cause the given signal names to + be displayed. + 3. String arguments to kill -l cause the given signal numbers to + be displayed. + 4. Synopsis changed for getopts conformance. + + j. print has a -f format option which is equivalent to + the IEEE POSIX printf. Both print -f format, and + printf have the following extensions from IEEE POSIX: + 1. Floating point formats are supported. + 2. Size and precision specifications can be *. + 3. The %d option can take an argument after precision to + specify the base that the number will be displayed. + 4. A %q format can be used to output a string quoted so + that it can be re-input to the shell. + 5. A %P format can be used to output the shell pattern which + corresponds to the give extended regular expression. + 6. For numerical fields, the arguments can be arithmetic + expressions which will be evaluated. + 7. The %n format works as described in ANSI-C. + + k. The following changes have been made to fc: + 1. It has been renamed hist. fc is now a predefined alias. + 2. hist uses ${HISTEDIT:-$FCEDIT}. FCEDIT is obsolete. + 3. A new -s option is equivalent to the obsolete -e -. + 4. If the first argument refers to a command earlier than the + first accessible command, it now implies the first accessible + command, so that hist -l 1 lists all accessible history commands. + + l. The dot command (.) has changed as follows: + 1. The argument can be the name of a function declared as + function name. The function will execute without creating a + new scope. + 2. If there are arguments to the given script or function, + the positional parameters are restored to their original + value when . completes. + + m. The read built-in has been changed as follows: + 1. A -A option to read has been added to allow the fields to be + read into an indexed array. + 2. A -t n option has been added which causes read to + timeout after n seconds when reading from a slow device. + 3. A -d char option has been added which causes the read + to terminate at char rather than at new-line. + + n. The trap command has been changed as follows: + 1. Trap names can be either upper case or lower case. + 2. Trap -p cause only the specified trap values to be displayed. + 3. The value of trap in a subshell will be the value in the parent + shell until a call to trap which changes the trap settings has + been made. Thus, savetraps=$(trap) works as required by the + POSIX standard. + + o. The exec command has been extended as follows: + 1. The -c option clears the environment first. + 2. The -a name option sets argv[0] to name for the program. + + p. true and false are built-ins, not aliases to built-ins. + + q. test has been modified to conform to the IEEE-POSIX 1003.2 + standard when there are three or less arguments. + + r. umask -S option displays the mask in a symbolic format. + + s. wait now returns the correct exit status of any previous + background job that has not been waited for, not just + the most recent one. + + t. The whence built-in has an option -a which causes all + uses for the given command name to be reported. + + u. unalias has -a option to clear all the aliases. + + v. The times built-in command has been removed. The time + reserved word, without a command, gives time cumulative + time for the shell and its children. A built-in alias + for times should enable scripts using times to continue + to run. + + w. Command substitution and arithmetic substitution will now be + performed for PS1, ENV, and PS4 evaluation in addition to + parameter expansion. + + x. The SECONDS variable now displays elapsed time in floating + point seconds with 3 places after the decimal point by + default. + + y. The getopts built-in now handles the complete libast optget + functionality. If any errors have occurred with getopts + when it has reached the end of arguments, then the Usage + message will be generated from the option string and the + exit status from getopts will be 2 rather than 1. The + usage message will be stored in the OPTARG variable if + the option string contains a leading colon; otherwise + it will be printed on standard error automatically. + + z. THE ENV file is only processed for interactive shell + invocations. In addition, the -x attributes for + aliases and functions is ignored. + + aa. The built-in edit modes have been changed as follows: + 1. The pathname completion and pathname listing options + now perform command completion and command listing + when applied to a word in the command position. + 2. In emacs mode ^N as the first related command after + the prompt will move to the next command relative to the + last known history position. + 3. In emacs mode, successive kill and delete commands will + accumulate their data in the kill buffer, by appending or + prepending as appropriate. This mode will be reset by any + command not adding something to the kill buffer. + 4. The control-T of emacs mode has been changed to behave like + control-T in gnu-emacs. + bb. The TMOUT variable also sets a limit for select timeouts + and default timeouts for read. + + +4. The source code has undergone significant modification. + a. Much of the code has been rewritten, In many cases this has + resulted in significant performance improvement. + + b. The code is organized differently. See the README files + for more details. + + c. Most configuration parameters now get generated using + the FEATURE mechanism of nmake. Other options are set + in the OPTIONS file. + + c. The are several new compile time options. See the README + file for details. Some of the old ones have been removed. + + d. The install script is a Mamfile that is generated by + nmake and processed by a script that comes with the + distribution. + + e. There are far fewer global names. This should make it + must easier to add built-in commands without worrying + about conflicts. + + f. The code uses the sfio library which makes it possible + to mix with stdio. + + g. The code is written in ANSI C with full prototypes. + The code is based on the IEEE POSIX 1003.1 standard. + The code can be compiled with K&R C and with C++ by + using the ANSI cpp that comes with nmake or running + the code through the proto filter before pre-processing. + This happens automatically with our shipping system. + + h. There is a programming interface for capturing references + and assignment to shell variables. It is also possible + to intercept variable creation and supply the array processing + function for that variable. See nval.3 for a description. diff --git a/usr/src/lib/libshell/common/RELEASE93 b/usr/src/lib/libshell/common/RELEASE93 new file mode 100644 index 0000000000..e99c8781c6 --- /dev/null +++ b/usr/src/lib/libshell/common/RELEASE93 @@ -0,0 +1,455 @@ +This is a list of changes that have been made since the 12/28/93 version +of ksh. + +1. New features in 12/28/93b + a. If IFS contains two consecutive identical characters belonging + to the [:space:] class, then this character is treated as + a non-space delimiter so that each instance will delimit + a field. For example, IFS=$'\t\t' will cause two consecutive + tabs to delimit a null field. + b. The getopts command has a -a name option that specifies a + name that will be used for usage messages. + +2. New features in 12/28/93e + a. The math functions, atan2, hypot, fmod, and pow were added. + b. When a shared library is loaded, if the function lib_init() + is defined in the library, it is invoked the first time that + the library is loaded with builtin -f library. + +3. New features in 12/28/93f + a. Hostnames in addition to host addresses can be given in + /dev/tcp/host/port virtual file names. + b. File name completion and expansion now quotes special + characters in file names from both emacs and vi edit modes. + +4. New features in 12/28/93g + a. The pipefail option has been added. With pipefail + enabled, a pipeline will not complete until all + commands are complete, and the return value will + be that of the last command to fail, or zero if + all complete successfully. + b. When an executable is found on a given path, + the appropriate library path variable is prepended + with a corresponding library directory. +5. New features in 12/28/93h + a. The PATH search algorithm has been modified to look + for a file named .fpath in each bin directory and if + found, to search for functions in this directory if + it cannot find the command in that directory. + b. When performing pathname expansion, the shell checks + to see whether each directory it reads is case sensitive + or not, and performs the matching accordingly. + c. The %T format for printing formatted date/time. +6. New features in 12/28/93i + a. Most of the built-in commands and ksh itself are now + self documenting. Running command --man will produce + screen output. Running command --html produces the + man page in html format. + b. The getopts builtin can process command description + strings to produce man pages. + +7. Bugs fixed in 12/28/93a for default OPTIONS + a. An expansion bug which causes portions of a word after + a $((...)) expansion that contains a nested $var expansion + to be lost has been fixed. + b. A bug that caused a core dump when a script that did not + have PWD set and did a cd inside command substitution + has been fixed. + c. A bug which caused a core dump on some machines when + the LANG variable was assigned to has been fixed. + d. A bug which incorrectly handled set disciplines that + performed arithmetic evaluation when the discipline + was called from the arithmetic evaluator has been fixed. + e. A bug caused by an EXIT trap inside a function that + was executed in a subshell was fixed. + f. If foo is a function, and not a program, then command foo + now reports that foo isn't found rather than invoking foo. + g. The previous version incorrectly listed -A as an + invocation option. The -A option is only for set. + h. A bug was fixed which caused ksh to loop when execution trace + was enabled and the PS4 prompt required command substitution. + i. A bug which could cause the job control switch character + to be disabled when a script that enabled monitor mode + terminated was fixed. + j. A bug in the macro expansion global replacement operator //, + when the pattern began with a [ or +( has been fixed. + k. A bug which prevented ~ expansion from occurring when + it was terminated with a colon inside an assignment + has been fixed. + l. A bug in the dot command which prevented autoload functions + from working has been fixed. + m. A bug which caused a variable to be unset if the + its value were expanded inside a set discipline has + been fixed. + n. Whence -a now longer reports that a defined function + is undefined. + o. A bug on some systems in which $0 would be incorrect + in scripts invoked by name has been fixed. + p. Here documents with an empty body now work. + 1. A bug which disabled argument passing and resetting + of options for a script invoked by name inside a + function has been fixed. + r. A bug in which an EXIT trap set the caller of a function + would be executed if a command called inside a function + was not found has been fixed. + s. A bug which allowed a script to trap signals that are + ignored at the time that the shell was invoked has + been fixed. + t. A bug which caused 2<&1- when applied to a shell built-in + to leave standard input closed has been fixed. + u. A bug which caused the shell to incorrectly parse + $() command substitutions with nested case statements + has been fixed. + +8. Bugs fixed in 12/28/93b for default OPTIONS + a. A bug which caused unset RANDOM to dump core has been + fixed. + b. A bug which prevented return for terminating a profile + or ENV file has been fixed. + c. A bug which prevented standard input from being + directed to /dev/null for background jobs when + monitor mode was turned off has been fixed. + d. Statements of the form typeset -options var[expr]=value + did not perform substitutions on expr as expected. + e. A bug which prevented the shell from sending a HUP + signal to some background jobs that were not disowned + has been fixed. + f. A bug which allowed a script to trap signals that are + ignored at the time that the shell was invoked by exec + has been fixed. + g. A bug which could cause a core dump when a discipline + function was unset within a discipline was fixed. + h. The typeset builtin now accepts a first argument of + + or - for compatibility with ksh88. + i. For compatibility with ksh88, the results of expansions + of command arguments will treat the extended character + match characters ()|& as ordinary characters. + j. A bug which caused read to fail on a file that was + open for read/write with <> when the first operation + was print or printf has been fixed. + k. When a job is suspended, it is put on the top of + the job list as required by the POSIX standard. + l. The value of OPTARG when an option that required + an argument but didn't have one was incorrect in the + case the the option string began with a :. + m. A bug which caused the terminal to get into a bad + state with some KEYBD traps in vi-mode has been fixed. + n. A bug which caused an invalid trap to cause a script + to terminate, rather than just return an error, has + been fixed. + o. Backreferencing sub-expressions in patterns and replacement + strings now works. + p. A bug in chmod which caused the -R option to fail has + been fixed. + +9. Bugs fixed in 12/28/93c for default OPTIONS + a. The expansion of "$@" was incorrect when $1 was the null + string. + b. A bug which could incorrectly report a syntax error in + a backquoted expression when a $ was preceded by \\ + has been fixed. + c. A bug which prevented the shell from exiting after + reporting an error when failing to open a script + has been fixed. + d. A bug that could lead to memory corruption when a + large here document that required parameter or command + substitution was expanded has been fixed. + e. A bug that could cause a core dump on some systems + after ksh detected an error when reading a function + has been fixed. + f. A bug which could cause a coprocess to hang when + reading from a process that has terminated has been fixed. + g. A bug which caused a script to terminate when set -e + was on and the first command of and && or || list + failed has been fixed. + h. A bug with here documents inside $(...) when the delimiter + word is an identifier has been fixed. + i. A bug which caused $0 to display the wrong value when + a script was invoked as an argument to the . command + and the eval command has been fixed. + j. A bug that could cause the built-in sleep to hang + has been fixed. + k. A bug introduces in 12/28/93b which caused the backslash + to be removed when it was followed by digit inside double + quotes in some instances has been fixed. + l. A bug which could cause a core dump if ksh was invoked with + standard input closed has been fixed. + m. A bug which could cause a core dump if typeset -A was + specified for an existing variable has been fixed. + n. Variables that were unset but had attributes such as readonly + and export were not listed with readonly, export and typeset. + o. Several problems with signals have been fixed. + p. A bug which prevented ulimit -t from working has been fixed. + Also, a bug in which failed ulimits could cause a core dump + has also been fixed. + q. A bug in expansion of the form ${name/#pattern/string} and + ${name/%pattern/string} has been fixed. + r. A bug which caused read -r on a line that contained only + blanks to get a non-null value has been fixed. + s. A bug introduced in the 'a' point release in which + ${x='\\'} expanded to \ when x was unset has been fixed. + t. A bug which prevented a trap on EXIT from being executed + when the last command in a script was a function invocation + has been fixed. + u. A bug which caused an interactive shell ignore input when + standard error was redirected to a file with exec, + and then restored with exec 2>&1 has been fixed. + v. An interactive shell turns on monitor mode even when + standard error has been redirected to a file. + w. A bug which could cause standard input to be incorrectly + positioned for the last command of a script has been fixed. + y. A bug in the edit modes which allowed walking back in + the history file for more than HISTSIZE commands has + beed fixed. + z. A bug which could cause a core dump if variable TMPDIR was + changed between two command substitutions has been fixed. + aa. A bug which prevented a trap on EXIT from being cleared + has been fixed. + +10. Bugs fixed in 12/28/93d for default OPTIONS + a. The \ character was not handled correctly in replacement + patterns with ${x/pattern/replace}. + b. A bug with read in which the line did not end with + a new-line has been fixed. + c. A bug in file name generation which sometimes + appended a . for filenames that ended in / has + been fixed. + d. If a process is waited for after a status has + been returned by a previous wait, wait now + returns 127. + e. A bug with hist (fc) -e which prevented a command + to re-executed after it had been edited has been fixed. + f. A bug which prevented quoting from removing the meaning + of unary test operators has been fixed. + +11. Bugs fixed in 12/28/93e for default OPTIONS + a. Empty command substitutions of the form $() now work. + b. whence -v foo now gives the correct result after calling + builtin -d foo. + c. A bug in right to left arithmetic assignment for which + the arithmetic expression (( y = x = 1.5 )) did not + yield 1 for y when x was declared typeset -i was fixed. + d. printf has been fixed to handle format containing \0 + and/or \0145 correctly. In addition, characters following + %b in the format string are no longer displayed when + the operand contains \c. + e. A bug in printf that could cause the %E format to + produce unnormalized results has been fixed. + f. A bug which causes some arithmetic expressions to be + incorrectly evaluated as integer expressions rather + that floating point has been fixed. + g. Functions defined inside a subshell no longer remain + defined when the subshell completes. + h. The error message from sh -c ';echo foo' has been + corrected. + i. The format for umask -S has been changed to agree + with the specification in the POSIX standard. + j. A bug that caused side effects in subscript evaluation + when tracing was enabled for subscripts using ++ or -- + has been fixed. + k. To conform to the Posix standard getopts has been changed + so that the option char is set to ? when it returns with + a non-zero exit status. + l. The handling of \} inside ${name...} has been fixed so + that the \ quotes the }. + m. A bug that caused the read builtin to resume execution + after processing a trap has been fixed. + n. [[ -s file ]] has been fixed so that if file is open + by ksh, it is flushed first. + o. In some cases attributes and sizes for non exported + variables weren't being reset before running a script. + p. The value of TMOUT was affected by changes make to + it in a subshell. + q. The jobs command did not reflect changes make by + sending the CONT signal to a command. + r. The error message for ksh -o unknown was incorrect. + s. Functions invoked as name=value name, did not use + values from the calling scope when evaluating value. + t. A bug in which the shell would reexecute previously + executed code when a shell script or coprocess was + run in the background has been fixed. + u. A bug in which an empty here-document would leave + a file descriptor open has been fixed. + v. A bug in which $(set -A array ...) would leave a + side effect has been fixed. + w. A discipline function for a global variable defined + within a function defined with the function keyword, + incorrectly created a local variable of the same name + and applied the discipline to it. + +12. Bugs fixed in 12/28/93f for default OPTIONS + a. A bug which would cause the secondary prompt to be + displayed when a user entered a literal carriage + return has been fixed. + b. I bug which caused ksh read -s name to core dump was + fixed. + c. I bug with the expansion of \} and \] inside double + quoted strings that also contained variable expansions + has been fixed + d. Changes in the 'e' point release caused autoload + functions invoked from within command substitution + to fail. This has been fixed. + e. A bug in the processing of here-documents that could + prevent variable substitution to occur after $(...) command + substitution for long here documents has been fixed. + f. A bug caused by a race condition that could cause SIGTERM + to be ignored by a child process has been fixed. + g. A bug which prevented the startup of a coprocess immediately + after killing a running coprocess has been fixed. + h. ulimit foobar, where foobar is not an arithmetic + expression, now gives an error message as it did with ksh88 + instead of setting the file size limit to 0. + i. A bug which could cause an interactive shell to terminate when + the last process of a pipeline was a POSIX function was fixed. + j. A bug which could cause command substitution of a shell script + to core dump has been fixed. + k. A security hole was fixed in suid_exec. + l. Arithmetic functions such as pow() that take more than + one argument, did not work if arguments other than the + first contained parenthesized sub-expression. + m. The error message from a script containing an incomplete + arithmetic expression has been corrected. + n. A bug which caused a core dump on some machines when + the value of a name reference contained a positional + parameter and the name reference was not defined inside + a function has been fixed. + o. Arithmetic expressions now correctly handle hexidecimal + constants. + p. A bug in which integer variables could be expanded + with a leading 10# when declared with typeset -i + multiple times has been corrected. + q. A bug in which IFS wasn't correctly restored when + set within command substitution has been fixed. + r. The _ character is now considered as part of a word + with the M-f and M-b emacs directives as it was in ksh88. + +13. Bugs fixed in 12/28/93g for default OPTIONS + a. A bug in which a name reference could be created to + itself and later cause the shell to get into an infinite + loop has been fixed. + b. A bug in shcomp relating to compound variables was fixed. + c. A bug introduced in 'e' in which leading 0's in -Z + fields caused the value to be treated as octal for arithmetic + evaluation has been fixed. + d. A bug when a name reference with a shorter name than + the variable it references was the subject of a compound + assignment has been fixed. + e. A bug which in which assignment to array variables in + a subshell could effect the parent shell has been + fixed. + f. read name?prompt was putting a 0 byte at the end of the + prompt on standard error. + g. A bug in [[ string1 > string2 ]] when ksh was run with -x + has been fixed. + k. A bug in which the escape character was not processed + correctly inside {...} when brace expansion is enabled + has been fixed, for example {\$foo}. + l. A bug in line continuation in here-documents has been + fixed. + m. The default base when not specified with typeset -i is + 10 in accordance with the documentation. Previously, + the value was determined by the first assignment. + n. A parsing bug in which a # preceded alphanumeric + characters inside a command substitution caused + a syntax error to be reported has been fixed. + o. A bug in which a decimal constant represented as 10#ddd + where ddd was more than five digits generated a syntax + error has been fixed. + p. A bug in here document expansion in which ${...} expansions + were split across buffer boundaries has been fixed. + +14. Bugs fixed in 12/28/93h for default OPTIONS + a. I bug in shcomp for compilation of unary operators with [[...]] + has been fixed. + b. A bug in which the value of $? was changed when executing + a keyboard trap has been fixed. + c. The handling of SIGCHLD has been changed so that the + trap is not triggered while executing trap commands + to avoid recursive trap calls. + d. I bug in which a local variable in a function declared readonly + would generated an error when the function went out of + scope has been fixed. + e. I bug in which \<new_line> entered from the keyboard + with the KEYBD trap enabled has been fixed. + f. The error message for a misplaced ((, for example print ((3), + was often garbled and has been fixed. + g. I bug in the KEYBD trap in which escape sequences of the form + <ESC>[#~ were not being handled as a unit has been fixed. + h. A bug in which ksh would consider expressions like [[ (a) ]] + as syntax errors has been fixed. + i. A function defined as foo() without a function body + was not reported as a syntax error. + j. A bug in which ksh could run out of file descriptors when + a stream was repeatedly opened with exec and read from + has been fixed. + k. A bug introduced when fixing item n from the 'g' point + release has been fixed. + +15. Bugs fixed in 12/28/93i for default OPTIONS + a. A bug in which a script could terminate when getopts + encountered an error when invoked inside a function + has been fixed. + b. When a symbolic link was specified as the name of + the script to invoke by name, the value of $0 was + set to the real file name rather than the link name + in some cases and this has been fixed. + +16. Bug fixes for specific non-default option combinations. + a. More signal names have been added for Solaris + b. A bug fixed for the v directive in vi MULTIBYTE has been + fixed. + c. Code to for IFS handling of multibyte characters has + been added. + d. The displaying of multibyte strings in export, readonly, + typeset, and execution traces has been fixed. + e. A bug with type ahead and KEYBOARD traps with the + MULTIBYTE option set has been fixed. + f. The k-shell information abstraction database option, KIA, + has been revamped for the 'e' point release. + g. A bug in brace pattern expansions that caused expressions + such as {foo\,bar,bam} to expand incorrectly have been fixed. + h. On the U/WIN version for Window 95 and Windows NT, + when a directory beginning with a letter followed by + a colon is given to cd, it is assumed to be an absolute + directory. + i. There was a bug in the compile option that does not + use fork() in which the current option settings where + not propagated to sub-shells. + j. A bug in setting .sh.editchar during the KEYBD trap + for the MULTIBYTE option was fixed in release 'h'. + k. A bug in which the precision given as an argument + to printf was not working has been fixed. + +17. Other changes to 12/28/93[abcdefghi] + a. A couple of minor changes to make adding built-ins easier. + b. Variables inside functions are now statically scoped. + The previous behavior was never documented. + c. A few changes have been made to the name-value library + that affect built-ins that use disciplines. The + changes allow disciplines to be shared by variables + and should make it possible to add new disciplines + without recompilation. + d. The name-value library interface has undergone significant + change for this revision. See the new nval.3 man page. + e. Builtin functions can take a third argument which is + a void*. + f. The nv_scan() function can restrict the scope of a walk + to the top scope. Starting in 'f', nv_scan() has an + additional pointer argument that is passed to each invoked + function. + g. Starting with release 'f', an empty for list behave like + a for list with null expansions. It produces a warning + message with sh -n. + h. Starting with release 'f' the code has been modified to + work with EBCDIC as well as ASCII. + i. Starting with the release 'g', the name-value pair library + uses the cdt library rather than the hash library. + j. The sh_fun() function now takes third argument which + is an argument list for the invoked discipline function + or built-in. + k. A callback function can be installed which will give + notification of file duplications and file closes. + +18. Incompatibilities with 12/28/93 version. + None intentional. + diff --git a/usr/src/lib/libshell/common/bltins/alarm.c b/usr/src/lib/libshell/common/bltins/alarm.c new file mode 100644 index 0000000000..6f4aa2f644 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/alarm.c @@ -0,0 +1,276 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * alarm [-r] [varname [+]when] + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <error.h> +#include <stak.h> +#include "builtins.h" +#include "FEATURE/time" + +#define R_FLAG 1 +#define L_FLAG 2 + +struct tevent +{ + Namfun_t fun; + Namval_t *node; + Namval_t *action; + struct tevent *next; + long milli; + int flags; + void *timeout; + Shell_t *sh; +}; + +static const char ALARM[] = "alarm"; + +static void trap_timeout(void*); + +/* + * insert timeout item on current given list in sorted order + */ +static void *time_add(struct tevent *item, void *list) +{ + register struct tevent *tp = (struct tevent*)list; + if(!tp || item->milli < tp->milli) + { + item->next = tp; + list = (void*)item; + } + else + { + while(tp->next && item->milli > tp->next->milli) + tp = tp->next; + item->next = tp->next; + tp->next = item; + } + tp = item; + tp->timeout = (void*)sh_timeradd(tp->milli,tp->flags&R_FLAG,trap_timeout,(void*)tp); + return(list); +} + +/* + * delete timeout item from current given list, delete timer + */ +static void *time_delete(register struct tevent *item, void *list) +{ + register struct tevent *tp = (struct tevent*)list; + if(item==tp) + list = (void*)tp->next; + else + { + while(tp && tp->next != item) + tp = tp->next; + if(tp) + tp->next = item->next; + } + if(item->timeout) + timerdel((void*)item->timeout); + return(list); +} + +static void print_alarms(void *list) +{ + register struct tevent *tp = (struct tevent*)list; + while(tp) + { + if(tp->timeout) + { + register char *name = nv_name(tp->node); + if(tp->flags&R_FLAG) + { + double d = tp->milli; + sfprintf(sfstdout,e_alrm1,name,d/1000.); + } + else + sfprintf(sfstdout,e_alrm2,name,nv_getnum(tp->node)); + } + tp = tp->next; + } +} + +static void trap_timeout(void* handle) +{ + register struct tevent *tp = (struct tevent*)handle; + tp->sh->trapnote |= SH_SIGALRM; + if(!(tp->flags&R_FLAG)) + tp->timeout = 0; + tp->flags |= L_FLAG; + tp->sh->sigflag[SIGALRM] |= SH_SIGALRM; + if(sh_isstate(SH_TTYWAIT)) + sh_timetraps(); +} + +void sh_timetraps(void) +{ + register struct tevent *tp, *tpnext; + register struct tevent *tptop; + while(1) + { + sh.sigflag[SIGALRM] &= ~SH_SIGALRM; + tptop= (struct tevent*)sh.st.timetrap; + for(tp=tptop;tp;tp=tpnext) + { + tpnext = tp->next; + if(tp->flags&L_FLAG) + { + tp->flags &= ~L_FLAG; + if(tp->action) + sh_fun(tp->action,tp->node,(char**)0); + tp->flags &= ~L_FLAG; + if(!tp->flags) + { + nv_unset(tp->node); + nv_close(tp->node); + } + } + } + if(!(sh.sigflag[SIGALRM]&SH_SIGALRM)) + break; + } +} + + +/* + * This trap function catches "alarm" actions only + */ +static char *setdisc(Namval_t *np, const char *event, Namval_t* action, Namfun_t + *fp) +{ + register struct tevent *tp = (struct tevent*)fp; + if(!event) + return(action?"":(char*)ALARM); + if(strcmp(event,ALARM)!=0) + { + /* try the next level */ + return(nv_setdisc(np, event, action, fp)); + } + if(action==np) + action = tp->action; + else + tp->action = action; + return(action?(char*)action:""); +} + +/* + * catch assignments and set alarm traps + */ +static void putval(Namval_t* np, const char* val, int flag, Namfun_t* fp) +{ + register struct tevent *tp; + register double d; + if(val) + { + double now; +#ifdef timeofday + struct timeval tmp; + timeofday(&tmp); + now = tmp.tv_sec + 1.e-6*tmp.tv_usec; +#else + now = (double)time(NIL(time_t*)); +#endif /* timeofday */ + nv_putv(np,val,flag,fp); + d = nv_getnum(np); + tp = (struct tevent*)fp; + if(*val=='+') + { + double x = d + now; + nv_putv(np,(char*)&x,NV_INTEGER,fp); + } + else + d -= now; + tp->milli = 1000*(d+.0005); + if(tp->timeout) + sh.st.timetrap = time_delete(tp,sh.st.timetrap); + if(tp->milli > 0) + sh.st.timetrap = time_add(tp,sh.st.timetrap); + } + else + { + tp = (struct tevent*)nv_stack(np, (Namfun_t*)0); + sh.st.timetrap = time_delete(tp,sh.st.timetrap); + if(tp->action) + nv_close(tp->action); + nv_unset(np); + free((void*)fp); + } +} + +static const Namdisc_t alarmdisc = +{ + sizeof(struct tevent), + putval, + 0, + 0, + setdisc, +}; + +int b_alarm(int argc,char *argv[],void *extra) +{ + register int n,rflag=0; + register Namval_t *np; + register struct tevent *tp; + register Shell_t *shp = (Shell_t*)extra; + while (n = optget(argv, sh_optalarm)) switch (n) + { + case 'r': + rflag = R_FLAG; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0)); + if(argc==0) + { + print_alarms(shp->st.timetrap); + return(0); + } + if(argc!=2) + errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0)); + np = nv_open(argv[0],shp->var_tree,NV_NOARRAY|NV_VARNAME|NV_NOASSIGN); + if(!nv_isnull(np)) + nv_unset(np); + nv_setattr(np, NV_INTEGER|NV_DOUBLE); + if(!(tp = newof(NIL(struct tevent*),struct tevent,1,0))) + errormsg(SH_DICT,ERROR_exit(1),e_nospace); + tp->fun.disc = &alarmdisc; + tp->flags = rflag; + tp->node = np; + tp->sh = shp; + nv_stack(np,(Namfun_t*)tp); + nv_putval(np, argv[1], 0); + return(0); +} + diff --git a/usr/src/lib/libshell/common/bltins/cd_pwd.c b/usr/src/lib/libshell/common/bltins/cd_pwd.c new file mode 100644 index 0000000000..f57f7c977e --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/cd_pwd.c @@ -0,0 +1,280 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * cd [-LP] [dirname] + * cd [-LP] [old] [new] + * pwd [-LP] + * + * David Korn + * AT&T Labs + * research!dgk + * + */ + +#include "defs.h" +#include <stak.h> +#include <error.h> +#include "variables.h" +#include "path.h" +#include "name.h" +#include "builtins.h" +#include <ls.h> +#include <ctype.h> + +#ifdef PATH_BFPATH +/* + * Invalidate path name bindings to relative paths + */ +static void rehash(register Namval_t *np,void *data) +{ + Pathcomp_t *pp = (Pathcomp_t*)np->nvalue.cp; + NOT_USED(data); + if(pp && *pp->name!='/') + nv_unset(np); +} +#endif + +int b_cd(int argc, char *argv[],void *extra) +{ +#ifdef PATH_BFPATH + register char *dir; + Pathcomp_t *cdpath = 0; +#else + register char *dir, *cdpath=""; +#endif + register const char *dp; + register Shell_t *shp = (Shell_t*)extra; + int saverrno=0; + int rval,flag=0; + char *oldpwd; + Namval_t *opwdnod, *pwdnod; + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted+4); + while((rval = optget(argv,sh_optcd))) switch(rval) + { + case 'L': + flag = 0; + break; + case 'P': + flag = 1; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + argc -= opt_info.index; + dir = argv[0]; + if(error_info.errors>0 || argc >2) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + oldpwd = (char*)shp->pwd; + opwdnod = (shp->subshell?sh_assignok(OLDPWDNOD,1):OLDPWDNOD); + pwdnod = (shp->subshell?sh_assignok(PWDNOD,1):PWDNOD); + if(argc==2) + dir = sh_substitute(oldpwd,dir,argv[1]); + else if(!dir || *dir==0) + dir = nv_getval(HOME); + else if(*dir == '-' && dir[1]==0) + dir = nv_getval(opwdnod); + if(!dir || *dir==0) + errormsg(SH_DICT,ERROR_exit(1),argc==2?e_subst+4:e_direct); +#if _WINIX + if(*dir != '/' && (dir[1]!=':')) +#else + if(*dir != '/') +#endif /* _WINIX */ + { +#ifdef PATH_BFPATH + if(!(cdpath = (Pathcomp_t*)shp->cdpathlist) && (dp=(CDPNOD)->nvalue.cp)) + { + if(cdpath=path_addpath((Pathcomp_t*)0,dp,PATH_CDPATH)) + { + shp->cdpathlist = (void*)cdpath; + cdpath->shp = shp; + } + } +#else + cdpath = nv_getval(nv_scoped(CDPNOD)); +#endif + if(!oldpwd) + oldpwd = path_pwd(1); + } +#ifndef PATH_BFPATH + if(!cdpath) + cdpath = ""; +#endif + if(*dir=='.') + { + /* test for pathname . ./ .. or ../ */ + if(*(dp=dir+1) == '.') + dp++; + if(*dp==0 || *dp=='/') +#ifdef PATH_BFPATH + cdpath = 0; +#else + cdpath = ""; +#endif + } + rval = -1; + do + { +#ifdef PATH_BFPATH + dp = cdpath?cdpath->name:""; + cdpath = path_nextcomp(cdpath,dir,0); +#else + dp = cdpath; + cdpath=path_join(cdpath,dir); +#endif +#if _WINIX + if(*stakptr(PATH_OFFSET+1)==':' && isalpha(*stakptr(PATH_OFFSET))) + { + *stakptr(PATH_OFFSET+1) = *stakptr(PATH_OFFSET); + *stakptr(PATH_OFFSET)='/'; + } +#endif /* _WINIX */ + if(*stakptr(PATH_OFFSET)!='/') + + { + char *last=(char*)stakfreeze(1); + stakseek(PATH_OFFSET); + stakputs(oldpwd); + /* don't add '/' of oldpwd is / itself */ + if(*oldpwd!='/' || oldpwd[1]) + stakputc('/'); + stakputs(last+PATH_OFFSET); + stakputc(0); + } + if(!flag) + { + register char *cp; + stakseek(PATH_MAX+PATH_OFFSET); +#if SHOPT_FS_3D + if(!(cp = pathcanon(stakptr(PATH_OFFSET),PATH_DOTDOT))) + continue; + /* eliminate trailing '/' */ + while(*--cp == '/' && cp>stakptr(PATH_OFFSET)) + *cp = 0; +#else + if(*(cp=stakptr(PATH_OFFSET))=='/') + if(!pathcanon(cp,PATH_DOTDOT)) + continue; +#endif /* SHOPT_FS_3D */ + } + if((rval=chdir(path_relative(stakptr(PATH_OFFSET)))) >= 0) + goto success; + if(errno!=ENOENT && saverrno==0) + saverrno=errno; + } + while(cdpath); + if(rval<0 && *dir=='/' && *(path_relative(stakptr(PATH_OFFSET)))!='/') + rval = chdir(dir); + /* use absolute chdir() if relative chdir() fails */ + if(rval<0) + { + if(saverrno) + errno = saverrno; + errormsg(SH_DICT,ERROR_system(1),"%s:",dir); + } +success: + if(dir == nv_getval(opwdnod) || argc==2) + dp = dir; /* print out directory for cd - */ + if(flag) + { + dir = stakptr(PATH_OFFSET); + if (!(dir=pathcanon(dir,PATH_PHYSICAL))) + { + dir = stakptr(PATH_OFFSET); + errormsg(SH_DICT,ERROR_system(1),"%s:",dir); + } + stakseek(dir-stakptr(0)); + } + dir = (char*)stakfreeze(1)+PATH_OFFSET; +#ifdef PATH_BFPATH + if(*dp && (*dp!='.'||dp[1]) && strchr(dir,'/')) +#else + if(*dp && *dp!= ':' && strchr(dir,'/')) +#endif + sfputr(sfstdout,dir,'\n'); + if(*dir != '/') + return(0); + nv_putval(opwdnod,oldpwd,NV_RDONLY); + if(oldpwd) + free(oldpwd); + flag = strlen(dir); + /* delete trailing '/' */ + while(--flag>0 && dir[flag]=='/') + dir[flag] = 0; + nv_putval(pwdnod,dir,NV_RDONLY); + nv_onattr(pwdnod,NV_NOFREE|NV_EXPORT); + shp->pwd = pwdnod->nvalue.cp; +#ifdef PATH_BFPATH + nv_scan(shp->track_tree,rehash,(void*)0,NV_TAGGED,NV_TAGGED); + path_newdir(shp->pathlist); + path_newdir(shp->cdpathlist); +#endif + return(0); +} + +int b_pwd(int argc, char *argv[],void *extra) +{ + register int n, flag = 0; + register char *cp; + register Shell_t *shp = (Shell_t*)extra; + NOT_USED(argc); + while((n = optget(argv,sh_optpwd))) switch(n) + { + case 'L': + flag = 0; + break; + case 'P': + flag = 1; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(*(cp = path_pwd(0)) != '/') + errormsg(SH_DICT,ERROR_system(1), e_pwd); + if(flag) + { +#if SHOPT_FS_3D + if(shp->lim.fs3d && (flag = mount(e_dot,NIL(char*),FS3D_GET|FS3D_VIEW,0))>=0) + { + cp = (char*)stakseek(++flag+PATH_MAX); + mount(e_dot,cp,FS3D_GET|FS3D_VIEW|FS3D_SIZE(flag),0); + } + else +#endif /* SHOPT_FS_3D */ + cp = strcpy(stakseek(strlen(cp)+PATH_MAX),cp); + pathcanon(cp,PATH_PHYSICAL); + } + sfputr(sfstdout,cp,'\n'); + return(0); +} + diff --git a/usr/src/lib/libshell/common/bltins/cflow.c b/usr/src/lib/libshell/common/bltins/cflow.c new file mode 100644 index 0000000000..3f32f4386d --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/cflow.c @@ -0,0 +1,118 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * break [n] + * continue [n] + * return [n] + * exit [n] + * + * David Korn + * AT&T Labs + * dgk@research.att.com + * + */ + +#include "defs.h" +#include <ast.h> +#include <error.h> +#include <ctype.h> +#include "shnodes.h" +#include "builtins.h" + +/* + * return and exit + */ +#if 0 + /* for the dictionary generator */ + int b_exit(int n, register char *argv[],void *extra){} +#endif +int b_return(register int n, register char *argv[],void *extra) +{ + register char *arg; + register Shell_t *shp = (Shell_t*)extra; + struct checkpt *pp = (struct checkpt*)shp->jmplist; + const char *options = (**argv=='r'?sh_optreturn:sh_optexit); + while((n = optget(argv,options))) switch(n) + { + case ':': + if(!strmatch(argv[opt_info.index],"[+-]+([0-9])")) + errormsg(SH_DICT,2, "%s", opt_info.arg); + goto done; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + } +done: + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + pp->mode = (**argv=='e'?SH_JMPEXIT:SH_JMPFUN); + argv += opt_info.index; + n = (((arg= *argv)?(int)strtol(arg, (char**)0, 10):shp->oldexit)&SH_EXITMASK); + /* return outside of function, dotscript and profile is exit */ + if(shp->fn_depth==0 && shp->dot_depth==0 && !sh_isstate(SH_PROFILE)) + pp->mode = SH_JMPEXIT; + sh_exit(shp->savexit=n); + return(1); +} + + +/* + * break and continue + */ +#if 0 + /* for the dictionary generator */ + int b_continue(int n, register char *argv[],void *extra){} +#endif +int b_break(register int n, register char *argv[],void *extra) +{ + char *arg; + register int cont= **argv=='c'; + register Shell_t *shp = (Shell_t*)extra; + while((n = optget(argv,cont?sh_optcont:sh_optbreak))) switch(n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += opt_info.index; + n=1; + if(arg= *argv) + { + n = strtol(arg,&arg,10); + if(n<=0 || *arg) + errormsg(SH_DICT,ERROR_exit(1),e_nolabels,*argv); + } + if(shp->st.loopcnt) + { + shp->st.execbrk = shp->st.breakcnt = n; + if(shp->st.breakcnt > shp->st.loopcnt) + shp->st.breakcnt = shp->st.loopcnt; + if(cont) + shp->st.breakcnt = -shp->st.breakcnt; + } + return(0); +} + diff --git a/usr/src/lib/libshell/common/bltins/getopts.c b/usr/src/lib/libshell/common/bltins/getopts.c new file mode 100644 index 0000000000..65057da8d2 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/getopts.c @@ -0,0 +1,185 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * getopts optstring name [arg...] + * + * David Korn + * AT&T Labs + * research!dgk + * + */ + +#include "defs.h" +#include "variables.h" +#include <error.h> +#include <nval.h> +#include "builtins.h" + +static int infof(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp) +{ + if(nv_search(s,sh.fun_tree,0)) + { + int savtop = staktell(); + char *savptr = stakfreeze(0); + stakputc('$'); + stakputc('('); + stakputs(s); + stakputc(')'); + sfputr(sp,sh_mactry(stakfreeze(1)),-1); + stakset(savptr,savtop); + } + return(1); +} + +int b_getopts(int argc,char *argv[],void *extra) +{ + register char *options=error_info.context->id; + register Namval_t *np; + register int flag, mode, r=0; + register Shell_t *shp = (Shell_t*)extra; + char value[2], key[2]; + int jmpval; + struct checkpt buff, *pp; + Optdisc_t disc; + memset(&disc, 0, sizeof(disc)); + disc.version = OPT_VERSION; + disc.infof = infof; + value[1] = 0; + key[1] = 0; + while((flag = optget(argv,sh_optgetopts))) switch(flag) + { + case 'a': + options = opt_info.arg; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + argc -= opt_info.index; + if(error_info.errors || argc<2) + errormsg(SH_DICT,ERROR_usage(2), "%s", optusage((char*)0)); + error_info.context->flags |= ERROR_SILENT; + error_info.id = options; + options = argv[0]; + np = nv_open(argv[1],shp->var_tree,NV_NOASSIGN|NV_VARNAME); + if(argc>2) + { + argv +=1; + argc -=1; + } + else + { + argv = shp->st.dolv; + argc = shp->st.dolc; + } + opt_info.index = shp->st.optindex; + opt_info.offset = shp->st.optchar; + if(mode= (*options==':')) + options++; + sh_pushcontext(&buff,1); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval) + { + sh_popcontext(&buff); + pp = (struct checkpt*)shp->jmplist; + pp->mode = SH_JMPERREXIT; + sh_exit(2); + } + opt_info.disc = &disc; + switch(opt_info.index>=0 && opt_info.index<=argc?(opt_info.num= LONG_MIN,flag=optget(argv,options)):0) + { + case '?': + if(mode==0) + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + opt_info.option[1] = '?'; + /* FALL THRU */ + case ':': + key[0] = opt_info.option[1]; + if(strmatch(opt_info.arg,"*unknown*")) + flag = '?'; + if(mode) + opt_info.arg = key; + else + { + errormsg(SH_DICT,2, "%s", opt_info.arg); + opt_info.arg = 0; + flag = '?'; + } + *(options = value) = flag; + shp->st.opterror = 1; + if (opt_info.offset != 0 && !argv[opt_info.index][opt_info.offset]) + { + opt_info.offset = 0; + opt_info.index++; + } + break; + case 0: + if(shp->st.opterror) + { + char *com[2]; + com[0] = "-?"; + com[1] = 0; + flag = opt_info.index; + opt_info.index = 0; + optget(com,options); + opt_info.index = flag; + if(!mode && strchr(options,' ')) + errormsg(SH_DICT,ERROR_usage(2), "%s", optusage((char*)0)); + } + opt_info.arg = 0; + options = value; + *options = '?'; + r=1; + opt_info.offset = 0; + break; + default: + options = opt_info.option + (*opt_info.option!='+'); + } + error_info.context->flags &= ~ERROR_SILENT; + shp->st.optindex = opt_info.index; + shp->st.optchar = opt_info.offset; + nv_putval(np, options, 0); + nv_close(np); + np = nv_open(nv_name(OPTARGNOD),shp->var_tree,NV_NOSCOPE); + if(opt_info.num == LONG_MIN) + nv_putval(np, opt_info.arg, NV_RDONLY); + else if (opt_info.num > 0 && opt_info.arg && opt_info.arg[0] == (char)opt_info.num) + { + key[0] = (char)opt_info.num; + key[1] = 0; + nv_putval(np, key, NV_RDONLY); + } + else + { + Sfdouble_t d; + d = opt_info.number; + nv_putval(np, (char*)&d, NV_LDOUBLE|NV_RDONLY); + } + nv_close(np); + sh_popcontext(&buff); + opt_info.disc = 0; + return(r); +} + diff --git a/usr/src/lib/libshell/common/bltins/hist.c b/usr/src/lib/libshell/common/bltins/hist.c new file mode 100644 index 0000000000..fcb81958e4 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/hist.c @@ -0,0 +1,309 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#include "defs.h" +#include <stak.h> +#include <ls.h> +#include <error.h> +#include <ctype.h> +#include "variables.h" +#include "io.h" +#include "name.h" +#include "history.h" +#include "builtins.h" +#if SHOPT_HISTEXPAND +# include "edit.h" +#endif + +#define HIST_RECURSE 5 + +static void hist_subst(const char*, int fd, char*); + +#if 0 + /* for the benefit of the dictionary generator */ + int b_fc(int argc,char *argv[], void *extra){} +#endif +int b_hist(int argc,char *argv[], void *extra) +{ + register History_t *hp; + register char *arg; + register int flag,fdo; + register Shell_t *shp = (Shell_t*)extra; + Sfio_t *outfile; + char *fname; + int range[2], incr, index2, indx= -1; + char *edit = 0; /* name of editor */ + char *replace = 0; /* replace old=new */ + int lflag = 0, nflag = 0, rflag = 0; +#if SHOPT_HISTEXPAND + int pflag = 0; +#endif + Histloc_t location; + NOT_USED(argc); + if(!sh_histinit()) + errormsg(SH_DICT,ERROR_system(1),e_histopen); + hp = shp->hist_ptr; + while((flag = optget(argv,sh_opthist))) switch(flag) + { + case 'e': + edit = opt_info.arg; + break; + case 'n': + nflag++; + break; + case 'l': + lflag++; + break; + case 'r': + rflag++; + break; + case 's': + edit = "-"; + break; +#if SHOPT_HISTEXPAND + case 'p': + pflag++; + break; +#endif + case 'N': + if(indx<=0) + { + if((flag = hist_max(hp) - opt_info.num-1) < 0) + flag = 1; + range[++indx] = flag; + break; + } + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += (opt_info.index-1); +#if SHOPT_HISTEXPAND + if(pflag) + { + hist_cancel(hp); + pflag = 0; + while(arg=argv[1]) + { + flag = hist_expand(arg,&replace); + if(!(flag & HIST_ERROR)) + sfputr(sfstdout, replace, '\n'); + else + pflag = 1; + if(replace) + free(replace); + argv++; + } + return pflag; + } +#endif + flag = indx; + while(flag<1 && (arg=argv[1])) + { + /* look for old=new argument */ + if(!replace && strchr(arg+1,'=')) + { + replace = arg; + argv++; + continue; + } + else if(isdigit(*arg) || *arg == '-') + { + /* see if completely numeric */ + do arg++; + while(isdigit(*arg)); + if(*arg==0) + { + arg = argv[1]; + range[++flag] = (int)strtol(arg, (char**)0, 10); + if(*arg == '-') + range[flag] += (hist_max(hp)-1); + argv++; + continue; + } + } + /* search for last line starting with string */ + location = hist_find(hp,argv[1],hist_max(hp)-1,0,-1); + if((range[++flag] = location.hist_command) < 0) + errormsg(SH_DICT,ERROR_exit(1),e_found,argv[1]); + argv++; + } + if(flag <0) + { + /* set default starting range */ + if(lflag) + { + flag = hist_max(hp)-16; + if(flag<1) + flag = 1; + } + else + flag = hist_max(hp)-2; + range[0] = flag; + flag = 0; + } + index2 = hist_min(hp); + if(range[0]<index2) + range[0] = index2; + if(flag==0) + /* set default termination range */ + range[1] = (lflag?hist_max(hp)-1:range[0]); + if(range[1]>=(flag=(hist_max(hp) - !lflag))) + range[1] = flag; + /* check for valid ranges */ + if(range[1]<index2 || range[0]>=flag) + errormsg(SH_DICT,ERROR_exit(1),e_badrange,range[0],range[1]); + if(edit && *edit=='-' && range[0]!=range[1]) + errormsg(SH_DICT,ERROR_exit(1),e_eneedsarg); + /* now list commands from range[rflag] to range[1-rflag] */ + incr = 1; + flag = rflag>0; + if(range[1-flag] < range[flag]) + incr = -1; + if(lflag) + { + outfile = sfstdout; + arg = "\n\t"; + } + else + { + if(!(fname=pathtmp(NIL(char*),0,0,NIL(int*)))) + errormsg(SH_DICT,ERROR_exit(1),e_create,""); + if((fdo=open(fname,O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) < 0) + errormsg(SH_DICT,ERROR_system(1),e_create,fname); + outfile= sfnew(NIL(Sfio_t*),shp->outbuff,IOBSIZE,fdo,SF_WRITE); + arg = "\n"; + nflag++; + } + while(1) + { + if(nflag==0) + sfprintf(outfile,"%d\t",range[flag]); + else if(lflag) + sfputc(outfile,'\t'); + hist_list(shp->hist_ptr,outfile,hist_tell(shp->hist_ptr,range[flag]),0,arg); + if(lflag) + sh_sigcheck(); + if(range[flag] == range[1-flag]) + break; + range[flag] += incr; + } + if(lflag) + return(0); + sfclose(outfile); + hist_eof(hp); + arg = edit; + if(!arg && !(arg=nv_getval(nv_scoped(HISTEDIT))) && !(arg=nv_getval(nv_scoped(FCEDNOD)))) + arg = (char*)e_defedit; +#ifdef apollo + /* + * Code to support the FC using the pad editor. + * Exampled of how to use: HISTEDIT=pad + */ + if (strcmp (arg, "pad") == 0) + { + extern int pad_create(char*); + sh_close(fdo); + fdo = pad_create(fname); + pad_wait(fdo); + unlink(fname); + strcat(fname, ".bak"); + unlink(fname); + lseek(fdo,(off_t)0,SEEK_SET); + } + else + { +#endif /* apollo */ + if(*arg != '-') + { + char *com[3]; + com[0] = arg; + com[1] = fname; + com[2] = 0; + error_info.errors = sh_eval(sh_sfeval(com),0); + } + fdo = sh_chkopen(fname); + unlink(fname); + free((void*)fname); +#ifdef apollo + } +#endif /* apollo */ + /* don't history fc itself unless forked */ + error_info.flags |= ERROR_SILENT; + if(!sh_isstate(SH_FORKED)) + hist_cancel(hp); + sh_onstate(SH_HISTORY); + sh_onstate(SH_VERBOSE); /* echo lines as read */ + if(replace) + hist_subst(error_info.id,fdo,replace); + else if(error_info.errors == 0) + { + char buff[IOBSIZE+1]; + Sfio_t *iop = sfnew(NIL(Sfio_t*),buff,IOBSIZE,fdo,SF_READ); + /* read in and run the command */ + if(shp->hist_depth++ > HIST_RECURSE) + errormsg(SH_DICT,ERROR_exit(1),e_toodeep,"history"); + sh_eval(iop,1); + shp->hist_depth--; + } + else + { + sh_close(fdo); + if(!sh_isoption(SH_VERBOSE)) + sh_offstate(SH_VERBOSE); + sh_offstate(SH_HISTORY); + } + return(shp->exitval); +} + + +/* + * given a file containing a command and a string of the form old=new, + * execute the command with the string old replaced by new + */ + +static void hist_subst(const char *command,int fd,char *replace) +{ + register char *newp=replace; + register char *sp; + register int c; + off_t size; + char *string; + while(*++newp != '='); /* skip to '=' */ + if((size = lseek(fd,(off_t)0,SEEK_END)) < 0) + return; + lseek(fd,(off_t)0,SEEK_SET); + c = (int)size; + string = stakalloc(c+1); + if(read(fd,string,c)!=c) + return; + string[c] = 0; + *newp++ = 0; + if((sp=sh_substitute(string,replace,newp))==0) + errormsg(SH_DICT,ERROR_exit(1),e_subst,command); + *(newp-1) = '='; + sh_eval(sfopen(NIL(Sfio_t*),sp,"s"),1); +} + diff --git a/usr/src/lib/libshell/common/bltins/misc.c b/usr/src/lib/libshell/common/bltins/misc.c new file mode 100644 index 0000000000..974c3de037 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/misc.c @@ -0,0 +1,589 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * exec [arg...] + * eval [arg...] + * jobs [-lnp] [job...] + * login [arg...] + * let expr... + * . file [arg...] + * :, true, false + * vpath [top] [base] + * vmap [top] [base] + * wait [job...] + * shift [n] + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include "variables.h" +#include "shnodes.h" +#include "path.h" +#include "io.h" +#include "name.h" +#include "history.h" +#include "builtins.h" +#include "jobs.h" + +#define DOTMAX MAXDEPTH /* maximum level of . nesting */ + +static void noexport(Namval_t*,void*); + +struct login +{ + Shell_t *sh; + int clear; + char *arg0; +}; + +int b_exec(int argc,char *argv[], void *extra) +{ + struct login logdata; + register int n; + logdata.clear = 0; + logdata.arg0 = 0; + logdata.sh = (Shell_t*)extra; + logdata.sh->st.ioset = 0; + while (n = optget(argv, sh_optexec)) switch (n) + { + case 'a': + logdata.arg0 = opt_info.arg; + argc = 0; + break; + case 'c': + logdata.clear=1; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + } + argv += opt_info.index; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(*argv) + B_login(0,argv,(void*)&logdata); + return(0); +} + +static void noexport(register Namval_t* np, void *data) +{ + NOT_USED(data); + nv_offattr(np,NV_EXPORT); +} + +int B_login(int argc,char *argv[],void *extra) +{ + struct checkpt *pp; + register struct login *logp=0; + register Shell_t *shp; + const char *pname; + if(argc) + shp = (Shell_t*)extra; + else + { + logp = (struct login*)extra; + shp = logp->sh; + } + pp = (struct checkpt*)shp->jmplist; + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,argv[0]); + else + { + register struct argnod *arg=shp->envlist; + register Namval_t* np; + register char *cp; + if(shp->subshell) + sh_subfork(); + if(logp && logp->clear) + { +#ifdef _ENV_H + env_close(shp->env); + shp->env = env_open((char**)0,3); +#else + nv_scan(shp->var_tree,noexport,0,NV_EXPORT,NV_EXPORT); +#endif + } + while(arg) + { + if((cp=strchr(arg->argval,'=')) && + (*cp=0,np=nv_search(arg->argval,shp->var_tree,0))) + { + nv_onattr(np,NV_EXPORT); + sh_envput(shp->env,np); + } + if(cp) + *cp = '='; + arg=arg->argnxt.ap; + } + pname = argv[0]; + if(logp && logp->arg0) + argv[0] = logp->arg0; +#ifdef JOBS + if(job_close() < 0) + return(1); +#endif /* JOBS */ + /* force bad exec to terminate shell */ + pp->mode = SH_JMPEXIT; + sh_sigreset(2); + sh_freeup(); + path_exec(pname,argv,NIL(struct argnod*)); + sh_done(0); + } + return(1); +} + +int b_let(int argc,char *argv[],void *extra) +{ + register int r; + register char *arg; + NOT_USED(argc); + NOT_USED(extra); + while (r = optget(argv,sh_optlet)) switch (r) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors || !*argv) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + while(arg= *argv++) + r = !sh_arith(arg); + return(r); +} + +int b_eval(int argc,char *argv[], void *extra) +{ + register int r; + register Shell_t *shp = (Shell_t*)extra; + NOT_USED(argc); + while (r = optget(argv,sh_opteval)) switch (r) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s",opt_info.arg); + return(2); + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += opt_info.index; + if(*argv && **argv) + { + sh_offstate(SH_MONITOR); + sh_eval(sh_sfeval(argv),0); + } + return(shp->exitval); +} + +int b_dot_cmd(register int n,char *argv[],void* extra) +{ + register char *script; + register Namval_t *np; + register int jmpval; + register Shell_t *shp = (Shell_t*)extra; + struct sh_scoped savst, *prevscope = shp->st.self; + char *filename=0; + int fd; + struct dolnod *argsave=0, *saveargfor; + struct checkpt buff; + Sfio_t *iop=0; + NOT_USED(extra); + while (n = optget(argv,sh_optdot)) switch (n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s",opt_info.arg); + return(2); + } + argv += opt_info.index; + script = *argv; + if(error_info.errors || !script) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(shp->dot_depth++ > DOTMAX) + errormsg(SH_DICT,ERROR_exit(1),e_toodeep,script); + shp->st.lineno = error_info.line; + if(!(np=shp->posix_fun)) + { + /* check for KornShell style function first */ + np = nv_search(script,shp->fun_tree,0); + if(np && is_afunction(np) && !nv_isattr(np,NV_FPOSIX)) + { + if(!np->nvalue.ip) + { +#ifdef PATH_BFPATH + path_search(script,NIL(Pathcomp_t*),0); +#else + path_search(script,NIL(char*),0); +#endif + if(np->nvalue.ip) + { + if(nv_isattr(np,NV_FPOSIX)) + np = 0; + } + else + errormsg(SH_DICT,ERROR_exit(1),e_found,script); + } + } + else + np = 0; + if(!np) + { + if((fd=path_open(script,path_get(script))) < 0) + errormsg(SH_DICT,ERROR_system(1),e_open,script); + filename = path_fullname(stakptr(PATH_OFFSET)); + } + } + *prevscope = shp->st; + if(filename) + shp->st.filename = filename; + shp->st.prevst = prevscope; + shp->st.self = &savst; + shp->topscope = (Shscope_t*)shp->st.self; + prevscope->save_tree = shp->var_tree; + shp->st.cmdname = argv[0]; + if(np) + shp->st.filename = np->nvalue.rp->fname; + nv_putval(SH_PATHNAMENOD, shp->st.filename ,NV_NOFREE); + shp->posix_fun = 0; + if(np || argv[1]) + argsave = sh_argnew(argv,&saveargfor); + sh_pushcontext(&buff,SH_JMPDOT); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval == 0) + { + if(np) + sh_exec((Shnode_t*)(nv_funtree(np)),sh_isstate(SH_ERREXIT)); + else + { + char buff[IOBSIZE+1]; + iop = sfnew(NIL(Sfio_t*),buff,IOBSIZE,fd,SF_READ); + sh_eval(iop,0); + } + } + sh_popcontext(&buff); + if(!np) + free((void*)shp->st.filename); + shp->dot_depth--; + if((np || argv[1]) && jmpval!=SH_JMPSCRIPT) + sh_argreset(argsave,saveargfor); + else + { + prevscope->dolc = shp->st.dolc; + prevscope->dolv = shp->st.dolv; + } + if (shp->st.self != &savst) + *shp->st.self = shp->st; + /* only restore the top Shscope_t portion for posix functions */ + memcpy((void*)&shp->st, (void*)prevscope, sizeof(Shscope_t)); + shp->topscope = (Shscope_t*)prevscope; + nv_putval(SH_PATHNAMENOD, shp->st.filename ,NV_NOFREE); + if(shp->exitval > SH_EXITSIG) + sh_fault(shp->exitval&SH_EXITMASK); + if(jmpval && jmpval!=SH_JMPFUN) + siglongjmp(*shp->jmplist,jmpval); + return(shp->exitval); +} + +/* + * null, true command + */ +int b_true(int argc,register char *argv[],void *extra) +{ + NOT_USED(argc); + NOT_USED(argv[0]); + NOT_USED(extra); + return(0); +} + +/* + * false command + */ +int b_false(int argc,register char *argv[], void *extra) +{ + NOT_USED(argc); + NOT_USED(argv[0]); + NOT_USED(extra); + return(1); +} + +int b_shift(register int n, register char *argv[], void *extra) +{ + register char *arg; + register Shell_t *shp = (Shell_t*)extra; + while((n = optget(argv,sh_optshift))) switch(n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s",opt_info.arg); + return(2); + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += opt_info.index; + n = ((arg= *argv)?(int)sh_arith(arg):1); + if(n<0 || shp->st.dolc<n) + errormsg(SH_DICT,ERROR_exit(1),e_number,arg); + else + { + shp->st.dolv += n; + shp->st.dolc -= n; + } + return(0); +} + +int b_wait(int n,register char *argv[],void *extra) +{ + register Shell_t *shp = (Shell_t*)extra; + while((n = optget(argv,sh_optwait))) switch(n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + break; + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += opt_info.index; + job_bwait(argv); + return(shp->exitval); +} + +#ifdef JOBS +# if 0 + /* for the dictionary generator */ + int b_fg(int n,char *argv[],void *extra){} + int b_disown(int n,char *argv[],void *extra){} +# endif +int b_bg(register int n,register char *argv[],void *extra) +{ + register int flag = **argv; + register Shell_t *shp = (Shell_t*)extra; + register const char *optstr = sh_optbg; + if(*argv[0]=='f') + optstr = sh_optfg; + else if(*argv[0]=='d') + optstr = sh_optdisown; + while((n = optget(argv,optstr))) switch(n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + break; + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += opt_info.index; + if(!sh_isoption(SH_MONITOR) || !job.jobcontrol) + { + if(sh_isstate(SH_INTERACTIVE)) + errormsg(SH_DICT,ERROR_exit(1),e_no_jctl); + return(1); + } + if(flag=='d' && *argv==0) + argv = (char**)0; + if(job_walk(sfstdout,job_switch,flag,argv)) + errormsg(SH_DICT,ERROR_exit(1),e_no_job); + return(shp->exitval); +} + +int b_jobs(register int n,char *argv[],void *extra) +{ + register int flag = 0; + register Shell_t *shp = (Shell_t*)extra; + while((n = optget(argv,sh_optjobs))) switch(n) + { + case 'l': + flag = JOB_LFLAG; + break; + case 'n': + flag = JOB_NFLAG; + break; + case 'p': + flag = JOB_PFLAG; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(*argv==0) + argv = (char**)0; + if(job_walk(sfstdout,job_list,flag,argv)) + errormsg(SH_DICT,ERROR_exit(1),e_no_job); + job_wait((pid_t)0); + return(shp->exitval); +} +#endif + +#ifdef _cmd_universe +/* + * There are several universe styles that are masked by the getuniv(), + * setuniv() calls. + */ +int b_universe(int argc, char *argv[],void *extra) +{ + register char *arg; + register int n; + NOT_USED(extra); + while((n = optget(argv,sh_optuniverse))) switch(n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + break; + } + argv += opt_info.index; + argc -= opt_info.index; + if(error_info.errors || argc>1) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(arg = argv[0]) + { + if(!astconf("UNIVERSE",0,arg)) + errormsg(SH_DICT,ERROR_exit(1), e_badname,arg); + } + else + { + if(!(arg=astconf("UNIVERSE",0,0))) + errormsg(SH_DICT,ERROR_exit(1),e_nouniverse); + else + sfputr(sfstdout,arg,'\n'); + } + return(0); +} +#endif /* cmd_universe */ + +#if SHOPT_FS_3D +# if 0 + /* for the dictionary generator */ + int b_vmap(int argc,char *argv[], void *extra){} +# endif + int b_vpath(register int argc,char *argv[], void *extra) + { + register int flag, n; + register const char *optstr; + register char *vend; + register Shell_t *shp = (Shell_t*)extra; + if(argv[0][1]=='p') + { + optstr = sh_optvpath; + flag = FS3D_VIEW; + } + else + { + optstr = sh_optvmap; + flag = FS3D_VERSION; + } + while(n = optget(argv, optstr)) switch(n) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + break; + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(!shp->lim.fs3d) + goto failed; + argv += opt_info.index; + argc -= opt_info.index; + switch(argc) + { + case 0: + case 1: + flag |= FS3D_GET; + if((n = mount(*argv,(char*)0,flag,0)) >= 0) + { + vend = stakalloc(++n); + n = mount(*argv,vend,flag|FS3D_SIZE(n),0); + } + if(n < 0) + goto failed; + if(argc==1) + { + sfprintf(sfstdout,"%s\n",vend); + break; + } + n = 0; + while(flag = *vend++) + { + if(flag==' ') + { + flag = e_sptbnl[n+1]; + n = !n; + } + sfputc(sfstdout,flag); + } + if(n) + sfputc(sfstdout,'\n'); + break; + default: + if((argc&1)) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + /*FALLTHROUGH*/ + case 2: + if(!shp->lim.fs3d) + goto failed; + if(shp->subshell) + sh_subfork(); + for(n=0;n<argc;n+=2) + { + if(mount(argv[n+1],argv[n],flag,0)<0) + goto failed; + } + } + return(0); +failed: + if(argc>1) + errormsg(SH_DICT,ERROR_exit(1),e_cantset,flag==2?e_mapping:e_versions); + else + errormsg(SH_DICT,ERROR_exit(1),e_cantget,flag==2?e_mapping:e_versions); + return(1); + } +#endif /* SHOPT_FS_3D */ + diff --git a/usr/src/lib/libshell/common/bltins/mkservice.c b/usr/src/lib/libshell/common/bltins/mkservice.c new file mode 100644 index 0000000000..bba532b79d --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/mkservice.c @@ -0,0 +1,494 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * mkservice varname pathname + * eloop [-t timeout] + * Written by David Korn + * AT&T Labs + */ + +static const char mkservice_usage[] = +"[-?\n@(#)$Id: mkservice (AT&T Research) 2001-06-13 $\n]" +USAGE_LICENSE +"[+NAME? mkservice - create a shell server ]" +"[+DESCRIPTION?\bmkservice\b creates a tcp or udp server that is " + "implemented by shell functions.]" +"[+?The \aservice_path\a must be of the form \b/dev/tcp/localhost/\b\aportno\a " + "or \b/dev/udp/localhost/\b\aportno\a depending on whether the " + "\btcp\b or \budp\b protocol is used. \aportno\a is the port " + "number that the service will use.]" +"[+?The shell variable \avarname\a is associated with the service. This " + "variable can have subvariables that keeps the state of all " + "active connections. The functions \avarname\a\b.accept\b, " + "\avarname\a\b.action\b and \avarname\a\b.close\b implement the " + "service as follows:]{" + "[+accept?This function is invoked when a client tries to connect " + "to the service. It is called with an argument which " + "is the file descriptor number associated with the " + "accepted connection. If the function returns a non-zero " + "value, this connection will be closed.]" + "[+action?This function is invoked when there is data waiting " + "to be read from one of the active connections. It is " + "called with the file descriptor number that has data " + "to be read. If the function returns a non-zero " + "value, this connection will be closed.]" + "[+close?This function is invoked when the connection is closed.]" + "}" +"[+?If \avarname\a is unset, then all active connection, and the service " + "itself will be closed.]" +"" +"\n" +"\nvarname service_path\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\beloop\b(1)]" +; + + +static const char eloop_usage[] = +"[-?\n@(#)$Id: eloop (AT&T Research) 2001-06-13 $\n]" +USAGE_LICENSE +"[+NAME? eloop - process event loop]" +"[+DESCRIPTION?\beloop\b causes the shell to block waiting for events " + "to process. By default, \beloop\b does not return.]" +"[t]#[timeout?\atimeout\a is the number of milliseconds to wait " + "without receiving any events to process.]" +"\n" +"\n\n" +"\n" +"[+EXIT STATUS?If no timeout is specified, \beloop\b will not return " + "unless interrupted. Otherwise]{" + "[+0?The specified timeout interval occurred.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bmkservice\b(1)]" +; + + +#include "defs.h" + +#include <cmd.h> +#include <error.h> +#include <nval.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#define ACCEPT 0 +#define ACTION 1 +#define CLOSE 2 + +#ifndef O_SERVICE +# define O_SERVICE O_NOCTTY +#endif + +static const char* disctab[] = +{ + "accept", + "action", + "close", + 0 +}; + +typedef struct Service_s Service_t; + +struct Service_s +{ + Namfun_t fun; + short fd; + int refcount; + int (*acceptf)(Service_t*,int); + int (*actionf)(Service_t*,int,int); + int (*errorf)(Service_t*,int,const char*, ...); + void *context; + Namval_t* node; + Namval_t* disc[elementsof(disctab)-1]; +}; + +static short *file_list; +static Sfio_t **poll_list; +static Service_t **service_list; +static int npoll; +static int nready; +static int ready; +static int (*covered_fdnotify)(int, int); + +static int fdclose(Service_t *sp, register int fd) +{ + register int i; + service_list[fd] = 0; + if(sp->fd==fd) + sp->fd = -1; + for(i=0; i < npoll; i++) + { + if(file_list[i]==fd) + { + file_list[i] = file_list[npoll--]; + if(sp->actionf) + (*sp->actionf)(sp, fd, 1); + return(1); + } + } + return(0); +} + +static int fdnotify(int fd1, int fd2) +{ + Service_t *sp; + if (covered_fdnotify) + (*covered_fdnotify)(fd1, fd2); + if(fd2!=SH_FDCLOSE) + { + register int i; + service_list[fd2] = service_list[fd1]; + service_list[fd1] = 0; + for(i=0; i < npoll; i++) + { + if(file_list[i]==fd1) + { + file_list[i] = fd2; + return(0); + } + } + } + else if(sp = service_list[fd1]) + { + fdclose(sp,fd1); + if(--sp->refcount==0) + nv_unset(sp->node); + } + return(0); +} + +static void process_stream(Sfio_t* iop) +{ + int r=0, fd = sffileno(iop); + Service_t * sp = service_list[fd]; + if(fd==sp->fd) /* connection socket */ + { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + fd = accept(fd, &addr, &addrlen); + service_list[fd] = sp; + sp->refcount++; + file_list[npoll++] = fd; + if(fd>=0) + { + if(sp->acceptf) + r = (*sp->acceptf)(sp,fd); + } + } + else if(sp->actionf) + { + service_list[fd] = 0; + r = (*sp->actionf)(sp, fd, 0); + service_list[fd] = sp; + if(r<0) + close(fd); + } +} + +static int waitnotify(int fd, long timeout, int rw) +{ + Sfio_t *special=0, **pstream; + register int i; + + if (fd >= 0) + special = sh_fd2sfio(fd); + while(1) + { + pstream = poll_list; + while(ready < nready) + process_stream(pstream[ready++]); + if(special) + *pstream++ = special; + for(i=0; i < npoll; i++) + { + if(service_list[file_list[i]]) + *pstream++ = sh_fd2sfio(file_list[i]); + } +#if 1 + for(i=0; i < pstream-poll_list; i++) + sfset(poll_list[i],SF_WRITE,0); +#endif + nready = ready = 0; + errno = 0; +#ifdef DEBUG + sfprintf(sfstderr,"before poll npoll=%d",pstream-poll_list); + for(i=0; i < pstream-poll_list; i++) + sfprintf(sfstderr," %d",sffileno(poll_list[i])); + sfputc(sfstderr,'\n'); +#endif + nready = sfpoll(poll_list,pstream-poll_list,timeout); +#ifdef DEBUG + sfprintf(sfstderr,"after poll nready=%d",nready); + for(i=0; i < nready; i++) + sfprintf(sfstderr," %d",sffileno(poll_list[i])); + sfputc(sfstderr,'\n'); +#endif +#if 1 + for(i=0; i < pstream-poll_list; i++) + sfset(poll_list[i],SF_WRITE,1); +#endif + if(nready<=0) + return(errno? -1: 0); + if(special && poll_list[0]==special) + { + ready = 1; + return(fd); + } + } +} + +static int service_init(void) +{ + file_list = newof(NULL,short,n,0); + poll_list = newof(NULL,Sfio_t*,n,0); + service_list = newof(NULL,Service_t*,n,0); + covered_fdnotify = sh_fdnotify(fdnotify); + sh_waitnotify(waitnotify); + return(1); +} + +void service_add(Service_t *sp) +{ + static int init; + if (!init) + init = service_init(); + service_list[sp->fd] = sp; + file_list[npoll++] = sp->fd; +} + +static int Accept(register Service_t *sp, int accept_fd) +{ + register Namval_t* nq = sp->disc[ACCEPT]; + int fd; + + fd = fcntl(accept_fd, F_DUPFD, 10); + if (fd >= 0) + { + close(accept_fd); + if (nq) + { + char* av[3]; + char buff[20]; + + av[1] = buff; + av[2] = 0; + sfsprintf(buff, sizeof(buff), "%d", fd); + if (sh_fun(nq, sp->node, av)) + { + close(fd); + return -1; + } + } + } + sfsync(NiL); + return fd; +} + +static int Action(Service_t *sp, int fd, int close) +{ + register Namval_t* nq; + int r=0; + + if(close) + nq = sp->disc[CLOSE]; + else + nq = sp->disc[ACTION]; + if (nq) + { + char* av[3]; + char buff[20]; + + av[1] = buff; + av[2] = 0; + sfsprintf(buff, sizeof(buff), "%d", fd); + r=sh_fun(nq, sp->node, av); + } + sfsync(NiL); + return r > 0 ? -1 : 1; +} + +static int Error(Service_t *sp, int level, const char* arg, ...) +{ + va_list ap; + + va_start(ap, arg); + if(sp->node) + nv_unset(sp->node); + free((void*)sp); + errorv(NiL, ERROR_exit(1), ap); + va_end(ap); + return 0; +} + +static char* setdisc(Namval_t* np, const char* event, Namval_t* action, Namfun_t* fp) +{ + register Service_t* sp = (Service_t*)fp; + register const char* cp; + register int i; + register int n = strlen(event) - 1; + register Namval_t* nq; + + for (i = 0; cp = disctab[i]; i++) + { + if (memcmp(event, cp, n)) + continue; + if (action == np) + action = sp->disc[i]; + else + { + if (nq = sp->disc[i]) + free((void*)nq); + if (action) + sp->disc[i] = action; + else + sp->disc[i] = 0; + } + return action ? (char*)action : ""; + } + /* try the next level */ + return nv_setdisc(np, event, action, fp); +} + +static void putval(Namval_t* np, const char* val, int flag, Namfun_t* fp) +{ + register Service_t* sp = (Service_t*)fp; + if (!val) + fp = nv_stack(np, NiL); + nv_putv(np, val, flag, fp); + if (!val) + { + register int i; + for(i=0; i< sh.lim.open_max; i++) + { + if(service_list[i]==sp) + { + close(i); + if(--sp->refcount<=0) + break; + } + } + free((void*)fp); + return; + } +} + +static const Namdisc_t servdisc = +{ + sizeof(Service_t), + putval, + 0, + 0, + setdisc +}; + +int b_mkservice(int argc, char** argv, void* extra) +{ + register char* var; + register char* path; + register Namval_t* np; + register Service_t* sp; + register int fd; + + NOT_USED(argc); + NOT_USED(extra); + for (;;) + { + switch (optget(argv, mkservice_usage)) + { + case 0: + break; + case ':': + error(2, opt_info.arg); + continue; + case '?': + error(ERROR_usage(2), opt_info.arg); + continue; + } + break; + } + argv += opt_info.index; + if (error_info.errors || !(var = *argv++) || !(path = *argv++) || *argv) + error(ERROR_usage(2), optusage(NiL)); + if (!(sp = newof(0, Service_t, 1, 0))) + error(ERROR_exit(1), "out of space"); + sp->acceptf = Accept; + sp->actionf = Action; + sp->errorf = Error; + sp->refcount = 1; + sp->context = extra; + sp->node = 0; + sp->fun.disc = &servdisc; + if((fd = sh_open(path, O_SERVICE|O_RDWR))<=0) + { + free((void*)sp); + error(ERROR_exit(1), "%s: cannot start service", path); + } + if((sp->fd = fcntl(fd, F_DUPFD, 10))>=10) + close(fd); + else + sp->fd = fd; + np = nv_open(var,sh.var_tree,NV_ARRAY|NV_VARNAME|NV_NOASSIGN); + sp->node = np; + nv_putval(np, path, 0); + nv_stack(np, (Namfun_t*)sp); + service_add(sp); + return(0); +} + +int b_eloop(int argc, char** argv, void* extra) +{ + register long timeout = -1; + NOT_USED(argc); + NOT_USED(extra); + for (;;) + { + switch (optget(argv, eloop_usage)) + { + case 0: + break; + case 't': + timeout = opt_info.num; + continue; + case ':': + error(2, opt_info.arg); + continue; + case '?': + error(ERROR_usage(2), opt_info.arg); + continue; + } + break; + } + argv += opt_info.index; + if (error_info.errors || *argv) + error(ERROR_usage(2), optusage(NiL)); + while(1) + { + if(waitnotify(-1, timeout, 0)==0) + break; + sfprintf(sfstderr,"interrupted\n"); + } + return(errno != 0); +} diff --git a/usr/src/lib/libshell/common/bltins/print.c b/usr/src/lib/libshell/common/bltins/print.c new file mode 100644 index 0000000000..5363c6f3ab --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/print.c @@ -0,0 +1,897 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * echo [arg...] + * print [-nrps] [-f format] [-u filenum] [arg...] + * printf format [arg...] + * + * David Korn + * AT&T Labs + */ + +#include "defs.h" +#include <error.h> +#include <stak.h> +#include "io.h" +#include "name.h" +#include "history.h" +#include "builtins.h" +#include "streval.h" +#include <tmx.h> +#include <ctype.h> +#include <ccode.h> + +union types_t +{ + unsigned char c; + short h; + int i; + long l; + Sflong_t ll; + Sfdouble_t ld; + double d; + float f; + char *s; + int *ip; + char **p; +}; + +struct printf +{ + Sffmt_t hdr; + int argsize; + int intvar; + char **nextarg; + char cescape; + char err; + Shell_t *sh; +}; + +static int extend(Sfio_t*,void*, Sffmt_t*); +static const char preformat[] = ""; +static char *genformat(char*); +static int fmtvecho(const char*, struct printf*); + +struct print +{ + Shell_t *sh; + const char *options; + char raw; + char echon; +}; + +static char* nullarg[] = { 0, 0 }; + +/* + * Need to handle write failures to avoid locking output pool + */ +static int outexceptf(Sfio_t* iop, int mode, void* data, Sfdisc_t* dp) +{ + if(mode==SF_DPOP || mode==SF_FINAL) + free((void*)dp); + else if(mode==SF_WRITE && (errno!= EINTR || sh.trapnote)) + { + int save = errno; + sfpurge(iop); + sfpool(iop,NIL(Sfio_t*),SF_WRITE); + errno = save; + errormsg(SH_DICT,ERROR_system(1),e_badwrite,sffileno(iop)); + } + return(0); +} + +#if !SHOPT_ECHOPRINT + int B_echo(int argc, char *argv[],void *extra) + { + static char bsd_univ; + struct print prdata; + prdata.options = sh_optecho+5; + prdata.raw = prdata.echon = 0; + prdata.sh = (Shell_t*)extra; + NOT_USED(argc); + /* This mess is because /bin/echo on BSD is different */ + if(!prdata.sh->universe) + { + register char *universe; + if(universe=astconf("UNIVERSE",0,0)) + bsd_univ = (strcmp(universe,"ucb")==0); + prdata.sh->universe = 1; + } + if(!bsd_univ) + return(b_print(0,argv,&prdata)); + prdata.options = sh_optecho; + prdata.raw = 1; + while(argv[1] && *argv[1]=='-') + { + if(strcmp(argv[1],"-n")==0) + prdata.echon = 1; +#if !SHOPT_ECHOE + else if(strcmp(argv[1],"-e")==0) + prdata.raw = 0; + else if(strcmp(argv[1],"-ne")==0 || strcmp(argv[1],"-en")==0) + { + prdata.raw = 0; + prdata.echon = 1; + } +#endif /* SHOPT_ECHOE */ + else + break; + argv++; + } + return(b_print(0,argv,&prdata)); + } +#endif /* SHOPT_ECHOPRINT */ + +int b_printf(int argc, char *argv[],void *extra) +{ + struct print prdata; + NOT_USED(argc); + memset(&prdata,0,sizeof(prdata)); + prdata.sh = (Shell_t*)extra; + prdata.options = sh_optprintf; + return(b_print(-1,argv,&prdata)); +} + +/* + * argc==0 when called from echo + * argc==-1 when called from printf + */ + +int b_print(int argc, char *argv[], void *extra) +{ + register Sfio_t *outfile; + register int exitval=0,n, fd = 1; + register Shell_t *shp = (Shell_t*)extra; + const char *options, *msg = e_file+4; + char *format = 0; + int sflag = 0, nflag=0, rflag=0; + if(argc>0) + { + options = sh_optprint; + nflag = rflag = 0; + format = 0; + } + else + { + struct print *pp = (struct print*)extra; + shp = pp->sh; + options = pp->options; + if(argc==0) + { + nflag = pp->echon; + rflag = pp->raw; + argv++; + goto skip; + } + } + while((n = optget(argv,options))) switch(n) + { + case 'n': + nflag++; + break; + case 'p': + fd = shp->coutpipe; + msg = e_query; + break; + case 'f': + format = opt_info.arg; + break; + case 's': + /* print to history file */ + if(!sh_histinit()) + errormsg(SH_DICT,ERROR_system(1),e_history); + fd = sffileno(shp->hist_ptr->histfp); + sh_onstate(SH_HISTORY); + sflag++; + break; + case 'e': + rflag = 0; + break; + case 'r': + rflag = 1; + break; + case 'u': + fd = (int)strtol(opt_info.arg,&opt_info.arg,10); + if(*opt_info.arg) + fd = -1; + else if(fd<0 || fd >= shp->lim.open_max) + fd = -1; + else if(!(sh.inuse_bits&(1<<fd)) && (sh_inuse(fd) || (shp->hist_ptr && fd==sffileno(shp->hist_ptr->histfp)))) + + fd = -1; + break; + case ':': + /* The following is for backward compatibility */ +#if OPT_VERSION >= 19990123 + if(strcmp(opt_info.name,"-R")==0) +#else + if(strcmp(opt_info.option,"-R")==0) +#endif + { + rflag = 1; + if(error_info.errors==0) + { + argv += opt_info.index+1; + /* special case test for -Rn */ + if(strchr(argv[-1],'n')) + nflag++; + if(*argv && strcmp(*argv,"-n")==0) + { + + nflag++; + argv++; + } + goto skip2; + } + } + else + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors || (argc<0 && !(format = *argv++))) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); +skip: + if(format) + format = genformat(format); + /* handle special case of '-' operand for print */ + if(argc>0 && *argv && strcmp(*argv,"-")==0 && strcmp(argv[-1],"--")) + argv++; +skip2: + if(fd < 0) + { + errno = EBADF; + n = 0; + } + else if(!(n=shp->fdstatus[fd])) + n = sh_iocheckfd(fd); + if(!(n&IOWRITE)) + { + /* don't print error message for stdout for compatibility */ + if(fd==1) + return(1); + errormsg(SH_DICT,ERROR_system(1),msg); + } + if(!(outfile=shp->sftable[fd])) + { + Sfdisc_t *dp; + sh_onstate(SH_NOTRACK); + n = SF_WRITE|((n&IOREAD)?SF_READ:0); + shp->sftable[fd] = outfile = sfnew(NIL(Sfio_t*),shp->outbuff,IOBSIZE,fd,n); + sh_offstate(SH_NOTRACK); + sfpool(outfile,shp->outpool,SF_WRITE); + if(dp = new_of(Sfdisc_t,0)) + { + dp->exceptf = outexceptf; + dp->seekf = 0; + dp->writef = 0; + dp->readf = 0; + sfdisc(outfile,dp); + } + } + /* turn off share to guarantee atomic writes for printf */ + n = sfset(outfile,SF_SHARE|SF_PUBLIC,0); + if(format) + { + /* printf style print */ + Sfio_t *pool; + struct printf pdata; + memset(&pdata, 0, sizeof(pdata)); + pdata.sh = shp; + pdata.hdr.version = SFIO_VERSION; + pdata.hdr.extf = extend; + pdata.nextarg = argv; + sh_offstate(SH_STOPOK); + pool=sfpool(sfstderr,NIL(Sfio_t*),SF_WRITE); + do + { + if(shp->trapnote&SH_SIGSET) + break; + pdata.hdr.form = format; + sfprintf(outfile,"%!",&pdata); + } while(*pdata.nextarg && pdata.nextarg!=argv); + if(pdata.nextarg == nullarg && pdata.argsize>0) + sfwrite(outfile,stakptr(staktell()),pdata.argsize); + sfpool(sfstderr,pool,SF_WRITE); + exitval = pdata.err; + } + else + { + /* echo style print */ + if(sh_echolist(outfile,rflag,argv) && !nflag) + sfputc(outfile,'\n'); + } + if(sflag) + { + hist_flush(shp->hist_ptr); + sh_offstate(SH_HISTORY); + } + else if(n&SF_SHARE) + { + sfset(outfile,SF_SHARE|SF_PUBLIC,1); + sfsync(outfile); + } + return(exitval); +} + +/* + * echo the argument list onto <outfile> + * if <raw> is non-zero then \ is not a special character. + * returns 0 for \c otherwise 1. + */ + +int sh_echolist(Sfio_t *outfile, int raw, char *argv[]) +{ + register char *cp; + register int n; + struct printf pdata; + pdata.cescape = 0; + pdata.err = 0; + while(!pdata.cescape && (cp= *argv++)) + { + if(!raw && (n=fmtvecho(cp,&pdata))>=0) + { + if(n) + sfwrite(outfile,stakptr(staktell()),n); + } + else + sfputr(outfile,cp,-1); + if(*argv) + sfputc(outfile,' '); + sh_sigcheck(); + } + return(!pdata.cescape); +} + +/* + * modified version of stresc for generating formats + */ +static char strformat(char *s) +{ + register char* t; + register int c; + char* b; + char* p; + + b = t = s; + for (;;) + { + switch (c = *s++) + { + case '\\': + if(*s==0) + break; + c = chresc(s - 1, &p); + s = p; +#if SHOPT_MULTIBYTE + if(c>UCHAR_MAX && mbwide()) + { + t += wctomb(t, c); + continue; + } +#endif /* SHOPT_MULTIBYTE */ + if(c=='%') + *t++ = '%'; + else if(c==0) + { + *t++ = '%'; + c = 'Z'; + } + break; + case 0: + *t = 0; + return(t - b); + } + *t++ = c; + } +} + + +static char *genformat(char *format) +{ + register char *fp; + stakseek(0); + stakputs(preformat); + stakputs(format); + fp = (char*)stakfreeze(1); + strformat(fp+sizeof(preformat)-1); + return(fp); +} + +static char *fmthtml(const char *string) +{ + register const char *cp = string; + register int c, offset = staktell(); + while(c= *(unsigned char*)cp++) + { +#if SHOPT_MULTIBYTE + register int s; + if((s=mbsize(cp-1)) > 1) + { + cp += (s-1); + continue; + } +#endif /* SHOPT_MULTIBYTE */ + if(c=='<') + stakputs("<"); + else if(c=='>') + stakputs(">"); + else if(c=='&') + stakputs("&"); + else if(c=='"') + stakputs("""); + else if(c=='\'') + stakputs("'"); + else if(c==' ') + stakputs(" "); + else if(!isprint(c) && c!='\n' && c!='\r') + sfprintf(stkstd,"&#%X;",CCMAPC(c,CC_NATIVE,CC_ASCII)); + else + stakputc(c); + } + stakputc(0); + return(stakptr(offset)); +} + +static void *fmtbase64(char *string, ssize_t *sz) +{ + char *cp; + Sfdouble_t d; + size_t size; + Namval_t *np = nv_open(string, NiL, NV_VARNAME|NV_NOASSIGN|NV_NOADD); + static union types_t number; + if(!np) + return(""); + if(nv_isattr(np,NV_INTEGER)) + { + d = nv_getnum(np); + if(nv_isattr(np,NV_DOUBLE)) + { + if(nv_isattr(np,NV_LONG)) + { + size = sizeof(Sfdouble_t); + number.ld = d; + } + else if(nv_isattr(np,NV_SHORT)) + { + size = sizeof(float); + number.f = (float)d; + } + else + { + size = sizeof(double); + number.d = (double)d; + } + } + else + { + if(nv_isattr(np,NV_LONG)) + { + size = sizeof(Sflong_t); + number.ll = (Sflong_t)d; + } + else if(nv_isattr(np,NV_SHORT)) + { + size = sizeof(short); + number.h = (short)d; + } + else + { + size = sizeof(short); + number.i = (int)d; + } + } + if(sz) + *sz = size; + return((void*)&number); + } + if(nv_isattr(np,NV_BINARY)) + nv_onattr(np,NV_RAW); + cp = nv_getval(np); + if(nv_isattr(np,NV_BINARY)) + nv_offattr(np,NV_RAW); + if((size = nv_size(np))==0) + size = strlen(cp); + if(sz) + *sz = size; + return((void*)cp); +} + +static int extend(Sfio_t* sp, void* v, Sffmt_t* fe) +{ + char* lastchar = ""; + register int neg = 0; + Sfdouble_t d; + Sfdouble_t longmin = LDBL_LLONG_MIN; + Sfdouble_t longmax = LDBL_LLONG_MAX; + int format = fe->fmt; + int n; + int fold = fe->base; + union types_t* value = (union types_t*)v; + struct printf* pp = (struct printf*)fe; + register char* argp = *pp->nextarg; + + fe->flags |= SFFMT_VALUE; + if(!argp || format=='Z') + { + switch(format) + { + case 'c': + value->c = 0; + fe->flags &= ~SFFMT_LONG; + break; + case 'q': + format = 's'; + /* FALL THROUGH */ + case 's': + case 'H': + case 'B': + case 'P': + case 'R': + case 'Z': + case 'b': + fe->fmt = 's'; + fe->size = -1; + fe->base = -1; + value->s = ""; + fe->flags &= ~SFFMT_LONG; + break; + case 'a': + case 'e': + case 'f': + case 'g': + case 'A': + case 'E': + case 'F': + case 'G': + if(SFFMT_LDOUBLE) + value->ld = 0.; + else + value->d = 0.; + break; + case 'n': + value->ip = &pp->intvar; + break; + case 'Q': + value->ll = 0; + break; + case 'T': + fe->fmt = 'd'; + value->ll = tmxgettime(); + break; + default: + if(!strchr("DdXxoUu",format)) + errormsg(SH_DICT,ERROR_exit(1),e_formspec,format); + fe->fmt = 'd'; + value->ll = 0; + break; + } + } + else + { + switch(format) + { + case 'p': + value->p = (char**)strtol(argp,&lastchar,10); + break; + case 'n': + { + Namval_t *np; + np = nv_open(argp,sh.var_tree,NV_VARNAME|NV_NOASSIGN|NV_NOARRAY); + nv_unset(np); + nv_onattr(np,NV_INTEGER); + if (np->nvalue.lp = new_of(int32_t,0)) + *np->nvalue.lp = 0; + nv_setsize(np,10); + if(sizeof(int)==sizeof(int32_t)) + value->ip = (int*)np->nvalue.lp; + else + { + int32_t sl = 1; + value->ip = (int*)(((char*)np->nvalue.lp) + (*((char*)&sl) ? 0 : sizeof(int))); + } + nv_close(np); + break; + } + case 'q': + case 'b': + case 's': + case 'B': + case 'H': + case 'P': + case 'R': + fe->fmt = 's'; + fe->size = -1; + if(format=='s' && fe->base>=0) + { + value->p = pp->nextarg; + pp->nextarg = nullarg; + } + else + { + fe->base = -1; + value->s = argp; + } + fe->flags &= ~SFFMT_LONG; + break; + case 'c': + if(fe->base >=0) + value->s = argp; + else + value->c = *argp; + fe->flags &= ~SFFMT_LONG; + break; + case 'o': + case 'x': + case 'X': + case 'u': + case 'U': + longmax = LDBL_ULLONG_MAX; + case '.': + if(fe->size==2 && strchr("bcsqHPRQTZ",*fe->form)) + { + value->ll = ((unsigned char*)argp)[0]; + break; + } + case 'd': + case 'D': + case 'i': + switch(*argp) + { + case '\'': + case '"': + value->ll = ((unsigned char*)argp)[1]; + break; + default: + d = sh_strnum(argp,&lastchar,0); + if(d<longmin) + { + errormsg(SH_DICT,ERROR_warn(0),e_overflow,argp); + pp->err = 1; + d = longmin; + } + else if(d>longmax) + { + errormsg(SH_DICT,ERROR_warn(0),e_overflow,argp); + pp->err = 1; + d = longmax; + } + value->ll = (Sflong_t)d; + if(lastchar == *pp->nextarg) + { + value->ll = *argp; + lastchar = ""; + } + break; + } + if(neg) + value->ll = -value->ll; + fe->size = sizeof(value->ll); + break; + case 'a': + case 'e': + case 'f': + case 'g': + case 'A': + case 'E': + case 'F': + case 'G': + d = sh_strnum(*pp->nextarg,&lastchar,0); + if(SFFMT_LDOUBLE) + { + value->ld = d; + fe->size = sizeof(value->ld); + } + else + { + value->d = d; + fe->size = sizeof(value->d); + } + break; + case 'Q': + value->ll = (Sflong_t)strelapsed(*pp->nextarg,&lastchar,1); + break; + case 'T': + value->ll = (Sflong_t)tmxdate(*pp->nextarg,&lastchar,TMX_NOW); + break; + default: + value->ll = 0; + fe->fmt = 'd'; + fe->size = sizeof(value->ll); + errormsg(SH_DICT,ERROR_exit(1),e_formspec,format); + break; + } + if (format == '.') + value->i = value->ll; + if(*lastchar) + { + errormsg(SH_DICT,ERROR_warn(0),e_argtype,format); + pp->err = 1; + } + pp->nextarg++; + } + switch(format) + { + case 'Z': + fe->fmt = 'c'; + fe->base = -1; + value->c = 0; + break; + case 'b': + if((n=fmtvecho(value->s,pp))>=0) + { + if(pp->nextarg == nullarg) + { + pp->argsize = n; + return -1; + } + value->s = stakptr(staktell()); + } + break; + case 'B': + value->s = (char*)fmtbase64(value->s, &fe->size); + break; + case 'H': + value->s = fmthtml(value->s); + break; + case 'q': + value->s = sh_fmtqf(value->s, !!(fe->flags & SFFMT_ALTER), fold); + break; + case 'P': + { + char *s = fmtmatch(value->s); + if(!s || *s==0) + errormsg(SH_DICT,ERROR_exit(1),e_badregexp,value->s); + value->s = s; + break; + } + case 'R': + value->s = fmtre(value->s); + if(*value->s==0) + errormsg(SH_DICT,ERROR_exit(1),e_badregexp,value->s); + break; + case 'Q': + if (fe->n_str>0) + { + fe->fmt = 'd'; + fe->size = sizeof(value->ll); + } + else + { + value->s = fmtelapsed(value->ll, 1); + fe->fmt = 's'; + fe->size = -1; + } + break; + case 'T': + if(fe->n_str>0) + { + n = fe->t_str[fe->n_str]; + fe->t_str[fe->n_str] = 0; + value->s = fmttmx(fe->t_str, value->ll); + fe->t_str[fe->n_str] = n; + } + else value->s = fmttmx(NIL(char*), value->ll); + fe->fmt = 's'; + fe->size = -1; + break; + } + return 0; +} + +/* + * construct System V echo string out of <cp> + * If there are not escape sequences, returns -1 + * Otherwise, puts null terminated result on stack, but doesn't freeze it + * returns length of output. + */ + +static int fmtvecho(const char *string, struct printf *pp) +{ + register const char *cp = string, *cpmax; + register int c; + register int offset = staktell(); +#if SHOPT_MULTIBYTE + int chlen; + if(mbwide()) + { + while(1) + { + if ((chlen = mbsize(cp)) > 1) + /* Skip over multibyte characters */ + cp += chlen; + else if((c= *cp++)==0 || c == '\\') + break; + } + } + else +#endif /* SHOPT_MULTIBYTE */ + while((c= *cp++) && (c!='\\')); + if(c==0) + return(-1); + c = --cp - string; + if(c>0) + stakwrite((void*)string,c); + for(; c= *cp; cp++) + { +#if SHOPT_MULTIBYTE + if (mbwide() && ((chlen = mbsize(cp)) > 1)) + { + stakwrite(cp,chlen); + cp += (chlen-1); + continue; + } +#endif /* SHOPT_MULTIBYTE */ + if( c=='\\') switch(*++cp) + { + case 'E': + c = ('a'==97?'\033':39); /* ASCII/EBCDIC */ + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'c': + pp->cescape++; + pp->nextarg = nullarg; + goto done; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 'v': + c = '\v'; + break; + case 't': + c = '\t'; + break; + case '\\': + c = '\\'; + break; + case '0': + c = 0; + cpmax = cp + 4; + while(++cp<cpmax && *cp>='0' && *cp<='7') + { + c <<= 3; + c |= (*cp-'0'); + } + default: + cp--; + } + stakputc(c); + } +done: + c = staktell()-offset; + stakputc(0); + stakseek(offset); + return(c); +} diff --git a/usr/src/lib/libshell/common/bltins/read.c b/usr/src/lib/libshell/common/bltins/read.c new file mode 100644 index 0000000000..930470275c --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/read.c @@ -0,0 +1,590 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * read [-Aprs] [-d delim] [-u filenum] [-t timeout] [-n n] [-N n] [name...] + * + * David Korn + * AT&T Labs + * + */ + +#include <ast.h> +#include <error.h> +#include <ctype.h> +#include "defs.h" +#include "variables.h" +#include "lexstates.h" +#include "io.h" +#include "name.h" +#include "builtins.h" +#include "history.h" +#include "terminal.h" +#include "edit.h" + +#define R_FLAG 1 /* raw mode */ +#define S_FLAG 2 /* save in history file */ +#define A_FLAG 4 /* read into array */ +#define N_FLAG 8 /* fixed size read at most */ +#define NN_FLAG 0x10 /* fixed size read exact */ +#define V_FLAG 0x20 /* use default value */ +#define D_FLAG 8 /* must be number of bits for all flags */ + +int b_read(int argc,char *argv[], void *extra) +{ + Sfdouble_t sec; + register char *name; + register int r, flags=0, fd=0; + register Shell_t *shp = (Shell_t*)extra; + long timeout = 1000*shp->st.tmout; + int save_prompt; + static char default_prompt[3] = {ESC,ESC}; + NOT_USED(argc); + while((r = optget(argv,sh_optread))) switch(r) + { + case 'A': + flags |= A_FLAG; + break; + case 't': + sec = sh_strnum(opt_info.arg, (char**)0,1); + timeout = sec ? 1000*sec : 1; + break; + case 'd': + if(opt_info.arg && *opt_info.arg!='\n') + { + char *cp = opt_info.arg; + flags &= ~((1<<D_FLAG)-1); + flags |= (mbchar(cp)<< D_FLAG); + } + break; + case 'p': + if((fd = shp->cpipe[0])<=0) + errormsg(SH_DICT,ERROR_exit(1),e_query); + break; + case 'n': case 'N': + flags &= ~((1<<D_FLAG)-1); + flags |= (r=='n'?N_FLAG:NN_FLAG); + r = (int)opt_info.num; + if((unsigned)r > (1<<((8*sizeof(int))-D_FLAG))-1) + errormsg(SH_DICT,ERROR_exit(1),e_overlimit,"n"); + flags |= (r<< D_FLAG); + break; + case 'r': + flags |= R_FLAG; + break; + case 's': + /* save in history file */ + flags |= S_FLAG; + break; + case 'u': + fd = (int)opt_info.num; + if(sh_inuse(fd)) + fd = -1; + break; + case 'v': + flags |= V_FLAG; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2), "%s", optusage((char*)0)); + if(!((r=shp->fdstatus[fd])&IOREAD) || !(r&(IOSEEK|IONOSEEK))) + r = sh_iocheckfd(fd); + if(fd<0 || !(r&IOREAD)) + errormsg(SH_DICT,ERROR_system(1),e_file+4); + /* look for prompt */ + shp->prompt = default_prompt; + if((name = *argv) && (name=strchr(name,'?')) && (r&IOTTY)) + { + r = strlen(++name)+1; + if(shp->prompt=(char*)sfreserve(sfstderr,r,SF_LOCKR)) + { + memcpy(shp->prompt,name,r); + sfwrite(sfstderr,shp->prompt,r-1); + } + } + shp->timeout = 0; + save_prompt = shp->nextprompt; + shp->nextprompt = 0; + r=sh_readline(shp,argv,fd,flags,timeout); + shp->nextprompt = save_prompt; + if(r==0 && (r=(sfeof(shp->sftable[fd])||sferror(shp->sftable[fd])))) + { + if(fd == shp->cpipe[0]) + { + sh_pclose(shp->cpipe); + return(1); + } + } + sfclrerr(shp->sftable[fd]); + return(r); +} + +/* + * here for read timeout + */ +static void timedout(void *handle) +{ + sfclrlock((Sfio_t*)handle); + sh_exit(1); +} + +/* + * This is the code to read a line and to split it into tokens + * <names> is an array of variable names + * <fd> is the file descriptor + * <flags> is union of -A, -r, -s, and contains delimiter if not '\n' + * <timeout> is number of milli-seconds until timeout + */ + +int sh_readline(register Shell_t *shp,char **names, int fd, int flags,long timeout) +{ + register int c; + register unsigned char *cp; + register Namval_t *np; + register char *name, *val; + register Sfio_t *iop; + char *ifs; + unsigned char *cpmax; + unsigned char *del; + char was_escape = 0; + char use_stak = 0; + char was_write = 0; + char was_share = 1; + int rel, wrd; + long array_index = 0; + void *timeslot=0; + int delim = '\n'; + int jmpval=0; + int size = 0; + struct checkpt buff; + if(!(iop=shp->sftable[fd]) && !(iop=sh_iostream(fd))) + return(1); + if(names && (name = *names)) + { + if(val= strchr(name,'?')) + *val = 0; + np = nv_open(name,shp->var_tree,NV_NOASSIGN|NV_VARNAME|NV_ARRAY); + if((flags&V_FLAG) && shp->ed_context) + ((struct edit*)shp->ed_context)->e_default = np; + if(flags&A_FLAG) + { + flags &= ~A_FLAG; + array_index = 1; + nv_unset(np); + nv_putsub(np,NIL(char*),0L); + } + else + name = *++names; + if(val) + *val = '?'; + } + else + { + name = 0; + if(dtvnext(shp->var_tree) || shp->namespace) + np = nv_open(nv_name(REPLYNOD),shp->var_tree,0); + else + np = REPLYNOD; + } + if(flags>>D_FLAG) /* delimiter not new-line or fixed size read */ + { + if(flags&(N_FLAG|NN_FLAG)) + size = ((unsigned)flags)>>D_FLAG; + else + delim = ((unsigned)flags)>>D_FLAG; + if(shp->fdstatus[fd]&IOTTY) + tty_raw(fd,1); + } + if(!(flags&(N_FLAG|NN_FLAG))) + { + Namval_t *mp; + /* set up state table based on IFS */ + ifs = nv_getval(mp=nv_scoped(IFSNOD)); + if((flags&R_FLAG) && shp->ifstable['\\']==S_ESC) + shp->ifstable['\\'] = 0; + else if(!(flags&R_FLAG) && shp->ifstable['\\']==0) + shp->ifstable['\\'] = S_ESC; + shp->ifstable[delim] = S_NL; + if(delim!='\n') + { + shp->ifstable['\n'] = 0; + nv_putval(mp, ifs, NV_RDONLY); + } + shp->ifstable[0] = S_EOF; + } + sfclrerr(iop); + if(np->nvfun && np->nvfun->disc->readf) + return((* np->nvfun->disc->readf)(np,iop,delim,np->nvfun)); + was_write = (sfset(iop,SF_WRITE,0)&SF_WRITE)!=0; + if(fd==0) + was_share = (sfset(iop,SF_SHARE,1)&SF_SHARE)!=0; + if(timeout || (shp->fdstatus[fd]&(IOTTY|IONOSEEK))) + { + sh_pushcontext(&buff,1); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval) + goto done; + if(timeout) + timeslot = (void*)sh_timeradd(timeout,0,timedout,(void*)iop); + } + if(flags&(N_FLAG|NN_FLAG)) + { + char buf[64],*var=buf; + /* reserved buffer */ + if((c=size)>=sizeof(buf)) + { + if(!(var = (char*)malloc(c+1))) + sh_exit(1); + } + if((sfset(iop,SF_SHARE,1)&SF_SHARE) && fd!=0) + was_share = 1; + if(size==0) + { + cp = sfreserve(iop,0,0); + c = 0; + } + else + { + c= (shp->fdstatus[fd]&(IOTTY|IONOSEEK))?1:-1; + if(flags&NN_FLAG) + c = size; + if(cp = sfreserve(iop,c,!(flags&NN_FLAG))) + c = sfvalue(iop); + else + c = 0; + if(c>size) + c = size; + if(c>0) + { + memcpy((void*)var,cp,c); + if(flags&N_FLAG) + sfread(iop,cp,c); + } + var[c] = 0; + if(c>=size) + sfclrerr(iop); + } + if(timeslot) + timerdel(timeslot); + if(nv_isattr(np,NV_BINARY)) + { + if(c<sizeof(buf)) + var = memdup(var,c); + nv_putval(np,var, NV_RAW); + nv_setsize(np,c); + } + else + { + nv_putval(np,var,0); + if(c>=sizeof(buf)) + free((void*)var); + } + goto done; + } + else if(cp = (unsigned char*)sfgetr(iop,delim,0)) + c = sfvalue(iop); + else if(cp = (unsigned char*)sfgetr(iop,delim,-1)) + c = sfvalue(iop)+1; + if(timeslot) + timerdel(timeslot); + if((flags&S_FLAG) && !shp->hist_ptr) + { + sh_histinit(); + if(!shp->hist_ptr) + flags &= ~S_FLAG; + } + if(cp) + { + cpmax = cp + c; +#if SHOPT_CRNL + if(delim=='\n' && c>=2 && cpmax[-2]=='\r') + cpmax--; +#endif /* SHOPT_CRNL */ + if(*(cpmax-1) != delim) + *(cpmax-1) = delim; + if(flags&S_FLAG) + sfwrite(shp->hist_ptr->histfp,(char*)cp,c); + c = shp->ifstable[*cp++]; +#if !SHOPT_MULTIBYTE + if(!name && (flags&R_FLAG)) /* special case single argument */ + { + /* skip over leading blanks */ + while(c==S_SPACE) + c = shp->ifstable[*cp++]; + /* strip trailing delimiters */ + if(cpmax[-1] == '\n') + cpmax--; + if(cpmax>cp) + { + while((c=shp->ifstable[*--cpmax])==S_DELIM || c==S_SPACE); + cpmax[1] = 0; + } + else + *cpmax =0; + if(nv_isattr(np, NV_RDONLY)) + { + errormsg(SH_DICT,ERROR_warn(0),e_readonly, nv_name(np)); + jmpval = 1; + } + else + nv_putval(np,(char*)cp-1,0); + goto done; + } +#endif /* !SHOPT_MULTIBYTE */ + } + else + c = S_NL; + shp->nextprompt = 2; + rel= staktell(); + /* val==0 at the start of a field */ + val = 0; + del = 0; + while(1) + { + switch(c) + { +#if SHOPT_MULTIBYTE + case S_MBYTE: + if(val==0) + val = (char*)(cp-1); + if(sh_strchr(ifs,(char*)cp-1)>=0) + { + c = mbsize((char*)cp-1); + if(name) + cp[-1] = 0; + if(c>1) + cp += (c-1); + c = S_DELIM; + } + else + c = 0; + continue; +#endif /*SHOPT_MULTIBYTE */ + case S_ESC: + /* process escape character */ + if((c = shp->ifstable[*cp++]) == S_NL) + was_escape = 1; + else + c = 0; + if(val) + { + stakputs(val); + use_stak = 1; + was_escape = 1; + *val = 0; + } + continue; + + case S_EOF: + /* check for end of buffer */ + if(val && *val) + { + stakputs(val); + use_stak = 1; + } + val = 0; + if(cp>=cpmax) + { + c = S_NL; + break; + } + /* eliminate null bytes */ + c = shp->ifstable[*cp++]; + if(!name && val && (c==S_SPACE||c==S_DELIM||c==S_MBYTE)) + c = 0; + continue; + case S_NL: + if(was_escape) + { + was_escape = 0; + if(cp = (unsigned char*)sfgetr(iop,delim,0)) + c = sfvalue(iop); + else if(cp=(unsigned char*)sfgetr(iop,delim,-1)) + c = sfvalue(iop)+1; + if(cp) + { + if(flags&S_FLAG) + sfwrite(shp->hist_ptr->histfp,(char*)cp,c); + cpmax = cp + c; + c = shp->ifstable[*cp++]; + val=0; + if(!name && (c==S_SPACE || c==S_DELIM || c==S_MBYTE)) + c = 0; + continue; + } + } + c = S_NL; + break; + + case S_SPACE: + /* skip over blanks */ + while((c=shp->ifstable[*cp++])==S_SPACE); + if(!val) + continue; +#if SHOPT_MULTIBYTE + if(c==S_MBYTE) + { + if(sh_strchr(ifs,(char*)cp-1)>=0) + { + if((c = mbsize((char*)cp-1))>1) + cp += (c-1); + c = S_DELIM; + } + else + c = 0; + } +#endif /* SHOPT_MULTIBYTE */ + if(c!=S_DELIM) + break; + /* FALL THRU */ + + case S_DELIM: + if(!del) + del = cp - 1; + if(name) + { + /* skip over trailing blanks */ + while((c=shp->ifstable[*cp++])==S_SPACE); + break; + } + /* FALL THRU */ + + case 0: + if(val==0 || was_escape) + { + val = (char*)(cp-1); + was_escape = 0; + } + /* skip over word characters */ + wrd = -1; + while(1) + { + while((c=shp->ifstable[*cp++])==0) + if(!wrd) + wrd = 1; + if(!del&&c==S_DELIM) + del = cp - 1; + if(name || c==S_NL || c==S_ESC || c==S_EOF || c==S_MBYTE) + break; + if(wrd<0) + wrd = 0; + } + if(wrd>0) + del = (unsigned char*)""; + if(c!=S_MBYTE) + cp[-1] = 0; + continue; + } + /* assign value and advance to next variable */ + if(!val) + val = ""; + if(use_stak) + { + stakputs(val); + stakputc(0); + val = stakptr(rel); + } + if(!name && *val) + { + /* strip off trailing space delimiters */ + register unsigned char *vp = (unsigned char*)val + strlen(val); + while(shp->ifstable[*--vp]==S_SPACE); + if(vp==del) + { + if(vp==(unsigned char*)val) + vp--; + else + while(shp->ifstable[*--vp]==S_SPACE); + } + vp[1] = 0; + } + if(nv_isattr(np, NV_RDONLY)) + { + errormsg(SH_DICT,ERROR_warn(0),e_readonly, nv_name(np)); + jmpval = 1; + } + else + nv_putval(np,val,0); + val = 0; + del = 0; + if(use_stak) + { + stakseek(rel); + use_stak = 0; + } + if(array_index) + { + nv_putsub(np, NIL(char*), array_index++); + if(c!=S_NL) + continue; + name = *++names; + } + while(1) + { + if(sh_isoption(SH_ALLEXPORT)&&!strchr(nv_name(np),'.') && !nv_isattr(np,NV_EXPORT)) + { + nv_onattr(np,NV_EXPORT); + sh_envput(sh.env,np); + } + if(name) + { + nv_close(np); + np = nv_open(name,shp->var_tree,NV_NOASSIGN|NV_VARNAME); + name = *++names; + } + else + np = 0; + if(c!=S_NL) + break; + if(!np) + goto done; + if(nv_isattr(np, NV_RDONLY)) + { + errormsg(SH_DICT,ERROR_warn(0),e_readonly, nv_name(np)); + jmpval = 1; + } + else + nv_putval(np, "", 0); + } + } +done: + if(timeout || (shp->fdstatus[fd]&(IOTTY|IONOSEEK))) + sh_popcontext(&buff); + if(was_write) + sfset(iop,SF_WRITE,1); + if(!was_share) + sfset(iop,SF_SHARE,0); + nv_close(np); + if((flags>>D_FLAG) && (shp->fdstatus[fd]&IOTTY)) + tty_cooked(fd); + if(flags&S_FLAG) + hist_flush(shp->hist_ptr); + if(jmpval > 1) + siglongjmp(*shp->jmplist,jmpval); + return(jmpval); +} + diff --git a/usr/src/lib/libshell/common/bltins/shiocmd_solaris.c b/usr/src/lib/libshell/common/bltins/shiocmd_solaris.c new file mode 100644 index 0000000000..25bced42a2 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/shiocmd_solaris.c @@ -0,0 +1,1180 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <shell.h> +#include <stdio.h> +#include <option.h> +#include <stk.h> +#include <tm.h> +#include "name.h" +#undef nv_isnull +#ifndef SH_DICT +# define SH_DICT "libshell" +#endif +#include <poll.h> + +/* + * time formatting related +*/ +struct dctime +{ + Namfun_t fun; + Namval_t *format; + char buff[256]; /* Must be large enougth for |tmfmt()| */ +}; + +static char *get_time(Namval_t* np, Namfun_t* nfp) +{ + struct dctime *dp = (struct dctime*)nfp; + time_t t = nv_getn(np,nfp); + char *format = nv_getval(dp->format); + tmfmt(dp->buff,sizeof(dp->buff),format,(time_t*)0); + return(dp->buff); +} + +static void put_time(Namval_t* np, const char* val, int flag, Namfun_t* nfp) +{ + struct dctime *dp = (struct dctime*)nfp; + char *last; + if(val) + { + int32_t t; + if(flag&NV_INTEGER) + { + if(flag&NV_LONG) + t = *(Sfdouble_t*)val; + else + t = *(double*)val; + } + else + { + t = tmdate(val, &last, (time_t*)0); + if(*last) + errormsg(SH_DICT, ERROR_exit(1),"%s: invalid date/time string", val); + } + nv_putv(np, (char*)&t,NV_INTEGER, nfp); + } + else + { + nv_unset(dp->format); + free((void*)dp->format); + nv_putv(np, val, flag, nfp); + } +} + +static Namval_t *create_time(Namval_t *np, const char *name, int flags, Namfun_t *nfp) +{ + struct dctime *dp = (struct dctime*)nfp; + if(strcmp(name, "format")) + return((Namval_t*)0); + return(dp->format); +} + +static const Namdisc_t timedisc = +{ + sizeof(struct dctime), + put_time, + get_time, + 0, + 0, + create_time, +}; + + +static Namval_t *make_time(Namval_t* np) +{ + int offset = stktell(stkstd); + char *name = nv_name(np); + struct dctime *dp = newof(NULL,struct dctime,1,0); + if(!dp) + return((Namval_t*)0); + sfprintf(stkstd,"%s.format\0",name); + sfputc(stkstd,0); + dp->format = nv_search(stkptr(stkstd,offset),sh.var_tree,NV_ADD); + dp->fun.disc = &timedisc; + nv_stack(np,&dp->fun); + return(np); +} + +/* + * mode formatting related +*/ +static char *get_mode(Namval_t* np, Namfun_t* nfp) +{ + mode_t mode = nv_getn(np,nfp); + return(fmtperm(mode)); +} + +static void put_mode(Namval_t* np, const char* val, int flag, Namfun_t* nfp) +{ + if(val) + { + int32_t mode; + char *last; + if(flag&NV_INTEGER) + { + if(flag&NV_LONG) + mode = *(Sfdouble_t*)val; + else + mode = *(double*)val; + } + else + { + mode = strperm(val, &last,0); + if(*last) + errormsg(SH_DICT, ERROR_exit(1),"%s: invalid mode string", val); + } + nv_putv(np,(char*)&mode,NV_INTEGER,nfp); + } + else + nv_putv(np,val,flag,nfp); +} + +static const Namdisc_t modedisc = +{ + 0, + put_mode, + get_mode, +}; + +static Namval_t *make_mode(Namval_t* np) +{ + char *name = nv_name(np); + Namfun_t *nfp = newof(NULL,Namfun_t,1,0); + if(!nfp) + return((Namval_t*)0); + nfp->disc = &modedisc; + nv_stack(np,nfp); + return(np); +} + +/* + * field related typese and functions + */ +typedef struct _field_ +{ + char *name; /* field name */ + int flags; /* flags */ + short offset; /* offset of field into data */ + short size; /* size of field */ + Namval_t *(*make)(Namval_t*); /* discipline constructor */ +} Shfield_t; + +/* + * lookup field in field table + */ +static Shfield_t *sh_findfield(Shfield_t *ftable, int nelem, const char *name) +{ + Shfield_t *fp = ftable; + register int i,n; + register const char *cp; + for(cp=name; *cp; cp++) + { + if(*cp=='.') + break; + } + n = cp-name; + for(i=0; i < nelem; i++,fp++) + { + if(memcmp(fp->name,name,n)==0 && fp->name[n]==0) + return(fp); + } + return(0); +} + +/* + * class types and functions + */ + +typedef struct _class_ +{ + int nelem; /* number of elements */ + int dsize; /* size for data structure */ + Shfield_t *fields; /* field description table */ +} Shclass_t; + +struct dcclass +{ + Namfun_t fun; + Shclass_t sclass; +}; + +static Namval_t *sh_newnode(register Shfield_t *fp, Namval_t *np) +{ + char *val = np->nvalue + fp->offset; + char *name = nv_name(np); + register Namval_t *nq; + int offset = stktell(stkstd); + sfprintf(stkstd,"%s.%s\0",name,fp->name); + sfputc(stkstd,0); + nq = nv_search(stkptr(stkstd,offset),sh.var_tree,NV_ADD); + if(fp->size<0) + val = *(char**)val; + nv_putval(nq,val,fp->flags|NV_NOFREE); + if(fp->make) + (*fp->make)(nq); + return(nq); +} + +static Namval_t *fieldcreate(Namval_t *np, const char *name, int flags, Namfun_t *nfp) +{ + struct dcclass *dcp = (struct dcclass*)nfp; + Shclass_t *sp = &dcp->sclass; + Shfield_t *fp = sh_findfield(sp->fields,sp->nelem,name); + Namval_t *nq,**nodes = (Namval_t**)(dcp+1); + int n = fp-sp->fields; + int len = strlen(fp->name); + void *data = (void*)np->nvalue; + if(!(nq=nodes[n])) + { + nodes[n] = nq = sh_newnode(fp,np); + nfp->last = ""; + } + if(name[len]==0) + return(nq); + return(nq); +} + +static void genvalue(Sfio_t *out, Shclass_t *sp, int indent, Namval_t *npar) +{ + Shfield_t *fp = sp->fields; + Namval_t *np, **nodes= (Namval_t**)(sp+1); + register int i,isarray; + if(out) + { + sfwrite(out,"(\n",2); + indent++; + } + for(i=0; i < sp->nelem; i++,fp++) + { +#if 0 + /* handle recursive case */ +#endif + if(!(np=nodes[i]) && out) + np = sh_newnode(fp,npar); + if(np) + { + isarray=0; + if(nv_isattr(np,NV_ARRAY)) + { + isarray=1; + if(array_elem(nv_arrayptr(np))==0) + isarray=2; + else + nv_putsub(np,(char*)0,ARRAY_SCAN); + } + sfnputc(out,'\t',indent); + sfputr(out,fp->name,(isarray==2?'\n':'=')); + if(isarray) + { + if(isarray==2) + continue; + sfwrite(out,"(\n",2); + sfnputc(out,'\t',++indent); + } + while(1) + { + char *fmtq; + if(isarray) + { + sfprintf(out,"[%s]",sh_fmtq(nv_getsub(np))); + sfputc(out,'='); + } + if(!(fmtq=nv_getval(np)) || !(fmtq=sh_fmtq(fmtq))) + fmtq = ""; + sfputr(out,fmtq,'\n'); + if(!nv_nextsub(np)) + break; + sfnputc(out,'\t',indent); + } + if(isarray) + { + sfnputc(out,'\t',--indent); + sfwrite(out,")\n",2); + } + } + } + if(out) + { + if(indent>1) + sfnputc(out,'\t',indent-1); + sfputc(out,')'); + } +} + +static char *walk_class(register Namval_t *np, int dlete, struct dcclass *dcp) +{ + static Sfio_t *out; + Sfio_t *outfile; + int savtop = stktell(stkstd); + char *savptr = stkfreeze(stkstd,0); + if(dlete) + outfile = 0; + else if(!(outfile=out)) + outfile = out = sfnew((Sfio_t*)0,(char*)0,-1,-1,SF_WRITE|SF_STRING); + else + sfseek(outfile,0L,SEEK_SET); + genvalue(outfile,&dcp->sclass,0,np); + stkset(stkstd,savptr,savtop); + if(!outfile) + return((char*)0); + sfputc(out,0); + return((char*)out->_data); +} + +static char *get_classval(Namval_t* np, Namfun_t* nfp) +{ + return(walk_class(np,0,(struct dcclass *)nfp)); +} + +static void put_classval(Namval_t* np, const char* val, int flag, Namfun_t* nfp) +{ + walk_class(np,1,(struct dcclass *)nfp); + if(nfp = nv_stack(np,(Namfun_t*)0)) + { + free((void*)nfp); + if(np->nvalue && !nv_isattr(np,NV_NOFREE)) + free((void*)np->nvalue); + } + if(val) + nv_putval(np,val,flag); +} + +static const Namdisc_t classdisc = +{ + sizeof(struct dcclass), + put_classval, + get_classval, + 0, + 0, + fieldcreate +}; + +static int mkclass(Namval_t *np, Shclass_t *sp) +{ + struct dcclass *tcp = newof(NULL,struct dcclass,1,sp->nelem*sizeof(Namval_t*)); + if(!tcp) + return(0); + memset((void*)(tcp+1),0,sp->nelem*sizeof(Namval_t*)); + tcp->fun.disc = &classdisc; + tcp->sclass = *sp; + np->nvalue = (char*)calloc(sp->dsize,1); + nv_stack(np,&tcp->fun); + return(1); +} + +/* + * ====================from here down is file class specific + */ +static struct stat *Sp; + +struct filedata +{ + struct stat statb; + int fd; + char *name; +}; + +static Shfield_t filefield[] = +{ + { "atime", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_atime), sizeof(Sp->st_atime), make_time}, + { "ctime", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_ctime), sizeof(Sp->st_ctime), make_time}, + { "dev", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_dev),sizeof(Sp->st_dev)}, + { "fd", NV_INTEGER|NV_RDONLY, offsetof(struct filedata,fd), sizeof(int)}, + { "gid", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_gid), sizeof(Sp->st_gid)}, + { "ino", NV_LONG|NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_ino), sizeof(Sp->st_ino)}, + { "mode", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_mode), sizeof(Sp->st_mode), make_mode}, + { "mtime", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_mtime), sizeof(Sp->st_mtime), make_time}, + { "name", NV_RDONLY, offsetof(struct filedata,name), -1 }, + { "nlink", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_nlink), sizeof(Sp->st_nlink)}, + { "size", NV_LONG|NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_size), sizeof(Sp->st_size)}, + { "uid", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_uid), sizeof(Sp->st_uid)} +}; + +static Shclass_t Fileclass = +{ + sizeof(filefield)/sizeof(*filefield), + sizeof(struct filedata), + filefield +}; + + +#define letterbit(bit) (1<<((bit)-'a')) + +static const char sh_optopen[] = +"[-?\n@(#)$Id: open (AT&T Labs Research) 2007-05-07 $\n]" +"[-author?David Korn <dgk@research.att.com>]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? open - create a shell variable correspnding to a file]" +"[+DESCRIPTION?\bopen\b creates the compound variable \avar\a correspinding " + "to the file given by the pathname \afile\a. The elements of \avar\a " + "are the names of elements in the \astat\a structure with the \bst_\b " + "prefix removed.]" +"[+?\afile\a is opened (based on \b-r\b and/or \b-w\b) and the variable " + "\avar\a\b.fd\b is the file descriptor.]" +"[a:append?Open for append.]" +"[b:binary?Open in binary mode" +#ifndef O_BINARY + " (not supported/ignored on this platform)" +#endif + ".]" +"[t:text?Open in text mode" +#ifndef O_TEXT + " (not supported/ignored on this platform)" +#endif + ".]" +"[c:create?Open for create.]" +"[i:inherit?Open without the close-on-exec bit set.]" +"[I:noinherit?Open with the close-on-exec bit set.]" +"[r:read?Open with read access.]" +"[w:write?Open with write access.]" +"[m:mode]:[mode:=rwrwrw?Open with access mode \amode\a.]" +"[x:exclusive?Open exclusive.]" + +"[N:nofollow?If the path names a symbolic link, open fails with ELOOP " +#ifndef O_NOFOLLOW + " (not supported/ignored on this platform)" +#endif + ".]" +"[S:sync?Write I/O operations on the file descriptor complete as " + "defined by synchronized I/O file integrity completion" +#ifndef O_SYNC + " (not supported/ignored on this platform)" +#endif + ".]" +"[T:trunc?If the file exists and is a regular file, and the file " + "is successfully opened read/write or write-only, its length is " + "truncated to 0 and the mode and owner are unchanged. It " + "has no effect on FIFO special files or terminal device " + "files. Its effect on other file types is " + "implementation-dependent. The result of using -T " + "with read-only files is undefined" +#ifndef O_TRUNC + " (not supported/ignored on this platform)" +#endif + ".]" +"\n" +"\nvar file\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\btmpfile\b(1),\bdup\b(1),\bclose\b(1),\bstat\b(1),\bpoll\b(1),\bstat\b(2)]" +; + + +extern int b_open(int argc, char *argv[], void *extra) +{ + register Namval_t *np; + register int n,oflag=0; + Shell_t *shp = (Shell_t*)extra; + struct filedata *fdp; + mode_t mode = 0666; + long flags = 0; + int fd = -1; + char *arg; + + while (n = optget(argv, sh_optopen)) switch (n) + { + case 'r': + case 'w': + case 'i': + flags |= letterbit(n); + break; + case 'I': + flags &= ~(letterbit('i')); + break; + case 'b': +#ifdef O_BINARY + oflag |= O_BINARY; +#endif + break; + case 't': +#ifdef O_TEXT + oflag |= O_TEXT; +#endif + break; + case 'N': +#ifdef O_NOFOLLOW + oflag |= O_NOFOLLOW; +#endif + break; + case 'T': +#ifdef O_TRUNC + oflag |= O_TRUNC; +#endif + break; + case 'x': + oflag |= O_EXCL; + break; + case 'c': + oflag |= O_CREAT; + break; + case 'a': + oflag |= O_APPEND; + break; + case 'S': +#ifdef O_SYNC + oflag |= O_SYNC; +#endif + break; + case 'm': + mode = strperm(arg = opt_info.arg, &opt_info.arg, mode); + if (*opt_info.arg) + errormsg(SH_DICT, ERROR_system(1), "%s: invalid mode", arg); + break; + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=2 || !(flags&(letterbit('r')|letterbit('w')))) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + if(flags&letterbit('r')) + { + if(flags&letterbit('w')) + oflag |= O_RDWR; + else + oflag |= O_RDONLY; + } + else if(flags&letterbit('w')) + oflag |= O_WRONLY; + + fd = sh_open(argv[1], oflag, mode); + if(fd<0) + errormsg(SH_DICT, ERROR_system(1), "%s: open failed", argv[1]); + + if(!(flags&letterbit('i'))) + fcntl(fd, F_SETFL, 0); + + np = nv_open(argv[0], shp->var_tree, NV_ARRAY|NV_VARNAME|NV_NOASSIGN); + if(!nv_isnull(np)) + nv_unset(np); + mkclass(np, &Fileclass); + fdp = (struct filedata*)np->nvalue; + fstat(fd, &fdp->statb); + fdp->fd = fd; + fdp->name = strdup(argv[1]); + return(0); +} + +static const char sh_optclose[] = +"[-?\n@(#)$Id: close (AT&T Labs Research) 2007-04-21 $\n]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? close - close a file descriptor]" +"[+DESCRIPTION?\bclose\b closes the file descriptor specified by fd.]" +"\n" +"\nfd\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bopen\b(1),\bdup\b(1),\btmpfile\b(1),\bpoll\b(1),\bstat\b(1)]" +; + +extern int b_close(int argc, char *argv[], void *extra) +{ + register int n=0; + int fd = -1; + + while (n = optget(argv, sh_optclose)) switch (n) + { + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=1) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + errno = 0; + fd = strtol(argv[0], (char **)NULL, 0); + if (errno != 0 || fd < 0) + errormsg(SH_DICT, ERROR_system(1), "%s: invalid descriptor", argv[0]); + + n = sh_close(fd); + + if (n < 0) + errormsg(SH_DICT, ERROR_system(1), "%s: close error", argv[0]); + + return(n==0?0:1); +} + + +static const char sh_opttmpfile[] = +"[-?\n@(#)$Id: tmpfile (AT&T Labs Research) 2007-05-07 $\n]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? tmpfile - create a shell variable correspnding to a temporary file]" +"[+DESCRIPTION?\btmpfile\b creates the compound variable \avar\a correspinding " + "to a temporary file. The elements of \avar\a " + "are the names of elements in the \astat\a structure with the \bst_\b " + "prefix removed.]" +"[i:inherit?Open without the close-on-exec bit set.]" +"[I:noinherit?Open with the close-on-exec bit set.]" +"\n" +"\nvar\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bopen\b(1),\bdup\b(1),\bclose\b(1),\bstat\b(1),\bstat\b(2)]" +; + + +extern int b_tmpfile(int argc, char *argv[], void *extra) +{ + register Namval_t *np; + register int n; + Shell_t *shp = (Shell_t*)extra; + struct filedata *fdp; + int inherit = 0; + FILE *file = NULL; + int ffd, fd = -1; + while (n = optget(argv, sh_opttmpfile)) switch (n) + { + case 'i': + inherit = 1; + break; + case 'I': + inherit = 0; + break; + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=1) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + file = tmpfile(); + if(!file) + errormsg(SH_DICT, ERROR_system(1), "%s: tmpfile failed", argv[1]); + ffd = fileno(file); + fd = sh_dup(ffd); + if(fd<0) + errormsg(SH_DICT, ERROR_system(1), "%s: tmpfile failed", argv[1]); + fclose(file); + + if(!inherit) + fcntl(fd, F_SETFL, 0); + + np = nv_open(argv[0], shp->var_tree, NV_ARRAY|NV_VARNAME|NV_NOASSIGN); + if(!nv_isnull(np)) + nv_unset(np); + mkclass(np,&Fileclass); + fdp = (struct filedata*)np->nvalue; + + fstat(fd, &fdp->statb); + fdp->fd = fd; + fdp->name = NULL; + return(0); +} + +static const char sh_optdup[] = +"[-?\n@(#)$Id: dup (AT&T Labs Research) 2007-05-07 $\n]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? dup - duplicate an open file descriptor]" +"[+DESCRIPTION?The \bdup\b commands returns a new file descriptor having the " + "following in common with the original open file descriptor " + "fd: same open file (or pipe), same file pointer (that is, both file descriptors " + "share one file pointer) same access mode (read, write or read/write). " + "The file descriptor returned is the lowest one available.]" +"[i:inherit?Open without the close-on-exec bit set.]" +"[I:noinherit?Open with the close-on-exec bit set.]" +"\n" +"\nvar fd\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bopen\b(1),\btmpfile\b(1),\bclose\b(1),\bpoll\b(1),\bstat\b(1)]" +; + + +extern int b_dup(int argc, char *argv[], void *extra) +{ + register Namval_t *np; + register int n; + Shell_t *shp = (Shell_t*)extra; + struct filedata *fdp; + int inherit = 0; + int ffd, fd = -1; + while (n = optget(argv, sh_optdup)) switch (n) + { + case 'i': + inherit = 1; + break; + case 'I': + inherit = 0; + break; + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=2) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + errno = 0; + ffd = strtol(argv[1], (char **)NULL, 0); + if (errno != 0 || ffd < 0) + errormsg(SH_DICT, ERROR_system(1), "%s: invalid fd", argv[1]); + + fd = sh_dup(ffd); + if(fd<0) + errormsg(SH_DICT, ERROR_system(1), "%s: dup failed", argv[1]); + + if(!inherit) + fcntl(fd,F_SETFL,0); + + np = nv_open(argv[0],shp->var_tree,NV_ARRAY|NV_VARNAME|NV_NOASSIGN); + if(!nv_isnull(np)) + nv_unset(np); + mkclass(np, &Fileclass); + fdp = (struct filedata*)np->nvalue; + + fstat(fd, &fdp->statb); + fdp->fd = fd; + fdp->name = NULL; + return(0); +} + +static const char sh_optstat[] = +"[-?\n@(#)$Id: stat (AT&T Labs Research) 2007-05-07 $\n]" +"[-author?David Korn <dgk@research.att.com>]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? stat - get file status]" +"[+DESCRIPTION?\bstat\b creates the compound variable \avar\a correspinding " + "to the file given by the pathname \afile\a. The elements of \avar\a " + "are the names of elements in the \astat\a structure with the \bst_\b " + "prefix removed.]" +"[l:lstat?If the the named file is a symbolic link returns information about " + "the link itself.]" +"\n" +"\nvar file\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bopen\b(1),\btmpfile\b(1),\bdup\b(1),\bclose\b(1),\bpoll\b(1),\bstat\b(2),\blstat\b(2)]" +; + + +extern int b_stat(int argc, char *argv[], void *extra) +{ + register Namval_t *np; + register int n; + Shell_t *shp = (Shell_t*)extra; + struct filedata *fdp; + long flags = 0; + struct stat statb; + while (n = optget(argv, sh_optstat)) switch (n) + { + case 'l': + flags |= letterbit(n); + break; + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=2) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + if(flags&letterbit('l')) + { + if(lstat(argv[1], &statb) < 0) + errormsg(SH_DICT, ERROR_system(1), "%s: stat failed", argv[1]); + } + else + { + if(stat(argv[1], &statb) < 0) + errormsg(SH_DICT, ERROR_system(1), "%s: stat failed", argv[1]); + + } + + np = nv_open(argv[0],shp->var_tree,NV_ARRAY|NV_VARNAME|NV_NOASSIGN); + if(!nv_isnull(np)) + nv_unset(np); + mkclass(np,&Fileclass); + fdp = (struct filedata*)np->nvalue; + fdp->statb = statb; + fdp->fd = -1; + fdp->name = strdup(argv[1]); + return(0); +} + +static const char sh_optpoll[] = +"[-?\n@(#)$Id: poll (AT&T Labs Research) 2007-05-07 $\n]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? poll - input/output multiplexing]" +"[+DESCRIPTION?The poll command provides applications with a mechanism " + "for multiplexing input/output over a set of file descriptors. " + "For each member of the array variable \bvar\b, " + "poll examines the given file descriptor in the subscript \b.fd\b " + "for the event(s) specified in the subscript \b.events\b." + "The poll command identifies those file descriptors on which an " + "application can read or write data, or on which certain events have " + "occurred.]" +"[+?The \bvar\b argument specifies the file descriptors to be examined " + "and the events of interest for each file descriptor. " + "It is a array of structured variables with one member for each open " + "file descriptor of interest. The array's members contain the following " + "subscripts:]{" + "[+?\b.fd\b # file descriptor]" + "[+?\b.events\b # requested events]" + "[+?\b.revents\b # returned event]" + "}" +"[+?The \bfd\b variable specifies an open file descriptor and the " + "\bevents\b and \brevents\b members are strings constructed from " + "a concaternation of the following event flags, seperated by '|':]" + "{ " + "[+POLLIN?Data other than high priority data may be " + "read without blocking. For STREAMS, this " + "flag is set in revents even if the message " + "is of zero length.]" + "[+POLLRDNORM?Normal data (priority band equals 0) may be " + "read without blocking. For STREAMS, this " + "flag is set in revents even if the message " + "is of zero length.]" + "[+POLLRDBAND?Data from a non-zero priority band may be " + "read without blocking. For STREAMS, this " + "flag is set in revents even if the message " + "is of zero length.]" + "[+POLLPRI?High priority data may be received without " + "blocking. For STREAMS, this flag is set in " + "revents even if the message is of zero " + "length.]" + "[+POLLOUT?Normal data (priority band equals 0) may be " + "written without blocking.]" + "[+POLLWRNORM?The same as POLLOUT.]" + "[+POLLWRBAND?Priority data (priority band > 0) may be " + "written. This event only examines bands " + "that have been written to at least once.]" + "[+POLLERR?An error has occurred on the device or " + "stream. This flag is only valid in the " + "revents bitmask; it is not used in the " + "events member.]" + "[+POLLHUP?A hangup has occurred on the stream. This " + "event and POLLOUT are mutually exclusive; a " + "stream can never be writable if a hangup has " + "occurred. However, this event and POLLIN, " + ", POLLRDBAND, or POLLPRI are not " + "mutually exclusive. This flag is only valid " + "in the revents bitmask; it is not used in " + "the events member.]" + "[+POLLNVAL?The specified fd value does not belong to an " + "open file. This flag is only valid in the " + "revents member; it is not used in the events " + "member.]" + "}" +"]" + +"[+?If the value fd is less than 0, events is ignored and " + "revents is set to 0 in that entry on return from poll.]" + +"[+?The results of the poll query are stored in the revents " + "member in the \bvar\b structure. POLL*-strings are set in the \brevents\b " + "variable to indicate which of the requested events are true. " + "If none are true, the \brevents\b will be an empty string when " + "the poll command returns. The event flags " + "POLLHUP, POLLERR, and POLLNVAL are always set in \brevents\b " + "if the conditions they indicate are true; this occurs even " + "though these flags were not present in events.]" + +"[+?If none of the defined events have occurred on any selected " + "file descriptor, poll waits at least timeout milliseconds " + "for an event to occur on any of the selected file descriptors. " + "On a computer where millisecond timing accuracy is not " + "available, timeout is rounded up to the nearest legal value " + "available on that system. If the value timeout is 0, poll " + "returns immediately. If the value of timeout is -1, poll " + "blocks until a requested event occurs or until the call is " + "interrupted.]" + +"[+?The poll function supports regular files, terminal and " + "pseudo-terminal devices, STREAMS-based files, FIFOs and " + "pipes. The behavior of poll on elements of fds that refer " + "to other types of file is unspecified.]" + +"[+?The poll function supports sockets.]" + +"[+?A file descriptor for a socket that is listening for connections " + "will indicate that it is ready for reading, once connections " + "are available. A file descriptor for a socket that " + "is connecting asynchronously will indicate that it is ready " + "for writing, once a connection has been established.]" + +"[+?Regular files always poll TRUE for reading and writing.]" + +"[t:timeout]:[milliseconds?Timeout in milliseconds. If the value timeout is 0, " + "poll returns immediately. If the value of timeout is -1, poll " + "blocks until a requested event occurs or until the call is " + "interrupted.]" +"\n" +"\nvar\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bopen\b(1),\btmpfile\b(1),\bdup\b(1),\bclose\b(1),\bpoll\b(2)]" +; + +/* + * |mystpcpy| - like |strcpy()| but returns the end of the buffer + * + * Copy string s2 to s1. s1 must be large enough. + * return s1-1 (position of string terminator ('\0') in destnation buffer). + */ +static +char *mystpcpy(char *s1, const char *s2) +{ + while (*s1++ = *s2++) + ; + return (s1-1); +} + +static +Namval_t *nv_open_fmt(Dt_t *dict, int flags, const char *namefmt, ...) +{ + char varnamebuff[PATH_MAX]; + va_list ap; + + va_start(ap, namefmt); + vsnprintf(varnamebuff, sizeof(varnamebuff), namefmt, ap); + va_end(ap); + + return nv_open(varnamebuff, dict, flags); +} + +static +int poll_strtoevents(const char *str) +{ + int events = 0; + + if (strstr(str, "POLLIN")) events |= POLLIN; + if (strstr(str, "POLLRDNORM")) events |= POLLRDNORM; + if (strstr(str, "POLLRDBAND")) events |= POLLRDBAND; + if (strstr(str, "POLLPRI")) events |= POLLPRI; + if (strstr(str, "POLLOUT")) events |= POLLOUT; + if (strstr(str, "POLLWRNORM")) events |= POLLWRNORM; + if (strstr(str, "POLLWRBAND")) events |= POLLWRBAND; + if (strstr(str, "POLLERR")) events |= POLLERR; + if (strstr(str, "POLLHUP")) events |= POLLHUP; + if (strstr(str, "POLLNVAL")) events |= POLLNVAL; + + return events; +} + + +static +void poll_eventstostr(char *s, int events) +{ + *s='\0'; + if (!events) + return; + + if (events & POLLIN) s=mystpcpy(s, "POLLIN|"); + if (events & POLLRDNORM) s=mystpcpy(s, "POLLRDNORM|"); + if (events & POLLRDBAND) s=mystpcpy(s, "POLLRDBAND|"); + if (events & POLLPRI) s=mystpcpy(s, "POLLPRI|"); + if (events & POLLOUT) s=mystpcpy(s, "POLLOUT|"); + if (events & POLLWRNORM) s=mystpcpy(s, "POLLWRNORM|"); + if (events & POLLWRBAND) s=mystpcpy(s, "POLLWRBAND|"); + if (events & POLLERR) s=mystpcpy(s, "POLLERR|"); + if (events & POLLHUP) s=mystpcpy(s, "POLLHUP|"); + if (events & POLLNVAL) s=mystpcpy(s, "POLLNVAL|"); + + /* Remove trailling '|' */ + s--; + if(*s=='|') + *s='\0'; +} + +extern int b_poll(int argc, char *argv[], void *extra) +{ + register Namval_t *np; + register int n; + Shell_t *shp = (Shell_t*)extra; + char *varname; + int fd; +/* |BPOLL_MAX| needs to be larger than |OPEN_MAX| to make sure we + * can listen to different sets of events per fd. + */ +#define BPOLL_MAX 512 + struct pollfd pollfd[BPOLL_MAX]; + unsigned int numpollfd = 0; + int i; + char *s; + long timeout = -1; + char buff[256]; + + while (n = optget(argv, sh_optpoll)) switch (n) + { + case 't': + errno = 0; + timeout = strtol(opt_info.arg, (char **)NULL, 0); + if (errno != 0) + errormsg(SH_DICT, ERROR_system(1), "%s: invalid timeout", opt_info.arg); + break; + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=1) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + varname = argv[0]; + + for(i=0 ; i < BPOLL_MAX ; i++) + { + np = nv_open_fmt(shp->var_tree, NV_ARRAY|NV_VARNAME|NV_NOASSIGN|NV_NOFAIL|NV_NOADD, "%s[%d].fd", varname, i); + if (!np) + break; + fd = (int)nv_getnum(np); + if (fd < 0 || fd > OPEN_MAX) + errormsg(SH_DICT, ERROR_system(1), "poll: invalid pollfd fd"); + nv_close(np); + pollfd[i].fd = fd; + + np = nv_open_fmt(shp->var_tree, NV_ARRAY|NV_VARNAME|NV_NOASSIGN|NV_NOFAIL|NV_NOADD, "%s[%d].events", varname, i); + if (!s) + errormsg(SH_DICT, ERROR_system(1), "poll: missing pollfd events"); + + s = nv_getval(np); + if (!s) + errormsg(SH_DICT, ERROR_system(1), "poll: missing pollfd events value"); + pollfd[i].events = poll_strtoevents(s); + nv_close(np); + + pollfd[i].revents = 0; + + numpollfd++; + } + + if (i == BPOLL_MAX) + errormsg(SH_DICT, ERROR_system(1), "poll: cannot handle more than %d entries.", BPOLL_MAX); + + n = poll(pollfd, numpollfd, timeout); + /* FixMe: EGAIN and EINTR may require extra handling */ + if (n < 0) + errormsg(SH_DICT, ERROR_system(1), "poll: failure"); + + for(i=0 ; i < numpollfd ; i++) + { + np = nv_open_fmt(shp->var_tree, NV_ARRAY|NV_VARNAME|NV_NOASSIGN|NV_NOFAIL, "%s[%d].revents", varname, i); + if (!np) + errormsg(SH_DICT, ERROR_system(1), "poll: couldn't create pollfd %s[%d].revents", varname, i); + + poll_eventstostr(buff, pollfd[i].revents); + + nv_putval(np, buff, 0); + nv_close(np); + } + + return(0); +} + +static const char sh_optrewind[] = +"[-?\n@(#)$Id: rewind (AT&T Labs Research) 2007-05-07 $\n]" +"[-author?Roland Mainz <roland.mainz@nrubsig.org>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? rewind - reset file position indicator in a stream]" +"[+DESCRIPTION?The \brewind\b command will move the file pointer of fd to position 0.]" +"\n" +"\nfd\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bopen\b(1),\btmpfile\b(1),\bdup\b(1),\bclose\b(1),\bstat\b(1),\bstat\b(2)]" +; + + +extern int b_rewind(int argc, char *argv[], void *extra) +{ + Shell_t *shp = (Shell_t*)extra; + int fd = -1; + register int n; + while (n = optget(argv, sh_optrewind)) switch (n) + { + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=1) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + errno = 0; + fd = strtol(argv[0], (char **)NULL, 0); + if (errno != 0 || fd < 0) + errormsg(SH_DICT, ERROR_system(1), "%s: invalid fd", argv[0]); + + if (sh_seek(fd, 0, SEEK_SET) == (off_t)-1) + errormsg(SH_DICT, ERROR_system(1), "seek error"); + + return(0); +} + diff --git a/usr/src/lib/libshell/common/bltins/shopen.c b/usr/src/lib/libshell/common/bltins/shopen.c new file mode 100644 index 0000000000..6a7a85a990 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/shopen.c @@ -0,0 +1,533 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +static const char id[] = "\n@(#)$Id: open (AT&T Research) 1998-07-07 $\0\n"; + +#include <shell.h> +#include <option.h> +#include <stk.h> +#include <tm.h> +#ifndef SH_DICT +# define SH_DICT "libshell" +#endif + +/* + * time formatting related +*/ +struct dctime +{ + Namfun_t fun; + Namval_t *format; +}; + +static char *get_time(Namval_t* np, Namfun_t* nfp) +{ + static char buff[256]; + struct dctime *dp = (struct dctime*)nfp; + time_t t = nv_getn(np,nfp); + char *format = nv_getval(dp->format); + tmfmt(buff,sizeof(buff),format,(time_t*)0); + return(buff); +} + +static void put_time(Namval_t* np, const char* val, int flag, Namfun_t* nfp) +{ + struct dctime *dp = (struct dctime*)nfp; + char *last; + if(val) + { + int32_t t; + if(flag&NV_INTEGER) + { + if(flag&NV_LONG) + t = *(Sfdouble_t*)val; + else + t = *(double*)val; + } + else + { + t = tmdate(val, &last, (time_t*)0); + if(*last) + errormsg(SH_DICT,ERROR_exit(1),"%s: invalid date/time string",val); + } + nv_putv(np,(char*)&t,NV_INTEGER,nfp); + } + else + { + nv_unset(dp->format); + free((void*)dp->format); + nv_putv(np,val,flag,nfp); + } +} + +static Namval_t *create_time(Namval_t *np, const char *name, int flags, Namfun_t *nfp) +{ + struct dctime *dp = (struct dctime*)nfp; + if(strcmp(name,"format")) + return((Namval_t*)0); + return(dp->format); +} + +static const Namdisc_t timedisc = +{ + sizeof(struct dctime), + put_time, + get_time, + 0, + 0, + create_time, +}; + + +static Namval_t *make_time(Namval_t* np) +{ + int offset = stktell(stkstd); + char *name = nv_name(np); + struct dctime *dp = newof(NULL,struct dctime,1,0); + if(!dp) + return((Namval_t*)0); + sfprintf(stkstd,"%s.format\0",name); + sfputc(stkstd,0); + dp->format = nv_search(stkptr(stkstd,offset),sh.var_tree,NV_ADD); + dp->fun.disc = &timedisc; + nv_stack(np,&dp->fun); + return(np); +} + +/* + * mode formatting related +*/ +static char *get_mode(Namval_t* np, Namfun_t* nfp) +{ + mode_t mode = nv_getn(np,nfp); + return(fmtperm(mode)); +} + +static void put_mode(Namval_t* np, const char* val, int flag, Namfun_t* nfp) +{ + if(val) + { + int32_t mode; + char *last; + if(flag&NV_INTEGER) + { + if(flag&NV_LONG) + mode = *(Sfdouble_t*)val; + else + mode = *(double*)val; + } + else + { + mode = strperm(val, &last,0); + if(*last) + errormsg(SH_DICT,ERROR_exit(1),"%s: invalid mode string",val); + } + nv_putv(np,(char*)&mode,NV_INTEGER,nfp); + } + else + nv_putv(np,val,flag,nfp); +} + +static const Namdisc_t modedisc = +{ + 0, + put_mode, + get_mode, +}; + +static Namval_t *make_mode(Namval_t* np) +{ + char *name = nv_name(np); + Namfun_t *nfp = newof(NULL,Namfun_t,1,0); + if(!nfp) + return((Namval_t*)0); + nfp->disc = &modedisc; + nv_stack(np,nfp); + return(np); +} + +/* + * field related typese and functions + */ +typedef struct _field_ +{ + char *name; /* field name */ + int flags; /* flags */ + short offset; /* offset of field into data */ + short size; /* size of field */ + Namval_t *(*make)(Namval_t*); /* discipline constructor */ +} Shfield_t; + +/* + * lookup field in field table + */ +static Shfield_t *sh_findfield(Shfield_t *ftable, int nelem, const char *name) +{ + Shfield_t *fp = ftable; + register int i,n; + register const char *cp; + for(cp=name; *cp; cp++) + { + if(*cp=='.') + break; + } + n = cp-name; + for(i=0; i < nelem; i++,fp++) + { + if(memcmp(fp->name,name,n)==0 && fp->name[n]==0) + return(fp); + } + return(0); +} + +/* + * class types and functions + */ + +typedef struct _class_ +{ + int nelem; /* number of elements */ + int dsize; /* size for data structure */ + Shfield_t *fields; /* field description table */ +} Shclass_t; + +struct dcclass +{ + Namfun_t fun; + Shclass_t sclass; +}; + +static Namval_t *sh_newnode(register Shfield_t *fp, Namval_t *np) +{ + char *val = np->nvalue + fp->offset; + char *name = nv_name(np); + register Namval_t *nq; + int offset = stktell(stkstd); + sfprintf(stkstd,"%s.%s\0",name,fp->name); + sfputc(stkstd,0); + nq = nv_search(stkptr(stkstd,offset),sh.var_tree,NV_ADD); + if(fp->size<0) + val = *(char**)val; + nv_putval(nq,val,fp->flags|NV_NOFREE); + if(fp->make) + (*fp->make)(nq); + return(nq); +} + +static Namval_t *fieldcreate(Namval_t *np, const char *name, int flags, Namfun_t *nfp) +{ + struct dcclass *dcp = (struct dcclass*)nfp; + Shclass_t *sp = &dcp->sclass; + Shfield_t *fp = sh_findfield(sp->fields,sp->nelem,name); + Namval_t *nq,**nodes = (Namval_t**)(dcp+1); + int n = fp-sp->fields; + int len = strlen(fp->name); + void *data = (void*)np->nvalue; + if(!(nq=nodes[n])) + { + nodes[n] = nq = sh_newnode(fp,np); + nfp->last = ""; + } + if(name[len]==0) + return(nq); + return(nq); +} + +static void genvalue(Sfio_t *out, Shclass_t *sp, int indent, Namval_t *npar) +{ + Shfield_t *fp = sp->fields; + Namval_t *np, **nodes= (Namval_t**)(sp+1); + register int i,isarray; + if(out) + { + sfwrite(out,"(\n",2); + indent++; + } + for(i=0; i < sp->nelem; i++,fp++) + { +#if 0 + /* handle recursive case */ +#endif + if(!(np=nodes[i]) && out) + np = sh_newnode(fp,npar); + if(np) + { + isarray=0; + if(nv_isattr(np,NV_ARRAY)) + { + isarray=1; + if(array_elem(nv_arrayptr(np))==0) + isarray=2; + else + nv_putsub(np,(char*)0,ARRAY_SCAN); + } + sfnputc(out,'\t',indent); + sfputr(out,fp->name,(isarray==2?'\n':'=')); + if(isarray) + { + if(isarray==2) + continue; + sfwrite(out,"(\n",2); + sfnputc(out,'\t',++indent); + } + while(1) + { + char *fmtq; + if(isarray) + { + sfprintf(out,"[%s]",sh_fmtq(nv_getsub(np))); + sfputc(out,'='); + } + if(!(fmtq=nv_getval(np)) || !(fmtq=sh_fmtq(fmtq))) + fmtq = ""; + sfputr(out,fmtq,'\n'); + if(!nv_nextsub(np)) + break; + sfnputc(out,'\t',indent); + } + if(isarray) + { + sfnputc(out,'\t',--indent); + sfwrite(out,")\n",2); + } + } + } + if(out) + { + if(indent>1) + sfnputc(out,'\t',indent-1); + sfputc(out,')'); + } +} + +static char *walk_class(register Namval_t *np, int dlete, struct dcclass *dcp) +{ + static Sfio_t *out; + Sfio_t *outfile; + int savtop = stktell(stkstd); + char *savptr = stkfreeze(stkstd,0); + if(dlete) + outfile = 0; + else if(!(outfile=out)) + outfile = out = sfnew((Sfio_t*)0,(char*)0,-1,-1,SF_WRITE|SF_STRING); + else + sfseek(outfile,0L,SEEK_SET); + genvalue(outfile,&dcp->sclass,0,np); + stkset(stkstd,savptr,savtop); + if(!outfile) + return((char*)0); + sfputc(out,0); + return((char*)out->_data); +} + +static char *get_classval(Namval_t* np, Namfun_t* nfp) +{ + return(walk_class(np,0,(struct dcclass *)nfp)); +} + +static void put_classval(Namval_t* np, const char* val, int flag, Namfun_t* nfp) +{ + walk_class(np,1,(struct dcclass *)nfp); + if(nfp = nv_stack(np,(Namfun_t*)0)) + { + free((void*)nfp); + if(np->nvalue && !nv_isattr(np,NV_NOFREE)) + free((void*)np->nvalue); + } + if(val) + nv_putval(np,val,flag); +} + +static const Namdisc_t classdisc = +{ + sizeof(struct dcclass), + put_classval, + get_classval, + 0, + 0, + fieldcreate +}; + +static int mkclass(Namval_t *np, Shclass_t *sp) +{ + struct dcclass *tcp = newof(NULL,struct dcclass,1,sp->nelem*sizeof(Namval_t*)); + if(!tcp) + return(0); + memset((void*)(tcp+1),0,sp->nelem*sizeof(Namval_t*)); + tcp->fun.disc = &classdisc; + tcp->sclass = *sp; + np->nvalue = (char*)calloc(sp->dsize,1); + nv_stack(np,&tcp->fun); + return(1); +} + +/* + * ====================from here down is file class specific + */ +static struct stat *Sp; + +struct filedata +{ + struct stat statb; + int fd; + char *name; +}; + +static Shfield_t filefield[] = +{ + { "atime", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_atime), sizeof(Sp->st_atime), make_time}, + { "ctime", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_ctime), sizeof(Sp->st_ctime), make_time}, + { "dev", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_dev),sizeof(Sp->st_dev)}, + { "fd", NV_INTEGER|NV_RDONLY, offsetof(struct filedata,fd), sizeof(int)}, + { "gid", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_gid), sizeof(Sp->st_gid)}, + { "ino", NV_LONG|NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_ino), sizeof(Sp->st_ino)}, + { "mode", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_mode), sizeof(Sp->st_mode), make_mode}, + { "mtime", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_mtime), sizeof(Sp->st_mtime), make_time}, + { "name", NV_RDONLY, offsetof(struct filedata,name), -1 }, + { "nlink", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_nlink), sizeof(Sp->st_nlink)}, + { "size", NV_LONG|NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_size), sizeof(Sp->st_size)}, + { "uid", NV_INTEGER|NV_RDONLY, offsetof(struct stat,st_uid), sizeof(Sp->st_uid)} +}; + +static Shclass_t Fileclass = +{ + sizeof(filefield)/sizeof(*filefield), + sizeof(struct filedata), + filefield +}; + + +#define letterbit(bit) (1<<((bit)-'a')) + +static const char sh_optopen[] = +"[-?\n@(#)$Id: open (AT&T Labs Research) 2007-03-11 $\n]" +"[-author?David Korn <dgk@research.att.com>]" +"[-license?http://www.opensource.org/licenses/cpl1.0.txt]" +"[+NAME? open - create a shell variable correspnding to a file]" +"[+DESCRIPTION?\bopen\b creates the compound variable \avar\a correspinding " + "to the file given by the pathname \afile\a. The elements of \avar\a " + "are the names of elements in the \astat\a structure with the \bst_\b " + "prefix removed.]" +"[+?If the \b-r\b and/or \b-w\b mode is specified, then \afile\a is opened and " + "the variable \avar\a\b.fd\b is the file descriptor.]" +"[a:append?Open for append.]" +"[b:binary?Open in binary mode.]" +"[c:create?Open for create.]" +"[i:inherit?Open without the close-on-exec bit set.]" +"[r:read?Open with read access.]" +"[w:write?Open with write access.]" +"[m:mode]:[mode:=rwrwrw?Open with access mode \amode\a.]" +"[x:exclusive?Open exclusive.]" +"\n" +"\nvar file\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bstat\b(2)]" +; + + +extern int b_open(int argc, char *argv[], void *extra) +{ + register Namval_t *np; + register int n,oflag=0; + Shell_t *shp = (Shell_t*)extra; + struct filedata *fdp; + struct stat statb; + mode_t mode = 0666; + long flags = 0; + int fd = -1; + while (n = optget(argv, sh_optopen)) switch (n) + { + case 'r': + case 'i': + case 'w': + flags |= letterbit(n); + break; + case 'b': +#ifdef O_BINARY + oflag |= O_BINARY; +#endif + break; + case 't': +#ifdef O_TEXT + oflag |= O_TEXT; +#endif + break; + case 'x': + oflag |= O_EXCL; + break; + case 'c': + oflag |= O_CREAT; + break; + case 'a': + oflag |= O_APPEND; + break; + case 'm': + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=2) + errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0)); + if(!(flags&(letterbit('r')|letterbit('w')))) + { + if(stat(argv[1],&statb)<0) + errormsg(SH_DICT,ERROR_system(1),"%s: open failed",argv[1]); + } + else + { + if(flags&letterbit('r')) + { + if(flags&letterbit('w')) + oflag |= O_RDWR; + else + oflag |= O_RDONLY; + } + else if(flags&letterbit('w')) + oflag |= O_WRONLY; + fd = open(argv[1],oflag,mode); + if(fd<0) + errormsg(SH_DICT,ERROR_system(1),"%s: open failed",argv[1]); + } + if(!(flags&letterbit('i'))) + fcntl(fd,F_SETFL,0); + np = nv_open(argv[0],shp->var_tree,NV_ARRAY|NV_VARNAME|NV_NOASSIGN); + if(!nv_isnull(np)) + nv_unset(np); + mkclass(np,&Fileclass); + fdp = (struct filedata*)np->nvalue; + if(!(flags&(letterbit('r')|letterbit('w')))) + fdp->statb = statb; + else + fstat(fd,&fdp->statb); + fdp->fd = fd; + fdp->name = strdup(argv[1]); + return(0); +} diff --git a/usr/src/lib/libshell/common/bltins/sleep.c b/usr/src/lib/libshell/common/bltins/sleep.c new file mode 100644 index 0000000000..2f52ca651c --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/sleep.c @@ -0,0 +1,190 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * sleep delay + * + * David Korn + * AT&T Labs + * research!dgk + * + */ + +#define sleep ______sleep +#include "defs.h" +#undef sleep +#include <error.h> +#include <errno.h> +#include "builtins.h" +#include "FEATURE/time" +#include "FEATURE/poll" +#ifdef _NEXT_SOURCE +# define sleep _ast_sleep +#endif /* _NEXT_SOURCE */ +#ifdef _lib_poll_notimer +# undef _lib_poll +#endif /* _lib_poll_notimer */ + +int b_sleep(register int argc,char *argv[],void *extra) +{ + register char *cp; + register double d; + register Shell_t *shp = (Shell_t*)extra; + time_t tloc = 0; + while((argc = optget(argv,sh_optsleep))) switch(argc) + { + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors || !(cp= *argv) || !(strmatch(cp,e_numeric))) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if((d=strtod(cp, (char**)0)) > .10) + { + sfsync(shp->outpool); + time(&tloc); + tloc += (time_t)(d+.5); + } + while(1) + { + time_t now; + errno = 0; + shp->lastsig=0; + sh_delay(d); + if(tloc==0 || errno!=EINTR || shp->lastsig) + break; + sh_sigcheck(); + if(tloc < (now=time(NIL(time_t*)))) + break; + d = (double)(tloc-now); + if(shp->sigflag[SIGALRM]&SH_SIGTRAP) + sh_timetraps(); + } + return(0); +} + +static void completed(void * handle) +{ + char *expired = (char*)handle; + *expired = 1; +} + +unsigned int sleep(unsigned int sec) +{ + pid_t newpid, curpid=getpid(); + void *tp; + char expired = 0; + sh.lastsig = 0; + tp = (void*)sh_timeradd(1000*sec, 0, completed, (void*)&expired); + do + { + if(!sh.waitevent || (*sh.waitevent)(-1,-1L,0)==0) + pause(); + if(sh.sigflag[SIGALRM]&SH_SIGTRAP) + sh_timetraps(); + if((newpid=getpid()) != curpid) + { + curpid = newpid; + sh.lastsig = 0; + sh.trapnote &= ~SH_SIGSET; + if(expired) + expired = 0; + else + timerdel(tp); + tp = (void*)sh_timeradd(1000*sec, 0, completed, (void*)&expired); + } + } + while(!expired && sh.lastsig==0); + if(!expired) + timerdel(tp); + sh_sigcheck(); + return(0); +} + +/* + * delay execution for time <t> + */ + +void sh_delay(double t) +{ + register int n = (int)t; +#ifdef _lib_poll + struct pollfd fd; + if(t<=0) + return; + else if(n > 30) + { + sleep(n); + t -= n; + } + if(n=(int)(1000*t)) + { + if(!sh.waitevent || (*sh.waitevent)(-1,(long)n,0)==0) + poll(&fd,0,n); + } +#else +# if defined(_lib_select) && defined(_mem_tv_usec_timeval) + struct timeval timeloc; + if(t<=0) + return; + if(n=(int)(1000*t) && sh.waitevent && (*sh.waitevent)(-1,(long)n,0)) + return; + n = (int)t; + timeloc.tv_sec = n; + timeloc.tv_usec = 1000000*(t-(double)n); + select(0,(fd_set*)0,(fd_set*)0,(fd_set*)0,&timeloc); +# else +# ifdef _lib_select + /* for 9th edition machines */ + if(t<=0) + return; + if(n > 30) + { + sleep(n); + t -= n; + } + if(n=(int)(1000*t)) + { + if(!sh.waitevent || (*sh.waitevent)(-1,(long)n,0)==0) + select(0,(fd_set*)0,(fd_set*)0,n); + } +# else + struct tms tt; + if(t<=0) + return; + sleep(n); + t -= n; + if(t) + { + clock_t begin = times(&tt); + if(begin==0) + return; + t *= sh.lim.clk_tck; + n += (t+.5); + while((times(&tt)-begin) < n); + } +# endif +# endif +#endif /* _lib_poll */ +} diff --git a/usr/src/lib/libshell/common/bltins/test.c b/usr/src/lib/libshell/common/bltins/test.c new file mode 100644 index 0000000000..f21db8a75c --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/test.c @@ -0,0 +1,629 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * test expression + * [ expression ] + * + * David Korn + * AT&T Labs + * + */ + + +#include "defs.h" +#include <ctype.h> +#include <error.h> +#include <ls.h> +#include "io.h" +#include "terminal.h" +#include "test.h" +#include "builtins.h" +#include "FEATURE/externs" +#include "FEATURE/poll" +#include <tmx.h> + +#if !_lib_setregid +# undef _lib_setreuid +#endif /* _lib_setregid */ + +#ifdef S_ISSOCK +# if _pipe_socketpair +# if _socketpair_shutdown_mode +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)||S_ISSOCK((p)->st_mode)&&(p)->st_ino&&((p)->st_mode&(S_IRUSR|S_IWUSR))!=(S_IRUSR|S_IWUSR)) +# else +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)||S_ISSOCK((p)->st_mode)&&(p)->st_ino) +# endif +# else +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)||S_ISSOCK((p)->st_mode)&&(p)->st_ino) +# endif +# define isasock(f,p) (test_stat(f,p)>=0&&S_ISSOCK((p)->st_mode)) +#else +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)) +# define isasock(f,p) (0) +#endif + +#define permission(a,f) (sh_access(a,f)==0) +static time_t test_time(const char*, const char*); +static int test_stat(const char*, struct stat*); +static int test_mode(const char*); + +/* single char string compare */ +#define c_eq(a,c) (*a==c && *(a+1)==0) +/* two character string compare */ +#define c2_eq(a,c1,c2) (*a==c1 && *(a+1)==c2 && *(a+2)==0) + +struct test +{ + Shell_t *sh; + int ap; + int ac; + char **av; +}; + +static char *nxtarg(struct test*,int); +static int expr(struct test*,int); +static int e3(struct test*); + +static int test_strmatch(const char *str, const char *pat) +{ + int match[2*(MATCH_MAX+1)],n; + register int c, m=0; + register const char *cp=pat; + while(c = *cp++) + { + if(c=='(') + m++; + if(c=='\\' && *cp) + cp++; + } + if(m) + m++; + else + match[0] = 0; + if(m > elementsof(match)/2) + m = elementsof(match)/2; + n = strgrpmatch(str, pat, match, m, STR_MAXIMAL|STR_LEFT|STR_RIGHT); + if(m==0 && n==1) + match[1] = strlen(str); + if(n) + sh_setmatch(str, -1, n, match); + return(n); +} + +int b_test(int argc, char *argv[],void *extra) +{ + struct test tdata; + register char *cp = argv[0]; + register int not; + tdata.sh = (Shell_t*)extra; + tdata.av = argv; + tdata.ap = 1; + if(c_eq(cp,'[')) + { + cp = argv[--argc]; + if(!c_eq(cp, ']')) + errormsg(SH_DICT,ERROR_exit(2),e_missing,"']'"); + } + if(argc <= 1) + return(1); + cp = argv[1]; + not = c_eq(cp,'!'); + /* posix portion for test */ + switch(argc) + { + case 5: + if(!not) + break; + argv++; + /* fall through */ + case 4: + { + register int op = sh_lookup(cp=argv[2],shtab_testops); + if(op&TEST_BINOP) + break; + if(!op) + { + if(argc==5) + break; + if(not && cp[0]=='-' && cp[2]==0) + return(test_unop(cp[1],argv[3])!=0); + else if(argv[1][0]=='-' && argv[1][2]==0) + return(!test_unop(argv[1][1],cp)); + errormsg(SH_DICT,ERROR_exit(2),e_badop,cp); + } + return(test_binop(op,argv[1],argv[3])^(argc!=5)); + } + case 3: + if(not) + return(*argv[2]!=0); + if(cp[0] != '-' || cp[2] || cp[1]=='?') + { + if(cp[0]=='-' && (cp[1]=='-' || cp[1]=='?') && + strcmp(argv[2],"--")==0) + { + char *av[3]; + av[0] = argv[0]; + av[1] = argv[1]; + av[2] = 0; + optget(av,sh_opttest); + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + return(2); + } + break; + } + return(!test_unop(cp[1],argv[2])); + case 2: + return(*cp==0); + } + if(argc==5) + argv--; + tdata.ac = argc; + return(!expr(&tdata,0)); +} + +/* + * evaluate a test expression. + * flag is 0 on outer level + * flag is 1 when in parenthesis + * flag is 2 when evaluating -a + */ +static int expr(struct test *tp,register int flag) +{ + register int r; + register char *p; + r = e3(tp); + while(tp->ap < tp->ac) + { + p = nxtarg(tp,0); + /* check for -o and -a */ + if(flag && c_eq(p,')')) + { + tp->ap--; + break; + } + if(*p=='-' && *(p+2)==0) + { + if(*++p == 'o') + { + if(flag==2) + { + tp->ap--; + break; + } + r |= expr(tp,3); + continue; + } + else if(*p == 'a') + { + r &= expr(tp,2); + continue; + } + } + if(flag==0) + break; + errormsg(SH_DICT,ERROR_exit(2),e_badsyntax); + } + return(r); +} + +static char *nxtarg(struct test *tp,int mt) +{ + if(tp->ap >= tp->ac) + { + if(mt) + { + tp->ap++; + return(0); + } + errormsg(SH_DICT,ERROR_exit(2),e_argument); + } + return(tp->av[tp->ap++]); +} + + +static int e3(struct test *tp) +{ + register char *arg, *cp; + register int op; + char *binop; + arg=nxtarg(tp,0); + if(arg && c_eq(arg, '!')) + return(!e3(tp)); + if(c_eq(arg, '(')) + { + op = expr(tp,1); + cp = nxtarg(tp,0); + if(!cp || !c_eq(cp, ')')) + errormsg(SH_DICT,ERROR_exit(2),e_missing,"')'"); + return(op); + } + cp = nxtarg(tp,1); + if(cp!=0 && (c_eq(cp,'=') || c2_eq(cp,'!','='))) + goto skip; + if(c2_eq(arg,'-','t')) + { + if(cp && isdigit(*cp)) + return(*(cp+1)?0:tty_check(*cp-'0')); + else + { + /* test -t with no arguments */ + tp->ap--; + return(tty_check(1)); + } + } + if(*arg=='-' && arg[2]==0) + { + op = arg[1]; + if(!cp) + { + /* for backward compatibility with new flags */ + if(op==0 || !strchr(test_opchars+10,op)) + return(1); + errormsg(SH_DICT,ERROR_exit(2),e_argument); + } + if(strchr(test_opchars,op)) + return(test_unop(op,cp)); + } + if(!cp) + { + tp->ap--; + return(*arg!=0); + } +skip: + op = sh_lookup(binop=cp,shtab_testops); + if(!(op&TEST_BINOP)) + cp = nxtarg(tp,0); + if(!op) + errormsg(SH_DICT,ERROR_exit(2),e_badop,binop); + if(op==TEST_AND | op==TEST_OR) + tp->ap--; + return(test_binop(op,arg,cp)); +} + +int test_unop(register int op,register const char *arg) +{ + struct stat statb; + int f; + switch(op) + { + case 'r': + return(permission(arg, R_OK)); + case 'w': + return(permission(arg, W_OK)); + case 'x': + return(permission(arg, X_OK)); + case 'V': +#if SHOPT_FS_3D + { + register int offset = staktell(); + if(stat(arg,&statb)<0 || !S_ISREG(statb.st_mode)) + return(0); + /* add trailing / */ + stakputs(arg); + stakputc('/'); + stakputc(0); + arg = (const char*)stakptr(offset); + stakseek(offset); + /* FALL THRU */ + } +#else + return(0); +#endif /* SHOPT_FS_3D */ + case 'd': + return(test_stat(arg,&statb)>=0 && S_ISDIR(statb.st_mode)); + case 'c': + return(test_stat(arg,&statb)>=0 && S_ISCHR(statb.st_mode)); + case 'b': + return(test_stat(arg,&statb)>=0 && S_ISBLK(statb.st_mode)); + case 'f': + return(test_stat(arg,&statb)>=0 && S_ISREG(statb.st_mode)); + case 'u': + return(test_mode(arg)&S_ISUID); + case 'g': + return(test_mode(arg)&S_ISGID); + case 'k': +#ifdef S_ISVTX + return(test_mode(arg)&S_ISVTX); +#else + return(0); +#endif /* S_ISVTX */ +#if SHOPT_TEST_L + case 'l': +#endif + case 'L': + case 'h': /* undocumented, and hopefully will disappear */ + if(*arg==0 || arg[strlen(arg)-1]=='/' || lstat(arg,&statb)<0) + return(0); + return(S_ISLNK(statb.st_mode)); + + case 'C': +#ifdef S_ISCTG + return(test_stat(arg,&statb)>=0 && S_ISCTG(statb.st_mode)); +#else + return(0); +#endif /* S_ISCTG */ + case 'H': +#ifdef S_ISCDF + { + register int offset = staktell(); + if(test_stat(arg,&statb)>=0 && S_ISCDF(statb.st_mode)) + return(1); + stakputs(arg); + stakputc('+'); + stakputc(0); + arg = (const char*)stakptr(offset); + stakseek(offset); + return(test_stat(arg,&statb)>=0 && S_ISCDF(statb.st_mode)); + } +#else + return(0); +#endif /* S_ISCDF */ + + case 'S': + return(isasock(arg,&statb)); + case 'N': + return(test_stat(arg,&statb)>=0 && tmxgetmtime(&statb) > tmxgetatime(&statb)); + case 'p': + return(isapipe(arg,&statb)); + case 'n': + return(*arg != 0); + case 'z': + return(*arg == 0); + case 's': + sfsync(sfstdout); + case 'O': + case 'G': + if(*arg==0 || test_stat(arg,&statb)<0) + return(0); + if(op=='s') + return(statb.st_size>0); + else if(op=='O') + return(statb.st_uid==sh.userid); + return(statb.st_gid==sh.groupid); + case 'a': + case 'e': + return(permission(arg, F_OK)); + case 'o': + f=1; + if(*arg=='?') + return(sh_lookopt(arg+1,&f)>0); + op = sh_lookopt(arg,&f); + return(op && (f==(sh_isoption(op)!=0))); + case 't': + if(isdigit(*arg) && arg[1]==0) + return(tty_check(*arg-'0')); + return(0); + default: + { + static char a[3] = "-?"; + a[1]= op; + errormsg(SH_DICT,ERROR_exit(2),e_badop,a); + /* NOTREACHED */ + return(0); + } + } +} + +int test_binop(register int op,const char *left,const char *right) +{ + register double lnum,rnum; + if(op&TEST_ARITH) + { + while(*left=='0') + left++; + while(*right=='0') + right++; + lnum = sh_arith(left); + rnum = sh_arith(right); + } + switch(op) + { + /* op must be one of the following values */ + case TEST_AND: + case TEST_OR: + return(*left!=0); + case TEST_PEQ: + return(test_strmatch(left, right)); + case TEST_PNE: + return(!test_strmatch(left, right)); + case TEST_SGT: + return(strcoll(left, right)>0); + case TEST_SLT: + return(strcoll(left, right)<0); + case TEST_SEQ: + return(strcmp(left, right)==0); + case TEST_SNE: + return(strcmp(left, right)!=0); + case TEST_EF: + return(test_inode(left,right)); + case TEST_NT: + return(test_time(left,right)>0); + case TEST_OT: + return(test_time(left,right)<0); + case TEST_EQ: + return(lnum==rnum); + case TEST_NE: + return(lnum!=rnum); + case TEST_GT: + return(lnum>rnum); + case TEST_LT: + return(lnum<rnum); + case TEST_GE: + return(lnum>=rnum); + case TEST_LE: + return(lnum<=rnum); + } + /* NOTREACHED */ + return(0); +} + +/* + * returns the modification time of f1 - modification time of f2 + */ + +static time_t test_time(const char *file1,const char *file2) +{ + Time_t t1, t2; + struct stat statb1,statb2; + int r=test_stat(file2,&statb2); + if(test_stat(file1,&statb1)<0) + return(r<0?0:-1); + if(r<0) + return(1); + t1 = tmxgetmtime(&statb1); + t2 = tmxgetmtime(&statb2); + if (t1 > t2) + return(1); + if (t1 < t2) + return(-1); + return(0); +} + +/* + * return true if inode of two files are the same + */ + +int test_inode(const char *file1,const char *file2) +{ + struct stat stat1,stat2; + if(test_stat(file1,&stat1)>=0 && test_stat(file2,&stat2)>=0) + if(stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino) + return(1); + return(0); +} + + +/* + * This version of access checks against effective uid/gid + * The static buffer statb is shared with test_mode. + */ + +int sh_access(register const char *name, register int mode) +{ + struct stat statb; + if(*name==0) + return(-1); + if(strmatch(name,(char*)e_devfdNN)) + return(sh_ioaccess((int)strtol(name+8, (char**)0, 10),mode)); + /* can't use access function for execute permission with root */ + if(mode==X_OK && sh.euserid==0) + goto skip; + if(sh.userid==sh.euserid && sh.groupid==sh.egroupid) + return(access(name,mode)); +#ifdef _lib_setreuid + /* swap the real uid to effective, check access then restore */ + /* first swap real and effective gid, if different */ + if(sh.groupid==sh.euserid || setregid(sh.egroupid,sh.groupid)==0) + { + /* next swap real and effective uid, if needed */ + if(sh.userid==sh.euserid || setreuid(sh.euserid,sh.userid)==0) + { + mode = access(name,mode); + /* restore ids */ + if(sh.userid!=sh.euserid) + setreuid(sh.userid,sh.euserid); + if(sh.groupid!=sh.egroupid) + setregid(sh.groupid,sh.egroupid); + return(mode); + } + else if(sh.groupid!=sh.egroupid) + setregid(sh.groupid,sh.egroupid); + } +#endif /* _lib_setreuid */ +skip: + if(test_stat(name, &statb) == 0) + { + if(mode == F_OK) + return(mode); + else if(sh.euserid == 0) + { + if(!S_ISREG(statb.st_mode) || mode!=X_OK) + return(0); + /* root needs execute permission for someone */ + mode = (S_IXUSR|S_IXGRP|S_IXOTH); + } + else if(sh.euserid == statb.st_uid) + mode <<= 6; + else if(sh.egroupid == statb.st_gid) + mode <<= 3; +#ifdef _lib_getgroups + /* on some systems you can be in several groups */ + else + { + static int maxgroups; + gid_t *groups; + register int n; + if(maxgroups==0) + { + /* first time */ + if((maxgroups=getgroups(0,(gid_t*)0)) <= 0) + { + /* pre-POSIX system */ + maxgroups=NGROUPS_MAX; + } + } + groups = (gid_t*)stakalloc((maxgroups+1)*sizeof(gid_t)); + n = getgroups(maxgroups,groups); + while(--n >= 0) + { + if(groups[n] == statb.st_gid) + { + mode <<= 3; + break; + } + } + } +# endif /* _lib_getgroups */ + if(statb.st_mode & mode) + return(0); + } + return(-1); +} + +/* + * Return the mode bits of file <file> + * If <file> is null, then the previous stat buffer is used. + * The mode bits are zero if the file doesn't exist. + */ + +static int test_mode(register const char *file) +{ + struct stat statb; + if(file && (*file==0 || test_stat(file,&statb)<0)) + return(0); + return(statb.st_mode); +} + +/* + * do an fstat() for /dev/fd/n, otherwise stat() + */ +static int test_stat(const char *name,struct stat *buff) +{ + if(*name==0) + { + errno = ENOENT; + return(-1); + } + if(strmatch(name,(char*)e_devfdNN)) + return(fstat((int)strtol(name+8, (char**)0, 10),buff)); + else + return(stat(name,buff)); +} diff --git a/usr/src/lib/libshell/common/bltins/trap.c b/usr/src/lib/libshell/common/bltins/trap.c new file mode 100644 index 0000000000..7197144d5e --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/trap.c @@ -0,0 +1,347 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * trap [-p] action sig... + * kill [-l] [sig...] + * kill [-s sig] pid... + * + * David Korn + * AT&T Labs + * research!dgk + * + */ + +#include "defs.h" +#include <ctype.h> +#include "jobs.h" +#include "builtins.h" + +#define L_FLAG 1 +#define S_FLAG 2 + +static const char trapfmt[] = "trap -- %s %s\n"; + +static int sig_number(const char*); +static void sig_list(Shell_t*,int); + +int b_trap(int argc,char *argv[],void *extra) +{ + register char *arg = argv[1]; + register int sig, pflag = 0; + register Shell_t *shp = (Shell_t*)extra; + NOT_USED(argc); + while (sig = optget(argv, sh_opttrap)) switch (sig) + { + case 'p': + pflag=1; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + break; + } + argv += opt_info.index; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s", optusage((char*)0)); + if(arg = *argv) + { + register int clear; + char *action = arg; + if(!pflag) + { + /* first argument all digits or - means clear */ + while(isdigit(*arg)) + arg++; + clear = (arg!=action && *arg==0); + if(!clear) + { + ++argv; + if(*action=='-' && action[1]==0) + clear++; + } + while(!argv[0]) + errormsg(SH_DICT,ERROR_exit(1),e_condition); + } + while(arg = *argv++) + { + sig = sig_number(arg); + if(sig<0) + { + errormsg(SH_DICT,2,e_trap,arg); + return(1); + } + /* internal traps */ + if(sig&SH_TRAP) + { + sig &= ~SH_TRAP; + if(sig>SH_DEBUGTRAP) + { + errormsg(SH_DICT,2,e_trap,arg); + return(1); + } + if(pflag) + { + if(arg=shp->st.trap[sig]) + sfputr(sfstdout,sh_fmtq(arg),'\n'); + continue; + } + if(shp->st.trap[sig]) + free(shp->st.trap[sig]); + shp->st.trap[sig] = 0; + if(!clear && *action) + shp->st.trap[sig] = strdup(action); + if(sig == SH_DEBUGTRAP) + { + if(shp->st.trap[sig]) + shp->trapnote |= SH_SIGTRAP; + else + shp->trapnote = 0; + } + continue; + } + if(sig>shp->sigmax) + { + errormsg(SH_DICT,2,e_trap,arg); + return(1); + } + else if(pflag) + { + char **trapcom = (shp->st.otrapcom?shp->st.otrapcom:shp->st.trapcom); + if(arg=trapcom[sig]) + sfputr(sfstdout,arg,'\n'); + } + else if(clear) + sh_sigclear(sig); + else + { + if(sig >= shp->st.trapmax) + shp->st.trapmax = sig+1; + if(arg=shp->st.trapcom[sig]) + free(arg); + shp->st.trapcom[sig] = strdup(action); + sh_sigtrap(sig); + } + } + } + else /* print out current traps */ + sig_list(shp,-1); + return(0); +} + +int b_kill(int argc,char *argv[],void *extra) +{ + register char *signame; + register int sig=SIGTERM, flag=0, n; + register Shell_t *shp = (Shell_t*)extra; + NOT_USED(argc); + while((n = optget(argv,sh_optkill))) switch(n) + { + case ':': + if((signame=argv[opt_info.index++]) && (sig=sig_number(signame+1))>=0) + goto endopts; + opt_info.index--; + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case 'n': + sig = (int)opt_info.num; + goto endopts; + case 's': + flag |= S_FLAG; + signame = opt_info.arg; + goto endopts; + case 'l': + flag |= L_FLAG; + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } +endopts: + argv += opt_info.index; + if(*argv && strcmp(*argv,"--")==0 && strcmp(*(argv-1),"--")!=0) + argv++; + if(error_info.errors || flag==(L_FLAG|S_FLAG) || (!(*argv) && !(flag&L_FLAG))) + errormsg(SH_DICT,ERROR_usage(2),"%s", optusage((char*)0)); + /* just in case we send a kill -9 $$ */ + sfsync(sfstderr); + if(flag&L_FLAG) + { + if(!(*argv)) + sig_list(shp,0); + else while(signame = *argv++) + { + if(isdigit(*signame)) + sig_list(shp,((int)strtol(signame, (char**)0, 10)&0177)+1); + else + { + if((sig=sig_number(signame))<0) + { + shp->exitval = 2; + errormsg(SH_DICT,ERROR_exit(1),e_nosignal,signame); + } + sfprintf(sfstdout,"%d\n",sig); + } + } + return(shp->exitval); + } + if(flag&S_FLAG) + { + if((sig=sig_number(signame)) < 0 || sig > shp->sigmax) + errormsg(SH_DICT,ERROR_exit(1),e_nosignal,signame); + } + if(job_walk(sfstdout,job_kill,sig,argv)) + shp->exitval = 1; + return(shp->exitval); +} + +/* + * Given the name or number of a signal return the signal number + */ + +static int sig_number(const char *string) +{ + const Shtable_t *tp; + register int n,sig=0; + char *last; + if(isdigit(*string)) + { + n = strtol(string,&last,10); + if(*last) + n = -1; + } + else + { + register int c; + n = staktell(); + do + { + c = *string++; + if(islower(c)) + c = toupper(c); + stakputc(c); + } + while(c); + stakseek(n); + if(memcmp(stakptr(n),"SIG",3)==0) + { + sig = 1; + n += 3; + } + tp = sh_locate(stakptr(n),(const Shtable_t*)shtab_signals,sizeof(*shtab_signals)); + n = tp->sh_number; + if(sig==1 && (n>=(SH_TRAP-1) && n < (1<<SH_SIGBITS))) + { + /* sig prefix cannot match internal traps */ + n = 0; + tp = (Shtable_t*)((char*)tp + sizeof(*shtab_signals)); + if(strcmp(stakptr(n),tp->sh_name)==0) + n = tp->sh_number; + } + n &= (1<<SH_SIGBITS)-1; + if(n < SH_TRAP) + n--; + } + return(n); +} + +/* + * if <flag> is positive, then print signal name corresponding to <flag> + * if <flag> is zero, then print all signal names + * if <flag> is negative, then print all traps + */ +static void sig_list(register Shell_t *shp,register int flag) +{ + register const struct shtable2 *tp; + register int sig = shp->sigmax+1; + const char *names[SH_TRAP]; + const char *traps[SH_DEBUGTRAP+1]; + tp=shtab_signals; + if(flag==0) + { + /* not all signals may be defined, so initialize */ + while(--sig >= 0) + names[sig] = 0; + for(sig=SH_DEBUGTRAP; sig>=0; sig--) + traps[sig] = 0; + } + while(*tp->sh_name) + { + sig = tp->sh_number; + sig &= ((1<<SH_SIGBITS)-1); + if(sig==flag) + { + sfprintf(sfstdout,"%s\n",tp->sh_name); + return; + } + else if(sig&SH_TRAP) + traps[sig&~SH_TRAP] = (char*)tp->sh_name; + else if(sig < sizeof(names)/sizeof(char*)) + names[sig] = (char*)tp->sh_name; + tp++; + } + if(flag > 0) + sfprintf(sfstdout,"%d\n",flag-1); + else if(flag<0) + { + /* print the traps */ + register char *trap,*sname,**trapcom; + char name[6]; + sig = shp->st.trapmax; + /* use parent traps if otrapcom is set (for $(trap) */ + trapcom = (shp->st.otrapcom?shp->st.otrapcom:shp->st.trapcom); + while(--sig >= 0) + { + if(!(trap=trapcom[sig])) + continue; + if(!(sname=(char*)names[sig+1])) + { + sname = name; + sname[0] = 'S'; + sname[1] = 'I'; + sname[2] = 'G'; + sname[3] = (sig/10)+'0'; + sname[4] = (sig%10)+'0'; + } + sfprintf(sfstdout,trapfmt,sh_fmtq(trap),sname); + } + for(sig=SH_DEBUGTRAP; sig>=0; sig--) + { + if(!(trap=shp->st.trap[sig])) + continue; + sfprintf(sfstdout,trapfmt,sh_fmtq(trap),traps[sig]); + } + } + else + { + /* print all the signal names */ + for(sig=2; sig <= shp->sigmax; sig++) + { + if(names[sig]) + sfputr(sfstdout,names[sig],'\n'); + else + sfprintf(sfstdout,"SIG%d\n",sig-1); + } + } +} + diff --git a/usr/src/lib/libshell/common/bltins/typeset.c b/usr/src/lib/libshell/common/bltins/typeset.c new file mode 100644 index 0000000000..14b02a6ad3 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/typeset.c @@ -0,0 +1,979 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * export [-p] [arg...] + * readonly [-p] [arg...] + * typeset [options] [arg...] + * alias [-ptx] [arg...] + * unalias [arg...] + * builtin [-sd] [-f file] [name...] + * set [options] [name...] + * unset [-fnv] [name...] + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <error.h> +#include "path.h" +#include "name.h" +#include "history.h" +#include "builtins.h" +#include "variables.h" +#include <dlldefs.h> + +struct tdata +{ + Shell_t *sh; + Namval_t *tp; + Sfio_t *outfile; + char *prefix; + int aflag; + int argnum; + int scanmask; + Dt_t *scanroot; + char **argnam; +}; + + +static int print_namval(Sfio_t*, Namval_t*, int, struct tdata*); +static void print_attribute(Namval_t*,void*); +static void print_all(Sfio_t*, Dt_t*, struct tdata*); +static void print_scan(Sfio_t*, int, Dt_t*, int, struct tdata*t); +static int b_unall(int, char**, Dt_t*, Shell_t*); +static int b_common(char**, int, Dt_t*, struct tdata*); +static void pushname(Namval_t*,void*); +static void(*nullscan)(Namval_t*,void*); + +static Namval_t *load_class(const char *name) +{ + errormsg(SH_DICT,ERROR_exit(1),"%s: type not loadable",name); + return(0); +} + +/* + * Note export and readonly are the same + */ +#if 0 + /* for the dictionary generator */ + int b_export(int argc,char *argv[],void *extra){} +#endif +int b_readonly(int argc,char *argv[],void *extra) +{ + register int flag; + char *command = argv[0]; + struct tdata tdata; + NOT_USED(argc); + memset((void*)&tdata,0,sizeof(tdata)); + tdata.sh = (Shell_t*)extra; + tdata.aflag = '-'; + while((flag = optget(argv,*command=='e'?sh_optexport:sh_optreadonly))) switch(flag) + { + case 'p': + tdata.prefix = command; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),optusage(NIL(char*))); + argv += (opt_info.index-1); + if(*command=='r') + flag = (NV_ASSIGN|NV_RDONLY|NV_VARNAME); +#ifdef _ENV_H + else if(!argv[1]) + { + char *cp,**env=env_get(tdata.sh->env); + while(cp = *env++) + { + if(tdata.prefix) + sfputr(sfstdout,tdata.prefix,' '); + sfprintf(sfstdout,"%s\n",sh_fmtq(cp)); + } + return(0); + } +#endif + else + { + flag = (NV_ASSIGN|NV_EXPORT|NV_IDENT); + if(!sh.prefix) + sh.prefix = ""; + } + return(b_common(argv,flag,tdata.sh->var_tree, &tdata)); +} + + +int b_alias(int argc,register char *argv[],void *extra) +{ + register unsigned flag = NV_NOARRAY|NV_NOSCOPE|NV_ASSIGN; + register Dt_t *troot; + register int n; + struct tdata tdata; + NOT_USED(argc); + memset((void*)&tdata,0,sizeof(tdata)); + tdata.sh = (Shell_t*)extra; + troot = tdata.sh->alias_tree; + if(*argv[0]=='h') + flag = NV_TAGGED; + if(argv[1]) + { + opt_info.offset = 0; + opt_info.index = 1; + *opt_info.option = 0; + tdata.argnum = 0; + tdata.aflag = *argv[1]; + while((n = optget(argv,sh_optalias))) switch(n) + { + case 'p': + tdata.prefix = argv[0]; + break; + case 't': + flag |= NV_TAGGED; + break; + case 'x': + flag |= NV_EXPORT; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage(NIL(char*))); + argv += (opt_info.index-1); + if(flag&NV_TAGGED) + { + if(argv[1] && strcmp(argv[1],"-r")==0) + { + /* hack to handle hash -r */ + nv_putval(PATHNOD,nv_getval(PATHNOD),NV_RDONLY); + return(0); + } + troot = tdata.sh->track_tree; + } + } + return(b_common(argv,flag,troot,&tdata)); +} + + +#if 0 + /* for the dictionary generator */ + int b_local(int argc,char *argv[],void *extra){} +#endif +int b_typeset(int argc,register char *argv[],void *extra) +{ + register int flag = NV_VARNAME|NV_ASSIGN; + register int n; + struct tdata tdata; + Namtype_t *ntp = (Namtype_t*)extra; + Dt_t *troot; + int isfloat=0, shortint=0; + NOT_USED(argc); + memset((void*)&tdata,0,sizeof(tdata)); + tdata.sh = ntp->shp; + tdata.tp = ntp->np; + troot = tdata.sh->var_tree; + opt_info.disc = (Optdisc_t*)ntp->optinfof; + while((n = optget(argv,ntp->optstring))) + { + switch(n) + { + case 'a': + flag |= NV_IARRAY; + break; + case 'A': + flag |= NV_ARRAY; + break; + case 'E': + /* The following is for ksh88 compatibility */ + if(opt_info.offset && !strchr(argv[opt_info.index],'E')) + { + tdata.argnum = (int)opt_info.num; + break; + } + case 'F': + if(!opt_info.arg || (tdata.argnum = opt_info.num) <0) + tdata.argnum = 10; + isfloat = 1; + if(n=='E') + flag |= NV_EXPNOTE; + break; + case 'b': + flag |= NV_BINARY; + break; + case 'n': + flag &= ~NV_VARNAME; + flag |= (NV_REF|NV_IDENT); + break; + case 'H': + flag |= NV_HOST; + break; + case 'T': + flag |= NV_TYPE; + tdata.prefix = opt_info.arg; + break; + case 'L': + if(tdata.argnum==0) + tdata.argnum = (int)opt_info.num; + if(tdata.argnum < 0) + errormsg(SH_DICT,ERROR_exit(1), e_badfield, tdata.argnum); + flag &= ~NV_RJUST; + flag |= NV_LJUST; + break; + case 'Z': + flag |= NV_ZFILL; + /* FALL THRU*/ + case 'R': + if(tdata.argnum==0) + tdata.argnum = (int)opt_info.num; + if(tdata.argnum < 0) + errormsg(SH_DICT,ERROR_exit(1), e_badfield, tdata.argnum); + flag &= ~NV_LJUST; + flag |= NV_RJUST; + break; + case 'f': + flag &= ~(NV_VARNAME|NV_ASSIGN); + troot = tdata.sh->fun_tree; + break; + case 'i': + if(!opt_info.arg || (tdata.argnum = opt_info.num) <0) + tdata.argnum = 10; + flag |= NV_INTEGER; + break; + case 'l': + flag |= NV_UTOL; + break; + case 'p': + tdata.prefix = argv[0]; + continue; + case 'r': + flag |= NV_RDONLY; + break; + case 's': + shortint=1; + break; + case 't': + flag |= NV_TAGGED; + break; + case 'u': + flag |= NV_LTOU; + break; + case 'x': + flag &= ~NV_VARNAME; + flag |= (NV_EXPORT|NV_IDENT); + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + opt_info.disc = 0; + return(2); + } + if(tdata.aflag==0) + tdata.aflag = *opt_info.option; + } + argv += opt_info.index; + opt_info.disc = 0; + /* handle argument of + and - specially */ + if(*argv && argv[0][1]==0 && (*argv[0]=='+' || *argv[0]=='-')) + tdata.aflag = *argv[0]; + else + argv--; + if((flag&NV_INTEGER) && (flag&(NV_LJUST|NV_RJUST|NV_ZFILL))) + error_info.errors++; + if((flag&NV_BINARY) && (flag&(NV_LJUST|NV_UTOL|NV_LTOU))) + error_info.errors++; + if(troot==tdata.sh->fun_tree && ((isfloat || flag&~(NV_FUNCT|NV_TAGGED|NV_EXPORT|NV_LTOU)))) + error_info.errors++; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s", optusage(NIL(char*))); + if(isfloat) + flag |= NV_INTEGER|NV_DOUBLE; + if(shortint) + flag |= NV_SHORT|NV_INTEGER; + if(tdata.sh->fn_depth) + flag |= NV_NOSCOPE; + if(flag&NV_TYPE) + { + int offset = staktell(); + stakputs(NV_CLASS); + if(NV_CLASS[sizeof(NV_CLASS)-2]!='.') + stakputc('.'); + stakputs(tdata.prefix); + stakputc(0); + tdata.tp = nv_open(stakptr(offset),tdata.sh->var_tree,NV_VARNAME|NV_NOARRAY|NV_NOASSIGN); + stakseek(offset); + if(!tdata.tp) + errormsg(SH_DICT,ERROR_exit(1),"%s: unknown type",tdata.prefix); + flag &= ~NV_TYPE; + } + else if(tdata.aflag==0 && ntp->np) + tdata.aflag = '-'; + return(b_common(argv,flag,troot,&tdata)); +} + +static int b_common(char **argv,register int flag,Dt_t *troot,struct tdata *tp) +{ + register char *name; + char *last = 0; + int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN)); + int r=0, ref=0; + Shell_t *shp =tp->sh; + if(!sh.prefix) + nvflags |= NV_NOSCOPE; + else if(*sh.prefix==0) + sh.prefix = 0; + flag &= ~(NV_NOARRAY|NV_NOSCOPE|NV_VARNAME|NV_IDENT); + if(argv[1]) + { + if(flag&NV_REF) + { + flag &= ~NV_REF; + ref=1; + if(tp->aflag!='-') + nvflags |= NV_NOREF; + } + while(name = *++argv) + { + register unsigned newflag; + register Namval_t *np; + unsigned curflag; + if(troot == shp->fun_tree) + { + /* + *functions can be exported or + * traced but not set + */ + flag &= ~NV_ASSIGN; + if(flag&NV_LTOU) + { + /* Function names cannot be special builtin */ + if((np=nv_search(name,shp->bltin_tree,0)) && nv_isattr(np,BLT_SPC)) + errormsg(SH_DICT,ERROR_exit(1),e_badfun,name); + np = nv_open(name,shp->fun_tree,NV_NOARRAY|NV_IDENT|NV_NOSCOPE); + } + else + np = nv_search(name,shp->fun_tree,HASH_NOSCOPE); + if(np && ((flag&NV_LTOU) || !nv_isnull(np) || nv_isattr(np,NV_LTOU))) + { + if(flag==0) + { + print_namval(sfstdout,np,tp->aflag=='+',tp); + continue; + } + if(shp->subshell) + sh_subfork(); + if(tp->aflag=='-') + nv_onattr(np,flag|NV_FUNCTION); + else if(tp->aflag=='+') + nv_offattr(np,flag); + } + else + r++; + continue; + } + np = nv_open(name,troot,nvflags); + /* tracked alias */ + if(troot==shp->track_tree && tp->aflag=='-') + { +#ifdef PATH_BFPATH + path_alias(np,path_absolute(nv_name(np),NIL(Pathcomp_t*))); +#else + nv_onattr(np,NV_NOALIAS); + path_alias(np,path_absolute(nv_name(np),NIL(char*))); +#endif + continue; + } + if(flag==NV_ASSIGN && !ref && tp->aflag!='-' && !strchr(name,'=')) + { + if(troot!=shp->var_tree && (nv_isnull(np) || !print_namval(sfstdout,np,0,tp))) + { + sfprintf(sfstderr,sh_translate(e_noalias),name); + r++; + } + continue; + } + if(tp->tp) + { + nv_settype(np,tp->tp,tp->aflag=='-'?0:NV_APPEND); + flag = (np->nvflag&NV_NOCHANGE); + } + if(troot==shp->var_tree && (flag&NV_IARRAY)) + { + flag &= ~NV_IARRAY; + if(nv_isnull(np)) + nv_onattr(np,NV_ARRAY); + else + nv_putsub(np, (char*)0, 0); + } + if(troot==shp->var_tree && (nvflags&NV_ARRAY)) + nv_setarray(np,nv_associative); + curflag = np->nvflag; + flag &= ~NV_ASSIGN; + if(last=strchr(name,'=')) + *last = 0; + if (tp->aflag == '-') + { + if((flag&NV_EXPORT) && strchr(name,'.')) + errormsg(SH_DICT,ERROR_exit(1),e_badexport,name); +#if SHOPT_BSH + if(flag&NV_EXPORT) + nv_offattr(np,NV_IMPORT); +#endif /* SHOPT_BSH */ + newflag = curflag; + if(flag&~NV_NOCHANGE) + newflag &= NV_NOCHANGE; + newflag |= flag; + if (flag & (NV_LJUST|NV_RJUST)) + { + if(!(flag&NV_RJUST)) + newflag &= ~NV_RJUST; + + else if(!(flag&NV_LJUST)) + newflag &= ~NV_LJUST; + } + if (flag & NV_UTOL) + newflag &= ~NV_LTOU; + else if (flag & NV_LTOU) + newflag &= ~NV_UTOL; + } + else + { + if((flag&NV_RDONLY) && (curflag&NV_RDONLY)) + errormsg(SH_DICT,ERROR_exit(1),e_readonly,nv_name(np)); + newflag = curflag & ~flag; + } + if (tp->aflag && (tp->argnum>0 || (curflag!=newflag))) + { + if(shp->subshell) + sh_assignok(np,1); + if(troot!=shp->var_tree) + nv_setattr(np,newflag&~NV_ASSIGN); + else + { + char *oldname=0; + if(tp->argnum==1 && newflag==NV_INTEGER && nv_isattr(np,NV_INTEGER)) + tp->argnum = 10; + /* use reference name for export */ + if((newflag^curflag)&NV_EXPORT) + { + oldname = np->nvname; + np->nvname = name; + } + nv_newattr (np, newflag&~NV_ASSIGN,tp->argnum); + if(oldname) + np->nvname = oldname; + } + } + if(last) + *last = '='; + /* set or unset references */ + if(ref) + { + if(tp->aflag=='-') + { + Dt_t *hp=0; + if(nv_isattr(np,NV_PARAM) && shp->st.prevst) + { + if(!(hp=(Dt_t*)shp->st.prevst->save_tree)) + hp = dtvnext(shp->var_tree); + } + nv_setref(np,hp,NV_VARNAME); + } + else + nv_unref(np); + } + nv_close(np); + } + } + else if(!sh.envlist) + { + if(tp->aflag) + { + if(troot==shp->fun_tree) + { + flag |= NV_FUNCTION; + tp->prefix = 0; + } + else if(troot==shp->var_tree) + flag |= (nvflags&NV_ARRAY); + print_scan(sfstdout,flag,troot,tp->aflag=='+',tp); + } + else if(troot==shp->alias_tree) + print_scan(sfstdout,0,troot,0,tp); + else + print_all(sfstdout,troot,tp); + sfsync(sfstdout); + } + return(r); +} + +typedef void (*Iptr_t)(int); +typedef int (*Fptr_t)(int, char*[], void*); + +#define GROWLIB 4 + +static void** liblist; +static int nlib; +static int maxlib; + +/* + * This allows external routines to load from the same library */ +void **sh_getliblist(void) +{ + return(liblist); +} + +/* + * add library to loaded list + * call (*lib_init)() on first load if defined + * always move to head of search list + * return: 0: already loaded 1: first load + */ +int sh_addlib(void* library) +{ + register int n; + register int r; + Iptr_t initfn; + + for (n = r = 0; n < nlib; n++) + { + if (r) + liblist[n-1] = liblist[n]; + else if (liblist[n] == library) + r++; + } + if (r) + nlib--; + else if ((initfn = (Iptr_t)dlllook(library, "lib_init"))) + (*initfn)(0); + if (nlib >= maxlib) + { + maxlib += GROWLIB; + if (liblist) + liblist = (void**)realloc((void*)liblist, (maxlib+1)*sizeof(void**)); + else + liblist = (void**)malloc((maxlib+1)*sizeof(void**)); + } + liblist[nlib++] = library; + liblist[nlib] = 0; + return !r; +} + +/* + * add change or list built-ins + * adding builtins requires dlopen() interface + */ +int b_builtin(int argc,char *argv[],void *extra) +{ + register char *arg=0, *name; + register int n, r=0, flag=0; + register Namval_t *np; + long dlete=0; + struct tdata tdata; + Fptr_t addr; + void *library=0; + char *errmsg; + NOT_USED(argc); + tdata.sh = (Shell_t*)extra; + while (n = optget(argv,sh_optbuiltin)) switch (n) + { + case 's': + flag = BLT_SPC; + break; + case 'd': + dlete=1; + break; + case 'f': +#if SHOPT_DYNAMIC + arg = opt_info.arg; +#else + errormsg(SH_DICT,2, "adding built-ins not supported"); + error_info.errors++; +#endif /* SHOPT_DYNAMIC */ + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s", optusage(NIL(char*))); + if(arg || *argv) + { + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,argv[-opt_info.index]); + if(sh_isoption(SH_PFSH)) + errormsg(SH_DICT,ERROR_exit(1),e_pfsh,argv[-opt_info.index]); + if(tdata.sh->subshell) + sh_subfork(); + } + if(arg) + { +#ifdef _hdr_dlldefs +#if (_AST_VERSION>=20040404) + if(!(library = dllplug(SH_ID,arg,NIL(char*),RTLD_LAZY,NIL(char*),0))) +#else + if(!(library = dllfind(arg,NIL(char*),RTLD_LAZY,NIL(char*),0))) +#endif +#else + if(!(library = dlopen(arg,DL_MODE))) +#endif + { + errormsg(SH_DICT,ERROR_exit(0),"%s: %s",arg,dlerror()); + return(1); + } + sh_addlib(library); + } + else if(*argv==0 && !dlete) + { + print_scan(sfstdout, flag, tdata.sh->bltin_tree, 1, &tdata); + return(0); + } + r = 0; + flag = staktell(); + while(arg = *argv) + { + name = path_basename(arg); + stakputs("b_"); + stakputs(name); + errmsg = 0; + addr = 0; + for(n=(nlib?nlib:dlete); --n>=0;) + { + /* (char*) added for some sgi-mips compilers */ + if(dlete || (addr = (Fptr_t)dlllook(liblist[n],stakptr(flag)))) + { + if(np = sh_addbuiltin(arg, addr,pointerof(dlete))) + { + if(dlete || nv_isattr(np,BLT_SPC)) + errmsg = "restricted name"; + } + break; + } + } + if(!dlete && !addr) + { + np = sh_addbuiltin(arg, 0 ,0); + if(np && nv_isattr(np,BLT_SPC)) + errmsg = "restricted name"; + else if(!np) + errmsg = "not found"; + } + if(errmsg) + { + errormsg(SH_DICT,ERROR_exit(0),"%s: %s",*argv,errmsg); + r = 1; + } + stakseek(flag); + argv++; + } + return(r); +} + +int b_set(int argc,register char *argv[],void *extra) +{ + struct tdata tdata; + memset(&tdata,0,sizeof(tdata)); + tdata.sh = (Shell_t*)extra; + tdata.prefix=0; + if(argv[1]) + { + if(sh_argopts(argc,argv) < 0) + return(2); + if(sh_isoption(SH_VERBOSE)) + sh_onstate(SH_VERBOSE); + else + sh_offstate(SH_VERBOSE); + if(sh_isoption(SH_MONITOR)) + sh_onstate(SH_MONITOR); + else + sh_offstate(SH_MONITOR); + } + else + /*scan name chain and print*/ + print_scan(sfstdout,0,tdata.sh->var_tree,0,&tdata); + return(0); +} + +/* + * The removing of Shell variable names, aliases, and functions + * is performed here. + * Unset functions with unset -f + * Non-existent items being deleted give non-zero exit status + */ + +int b_unalias(int argc,register char *argv[],void *extra) +{ + Shell_t *shp = (Shell_t*)extra; + return(b_unall(argc,argv,shp->alias_tree,shp)); +} + +int b_unset(int argc,register char *argv[],void *extra) +{ + Shell_t *shp = (Shell_t*)extra; + return(b_unall(argc,argv,shp->var_tree,shp)); +} + +static int b_unall(int argc, char **argv, register Dt_t *troot, Shell_t* shp) +{ + register Namval_t *np; + register const char *name; + register int r; + int nflag=0,all=0,isfun; + NOT_USED(argc); + if(troot==shp->alias_tree) + { + name = sh_optunalias; + if(shp->subshell) + troot = sh_subaliastree(0); + } + else + name = sh_optunset; + while(r = optget(argv,name)) switch(r) + { + case 'f': + troot = sh_subfuntree(0); + break; + case 'a': + all=1; + break; + case 'n': + nflag = NV_NOREF; + case 'v': + troot = shp->var_tree; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(2); + } + argv += opt_info.index; + if(error_info.errors || (*argv==0 &&!all)) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage(NIL(char*))); + if(!troot) + return(1); + r = 0; + if(troot==shp->var_tree) + nflag |= NV_VARNAME; + else + nflag = NV_NOSCOPE; + if(all) + dtclear(troot); + else while(name = *argv++) + { + if(np=nv_open(name,troot,NV_NOADD|nflag)) + { + if(is_abuiltin(np)) + { + r = 1; + continue; + } + isfun = is_afunction(np); + if(shp->subshell && troot==shp->var_tree) + np=sh_assignok(np,0); + nv_unset(np); + nv_close(np); + if(isfun) + dtdelete(troot,np); + } + else + r = 1; + } + return(r); +} + +/* + * print out the name and value of a name-value pair <np> + */ + +static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, struct tdata *tp) +{ + register char *cp; + sh_sigcheck(); + if(flag) + flag = '\n'; + if(nv_isattr(np,NV_NOPRINT)==NV_NOPRINT) + { + if(is_abuiltin(np)) + sfputr(file,nv_name(np),'\n'); + return(0); + } + if(tp->prefix) + sfputr(file,tp->prefix,' '); + if(is_afunction(np)) + { + Sfio_t *iop=0; + char *fname=0; + if(!flag && !np->nvalue.ip) + sfputr(file,"typeset -fu",' '); + else if(!flag && !nv_isattr(np,NV_FPOSIX)) + sfputr(file,"function",' '); + sfputr(file,nv_name(np),-1); + if(nv_isattr(np,NV_FPOSIX)) + sfwrite(file,"()",2); + if(np->nvalue.ip && np->nvalue.rp->hoffset>=0) + fname = np->nvalue.rp->fname; + else + flag = '\n'; + if(flag) + { + if(np->nvalue.ip && np->nvalue.rp->hoffset>=0) + sfprintf(file," #line %d %s\n",np->nvalue.rp->lineno,fname?sh_fmtq(fname):""); + else + sfputc(file, '\n'); + } + else + { + if(nv_isattr(np,NV_FTMP)) + { + fname = 0; + iop = tp->sh->heredocs; + } + else if(fname) + iop = sfopen(iop,fname,"r"); + else if(tp->sh->hist_ptr) + iop = (tp->sh->hist_ptr)->histfp; + if(iop && sfseek(iop,(Sfoff_t)np->nvalue.rp->hoffset,SEEK_SET)>=0) + sfmove(iop,file, nv_size(np), -1); + else + flag = '\n'; + if(fname) + sfclose(iop); + } + return(nv_size(np)+1); + } + if(cp=nv_getval(np)) + { + sfputr(file,nv_name(np),-1); + if(!flag) + { + flag = '='; + if(nv_arrayptr(np)) + sfprintf(file,"[%s]", sh_fmtq(nv_getsub(np))); + } + sfputc(file,flag); + if(flag != '\n') + { + if(nv_isref(np) && nv_refsub(np)) + { + sfputr(file,sh_fmtq(cp),-1); + sfprintf(file,"[%s]\n", sh_fmtq(nv_refsub(np))); + } + else + sfputr(file,sh_fmtq(cp),'\n'); + } + return(1); + } + else if(tp->scanmask && tp->scanroot==tp->sh->var_tree) + sfputr(file,nv_name(np),'\n'); + return(0); +} + +/* + * print attributes at all nodes + */ +static void print_all(Sfio_t *file,Dt_t *root, struct tdata *tp) +{ + tp->outfile = file; + nv_scan(root, print_attribute, (void*)tp, 0, 0); +} + +/* + * print the attributes of name value pair give by <np> + */ +static void print_attribute(register Namval_t *np,void *data) +{ + register struct tdata *dp = (struct tdata*)data; + nv_attribute(np,dp->outfile,dp->prefix,dp->aflag); +} + +/* + * print the nodes in tree <root> which have attributes <flag> set + * of <option> is non-zero, no subscript or value is printed. + */ + +static void print_scan(Sfio_t *file, int flag, Dt_t *root, int option,struct tdata *tp) +{ + register char **argv; + register Namval_t *np; + register int namec; + Namval_t *onp = 0; + sh.last_table=0; + flag &= ~NV_ASSIGN; + tp->scanmask = flag&~NV_NOSCOPE; + tp->scanroot = root; + tp->outfile = file; + if(flag&NV_INTEGER) + tp->scanmask |= (NV_DOUBLE|NV_EXPNOTE); + namec = nv_scan(root,nullscan,(void*)tp,tp->scanmask,flag); + argv = tp->argnam = (char**)stakalloc((namec+1)*sizeof(char*)); + namec = nv_scan(root, pushname, (void*)tp, tp->scanmask, flag); + if(mbcoll()) + strsort(argv,namec,strcoll); + while(namec--) + { + if((np=nv_search(*argv++,root,0)) && np!=onp && (!nv_isnull(np) || np->nvfun || nv_isattr(np,~NV_NOFREE))) + { + onp = np; + if((flag&NV_ARRAY) && nv_aindex(np)>=0) + continue; + if(!flag && nv_isattr(np,NV_ARRAY)) + { + if(array_elem(nv_arrayptr(np))==0) + continue; + nv_putsub(np,NIL(char*),ARRAY_SCAN); + do + { + print_namval(file,np,option,tp); + } + while(!option && nv_nextsub(np)); + } + else + print_namval(file,np,option,tp); + } + } +} + +/* + * add the name of the node to the argument list argnam + */ + +static void pushname(Namval_t *np,void *data) +{ + struct tdata *tp = (struct tdata*)data; + *tp->argnam++ = nv_name(np); +} + diff --git a/usr/src/lib/libshell/common/bltins/ulimit.c b/usr/src/lib/libshell/common/bltins/ulimit.c new file mode 100644 index 0000000000..4f7a2fc844 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/ulimit.c @@ -0,0 +1,211 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * ulimit [-HSacdfmnstuv] [limit] + * + * David Korn + * AT&T Labs + * + */ + +#include <ast.h> +#include <sfio.h> +#include <error.h> +#include <shell.h> +#include "builtins.h" +#include "name.h" +#include "ulimit.h" +#ifndef SH_DICT +# define SH_DICT "libshell" +#endif + +#ifdef _no_ulimit + int b_ulimit(int argc,char *argv[],void *extra) + { + NOT_USED(argc); + NOT_USED(argv); + NOT_USED(extra); + errormsg(SH_DICT,ERROR_exit(2),e_nosupport); + return(0); + } +#else + +static int infof(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp) +{ + register const Limit_t* tp; + + for (tp = shtab_limits; tp->option; tp++) + { + sfprintf(sp, "[%c=%d:%s?The %s", tp->option, tp - shtab_limits + 1, tp->name, tp->description); + if(tp->type != LIM_COUNT) + sfprintf(sp, " in %ss", e_units[tp->type]); + sfprintf(sp, ".]"); + } + return(1); +} + +#define HARD 2 +#define SOFT 4 + +int b_ulimit(int argc,char *argv[],void *extra) +{ + register char *limit; + register int mode=0, n; + register unsigned long hit = 0; + Shell_t *shp = (Shell_t*)extra; +#ifdef _lib_getrlimit + struct rlimit rlp; +#endif /* _lib_getrlimit */ + const Limit_t* tp; + char* conf; + int label, unit, noargs, nosupport; + rlim_t i; + char tmp[32]; + Optdisc_t disc; + memset(&disc, 0, sizeof(disc)); + disc.version = OPT_VERSION; + disc.infof = infof; + opt_info.disc = &disc; + while((n = optget(argv,sh_optulimit))) switch(n) + { + case 'H': + mode |= HARD; + continue; + case 'S': + mode |= SOFT; + continue; + case 'a': + hit = ~0; + break; + default: + if(n < 0) + hit |= (1L<<(-(n+1))); + else + errormsg(SH_DICT,2, e_notimp, opt_info.name); + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + opt_info.disc = 0; + limit = argv[opt_info.index]; + /* default to -f */ + if(noargs=(hit==0)) + for(n=0; shtab_limits[n].option; n++) + if(shtab_limits[n].index == RLIMIT_FSIZE) + { + hit |= (1L<<n); + break; + } + /* only one option at a time for setting */ + label = (hit&(hit-1)); + if(error_info.errors || (limit && label) || argc>opt_info.index+1) + errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0)); + if(mode==0) + mode = (HARD|SOFT); + for(tp = shtab_limits; tp->option && hit; tp++,hit>>=1) + { + if(!(hit&1)) + continue; + nosupport = (n = tp->index) == RLIMIT_UNKNOWN; + unit = shtab_units[tp->type]; + if(limit) + { + if(shp->subshell) + sh_subfork(); + if(strcmp(limit,e_unlimited)==0) + i = INFINITY; + else + { + char *last; + if((i=sh_strnum(limit,&last,2))==INFINITY || *last) + errormsg(SH_DICT,ERROR_system(1),e_number,limit); + i *= unit; + } + if(nosupport) + errormsg(SH_DICT,ERROR_system(1),e_readonly,tp->name); + else + { +#ifdef _lib_getrlimit + if(getrlimit(n,&rlp) <0) + errormsg(SH_DICT,ERROR_system(1),e_number,limit); + if(mode&HARD) + rlp.rlim_max = i; + if(mode&SOFT) + rlp.rlim_cur = i; + if(setrlimit(n,&rlp) <0) + errormsg(SH_DICT,ERROR_system(1),e_overlimit,limit); +#else + if((i=vlimit(n,i)) < 0) + errormsg(SH_DICT,ERROR_system(1),e_number,limit); +#endif /* _lib_getrlimit */ + } + } + else + { + if(!nosupport) + { +#ifdef _lib_getrlimit + if(getrlimit(n,&rlp) <0) + errormsg(SH_DICT,ERROR_system(1),e_number,limit); + if(mode&HARD) + i = rlp.rlim_max; + if(mode&SOFT) + i = rlp.rlim_cur; +#else +# ifdef _lib_ulimit + n--; +# endif /* _lib_ulimit */ + i = -1; + if((i=vlimit(n,i)) < 0) + errormsg(SH_DICT,ERROR_system(1),e_number,limit); +#endif /* _lib_getrlimit */ + } + if(label) + { + if(tp->type != LIM_COUNT) + sfsprintf(tmp,sizeof(tmp),"%s (%ss)", tp->description, e_units[tp->type]); + else + sfsprintf(tmp,sizeof(tmp),"%s", tp->name); + sfprintf(sfstdout,"%-30s (-%c) ",tmp,tp->option); + } + if(nosupport) + { + if(!tp->conf || !*(conf = astconf(tp->conf, NiL, NiL))) + conf = (char*)e_nosupport; + sfputr(sfstdout,conf,'\n'); + } + else if(i!=INFINITY || noargs) + { + if(!noargs) + i += (unit-1); + sfprintf(sfstdout,"%I*d\n",sizeof(i),i/unit); + } + else + sfputr(sfstdout,e_unlimited,'\n'); + } + } + return(0); +} +#endif /* _no_ulimit */ diff --git a/usr/src/lib/libshell/common/bltins/umask.c b/usr/src/lib/libshell/common/bltins/umask.c new file mode 100644 index 0000000000..a3f0072363 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/umask.c @@ -0,0 +1,98 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * umask [-S] [mask] + * + * David Korn + * AT&T Labs + * research!dgk + * + */ + +#include <ast.h> +#include <sfio.h> +#include <error.h> +#include <ctype.h> +#include <ls.h> +#include <shell.h> +#include "builtins.h" +#ifndef SH_DICT +# define SH_DICT "libshell" +#endif + +int b_umask(int argc,char *argv[],void *extra) +{ + register char *mask; + register int flag = 0, sflag = 0; + NOT_USED(extra); + while((argc = optget(argv,sh_optumask))) switch(argc) + { + case 'S': + sflag++; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + break; + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + argv += opt_info.index; + if(mask = *argv) + { + register int c; + if(isdigit(*mask)) + { + while(c = *mask++) + { + if (c>='0' && c<='7') + flag = (flag<<3) + (c-'0'); + else + errormsg(SH_DICT,ERROR_exit(1),e_number,*argv); + } + } + else + { + char *cp = mask; + flag = umask(0); + c = strperm(cp,&cp,~flag); + if(*cp) + { + umask(flag); + errormsg(SH_DICT,ERROR_exit(1),e_format,mask); + } + flag = (~c&0777); + } + umask(flag); + } + else + { + umask(flag=umask(0)); + if(sflag) + sfprintf(sfstdout,"%s\n",fmtperm(~flag&0777)); + else + sfprintf(sfstdout,"%0#4o\n",flag); + } + return(0); +} + diff --git a/usr/src/lib/libshell/common/bltins/whence.c b/usr/src/lib/libshell/common/bltins/whence.c new file mode 100644 index 0000000000..7d05a741d6 --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/whence.c @@ -0,0 +1,273 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * command [-pvVx] name [arg...] + * whence [-afvp] name... + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <error.h> +#include "shtable.h" +#include "name.h" +#include "path.h" +#include "shlex.h" +#include "builtins.h" + +#define P_FLAG 1 +#define V_FLAG 2 +#define A_FLAG 4 +#define F_FLAG 010 +#define X_FLAG 020 + +static int whence(Shell_t *,char**, int); + +/* + * command is called with argc==0 when checking for -V or -v option + * In this case return 0 when -v or -V or unknown option, otherwise + * the shift count to the command is returned + */ +int b_command(register int argc,char *argv[],void *extra) +{ + register int n, flags=0; + register Shell_t *shp = (Shell_t*)extra; + opt_info.index = opt_info.offset = 0; + while((n = optget(argv,sh_optcommand))) switch(n) + { + case 'p': + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,"-p"); + sh_onstate(SH_DEFPATH); + break; + case 'v': + flags |= X_FLAG; + break; + case 'V': + flags |= V_FLAG; + break; + case 'x': + shp->xargexit = 1; + break; + case ':': + if(argc==0) + return(0); + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + if(argc==0) + return(0); + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + if(argc==0) + return(flags?0:opt_info.index); + argv += opt_info.index; + if(error_info.errors || !*argv) + errormsg(SH_DICT,ERROR_usage(2),"%s", optusage((char*)0)); + return(whence(shp,argv, flags)); +} + +/* + * for the whence command + */ +int b_whence(int argc,char *argv[],void *extra) +{ + register int flags=0, n; + register Shell_t *shp = (Shell_t*)extra; + NOT_USED(argc); + if(*argv[0]=='t') + flags = V_FLAG; + while((n = optget(argv,sh_optwhence))) switch(n) + { + case 'a': + flags |= A_FLAG; + /* FALL THRU */ + case 'v': + flags |= V_FLAG; + break; + case 'f': + flags |= F_FLAG; + break; + case 'p': + flags |= P_FLAG; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); + break; + } + argv += opt_info.index; + if(error_info.errors || !*argv) + errormsg(SH_DICT,ERROR_usage(2),optusage((char*)0)); + return(whence(shp, argv, flags)); +} + +static int whence(Shell_t *shp,char **argv, register int flags) +{ + register const char *name; + register Namval_t *np; + register const char *cp; + register int aflag,r=0; + register const char *msg; + int tofree; + Dt_t *root; + Namval_t *nq; + char *notused; +#ifdef PATH_BFPATH + Pathcomp_t *pp; +#endif + int notrack = 1; + while(name= *argv++) + { + tofree=0; + aflag = ((flags&A_FLAG)!=0); + cp = 0; + np = 0; +#ifdef PATH_BFPATH + pp = 0; +#endif + if(flags&P_FLAG) + goto search; + /* reserved words first */ + if(sh_lookup(name,shtab_reserved)) + { + sfprintf(sfstdout,"%s%s\n",name,(flags&V_FLAG)?sh_translate(is_reserved):""); + if(!aflag) + continue; + aflag++; + } + /* non-tracked aliases */ + if((np=nv_search(name,shp->alias_tree,0)) + && !nv_isnull(np) && !(notrack=nv_isattr(np,NV_TAGGED)) + && (cp=nv_getval(np))) + { + if(flags&V_FLAG) + { + if(nv_isattr(np,NV_EXPORT)) + msg = sh_translate(is_xalias); + else + msg = sh_translate(is_alias); + sfprintf(sfstdout,msg,name); + } + sfputr(sfstdout,sh_fmtq(cp),'\n'); + if(!aflag) + continue; + cp = 0; + aflag++; + } + /* built-ins and functions next */ + root = (flags&F_FLAG)?shp->bltin_tree:shp->fun_tree; + if(np= nv_bfsearch(name, root, &nq, ¬used)) + { + if(is_abuiltin(np) && nv_isnull(np)) + goto search; + cp = ""; + if(flags&V_FLAG) + { + if(nv_isnull(np)) + cp = sh_translate(is_ufunction); + else if(is_abuiltin(np)) + cp = sh_translate(is_builtin); + else + cp = sh_translate(is_function); + } + sfprintf(sfstdout,"%s%s\n",name,cp); + if(!aflag) + continue; + cp = 0; + aflag++; + } + search: + if(sh_isstate(SH_DEFPATH)) + { + cp=0; + notrack=1; + } +#ifdef PATH_BFPATH + if(path_search(name,pp,2)) + cp = name; + else + { + cp = stakptr(PATH_OFFSET); + if(*cp==0) + cp = 0; + else if(*cp!='/') + { + cp = path_fullname(cp); + tofree=1; + } + } +#else + if(path_search(name,cp,2)) + cp = name; + else + cp = shp->lastpath; + shp->lastpath = 0; +#endif + if(cp) + { + if(flags&V_FLAG) + { + if(*cp!= '/') + { +#ifdef PATH_BFPATH + if(!np && (np=nv_search(name,shp->track_tree,0))) + sfprintf(sfstdout,"%s %s %s/%s\n",name,sh_translate(is_talias),path_pwd(0),cp); + else if(!np || nv_isnull(np)) +#else + if(!np || nv_isnull(np)) +#endif + sfprintf(sfstdout,"%s%s\n",name,sh_translate(is_ufunction)); + continue; + } + sfputr(sfstdout,sh_fmtq(name),' '); + /* built-in version of program */ + if(*cp=='/' && (np=nv_search(cp,shp->bltin_tree,0))) + msg = sh_translate(is_builtver); + /* tracked aliases next */ + else if(!notrack || strchr(name,'/')) + msg = sh_translate("is"); + else + msg = sh_translate(is_talias); + sfputr(sfstdout,msg,' '); + } + sfputr(sfstdout,sh_fmtq(cp),'\n'); + if(tofree) + free((char*)cp); + } + else if(aflag<=1) + { + r |= 1; + if(flags&V_FLAG) + { + sfprintf(sfstdout,sh_translate(e_found),sh_fmtq(name)); + sfputc(sfstdout,'\n'); + } + } + } + return(r); +} + diff --git a/usr/src/lib/libshell/common/builtins.mm b/usr/src/lib/libshell/common/builtins.mm new file mode 100644 index 0000000000..5713c48eab --- /dev/null +++ b/usr/src/lib/libshell/common/builtins.mm @@ -0,0 +1,631 @@ +.ds DT July 9, 1993 \" use troff -mm +.nr C 3 +.nr N 2 +.SA 1 \" right justified +.TL "311466-6713" "49059-6" \" charging case filing case +Guidelines for writing \f5ksh-93\fP built-in commands +.AU "David G. Korn" DGK FP 11267 8062 D-237 "(research!dgk)" +.AF +.TM 11267-930???-93 \" technical memo + TM numbers +.MT 4 +.AS 2 \" abstract start for TM +One of the features of \f5ksh93\fP, the latest version of \f5ksh\fP, +is the ability to add built-in commands at run time. +This feature only works on operating systems that have the ability +to load and link code into the current process at run time. +Some examples of the systems that have this feature +are System V Release 4, Solaris, Sun OS, HP-UX Release 8 and above, +AIX 3.2 and above, and Microsoft Windows systems. +.P +This memo describes how to write and compile programs +to can be loaded into \f5ksh\fP at run time as built-in +commands. +.AE \" abstract end +.OK Shell "Command interpreter" Language UNIX \" keyword +.MT 1 \" memo type +.H 1 INTRODUCTION +A built-in command is executed without creating a separate process. +Instead, the command is invoked as a C function by \f5ksh\fP. +If this function has no side effects in the shell process, +then the behavior of this built-in is identical to that of +the equivalent stand-alone command. The primary difference +in this case is performance. The overhead of process creation +is eliminated. For commands of short duration, the effect +can be dramatic. For example, on SUN OS 4.1, the time do +run \f5wc\fP on a small file of about 1000 bytes, runs +about 50 times faster as a built-in command. +.P +In addition, built-in commands that have side effects on the +shell environment can be written. +This is usually done to extend the application domain for +shell programming. For example, an X-windows extension +that makes heavy use of the shell variable namespace +was added as a group of built-ins commands that +are added at run time. +The result is a windowing shell that can be used to write +X-windows applications. +.P +While there are definite advantages to adding built-in +commands, there are some disadvantages as well. +Since the built-in command and \f5ksh\fP share the same +address space, a coding error in the built-in program +may affect the behavior of \f5ksh\fP; perhaps causing +it to core dump or hang. +Debugging is also more complex since your code is now +a part of a larger entity. +The isolation provided by a separate process +guarantees that all resources used by the command +will be freed when the command completes. +Also, since the address space of \f5ksh\fP will be larger, +this may increase the time it takes \f5ksh\fP to fork() and +exec() a non-builtin command. +It makes no sense to add a built-in command that takes +a long time to run or that is run only once, since the performance +benefits will be negligible. +Built-ins that have side effects in the current shell +environment have the disadvantage of increasing the +coupling between the built-in and \f5ksh\fP making +the overall system less modular and more monolithic. +.P +Despite these drawbacks, in many cases extending +\f5ksh\fP by adding built-in +commands makes sense and allows reuse of the shell +scripting ability in an application specific domain. +This memo describes how to write \f5ksh\fP extensions. +.H 1 "WRITING BUILT-IN COMMANDS" +There is a development kit available for writing \f5ksh\fP +built-ins. The development kit has three directories, +\f5include\fP, \f5lib\fP, and \f5bin\fP. +The \f5include\fP directory contains a sub-directory +named \f5ast\fP that contains interface prototypes +for functions that you can call from built-ins. The \f5lib\fP +directory contains the \fBast\fP library\*F +.FS +\fBast\fP stands for Advanced Software Technology +.FE +and a library named \fBlibcmd\fP that contains a version +of several of the standard POSIX\*(Rf +.RS +.I "POSIX \- Part 2: Shell and Utilities," +IEEE Std 1003.2-1992, ISO/IEC 9945-2:1993. +.RF +utilities that can be made run time built-ins. +It is best to set the value of the environment variable +\fB\s-1PACKAGE_\s+1ast\fP to the pathname of the directory +containing the development kit. +Users of \f5nmake\fP\*(Rf +.RS +Glenn Fowler, +Nmake reference needed +.RF +2.3 and above will then be able to +use the rule +.nf +.in .5i +\f5:PACKAGE: ast\fP +.in +.fi +in their makefiles and not have to specify any \f5-I\fP switches +to the compiler. +.P +A built-in command has a calling convention similar to +the \f5main\fP function of a program, +.nf +.in .5i +\f5int main(int argc, char *argv[])\fP. +.in +.fi +However, instead of \f5main\fP, you must use the function name +\f5b_\fP\fIname\fP, where \fIname\fP is the name +of the built-in you wish to define. +The built-in function takes a third +\f5void*\fP argument which you can define as \f5NULL\fP. +Instead of \f5exit\fP, you need to use \f5return\fP +to terminate your command. +The return value, will become the exit status of the command. +.P +The steps necessary to create and add a run time built-in are +illustrated in the following simple example. +Suppose, you wish to add a built-in command named \f5hello\fP +which requires one argument and prints the word hello followed +by its argument. First, write the following program in the file +\f5hello.c\fP: +.nf +.in .5i +\f5#include <stdio.h> +int b_hello(int argc, char *argv[], void *context) +{ + if(argc != 2) + { + fprintf(stderr,"Usage: hello arg\en"); + return(2); + } + printf("hello %s\en",argv[1]); + return(0); +}\fP +.in +.fi +.P +Next, the program needs to be compiled. +On some systems it is necessary to specify a compiler +option to produce position independent code +for dynamic linking. +If you do not compile with \f5nmake\fP +it is important to specify the a special include directory +when compiling built-ins. +.nf +.in .5i +\f5cc -pic -I$PACKAGE_ast/include -c hello.c\fP +.in +.fi +since the special version of \f5<stdio.h>\fP +in the development kit is required. +This command generates \f5hello.o\fP in the current +directory. +.P +On some systems, you cannot load \f5hello.o\fP directly, +you must build a shared library instead. +Unfortunately, the method for generating a shared library +differs with operating system. +However, if you are building with the AT\&T \f5nmake\fP +program you can use the \f5:LIBRARY:\fP rule to specify +this in a system independent fashion. +In addition, if you have several built-ins, it is desirable +to build a shared library that contains them all. +.P +The final step is using the built-in. +This can be done with the \f5ksh\fP command \f5builtin\fP. +To load the shared library \f5hello.so\fP and to add +the built-in \f5hello\fP, invoke the command, +.nf +.in .5i +\f5builtin -f hello hello\fP +.in +.fi +The suffix for the shared library can be omitted in +which case the shell will add an appropriate suffix +for the system that it is loading from. +Once this command has been invoked, you can invoke \f5hello\fP +as you do any other command. +.P +It is often desirable to make a command \fIbuilt-in\fP +the first time that it is referenced. The first +time \f5hello\fP is invoked, \f5ksh\fP should load and execute it, +whereas for subsequent invocations \f5ksh\fP should just execute the built-in. +This can be done by creating a file named \f5hello\fP +with the following contents: +.nf +.in .5i +\f5function hello +{ + unset -f hello + builtin -f hello hello + hello "$@" +}\fP +.in +.fi +This file \f5hello\fP needs to be placed in a directory that is +in your \fB\s-1FPATH\s+1\fP variable. In addition, the full +pathname for \f5hello.so\fP should be used in this script +so that the run time loader will be able to find this shared library +no matter where the command \f5hello\fP is invoked. +.H 1 "CODING REQUIREMENTS AND CONVENTIONS" +As mentioned above, the entry point for built-ins must be of +the form \f5b_\fP\fIname\fP. +Your built-ins can call functions from the standard C library, +the \fBast\fP library, interface functions provided by \f5ksh\fP, +and your own functions. +You should avoid using any global symbols beginning with +.BR sh_ , +.BR nv_ , +and +.B ed_ +since these are used by \f5ksh\fP itself. +In addition, \f5#define\fP constants in \f5ksh\fP interface +files, use symbols beginning with \fBSH_\fP to that you should +avoid using names beginning with \fBSH_\fP. +.H 2 "Header Files" +The development kit provides a portable interface +to the C library and to libast. +The header files in the development kit are compatible with +K&R C\*(Rf, +.RS +Brian W. Kernighan and Dennis M. Ritchie, +.IR "The C Programming Language" , +Prentice Hall, 1978. +.RF +ANSI-C\*(Rf, +.RS +American National Standard for Information Systems \- Programming +Language \- C, ANSI X3.159-1989. +.RF +and C++\*(Rf. +.RS +Bjarne Stroustroup, +.IR "C++" , +Addison Wesley, xxxx +.RF +.P +The best thing to do is to include the header file \f5<shell.h>\fP. +This header file causes the \f5<ast.h>\fP header, the +\f5<error.h>\fP header and the \f5<stak.h>\fP +header to be included as well as defining prototypes +for functions that you can call to get shell +services for your builtins. +The header file \f5<ast.h>\fP +provides prototypes for many \fBlibast\fP functions +and all the symbol and function definitions from the +ANSI-C headers, \f5<stddef.h>\fP, +\f5<stdlib.h>\fP, \f5<stdarg.h>\fP, \f5<limits.h>\fP, +and \f5<string.h>\fP. +It also provides all the symbols and definitions for the +POSIX\*(Rf +.RS +.I "POSIX \- Part 1: System Application Program Interface," +IEEE Std 1003.1-1990, ISO/IEC 9945-1:1990. +.RF +headers \f5<sys/types.h>\fP, \f5<fcntl.h>\fP, and +\f5<unistd.h>\fP. +You should include \f5<ast.h>\fP instead of one or more of +these headers. +The \f5<error.h>\fP header provides the interface to the error +and option parsing routines defined below. +The \f5<stak.h>\fP header provides the interface to the memory +allocation routines described below. +.P +Programs that want to use the information in \f5<sys/stat.h>\fP +should include the file \f5<ls.h>\fP instead. +This provides the complete POSIX interface to \f5stat()\fP +related functions even on non-POSIX systems. +.P +.H 2 "Input/Output" +\f5ksh\fP uses \fBsfio\fP, +the Safe/Fast I/O library\*(Rf, +.RS +David Korn and Kiem-Phong Vo, +.IR "SFIO - A Safe/Fast Input/Output library," +Proceedings of the Summer Usenix, +pp. , 1991. +.RF +to perform all I/O operations. +The \fBsfio\fP library, which is part of \fBlibast\fP, +provides a superset of the functionality provided by the standard +I/O library defined in ANSI-C. +If none of the additional functionality is required, +and if you are not familiar with \fBsfio\fP and +you do not want to spend the time learning it, +then you can use \fBsfio\fP via the \fBstdio\fP library +interface. The development kit contains the header \f5<stdio.h>\fP +which maps \fBstdio\fP calls to \fBsfio\fP calls. +In most instances the mapping is done +by macros or inline functions so that there is no overhead. +The man page for the \fBsfio\fP library is in an Appendix. +.P +However, there are some very nice extensions and +performance improvements in \fBsfio\fP +and if you plan any major extensions I recommend +that you use it natively. +.H 2 "Error Handling" +For error messages it is best to use the \fBast\fP library +function \f5errormsg()\fP rather that sending output to +\f5stderr\fP or the equivalent \f5sfstderr\fP directly. +Using \f5errormsg()\fP will make error message appear +more uniform to the user. +Furthermore, using \f5errormsg()\fP should make it easier +to do error message translation for other locales +in future versions of \f5ksh\fP. +.P +The first argument to +\f5errormsg()\fP specifies the dictionary in which the string +will be searched for translation. +The second argument to \f5errormsg()\fP contains that error type +and value. The third argument is a \fIprintf\fP style format +and the remaining arguments are arguments to be printed +as part of the message. A new-line is inserted at the +end of each message and therefore, should not appear as +part of the format string. +The second argument should be one of the following: +.VL .5i +.LI \f5ERROR_exit(\fP\fIn\fP\f5)\fP: +If \fIn\fP is not-zero, the builtin will exit value \fIn\fP after +printing the message. +.LI \f5ERROR_system(\fP\fIn\fP\f5)\fP: +Exit builtin with exit value \fIn\fP after printing the message. +The message will display the message corresponding to \f5errno\fP +enclosed within \f5[\ ]\fP at the end of the message. +.LI \f5ERROR_usage(\fP\fIn\fP\f5)\fP: +Will generate a usage message and exit. If \fIn\fP is non-zero, +the exit value will be 2. Otherwise the exit value will be 0. +.LI \f5ERROR_debug(\fP\fIn\fP\f5)\fP: +Will print a level \fIn\fP debugging message and will then continue. +.LI \f5ERROR_warn(\fP\fIn\fP\f5)\fP: +Prints a warning message. \fIn\fP is ignored. +.H 2 "Option Parsing" +The first thing that a built-in should do is to check +the arguments for correctness and to print any usage +messages on standard error. +For consistency with the rest of \f5ksh\fP, it is best +to use the \f5libast\fP functions \f5optget()\fP and +\f5optusage()\fPfor this +purpose. +The header \f5<error.h>\fP included prototypes for +these functions. +The \f5optget()\fP function is similar to the +System V C library function \f5getopt()\fP, +but provides some additional capabilities. +Built-ins that use \f5optget()\fP provide a more +consistent user interface. +.P +The \f5optget()\fP function is invoked as +.nf +.in .5i +\f5int optget(char *argv[], const char *optstring)\fP +.in +.fi +where \f5argv\fP is the argument list and \f5optstring\fP +is a string that specifies the allowable arguments and +additional information that is used to format \fIusage\fP +messages. +In fact a complete man page in \f5troff\fP or \f5html\fP +can be generated by passing a usage string as described +by the \f5getopts\fP command. +Like \f5getopt()\fP, +single letter options are represented by the letter itself, +and options that take a string argument are followed by the \f5:\fP +character. +Option strings have the following special characters: +.VL .5i +.LI \f5:\fP +Used after a letter option to indicate that the option +takes an option argument. +The variable \f5opt_info.arg\fP will point to this +value after the given argument is encountered. +.LI \f5#\fP +Used after a letter option to indicate that the option +can only take a numerical value. +The variable \f5opt_info.num\fP will contain this +value after the given argument is encountered. +.LI \f5?\fP +Used after a \f5:\fP or \f5#\fP (and after the optional \f5?\fP) +to indicate the the +preceding option argument is not required. +.LI \f5[\fP...\f5]\fP +After a \f5:\fP or \f5#\fP, the characters contained +inside the brackets are used to identify the option +argument when generating a \fIusage\fP message. +.LI \fIspace\fP +The remainder of the string will only be used when generating +usage messages. +.LE +.P +The \f5optget()\fP function returns the matching option letter if +one of the legal option is matched. +Otherwise, \f5optget()\fP returns +.VL .5i +.LI \f5':'\fP +If there is an error. In this case the variable \f5opt_info.arg\fP +contains the error string. +.LI \f50\fP +Indicates the end of options. +The variable \f5opt_info.index\fP contains the number of arguments +processed. +.LI \f5'?'\fP +A usage message has been required. +You normally call \f5optusage()\fP to generate and display +the usage message. +.LE +.P +The following is an example of the option parsing portion +of the \f5wc\fP utility. +.nf +.in +5 +\f5#include <shell.h> +while(1) switch(n=optget(argv,"xf:[file]")) +{ + case 'f': + file = opt_info.arg; + break; + case ':': + error(ERROR_exit(0), opt_info.arg); + break; + case '?': + error(ERROR_usage(2), opt_info.arg); + break; +}\fP +.in +.fi +.H 2 "Storage Management" +It is important that any memory used by your built-in +be returned. Otherwise, if your built-in is called frequently, +\f5ksh\fP will eventually run out of memory. +You should avoid using \f5malloc()\fP for memory that must +be freed before returning from you built-in, because by default, +\f5ksh\fP will terminate you built-in in the event of an +interrupt and the memory will not be freed. +.P +The best way to to allocate variable sized storage is +through calls to the \fBstak\fP library +which is included in \fBlibast\fP +and which is used extensively by \f5ksh\fP itself. +Objects allocated with the \f5stakalloc()\fP +function are freed when you function completes +or aborts. +The \fBstak\fP library provides a convenient way to +build variable length strings and other objects dynamically. +The man page for the \fBstak\fP library is contained +in the Appendix. +.P +Before \f5ksh\fP calls each built-in command, it saves +the current stack location and restores it after +it returns. +It is not necessary to save and restore the stack +location in the \f5b_\fP entry function, +but you may want to write functions that use this stack +are restore it when leaving the function. +The following coding convention will do this in +an efficient manner: +.nf +.in .5i +\fIyourfunction\fP\f5() +{ + char *savebase; + int saveoffset; + if(saveoffset=staktell()) + savebase = stakfreeze(0); + \fP...\f5 + if(saveoffset) + stakset(savebase,saveoffset); + else + stakseek(0); +}\fP +.in +.fi +.H 1 "CALLING \f5ksh\fP SERVICES" +Some of the more interesting applications are those that extend +the functionality of \f5ksh\fP in application specific directions. +A prime example of this is the X-windows extension which adds +builtins to create and delete widgets. +The \fBnval\fP library is used to interface with the shell +name space. +The \fBshell\fP library is used to access other shell services. +.H 2 "The nval library" +A great deal of power is derived from the ability to use +portions of the hierarchal variable namespace provided by \f5ksh-93\fP +and turn these names into active objects. +.P +The \fBnval\fP library is used to interface with shell +variables. +A man page for this file is provided in an Appendix. +You need to include the header \f5<nval.h>\fP +to access the functions defined in the \fBnval\fP library. +All the functions provided by the \fBnval\fP library begin +with the prefix \f5nv_\fP. +Each shell variable is an object in an associative table +that is referenced by name. +The type \f5Namval_t*\fP is pointer to a shell variable. +To operate on a shell variable, you first get a handle +to the variable with the \f5nv_open()\fP function +and then supply the handle returned as the first +argument of the function that provides an operation +on the variable. +You must call \f5nv_close()\fP when you are finished +using this handle so that the space can be freed once +the value is unset. +The two most frequent operations are to get the value of +the variable, and to assign value to the variable. +The \f5nv_getval()\fP returns a pointer the the +value of the variable. +In some cases the pointer returned is to a region that +will be overwritten by the next \f5nv_getval()\fP call +so that if the value isn't used immediately, it should +be copied. +Many variables can also generate a numeric value. +The \f5nv_getnum()\fP function returns a numeric +value for the given variable pointer, calling the +arithmetic evaluator if necessary. +.P +The \f5nv_putval()\fP function is used to assign a new +value to a given variable. +The second argument to \f5putval()\fP is the value +to be assigned +and the third argument is a \fIflag\fP which +is used in interpreting the second argument. +.P +Each shell variable can have one or more attributes. +The \f5nv_isattr()\fP is used to test for the existence +of one or more attributes. +See the appendix for a complete list of attributes. +.P +By default, each shell variable passively stores the string you +give with with \f5nv_putval()\fP, and returns the value +with \f5getval()\fP. However, it is possible to turn +any node into an active entity by assigning functions +to it that will be called whenever \f5nv_putval()\fP +and/or \f5nv_getval()\fP is called. +In fact there are up to five functions that can +associated with each variable to override the +default actions. +The type \f5Namfun_t\fP is used to define these functions. +Only those that are non-\f5NULL\fP override the +default actions. +To override the default actions, you must allocate an +instance of \f5Namfun_t\fP, and then assign +the functions that you wish to override. +The \f5putval()\fP +function is called by the \f5nv_putval()\fP function. +A \f5NULL\fP for the \fIvalue\fP argument +indicates a request to unset the variable. +The \fItype\fP argument might contain the \f5NV_INTEGER\fP +bit so you should be prepared to do a conversion if +necessary. +The \f5getval()\fP +function is called by \f5nv_getval()\fP +value and must return a string. +The \f5getnum()\fP +function is called by by the arithmetic evaluator +and must return double. +If omitted, then it will call \f5nv_getval()\fP and +convert the result to a number. +.P +The functionality of a variable can further be increased +by adding discipline functions that +can be associated with the variable. +A discipline function allows a script that uses your +variable to define functions whose name is +\fIvarname\fP\f5.\fP\fIdiscname\fP +where \fIvarname\fP is the name of the variable, and \fIdiscname\fP +is the name of the discipline. +When the user defines such a function, the \f5settrap()\fP +function will be called with the name of the discipline and +a pointer to the parse tree corresponding to the discipline +function. +The application determines when these functions are actually +executed. +By default, \f5ksh\fP defines \f5get\fP, +\f5set\fP, and \f5unset\fP as discipline functions. +.P +In addition, it is possible to provide a data area that +will be passed as an argument to +each of these functions whenever any of these functions are called. +To have private data, you need to define and allocate a structure +that looks like +.nf +.in .5i +\f5struct \fIyours\fP +{ + Namfun_t fun; + \fIyour_data_fields\fP; +};\fP +.in +.fi +.H 2 "The shell library" +There are several functions that are used by \f5ksh\fP itself +that can also be called from built-in commands. +The man page for these routines are in the Appendix. +.P +The \f5sh_addbuiltin()\fP function can be used to add or delete +builtin commands. It takes the name of the built-in, the +address of the function that implements the built-in, and +a \f5void*\fP pointer that will be passed to this function +as the third agument whenever it is invoked. +If the function address is \f5NULL\fP, the specified built-in +will be deleted. However, special built-in functions cannot +be deleted or modified. +.P +The \f5sh_fmtq()\fP function takes a string and returns +a string that is quoted as necessary so that it can +be used as shell input. +This function is used to implement the \f5%q\fP option +of the shell built-in \f5printf\fP command. +.P +The \f5sh_parse()\fP function returns a parse tree corresponding +to a give file stream. The tree can be executed by supplying +it as the first argument to +the \f5sh_trap()\fP function and giving a value of \f51\fP as the +second argument. +Alternatively, the \f5sh_trap()\fP function can parse and execute +a string by passing the string as the first argument and giving \f50\fP +as the second argument. +.P +The \f5sh_isoption()\fP function can be used to set to see whether one +or more of the option settings is enabled. diff --git a/usr/src/lib/libshell/common/data/aliases.c b/usr/src/lib/libshell/common/data/aliases.c new file mode 100644 index 0000000000..577f098f1d --- /dev/null +++ b/usr/src/lib/libshell/common/data/aliases.c @@ -0,0 +1,58 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#include <ast.h> +#include <signal.h> +#include "FEATURE/options" +#include "FEATURE/dynamic" +#include "shtable.h" +#include "name.h" + +/* + * This is the table of built-in aliases. These should be exported. + */ + +const struct shtable2 shtab_aliases[] = +{ +#if SHOPT_FS_3D + "2d", NV_NOFREE, "set -f;_2d", +#endif /* SHOPT_FS_3D */ + "autoload", NV_NOFREE, "typeset -fu", + "command", NV_NOFREE, "command ", + "fc", NV_NOFREE, "hist", + "float", NV_NOFREE, "typeset -lE", + "functions", NV_NOFREE, "typeset -f", + "hash", NV_NOFREE, "alias -t --", + "history", NV_NOFREE, "hist -l", + "integer", NV_NOFREE, "typeset -li", + "nameref", NV_NOFREE, "typeset -n", + "nohup", NV_NOFREE, "nohup ", + "r", NV_NOFREE, "hist -s", + "redirect", NV_NOFREE, "command exec", + "source", NV_NOFREE, "command .", +#ifdef SIGTSTP + "stop", NV_NOFREE, "kill -s STOP", + "suspend", NV_NOFREE, "kill -s STOP $$", +#endif /*SIGTSTP */ + "times", NV_NOFREE, "{ { time;} 2>&1;}", + "type", NV_NOFREE, "whence -v", + "", 0, (char*)0 +}; + diff --git a/usr/src/lib/libshell/common/data/bash_pre_rc.sh b/usr/src/lib/libshell/common/data/bash_pre_rc.sh new file mode 100644 index 0000000000..98e6aba833 --- /dev/null +++ b/usr/src/lib/libshell/common/data/bash_pre_rc.sh @@ -0,0 +1,221 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +# +# bash compatibility startup script +# +# Author: +# Karsten Fleischer +# Omnium Software Engineering +# An der Luisenburg 7 +# D-51379 Leverkusen +# Germany +# +# <K.Fleischer@omnium.de> +# + +alias declare=typeset + +nameref FUNCNAME=.sh.fun +integer SHLVL +export SHLVL +SHLVL+=1 + +if [[ ! $EUID ]] +then EUID=$(id -u) + readonly EUID +fi + +if [[ ! $UID ]] +then UID=$(id -u) + readonly UID +fi + +readonly SHELLOPTS +if ! shopt -qo restricted; then + IFS=: + for i in $SHELLOPTS + do + [[ -n "$i" ]] && set -o $i + done + unset IFS +fi +function SHELLOPTS.get +{ + .sh.value=$(shopt -so) + .sh.value=${.sh.value//+([[:space:]])on*([[:space:]])/:} + .sh.value=${.sh.value%:} +} + +set -A GROUPS $(id -G) +function GROUPS.set +{ + return 1 +} +function GROUPS.unset +{ + unset -f GROUPS.set + unset -f GROUPS.unset +} + +typeset -A DIRSTACK +function DIRSTACK.get +{ + set -A .sh.value $(dirs) +} +function DIRSTACK.set +{ + integer index + index=_push_max-.sh.subscript + (( index == _push_max || index < _push_top )) && return + _push_stack[index]=${.sh.value} +} +function DIRSTACK.unset +{ + unset -f DIRSTACK.get + unset -f DIRSTACK.set + unset -f DIRSTACK.unset +} + +function PS1.set +{ + typeset prefix remaining=${.sh.value} var= n= k= + while [[ $remaining ]] + do prefix=${remaining%%'\'*} + remaining=${remaining#$prefix} + var+="$prefix" + case ${remaining:1:1} in + t) var+="\$(printf '%(%H:%M:%S)T')";; + d) var+="\$(printf '%(%a %b:%d)T')";; + n) var+=$'\n';; + s) var+=ksh;; + w) var+="\$(pwd)";; + W) var+="\$(basename \"\$(pwd)\")";; + u) var+=$USER;; + h) var+=$(hostname);; + '#') var+=!;; + !) var+=!;; + '$') if (( $(id -u) == 0 )) + then var+='#' + else var+='$' + fi;; + '\') var+='\\';; + '['|']') ;; + [0-7]) case ${remaining:1:3} in + [0-7][0-7][0-7]) + k=4;; + [0-7][0-7]) + k=3;; + *) k=2;; + esac + eval n="\$'"${remaining:0:k}"'" + var+=$n + remaining=${remaining:k} + continue + ;; + "") ;; + *) var+='\'${remaining:0:2};; + esac + remaining=${remaining:2} + done + .sh.value=$var +} +function logout +{ + if shopt -q login_shell; then + exit + else + print ${BASH##*/}: $0: not login shell: use 'exit' >&2 + return 1 + fi +} +PS1="bash$ " + +function source +{ + if ! shopt -qpo posix; then + unset OPATH + typeset OPATH=$PATH + typeset PATH=$PATH + if shopt -q sourcepath; then + PATH=$OPATH:. + else + PATH=. + fi + fi + . "$@" +} +unalias . +alias .=source + +alias enable=builtin + +function help +{ + man=--man + [[ $1 == -s ]] && man=--short && shift + b=$(builtin) + for i + do + for j in $b + do + [[ $i == $j ]] && $j $man + done + done +} + +function cd +{ + + local msg + local args + local i + local a + local ret + + if ! shopt -q cdable_vars; then + command cd "$@" + else + msg=$(command cd "$@" 2>&1) + ret=$? + if [[ $ret != 0 ]]; then + for i + do + case $i in + -*) args="$args $i" ;; + */*) args="$args $i" ;; + *) eval a="$"$i + if [[ -n $a ]]; then args="$args $a" + else args="$args $i" + fi + ;; + esac + done + + command cd $args + else + print -- $msg + return $ret + fi + fi +} + +typeset BASH=$0 +! shopt -qo posix && HISTFILE=~/.bash_history +HOSTNAME=$(hostname) diff --git a/usr/src/lib/libshell/common/data/builtins.c b/usr/src/lib/libshell/common/data/builtins.c new file mode 100644 index 0000000000..ddbe5c74ea --- /dev/null +++ b/usr/src/lib/libshell/common/data/builtins.c @@ -0,0 +1,1833 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <shell.h> +#include <signal.h> +#include "defs.h" +#include "shtable.h" +#include "ulimit.h" +#include "name.h" +#include "version.h" +#if KSHELL +# include "builtins.h" +# include "jobs.h" +# include "FEATURE/cmds" +# define bltin(x) (b_##x) + /* The following is for builtins that do not accept -- options */ +# define Bltin(x) (B_##x) +#else +# define bltin(x) 0 +#endif + +#ifndef SH_CMDLIB_DIR +# define SH_CMDLIB_DIR "/opt/ast/bin" +#endif +#if defined(SHOPT_CMDLIB_DIR) && !defined(SHOPT_CMDLIB_HDR) +# define SHOPT_CMDLIB_HDR <cmdlist.h> +#endif +#define Q(f) #f /* libpp cpp workaround -- fixed 2005-04-11 */ +#define CMDLIST(f) SH_CMDLIB_DIR "/" Q(f), NV_BLTIN|NV_NOFREE, bltin(f), + +#undef basename +#undef dirname + +/* + * The order up through "[" is significant + */ +const struct shtable3 shtab_builtins[] = +{ + "login", NV_BLTIN|BLT_ENV|BLT_SPC, Bltin(login), + "exec", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(exec), + "set", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(set), + ":", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(true), + "true", NV_BLTIN|BLT_ENV, bltin(true), + "command", NV_BLTIN|BLT_ENV|BLT_EXIT, bltin(command), + "cd", NV_BLTIN|BLT_ENV, bltin(cd), + "break", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(break), + "continue", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(break), + "typeset", NV_BLTIN|BLT_ENV|BLT_SPC|BLT_DCL,bltin(typeset), + "test", NV_BLTIN|BLT_ENV|NV_NOFREE, bltin(test), + "[", NV_BLTIN|BLT_ENV, bltin(test), + "let", NV_BLTIN|BLT_ENV, bltin(let), + "export", NV_BLTIN|BLT_SPC|BLT_DCL, bltin(readonly), +#if SHOPT_BASH + "local", NV_BLTIN|BLT_ENV|BLT_SPC|BLT_DCL,bltin(typeset), +#endif +#if _bin_newgrp || _usr_bin_newgrp + "newgrp", NV_BLTIN|BLT_ENV|BLT_SPC, Bltin(login), +#endif /* _bin_newgrp || _usr_bin_newgrp */ + ".", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(dot_cmd), + "alias", NV_BLTIN|BLT_SPC|BLT_DCL, bltin(alias), + "hash", NV_BLTIN|BLT_SPC|BLT_DCL, bltin(alias), + "exit", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(return), + "eval", NV_BLTIN|BLT_ENV|BLT_SPC|BLT_EXIT,bltin(eval), + "fc", NV_BLTIN|BLT_ENV|BLT_EXIT, bltin(hist), + "hist", NV_BLTIN|BLT_ENV|BLT_EXIT, bltin(hist), + "readonly", NV_BLTIN|BLT_ENV|BLT_SPC|BLT_DCL,bltin(readonly), + "return", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(return), + "shift", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(shift), + "trap", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(trap), + "unalias", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(unalias), + "unset", NV_BLTIN|BLT_ENV|BLT_SPC, bltin(unset), + "builtin", NV_BLTIN, bltin(builtin), +#if SHOPT_ECHOPRINT + "echo", NV_BLTIN|BLT_ENV, bltin(print), +#else + "echo", NV_BLTIN|BLT_ENV, Bltin(echo), +#endif /* SHOPT_ECHOPRINT */ +#ifdef JOBS +# ifdef SIGTSTP + "bg", NV_BLTIN|BLT_ENV, bltin(bg), + "fg", NV_BLTIN|BLT_ENV|BLT_EXIT, bltin(bg), + "disown", NV_BLTIN|BLT_ENV, bltin(bg), + "kill", NV_BLTIN|BLT_ENV|NV_NOFREE, bltin(kill), +# else + "/bin/kill", NV_BLTIN|BLT_ENV|NV_NOFREE, bltin(kill), +# endif /* SIGTSTP */ + "jobs", NV_BLTIN|BLT_ENV, bltin(jobs), +#endif /* JOBS */ + "false", NV_BLTIN|BLT_ENV, bltin(false), +SH_CMDLIB_DIR "/getconf",NV_BLTIN|BLT_ENV, bltin(getconf), + "getopts", NV_BLTIN|BLT_ENV, bltin(getopts), + "print", NV_BLTIN|BLT_ENV, bltin(print), + "printf", NV_BLTIN|NV_NOFREE, bltin(printf), + "pwd", NV_BLTIN|NV_NOFREE, bltin(pwd), + "read", NV_BLTIN|BLT_ENV, bltin(read), + "sleep", NV_BLTIN|NV_NOFREE, bltin(sleep), + "alarm", NV_BLTIN, bltin(alarm), + "ulimit", NV_BLTIN|BLT_ENV, bltin(ulimit), + "umask", NV_BLTIN|BLT_ENV, bltin(umask), +#ifdef _cmd_universe + "universe", NV_BLTIN|BLT_ENV, bltin(universe), +#endif /* _cmd_universe */ +#if SHOPT_FS_3D + "vpath", NV_BLTIN|BLT_ENV, bltin(vpath), + "vmap", NV_BLTIN|BLT_ENV, bltin(vpath), +#endif /* SHOPT_FS_3D */ + "wait", NV_BLTIN|BLT_ENV|BLT_EXIT, bltin(wait), + "type", NV_BLTIN|BLT_ENV, bltin(whence), + "whence", NV_BLTIN|BLT_ENV, bltin(whence), +#ifdef SHOPT_CMDLIB_HDR +#include SHOPT_CMDLIB_HDR +#else + CMDLIST(basename) + CMDLIST(chmod) + CMDLIST(dirname) + CMDLIST(head) + CMDLIST(mkdir) + CMDLIST(logname) + CMDLIST(cat) + CMDLIST(cmp) + CMDLIST(cut) + CMDLIST(uname) + CMDLIST(wc) + CMDLIST(sync) +#endif + "", 0, 0 +}; + + +const char sh_set[] = +"[a?Set the export attribute for each variable whose name does not " + "contain a \b.\b that you assign a value in the current shell " + "environment.]" +"[b?The shell writes a message to standard error as soon it detects that " + "a background job completes rather than waiting until the next prompt.]" +"[e?A simple command that has an non-zero exit status will cause the shell " + "to exit unless the simple command is:]{" + "[++?contained in an \b&&\b or \b||\b list.]" + "[++?the command immediately following \bif\b, \bwhile\b, or \buntil\b.]" + "[++?contained in the pipeline following \b!\b.]" +"}" +"[f?Pathname expansion is disabled.]" +"[h?Obsolete. Causes each command whose name has the syntax of an " + "alias to become a tracked aliase when it is first encountered.]" +"[k?This is obsolete. All arguments of the form \aname\a\b=\b\avalue\a " + "are removed and placed in the variable assignment list for " + "the command. Ordinarily, variable assignments must precede " + "command arguments.]" +"[m?When enabled, the shell runs background jobs in a separate process " + "group and displays a line upon completion. This mode is enabled " + "by default for interactive shells on systems that support job " + "control.]" +"[n?The shell reads commands and checks for syntax errors, but does " + "not execute the command. Usually specified on command invocation.]" +"[o]:?[option?If \aoption\a is not specified, the list of options and " + "their current settings will be written to standard output. When " + "invoked with a \b+\b the options will be written in a format " + "that can be reinput to the shell to restore the settings. " + "This option can be repeated to enable/disable multiple options. " + "The value of \aoption\a must be one of the following:]{" + "[+allexport?Equivalent to \b-a\b.]" + "[+bgnice?Runs background jobs at lower priorities.]" + "[+braceexpand?Equivalent to \b-B\b.] " + "[+emacs?Enables/disables \bemacs\b editing mode.]" + "[+errexit?Equivalent to \b-e\b.]" + "[+globstar?Equivalent to \b-G\b.]" + "[+gmacs?Enables/disables \bgmacs\b editing mode. \bgmacs\b " + "editing mode is the same as \bemacs\b editing mode " + "except for the handling of \b^T\b.]" +#if SHOPT_BASH + "[+hashall?Equivalent to \b-h\b and \b-o trackall\b. Available " + "in bash compatibility mode only.]" + "[+history?Enable command history. Available in bash " + "compatibility mode only. On by default in interactive " + "shells.]" +#endif +#if SHOPT_HISTEXPAND + "[+histexpand?Equivalent to \b-H\b.]" +#endif + "[+ignoreeof?Prevents an interactive shell from exiting on " + "reading an end-of-file.]" + "[+keyword?Equivalent to \b-k\b.]" + "[+markdirs?A trailing \b/\b is appended to directories " + "resulting from pathname expansion.]" + "[+monitor?Equivalent to \b-m\b.]" + "[+multiline?Use multiple lines when editing lines that are " + "longer than the window width.]" + "[+noclobber?Equivalent to \b-C\b.]" + "[+noexec?Equivalent to \b-n\b.]" + "[+noglob?Equivalent to \b-f\b.]" + "[+nolog?This has no effect. It is provided for backward " + "compatibility.]" + "[+notify?Equivalent to \b-b\b.]" + "[+nounset?Equivalent to \b-u\b.]" +#if SHOPT_BASH + "[+onecmd?Equivalent to \b-t\b. Available in bash compatibility " + "mode only.]" + "[+physical?Equivalent to \b-P\b. Available in bash " + "compatibility mode only.]" + "[+posix?Turn on POSIX compatibility. Available in bash " + "compatibility mode only. Bash in POSIX mode is not the " + "same as ksh.]" +#endif + "[+pipefail?A pipeline will not complete until all components " + "of the pipeline have completed, and the exit status " + "of the pipeline will be the value of the last " + "command to exit with non-zero exit status, or will " + "be zero if all commands return zero exit status.]" + "[+privileged?Equivalent to \b-p\b.]" + "[+showme?Simple commands preceded by a \b;\b will be traced " + "as if \b-x\b were enabled but not executed.]" + "[+trackall?Equivalent to \b-h\b.]" + "[+verbose?Equivalent to \b-v\b.]" + "[+vi?Enables/disables \bvi\b editing mode.]" + "[+viraw?Does not use canonical input mode when using \bvi\b " + "edit mode.]" + "[+xtrace?Equivalent to \b-x\b.]" +"}" +"[p?Privileged mode. Disabling \b-p\b sets the effective user id to the " + "real user id, and the effective group id to the real group id. " + "Enabling \b-p\b restores the effective user and group ids to their " + "values when the shell was invoked. The \b-p\b option is on " + "whenever the real and effective user id is not equal or the " + "real and effective group id is not equal. User profiles are " + "not processed when \b-p\b is enabled.]" +"[r?restricted. Enables restricted shell. This option cannot be unset once " + "enabled.]" +"[t?Obsolete. The shell reads one command and then exits.]" +"[u?If enabled, the shell displays an error message when it tries to expand " + "a variable that is unset.]" +"[v?Verbose. The shell displays its input onto standard error as it " + "reads it.]" +"[x?Execution trace. The shell will display each command after all " + "expansion and before execution preceded by the expanded value " + "of the \bPS4\b parameter.]" +#if SHOPT_BASH + "\fbash1\f" +#endif +#if SHOPT_BRACEPAT +"[B?Enable {...} group expansion. On by default.]" +#endif +"[C?Prevents existing regular files from being overwritten using the \b>\b " + "redirection operator. The \b>|\b redirection overrides this " + "\bnoclobber\b option.]" +"[G?Causes \b**\b by itself to also match all sub-directories during pathname " + "expansion.]" +#if SHOPT_HISTEXPAND + "[H?Enable \b!\b-style history expansion similar to \bcsh\b.]" +#endif +; + +const char sh_optbreak[] = +"[-1c?\n@(#)$Id: break (AT&T Research) 1999-04-07 $\n]" +USAGE_LICENSE +"[+NAME?break - break out of loop ]" +"[+DESCRIPTION?\bbreak\b is a shell special built-in that exits the " + "smallest enclosing \bfor\b, \bselect\b, \bwhile\b, or \buntil\b loop, " + "or the \an\a-th enclosing loop if \an\a is specified. " + "Execution continues at the command following the loop(s).]" +"[+?If \an\a is given, it must be a positive integer >= 1. If \an\a " + "is larger than the number of enclosing loops, the last enclosing " + "loop will be exited.]" +"\n" +"\n[n]\n" +"\n" +"[+EXIT STATUS?0]" +"[+SEE ALSO?\bcontinue\b(1), \breturn\b(1)]" +; + +const char sh_optcont[] = +"[-1c?\n@(#)$Id: continue (AT&T Research) 1999-04-07 $\n]" +USAGE_LICENSE +"[+NAME?continue - continue execution at top of the loop]" +"[+DESCRIPTION?\bcontinue\b is a shell special built-in that continues " + "execution at the top of smallest enclosing enclosing \bfor\b, " + "\bselect\b, \bwhile\b, or \buntil\b loop, if any; or the top of " + "the \an\a-th enclosing loop if \an\a is specified.]" +"[+?If \an\a is given, it must be a positive integer >= 1. If \an\a " + "is larger than the number of enclosing loops, the last enclosing " + " loop will be used.]" + +"\n" +"\n[n]\n" +"\n" +"[+SEE ALSO?\bbreak\b(1)]" +; + +const char sh_optalarm[] = "r [varname seconds]"; +const char sh_optalias[] = +"[-1c?\n@(#)$Id: alias (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?alias - define or display aliases]" +"[+DESCRIPTION?\balias\b creates or redefines alias definitions " + "or writes the existing alias definitions to standard output. " + "An alias definitions provides a string value that will replace " + "a command name when the command is read. Alias names can " + "contain any printable character which is not special to the shell. " + "If an alias value ends in a space or tab, then the word " + "following the command name the alias replaces is also checked " + "to see whether it is an alias.]" +"[+?If no \aname\as are specified then the names and values of all " + "aliases are written to standard output. Otherwise, for " + "each \aname\a that is specified, and \b=\b\avalue\a is not " + "specified, the current value of the alias corresponding to " + "\aname\a is written to standard output. If \b=\b\avalue\a is " + "specified, the alias \aname\a will be created or redefined.]" +"[+?\balias\b is built-in to the shell as a declaration command so that " + "field splitting and pathname expansion are not performed on " + "the arguments. Tilde expansion occurs on \avalue\a. An alias " + "definition only affects scripts read by the current shell " + "environment. It does not effect scripts run by this shell.]" +"[p?Causes the output to be in the form of alias commands that can be used " + "as input to the shell to recreate the current aliases.]" +"[t?Used for tracked aliases. These are aliases that connect a " + "command name to the pathname of the command and are reset " + "when the \bPATH\b variable is unset. The tracked aliases feature is " + "now obsolete.]" +"[x?Ignored, this option is obsolete.]" +"\n" +"\n[name[=value]...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?One or more \aname\a operands did not have an alias " + "definition, or an error occurred.]" +"}" + +"[+SEE ALSO?\bsh\b(1), \bunalias\b(1)]" +; + +const char sh_optbuiltin[] = +"[-1c?\n@(#)$Id: builtin (AT&T Research) 1999-07-10 $\n]" +USAGE_LICENSE +"[+NAME?builtin - add, delete, or display shell built-ins]" +"[+DESCRIPTION?\bbuiltin\b can be used to add, delete, or display " + "built-in commands in the current shell environment. A built-in command " + "executes in the current shell process and can have side effects in the " + "current shell. On most systems, the invocation time for built-in " + "commands is one or two orders of magnitude less than commands that " + "create a separate process.]" +"[+?For each \apathname\a specified, the basename of the pathname " + "determines the name of the built-in. For each basename, the shell looks " + "for a C level function in the current shell whose name is determined by " + "prepending \bb_\b to the built-in name. If \apathname\a contains a " + "\b/\b, then the built-in is bound to this pathname. A built-in bound to " + "a pathname will only be executed if \apathname\a is the first " + "executable found during a path search. Otherwise, built-ins are found " + "prior to performing the path search.]" +"[+?If no \apathname\a operands are specified, then \bbuiltin\b displays " + "the current list of built-ins, or just the special built-ins if \b-s\b " + "is specified, on standard output. The full pathname for built-ins that " + "are bound to pathnames are displayed.]" +"[+?Libraries containing built-ins can be specified with the \b-f\b " + "option. If the library contains a function named \blib_init\b(), this " + "function will be invoked with argument \b0\b when the library is " + "loaded. The \blib_init\b() function can load built-ins by invoking an " + "appropriate C level function. In this case there is no restriction on " + "the C level function name.]" +"[+?The C level function will be invoked with three arguments. The first " + "two are the same as \bmain\b() and the third one is a pointer.]" +"[+?\bbuiltin\b cannot be invoked from a restricted shell.]" +"[d?Deletes each of the specified built-ins. Special built-ins cannot be " + "deleted.]" +"[f]:[lib?On systems with dynamic linking, \alib\a names a shared " + "library to load and search for built-ins. Libraries are search for in " + "\b$PATH\b and system dependent library directories. The system " + "dependent shared library prefix and/or suffix may be omitted. Once a " + "library is loaded, its symbols become available for the current and " + "subsequent invocations of \bbuiltin\b. Multiple libraries can be " + "specified with separate invocations of \bbuiltin\b. Libraries are " + "searched in the reverse order in which they are specified.]" +"[s?Display only the special built-ins.]" +"\n" +"\n[pathname ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?All \apathname\a operands and \b-f\b options processed " + "successfully.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bwhence\b(1)]" +; + +const char sh_optcd[] = +"[-1c?\n@(#)$Id: cd (AT&T Research) 1999-06-05 $\n]" +USAGE_LICENSE +"[+NAME?cd - change working directory ]" +"[+DESCRIPTION?\bcd\b changes the current working directory of the " + "current shell environment.]" +"[+?In the first form with one operand, if \adirectory\a begins with " + "\b/\b, or if the first component is \b.\b or \b..\b, the " + "directory will be changed to this directory. If directory is \b-\b, " + "the directory will be changed to the last directory visited. " + "Otherwise, if the \bCDPATH\b environment variable is set, \bcd\b " + "searches for \adirectory\a relative to each directory named in " + "the colon separated list of directories defined by \bCDPATH\b. " + "If \bCDPATH\b not set, \bcd\b changes to the directory specified " + "by \adirectory\a.]" +"[+?In the second form, the first occurrence of the string \aold\a " + "contained in the pathname of the present working directory " + "is replaced by the string \anew\a and the resulting string " + "is used as the directory to which to change.]" +"[+?When invoked without operands and when the \bHOME\b environment " + "variable is set to a nonempty value, the directory named by " + "the \bHOME\b environment variable will be used. If \bHOME\b " + "is empty or unset, \bcd\b will fail.]" +"[+?When \bcd\b is successful, the \bPWD\b environment variable will be set " + "to the name of an absolute pathname that does not contain any " + "\b..\b components corresponding to the new directory. The " + "environment variable \bOLDPWD\b will be set to the previous " + "value of \bPWD\b. If the new directory is found by searching " + "the directories named by \bCDPATH\b, or if \adirectory\a is \b-\b, " + "or if the two operand form is used, the new value of \bPWD\b will be " + "written to standard output.]" +"[+?If both \b-L\b and \b-P\b are specified, the last one specified will " + "be used. If neither \b-P\b or \b-L\b is specified then the " + "behavior will be determined by the \bgetconf\b parameter " + "\bPATH_RESOLVE\b. If \bPATH_RESOLVE\b is \bphysical\b, " + "then the behavior will be as if \b-P\b were specified. Otherwise, " + "the behavior will be as if \b-L\b were specified.]" +"[L?Handle each pathname component \b..\b in a logical fashion by moving " + "up one level by name in the present working directory.]" +"[P?The present working directory is first converted to an absolute pathname " + "that does not contain symbolic link components and symbolic name " + "components are expanded in the resulting directory name.]" +"\n" +"\n[directory]\n" +"old new\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Directory successfully changed.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bpwd\b(1), \bgetconf\b(1)]" +; + +const char sh_optcommand[] = +"[-1c?\n@(#)$Id: command (AT&T Research) 2003-08-01 $\n]" +USAGE_LICENSE +"[+NAME?command - execute a simple command]" +"[+DESCRIPTION?Without \b-v\b or \b-V\b, \bcommand\b executes \acommand\a " + "with arguments given by \aarg\a, suppressing the shell function lookup " + "that normally occurs. In addition, if \acommand\a is a special " + "built-in command, then the special properties are removed so that " + "failures will not cause the script that executes it to terminate.]" +"[+?With the \b-v\b or \b-V\b options, \bcommand\b is equivalent to the " + "\bwhence\b(1) command.]" +"[p?Causes a default path to be searched rather than the one defined by the " + "value of \bPATH\b.]" +"[v?Equivalent to \bwhence\b \acommand\a [\aarg\a ...]].]" +"[x?If \acommand\a fails because there are too many \aarg\as, it will be " + "invoked multiple times with a subset of the arguments on each " + "invocation. Arguments that occur prior to the first word that expand " + "to multiple arguments and arguments that occur after the last word " + "that expands to multiple arguments will be passed on each invocation. " + "The exit status will be the maximum invocation exit status.]" +"[V?Equivalent to \bwhence \b-v\b \acommand\a [\aarg\a ...]].]" +"\n" +"\n[command [arg ...]]\n" +"\n" +"[+EXIT STATUS?If \acommand\a is invoked, the exit status of \bcommand\b " + "will be that of \acommand\a. Otherwise, it will be one of " + "the following:]{" + "[+0?\bcommand\b completed successfully.]" + "[+>0?\b-v\b or \b-V\b has been specified and an error occurred.]" + "[+126?\acommand\a was found but could not be invoked.]" + "[+127?\acommand\a could not be found.]" +"}" + +"[+SEE ALSO?\bwhence\b(1), \bgetconf\b(1)]" +; + +const char sh_optdot[] = +"[-1c?@(#)$Id: \b.\b (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?\b.\b - execute commands in the current environment]" +"[+DESCRIPTION?\b.\b is a special built-in command that executes commands " + "from a function or a file in the current environment.]" +"[+?If \aname\a refers to a function defined with the \bfunction\b \aname\a " + "syntax, the function executes in the current environment as " + "if it had been defined with the \aname\a\b()\b syntax so that " + "there is no scoping. Otherwise, commands from the file defined " + "by \aname\a are executed in the current environment. Note that " + "the complete script is read before it begins to execute so that " + "any aliases defined in this script will not take effect until " + "the script completes execution.]" +"[+?When \aname\a refers to a file, the \bPATH\b variable is searched " + "for the file containing commands. In this case execute permission " + "is not required for \aname\a.]" +"[+?If any \aarg\as are specified, these become the positional parameters " + "for the duration of the function or script and are restored " + "upon completion.]" +"\n" +"\n name [arg ...]\n" +"\n" +"[+EXIT STATUS?If \aname\a is found, then the exit status is that " + "of the last command executed. Otherwise, since this is a special " + "built-in, an error will cause a non-interactive shell to exit with " + "a non-zero exit status. An interactive shell returns a non-zero exit " + "status to indicate an error.]" + +"[+SEE ALSO?\bcommand\b(1), \bksh\b(1)]" +; + +#ifndef ECHOPRINT + const char sh_optecho[] = " [-n] [arg...]"; +#endif /* !ECHOPRINT */ + +const char sh_opteval[] = +"[-1c?\n@(#)$Id: eval (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?eval - create a shell command and process it]" +"[+DESCRIPTION?\beval\b is a shell special built-in command that constructs " + "a command by concatenating the \aarg\as together, separating each " + "with a space. The resulting string is then taken as input to " + "the shell and evaluated in the current environment. Note that " + "command words are expanded twice; once to construct \aarg\a, and " + "again when the shell executes the constructed command.]" +"[+?It is not an error if \aarg\a is not given.]" +"\n" +"\n[arg...]\n" +"\n" +"[+EXIT STATUS?If \aarg\a is not specified, the exit status is \b0\b. " + "Otherwise, it is the exit status of the command defined by the " + "\aarg\a operands.]" +"[+SEE ALSO?\bexec\b(1), \btrap\b(1), \b.\b(1)]" +; + +const char sh_optexec[] = +"[-1c?\n@(#)$Id: exec (AT&T Research) 1999-07-10 $\n]" +USAGE_LICENSE +"[+NAME?exec - execute command, open/close and duplicate file descriptors]" +"[+DESCRIPTION?\bexec\b is a special built-in command that can be used to " + "manipulate file descriptors or to replace the current shell " + "with a new command.]" +"[+?If \acommand\a is specified, then the current shell process will be " + "replaced by \acommand\a rather than running \acommand\a and waiting " + "for it to complete. Note that there is no need to use " + "\bexec\b to enhance performance since the shell implicitly " + "uses the exec mechanism internally whenever possible.]" +"[+?If no operands are specified, \bexec\b can be used to open or " + "close files, or to manipulate file descriptors from \b0\b to " + "\b9\b in the current shell environment using the standard " + "redirection mechanism available with all commands. The " + "close-on-exec flags will be set on file descriptor numbers " + "greater than \b2\b that are opened this way so that they " + "will be closed when another program is invoked.]" +"[+?Because \bexec\b is a special command, any failure will cause the " + "script that invokes it to exit. This can be prevented by " + "invoking \bexec\b from the \bcommand\b utility.]" +"[+?\bexec\b cannot be invoked from a restricted shell to create " + "files or to open a file for writing or appending.]" +"[c?Clear all environment variables before executions except variable " + "assignments that are part of the current \bexec\b command.]" +"[a]:[name?\bargv[0]]\b will be set to \aname\a for \acommand\a]" +"\n" +"\n[command [arg ...]]\n" +"\n" +"[+EXIT STATUS?If \acommand\a is specified, \bexec\b does not return. " + "Otherwise, the exit status is one of the following:]{" + "[+0?All I/O redirections were successful.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bcommand\b(1), \beval\b(1)]" +; + + +const char sh_optexit[] = +"[-1c?\n@(#)$Id: exit (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?exit - exit the current shell]" +"[+DESCRIPTION?\bexit\b is shell special built-in that causes the " + "shell that invokes it to exit. Before exiting the shell, if the " + "\bEXIT\b trap is set it will be invoked.]" +"[+?If \an\a is given, it will be used to set the exit status.]" +"\n" +"\n[n]\n" +"\n" +"[+EXIT STATUS?If \an\a is specified, the exit status is the least significant " + "eight bits of the value of \an\a. Otherwise, the exit status is the " + "exit status of preceding command. When invoked inside a trap, the " + "preceding command means the command that invoked the trap.]" +"[+SEE ALSO?\bbreak\b(1), \breturn\b(1)]" +; + +const char sh_optexport[] = +"[-1c?\n@(#)$Id: export (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?export - set export attribute on variables]" +"[+DESCRIPTION?\bexport\b sets the export attribute on each of " + "the variables specified by \aname\a which causes them " + "to be in the environment of subsequently executed commands. " + "If \b=\b\avalue\a is specified, the variable \aname\a is " + "set to \avalue\a.]" +"[+?If no \aname\as are specified then the names and values of all " + "exported variables are written to standard output.]" +"[+?\bexport\b is built-in to the shell as a declaration command so that " + "field splitting and pathname expansion are not performed on " + "the arguments. Tilde expansion occurs on \avalue\a.]" +"[p?Causes the output to be in the form of \bexport\b commands that can be " + "used as input to the shell to recreate the current exports.]" +"\n" +"\n[name[=value]...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" + +"[+SEE ALSO?\bsh\b(1), \btypeset\b(1)]" +; + +const char sh_optgetopts[] = +":[-1c?\n@(#)$Id: getopts (AT&T Research) 2005-01-01 $\n]" +"[-author?Glenn Fowler <gsf@research.att.com>]" +USAGE_LICENSE +"[+NAME?\f?\f - parse utility options]" +"[+DESCRIPTION?The \bgetopts\b utility can be used to retrieve options and " + "arguments from a list of arguments given by \aargs\a or the positional " + "parameters if \aargs\a is omitted. It can also generate usage messages " + "and a man page for the command based on the information in \aoptstring\a.]" +"[+?Each time it is invoked, the \bgetopts\b utility places the value " + "of the next option in the shell variable specified by the \aname\a " + "operand and the index of the next argument to be processed in the " + "shell variable \bOPTIND\b. When the shell is invoked \bOPTIND\b " + "is initialized to \b1\b. When an option requires or permits an option " + "argument, \bgetopts\b places the option argument in the shell " + "variable \bOPTARG\b. Otherwise \bOPTARG\b is set to \b1\b when the " + "option is set and \b0\b when the option is unset.]" +"[+?The \aoptstring\a string consists of alpha-numeric characters, " + "the special characters +, -, ?, :, and <space>, or character groups " + "enclosed in [...]]. Character groups may be nested in {...}. " + "Outside of a [...]] group, a single new-line followed by zero or " + "more blanks is ignored. One or more blank lines separate the " + "options from the command argument synopsis.]" +"[+?Each [...]] group consists of an optional label, " + "optional attributes separated by :, and an " + "optional description string following ?. The characters from the ? " + "to the end of the next ]] are ignored for option parsing and short " + "usage messages. They are used for generating verbose help or man pages. " + "The : character may not appear in the label. " + "The ? character must be specified as ?? in the label and the ]] character " + "must be specified as ]]]] in the description string. " + "Text between two \\b (backspace) characters indicates " + "that the text should be emboldened when displayed. " + "Text between two \\a (bell) characters indicates that the text should " + "be emphasised or italicized when displayed. " + "Text between two \\v (vertical tab) characters indicates " + "that the text should displayed in a fixed width font. " + "Text between two \\f (formfeed) characters will be replaced by the " + "output from the shell function whose name is that of the enclosed text.]" +"[+?All output from this interface is written to the standard error.]" +"[+?There are several group types:]{" + "[+1.?A group of the form " + "[-[\aversion\a]][\aflag\a[\anumber\a]]]]...[?\atext\a]]]] " + "appearing as the first group enables the extended interface. \aversion\a " + "specifies the interface version, currently \b1\b. The latest version is " + "assumed if \aversion\a is omitted. Future enhancements " + "may increment \aversion\a, but all versions will be supported. \atext\a " + "typically specifies an SCCS or CVS identification string. Zero or more " + "\aflags\a with optional \anumber\a values may be specified to control " + "option parsing. " + "The flags are:]{" + "[+c?Cache this \aoptstring\a for multiple passes. Used to optimize " + "builtins that may be called many times within the same process.]" + "[+i?Ignore this \aoptstring\a when generating help. Used when " + "combining \aoptstring\a values from multiple passes.]" + "[+l?Display only \alongname\a options in help messages.]" + "[+o?The \b-\b option character prefix is optional (supports " + "obsolete \bps\b(1) option syntax.)]" + "[+p?\anumber\a specifies the number of \b-\b characters that must " + "prefix long option names. The default is \b2\b; \b0\b, \b1\b or " + "\b2\b are accepted (e.g., \bp0\b for \bdd\b(1) and \bp1\b for " + "\bfind\b(1).)]" + "[+s?\anumber\a specifies the \b--??man\b section number, " + "\b1\b by default.]" + "}" + "[+2.?An option specification of the form " + "[\aoption\a[!]][=\anumber\a]][:\alongname\a]][?\atext\a]]]]. In this " + "case the first field is the option character; this is the value returned " + "in the \aname\a operand when the option is matched. If there is no " + "option character then a two or more digit number should be specified. " + "This number will be returned as the value of the \aname\a operand if the " + "long option is matched. If \aoption\a is followed by \b!\b then the option " + "character sense is the inverse of the longname sense. For options that do " + "not take values \bOPTARG\b will be set to \b0\b for \b!\b inverted option " + "characters and \b1\b otherwise. =\anumber\a optionally specifies a number to " + "be returned in the \aname\a operand instead of the option character. A " + "longname is specified by \b--\b\alongname\a and is matched by the shortest " + "non-ambiguous prefix of all long options. * in the \alongname\a field " + "indicates that only characters up to that point need to match, provided " + "any additional characters match exactly. The enclosing [ and ]] can be " + "omitted for an option that does not have a longname or descriptive text.]" + "[+3.?An option argument specification. " + "Options that take arguments can be followed by : (string value) or # " + "(numeric value) and an option argument specification. An option argument " + "specification consists of the option argument name as field 1. " + "The remaining \b:\b separated fields are a type name and zero or more of " + "the special attribute words \blistof\b, \boneof\b, and \bignorecase\b. " + "A default option value may be specified in the final field as " + "\b:=\b\adefault\a. The option argument specification may be followed " + "by a list of option value descriptions enclosed in braces. " + "A long option that takes an argument is specified as " + "\b--\b\alongname\a=\avalue\a. If the : or # is followed by ? then the " + "option argument is optional. If only the option character form is " + "specified then the optional argument value is not set if the next " + "argument starts with - or +.]" + "[+4.?A option value description.]" + "[+5.?A argument specification. A list of valid option argument values " + "can be specified by enclosing them inside a {...} following " + "the option argument specification. Each of the permitted " + "values can be specified with a [...]] containing the " + "value followed by a description.]" + "[+6.?A group of the form [+\\n...]] will display the characters " + "representing ... in fixed with font without adding line breaks.]" + "[+7.?A group of the form [+\aname\a?\atext\a]] specifies a section " + "\aname\a with descriptive \atext\a. If \aname\a is omitted then " + "\atext\a is placed in a new paragraph.]" + "[+8.?A group of the form [-\aname\a?\atext\a]] specifies entries " + "for the \bIMPLEMENTATION\b section.]" +"}" +"[+?If the leading character of \aoptstring\a is +, then arguments " + "beginning with + will also be considered options.]" +"[+?A leading : character or a : following a leading + in \aoptstring\a " + "affects the way errors are handled. If an option character or longname " + "argument not specified in \aoptstring\a is encountered when processing " + "options, the shell variable whose name is \aname\a will be set to the ? " + "character. The shell variable \bOPTARG\b will be set to " + "the character found. If an option argument is missing or has an invalid " + "value, then \aname\a will be set to the : character and the shell variable " + "\bOPTARG\b will be set to the option character found. " + "Without the leading :, \aname\a will be set to the ? character, \bOPTARG\b " + "will be unset, and an error message will be written to standard error " + "when errors are encountered.]" +"[+?The end of options occurs when:]{" + "[+1.?The special argument \b--\b is encountered.]" + "[+2.?An argument that does not begin with a \b-\b is encountered.]" + "[+3.?A help argument is specified.]" + "[+4.?An error is encountered.]" +"}" +"[+?If \bOPTIND\b is set to the value \b1\b, a new set of arguments " + "can be used.]" +"[+?\bgetopts\b can also be used to generate help messages containing command " + "usage and detailed descriptions. Specify \aargs\a as:]" +"{ " + "[+-???To generate a usage synopsis.]" + "[+--?????To generate a verbose usage message.]" + "[+--????man?To generate a formatted man page.]" + "[+--????api?To generate an easy to parse usage message.]" + "[+--????html?To generate a man page in \bhtml\b format.]" + "[+--????nroff?To generate a man page in \bnroff\b format.]" + "[+--????usage?List the current \aoptstring\a.]" + "[+--??????\aname\a?List \bversion=\b\an\a, \an\a>0, " + "if the option \aname\a is recognized by \bgetopts\b.]" +"}" +"[+?When the end of options is encountered, \bgetopts\b exits with a " + "non-zero return value and the variable \bOPTIND\b is set to the " + "index of the first non-option argument.]" +"a:[name?Use \aname\a instead of the command name in usage messages.]" +"\n" +"\nopstring name [args...]\n" +"\n" +"[+EXIT STATUS]{" + "[+0?An option specified was found.]" + "[+1?An end of options was encountered.]" + "[+2?A usage or information message was generated.]" +"}" +; + +const char sh_optbg[] = +"[-1c?@(#)$Id: bg (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?bg - resume jobs in the background]" +"[+DESCRIPTION?\bbg\b places the given \ajob\as into the background " + "and sends them a \bCONT\b signal to start them running.]" +"[+?If \ajob\a is omitted, the most recently started or stopped " + "background job is resumed or continued in the background.]" +"[+?Each \ajob\a can be specified as one of the following:]{" + "[+\anumber\a?\anumber\a refers to a process id.]" + "[+-\anumber\a?\anumber\a refers to a process group id.]" + "[+%\anumber\a?\anumber\a refer to a job number.]" + "[+%\astring\a?Refers to a job whose name begins with \astring\a.]" + "[+%??\astring\a?Refers to a job whose name contains \astring\a.]" + "[+%+ \bor\b %%?Refers to the current job.]" + "[+%-?Refers to the previous job.]" +"}" +"\n" +"\n[job ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?If all background jobs are started.]" + "[+>0?If one more jobs does not exist or there are no background " + "jobs.]" +"}" + +"[+SEE ALSO?\bwait\b(1), \bfg\b(1), \bdisown\b(1), \bjobs\b(1)]" +; + +const char sh_optfg[] = +"[-1c?@(#)$Id: fg (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?fg - move jobs to the foreground]" +"[+DESCRIPTION?\bfg\b places the given \ajob\as into the foreground " + "in sequence and sends them a \bCONT\b signal to start each running.]" +"[+?If \ajob\a is omitted, the most recently started or stopped " + "background job is moved to the foreground.]" +"[+?Each \ajob\a can be specified as one of the following:]{" + "[+\anumber\a?\anumber\a refers to a process id.]" + "[+-\anumber\a?\anumber\a refers to a process group id.]" + "[+%\anumber\a?\anumber\a refer to a job number.]" + "[+%\astring\a?Refers to a job whose name begins with \astring\a.]" + "[+%??\astring\a?Refers to a job whose name contains \astring\a.]" + "[+%+ \bor\b %%?Refers to the current job.]" + "[+%-?Refers to the previous job.]" +"}" +"\n" +"\n[job ...]\n" +"\n" +"[+EXIT STATUS?If \bfg\b brings one or more jobs into the foreground, " + "the exit status of \bfg\b will be that of the last \ajob\a. " + "If one or more jobs does not exist or has completed, \bfg\b will " + "return a non-zero exit status.]" +"}" + +"[+SEE ALSO?\bwait\b(1), \bbg\b(1), \bjobs\b(1)]" +; + +const char sh_optdisown[] = +"[-1c?@(#)$Id: disown (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?disown - disassociate a job with the current shell]" +"[+DESCRIPTION?\bdisown\b prevents the current shell from sending " + "a \bHUP\b signal to each of the given \ajob\as when " + "the current shell terminates a login session.]" +"[+?If \ajob\a is omitted, the most recently started or stopped " + "background job is used.]" +"[+?Each \ajob\a can be specified as one of the following:]{" + "[+\anumber\a?\anumber\a refers to a process id.]" + "[+-\anumber\a?\anumber\a refers to a process group id.]" + "[+%\anumber\a?\anumber\a refer to a job number.]" + "[+%\astring\a?Refers to a job whose name begins with \astring\a.]" + "[+%??\astring\a?Refers to a job whose name contains \astring\a.]" + "[+%+ \bor\b %%?Refers to the current job.]" + "[+%-?Refers to the previous job.]" +"}" +"\n" +"\n[job ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?If all jobs are sucessfully disowned.]" + "[+>0?If one more \ajob\as does not exist.]" +"}" + +"[+SEE ALSO?\bwait\b(1), \bbg\b(1), \bjobs\b(1)]" +; + +const char sh_optjobs[] = +"[-1c?@(#)$Id: jobs (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?jobs - display status of jobs]" +"[+DESCRIPTION?\bjobs\b displays information about specified \ajob\as " + "that were started by the current shell environment on standard " + "output. The information contains the job number enclosed in " + "[...]], the status, and the command line that started the job.]" +"[+?If \ajob\a is omitted, \bjobs\b displays the status of all stopped jobs, " + "background jobs, and all jobs whose status has changed since last " + "reported by the shell.]" +"[+?When \bjobs\b reports the termination status of a job, the " + "shell removes the jobs from the list of known jobs in " + "the current shell environment.]" +"[+?Each \ajob\a can be specified as one of the following:]{" + "[+\anumber\a?\anumber\a refers to a process id.]" + "[+-\anumber\a?\anumber\a refers to a process group id.]" + "[+%\anumber\a?\anumber\a refer to a job number.]" + "[+%\astring\a?Refers to a job whose name begins with \astring\a.]" + "[+%??\astring\a?Refers to a job whose name contains \astring\a.]" + "[+%+ \bor\b %%?Refers to the current job.]" + "[+%-?Refers to the previous job.]" +"}" +"[l?\bjobs\b displays process id's after the job number in addition " + "to the usual information]" +"[n?Only the jobs whose status has changed since the last prompt " + "is displayed.]" +"[p?The process group leader id's for the specified jobs are displayed.]" +"\n" +"\n[job ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?The information for each job is written to standard output.]" + "[+>0?One or more jobs does not exist.]" +"}" + +"[+SEE ALSO?\bwait\b(1), \bps\b(1), \bfg\b(1), \bbg\b(1)]" +; + +const char sh_opthist[] = +"[-1c?@(#)$Id: hist (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?\f?\f - process command history list]" +"[+DESCRIPTION?\b\f?\f\b lists, edits, or re-executes, commands " + "previously entered into the current shell environment.]" +"[+?The command history list references commands by number. The first number " + "in the list is selected arbitrarily. The relationship of a number " + "to its command does not change during a login session. When the " + "number reaches 32767 the number wraps around to 1 but " + "maintains the ordering.]" +"[+?When commands are edited (when the \b-l\b option is not specified), the " + "resulting lines will be entered at the end of the history list and " + "then reexecuted by the current shell. The \b\f?\f\b command that " + "caused the editing will not be entered into the history list. If the " + "editor returns a non-zero exit status, this will suppress the " + "entry into the history list and the command reexecution. Command " + "line variable assignments and redirections affect both the \f?\f " + "command and the commands that are reexecuted.]" +"[+?\afirst\a and \alast\a define the range of commands. \afirst\a and " + "\alast\a can be one of the following:]{" + "[+\anumber\a?A positive number representing a command " + "number. A \b+\b sign can precede \anumber\a.]" + "[+-\anumber\a?A negative number representing a command " + "that was executed \anumber\a commands previously. " + "For example, \b-1\b is the previous command.]" + "[+\astring\a?\astring\a indicates the most recently " + "entered command that begins with \astring\a. " + "\astring\a should not contain an \b=\b.]" + "}" +"[+?If \afirst\a is omitted, the previous command is used, unless \b-l\b " + "is specified, in which case it will default to \b-16\b and \alast\a " + "will default to \b-1\b.]" +"[+?If \afirst\a is specified and \alast\a is omitted, then \alast\a will " + "default to \afirst\a unless \b-l\b is specified in which case " + "it will default to \b-1\b.]" +"[+?If no editor is specified, then the editor specfied by the \bHISTEDIT\b " + "variable will be used if set, or the \bFCEDIT\b variable will be " + "used if set, otherwise, \bed\b will be used.]" +"[e]:[editor?\aeditor\a specifies the editor to use to edit the history " + "command. A value of \b-\b for \aeditor\a is equivalent to " + "specifiying the \b-s\b option.]" +"[l?List the commands rather than editing and reexecuting them.]" +"[N]#[num?Start at \anum\a commands back.]" +"[n?Suppress the command numbers when the commands are listed.]" +#if SHOPT_HISTEXPAND +"[p?Writes the result of history expansion for each operand to standard " + "output. All other options are ignored.]" +#endif +"[r?Reverse the order of the commands.]" +"[s?Reexecute the command without invoking an editor. In this case " + "an operand of the form \aold\a\b=\b\anew\a can be specified " + "to change the first occurrence of the string \aold\a in the " + "command to \anew\a before reexecuting the command.]" + +"\n" +"\n[first [last] ]\n" +"\n" +"[+EXIT STATUS?If a command is reexecuted, the exit status is that of " + "the command that gets reexecuted. Otherwise, it is one of the " + "following:]{" + "[+0?Successfully completion of the listing.]" + "[+>0?An error occurred.]" +"}" + +"[+SEE ALSO?\bksh\b(1), \bsh\b(1), \bed\b(1)]" +; + +const char sh_optkill[] = +"[-1c?\n@(#)$Id: kill (AT&T Research) 1999-06-17 $\n]" +USAGE_LICENSE +"[+NAME?kill - terminate or signal process]" +"[+DESCRIPTION?With the first form in which \b-l\b is not specified, " + "\bkill\b sends a signal to one or more processes specified by " + "\ajob\a. This normally terminates the processes unless the signal " + "is being caught or ignored.]" +"[+?A \ajob\a can be specified as one of the following:]{" + "[+\anumber\a?\anumber\a refers to a process id.]" + "[+-\anumber\a?\anumber\a refers to a process group id.]" + "[+%\anumber\a?\anumber\a refer to a job number.]" + "[+%\astring\a?Refers to a job whose name begins with \astring\a.]" + "[+%??\astring\a?Refers to a job whose name contains \astring\a.]" + "[+%+ \bor\b %%?Refers to the current job.]" + "[+%-?Refers to the previous job.]" +"}" +"[+?If the signal is not specified with either the \b-n\b or the \b-s\b " + "option, the \bSIGTERM\b signal is used.]" +"[+?If \b-l\b is specified, and no \aarg\a is specified, then \bkill\b " + "writes the list of signals to standard output. Otherwise, \aarg\a " + "can be either a signal name, or a number representing either a " + "signal number or exit status for a process that was terminated " + "due to a signal. If a name is given the corresponding signal " + "number will be written to standard output. If a number is given " + "the corresponding signal name will be written to standard output.]" +"[l?List signal names or signal numbers rather than sending signals as " + "described above. " + "The \b-n\b and \b-s\b options cannot be specified.]" +"[n]#[signum?Specify a signal number to send. Signal numbers are not " + "portable across platforms, except for the following:]{" + "[+0?No signal]" + "[+1?\bHUP\b]" + "[+2?\bINT\b]" + "[+3?\bQUIT\b]" + "[+6?\bABRT\b]" + "[+9?\bKILL\b]" + "[+14?\bALRM\b]" + "[+15?\bTERM\b]" + "}" +"[s]:[signame?Specify a signal name to send. The signal names are derived " + "from their names in \b<signal.h>\b without the \bSIG\b prefix and " + "are case insensitive. \bkill -l\b will generate the list of " + "signals on the current platform.]" +"\n" +"\njob ...\n" +" -l [arg ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?At least one matching process was found for each \ajob\a " + "operand, and the specified signal was successfully sent to at " + "least one matching process.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bps\b(1), \bjobs\b(1), \bkill\b(2), \bsignal\b(2)]" +; + +const char sh_optlet[] = +"[-1c?@(#)$Id: let (AT&T Research) 2000-04-02 $\n]" +USAGE_LICENSE +"[+NAME?let - evaluate arithmetic expressions]" +"[+DESCRIPTION?\blet\b evaluates each \aexpr\a in the current " + "shell environment as an arithmetic expression using ANSI C " + "syntax. Variables names are shell variables and they " + "are recursively evaluated as arithmetic expressions to " + "get numerical values.]" +"[+?\blet\b has been made obsolete by the \b((\b...\b))\b syntax " + "of \bksh\b(1) which does not require quoting of the operators " + "to pass them as command arguments.]" +"\n" +"\n[expr ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?The last \aexpr\a evaluates to a non-zero value.]" + "[+>0?The last \aexpr\a evaluates to \b0\b or an error occurred.]" +"}" + +"[+SEE ALSO?\bexpr\b(1), \btest\b(1), \bksh\b(1)]" +; + +const char sh_optprint[] = +"[-1c?\n@(#)$Id: print (AT&T Research) 1999-04-07 $\n]" +USAGE_LICENSE +"[+NAME?print - write arguments to standard output]" +"[+DESCRIPTION?By default, \bprint\b writes each \astring\a operand to " + "standard output and appends a newline character.]" +"[+?Unless, the \b-r\b or \b-f\b option is specifed, each \b\\\b " + "character in each \astring\a operand is processed specially as " + "follows:]{" + "[+\\a?Alert character.]" + "[+\\b?Backspace character.]" + "[+\\c?Terminate output without appending newline. The " + "remaining \astring\a operands are ignored.]" + "[+\\f?Formfeed character.]" + "[+\\n?Newline character.]" + "[+\\t?Tab character.]" + "[+\\v?Vertical tab character.]" + "[+\\\\?Backslash character.]" + "[+\\E?Escape character (ASCII octal 033).]" + "[+\\0\ax\a?The 8-bit character whose ASCII code is the " + "1-, 2-, or 3-digit octal number \ax\a.]" + "}" +"[+?If both \b-e\b and \b-r\b are specified, the last one specified is " + "the one that is used.]" +"[+?When the \b-f\b option is specified and there are more \astring\a " + "operands than format specifiers, the format string is " + "reprocessed from the beginning. If there are fewer \astring\a " + "operands than format specifiers, then outputting will end " + "at the first unneeded format specifier.]" +"[e?Unless \b-f\b is specified, process \b\\\b sequences in each \astring\a " + "operand as described above. This is the default behavior.]" +"[n?Do not append a new-line character to the output.]" +"[f]:[format?Write the \astring\a arguments using the format string " + "\aformat\a and do not append a new-line. See \bprintf\b for " + "details on how to specify \aformat\a.]" +"[p?Write to the current co-process instead of standard output.]" +"[r?Do not process \b\\\b sequences in each \astring\a operand as described " + "above.]" +"[s?Write the output as an entry in the shell history file instead of " + "standard output.]" +"[u]:[fd:=1?Write to file descriptor number \afd\a instead of standard output.]" +"\n" +"\n[string ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\becho\b(1), \bprintf\b(1), \bread\b(1)]" +; + +const char sh_optprintf[] = +"[-1c?\n@(#)$Id: printf (AT&T Research) 2006-10-26 $\n]" +USAGE_LICENSE +"[+NAME?printf - write formatted output]" +"[+DESCRIPTION?\bprintf\b writes each \astring\a operand to " + "standard output using \aformat\a to control the output format.]" +"[+?The \aformat\a operands supports the full range of ANSI C formatting " + "specifiers plus the following additional specifiers:]{" + "[+%b?Each character in the \astring\a operand is processed " + "specially as follows:]{" + "[+\\a?Alert character.]" + "[+\\b?Backspace character.]" + "[+\\c?Terminate output without appending newline. " + "The remaining \astring\a operands are ignored.]" + "[+\\f?Formfeed character.]" + "[+\\n?Newline character.]" + "[+\\t?Tab character.]" + "[+\\v?Vertical tab character.]" + "[+\\\\?Backslash character.]" + "[+\\E?Escape character (ASCII octal 033).]" + "[+\\0\ax\a?The 8-bit character whose ASCII code is " + "the 1-, 2-, or 3-digit octal number \ax\a.]" + "}" + "[+%q?Output \astring\a quoted in a manner that it can be read in " + "by the shell to get back the same string. However, empty " + "strings resulting from missing \astring\a operands will " + "not be quoted.]" + "[+%B?Treat the argument as a variable name and output the value " + "without converting it to a string. This is most useful for " + "variables of type \b-b\b.]" + "[+%H?Output \astring\a with characters \b<\b, \b&\b, \b>\b, " + "\b\"\b, and non-printable characters properly escaped for " + "use in HTML and XML documents.]" + "[+%P?Treat \astring\a as an extended regular expression and " + "convert it to a shell pattern.]" + "[+%R?Treat \astring\a as an shell pattern expression and " + "convert it to an extended regular expression.]" + "[+%T?Treat \astring\a as a date/time string and format it. The " + "\bT\b can be preceded by \b(\b\adformat\a\b)\b, where " + "\adformat\a is a date format as defined by the \bdate\b " + "command.]" + "[+%Z?Output a byte whose value is \b0\b.]" +"}" +"[+?When performing conversions of \astring\a to satisfy a numeric " + "format specifier, if the first character of \astring\a " + "is \b\"\b or \b'\b, then the value will be the numeric value " + "in the underlying code set of the character following the " + "\b\"\b or \b'\b. Otherwise, \astring\a is treated like a shell " + "arithmetic expression and evaluated.]" +"[+?If a \astring\a operand cannot be completed converted into a value " + "appropriate for that format specifier, an error will occur, " + "but remaining \astring\a operands will continue to be processed.]" +"[+?In addition to the format specifier extensions, the following " + "extensions of ANSI-C are permitted in format specifiers:]{" + "[+-?The escape sequences \b\\E\b and \b\\e\b expand to the escape " + "character which is octal \b033\b in ASCII.]" + "[+-?The escape sequence \b\\c\b\ax\a expands to Control-\ax\a.]" + "[+-?The escape sequence \b\\C[.\b\aname\a\b.]]\b expands to " + "the collating element \aname\a.]" + "[+-?The escape sequence \b\\x{\b\ahex\a\b}\b expands to the " + "character corresponding to the hexidecimal value \ahex\a.]" + "[+-?The format modifier flag \b=\b can be used to center a field to " + "a specified width. When the output is a terminal, the " + "character width is used rather than the number of bytes.]" + "[+-?Each of the integral format specifiers can have a third " + "modifier after width and precision that specifies the " + "base of the conversion from 2 to 64. In this case the " + "\b#\b modifier will cause \abase\a\b#\b to be prepended to " + "the value.]" + "[+-?The \b#\b modifier can be used with the \bd\b specifier when " + "no base is specified cause the output to be written in units " + "of \b1000\b with a suffix of one of \bk M G T P E\b.]" + "[+-?The \b#\b modifier can be used with the \bi\b specifier to " + "cause the output to be written in units of \b1024\b with " + "a suffix of one of \bKi Mi Gi Ti Pi Ei\b.]" + "}" +"[+?If there are more \astring\a operands than format specifiers, the " + "\aformat\a string is reprocessed from the beginning. If there are " + "fewer \astring\a operands than format specifiers, then string " + "specifiers will be treated as if empty strings were supplied, " + "numeric conversions will be treated as if 0 were supplied, and " + "time conversions will be treated as if \bnow\b were supplied.]" +"[+?\bprintf\b is equivalent to \bprint -f\b which allows additional " + "options to be specified.]" +"\n" +"\nformat [string ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bdate\b(1), \bprint\b(1), \bread\b(1)]" +; + +const char sh_optpwd[] = +"[-1c?\n@(#)$Id: pwd (AT&T Research) 1999-06-07 $\n]" +USAGE_LICENSE +"[+NAME?pwd - write working directory name]" +"[+DESCRIPTION?\bpwd\b writes an absolute pathname of the current working " + "directory to standard output. An absolute pathname is a " + "pathname that begins with \b/\b that does not contains any " + "\b.\b or \b..\b components.]" +"[+?If both \b-L\b and \b-P\b are specified, the last one specified will " + "be used. If neither \b-P\b or \b-L\b is specified then the " + "behavior will be determined by the \bgetconf\b parameter " + "\bPATH_RESOLVE\b. If \bPATH_RESOLVE\b is \bphysical\b, " + "then the behavior will be as if \b-P\b were specified. Otherwise, " + "the behavior will be as if \b-L\b were specified.]" +"[L?The absolute pathname may contains symbolic link components. This is " + "the default.]" +"[P?The absolute pathname will not contain any symbolic link components.]" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bcd\b(1), \bgetconf\b(1)]" +; + +const char sh_optread[] = +"[-1c?\n@(#)$Id: read (AT&T Research) 2006-12-19 $\n]" +USAGE_LICENSE +"[+NAME?read - read a line from standard input]" +"[+DESCRIPTION?\bread\b reads a line from standard input and breaks it " + "into fields using the characters in value of the \bIFS\b variable " + "as separators. The escape character, \b\\\b, is used to remove " + "any special meaning for the next character and for line continuation " + "unless the \b-r\b option is specified.]" +"[+?If there are more variables than fields, the remaining variables are " + "set to empty strings. If there are fewer variables than fields, " + "the leftover fields and their intervening separators are assigned " + "to the last variable. If no \avar\a is specifed then the variable " + "\bREPLY\b is used.]" +"[+?When \avar\a has the binary attribute and \b-n\b or \b-N\b is specified, " + "the bytes that are read are stored directly into \bvar\b.]" +"[+?If you specify \b?\b\aprompt\a after the first \avar\a, then \bread\b " + "will display \aprompt\a on standard error when standard input " + "is a terminal or pipe.]" +"[A?Unset \avar\a and then create an indexed array containing each field in " + "the line starting at index 0.]" +"[d]:[delim?Read until delimiter \adelim\a instead of to the end of line.]" +"[p?Read from the current co-process instead of standard input. An end of " + "file causes \bread\b to disconnect the co-process so that another " + "can be created.]" +"[r?Do not treat \b\\\b specially when processing the input line.]" +"[s?Save a copy of the input as an entry in the shell history file.]" +"[u]#[fd:=0?Read from file descriptor number \afd\a instead of standard input.]" +"[t]:[timeout?Specify a timeout \atimeout\a in seconds when reading from " + "a terminal or pipe.]" +"[n]#[nbyte?Read at most \ansize\a characters. For binary fields \asize\a " + "will be in bytes.]" +"[N]#[nbyte?Read exactly \ansize\a characters. For binary fields \asize\a " + "will be in bytes.]" +"[v?When reading from a terminal the value of the first variable is displayed " + "and used as a default value.]" +"\n" +"\n[var?prompt] [var ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0? Successful completion.]" + "[+>0?End of file was detected or an error occurred.]" +"}" +"[+SEE ALSO?\bprint\b(1), \bprintf\b(1), \bcat\b(1)]" +; + +const char sh_optreadonly[] = +"[-1c?\n@(#)$Id: readonly (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?readonly - set readonly attribute on variables]" +"[+DESCRIPTION?\breadonly\b sets the readonly attribute on each of " + "the variables specified by \aname\a which prevents their " + "values from being changed. If \b=\b\avalue\a is specified, " + "the variable \aname\a is set to \avalue\a before the variable " + "is made readonly.]" +"[+?If no \aname\as are specified then the names and values of all " + "readonly variables are written to standard output.]" +"[+?\breadonly\b is built-in to the shell as a declaration command so that " + "field splitting and pathname expansion are not performed on " + "the arguments. Tilde expansion occurs on \avalue\a.]" +"[p?Causes the output to be in a form of \breadonly\b commands that can be " + "used as input to the shell to recreate the current set of " + "readonly variables.]" +"\n" +"\n[name[=value]...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" + +"[+SEE ALSO?\bsh\b(1), \btypeset\b(1)]" +; + +const char sh_optreturn[] = +"[-1c?\n@(#)$Id: return (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?return - return from a function or dot script ]" +"[+DESCRIPTION?\breturn\b is a shell special built-in that causes the " + "function or dot script that invokes it to exit. " + "If \breturn\b is invoked outside of a function or dot script " + "it is equivalent to \bexit\b.]" +"[+?If \breturn\b is invoked inside a function defined with the \bfunction\b " + "reserved word syntax, then any \bEXIT\b trap set within the " + "then function will be invoked in the context of the caller " + "before the function returns.]" +"[+?If \an\a is given, it will be used to set the exit status.]" +"\n" +"\n[n]\n" +"\n" +"[+EXIT STATUS?If \an\a is specified, the exit status is the least significant " + "eight bits of the value of \an\a. Otherwise, the exit status is the " + "exit status of preceding command.]" +"[+SEE ALSO?\bbreak\b(1), \bexit\b(1)]" +; + + +const char sh_optksh[] = +"+[-1c?\n@(#)$Id: sh (AT&T Research) "SH_RELEASE" $\n]" +USAGE_LICENSE +"[+NAME?\b\f?\f\b - Shell, the standard command language interpreter]" +"[+DESCRIPTION?\b\f?\f\b is a command language interpreter that " + "executes commands read from a command line string, the " + "standard input, or a specified file.]" +"[+?If the \b-i\b option is present, or there are no \aarg\as and " + "the standard input and standard error are attached to a " + "terminal, the shell is considered to be interactive.]" +"[+?The \b-s\b and \b-c\b options are mutually exclusive. If the \b-c\b " + "option is specified, the first \aarg\a is the command-line string " + "and must be specified. Any remaining \aarg\as will be used " + "to initialize \b$0\b and positional parameters.]" +"[+?If the neither \b-s\b nor \b-c\b is specified, then the first \barg\b " + "will be the pathname of the file containing commands and \b$0\b " + "will be set to this value. If there is no file with this pathname, " + "and this pathame does not contain a \b/\b, then the \bPATH\b " + "will be searched for an executable with this name. Any remaining " + "\aarg\as will be used to initialize the positional parmaeters.]" +"[+?Any option can use a \b+\b instead of a \b-\b to disable the corresponding " + "option.]" +"[c?Read the commands from the first \aarg\a.]" +"[i?Specifies that the shell is interactive.]" +"[l?Invoke the shell as a login shell; \b/etc/profile\b and \b$HOME/.profile\b, " + "if they exist, are read before the first command.]" +"[r\f:restricted\f?Invoke the shell in a restricted mode. A restricted " + "shell does not permit any of the following:]{" + "[+-?Changing the working directory.]" + "[+-?Setting values or attributes of the variables \bSHELL\b, " + "\bENV\b, \bFPATH\b, or \bPATH\b.]" + "[+-?Executing any command whose name as a \b/\b in it.]" + "[+-?Redirecting output of a command with \b>\b, \b>|\b, " + "\b<>\b, or \b>>\b.]" + "[+-?Adding or deleting built-in commands or libraries with " + "\bbuiltin\b.]" + "[+-?Executing \bcommand -p\b \a...\a .]" + "}" +"[s?Read the commands from standard input. The positional parameters will be " + "initialized from \aarg\a.]" +"[D\f:dump-strings\f?Do not execute the script, but output the set of double " + "quoted strings preceded by a \b$\b. These strings are needed for " + "localization of the script to different locales.]" +"[E?Reads the file \b${ENV-$HOME/.kshrc}\b, if it exists, as a profile. " + "On by default for interactive shells; use \b+E\b to disable.]" +#if SHOPT_PFSH +"[P?Invoke the shell as a profile shell. See \bpfexec\b(1).]" +#endif +#if SHOPT_KIA +"[R]:[file?Do not execute the script, but create a cross reference database " + "in \afile\a that can be used a separate shell script browser.]" +#endif /* SHOPT_KIA */ +#if SHOPT_BASH + "\fbash2\f" +#endif +"\fabc\f" +"\n" +"\n[arg ...]\n" +"\n" +"[+EXIT STATUS?If \b\f?\f\b executes command, the exit status will be that " + "of the last command executed. Otherwise, it will be one of " + "the following:]{" + "[+0?The script or command line to be executed consists entirely " + "of zero or more blank lines or comments.]" + "[+>1-125?A noninteractive shell detected a syntax error, a variable " + "assignment error, or an error in a special built-in.]" + "[+126?\b-c\b and \b-s\b were not specified and the command script " + "was found on \bPATH\b but was not executable.]" + "[+127?\b-c\b and \b-s\b were not specified and the command script " + "corresponding to \aarg\a could not be found.]" +"}" + +"[+SEE ALSO?\bset\b(1), \bbuiltin\b(1)]" +; +const char sh_optset[] = +"+[-1c?\n@(#)$Id: set (AT&T Research) 1999-09-28 $\n]" +USAGE_LICENSE +"[+NAME?set - set/unset options and positional parameters]" +"[+DESCRIPTION?\bset\b sets or unsets options and positional parameters. " + "Options that are specified with a \b-\b cause the options to " + "be set. Options that are specified with a \b+\b cause the " + "option to be unset.]" +"[+?\bset\b without any options or arguments displays the names and " + "values of all shell variables in the order of the collation " + "sequence in the current locale. The values are quoted so that " + "they are suitable for reinput to the shell.]" +"[+?If no \aarg\as are specified, not even the end of options argument \b--\b, " + "the positional parameters are unchanged. Otherwise, unless " + "the \b-A\b options has been specified, the positional parameters " + "are replaced by the list of \aarg\as. A first \aarg\a of " + "\b--\b is ignored when setting positional parameters.]" +"[+?For backward compatibility, a \bset\b command without any options " + "specified whose first \aarg\a is \b-\b will turn off " + "the \b-v\b and \b-x\b options. If any additional \aarg\as " + "are specified, they will replace the positional parameters.]" +"[s?Sort the positional parameters.]" +"[A]:[name?Assign the arguments sequentially to the array named by \aname\a " + "starting at subscript 0 rather than to the positional parameters.]" +"\fabc\f" +"\n" +"\n[arg ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?No errors occurred.]" + "[+>0?An error occurred.]" +"}" + +"[+SEE ALSO?\btypeset\b(1), \bshift\b(1)]" +; + + + +const char sh_optshift[] = +"[-1c?\n@(#)$Id: shift (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?shift - shift positional parameters]" +"[+DESCRIPTION?\bshift\b is a shell special built-in that shifts the " + "positional parameters to the left by the number of places " + "defined by \an\a, or \b1\b if \an\a is omitted. The number of " + "positional parameters remaining will be reduced by the " + "number of places that are shifted.]" +"[+?If \an\a is given, it will be evaluated as an arithmetic expression " + "to determinate the number of places to shift. It is an error " + "to shift more than the number of positional parameters or a " + "negative number of places.]" +"\n" +"\n[n]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?The positional parameters were successfully shifted.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bset\b(1)]" +; + +const char sh_optsleep[] = +"[-1c?\n@(#)$Id: sleep (AT&T Research) 1999-04-07 $\n]" +USAGE_LICENSE +"[+NAME?sleep - suspend execution for an interval]" +"[+DESCRIPTION?\bsleep\b suspends execution for at least the time specified " + "by \aseconds\a or until a \bSIGALRM\b signal is received. " + "\aseconds\a can be specifed as a floating point number but the " + "actual granularity depends on the underlying system, normally " + "around 1 millisecond.]" +"\n" +"\nseconds\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?The execution was successfully suspended for at least \atime\a " + "seconds, or a \bSIGALRM\b signal was received.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\btime\b(1), \bwait\b(1)]" +; + +const char sh_opttrap[] = +"[-1c?\n@(#)$Id: trap (AT&T Research) 1999-07-17 $\n]" +USAGE_LICENSE +"[+NAME?trap - trap signals and conditions]" +"[+DESCRIPTION?\btrap\b is a special built-in that defines actions to be " + "taken when conditions such as receiving a signal occur. Also, " + "\btrap\b can be used to display the current trap settings on " + "standard output.]" +"[+?If \aaction\a is \b-\b, \btrap\b resets each \acondition\a " + "to the default value. If \aaction\a is an empty string, the " + "shell ignores each of the \acondition\as if they arise. " + "Otherwise, the argument \aaction\a will be read and executed " + "by the shell as if it were processed by \beval\b(1) when one " + "of the corresponding conditions arise. The action of the trap " + "will override any previous action associated with each specified " + "\acondition\a. The value of \b$?\b is not altered by the trap " + "execution.]" +"[+?\acondition\a can be the name or number of a signal, or one of the " + "following:]{" + "[+EXIT?This trap is executed when the shell exits. If defined " + "within a function defined with the \bfunction\b reserved " + "word, the trap is executed in the caller's environment " + "when the function returns and the trap action is restored " + "to the value it had when it called the function.]" + "[+0?Same as EXIT.]" + "[+DEBUG?Executed before each simple command is executed but after " + "the arguments are expanded.]" + "[+ERR?Executed whenever \bset -e\b would cause the shell to exit.]" + "[+KEYBD?Executed when a key is entered from a terminal device.]" +"}" +"[+?Signal names are case insensitive and the \bsig\b prefix is optional. " + "Signals that were ignored on entry to a noninteractive shell cannot " + "trapped or reset although doing so will not report an error. The " + "use of signal numbers other than \b1\b, \b2\b, \b3\b, \b6\b, " + "\b9\b, \b14\b, and \b15\b is not portable.]" +"[+?Although \btrap\b is a special built-in, specifying a condition that " + "the shell does not know about causes \btrap\b to exit with a " + "non-zero exit status, but does not terminate the invoking shell.]" +"[+?If no \aaction\a or \acondition\as are specified then all the current " + "trap settings are written to standard output.]" +"[p?Causes the current traps to be output in a format that can be processed " + "as input to the shell to recreate the current traps.]" +"\n" +"\n[action condition ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" + +"[+SEE ALSO?\bkill\b(1), \beval\b(1), \bsignal\b(3)]" +; + +const char sh_opttypeset[] = +"+[-1c?\n@(#)$Id: typeset (AT&T Research) 2003-01-15 $\n]" +USAGE_LICENSE +"[+NAME?\f?\f - declare or display variables with attributes]" +"[+DESCRIPTION?Without the \b-f\b option, \b\f?\f\b sets, unsets, " + "or displays attributes of variables as specified with the " + "options. If the first option is specified with a \b-\b " + "then the attributes are set for each of the given \aname\as. " + "If the first option is specified with a \b+\b, then the specified " + "attributes are unset. If \b=\b\avalue\a is specified value is " + "assigned before the attributes are set.]" +"[+?When \b\f?\f\b is called inside a function defined with the " + "\bfunction\b reserved word, and \aname\a does not contain a " + "\b.\b, then a local variable statically scoped to that function " + "will be created.]" +"[+?Not all option combinations are possible. For example, the numeric " + "options \b-i\b, \b-E\b, and \b-F\b cannot be specified with " + "the justification options \b-L\b, \b-R\b, and \b-Z\b.]" +"[+?Note that the following preset aliases are set by the shell:]{" + "[+float?\b\f?\f -E\b.]" + "[+functions?\b\f?\f -f\b.]" + "[+integer?\b\f?\f -i\b.]" + "[+nameref?\b\f?\f -n\b.]" +"}" +"[+?If no \aname\as are specified then variables that have the specified " + "options are displayed. If the first option is specified with " + "a leading \b-\b then the name and value of each variable is " + "written to standard output. Otherwise, only the names are " + "written. If no options are specified or just \b-p\b is " + "specified, then the names and attributes of all variables that have " + "attributes are written to standard output. When \b-f\b is specified, " + "the names displayed will be function names.]" +"[+?If \b-f\b is specified, then each \aname\a refers to a function " + "and the only valid options are \b-u\b and \b-t\b. In this " + "case no \b=\b\avalue\a can be specified.]" +"[+?\b\f?\f\b is built-in to the shell as a declaration command so that " + "field splitting and pathname expansion are not performed on " + "the arguments. Tilde expansion occurs on \avalue\a.]" +#if SHOPT_BASH +"[a?Ignored, used for bash compatibility.]" +#endif +"[a?Indexed array. this is the default.]" +"[b?Each \aname\a may contain binary data. Its value is the mime " + "base64 encoding of the data. It can be used with \b-Z\b, " + "to specify fixed sized fields.]" +"[f?Each of the options and \aname\as refers to a function.]" +"[i]#?[base:=10?An integer. \abase\a represents the arithmetic base " + "from 2 to 64.]" +"[l?Convert uppercase character to lowercase. Unsets \b-u\b attribute. When " + "used with \b-i\b, \b-E\b, or \b-F\b indicates long variant.]" +"[n?Name reference. The value is the name of a variable that \aname\a " + "references. \aname\a cannot contain a \b.\b.]" +"[p?Causes the output to be in a format that can be used as input to the " + "shell to recreate the attributes for variables.]" +"[r?Enables readonly. Once enabled it cannot be disabled. See " + "\breadonly\b(1).]" +"[s?Used with \b-i\b to restrict integer size to short.]" +"[t?When used with \b-f\b, enables tracing for each of the specified " + "functions. Otherwise, \b-t\b is a user defined attribute and " + "has no meaning to the shell.]" +"[u?Without \b-f\b or \b-i\b, converts lowercase character to uppercase " + "and unsets \b-l\b. With \b-f\b specifies that \aname\a is a function " + "that hasn't been loaded yet. With \b-i\b specifies that the " + "value will be displayed as an unsigned integer.]" +"[x?Puts each \aname\a on the export list. See \bexport\b(1). \aname\a " + "cannot contain a \b.\b.]" +"[A?Associative array. Each \aname\a will converted to an associate " + "array. If a variable already exists, the current value will " + "become index \b0\b.]" +"[E]#?[n:=10?Floating point number represented in scientific notation. " + "\an\a specifies the number of significant figures when the " + "value is expanded.]" +"[F]#?[n:=10?Floating point. \an\a is the number of places after the " + "decimal point when the value is expanded.]" +"[H?Hostname mapping. Each \aname\a holds a native pathname. Assigning a " + "UNIX format pathname will cause it to be converted to a pathname " + "suitable for the current host. This has no effect when the " + "native system is UNIX.]" +"[L]#?[n?Left justify. If \an\a is given it represents the field width. If " + "the \b-Z\b attribute is also specified, then leading zeros are " + "stripped.]" +"[R]#?[n?Right justify. If \an\a is given it represents the field width. If " + "the \b-Z\b attribute is also specified, then zeros will " + "be used as the fill character. Otherwise, spaces are used.]" +"[T]:[tname?\atname\a is the name of a type name given to each \aname\a.]" +"[Z]#?[n?Zero fill. If \an\a is given it represents the field width.]" +"\n" +"\n[name[=value]...]\n" +" -f [name...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?No errors occurred.]" + "[+>0?An error occurred.]" +"}" + +"[+SEE ALSO?\breadonly\b(1), \bexport\b(1)]" +; + +const char sh_optulimit[] = +"[-1c?@(#)$Id: ulimit (AT&T Research) 2003-06-21 $\n]" +USAGE_LICENSE +"[+NAME?ulimit - set or display resource limits]" +"[+DESCRIPTION?\bulimit\b sets or displays resource limits. These " + "limits apply to the current process and to each child process " + "created after the resource limit has been set. If \alimit\a " + "is specified, the resource limit is set, otherwise, its current value " + "is displayed on standard output.]" +"[+?Increasing the limit for a resource usually requires special privileges. " + "Some systems allow you to lower resource limits and later increase " + "them. These are called soft limits. Once a hard limit is " + "set the resource can not be increased.]" +"[+?Different systems allow you to specify different resources and some " + "restrict how much you can raise the limit of the resource.]" +"[+?The value of \alimit\a depends on the unit of the resource listed " + "for each resource. In addition, \alimit\a can be \bunlimited\b " + "to indicate no limit for that resource.]" +"[+?If you do not specify \b-H\b or \b-S\b, then \b-S\b is used for " + "listing and both \b-S\b and \b-H\b are used for setting resources.]" +"[+?If you do not specify any resource, the default is \b-f\b.]" +"[H?A hard limit is set or displayed.]" +"[S?A soft limit is set or displayed.]" +"[a?Displays all current resource limits]" +"\flimits\f" +"\n" +"\n[limit]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?A request for a higher limit was rejected or an error occurred.]" +"}" + +"[+SEE ALSO?\bulimit\b(2), \bgetrlimit\b(2)]" +; + +const char sh_optumask[] = +"[-1c?\n@(#)$Id: umask (AT&T Research) 1999-04-07 $\n]" +USAGE_LICENSE +"[+NAME?umask - get or set the file creation mask]" +"[+DESCRIPTION?\bumask\b sets the file creation mask of the current " + "shell execution environment to the value specified by the " + "\amask\a operand. This mask affects the file permission bits " + "of subsequently created files. \amask\a can either be an " + "octal number or a symbolic value as described in \bchmod\b(1). " + "If a symbolic value is given, the new file creation mask is the " + "complement of the result of applying \amask\a to the complement " + "of the current file creation mask.]" +"[+?If \amask\a is not specified, \bumask\b writes the value of the " + "file creation mask for the current process to standard output.]" +"[S?Causes the file creation mask to be written or treated as a symbolic value " + "rather than an octal number.]" +"\n" +"\n[mask]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?The file creation mask was successfully changed, or no " + "\amask\a operand was supplied.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bchmod\b(1)]" +; +const char sh_optuniverse[] = " [name]"; +const char sh_optunset[] = +"[-1c?\n@(#)$Id: unset (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?unset - unset values and attributes of variables and functions]" +"[+DESCRIPTION?For each \aname\a specified, \bunset\b unsets the variable, " + "or function if \b-f\b is specified, from the current shell " + "execution environment. Readonly variables cannot be unset.]" +"[n?If \aname\a refers to variable that is a reference, the variable \aname\a " + "will be unset rather than the variable it references. Otherwise, " + "is is equivalent to \b-v\b.]" +"[f?\aname\a refers to a function name and the shell will unset the " + "function definition.]" +"[v?\aname\a refers to a variable name and the shell will unset it and " + "remove it from the environment. This is the default behavior.]" +"\n" +"\nname...\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?All \aname\as were successfully unset.]" + "[+>0?One or more \aname\a operands could not be unset " + "or an error occurred.]" +"}" + +"[+SEE ALSO?\btypeset\b(1)]" +; + +const char sh_optunalias[] = +"[-1c?\n@(#)$Id: unalias (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?unalias - remove alias definitions]" +"[+DESCRIPTION?\bunalias\b removes the definition of each named alias " + "from the current shell execution environment, or all aliases if " + "\b-a\b is specified. It will not affect any commands that " + "have already been read and subsequently executed.]" +"[a?Causes all alias definitions to be removed. \aname\a operands " + "are optional and ignored in this case.]" +"\n" +"\nname...\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?\b-a\b was not specified and one or more \aname\a operands " + "did not have an alias definition, or an error occurred.]" +"}" + +"[+SEE ALSO?\balias\b(1)]" +; + +const char sh_optwait[] = +"[-1c?\n@(#)$Id: wait (AT&T Research) 1999-06-17 $\n]" +USAGE_LICENSE +"[+NAME?wait - wait for process or job completion]" +"[+DESCRIPTION?\bwait\b with no operands, waits until all jobs " + "known to the invoking shell have terminated. If one or more " + "\ajob\a operands are specified, \bwait\b waits until all of them " + "have completed.]" +"[+?Each \ajob\a can be specified as one of the following:]{" + "[+\anumber\a?\anumber\a refers to a process id.]" + "[+-\anumber\a?\anumber\a refers to a process group id.]" + "[+%\anumber\a?\anumber\a refer to a job number.]" + "[+%\astring\a?Refers to a job whose name begins with \astring\a.]" + "[+%??\astring\a?Refers to a job whose name contains \astring\a.]" + "[+%+ \bor\b %%?Refers to the current job.]" + "[+%-?Refers to the previous job.]" +"}" +"[+?If one ore more \ajob\a operands is a process id or process group id " + "not known by the current shell environment, \bwait\b treats each " + "of them as if it were a process that exited with status 127.]" +"\n" +"\n[job ...]\n" +"\n" +"[+EXIT STATUS?If \await\a is invoked with one or more \ajob\as, and all of " + "them have terminated or were not known by the invoking shell, " + "the exit status of \bwait\b will be that of the last \ajob\a. " + "Otherwise, it will be one of the following:]{" + "[+0?\bwait\b utility was invoked with no operands and all " + "processes known by the invoking process have terminated.]" + "[+127?\ajob\a is a process id or process group id that is unknown " + "to the current shell environment.]" +"}" + +"[+SEE ALSO?\bjobs\b(1), \bps\b(1)]" +; + +#if SHOPT_FS_3D + const char sh_optvpath[] = " [top] [base]"; + const char sh_optvmap[] = " [dir] [list]"; +#endif /* SHOPT_FS_3D */ + +const char sh_optwhence[] = +"[-1c?\n@(#)$Id: whence (AT&T Research) 1999-07-07 $\n]" +USAGE_LICENSE +"[+NAME?whence - locate a command and describe its type]" +"[+DESCRIPTION?Without \b-v\b, \bwhence\b writes on standard output an " + "absolute pathname, if any, corresponding to \aname\a based " + "on the complete search order that the shell uses. If \aname\a " + "is not found, then no output is produced.]" +"[+?If \b-v\b is specified, the output will also contain information " + "that indicates how the given \aname\a would be interpretted by " + "the shell in the current execution environment.]" +"[a?Displays all uses for each \aname\a rather than the first.]" +"[f?Do not check for functions.]" +"[p?Do not check to see if \aname\a is a reserved word, a built-in, " + "an alias, or a function.]" +"[v?For each name you specify, the shell displays a line that indicates " + "if that name is one of the following:]{" + "[+?Reserved word]" + "[+?Alias]" + "[+?Built-in]" + "[+?Undefined function]" + "[+?Function]" + "[+?Tracked alias]" + "[+?Program]" + "[+?Not found]" +"}" +"\n" +"\nname ...\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Each \aname\a was found by the shell.]" + "[+1?One or more \aname\as were not found by the shell.]" + "[+>1?An error occurred.]" +"}" + +"[+SEE ALSO?\bcommand\b(1)]" +; + + +const char e_alrm1[] = "alarm -r %s +%.3g\n"; +const char e_alrm2[] = "alarm %s %.3f\n"; +const char e_baddisc[] = "%s: invalid discipline function"; +const char e_nospace[] = "out of memory"; +const char e_nofork[] = "cannot fork"; +const char e_nosignal[] = "%s: unknown signal name"; +const char e_numeric[] = "*([0-9])?(.)*([0-9])"; +const char e_condition[] = "condition(s) required"; +const char e_cneedsarg[] = "-c requires argument"; diff --git a/usr/src/lib/libshell/common/data/keywords.c b/usr/src/lib/libshell/common/data/keywords.c new file mode 100644 index 0000000000..66b3c3cea2 --- /dev/null +++ b/usr/src/lib/libshell/common/data/keywords.c @@ -0,0 +1,63 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#include "shtable.h" +#include <shell.h> +#include "shlex.h" +#include "FEATURE/options" + +/* + * table of reserved words in shell language + * This list must be in in ascii sorted order + */ + +const Shtable_t shtab_reserved[] = +{ + "!", NOTSYM, + "[[", BTESTSYM, + "case", CASESYM, + "do", DOSYM, + "done", DONESYM, + "elif", ELIFSYM, + "else", ELSESYM, + "esac", ESACSYM, + "fi", FISYM, + "for", FORSYM, + "function", FUNCTSYM, + "if", IFSYM, + "in", INSYM, +#if SHOPT_NAMESPACE + "namespace", NSPACESYM, +#endif /* SHOPT_NAMESPACE */ + "select", SELECTSYM, + "then", THENSYM, + "time", TIMESYM, + "until", UNTILSYM, + "while", WHILESYM, + "{", LBRACE, + "}", RBRACE, + "", 0, +}; + +const char e_unexpected[] = "unexpected"; +const char e_unmatched[] = "unmatched"; +const char e_endoffile[] = "end of file"; +const char e_newline[] = "newline"; + diff --git a/usr/src/lib/libshell/common/data/lexstates.c b/usr/src/lib/libshell/common/data/lexstates.c new file mode 100644 index 0000000000..0cedf9a4dc --- /dev/null +++ b/usr/src/lib/libshell/common/data/lexstates.c @@ -0,0 +1,411 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <ast.h> + +#include "FEATURE/options" +#include "lexstates.h" + + +/* + * This is the initial state for tokens + */ +static const char sh_lexstate0[256] = +{ + S_EOF, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, +#if SHOPT_CRNL + S_REG, 0, S_NLTOK,S_REG, S_REG, 0, S_REG, S_REG, +#else + S_REG, 0, S_NLTOK,S_REG, S_REG, S_REG, S_REG, S_REG, +#endif /* SHOPT_CRNL */ + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + + 0, S_REG, S_REG, S_COM, S_REG, S_REG, S_OP, S_REG, + S_OP, S_OP, S_REG, S_REG, S_REG, S_REG, S_NAME, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_OP, S_OP, S_REG, S_OP, S_REG, + + S_REG, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, + S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, + S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, + S_NAME, S_NAME, S_NAME, S_REG, S_REG, S_REG, S_REG, S_NAME, + + S_REG, S_NAME, S_NAME, S_RES, S_RES, S_RES, S_RES, S_NAME, +#if SHOPT_NAMESPACE + S_NAME, S_RES, S_NAME, S_NAME, S_NAME, S_NAME, S_RES, S_NAME, +#else + S_NAME, S_RES, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, S_NAME, +#endif /* SHOPT_NAMESPACE */ + S_NAME, S_NAME, S_NAME, S_RES, S_RES, S_RES, S_NAME, S_RES, + S_NAME, S_NAME, S_NAME, S_REG, S_OP, S_REG, S_TILDE,S_REG, + + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, +}; + +/* + * This state is for identifiers + */ +static const char sh_lexstate1[256] = +{ + S_EOF, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, +#if SHOPT_CRNL + S_REG, S_BREAK,S_BREAK,S_REG, S_REG, S_BREAK,S_REG, S_REG, +#else + S_REG, S_BREAK,S_BREAK,S_REG, S_REG, S_REG, S_REG, S_REG, +#endif /* SHOPT_CRNL */ + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + + S_BREAK,S_EPAT, S_QUOTE,S_REG, S_DOL, S_EPAT, S_BREAK,S_LIT, + S_BREAK,S_BREAK,S_PAT, S_EPAT, S_REG, S_EPAT, S_DOT, S_REG, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_LABEL,S_BREAK,S_BREAK,S_EQ, S_BREAK,S_PAT, + + S_EPAT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACT,S_ESC, S_REG, S_REG, 0, + + S_GRAVE,0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACE,S_BREAK,S_BRACE,S_EPAT, S_REG, + + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, + S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, S_REG, +}; + +static const char sh_lexstate2[256] = +{ + S_EOF, 0, 0, 0, 0, 0, 0, 0, +#if SHOPT_CRNL + 0, S_BREAK,S_BREAK,0, 0, S_BREAK,0, 0, +#else + 0, S_BREAK,S_BREAK,0, 0, 0, 0, 0, +#endif /* SHOPT_CRNL */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S_BREAK,S_EPAT, S_QUOTE,0, S_DOL, S_EPAT, S_BREAK,S_LIT, + S_BREAK,S_BREAK,S_PAT, S_EPAT, 0, S_EPAT, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_COLON,S_BREAK,S_BREAK,0, S_BREAK,S_PAT, + + S_EPAT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_PAT, S_ESC, 0, 0, 0, + + S_GRAVE,0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACE,S_BREAK,S_BRACE,S_EPAT, 0, +}; + +/* + * for skipping over '...' + */ +static const char sh_lexstate3[256] = +{ + S_EOF, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_NL, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, S_LIT, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, S_ESC2, 0, 0, 0 +}; + +/* + * for skipping over "..." and `...` + */ +static const char sh_lexstate4[256] = +{ + S_EOF, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_NL, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_QUOTE,0, S_DOL, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, S_ESC, 0, 0, 0, + S_GRAVE,0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, S_RBRA, 0, 0 +}; + +/* + * for skipping over ?(...), [...] + */ +static const char sh_lexstate5[256] = +{ + S_EOF, 0, 0, 0, 0, 0, 0, 0, + 0, S_BLNK, S_NL, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + S_BLNK, 0, S_QUOTE,0, S_DOL, 0, S_META, S_LIT, + S_PUSH, S_POP, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_POP, S_META, 0, S_META, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACT,S_ESC, S_POP, 0, 0, + S_GRAVE,0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACE,S_META, S_POP, 0, 0 +}; + +/* + * Defines valid expansion characters + */ +static const char sh_lexstate6[256] = +{ + S_EOF, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + + S_ERR, S_SPC1, S_ERR, S_SPC1, S_SPC2, S_ERR, S_ERR, S_LIT, + S_PAR, S_ERR, S_SPC2, S_ERR, S_ERR, S_SPC2, S_ALP, S_ERR, + S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, + S_DIG, S_DIG, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_SPC2, + +#if SHOPT_TYPEDEF + S_SPC1, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, +#else + S_SPC2, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, +#endif + S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, + S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, + S_ALP, S_ALP, S_ALP, S_ERR, S_ERR, S_ERR, S_ERR, S_ALP, + + S_ERR, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, + S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, + S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, S_ALP, + S_ALP, S_ALP, S_ALP, S_LBRA, S_ERR, S_RBRA, S_ERR, S_ERR, + + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, +}; + +/* + * for skipping over ${...} until modifier + */ +static const char sh_lexstate7[256] = +{ + S_EOF, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + + S_ERR, S_ERR, S_ERR, S_MOD2, S_ERR, S_MOD2, S_ERR, S_ERR, + S_ERR, S_ERR, S_MOD1, S_MOD1, S_ERR, S_MOD1, S_DOT, S_MOD2, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_MOD1, S_ERR, S_ERR, S_MOD1, S_ERR, S_MOD1, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACT,S_ESC, S_ERR, S_ERR, 0, + + S_ERR, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_ERR, S_ERR, S_POP, S_ERR, S_ERR, + + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, + S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, S_ERR, +}; + +/* + * This state is for $name + */ +static const char sh_lexstate8[256] = +{ + S_EOF, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + + S_EDOL, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_EDOL, S_EDOL, S_EDOL, S_EDOL, 0, + + S_EDOL,0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, + S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, S_EDOL, +}; + +/* + * This is used for macro expansion + */ +static const char sh_lexstate9[256] = +{ + S_EOF, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, S_QUOTE,0, S_DOL, 0, S_PAT, S_LIT, + S_PAT, S_PAT, S_PAT, 0, 0, 0, 0, S_SLASH, + 0, S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, S_DIG, + S_DIG, S_DIG, S_COLON,0, 0, S_EQ, 0, S_PAT, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, S_BRACT,S_ESC, S_ENDCH,0, 0, + S_GRAVE,0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +#if SHOPT_BRACEPAT + 0, 0, 0, S_BRACE,S_PAT, S_ENDCH,0, 0 +#else + 0, 0, 0, 0, S_PAT, S_ENDCH,0, 0 +#endif /* SHOPT_BRACEPAT */ +}; + +const char *sh_lexrstates[ST_NONE] = +{ + sh_lexstate0, sh_lexstate1, sh_lexstate2, sh_lexstate3, + sh_lexstate4, sh_lexstate5, sh_lexstate6, sh_lexstate7, + sh_lexstate8, sh_lexstate9, sh_lexstate5 +}; + + +const char e_lexversion[] = "%d: invalid binary script version"; +const char e_lexspace[] = "line %d: use space or tab to separate operators %c and %c"; +const char e_lexslash[] = "line %d: $ not preceeded by \\"; +const char e_lexsyntax1[] = "syntax error at line %d: `%s' %s"; +const char e_lexsyntax2[] = "syntax error: `%s' %s"; +const char e_lexsyntax3[] = "syntax error at line %d: duplicate label %s"; +const char e_lexlabignore[] = "line %d: label %s ignored"; +const char e_lexlabunknown[] = "line %d: %s unknown label"; +const char e_lexobsolete1[] = "line %d: `...` obsolete, use $(...)"; +const char e_lexobsolete2[] = "line %d: -a obsolete, use -e"; +const char e_lexobsolete3[] = "line %d: '=' obsolete, use '=='"; +const char e_lexobsolete4[] = "line %d: %s within [[...]] obsolete, use ((...))"; +const char e_lexobsolete5[] = "line %d: set %s obsolete"; +const char e_lexobsolete6[] = "line %d: `{' instead of `in' is obsolete"; +const char e_lexusebrace[] = "line %d: use braces to avoid ambiguities with $id[...]"; +const char e_lexusequote[] = "line %d: %c within ${} should be quoted"; +const char e_lexescape[] = "line %d: escape %c to avoid ambiguities"; +const char e_lexquote[] = "line %d: quote %c to avoid ambiguities"; +const char e_lexnested[] = "line %d: spaces required for nested subshell"; +const char e_lexbadchar[] = "%c: invalid character in expression - %s"; +const char e_lexfuture[] = "line %d: \\ in front of %c reserved for future use"; +const char e_lexlongquote[] = "line %d: %c quote may be missing"; +const char e_lexzerobyte[] = "zero byte"; +const char e_lexemptyfor[] = "line %d: empty for list"; diff --git a/usr/src/lib/libshell/common/data/limits.c b/usr/src/lib/libshell/common/data/limits.c new file mode 100644 index 0000000000..9f0a87b3e8 --- /dev/null +++ b/usr/src/lib/libshell/common/data/limits.c @@ -0,0 +1,57 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <ast.h> +#include "ulimit.h" + +/* + * This is the list of resouce limits controlled by ulimit + * This command requires getrlimit(), vlimit(), or ulimit() + */ + +#ifndef _no_ulimit + +const char e_unlimited[] = "unlimited"; +const char* e_units[] = { 0, "block", "byte", "kbyte", "second" }; + +const int shtab_units[] = { 1, 512, 1, 1024, 1 }; + +const Limit_t shtab_limits[] = +{ +"as", "address space limit", RLIMIT_AS, 0, 'M', LIM_KBYTE, +"core", "core file size", RLIMIT_CORE, 0, 'c', LIM_BLOCK, +"cpu", "cpu time", RLIMIT_CPU, 0, 't', LIM_SECOND, +"data", "data size", RLIMIT_DATA, 0, 'd', LIM_KBYTE, +"fsize", "file size", RLIMIT_FSIZE, 0, 'f', LIM_BLOCK, +"locks", "number of file locks", RLIMIT_LOCKS, 0, 'L', LIM_COUNT, +"memlock", "locked address space", RLIMIT_MEMLOCK, 0, 'l', LIM_KBYTE, +"nofile", "number of open files", RLIMIT_NOFILE, "OPEN_MAX", 'n', LIM_COUNT, +"nproc", "number of processes", RLIMIT_NPROC, "CHILD_MAX", 'u', LIM_COUNT, +"pipe", "pipe buffer size", RLIMIT_PIPE, "PIPE_BUF", 'p', LIM_BYTE, +"rss", "resident set size", RLIMIT_RSS, 0, 'm', LIM_KBYTE, +"sbsize", "socket buffer size", RLIMIT_SBSIZE, "PIPE_BUF", 'b', LIM_BYTE, +"stack", "stack size", RLIMIT_STACK, 0, 's', LIM_KBYTE, +"threads", "number of threads", RLIMIT_PTHREAD, "THREADS_MAX", 'T', LIM_COUNT, +"vmem", "process size", RLIMIT_VMEM, 0, 'v', LIM_KBYTE, +{ 0 } +}; + +#endif diff --git a/usr/src/lib/libshell/common/data/math.tab b/usr/src/lib/libshell/common/data/math.tab new file mode 100644 index 0000000000..cecfc12a34 --- /dev/null +++ b/usr/src/lib/libshell/common/data/math.tab @@ -0,0 +1,64 @@ +# <return type: i:integer f:floating-point> <#floating-point-args> <function-name> [<alias> ...] +# <function-name>l variants are handled by features/math.sh +# @(#)math.tab (AT&T Research) 2006-10-18 +f 1 acos +f 1 acosh +f 1 asin +f 1 asinh +f 1 atan +f 2 atan2 +f 1 atanh +f 1 cbrt +f 2 copysign +f 1 cos +f 1 cosh +f 1 erf +f 1 erfc +f 1 exp +f 1 exp2 +f 1 expm1 +f 1 fabs abs +f 2 fdim +f 1 finite +f 1 floor int +f 3 fma +f 2 fmax +f 2 fmin +f 2 fmod +i 1 fpclassify +f 2 hypot +i 1 ilogb +i 1 isfinite +i 2 isgreater +i 2 isgreaterequal +i 1 isinf +i 2 isless +i 2 islessequal +i 2 islessgreater +i 1 isnan +i 1 isnormal +i 1 issubnormal +i 2 isunordered +i 1 iszero +f 1 lgamma +f 1 log +f 1 log1p +f 1 log2 +f 1 logb +f 1 nearbyint +f 2 nextafter +f 2 nexttoward +f 2 pow +f 2 remainder +f 1 rint +f 1 round +f 2 scalb +f 2 scalbn +i 1 signbit +f 1 sin +f 1 sinh +f 1 sqrt +f 1 tan +f 1 tanh +f 1 tgamma +f 1 trunc diff --git a/usr/src/lib/libshell/common/data/msg.c b/usr/src/lib/libshell/common/data/msg.c new file mode 100644 index 0000000000..498f6558d0 --- /dev/null +++ b/usr/src/lib/libshell/common/data/msg.c @@ -0,0 +1,188 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * UNIX shell + * S. R. Bourne + * Rewritten by David Korn + * + * AT&T Labs + * + */ + +#include <ast.h> +#include <errno.h> +#include "defs.h" +#include "path.h" +#include "io.h" +#include "shlex.h" +#include "timeout.h" +#include "history.h" +#include "builtins.h" +#include "jobs.h" +#include "edit.h" + +/* error messages */ +const char e_timewarn[] = "\r\n\ashell will timeout in 60 seconds due to inactivity"; +const char e_runvi[] = "\\hist -e \"${VISUAL:-${EDITOR:-vi}}\" "; +const char e_timeout[] = "timed out waiting for input"; +const char e_mailmsg[] = "you have mail in $_"; +const char e_query[] = "no query process"; +const char e_history[] = "no history file"; +const char e_histopen[] = "history file cannot open"; +const char e_option[] = "%s: bad option(s)"; +const char e_toomany[] = "open file limit exceeded"; +const char e_argtype[] = "invalid argument of type %c"; +const char e_formspec[] = "%c: unknown format specifier"; +const char e_badregexp[] = "%s: invalid regular expression"; +const char e_number[] = "%s: bad number"; +const char e_badlocale[] = "%s: unknown locale"; +const char e_nullset[] = "%s: parameter null"; +const char e_notset[] = "%s: parameter not set"; +const char e_noparent[] = "%s: no parent"; +const char e_subst[] = "%s: bad substitution"; +const char e_create[] = "%s: cannot create"; +const char e_tmpcreate[] = "cannot create temporary file"; +const char e_restricted[] = "%s: restricted"; +const char e_pfsh[] = "%s: disabled in profile shell"; +const char e_pexists[] = "process already exists"; +const char e_exists[] = "%s: file already exists"; +const char e_pipe[] = "cannot create pipe"; +const char e_alarm[] = "cannot set alarm"; +const char e_open[] = "%s: cannot open"; +const char e_notseek[] = "%s: not seekable"; +const char e_badseek[] = "%s: invalid seek offset"; +const char e_badpattern[] = "%s: invalid shell pattern"; +const char e_noread[] = "%s: pattern seek requires read access"; +const char e_logout[] = "Use 'exit' to terminate this shell"; +const char e_exec[] = "%s: cannot execute"; +const char e_pwd[] = "cannot access parent directories"; +const char e_found[] = "%s: not found"; +const char e_defined[] = "%s: function not defined"; +const char e_nointerp[] = "%s: interpreter not found"; +const char e_subscript[] = "%s: subscript out of range"; +const char e_toodeep[] = "%s: recursion too deep"; +const char e_access[] = "permission denied"; +#ifdef _cmd_universe + const char e_nouniverse[] = "universe not accessible"; +#endif /* _cmd_universe */ +const char e_direct[] = "bad directory"; +const char e_file[] = "%s: bad file unit number"; +const char e_trap[] = "%s: bad trap"; +const char e_readonly[] = "%s: is read only"; +const char e_badfield[] = "%d: negative field size"; +const char e_ident[] = "%s: is not an identifier"; +const char e_badname[] = "%s: invalid name"; +const char e_varname[] = "%s: invalid variable name"; +const char e_badfun[] = "%s: invalid function name"; +const char e_aliname[] = "%s: invalid alias name"; +const char e_badexport[] = "%s: invalid export name"; +const char e_badref[] = "%s: reference variable cannot be an array"; +const char e_noarray[] = "%s: cannot be an array"; +const char e_noref[] = "%s: no reference name"; +const char e_selfref[] = "%s: invalid self reference"; +const char e_noalias[] = "%s: alias not found\n"; +const char e_format[] = "%s: bad format"; +const char e_redef[] = "%s: type cannot be redefined"; +const char e_badtformat[] = "%c: bad format character in time format"; +const char e_nolabels[] = "%s: label not implemented"; +const char e_notimp[] = "%s: not implemented"; +const char e_nosupport[] = "not supported"; +const char e_badrange[] = "%d-%d: invalid range"; +const char e_eneedsarg[] = "-e - requires single argument"; +const char e_badbase[] = "%s unknown base"; +const char e_loop[] = "%s: would cause loop"; +const char e_overlimit[] = "%s: limit exceeded"; +const char e_badsyntax[] = "incorrect syntax"; +const char e_badwrite[] = "write to %d failed"; +const char e_on [] = "on"; +const char e_off[] = "off"; +const char is_reserved[] = " is a keyword"; +const char is_builtin[] = " is a shell builtin"; +const char is_builtver[] = "is a shell builtin version of"; +const char is_alias[] = "%s is an alias for "; +const char is_xalias[] = "%s is an exported alias for "; +const char is_talias[] = "is a tracked alias for"; +const char is_function[] = " is a function"; +const char is_ufunction[] = " is an undefined function"; +#ifdef JOBS +# ifdef SIGTSTP + const char e_newtty[] = "Switching to new tty driver..."; + const char e_oldtty[] = "Reverting to old tty driver..."; + const char e_no_start[] = "Cannot start job control"; +# endif /*SIGTSTP */ + const char e_no_jctl[] = "No job control"; + const char e_terminate[] = "You have stopped jobs"; + const char e_done[] = " Done"; + const char e_nlspace[] = "\n "; + const char e_running[] = " Running"; + const char e_ambiguous[] = "%s: Ambiguous"; + const char e_jobsrunning[] = "You have running jobs"; + const char e_no_job[] = "no such job"; + const char e_no_proc[] = "no such process"; + const char e_jobusage[] = "%s: Arguments must be %%job or process ids"; +#endif /* JOBS */ +const char e_coredump[] = "(coredump)"; +const char e_alphanum[] = "[_[:alpha:]]*([_[:alnum:]])"; +const char e_devfdNN[] = "/dev/fd/+([0-9])"; +const char e_devfdstd[] = "/dev/@(fd/+([0-9])|std@(in|out|err))"; +const char e_signo[] = "Signal %d"; +#if SHOPT_FS_3D + const char e_cantget[] = "cannot get %s"; + const char e_cantset[] = "cannot set %s"; + const char e_mapping[] = "mapping"; + const char e_versions[] = "versions"; +#endif /* SHOPT_FS_3D */ + +/* string constants */ +const char e_heading[] = "Current option settings"; +const char e_sptbnl[] = " \t\n"; +const char e_defpath[] = "/bin:/usr/bin:"; +const char e_defedit[] = "/bin/ed"; +const char e_unknown [] = "<command unknown>"; +const char e_devnull[] = "/dev/null"; +const char e_traceprompt[] = "+ "; +const char e_supprompt[] = "# "; +const char e_stdprompt[] = "$ "; +const char e_profile[] = "$HOME/.profile"; +const char e_sysprofile[] = "/etc/profile"; +const char e_suidprofile[] = "/etc/suid_profile"; +#if SHOPT_SYSRC +const char e_sysrc[] = "/etc/ksh.kshrc"; +#endif +#if SHOPT_BASH +#if SHOPT_SYSRC +const char e_bash_sysrc[] = "/etc/bash.bashrc"; +#endif +const char e_bash_rc[] = "$HOME/.bashrc"; +const char e_bash_login[] = "$HOME/.bash_login"; +const char e_bash_logout[] = "$HOME/.bash_logout"; +const char e_bash_profile[] = "$HOME/.bash_profile"; +#endif +const char e_crondir[] = "/usr/spool/cron/atjobs"; +const char e_prohibited[] = "login setuid/setgid shells prohibited"; +#if SHOPT_SUID_EXEC + const char e_suidexec[] = "/etc/suid_exec"; +#endif /* SHOPT_SUID_EXEC */ +const char hist_fname[] = "/.sh_history"; +const char e_dot[] = "."; +const char e_envmarker[] = "A__z"; +const char e_timeformat[] = "\nreal\t%2lR\nuser\t%2lU\nsys\t%2lS"; +const char e_dict[] = "libshell"; diff --git a/usr/src/lib/libshell/common/data/options.c b/usr/src/lib/libshell/common/data/options.c new file mode 100644 index 0000000000..82ffd82d6c --- /dev/null +++ b/usr/src/lib/libshell/common/data/options.c @@ -0,0 +1,140 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <defs.h> +#include "FEATURE/options" +#include "name.h" +#include "shtable.h" + +#if SHOPT_BASH +# define bashopt(a,b) a, b|SH_BASHOPT, +# define bashextra(a,b) a, b|SH_BASHEXTRA, +#else +# define bashopt(a,b) +# define bashextra(a,b) +#endif + +/* + * This is the list of invocation and set options + * This list must be in in ascii sorted order + */ + +const Shtable_t shtab_options[] = +{ + "allexport", SH_ALLEXPORT, +#if SHOPT_BASH + "bash", (SH_BASH|SH_COMMANDLINE), +#endif + "bgnice", SH_BGNICE, + "braceexpand", SH_BRACEEXPAND, + bashopt("cdable_vars", SH_CDABLE_VARS) + bashopt("cdspell", SH_CDSPELL) + bashopt("checkhash", SH_CHECKHASH) + bashopt("checkwinsize", SH_CHECKWINSIZE) + "noclobber", SH_NOCLOBBER, + bashopt("dotglob", SH_DOTGLOB) + "emacs", SH_EMACS, + "errexit", SH_ERREXIT, + "noexec", SH_NOEXEC, + bashopt("execfail", SH_EXECFAIL) + bashopt("expand_aliases", SH_EXPAND_ALIASES) + bashopt("extglob", SH_EXTGLOB) + "noglob", SH_NOGLOB, + "globstar", SH_GLOBSTARS, + "gmacs", SH_GMACS, + bashextra("hashall", SH_TRACKALL) + bashopt("histappend", SH_HISTAPPEND) +#if SHOPT_HISTEXPAND + "histexpand", SH_HISTEXPAND, +#else + bashextra("histexpand", SH_HISTEXPAND) +#endif + bashextra("history", SH_HISTORY2) + bashopt("histreedit", SH_HISTREEDIT) + bashopt("histverify", SH_HISTVERIFY) + bashopt("hostcomplete", SH_HOSTCOMPLETE) + bashopt("huponexit", SH_HUPONEXIT) + "ignoreeof", SH_IGNOREEOF, + "interactive", SH_INTERACTIVE|SH_COMMANDLINE, + bashextra("interactive_comments", SH_INTERACTIVE_COMM) + "keyword", SH_KEYWORD, + bashopt("lithist", SH_LITHIST) + "nolog", SH_NOLOG, + "login_shell", SH_LOGIN_SHELL|SH_COMMANDLINE, + bashopt("mailwarn", SH_MAILWARN) + "markdirs", SH_MARKDIRS, + "monitor", SH_MONITOR, + "multiline", SH_MULTILINE, + bashopt("no_empty_cmd_completion", SH_NOEMPTYCMDCOMPL) + bashopt("nocaseglob", SH_NOCASEGLOB) + "notify", SH_NOTIFY, + bashopt("nullglob", SH_NULLGLOB) + bashextra("onecmd", SH_TFLAG) + "pipefail", SH_PIPEFAIL, + bashextra("physical", SH_PHYSICAL) + bashextra("posix", SH_POSIX) + "privileged", SH_PRIVILEGED, +#if SHOPT_PFSH + "profile", SH_PFSH|SH_COMMANDLINE, +#endif + bashopt("progcomp", SH_PROGCOMP) + bashopt("promptvars", SH_PROMPTVARS) + "rc", SH_RC|SH_COMMANDLINE, + "restricted", SH_RESTRICTED, + bashopt("restricted_shell", SH_RESTRICTED2|SH_COMMANDLINE) + bashopt("shift_verbose", SH_SHIFT_VERBOSE) + "showme", SH_SHOWME, + bashopt("sourcepath", SH_SOURCEPATH) + "trackall", SH_TRACKALL, + "nounset", SH_NOUNSET, + "verbose", SH_VERBOSE, + "vi", SH_VI, + "viraw", SH_VIRAW, + bashopt("xpg_echo", SH_XPG_ECHO) + "xtrace", SH_XTRACE, + "", 0 +}; + +const Shtable_t shtab_attributes[] = +{ + {"-nnameref", NV_REF}, + {"-xexport", NV_EXPORT}, + {"-rreadonly", NV_RDONLY}, + {"-ttagged", NV_TAGGED}, + {"-llong", (NV_INTEGER|NV_DOUBLE|NV_LONG)}, + {"-Eexponential",(NV_INTEGER|NV_DOUBLE|NV_EXPNOTE)}, + {"-Ffloat", (NV_INTEGER|NV_DOUBLE)}, + {"-llong", (NV_INTEGER|NV_LONG)}, + {"-sshort", (NV_INTEGER|NV_SHORT)}, + {"-uunsigned", (NV_INTEGER|NV_UNSIGN)}, + {"-iinteger", NV_INTEGER}, + {"-Hfilename", NV_HOST}, + {"-bbinary", NV_BINARY}, + {"-llowercase", NV_UTOL}, + {"-Zzerofill", NV_ZFILL}, + {"-Lleftjust", NV_LJUST}, + {"-Rrightjust", NV_RJUST}, + {"-uuppercase", NV_LTOU}, + {"-Aassociative array", NV_ARRAY}, + {"-aindexed array", NV_ARRAY}, + {"++namespace", NV_TABLE}, + {"", 0} +}; diff --git a/usr/src/lib/libshell/common/data/signals.c b/usr/src/lib/libshell/common/data/signals.c new file mode 100644 index 0000000000..cb29d32a1a --- /dev/null +++ b/usr/src/lib/libshell/common/data/signals.c @@ -0,0 +1,227 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#include <ast.h> +#include "shtable.h" +#include "fault.h" + +#if defined(SIGCLD) && !defined(SIGCHLD) +# define SIGCHLD SIGCLD +#endif + +#define VAL(sig,mode) ((sig+1)|(mode)<<SH_SIGBITS) +#define TRAP(n) (((n)|SH_TRAP)-1) + +#ifndef ERROR_dictionary +# define ERROR_dictionary(s) (s) +#endif +#define S(s) ERROR_dictionary(s) + +/* + * This is a table that gives numbers and default settings to each signal + * The signal numbers go in the low bits and the attributes go in the high bits + */ + +const struct shtable2 shtab_signals[] = +{ +#ifdef SIGABRT + "ABRT", VAL(SIGABRT,SH_SIGDONE), S("Abort"), +#endif /*SIGABRT */ +#ifdef SIGAIO + "AIO", VAL(SIGAIO,SH_SIGIGNORE), S("Asynchronous I/O"), +#endif /*SIGAIO */ +#ifdef SIGALRM + "ALRM", VAL(SIGALRM,SH_SIGDONE), S("Alarm call"), +#endif /* SIGALRM */ +#ifdef SIGAPOLLO + "APOLLO", VAL(SIGAPOLLO,0), "SIGAPOLLO"), +#endif /* SIGAPOLLO */ +#ifdef SIGBUS + "BUS", VAL(SIGBUS,SH_SIGDONE), S("Bus error"), +#endif /* SIGBUS */ +#ifdef SIGCHLD + "CHLD", VAL(SIGCHLD,SH_SIGFAULT), S("Death of Child"), +# ifdef SIGCLD +# if SIGCLD!=SIGCHLD + "CLD", VAL(SIGCLD,SH_SIGFAULT), S("Death of Child"), +# endif +# endif /* SIGCLD */ +#else +# ifdef SIGCLD + "CLD", VAL(SIGCLD,SH_SIGFAULT), S("Death of Child"), +# endif /* SIGCLD */ +#endif /* SIGCHLD */ +#ifdef SIGCONT + "CONT", VAL(SIGCONT,SH_SIGIGNORE), S("Stopped process continued"), +#endif /* SIGCONT */ + "DEBUG", VAL(TRAP(SH_DEBUGTRAP),0), "", +#ifdef SIGDIL + "DIL", VAL(SIGDIL,0), S("DIL signal"), +#endif /* SIGDIL */ +#ifdef SIGEMT + "EMT", VAL(SIGEMT,SH_SIGDONE), S("EMT trap"), +#endif /* SIGEMT */ + "ERR", VAL(TRAP(SH_ERRTRAP),0), "", +#ifdef SIGERR + "ERR", VAL(SIGERR,0), "", +#endif /* SIGERR */ + "EXIT", VAL(0,0), "", + "FPE", VAL(SIGFPE,SH_SIGDONE), S("Floating exception"), +#ifdef SIGFREEZE + "FREEZE", VAL(SIGFREEZE,SH_SIGIGNORE), S("Special signal used by CPR"), +#endif /* SIGFREEZE */ + "HUP", VAL(SIGHUP,SH_SIGDONE), S("Hangup"), + "ILL", VAL(SIGILL,SH_SIGDONE), S("Illegal instruction"), +#ifdef JOBS + "INT", VAL(SIGINT,SH_SIGINTERACTIVE), S("Interrupt"), +#else + "INT", VAL(SIGINT,SH_SIGINTERACTIVE), "", +#endif /* JOBS */ +#ifdef SIGIO + "IO", VAL(SIGIO,SH_SIGIGNORE), S("IO signal"), +#endif /* SIGIO */ +#ifdef SIGIOT + "IOT", VAL(SIGIOT,SH_SIGDONE), S("Abort"), +#endif /* SIGIOT */ + "KEYBD", VAL(TRAP(SH_KEYTRAP),0), "", +#ifdef SIGKILL + "KILL", VAL(SIGKILL,0), S("Killed"), +#endif /* SIGKILL */ +#ifdef SIGLAB + "LAB", VAL(SIGLAB,0), S("Security label changed"), +#endif /* SIGLAB */ +#ifdef SIGLOST + "LOST", VAL(SIGLOST,SH_SIGDONE), S("Resources lost"), +#endif /* SIGLOST */ +#ifdef SIGLWP + "LWP", VAL(SIGLWP,SH_SIGIGNORE), S("Special signal used by thread library"), +#endif /* SIGLWP */ +#ifdef SIGPHONE + "PHONE", VAL(SIGPHONE,0), S("Phone interrupt"), +#endif /* SIGPHONE */ +#ifdef SIGPIPE +#ifdef JOBS + "PIPE", VAL(SIGPIPE,SH_SIGDONE), S("Broken Pipe"), +#else + "PIPE", VAL(SIGPIPE,SH_SIGDONE), "", +#endif /* JOBS */ +#endif /* SIGPIPE */ +#ifdef SIGPOLL + "POLL", VAL(SIGPOLL,SH_SIGDONE), S("Polling alarm"), +#endif /* SIGPOLL */ +#ifdef SIGPROF + "PROF", VAL(SIGPROF,SH_SIGDONE), S("Profiling time alarm"), +#endif /* SIGPROF */ +#ifdef SIGPWR +# if SIGPWR>0 + "PWR", VAL(SIGPWR,SH_SIGIGNORE), S("Power fail"), +# endif +#endif /* SIGPWR */ +#ifdef SIGQUIT + "QUIT", VAL(SIGQUIT,SH_SIGDONE|SH_SIGINTERACTIVE), S("Quit"), +#ifdef __SIGRTMIN +#undef SIGRTMIN +#define SIGRTMIN __SIGRTMIN +#else +#ifdef _SIGRTMIN +#undef SIGRTMIN +#define SIGRTMIN _SIGRTMIN +#endif +#endif +#ifdef SIGRTMIN + "RTMIN", VAL(SIGRTMIN,0), S("Lowest priority realtime signal"), +#endif /* SIGRTMIN */ +#ifdef __SIGRTMAX +#undef SIGRTMAX +#define SIGRTMAX __SIGRTMAX +#else +#ifdef _SIGRTMAX +#undef SIGRTMAX +#define SIGRTMAX _SIGRTMAX +#endif +#endif +#ifdef SIGRTMAX + "RTMAX", VAL(SIGRTMAX,0), S("Highest priority realtime signal"), +#endif /* SIGRTMAX */ +#endif /* SIGQUIT */ + "SEGV", VAL(SIGSEGV,0), S("Memory fault"), +#ifdef SIGSTOP + "STOP", VAL(SIGSTOP,0), S("Stopped (SIGSTOP)"), +#endif /* SIGSTOP */ +#ifdef SIGSYS + "SYS", VAL(SIGSYS,SH_SIGDONE), S("Bad system call"), +#endif /* SIGSYS */ + "TERM", VAL(SIGTERM,SH_SIGDONE|SH_SIGINTERACTIVE), S("Terminated"), +#ifdef SIGTINT +# ifdef JOBS + "TINT", VAL(SIGTINT,0), S("Interrupt"), +# else + "TINT", VAL(SIGTINT,0), "". +# endif /* JOBS */ +#endif /* SIGTINT */ +#ifdef SIGTRAP + "TRAP", VAL(SIGTRAP,SH_SIGDONE), S("Trace/BPT trap"), +#endif /* SIGTRAP */ +#ifdef SIGTSTP + "TSTP", VAL(SIGTSTP,0), S("Stopped"), +#endif /* SIGTSTP */ +#ifdef SIGTTIN + "TTIN", VAL(SIGTTIN,0), S("Stopped (SIGTTIN)"), +#endif /* SIGTTIN */ +#ifdef SIGTTOU + "TTOU", VAL(SIGTTOU,0), S("Stopped(SIGTTOU)"), +#endif /* SIGTTOU */ +#ifdef SIGURG + "URG", VAL(SIGURG,SH_SIGIGNORE), S("Socket interrupt"), +#endif /* SIGURG */ +#ifdef SIGUSR1 + "USR1", VAL(SIGUSR1,SH_SIGDONE), S("User signal 1"), +#endif /* SIGUSR1 */ +#ifdef SIGUSR2 + "USR2", VAL(SIGUSR2,SH_SIGDONE), S("User signal 2"), +#endif /* SIGUSR2 */ +#ifdef SIGVTALRM + "VTALRM", VAL(SIGVTALRM,SH_SIGDONE), S("Virtual time alarm"), +#endif /* SIGVTALRM */ +#ifdef SIGWINCH + "WINCH", VAL(SIGWINCH,SH_SIGIGNORE), S("Window size change"), +#endif /* SIGWINCH */ +#ifdef SIGMIGRATE + "MIGRATE", VAL(SIGMIGRATE,0), S("Migrate process"), +#endif /* SIGMIGRATE */ +#ifdef SIGDANGER + "DANGER", VAL(SIGDANGER,0), S("System crash soon"), +#endif /* SIGDANGER */ +#ifdef SIGSOUND + "SOUND", VAL(SIGSOUND,0), S("Sound completed"), +#endif /* SIGSOUND */ +#ifdef SIGTHAW + "THAW", VAL(SIGTHAW,SH_SIGIGNORE), S("Special signal used by CPR"), +#endif /* SIGTHAW */ +#ifdef SIGWAITING + "WAITING", VAL(SIGWAITING,SH_SIGIGNORE), S("All threads blocked"), +#endif /* SIGWAITING */ +#ifdef SIGXCPU + "XCPU", VAL(SIGXCPU,SH_SIGDONE|SH_SIGINTERACTIVE), S("Exceeded CPU time limit"), +#endif /* SIGXCPU */ +#ifdef SIGXFSZ + "XFSZ", VAL(SIGXFSZ,SH_SIGDONE|SH_SIGINTERACTIVE), S("Exceeded file size limit"), +#endif /* SIGXFSZ */ + "", 0, 0 +}; diff --git a/usr/src/lib/libshell/common/data/solaris_cmdlist.h b/usr/src/lib/libshell/common/data/solaris_cmdlist.h new file mode 100644 index 0000000000..d12e2c0ddb --- /dev/null +++ b/usr/src/lib/libshell/common/data/solaris_cmdlist.h @@ -0,0 +1,128 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SOLARIS_CMDLIST_H +#define _SOLARIS_CMDLIST_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * List builtins for Solaris. + * The list here is partially autogenerated and partially hand-picked + * based on compatibility with the native Solaris versions of these + * tools + */ + +/* POSIX compatible commands */ +#ifdef _NOT_YET +#define XPG6CMDLIST(f) { "/usr/xpg6/bin/" #f, NV_BLTIN|NV_NOFREE, bltin(f) }, +#define XPG4CMDLIST(f) { "/usr/xpg4/bin/" #f, NV_BLTIN|NV_NOFREE, bltin(f) }, +#else +#define XPG6CMDLIST(f) +#define XPG4CMDLIST(f) +#endif /* NOT_YET */ +/* + * Commands which are 100% compatible with native Solaris versions (/bin is + * a softlink to ./usr/bin so both need to be listed here) + */ +#define BINCMDLIST(f) { "/bin/" #f, NV_BLTIN|NV_NOFREE, bltin(f) }, +/* Make all ksh93 builtins accessible when /usr/ast/bin was added to ${PATH} */ +#define ASTCMDLIST(f) { "/usr/ast/bin/" #f, NV_BLTIN|NV_NOFREE, bltin(f) }, + +/* undo ast_map.h #defines to avoid collision */ +#undef basename +#undef dirname + +/* Generated data, do not edit. */ +XPG4CMDLIST(basename) +ASTCMDLIST(basename) +BINCMDLIST(cat) +ASTCMDLIST(cat) +XPG4CMDLIST(chgrp) +ASTCMDLIST(chgrp) +ASTCMDLIST(chmod) +XPG4CMDLIST(chown) +BINCMDLIST(chown) +ASTCMDLIST(chown) +ASTCMDLIST(cmp) +ASTCMDLIST(comm) +XPG4CMDLIST(cp) +ASTCMDLIST(cp) +ASTCMDLIST(cut) +XPG4CMDLIST(date) +ASTCMDLIST(date) +ASTCMDLIST(dirname) +XPG4CMDLIST(expr) +ASTCMDLIST(expr) +ASTCMDLIST(fds) +ASTCMDLIST(fmt) +ASTCMDLIST(fold) +BINCMDLIST(head) +ASTCMDLIST(head) +XPG4CMDLIST(id) +ASTCMDLIST(id) +ASTCMDLIST(join) +XPG4CMDLIST(ln) +ASTCMDLIST(ln) +ASTCMDLIST(logname) +BINCMDLIST(mkdir) +ASTCMDLIST(mkdir) +ASTCMDLIST(mkfifo) +XPG4CMDLIST(mv) +ASTCMDLIST(mv) +ASTCMDLIST(paste) +ASTCMDLIST(pathchk) +ASTCMDLIST(rev) +XPG4CMDLIST(rm) +ASTCMDLIST(rm) +BINCMDLIST(rmdir) +ASTCMDLIST(rmdir) +XPG4CMDLIST(stty) +ASTCMDLIST(stty) +XPG4CMDLIST(tail) +ASTCMDLIST(tail) +BINCMDLIST(tee) +ASTCMDLIST(tee) +ASTCMDLIST(tty) +ASTCMDLIST(uname) +BINCMDLIST(uniq) +ASTCMDLIST(uniq) +BINCMDLIST(wc) +ASTCMDLIST(wc) +/* End-of-generated-data. */ + +/* Mandatory for ksh93 test suite and AST scripts */ +BINCMDLIST(getconf) + +#ifdef __cplusplus +} +#endif + +#endif /* _SOLARIS_CMDLIST_H */ diff --git a/usr/src/lib/libshell/common/data/strdata.c b/usr/src/lib/libshell/common/data/strdata.c new file mode 100644 index 0000000000..57689b68a3 --- /dev/null +++ b/usr/src/lib/libshell/common/data/strdata.c @@ -0,0 +1,104 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * data for string evaluator library + */ + +#include <ast_standards.h> +#include "FEATURE/options" +#include "streval.h" + +const unsigned char strval_precedence[35] = + /* opcode precedence,assignment */ +{ + /* DEFAULT */ MAXPREC|NOASSIGN, + /* DONE */ 0|NOASSIGN|RASSOC, + /* NEQ */ 10|NOASSIGN, + /* NOT */ MAXPREC|NOASSIGN, + /* MOD */ 14, + /* ANDAND */ 6|NOASSIGN|SEQPOINT, + /* AND */ 9|NOFLOAT, + /* LPAREN */ MAXPREC|NOASSIGN|SEQPOINT, + /* RPAREN */ 1|NOASSIGN|RASSOC|SEQPOINT, + /* POW */ 14|NOASSIGN|RASSOC, + /* TIMES */ 14, + /* PLUSPLUS */ 15|NOASSIGN|NOFLOAT|SEQPOINT, + /* PLUS */ 13, + /* COMMA */ 1|NOASSIGN|SEQPOINT, + /* MINUSMINUS */ 15|NOASSIGN|NOFLOAT|SEQPOINT, + /* MINUS */ 13, + /* DIV */ 14, + /* LSHIFT */ 12|NOFLOAT, + /* LE */ 11|NOASSIGN, + /* LT */ 11|NOASSIGN, + /* EQ */ 10|NOASSIGN, + /* ASSIGNMENT */ 2|RASSOC, + /* COLON */ 0|NOASSIGN, + /* RSHIFT */ 12|NOFLOAT, + /* GE */ 11|NOASSIGN, + /* GT */ 11|NOASSIGN, + /* QCOLON */ 3|NOASSIGN|SEQPOINT, + /* QUEST */ 3|NOASSIGN|SEQPOINT|RASSOC, + /* XOR */ 8|NOFLOAT, + /* OROR */ 5|NOASSIGN|SEQPOINT, + /* OR */ 7|NOFLOAT, + /* DEFAULT */ MAXPREC|NOASSIGN, + /* DEFAULT */ MAXPREC|NOASSIGN, + /* DEFAULT */ MAXPREC|NOASSIGN, + /* DEFAULT */ MAXPREC|NOASSIGN +}; + +/* + * This is for arithmetic expressions + */ +const char strval_states[64] = +{ + A_EOF, A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, + A_REG, 0, 0, A_REG, A_REG, A_REG, A_REG, A_REG, + A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, + A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, A_REG, + + 0, A_NOT, 0, A_REG, A_REG, A_MOD, A_AND, A_LIT, + A_LPAR, A_RPAR, A_TIMES,A_PLUS, A_COMMA,A_MINUS,A_DOT, A_DIV, + A_DIG, A_DIG, A_DIG, A_DIG, A_DIG, A_DIG, A_DIG, A_DIG, + A_DIG, A_DIG, A_COLON,A_REG, A_LT, A_ASSIGN,A_GT, A_QUEST + +}; + + +const char e_argcount[] = "%s: function has wrong number of arguments"; +const char e_badnum[] = "%s: bad number"; +const char e_moretokens[] = "%s: more tokens expected"; +const char e_paren[] = "%s: unbalanced parenthesis"; +const char e_badcolon[] = "%s: invalid use of :"; +const char e_divzero[] = "%s: divide by zero"; +const char e_synbad[] = "%s: arithmetic syntax error"; +const char e_notlvalue[] = "%s: assignment requires lvalue"; +const char e_recursive[] = "%s: recursion too deep"; +const char e_questcolon[] = "%s: ':' expected for '?' operator"; +const char e_function[] = "%s: unknown function"; +const char e_incompatible[] = "%s: invalid floating point operation"; +const char e_overflow[] = "%s: overflow exception"; +const char e_domain[] = "%s: domain exception"; +const char e_singularity[] = "%s: singularity exception"; +const char e_charconst[] = "%s: invalid character constant"; + +#include "FEATURE/math" diff --git a/usr/src/lib/libshell/common/data/testops.c b/usr/src/lib/libshell/common/data/testops.c new file mode 100644 index 0000000000..449ca99efb --- /dev/null +++ b/usr/src/lib/libshell/common/data/testops.c @@ -0,0 +1,168 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +/* + * tables for the test builin [[...]] and [...] + */ + +#include <ast.h> + +#include "shtable.h" +#include "test.h" + +/* + * This is the list of binary test and [[...]] operators + */ + +const Shtable_t shtab_testops[] = +{ + "!=", TEST_SNE, + "-a", TEST_AND, + "-ef", TEST_EF, + "-eq", TEST_EQ, + "-ge", TEST_GE, + "-gt", TEST_GT, + "-le", TEST_LE, + "-lt", TEST_LT, + "-ne", TEST_NE, + "-nt", TEST_NT, + "-o", TEST_OR, + "-ot", TEST_OT, + "=", TEST_SEQ, + "==", TEST_SEQ, + "=~", TEST_REP, + "<", TEST_SLT, + ">", TEST_SGT, + "]]", TEST_END, + "", 0 +}; + +const char sh_opttest[] = +"[-1c?\n@(#)$Id: test (AT&T Research) 2003-03-18 $\n]" +USAGE_LICENSE +"[+NAME?test - evaluate expression]" +"[+DESCRIPTION?\btest\b evaluates expressions and indicates its " + "results based on the exit status. Option parsing is not " + "performed so that all arguments, including \b--\b are processed " + " as operands. The evaluation of the " + "expression depends on the number of operands as follows:]{" + "[+0?Evaluates to false.]" + "[+1?True if argument is not an empty string.]" + "[+2?If first operand is \b!\b, the result is True if the second " + "operand an empty string. Otherwise, it is evaluated " + "as one of the unary expressions defined below. If the " + "unary operator is invalid and the second argument is \b--\b," + "then the first argument is processed as an option argument.]" + "[+3?If first operand is \b!\b, the result is True if the second " + "and third operand evaluated as a unary expression is False. " + "Otherwise, the three operands are evaluaged as one of the " + "binary expressions listed below.]" + "[+4?If first operand is \b!\b, the result is True if the next " + "three operands are a valid binary expression that is False.]" +"}" +"[If any \afile\a is of the form \b/dev/fd/\b\an\a, then file descriptor " + "\an\a is checked.]" +"[+?Unary expressions can be one of the following:]{" + "[+-a \afile\a?True if \afile\a exists, obsolete.]" + "[+-b \afile\a?True if \afile\a exists and is a block special file.]" + "[+-c \afile\a?True if \afile\a exists and is a character special " + "file.]" + "[+-d \afile\a?True if \afile\a exists and is a directory.]" + "[+-e \afile\a?True if \afile\a exists.]" + "[+-f \afile\a?True if \afile\a exists and is a regular file.]" + "[+-g \afile\a?True if \afile\a exists and has its set-group-id bit " + "set.]" + "[+-h \afile\a?True if \afile\a exists and is a symbolic link.]" + "[+-k \afile\a?True if \afile\a exists and has its sticky bit on.]" +#if SHOPT_TEST_L + "[+-l \afile\a?True if \afile\a exists and is a symbolic link.]" +#endif + "[+-n \astring\a?True if length of \astring\a is non-zero.]" + "[+-o \aoption\a?True if the shell option \aoption\a is enabled.]" + "[+-p \afile\a?True if \afile\a exists and is a pipe or fifo.]" + "[+-r \afile\a?True if \afile\a exists and is readable.]" + "[+-s \afile\a?True if \afile\a exists and has size > 0.]" + "[+-t \afildes\a?True if file descriptor number \afildes\a is " + "open and is associated with a terminal device.]" + "[+-u \afile\a?True if \afile\a exists and has its set-user-id bit " + "set.]" + "[+-w \afile\a?True if \afile\a exists and is writable.]" + "[+-x \afile\a?True if \afile\a exists and is executable. For a " + "directory it means that it can be searched.]" + "[+-z \astring\a?True if \astring\a is a zero length string.]" + "[+-G \afile\a?True if \afile\a exists and group is the effective " + "group id of the current process.]" + "[+-L \afile\a?True if \afile\a exists and is a symbolic link.]" + "[+-N \afile\a?True if \afile\a exists and has been modified since " + "it was last read.]" + "[+-O \afile\a?True if \afile\a exists and owner is the effective " + "user id of the current process.]" + "[+-S \afile\a?True if \afile\a exists and is a socket.]" +"}" +"[+?Binary expressions can be one of the following:]{" + "[+\astring1\a = \astring2\a?True if \astring1\a is equal to " + "\astring2\a.]" + "[+\astring1\a == \astring2\a?True if \astring1\a is equal to " + "\astring2\a.]" + "[+\astring1\a != \astring2\a?True if \astring1\a is not equal to " + "\astring2\a.]" + "[+\anum1\a -eq \anum2\a?True if numerical value of \anum1\a is " + "equal to \anum2\a.]" + "[+\anum1\a -ne \anum2\a?True if numerical value of \anum1\a is not " + "equal to \anum2\a.]" + "[+\anum1\a -lt \anum2\a?True if numerical value of \anum1\a is less " + "than \anum2\a.]" + "[+\anum1\a -le \anum2\a?True if numerical value of \anum1\a is less " + "than or equal to \anum2\a.]" + "[+\anum1\a -gt \anum2\a?True if numerical value of \anum1\a is " + "greater than \anum2\a.]" + "[+\anum1\a -ge \anum2\a?True if numerical value of \anum1\a is " + "greater than or equal to \anum2\a.]" + "[+\afile1\a -nt \afile2\a?True if \afile1\a is newer than \afile2\a " + "or \afile2\a does not exist.]" + "[+\afile1\a -ot \afile2\a?True if \afile1\a is older than \afile2\a " + "or \afile2\a does not exist.]" + "[+\afile1\a -ef \afile2\a?True if \afile1\a is another name for " + "\afile2\a. This will be true if \afile1\a is a hard link " + "or a symbolic link to \afile2\a.]" +"}" +"\n" +"\n[expression]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Indicates that the specified expression is True.]" + "[+1?Indicates that the specified expression is False.]" + "[+>1?An error occurred.]" +"}" + +"[+SEE ALSO?\blet\b(1), \bexpr\b(1)]" +; + +const char test_opchars[] = "HLNSVOGCaeohrwxdcbfugk" +#if SHOPT_TEST_L + "l" +#endif + "psnzt"; +const char e_argument[] = "argument expected"; +const char e_missing[] = "%s missing"; +const char e_badop[] = "%s: unknown operator"; +const char e_tstbegin[] = "[[ ! "; +const char e_tstend[] = " ]]\n"; diff --git a/usr/src/lib/libshell/common/data/variables.c b/usr/src/lib/libshell/common/data/variables.c new file mode 100644 index 0000000000..9042e235b0 --- /dev/null +++ b/usr/src/lib/libshell/common/data/variables.c @@ -0,0 +1,105 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <ast.h> +#include "FEATURE/options" +#include "FEATURE/dynamic" +#include <shell.h> +#include "shtable.h" +#include "name.h" +#include "defs.h" + +/* + * This is the list of built-in shell variables and default values + * and default attributes. + */ + +const struct shtable2 shtab_variables[] = +{ + "PATH", 0, (char*)0, + "PS1", 0, (char*)0, + "PS2", NV_NOFREE, "> ", + "IFS", NV_NOFREE, " \t\n", + "PWD", 0, (char*)0, + "HOME", 0, (char*)0, + "MAIL", 0, (char*)0, + "REPLY", 0, (char*)0, + "SHELL", NV_NOFREE, "/bin/" SH_STD, + "EDITOR", 0, (char*)0, + "MAILCHECK", NV_NOFREE|NV_INTEGER, (char*)0, + "RANDOM", NV_NOFREE|NV_INTEGER, (char*)0, + "ENV", NV_NOFREE, "$HOME/.kshrc", + "HISTFILE", 0, (char*)0, + "HISTSIZE", 0, (char*)0, + "HISTEDIT", NV_NOFREE, (char*)0, + "HISTCMD", NV_NOFREE|NV_INTEGER, (char*)0, + "FCEDIT", NV_NOFREE, "/bin/ed", + "CDPATH", 0, (char*)0, + "MAILPATH", 0, (char*)0, + "PS3", NV_NOFREE, "#? ", + "OLDPWD", 0, (char*)0, + "VISUAL", 0, (char*)0, + "COLUMNS", 0, (char*)0, + "LINES", 0, (char*)0, + "PPID", NV_NOFREE|NV_INTEGER, (char*)0, + "_", NV_EXPORT, (char*)0, + "TMOUT", NV_NOFREE|NV_INTEGER, (char*)0, + "SECONDS", NV_NOFREE|NV_INTEGER|NV_DOUBLE, (char*)0, + "LINENO", NV_NOFREE|NV_INTEGER, (char*)0, + "OPTARG", 0, (char*)0, + "OPTIND", NV_NOFREE|NV_INTEGER, (char*)0, + "PS4", 0, (char*)0, + "FPATH", 0, (char*)0, + "LANG", 0, (char*)0, + "LC_ALL", 0, (char*)0, + "LC_COLLATE", 0, (char*)0, + "LC_CTYPE", 0, (char*)0, + "LC_MESSAGES", 0, (char*)0, + "LC_NUMERIC", 0, (char*)0, + "FIGNORE", 0, (char*)0, + ".sh", NV_TABLE|NV_RDONLY|NV_NOFREE|NV_NOPRINT,(char*)0, + ".sh.edchar", 0, (char*)0, + ".sh.edcol", 0, (char*)0, + ".sh.edtext", 0, (char*)0, + ".sh.edmode", 0, (char*)0, + ".sh.name", 0, (char*)0, + ".sh.subscript",0, (char*)0, + ".sh.value", 0, (char*)0, + ".sh.version", NV_NOFREE, (char*)(&e_version[10]), + ".sh.dollar", 0, (char*)0, + ".sh.match", 0, (char*)0, + ".sh.command", 0, (char*)0, + ".sh.file", 0, (char*)0, + ".sh.fun", 0, (char*)0, + ".sh.subshell", NV_INTEGER|NV_SHORT|NV_NOFREE, (char*)0, + ".sh.level", 0, (char*)0, +#if SHOPT_FS_3D + "VPATH", 0, (char*)0, +#endif /* SHOPT_FS_3D */ +#if SHOPT_MULTIBYTE + "CSWIDTH", 0, (char*)0, +#endif /* SHOPT_MULTIBYTE */ +#ifdef apollo + "SYSTYPE", 0, (char*)0, +#endif /* apollo */ + "", 0, (char*)0 +}; + diff --git a/usr/src/lib/libshell/common/edit/completion.c b/usr/src/lib/libshell/common/edit/completion.c new file mode 100644 index 0000000000..050019fbe5 --- /dev/null +++ b/usr/src/lib/libshell/common/edit/completion.c @@ -0,0 +1,522 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * completion.c - command and file completion for shell editors + * + */ + +#include "defs.h" +#include <ctype.h> +#include <ast_wchar.h> +#include "lexstates.h" +#include "path.h" +#include "io.h" +#include "edit.h" +#include "history.h" + +static int charcmp(int a, int b, int nocase) +{ + if(nocase) + { + if(isupper(a)) + a = tolower(a); + if(isupper(b)) + b = tolower(b); + } + return(a==b); +} + +/* + * overwrites <str> to common prefix of <str> and <newstr> + * if <str> is equal to <newstr> returns <str>+strlen(<str>)+1 + * otherwise returns <str>+strlen(<str>) + */ +static char *overlaid(register char *str,register const char *newstr,int nocase) +{ + register int c,d; + while((c= *(unsigned char *)str) && ((d= *(unsigned char*)newstr++),charcmp(c,d,nocase))) + str++; + if(*str) + *str = 0; + else if(*newstr==0) + str++; + return(str); +} + + +/* + * returns pointer to beginning of expansion and sets type of expansion + */ +static char *find_begin(char outbuff[], char *last, int endchar, int *type) +{ + register char *cp=outbuff, *bp, *xp; + register int c,inquote = 0; + bp = outbuff; + *type = 0; + while(cp < last) + { + xp = cp; + switch(c= mbchar(cp)) + { + case '\'': case '"': + if(!inquote) + { + inquote = c; + bp = xp; + break; + } + if(inquote==c) + inquote = 0; + break; + case '\\': + if(inquote != '\'') + mbchar(cp); + break; + case '$': + if(inquote == '\'') + break; + c = *(unsigned char*)cp; + if(isaletter(c) || c=='{') + { + int dot = '.'; + if(c=='{') + { + xp = cp; + mbchar(cp); + c = *(unsigned char*)cp; + if(c!='.' && !isaletter(c)) + break; + } + else + dot = 'a'; + while(cp < last) + { + if((c= mbchar(cp)) , c!=dot && !isaname(c)) + break; + } + if(cp>=last) + { + *type='$'; + return(++xp); + } + } + else if(c=='(') + { + xp = find_begin(cp,last,')',type); + if(*(cp=xp)!=')') + bp = xp; + else + cp++; + } + break; + case '=': + if(!inquote) + bp = cp; + break; + case '~': + if(*cp=='(') + break; + /* fall through */ + default: + if(c && c==endchar) + return(xp); + if(!inquote && ismeta(c)) + bp = cp; + break; + } + } + if(inquote && *bp==inquote) + *type = *bp++; + return(bp); +} + +/* + * file name generation for edit modes + * non-zero exit for error, <0 ring bell + * don't search back past beginning of the buffer + * mode is '*' for inline expansion, + * mode is '\' for filename completion + * mode is '=' cause files to be listed in select format + */ + +int ed_expand(Edit_t *ep, char outbuff[],int *cur,int *eol,int mode, int count) +{ + struct comnod *comptr; + struct argnod *ap; + register char *out; + char *av[2], *begin , *dir=0; + int addstar=0, rval=0, var=0, strip=1; + int nomarkdirs = !sh_isoption(SH_MARKDIRS); + sh_onstate(SH_FCOMPLETE); + if(ep->e_nlist) + { + if(mode=='=' && count>0) + { + if(count> ep->e_nlist) + return(-1); + mode = '*'; + av[0] = ep->e_clist[count-1]; + av[1] = 0; + } + else + { + stakset(ep->e_stkptr,ep->e_stkoff); + ep->e_nlist = 0; + } + } + comptr = (struct comnod*)stakalloc(sizeof(struct comnod)); + ap = (struct argnod*)stakseek(ARGVAL); +#if SHOPT_MULTIBYTE + { + register int c = *cur; + register genchar *cp; + /* adjust cur */ + cp = (genchar *)outbuff + *cur; + c = *cp; + *cp = 0; + *cur = ed_external((genchar*)outbuff,(char*)stakptr(0)); + *cp = c; + *eol = ed_external((genchar*)outbuff,outbuff); + } +#endif /* SHOPT_MULTIBYTE */ + out = outbuff + *cur + (sh_isoption(SH_VI)!=0); + comptr->comtyp = COMSCAN; + comptr->comarg = ap; + ap->argflag = (ARG_MAC|ARG_EXP); + ap->argnxt.ap = 0; + ap->argchn.cp = 0; + { + register int c; + char *last = out; + c = *(unsigned char*)out; + begin = out = find_begin(outbuff,last,0,&var); + /* addstar set to zero if * should not be added */ + if(var=='$') + { + stakputs("${!"); + stakwrite(out,last-out); + stakputs("@}"); + out = last; + } + else + { + addstar = '*'; + while(out < last) + { + c = *(unsigned char*)out; + if(isexp(c)) + addstar = 0; + if (c == '/') + { + if(addstar == 0) + strip = 0; + dir = out+1; + } + stakputc(c); + out++; + } + } + if(var!='$' && mode=='\\' && out[-1]!='*') + addstar = '*'; + if(*begin=='~' && !strchr(begin,'/')) + addstar = 0; + stakputc(addstar); + ap = (struct argnod*)stakfreeze(1); + } + if(mode!='*') + sh_onoption(SH_MARKDIRS); + { + register char **com; + char *cp=begin, *left=0, *saveout="."; + int nocase=0,narg,cmd_completion=0; + register int size='x'; + while(cp>outbuff && ((size=cp[-1])==' ' || size=='\t')) + cp--; + if(!var && !strchr(ap->argval,'/') && (((cp==outbuff&&sh.nextprompt==1) || (strchr(";&|(",size)) && (cp==outbuff+1||size=='('||cp[-2]!='>') && *begin!='~' ))) + { + cmd_completion=1; + sh_onstate(SH_COMPLETE); + } + if(ep->e_nlist) + { + narg = 1; + com = av; + if(dir) + begin += (dir-begin); + } + else + { + com = sh_argbuild(&narg,comptr,0); + /* special handling for leading quotes */ + if(begin>outbuff && (begin[-1]=='"' || begin[-1]=='\'')) + begin--; + } + sh_offstate(SH_COMPLETE); + /* allow a search to be aborted */ + if(sh.trapnote&SH_SIGSET) + { + rval = -1; + goto done; + } + /* match? */ + if (*com==0 || (narg <= 1 && (strcmp(ap->argval,*com)==0) || (addstar && com[0][strlen(*com)-1]=='*'))) + { + rval = -1; + goto done; + } + if(mode=='=') + { + if (strip && !cmd_completion) + { + register char **ptrcom; + for(ptrcom=com;*ptrcom;ptrcom++) + /* trim directory prefix */ + *ptrcom = path_basename(*ptrcom); + } + sfputc(sfstderr,'\n'); + sh_menu(sfstderr,narg,com); + sfsync(sfstderr); + ep->e_nlist = narg; + ep->e_clist = com; + goto done; + } + /* see if there is enough room */ + size = *eol - (out-begin); + if(mode=='\\') + { + int c; + if(dir) + { + c = *dir; + *dir = 0; + saveout = begin; + } + if(saveout=astconf("PATH_ATTRIBUTES",saveout,(char*)0)) + nocase = (strchr(saveout,'c')!=0); + if(dir) + *dir = c; + /* just expand until name is unique */ + size += strlen(*com); + } + else + { + size += narg; + { + char **savcom = com; + while (*com) + size += strlen(cp=sh_fmtq(*com++)); + com = savcom; + } + } + /* see if room for expansion */ + if(outbuff+size >= &outbuff[MAXLINE]) + { + com[0] = ap->argval; + com[1] = 0; + } + /* save remainder of the buffer */ + if(*out) + left=stakcopy(out); + if(cmd_completion && mode=='\\') + out = strcopy(begin,path_basename(cp= *com++)); + else if(mode=='*') + { + if(ep->e_nlist && dir && var) + { + if(*cp==var) + cp++; + else + *begin++ = var; + out = strcopy(begin,cp); + var = 0; + } + else + out = strcopy(begin,sh_fmtq(*com)); + com++; + } + else + out = strcopy(begin,*com++); + if(mode=='\\') + { + saveout= ++out; + while (*com && *begin) + { + if(cmd_completion) + out = overlaid(begin,path_basename(*com++),nocase); + else + out = overlaid(begin,*com++,nocase); + } + mode = (out==saveout); + if(out[-1]==0) + out--; + if(mode && out[-1]!='/') + { + if(cmd_completion) + { + Namval_t *np; + /* add as tracked alias */ +#ifdef PATH_BFPATH + Pathcomp_t *pp; + if(*cp=='/' && (pp=path_dirfind(sh.pathlist,cp,'/')) && (np=nv_search(begin,sh.track_tree,NV_ADD))) + path_alias(np,pp); +#else + if(*cp=='/' && (np=nv_search(begin,sh.track_tree,NV_ADD))) + path_alias(np,cp); +#endif + out = strcopy(begin,cp); + } + /* add quotes if necessary */ + if((cp=sh_fmtq(begin))!=begin) + out = strcopy(begin,cp); + if(var=='$' && begin[-1]=='{') + *out = '}'; + else + *out = ' '; + *++out = 0; + } + else if(out[-1]=='/' && (cp=sh_fmtq(begin))!=begin) + { + out = strcopy(begin,cp); + if(out[-1] =='"' || out[-1]=='\'') + *--out = 0;; + } + if(*begin==0) + ed_ringbell(); + } + else + { + while (*com) + { + *out++ = ' '; + out = strcopy(out,sh_fmtq(*com++)); + } + } + if(ep->e_nlist) + { + cp = com[-1]; + if(cp[strlen(cp)-1]!='/') + { + if(var=='$' && begin[-1]=='{') + *out = '}'; + else + *out = ' '; + out++; + } + else if(out[-1] =='"' || out[-1]=='\'') + out--; + *out = 0; + } + *cur = (out-outbuff); + /* restore rest of buffer */ + if(left) + out = strcopy(out,left); + *eol = (out-outbuff); + } + done: + sh_offstate(SH_FCOMPLETE); + if(!ep->e_nlist) + stakset(ep->e_stkptr,ep->e_stkoff); + if(nomarkdirs) + sh_offoption(SH_MARKDIRS); +#if SHOPT_MULTIBYTE + { + register int c,n=0; + /* first re-adjust cur */ + c = outbuff[*cur]; + outbuff[*cur] = 0; + for(out=outbuff; *out;n++) + mbchar(out); + outbuff[*cur] = c; + *cur = n; + outbuff[*eol+1] = 0; + *eol = ed_internal(outbuff,(genchar*)outbuff); + } +#endif /* SHOPT_MULTIBYTE */ + return(rval); +} + +/* + * look for edit macro named _i + * if found, puts the macro definition into lookahead buffer and returns 1 + */ +int ed_macro(Edit_t *ep, register int i) +{ + register char *out; + Namval_t *np; + genchar buff[LOOKAHEAD+1]; + if(i != '@') + ep->e_macro[1] = i; + /* undocumented feature, macros of the form <ESC>[c evoke alias __c */ + if(i=='_') + ep->e_macro[2] = ed_getchar(ep,1); + else + ep->e_macro[2] = 0; + if (isalnum(i)&&(np=nv_search(ep->e_macro,sh.alias_tree,HASH_SCOPE))&&(out=nv_getval(np))) + { +#if SHOPT_MULTIBYTE + /* copy to buff in internal representation */ + int c = 0; + if( strlen(out) > LOOKAHEAD ) + { + c = out[LOOKAHEAD]; + out[LOOKAHEAD] = 0; + } + i = ed_internal(out,buff); + if(c) + out[LOOKAHEAD] = c; +#else + strncpy((char*)buff,out,LOOKAHEAD); + buff[LOOKAHEAD] = 0; + i = strlen((char*)buff); +#endif /* SHOPT_MULTIBYTE */ + while(i-- > 0) + ed_ungetchar(ep,buff[i]); + return(1); + } + return(0); +} + +/* + * Enter the fc command on the current history line + */ +int ed_fulledit(Edit_t *ep) +{ + register char *cp; + if(!sh.hist_ptr) + return(-1); + /* use EDITOR on current command */ + if(ep->e_hline == ep->e_hismax) + { + if(ep->e_eol<0) + return(-1); +#if SHOPT_MULTIBYTE + ep->e_inbuf[ep->e_eol+1] = 0; + ed_external(ep->e_inbuf, (char *)ep->e_inbuf); +#endif /* SHOPT_MULTIBYTE */ + sfwrite(sh.hist_ptr->histfp,(char*)ep->e_inbuf,ep->e_eol+1); + sh_onstate(SH_HISTORY); + hist_flush(sh.hist_ptr); + } + cp = strcopy((char*)ep->e_inbuf,e_runvi); + cp = strcopy(cp, fmtbase((long)ep->e_hline,10,0)); + ep->e_eol = ((unsigned char*)cp - (unsigned char*)ep->e_inbuf)-(sh_isoption(SH_VI)!=0); + return(0); +} diff --git a/usr/src/lib/libshell/common/edit/edit.c b/usr/src/lib/libshell/common/edit/edit.c new file mode 100644 index 0000000000..a832a4a965 --- /dev/null +++ b/usr/src/lib/libshell/common/edit/edit.c @@ -0,0 +1,1489 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * edit.c - common routines for vi and emacs one line editors in shell + * + * David Korn P.D. Sullivan + * AT&T Labs + * + * Coded April 1983. + */ + +#include <ast.h> +#include <errno.h> +#include <ccode.h> +#include <ctype.h> +#include "FEATURE/options" +#include "FEATURE/time" +#include "FEATURE/cmds" +#ifdef _hdr_utime +# include <utime.h> +# include <ls.h> +#endif + +#if KSHELL +# include "defs.h" +# include "variables.h" +#else + extern char ed_errbuf[]; + char e_version[] = "\n@(#)$Id: Editlib version 1993-12-28 r $\0\n"; +#endif /* KSHELL */ +#include "io.h" +#include "terminal.h" +#include "history.h" +#include "edit.h" + +static char CURSOR_UP[20] = { ESC, '[', 'A', 0 }; + +#if SHOPT_MULTIBYTE +# define is_cntrl(c) ((c<=STRIP) && iscntrl(c)) +# define is_print(c) ((c&~STRIP) || isprint(c)) +#else +# define is_cntrl(c) iscntrl(c) +# define is_print(c) isprint(c) +#endif + +#if (CC_NATIVE == CC_ASCII) +# define printchar(c) ((c) ^ ('A'-cntl('A'))) +#else + static int printchar(int c) + { + switch(c) + { + + case cntl('A'): return('A'); + case cntl('B'): return('B'); + case cntl('C'): return('C'); + case cntl('D'): return('D'); + case cntl('E'): return('E'); + case cntl('F'): return('F'); + case cntl('G'): return('G'); + case cntl('H'): return('H'); + case cntl('I'): return('I'); + case cntl('J'): return('J'); + case cntl('K'): return('K'); + case cntl('L'): return('L'); + case cntl('M'): return('M'); + case cntl('N'): return('N'); + case cntl('O'): return('O'); + case cntl('P'): return('P'); + case cntl('Q'): return('Q'); + case cntl('R'): return('R'); + case cntl('S'): return('S'); + case cntl('T'): return('T'); + case cntl('U'): return('U'); + case cntl('V'): return('V'); + case cntl('W'): return('W'); + case cntl('X'): return('X'); + case cntl('Y'): return('Y'); + case cntl('Z'): return('Z'); + case cntl(']'): return(']'); + case cntl('['): return('['); + } + return('?'); + } +#endif +#define MINWINDOW 15 /* minimum width window */ +#define DFLTWINDOW 80 /* default window width */ +#define RAWMODE 1 +#define ALTMODE 2 +#define ECHOMODE 3 +#define SYSERR -1 + +#if SHOPT_OLDTERMIO +# undef tcgetattr +# undef tcsetattr +#endif /* SHOPT_OLDTERMIO */ + +#ifdef RT +# define VENIX 1 +#endif /* RT */ + + +#ifdef _hdr_sgtty +# ifdef TIOCGETP + static int l_mask; + static struct tchars l_ttychars; + static struct ltchars l_chars; + static char l_changed; /* set if mode bits changed */ +# define L_CHARS 4 +# define T_CHARS 2 +# define L_MASK 1 +# endif /* TIOCGETP */ +#endif /* _hdr_sgtty */ + +#if KSHELL + static int keytrap(Edit_t *,char*, int, int, int); +#else + Edit_t editb; +#endif /* KSHELL */ + + +#ifndef _POSIX_DISABLE +# define _POSIX_DISABLE 0 +#endif + +#ifdef future + static int compare(const char*, const char*, int); +#endif /* future */ +#if SHOPT_VSH || SHOPT_ESH +# define ttyparm (ep->e_ttyparm) +# define nttyparm (ep->e_nttyparm) + static const char bellchr[] = "\a"; /* bell char */ +#endif /* SHOPT_VSH || SHOPT_ESH */ + + +/* + * This routine returns true if fd refers to a terminal + * This should be equivalent to isatty + */ +int tty_check(int fd) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + struct termios tty; + ep->e_savefd = -1; + return(tty_get(fd,&tty)==0); +} + +/* + * Get the current terminal attributes + * This routine remembers the attributes and just returns them if it + * is called again without an intervening tty_set() + */ + +int tty_get(register int fd, register struct termios *tty) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + if(fd == ep->e_savefd) + *tty = ep->e_savetty; + else + { + while(tcgetattr(fd,tty) == SYSERR) + { + if(errno !=EINTR) + return(SYSERR); + errno = 0; + } + /* save terminal settings if in cannonical state */ + if(ep->e_raw==0) + { + ep->e_savetty = *tty; + ep->e_savefd = fd; + } + } + return(0); +} + +/* + * Set the terminal attributes + * If fd<0, then current attributes are invalidated + */ + +int tty_set(int fd, int action, struct termios *tty) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + if(fd >=0) + { +#ifdef future + if(ep->e_savefd>=0 && compare(&ep->e_savetty,tty,sizeof(struct termios))) + return(0); +#endif + while(tcsetattr(fd, action, tty) == SYSERR) + { + if(errno !=EINTR) + return(SYSERR); + errno = 0; + } + ep->e_savetty = *tty; + } + ep->e_savefd = fd; + return(0); +} + +#if SHOPT_ESH || SHOPT_VSH +/*{ TTY_COOKED( fd ) + * + * This routine will set the tty in cooked mode. + * It is also called by error.done(). + * +}*/ + +void tty_cooked(register int fd) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + if(ep->e_raw==0) + return; + if(fd < 0) + fd = ep->e_savefd; +#ifdef L_MASK + /* restore flags */ + if(l_changed&L_MASK) + ioctl(fd,TIOCLSET,&l_mask); + if(l_changed&T_CHARS) + /* restore alternate break character */ + ioctl(fd,TIOCSETC,&l_ttychars); + if(l_changed&L_CHARS) + /* restore alternate break character */ + ioctl(fd,TIOCSLTC,&l_chars); + l_changed = 0; +#endif /* L_MASK */ + /*** don't do tty_set unless ttyparm has valid data ***/ + if(tty_set(fd, TCSANOW, &ttyparm) == SYSERR) + return; + ep->e_raw = 0; + return; +} + +/*{ TTY_RAW( fd ) + * + * This routine will set the tty in raw mode. + * +}*/ + +int tty_raw(register int fd, int echomode) +{ + int echo = echomode; +#ifdef L_MASK + struct ltchars lchars; +#endif /* L_MASK */ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + if(ep->e_raw==RAWMODE) + return(echo?-1:0); + else if(ep->e_raw==ECHOMODE) + return(echo?0:-1); +#if !SHOPT_RAWONLY + if(ep->e_raw != ALTMODE) +#endif /* SHOPT_RAWONLY */ + { + if(tty_get(fd,&ttyparm) == SYSERR) + return(-1); + } +#if L_MASK || VENIX + if(ttyparm.sg_flags&LCASE) + return(-1); + if(!(ttyparm.sg_flags&ECHO)) + { + if(!echomode) + return(-1); + echo = 0; + } + nttyparm = ttyparm; + if(!echo) + nttyparm.sg_flags &= ~(ECHO | TBDELAY); +# ifdef CBREAK + nttyparm.sg_flags |= CBREAK; +# else + nttyparm.sg_flags |= RAW; +# endif /* CBREAK */ + ep->e_erase = ttyparm.sg_erase; + ep->e_kill = ttyparm.sg_kill; + ep->e_eof = cntl('D'); + ep->e_werase = cntl('W'); + ep->e_lnext = cntl('V'); + if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR ) + return(-1); + ep->e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW); +# ifdef TIOCGLTC + /* try to remove effect of ^V and ^Y and ^O */ + if(ioctl(fd,TIOCGLTC,&l_chars) != SYSERR) + { + lchars = l_chars; + lchars.t_lnextc = -1; + lchars.t_flushc = -1; + lchars.t_dsuspc = -1; /* no delayed stop process signal */ + if(ioctl(fd,TIOCSLTC,&lchars) != SYSERR) + l_changed |= L_CHARS; + } +# endif /* TIOCGLTC */ +#else + if (!(ttyparm.c_lflag & ECHO )) + { + if(!echomode) + return(-1); + echo = 0; + } +# ifdef FLUSHO + ttyparm.c_lflag &= ~FLUSHO; +# endif /* FLUSHO */ + nttyparm = ttyparm; +# ifndef u370 + nttyparm.c_iflag &= ~(IGNPAR|PARMRK|INLCR|IGNCR|ICRNL); + nttyparm.c_iflag |= BRKINT; +# else + nttyparm.c_iflag &= + ~(IGNBRK|PARMRK|INLCR|IGNCR|ICRNL|INPCK); + nttyparm.c_iflag |= (BRKINT|IGNPAR); +# endif /* u370 */ + if(echo) + nttyparm.c_lflag &= ~ICANON; + else + nttyparm.c_lflag &= ~(ICANON|ECHO|ECHOK); + nttyparm.c_cc[VTIME] = 0; + nttyparm.c_cc[VMIN] = 1; +# ifdef VREPRINT + nttyparm.c_cc[VREPRINT] = _POSIX_DISABLE; +# endif /* VREPRINT */ +# ifdef VDISCARD + nttyparm.c_cc[VDISCARD] = _POSIX_DISABLE; +# endif /* VDISCARD */ +# ifdef VDSUSP + nttyparm.c_cc[VDSUSP] = _POSIX_DISABLE; +# endif /* VDSUSP */ +# ifdef VWERASE + if(ttyparm.c_cc[VWERASE] == _POSIX_DISABLE) + ep->e_werase = cntl('W'); + else + ep->e_werase = nttyparm.c_cc[VWERASE]; + nttyparm.c_cc[VWERASE] = _POSIX_DISABLE; +# else + ep->e_werase = cntl('W'); +# endif /* VWERASE */ +# ifdef VLNEXT + if(ttyparm.c_cc[VLNEXT] == _POSIX_DISABLE ) + ep->e_lnext = cntl('V'); + else + ep->e_lnext = nttyparm.c_cc[VLNEXT]; + nttyparm.c_cc[VLNEXT] = _POSIX_DISABLE; +# else + ep->e_lnext = cntl('V'); +# endif /* VLNEXT */ + ep->e_eof = ttyparm.c_cc[VEOF]; + ep->e_erase = ttyparm.c_cc[VERASE]; + ep->e_kill = ttyparm.c_cc[VKILL]; + if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR ) + return(-1); + ep->e_ttyspeed = (cfgetospeed(&ttyparm)>=B1200?FAST:SLOW); +#endif + ep->e_raw = (echomode?ECHOMODE:RAWMODE); + return(0); +} + +#if !SHOPT_RAWONLY + +/* + * + * Get tty parameters and make ESC and '\r' wakeup characters. + * + */ + +# ifdef TIOCGETC +int tty_alt(register int fd) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + int mask; + struct tchars ttychars; + switch(ep->e_raw) + { + case ECHOMODE: + return(-1); + case ALTMODE: + return(0); + case RAWMODE: + tty_cooked(fd); + } + l_changed = 0; + if( ep->e_ttyspeed == 0) + { + if((tty_get(fd,&ttyparm) != SYSERR)) + ep->e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW); + ep->e_raw = ALTMODE; + } + if(ioctl(fd,TIOCGETC,&l_ttychars) == SYSERR) + return(-1); + if(ioctl(fd,TIOCLGET,&l_mask)==SYSERR) + return(-1); + ttychars = l_ttychars; + mask = LCRTBS|LCRTERA|LCTLECH|LPENDIN|LCRTKIL; + if((l_mask|mask) != l_mask) + l_changed = L_MASK; + if(ioctl(fd,TIOCLBIS,&mask)==SYSERR) + return(-1); + if(ttychars.t_brkc!=ESC) + { + ttychars.t_brkc = ESC; + l_changed |= T_CHARS; + if(ioctl(fd,TIOCSETC,&ttychars) == SYSERR) + return(-1); + } + return(0); +} +# else +# ifndef PENDIN +# define PENDIN 0 +# endif /* PENDIN */ +# ifndef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN */ + +int tty_alt(register int fd) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + switch(ep->e_raw) + { + case ECHOMODE: + return(-1); + case ALTMODE: + return(0); + case RAWMODE: + tty_cooked(fd); + } + if((tty_get(fd, &ttyparm)==SYSERR) || (!(ttyparm.c_lflag&ECHO))) + return(-1); +# ifdef FLUSHO + ttyparm.c_lflag &= ~FLUSHO; +# endif /* FLUSHO */ + nttyparm = ttyparm; + ep->e_eof = ttyparm.c_cc[VEOF]; +# ifdef ECHOCTL + /* escape character echos as ^[ */ + nttyparm.c_lflag |= (ECHOE|ECHOK|ECHOCTL|PENDIN|IEXTEN); + nttyparm.c_cc[VEOL] = ESC; +# else + /* switch VEOL2 and EOF, since EOF isn't echo'd by driver */ + nttyparm.c_lflag |= (ECHOE|ECHOK); + nttyparm.c_cc[VEOF] = ESC; /* make ESC the eof char */ +# ifdef VEOL2 + nttyparm.c_iflag &= ~(IGNCR|ICRNL); + nttyparm.c_iflag |= INLCR; + nttyparm.c_cc[VEOL] = '\r'; /* make CR an eol char */ + nttyparm.c_cc[VEOL2] = ep->e_eof; /* make EOF an eol char */ +# else + nttyparm.c_cc[VEOL] = ep->e_eof; /* make EOF an eol char */ +# endif /* VEOL2 */ +# endif /* ECHOCTL */ +# ifdef VREPRINT + nttyparm.c_cc[VREPRINT] = _POSIX_DISABLE; +# endif /* VREPRINT */ +# ifdef VDISCARD + nttyparm.c_cc[VDISCARD] = _POSIX_DISABLE; +# endif /* VDISCARD */ +# ifdef VWERASE + if(ttyparm.c_cc[VWERASE] == _POSIX_DISABLE) + nttyparm.c_cc[VWERASE] = cntl('W'); + ep->e_werase = nttyparm.c_cc[VWERASE]; +# else + ep->e_werase = cntl('W'); +# endif /* VWERASE */ +# ifdef VLNEXT + if(ttyparm.c_cc[VLNEXT] == _POSIX_DISABLE ) + nttyparm.c_cc[VLNEXT] = cntl('V'); + ep->e_lnext = nttyparm.c_cc[VLNEXT]; +# else + ep->e_lnext = cntl('V'); +# endif /* VLNEXT */ + ep->e_erase = ttyparm.c_cc[VERASE]; + ep->e_kill = ttyparm.c_cc[VKILL]; + if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR ) + return(-1); + ep->e_ttyspeed = (cfgetospeed(&ttyparm)>=B1200?FAST:SLOW); + ep->e_raw = ALTMODE; + return(0); +} + +# endif /* TIOCGETC */ +#endif /* SHOPT_RAWONLY */ + +/* + * ED_WINDOW() + * + * return the window size + */ +int ed_window(void) +{ + int rows,cols; + register char *cp = nv_getval(COLUMNS); + if(cp) + cols = (int)strtol(cp, (char**)0, 10)-1; + else + { + astwinsize(2,&rows,&cols); + if(--cols <0) + cols = DFLTWINDOW-1; + } + if(cols < MINWINDOW) + cols = MINWINDOW; + else if(cols > MAXWINDOW) + cols = MAXWINDOW; + return(cols); +} + +/* E_FLUSH() + * + * Flush the output buffer. + * + */ + +void ed_flush(Edit_t *ep) +{ + register int n = ep->e_outptr-ep->e_outbase; + register int fd = ERRIO; + if(n<=0) + return; + write(fd,ep->e_outbase,(unsigned)n); + ep->e_outptr = ep->e_outbase; +} + +/* + * send the bell character ^G to the terminal + */ + +void ed_ringbell(void) +{ + write(ERRIO,bellchr,1); +} + +/* + * send a carriage return line feed to the terminal + */ + +void ed_crlf(register Edit_t *ep) +{ +#ifdef cray + ed_putchar(ep,'\r'); +#endif /* cray */ +#ifdef u370 + ed_putchar(ep,'\r'); +#endif /* u370 */ +#ifdef VENIX + ed_putchar(ep,'\r'); +#endif /* VENIX */ + ed_putchar(ep,'\n'); + ed_flush(ep); +} + +/* ED_SETUP( max_prompt_size ) + * + * This routine sets up the prompt string + * The following is an unadvertised feature. + * Escape sequences in the prompt can be excluded from the calculated + * prompt length. This is accomplished as follows: + * - if the prompt string starts with "%\r, or contains \r%\r", where % + * represents any char, then % is taken to be the quote character. + * - strings enclosed by this quote character, and the quote character, + * are not counted as part of the prompt length. + */ + +void ed_setup(register Edit_t *ep, int fd, int reedit) +{ + register char *pp; + register char *last; + char *ppmax; + int myquote = 0, n; + register int qlen = 1; + char inquote = 0; + ep->e_fd = fd; + ep->e_multiline = sh_isoption(SH_MULTILINE)!=0; +#ifdef SIGWINCH + if(!(sh.sigflag[SIGWINCH]&SH_SIGFAULT)) + { + signal(SIGWINCH,sh_fault); + sh.sigflag[SIGWINCH] |= SH_SIGFAULT; + } + sh_fault(SIGWINCH); +#endif +#if KSHELL + ep->e_stkptr = stakptr(0); + ep->e_stkoff = staktell(); + if(!(last = sh.prompt)) + last = ""; + sh.prompt = 0; +#else + last = ep->e_prbuff; +#endif /* KSHELL */ + if(sh.hist_ptr) + { + register History_t *hp = sh.hist_ptr; + ep->e_hismax = hist_max(hp); + ep->e_hismin = hist_min(hp); + } + else + { + ep->e_hismax = ep->e_hismin = ep->e_hloff = 0; + } + ep->e_hline = ep->e_hismax; + if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS)) + ep->e_wsize = MAXLINE; + else + ep->e_wsize = ed_window()-2; + ep->e_winsz = ep->e_wsize+2; + ep->e_crlf = 1; + ep->e_plen = 0; + pp = ep->e_prompt; + ppmax = pp+PRSIZE-1; + *pp++ = '\r'; + { + register int c; + while(c= *last++) switch(c) + { + case ESC: + { + int skip=0; + ep->e_crlf = 0; + *pp++ = c; + for(n=1; c = *last++; n++) + { + if(pp < ppmax) + *pp++ = c; + if(c=='\a') + break; + if(skip || (c>='0' && c<='9')) + continue; + if(n>1 && c==';') + skip = 1; + else if(n>2 || (c!= '[' && c!= ']')) + break; + } + qlen += (n+1); + break; + } + case '\b': + if(pp>ep->e_prompt+1) + pp--; + break; + case '\r': + if(pp == (ep->e_prompt+2)) /* quote char */ + myquote = *(pp-1); + /*FALLTHROUGH*/ + + case '\n': + /* start again */ + ep->e_crlf = 1; + qlen = 1; + inquote = 0; + pp = ep->e_prompt+1; + break; + + case '\t': + /* expand tabs */ + while((pp-ep->e_prompt)%TABSIZE) + { + if(pp >= ppmax) + break; + *pp++ = ' '; + } + break; + + case '\a': + /* cut out bells */ + break; + + default: + if(c==myquote) + { + qlen += inquote; + inquote ^= 1; + } + if(pp < ppmax) + { + qlen += inquote; + *pp++ = c; + if(!inquote && !is_print(c)) + ep->e_crlf = 0; + } + } + } + if(pp-ep->e_prompt > qlen) + ep->e_plen = pp - ep->e_prompt - qlen; + *pp = 0; + if((ep->e_wsize -= ep->e_plen) < 7) + { + register int shift = 7-ep->e_wsize; + ep->e_wsize = 7; + pp = ep->e_prompt+1; + strcpy(pp,pp+shift); + ep->e_plen -= shift; + last[-ep->e_plen-2] = '\r'; + } + sfsync(sfstderr); + if(fd == sffileno(sfstderr)) + { + /* can't use output buffer when reading from stderr */ + static char *buff; + if(!buff) + buff = (char*)malloc(MAXLINE); + ep->e_outbase = ep->e_outptr = buff; + ep->e_outlast = ep->e_outptr + MAXLINE; + return; + } + qlen = sfset(sfstderr,SF_READ,0); + /* make sure SF_READ not on */ + ep->e_outbase = ep->e_outptr = (char*)sfreserve(sfstderr,SF_UNBOUND,SF_LOCKR); + ep->e_outlast = ep->e_outptr + sfvalue(sfstderr); + if(qlen) + sfset(sfstderr,SF_READ,1); + sfwrite(sfstderr,ep->e_outptr,0); + ep->e_eol = reedit; + if(ep->e_multiline) + { +#ifdef _cmd_tput + char *term; + if(!ep->e_term) + ep->e_term = nv_search("TERM",sh.var_tree,0); + if(ep->e_term && (term=nv_getval(ep->e_term)) && strlen(term)<sizeof(ep->e_termname) && strcmp(term,ep->e_termname)) + { + sh_trap(".sh.subscript=$(tput cuu1 2>/dev/null)",0); + if(pp=nv_getval(SH_SUBSCRNOD)) + strncpy(CURSOR_UP,pp,sizeof(CURSOR_UP)-1); + nv_unset(SH_SUBSCRNOD); + strcpy(ep->e_termname,term); + } +#endif + ep->e_wsize = MAXLINE - (ep->e_plen-2); + } + if(ep->e_default && (pp = nv_getval(ep->e_default))) + { + n = strlen(pp); + if(n > LOOKAHEAD) + n = LOOKAHEAD; + ep->e_lookahead = n; + while(n-- > 0) + ep->e_lbuf[n] = *pp++; + ep->e_default = 0; + } +} + +/* + * Do read, restart on interrupt unless SH_SIGSET or SH_SIGTRAP is set + * Use sfpkrd() to poll() or select() to wait for input if possible + * Unfortunately, systems that get interrupted from slow reads update + * this access time for for the terminal (in violation of POSIX). + * The fixtime() macro, resets the time to the time at entry in + * this case. This is not necessary for systems that can handle + * sfpkrd() correctly (i,e., those that support poll() or select() + */ +int ed_read(void *context, int fd, char *buff, int size, int reedit) +{ + register Edit_t *ep = (Edit_t*)context; + register int rv= -1; + register int delim = (ep->e_raw==RAWMODE?'\r':'\n'); + int mode = -1; + int (*waitevent)(int,long,int) = sh.waitevent; + if(ep->e_raw==ALTMODE) + mode = 1; + if(size < 0) + { + mode = 1; + size = -size; + } + sh_onstate(SH_TTYWAIT); + errno = EINTR; + sh.waitevent = 0; + while(rv<0 && errno==EINTR) + { + if(sh.trapnote&(SH_SIGSET|SH_SIGTRAP)) + goto done; + /* an interrupt that should be ignored */ + errno = 0; + if(!waitevent || (rv=(*waitevent)(fd,-1L,0))>=0) + rv = sfpkrd(fd,buff,size,delim,-1L,mode); + } + if(rv < 0) + { +#ifdef _hdr_utime +# define fixtime() if(isdevtty)utime(ep->e_tty,&utimes) + int isdevtty=0; + struct stat statb; + struct utimbuf utimes; + if(errno==0 && !ep->e_tty) + { + if((ep->e_tty=ttyname(fd)) && stat(ep->e_tty,&statb)>=0) + { + ep->e_tty_ino = statb.st_ino; + ep->e_tty_dev = statb.st_dev; + } + } + if(ep->e_tty_ino && fstat(fd,&statb)>=0 && statb.st_ino==ep->e_tty_ino && statb.st_dev==ep->e_tty_dev) + { + utimes.actime = statb.st_atime; + utimes.modtime = statb.st_mtime; + isdevtty=1; + } +#else +# define fixtime() +#endif /* _hdr_utime */ + while(1) + { + rv = read(fd,buff,size); + if(rv>=0 || errno!=EINTR) + break; + if(sh.trapnote&(SH_SIGSET|SH_SIGTRAP)) + goto done; + /* an interrupt that should be ignored */ + fixtime(); + } + } + else if(rv>=0 && mode>0) + rv = read(fd,buff,rv>0?rv:1); +done: + sh.waitevent = waitevent; + sh_offstate(SH_TTYWAIT); + return(rv); +} + + +/* + * put <string> of length <nbyte> onto lookahead stack + * if <type> is non-zero, the negation of the character is put + * onto the stack so that it can be checked for KEYTRAP + * putstack() returns 1 except when in the middle of a multi-byte char + */ +static int putstack(Edit_t *ep,char string[], register int nbyte, int type) +{ + register int c; +#if SHOPT_MULTIBYTE + char *endp, *p=string; + int size, offset = ep->e_lookahead + nbyte; + *(endp = &p[nbyte]) = 0; + endp = &p[nbyte]; + do + { + c = (int)((*p) & STRIP); + if(c< 0x80 && c!='<') + { + if (type) + c = -c; +# ifndef CBREAK + if(c == '\0') + { + /*** user break key ***/ + ep->e_lookahead = 0; +# if KSHELL + sh_fault(SIGINT); + siglongjmp(ep->e_env, UINTR); +# endif /* KSHELL */ + } +# endif /* CBREAK */ + + } + else + { + again: + if((c=mbchar(p)) >=0) + { + p--; /* incremented below */ + if(type) + c = -c; + } +#ifdef EILSEQ + else if(errno == EILSEQ) + errno = 0; +#endif + else if((endp-p) < mbmax()) + { + if ((c=ed_read(ep,ep->e_fd,endp, 1,0)) == 1) + { + *++endp = 0; + goto again; + } + return(c); + } + else + { + ed_ringbell(); + c = -(int)((*p) & STRIP); + offset += mbmax()-1; + } + } + ep->e_lbuf[--offset] = c; + p++; + } + while (p < endp); + /* shift lookahead buffer if necessary */ + if(offset -= ep->e_lookahead) + { + for(size=offset;size < nbyte;size++) + ep->e_lbuf[ep->e_lookahead+size-offset] = ep->e_lbuf[ep->e_lookahead+size]; + } + ep->e_lookahead += nbyte-offset; +#else + while (nbyte > 0) + { + c = string[--nbyte] & STRIP; + ep->e_lbuf[ep->e_lookahead++] = (type?-c:c); +# ifndef CBREAK + if( c == '\0' ) + { + /*** user break key ***/ + ep->e_lookahead = 0; +# if KSHELL + sh_fault(SIGINT); + siglongjmp(ep->e_env, UINTR); +# endif /* KSHELL */ + } +# endif /* CBREAK */ + } +#endif /* SHOPT_MULTIBYTE */ + return(1); +} + +/* + * routine to perform read from terminal for vi and emacs mode + * <mode> can be one of the following: + * -2 vi insert mode - key binding is in effect + * -1 vi control mode - key binding is in effect + * 0 normal command mode - key binding is in effect + * 1 edit keys not mapped + * 2 Next key is literal + */ +int ed_getchar(register Edit_t *ep,int mode) +{ + register int n, c; + char readin[LOOKAHEAD+1]; + if(!ep->e_lookahead) + { + ed_flush(ep); + ep->e_inmacro = 0; + /* The while is necessary for reads of partial multbyte chars */ + if((n=ed_read(ep,ep->e_fd,readin,-LOOKAHEAD,0)) > 0) + n = putstack(ep,readin,n,1); + } + if(ep->e_lookahead) + { + /* check for possible key mapping */ + if((c = ep->e_lbuf[--ep->e_lookahead]) < 0) + { + if(mode<=0 && sh.st.trap[SH_KEYTRAP]) + { + n=1; + if((readin[0]= -c) == ESC) + { + while(1) + { + if(!ep->e_lookahead) + { + if((c=sfpkrd(ep->e_fd,readin+n,1,'\r',(mode?400L:-1L),0))>0) + putstack(ep,readin+n,c,1); + } + if(!ep->e_lookahead) + break; + if((c=ep->e_lbuf[--ep->e_lookahead])>=0) + { + ep->e_lookahead++; + break; + } + c = -c; + readin[n++] = c; + if(c>='0' && c<='9' && n>2) + continue; + if(n>2 || (c!= '[' && c!= 'O')) + break; + } + } + if(n=keytrap(ep,readin,n,LOOKAHEAD-n,mode)) + { + putstack(ep,readin,n,0); + c = ep->e_lbuf[--ep->e_lookahead]; + } + else + c = ed_getchar(ep,mode); + } + else + c = -c; + } + /*** map '\r' to '\n' ***/ + if(c == '\r' && mode!=2) + c = '\n'; + if(ep->e_tabcount && !(c=='\t'||c==ESC || c=='\\' || c=='=' || c==cntl('L') || isdigit(c))) + ep->e_tabcount = 0; + } + else + siglongjmp(ep->e_env,(n==0?UEOF:UINTR)); + return(c); +} + +void ed_ungetchar(Edit_t *ep,register int c) +{ + if (ep->e_lookahead < LOOKAHEAD) + ep->e_lbuf[ep->e_lookahead++] = c; + return; +} + +/* + * put a character into the output buffer + */ + +void ed_putchar(register Edit_t *ep,register int c) +{ + char buf[8]; + register char *dp = ep->e_outptr; + register int i,size=1; + buf[0] = c; +#if SHOPT_MULTIBYTE + /* check for place holder */ + if(c == MARKER) + return; + if((size = mbconv(buf, (wchar_t)c)) > 1) + { + for (i = 0; i < (size-1); i++) + *dp++ = buf[i]; + c = buf[i]; + } + else + { + buf[0] = c; + size = 1; + } +#endif /* SHOPT_MULTIBYTE */ + if (buf[0] == '_' && size==1) + { + *dp++ = ' '; + *dp++ = '\b'; + } + *dp++ = c; + *dp = '\0'; + if(dp >= ep->e_outlast) + ed_flush(ep); + else + ep->e_outptr = dp; +} + +/* + * returns the line and column corresponding to offset <off> in the physical buffer + * if <cur> is non-zero and <= <off>, then correspodning <curpos> will start the search + */ +Edpos_t ed_curpos(Edit_t *ep,genchar *phys, int off, int cur, Edpos_t curpos) +{ + register genchar *sp=phys; + register int c=1, col=ep->e_plen; + Edpos_t pos; +#if SHOPT_MULTIBYTE + char p[16]; +#endif /* SHOPT_MULTIBYTE */ + if(cur && off>=cur) + { + sp += cur; + off -= cur; + pos = curpos; + col = pos.col; + } + else + pos.line = 0; + while(off-->0) + { + if(c) + c = *sp++; +#if SHOPT_MULTIBYTE + if(c && (mbconv(p, (wchar_t)c))==1 && p[0]=='\n') +#else + if(c=='\n') +#endif /* SHOPT_MULTIBYTE */ + col = 0; + else + col++; + if(col > ep->e_winsz) + col = 0; + if(col==0) + pos.line++; + } + pos.col = col; + return(pos); +} + +static void ed_putstring(register Edit_t *ep, const char *str) +{ + register int c; + while(c = *str++) + ed_putchar(ep,c); +} + +int ed_setcursor(register Edit_t *ep,genchar *physical,register int old,register int new,int first) +{ + static int oldline; + register int delta; + Edpos_t newpos; + + delta = new - old; + if( delta == 0 ) + return(new); + if(ep->e_multiline) + { + ep->e_curpos = ed_curpos(ep, physical, old,0,ep->e_curpos); + newpos = ed_curpos(ep, physical, new,old,ep->e_curpos); + if(ep->e_curpos.col==0 && ep->e_curpos.line>0 && oldline<ep->e_curpos.line && delta<0) + ed_putstring(ep,"\r\n"); + oldline = newpos.line; + if(ep->e_curpos.line > newpos.line) + { + int n; + for(;ep->e_curpos.line > newpos.line; ep->e_curpos.line--) + ed_putstring(ep,CURSOR_UP); + if(newpos.line==0 && (n=ep->e_plen- ep->e_curpos.col)>0) + { + ep->e_curpos.col += n; + ed_putchar(ep,'\r'); + if(!ep->e_crlf) + ed_putstring(ep,ep->e_prompt); + else + { + int m = ep->e_winsz+1-ep->e_plen; + ed_putchar(ep,'\n'); + n = ep->e_plen; + if(m < ed_genlen(physical)) + { + while(physical[m] && n-->0) + ed_putchar(ep,physical[m++]); + } + while(n-->0) + ed_putchar(ep,' '); + ed_putstring(ep,CURSOR_UP); + } + } + } + else if(ep->e_curpos.line < newpos.line) + { + for(;ep->e_curpos.line < newpos.line;ep->e_curpos.line++) + ed_putchar(ep,'\n'); + ed_putchar(ep,'\r'); + ep->e_curpos.col = 0; + } + delta = newpos.col - ep->e_curpos.col; + old = new - delta; + } + else + newpos.line=0; + if(delta<0) + { + /*** move to left ***/ + delta = -delta; + /*** attempt to optimize cursor movement ***/ + if(!ep->e_crlf || (2*delta <= ((old-first)+(newpos.line?0:ep->e_plen))) ) + { + for( ; delta; delta-- ) + ed_putchar(ep,'\b'); + } + else + { + if(newpos.line==0) + ed_putstring(ep,ep->e_prompt); + old = first; + delta = new-first; + } + } + while(delta-->0) + ed_putchar(ep,physical[old++]); + return(new); +} + +/* + * copy virtual to physical and return the index for cursor in physical buffer + */ +int ed_virt_to_phys(Edit_t *ep,genchar *virt,genchar *phys,int cur,int voff,int poff) +{ + register genchar *sp = virt; + register genchar *dp = phys; + register int c; + genchar *curp = sp + cur; + genchar *dpmax = phys+MAXLINE; + int d, r; + sp += voff; + dp += poff; + for(r=poff;c= *sp;sp++) + { + if(curp == sp) + r = dp - phys; +#if SHOPT_MULTIBYTE + d = mbwidth((wchar_t)c); + if(d==1 && is_cntrl(c)) + d = -1; + if(d>1) + { + /* multiple width character put in place holders */ + *dp++ = c; + while(--d >0) + *dp++ = MARKER; + /* in vi mode the cursor is at the last character */ + if(dp>=dpmax) + break; + continue; + } + else +#else + d = (is_cntrl(c)?-1:1); +#endif /* SHOPT_MULTIBYTE */ + if(d<0) + { + if(c=='\t') + { + c = dp-phys; + if(sh_isoption(SH_VI)) + c += ep->e_plen; + c = TABSIZE - c%TABSIZE; + while(--c>0) + *dp++ = ' '; + c = ' '; + } + else + { + *dp++ = '^'; + c = printchar(c); + } + /* in vi mode the cursor is at the last character */ + if(curp == sp && sh_isoption(SH_VI)) + r = dp - phys; + } + *dp++ = c; + if(dp>=dpmax) + break; + } + *dp = 0; + return(r); +} + +#if SHOPT_MULTIBYTE +/* + * convert external representation <src> to an array of genchars <dest> + * <src> and <dest> can be the same + * returns number of chars in dest + */ + +int ed_internal(const char *src, genchar *dest) +{ + register const unsigned char *cp = (unsigned char *)src; + register int c; + register wchar_t *dp = (wchar_t*)dest; + if(dest == (genchar*)roundof(cp-(unsigned char*)0,sizeof(genchar))) + { + genchar buffer[MAXLINE]; + c = ed_internal(src,buffer); + ed_gencpy((genchar*)dp,buffer); + return(c); + } + while(*cp) + *dp++ = mbchar(cp); + *dp = 0; + return(dp-(wchar_t*)dest); +} + +/* + * convert internal representation <src> into character array <dest>. + * The <src> and <dest> may be the same. + * returns number of chars in dest. + */ + +int ed_external(const genchar *src, char *dest) +{ + register genchar wc; + register int c,size; + register char *dp = dest; + char *dpmax = dp+sizeof(genchar)*MAXLINE-2; + if((char*)src == dp) + { + char buffer[MAXLINE*sizeof(genchar)]; + c = ed_external(src,buffer); + +#ifdef _lib_wcscpy + wcscpy((wchar_t *)dest,(const wchar_t *)buffer); +#else + strcpy(dest,buffer); +#endif + return(c); + } + while((wc = *src++) && dp<dpmax) + { + if((size = mbconv(dp, wc)) < 0) + { + /* copy the character as is */ + size = 1; + *dp = wc; + } + dp += size; + } + *dp = 0; + return(dp-dest); +} + +/* + * copy <sp> to <dp> + */ + +void ed_gencpy(genchar *dp,const genchar *sp) +{ + dp = (genchar*)roundof((char*)dp-(char*)0,sizeof(genchar)); + sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar)); + while(*dp++ = *sp++); +} + +/* + * copy at most <n> items from <sp> to <dp> + */ + +void ed_genncpy(register genchar *dp,register const genchar *sp, int n) +{ + dp = (genchar*)roundof((char*)dp-(char*)0,sizeof(genchar)); + sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar)); + while(n-->0 && (*dp++ = *sp++)); +} + +/* + * find the string length of <str> + */ + +int ed_genlen(register const genchar *str) +{ + register const genchar *sp = str; + sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar)); + while(*sp++); + return(sp-str-1); +} +#endif /* SHOPT_MULTIBYTE */ +#endif /* SHOPT_ESH || SHOPT_VSH */ + +#ifdef future +/* + * returns 1 when <n> bytes starting at <a> and <b> are equal + */ +static int compare(register const char *a,register const char *b,register int n) +{ + while(n-->0) + { + if(*a++ != *b++) + return(0); + } + return(1); +} +#endif + +#if SHOPT_OLDTERMIO + +# include <sys/termio.h> + +#ifndef ECHOCTL +# define ECHOCTL 0 +#endif /* !ECHOCTL */ +#define ott ep->e_ott + +/* + * For backward compatibility only + * This version will use termios when possible, otherwise termio + */ + + +tcgetattr(int fd, struct termios *tt) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + register int r,i; + ep->e_tcgeta = 0; + ep->e_echoctl = (ECHOCTL!=0); + if((r=ioctl(fd,TCGETS,tt))>=0 || errno!=EINVAL) + return(r); + if((r=ioctl(fd,TCGETA,&ott)) >= 0) + { + tt->c_lflag = ott.c_lflag; + tt->c_oflag = ott.c_oflag; + tt->c_iflag = ott.c_iflag; + tt->c_cflag = ott.c_cflag; + for(i=0; i<NCC; i++) + tt->c_cc[i] = ott.c_cc[i]; + ep->e_tcgeta++; + ep->e_echoctl = 0; + } + return(r); +} + +tcsetattr(int fd,int mode,struct termios *tt) +{ + register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context); + register int r; + if(ep->e_tcgeta) + { + register int i; + ott.c_lflag = tt->c_lflag; + ott.c_oflag = tt->c_oflag; + ott.c_iflag = tt->c_iflag; + ott.c_cflag = tt->c_cflag; + for(i=0; i<NCC; i++) + ott.c_cc[i] = tt->c_cc[i]; + if(tt->c_lflag&ECHOCTL) + { + ott.c_lflag &= ~(ECHOCTL|IEXTEN); + ott.c_iflag &= ~(IGNCR|ICRNL); + ott.c_iflag |= INLCR; + ott.c_cc[VEOF]= ESC; /* ESC -> eof char */ + ott.c_cc[VEOL] = '\r'; /* CR -> eol char */ + ott.c_cc[VEOL2] = tt->c_cc[VEOF]; /* EOF -> eol char */ + } + switch(mode) + { + case TCSANOW: + mode = TCSETA; + break; + case TCSADRAIN: + mode = TCSETAW; + break; + case TCSAFLUSH: + mode = TCSETAF; + } + return(ioctl(fd,mode,&ott)); + } + return(ioctl(fd,mode,tt)); +} +#endif /* SHOPT_OLDTERMIO */ + +#if KSHELL +/* + * Execute keyboard trap on given buffer <inbuff> of given size <isize> + * <mode> < 0 for vi insert mode + */ +static int keytrap(Edit_t *ep,char *inbuff,register int insize, int bufsize, int mode) +{ + register char *cp; + int savexit; +#if SHOPT_MULTIBYTE + char buff[MAXLINE]; + ed_external(ep->e_inbuf,cp=buff); +#else + cp = ep->e_inbuf; +#endif /* SHOPT_MULTIBYTE */ + inbuff[insize] = 0; + ep->e_col = ep->e_cur; + if(mode== -2) + { + ep->e_col++; + *ep->e_vi_insert = ESC; + } + else + *ep->e_vi_insert = 0; + nv_putval(ED_CHRNOD,inbuff,NV_NOFREE); + nv_putval(ED_COLNOD,(char*)&ep->e_col,NV_NOFREE|NV_INTEGER); + nv_putval(ED_TXTNOD,(char*)cp,NV_NOFREE); + nv_putval(ED_MODENOD,ep->e_vi_insert,NV_NOFREE); + savexit = sh.savexit; + sh_trap(sh.st.trap[SH_KEYTRAP],0); + sh.savexit = savexit; + if((cp = nv_getval(ED_CHRNOD)) == inbuff) + nv_unset(ED_CHRNOD); + else + { + strncpy(inbuff,cp,bufsize); + insize = strlen(inbuff); + } + nv_unset(ED_TXTNOD); + return(insize); +} +#endif /* KSHELL */ + +void *ed_open(Shell_t *shp) +{ + Edit_t *ed = newof(0,Edit_t,1,0); + ed->sh = shp; + strcpy(ed->e_macro,"_??"); + return((void*)ed); +} diff --git a/usr/src/lib/libshell/common/edit/emacs.c b/usr/src/lib/libshell/common/edit/emacs.c new file mode 100644 index 0000000000..0882693cc9 --- /dev/null +++ b/usr/src/lib/libshell/common/edit/emacs.c @@ -0,0 +1,1444 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* Original version by Michael T. Veach + * Adapted for ksh by David Korn */ +/* EMACS_MODES: c tabstop=4 + +One line screen editor for any program + +*/ + + +/* The following is provided by: + * + * Matthijs N. Melchior + * AT&T Network Systems International + * APT Nederland + * HV BZ335 x2962 + * hvlpb!mmelchio + * + * These are now on by default + * + * ESH_NFIRST + * - A ^N as first history related command after the prompt will move + * to the next command relative to the last known history position. + * It will not start at the position where the last command was entered + * as is done by the ^P command. Every history related command will + * set both the current and last position. Executing a command will + * only set the current position. + * + * ESH_KAPPEND + * - Successive kill and delete commands will accumulate their data + * in the kill buffer, by appending or prepending as appropriate. + * This mode will be reset by any command not adding something to the + * kill buffer. + * + * ESH_BETTER + * - Some enhancements: + * - argument for a macro is passed to its replacement + * - ^X^H command to find out about history position (debugging) + * - ^X^D command to show any debugging info + * + * I do not pretend these for changes are completely independent, + * but you can use them to seperate features. + */ + +#include <ast.h> +#include <ctype.h> +#include "FEATURE/cmds" +#if KSHELL +# include "defs.h" +#endif /* KSHELL */ +#include "io.h" + +#include "history.h" +#include "edit.h" +#include "terminal.h" + +#define ESH_NFIRST +#define ESH_KAPPEND +#define ESH_BETTER + +#undef putchar +#define putchar(ed,c) ed_putchar(ed,c) +#define beep() ed_ringbell() + + +#if SHOPT_MULTIBYTE +# define gencpy(a,b) ed_gencpy(a,b) +# define genncpy(a,b,n) ed_genncpy(a,b,n) +# define genlen(str) ed_genlen(str) + static int print(int); + static int _isword(int); +# define isword(c) _isword(out[c]) + +#else +# define gencpy(a,b) strcpy((char*)(a),(char*)(b)) +# define genncpy(a,b,n) strncpy((char*)(a),(char*)(b),n) +# define genlen(str) strlen(str) +# define print(c) isprint(c) +# define isword(c) (isalnum(out[c]) || (out[c]=='_')) +#endif /*SHOPT_MULTIBYTE */ + +typedef struct _emacs_ +{ + genchar *screen; /* pointer to window buffer */ + genchar *cursor; /* Cursor in real screen */ + int mark; + int in_mult; + char cr_ok; + char CntrlO; + char overflow; /* Screen overflow flag set */ + char scvalid; /* Screen is up to date */ + int offset; /* Screen offset */ + enum + { + CRT=0, /* Crt terminal */ + PAPER /* Paper terminal */ + } terminal; + Histloc_t _location; + int prevdirection; + Edit_t *ed; /* pointer to edit data */ +} Emacs_t; + +#define editb (*ep->ed) +#define eol editb.e_eol +#define cur editb.e_cur +#define hline editb.e_hline +#define hloff editb.e_hloff +#define hismin editb.e_hismin +#define usrkill editb.e_kill +#define usrlnext editb.e_lnext +#define usreof editb.e_eof +#define usrerase editb.e_erase +#define crallowed editb.e_crlf +#define Prompt editb.e_prompt +#define plen editb.e_plen +#define kstack editb.e_killbuf +#define lstring editb.e_search +#define lookahead editb.e_lookahead +#define env editb.e_env +#define raw editb.e_raw +#define histlines editb.e_hismax +#define w_size editb.e_wsize +#define drawbuff editb.e_inbuf +#define killing editb.e_mode +#define location ep->_location + +#define LBUF 100 +#define KILLCHAR UKILL +#define ERASECHAR UERASE +#define EOFCHAR UEOF +#define LNEXTCHAR ULNEXT +#define DELETE ('a'==97?0177:7) + +/********************** +A large lookahead helps when the user is inserting +characters in the middle of the line. +************************/ + + +typedef enum +{ + FIRST, /* First time thru for logical line, prompt on screen */ + REFRESH, /* Redraw entire screen */ + APPEND, /* Append char before cursor to screen */ + UPDATE, /* Update the screen as need be */ + FINAL /* Update screen even if pending look ahead */ +} Draw_t; + +static void draw(Emacs_t*,Draw_t); +static int escape(Emacs_t*,genchar*, int); +static void putstring(Emacs_t*,char*); +static void search(Emacs_t*,genchar*,int); +static void setcursor(Emacs_t*,int, int); +static void show_info(Emacs_t*,const char*); +static void xcommands(Emacs_t*,int); + +int ed_emacsread(void *context, int fd,char *buff,int scend, int reedit) +{ + Edit_t *ed = (Edit_t*)context; + register int c; + register int i; + register genchar *out; + register int count; + register Emacs_t *ep = ed->e_emacs; + int adjust,oadjust; + char backslash; + genchar *kptr; + char prompt[PRSIZE]; + genchar Screen[MAXLINE]; + if(!ep) + { + ep = ed->e_emacs = newof(0,Emacs_t,1,0); + ep->ed = ed; + ep->prevdirection = 1; + location.hist_command = -5; + } + Prompt = prompt; + ep->screen = Screen; + if(tty_raw(ERRIO,0) < 0) + { + return(reedit?reedit:ed_read(context, fd,buff,scend,0)); + } + raw = 1; + /* This mess in case the read system call fails */ + + ed_setup(ep->ed,fd,reedit); + out = (genchar*)buff; +#if SHOPT_MULTIBYTE + out = (genchar*)roundof((char*)out-(char*)0,sizeof(genchar)); + ed_internal(buff,out); +#endif /* SHOPT_MULTIBYTE */ + if(!kstack) + { + kstack = (genchar*)malloc(CHARSIZE*MAXLINE); + kstack[0] = '\0'; + } + drawbuff = out; +#ifdef ESH_NFIRST + if (location.hist_command == -5) /* to be initialized */ + { + kstack[0] = '\0'; /* also clear kstack... */ + location.hist_command = hline; + location.hist_line = hloff; + } + if (location.hist_command <= hismin) /* don't start below minimum */ + { + location.hist_command = hismin + 1; + location.hist_line = 0; + } + ep->in_mult = hloff; /* save pos in last command */ +#endif /* ESH_NFIRST */ + i = sigsetjmp(env,0); + if (i !=0) + { + tty_cooked(ERRIO); + if (i == UEOF) + { + return(0); /* EOF */ + } + return(-1); /* some other error */ + } + out[reedit] = 0; + if(scend+plen > (MAXLINE-2)) + scend = (MAXLINE-2)-plen; + ep->mark = 0; + cur = eol; + draw(ep,reedit?REFRESH:FIRST); + adjust = -1; + backslash = 0; + if (ep->CntrlO) + { +#ifdef ESH_NFIRST + ed_ungetchar(ep->ed,cntl('N')); +#else + location = hist_locate(sh.hist_ptr,location.hist_command,location.hist_line,1); + if (location.hist_command < histlines) + { + hline = location.hist_command; + hloff = location.hist_line; + hist_copy((char*)kstack,MAXLINE, hline,hloff); +# if SHOPT_MULTIBYTE + ed_internal((char*)kstack,kstack); +# endif /* SHOPT_MULTIBYTE */ + ed_ungetchar(ep->ed,cntl('Y')); + } +#endif /* ESH_NFIRST */ + } + ep->CntrlO = 0; + while ((c = ed_getchar(ep->ed,0)) != (-1)) + { + if (backslash) + { + backslash = 0; + if (c==usrerase||c==usrkill||(!print(c) && + (c!='\r'&&c!='\n'))) + { + /* accept a backslashed character */ + cur--; + out[cur++] = c; + out[eol] = '\0'; + draw(ep,APPEND); + continue; + } + } + if (c == usrkill) + { + c = KILLCHAR ; + } + else if (c == usrerase) + { + c = ERASECHAR ; + } + else if (c == usrlnext) + { + c = LNEXTCHAR ; + } + else if ((c == usreof)&&(eol == 0)) + { + c = EOFCHAR; + } +#ifdef ESH_KAPPEND + if (--killing <= 0) /* reset killing flag */ + killing = 0; +#endif + oadjust = count = adjust; + if(count<0) + count = 1; + adjust = -1; + i = cur; + switch(c) + { + case LNEXTCHAR: + c = ed_getchar(ep->ed,2); + goto do_default_processing; + case cntl('V'): + show_info(ep,fmtident(e_version)); + continue; + case '\0': + ep->mark = i; + continue; + case cntl('X'): + xcommands(ep,count); + continue; + case EOFCHAR: + ed_flush(ep->ed); + tty_cooked(ERRIO); + return(0); +#ifdef u370 + case cntl('S') : + case cntl('Q') : + continue; +#endif /* u370 */ + case '\t': + if(cur>0 && ep->ed->sh->nextprompt) + { + if(ep->ed->e_tabcount==0) + { + ep->ed->e_tabcount=1; + ed_ungetchar(ep->ed,ESC); + goto do_escape; + } + else if(ep->ed->e_tabcount==1) + { + ed_ungetchar(ep->ed,'='); + goto do_escape; + } + ep->ed->e_tabcount = 0; + } + do_default_processing: + default: + + if ((eol+1) >= (scend)) /* will not fit on line */ + { + ed_ungetchar(ep->ed,c); /* save character for next line */ + goto process; + } + for(i= ++eol; i>cur; i--) + out[i] = out[i-1]; + backslash = (c == '\\'); + out[cur++] = c; + draw(ep,APPEND); + continue; + case cntl('Y') : + { + c = genlen(kstack); + if ((c + eol) > scend) + { + beep(); + continue; + } + ep->mark = i; + for(i=eol;i>=cur;i--) + out[c+i] = out[i]; + kptr=kstack; + while (i = *kptr++) + out[cur++] = i; + draw(ep,UPDATE); + eol = genlen(out); + continue; + } + case '\n': + case '\r': + c = '\n'; + goto process; + + case DELETE: /* delete char 0x7f */ + case '\b': /* backspace, ^h */ + case ERASECHAR : + if (count > i) + count = i; +#ifdef ESH_KAPPEND + kptr = &kstack[count]; /* move old contents here */ + if (killing) /* prepend to killbuf */ + { + c = genlen(kstack) + CHARSIZE; /* include '\0' */ + while(c--) /* copy stuff */ + kptr[c] = kstack[c]; + } + else + *kptr = 0; /* this is end of data */ + killing = 2; /* we are killing */ + i -= count; + eol -= count; + genncpy(kstack,out+i,cur-i); +#else + while ((count--)&&(i>0)) + { + i--; + eol--; + } + genncpy(kstack,out+i,cur-i); + kstack[cur-i] = 0; +#endif /* ESH_KAPPEND */ + gencpy(out+i,out+cur); + ep->mark = i; + goto update; + case cntl('W') : +#ifdef ESH_KAPPEND + ++killing; /* keep killing flag */ +#endif + if (ep->mark > eol ) + ep->mark = eol; + if (ep->mark == i) + continue; + if (ep->mark > i) + { + adjust = ep->mark - i; + ed_ungetchar(ep->ed,cntl('D')); + continue; + } + adjust = i - ep->mark; + ed_ungetchar(ep->ed,usrerase); + continue; + case cntl('D') : + ep->mark = i; +#ifdef ESH_KAPPEND + if (killing) + kptr = &kstack[genlen(kstack)]; /* append here */ + else + kptr = kstack; + killing = 2; /* we are now killing */ +#else + kptr = kstack; +#endif /* ESH_KAPPEND */ + while ((count--)&&(eol>0)&&(i<eol)) + { + *kptr++ = out[i]; + eol--; + while(1) + { + if ((out[i] = out[(i+1)])==0) + break; + i++; + } + i = cur; + } + *kptr = '\0'; + goto update; + case cntl('C') : + case cntl('F') : + { + int cntlC = (c==cntl('C')); + while (count-- && eol>i) + { + if (cntlC) + { + c = out[i]; +#if SHOPT_MULTIBYTE + if((c&~STRIP)==0 && islower(c)) +#else + if(islower(c)) +#endif /* SHOPT_MULTIBYTE */ + { + c += 'A' - 'a'; + out[i] = c; + } + } + i++; + } + goto update; + } + case cntl(']') : + c = ed_getchar(ep->ed,1); + if ((count == 0) || (count > eol)) + { + beep(); + continue; + } + if (out[i]) + i++; + while (i < eol) + { + if (out[i] == c && --count==0) + goto update; + i++; + } + i = 0; + while (i < cur) + { + if (out[i] == c && --count==0) + break; + i++; + }; + +update: + cur = i; + draw(ep,UPDATE); + continue; + + case cntl('B') : + if (count > i) + count = i; + i -= count; + goto update; + case cntl('T') : + if ((sh_isoption(SH_EMACS))&& (eol!=i)) + i++; + if (i >= 2) + { + c = out[i - 1]; + out[i-1] = out[i-2]; + out[i-2] = c; + } + else + { + if(sh_isoption(SH_EMACS)) + i--; + beep(); + continue; + } + goto update; + case cntl('A') : + i = 0; + goto update; + case cntl('E') : + i = eol; + goto update; + case cntl('U') : + adjust = 4*count; + continue; + case KILLCHAR : + cur = 0; + oadjust = -1; + case cntl('K') : + if(oadjust >= 0) + { +#ifdef ESH_KAPPEND + killing = 2; /* set killing signal */ +#endif + ep->mark = count; + ed_ungetchar(ep->ed,cntl('W')); + continue; + } + i = cur; + eol = i; + ep->mark = i; +#ifdef ESH_KAPPEND + if (killing) /* append to kill buffer */ + gencpy(&kstack[genlen(kstack)], &out[i]); + else + gencpy(kstack,&out[i]); + killing = 2; /* set killing signal */ +#else + gencpy(kstack,&out[i]); +#endif /* ESH_KAPPEND */ + out[i] = 0; + draw(ep,UPDATE); + if (c == KILLCHAR) + { + if (ep->terminal == PAPER) + { + putchar(ep->ed,'\n'); + putstring(ep,Prompt); + } + c = ed_getchar(ep->ed,0); + if (c != usrkill) + { + ed_ungetchar(ep->ed,c); + continue; + } + if (ep->terminal == PAPER) + ep->terminal = CRT; + else + { + ep->terminal = PAPER; + putchar(ep->ed,'\n'); + putstring(ep,Prompt); + } + } + continue; + case cntl('L'): + ed_crlf(ep->ed); + draw(ep,REFRESH); + continue; + case cntl('[') : + do_escape: + adjust = escape(ep,out,oadjust); + continue; + case cntl('R') : + search(ep,out,count); + goto drawline; + case cntl('P') : + if (count <= hloff) + hloff -= count; + else + { + hline -= count - hloff; + hloff = 0; + } +#ifdef ESH_NFIRST + if (hline <= hismin) +#else + if (hline < hismin) +#endif /* ESH_NFIRST */ + { + hline = hismin+1; + beep(); +#ifndef ESH_NFIRST + continue; +#endif + } + goto common; + + case cntl('O') : + location.hist_command = hline; + location.hist_line = hloff; + ep->CntrlO = 1; + c = '\n'; + goto process; + case cntl('N') : +#ifdef ESH_NFIRST + hline = location.hist_command; /* start at saved position */ + hloff = location.hist_line; +#endif /* ESH_NFIRST */ + location = hist_locate(sh.hist_ptr,hline,hloff,count); + if (location.hist_command > histlines) + { + beep(); +#ifdef ESH_NFIRST + location.hist_command = histlines; + location.hist_line = ep->in_mult; +#else + continue; +#endif /* ESH_NFIRST */ + } + hline = location.hist_command; + hloff = location.hist_line; + common: +#ifdef ESH_NFIRST + location.hist_command = hline; /* save current position */ + location.hist_line = hloff; +#endif + hist_copy((char*)out,MAXLINE, hline,hloff); +#if SHOPT_MULTIBYTE + ed_internal((char*)(out),out); +#endif /* SHOPT_MULTIBYTE */ + drawline: + eol = genlen(out); + cur = eol; + draw(ep,UPDATE); + continue; + } + + } + +process: + + if (c == (-1)) + { + lookahead = 0; + beep(); + *out = '\0'; + } + draw(ep,FINAL); + tty_cooked(ERRIO); + if(ed->e_nlist) + { + ed->e_nlist = 0; + stakset(ed->e_stkptr,ed->e_stkoff); + } + if(c == '\n') + { + out[eol++] = '\n'; + out[eol] = '\0'; + ed_crlf(ep->ed); + } +#if SHOPT_MULTIBYTE + ed_external(out,buff); +#endif /* SHOPT_MULTIBYTE */ + i = strlen(buff); + if (i) + return(i); + return(-1); +} + +static void show_info(Emacs_t *ep,const char *str) +{ + register genchar *out = drawbuff; + register int c; + genchar string[LBUF]; + int sav_cur = cur; + /* save current line */ + genncpy(string,out,sizeof(string)/sizeof(*string)); + *out = 0; + cur = 0; +#if SHOPT_MULTIBYTE + ed_internal(str,out); +#else + gencpy(out,str); +#endif /* SHOPT_MULTIBYTE */ + draw(ep,UPDATE); + c = ed_getchar(ep->ed,0); + if(c!=' ') + ed_ungetchar(ep->ed,c); + /* restore line */ + cur = sav_cur; + genncpy(out,string,sizeof(string)/sizeof(*string)); + draw(ep,UPDATE); +} + +static void putstring(Emacs_t* ep,register char *sp) +{ + register int c; + while (c= *sp++) + putchar(ep->ed,c); +} + + +static int escape(register Emacs_t* ep,register genchar *out,int count) +{ + register int i,value; + int digit,ch; + digit = 0; + value = 0; + while ((i=ed_getchar(ep->ed,0)),isdigit(i)) + { + value *= 10; + value += (i - '0'); + digit = 1; + } + if (digit) + { + ed_ungetchar(ep->ed,i) ; +#ifdef ESH_KAPPEND + ++killing; /* don't modify killing signal */ +#endif + return(value); + } + value = count; + if(value<0) + value = 1; + switch(ch=i) + { + case cntl('V'): + show_info(ep,fmtident(e_version)); + return(-1); + case ' ': + ep->mark = cur; + return(-1); + +#ifdef ESH_KAPPEND + case '+': /* M-+ = append next kill */ + killing = 2; + return -1; /* no argument for next command */ +#endif + + case 'p': /* M-p == ^W^Y (copy stack == kill & yank) */ + ed_ungetchar(ep->ed,cntl('Y')); + ed_ungetchar(ep->ed,cntl('W')); +#ifdef ESH_KAPPEND + killing = 0; /* start fresh */ +#endif + return(-1); + + case 'l': /* M-l == lower-case */ + case 'd': + case 'c': + case 'f': + { + i = cur; + while(value-- && i<eol) + { + while ((out[i])&&(!isword(i))) + i++; + while ((out[i])&&(isword(i))) + i++; + } + if(ch=='l') + { + value = i-cur; + while (value-- > 0) + { + i = out[cur]; +#if SHOPT_MULTIBYTE + if((i&~STRIP)==0 && isupper(i)) +#else + if(isupper(i)) +#endif /* SHOPT_MULTIBYTE */ + { + i += 'a' - 'A'; + out[cur] = i; + } + cur++; + } + draw(ep,UPDATE); + return(-1); + } + + else if(ch=='f') + goto update; + else if(ch=='c') + { + ed_ungetchar(ep->ed,cntl('C')); + return(i-cur); + } + else + { + if (i-cur) + { + ed_ungetchar(ep->ed,cntl('D')); +#ifdef ESH_KAPPEND + ++killing; /* keep killing signal */ +#endif + return(i-cur); + } + beep(); + return(-1); + } + } + + + case 'b': + case DELETE : + case '\b': + case 'h': + { + i = cur; + while(value-- && i>0) + { + i--; + while ((i>0)&&(!isword(i))) + i--; + while ((i>0)&&(isword(i-1))) + i--; + } + if(ch=='b') + goto update; + else + { + ed_ungetchar(ep->ed,usrerase); +#ifdef ESH_KAPPEND + ++killing; +#endif + return(cur-i); + } + } + + case '>': + ed_ungetchar(ep->ed,cntl('N')); +#ifdef ESH_NFIRST + if (ep->in_mult) + { + location.hist_command = histlines; + location.hist_line = ep->in_mult - 1; + } + else + { + location.hist_command = histlines - 1; + location.hist_line = 0; + } +#else + hline = histlines-1; + hloff = 0; +#endif /* ESH_NFIRST */ + return(0); + + case '<': + ed_ungetchar(ep->ed,cntl('P')); + hloff = 0; +#ifdef ESH_NFIRST + hline = hismin + 1; + return 0; +#else + return(hline-hismin); +#endif /* ESH_NFIRST */ + + + case '#': + ed_ungetchar(ep->ed,'\n'); + ed_ungetchar(ep->ed,(out[0]=='#')?cntl('D'):'#'); + ed_ungetchar(ep->ed,cntl('A')); + return(-1); + case '_' : + case '.' : + { + genchar name[MAXLINE]; + char buf[MAXLINE]; + char *ptr; + ptr = hist_word(buf,MAXLINE,(count?count:-1)); +#if !KSHELL + if(ptr==0) + { + beep(); + break; + } +#endif /* KSHELL */ + if ((eol - cur) >= sizeof(name)) + { + beep(); + return(-1); + } + ep->mark = cur; + gencpy(name,&out[cur]); + while(*ptr) + { + out[cur++] = *ptr++; + eol++; + } + gencpy(&out[cur],name); + draw(ep,UPDATE); + return(-1); + } +#if KSHELL + + /* file name expansion */ + case cntl('[') : /* filename completion */ + i = '\\'; + case '*': /* filename expansion */ + case '=': /* escape = - list all matching file names */ + ep->mark = cur; + if(ed_expand(ep->ed,(char*)out,&cur,&eol,i,count) < 0) + { + if(ep->ed->e_tabcount==1) + { + ep->ed->e_tabcount=2; + ed_ungetchar(ep->ed,cntl('\t')); + return(-1); + } + beep(); + } + else if(i=='=') + { + draw(ep,REFRESH); + if(count>0) + ep->ed->e_tabcount=0; + else + { + i=ed_getchar(ep->ed,0); + ed_ungetchar(ep->ed,i); + if(isdigit(i)) + ed_ungetchar(ep->ed,ESC); + } + } + else + { + if(i=='\\' && cur>ep->mark && (out[cur-1]=='/' || out[cur-1]==' ')) + ep->ed->e_tabcount=0; + draw(ep,UPDATE); + } + return(-1); + + /* search back for character */ + case cntl(']'): /* feature not in book */ + { + int c = ed_getchar(ep->ed,1); + if ((value == 0) || (value > eol)) + { + beep(); + return(-1); + } + i = cur; + if (i > 0) + i--; + while (i >= 0) + { + if (out[i] == c && --value==0) + goto update; + i--; + } + i = eol; + while (i > cur) + { + if (out[i] == c && --value==0) + break; + i--; + }; + + } + update: + cur = i; + draw(ep,UPDATE); + return(-1); + +#ifdef _cmd_tput + case cntl('L'): /* clear screen */ + sh_trap("tput clear", 0); + draw(ep,REFRESH); + return(-1); +#endif + case '[': /* feature not in book */ + switch(i=ed_getchar(ep->ed,1)) + { + case 'A': + ed_ungetchar(ep->ed,cntl('P')); + return(-1); + case 'B': + ed_ungetchar(ep->ed,cntl('N')); + return(-1); + case 'C': + ed_ungetchar(ep->ed,cntl('F')); + return(-1); + case 'D': + ed_ungetchar(ep->ed,cntl('B')); + return(-1); + case 'H': + ed_ungetchar(ep->ed,cntl('A')); + return(-1); + case 'Y': + ed_ungetchar(ep->ed,cntl('E')); + return(-1); + default: + ed_ungetchar(ep->ed,i); + } + i = '_'; + + default: + /* look for user defined macro definitions */ + if(ed_macro(ep->ed,i)) +# ifdef ESH_BETTER + return(count); /* pass argument to macro */ +# else + return(-1); +# endif /* ESH_BETTER */ +#else + update: + cur = i; + draw(ep,UPDATE); + return(-1); + + default: +#endif /* KSHELL */ + beep(); + return(-1); + } +} + + +/* + * This routine process all commands starting with ^X + */ + +static void xcommands(register Emacs_t *ep,int count) +{ + register int i = ed_getchar(ep->ed,0); + NOT_USED(count); + switch(i) + { + case cntl('X'): /* exchange dot and mark */ + if (ep->mark > eol) + ep->mark = eol; + i = ep->mark; + ep->mark = cur; + cur = i; + draw(ep,UPDATE); + return; + +#if KSHELL +# ifdef ESH_BETTER + case cntl('E'): /* invoke emacs on current command */ + if(ed_fulledit(ep->ed)==-1) + beep(); + else + { +#if SHOPT_MULTIBYTE + ed_internal((char*)drawbuff,drawbuff); +#endif /* SHOPT_MULTIBYTE */ + ed_ungetchar(ep->ed,'\n'); + } + return; + +# define itos(i) fmtbase((long)(i),0,0)/* want signed conversion */ + + case cntl('H'): /* ^X^H show history info */ + { + char hbuf[MAXLINE]; + + strcpy(hbuf, "Current command "); + strcat(hbuf, itos(hline)); + if (hloff) + { + strcat(hbuf, " (line "); + strcat(hbuf, itos(hloff+1)); + strcat(hbuf, ")"); + } + if ((hline != location.hist_command) || + (hloff != location.hist_line)) + { + strcat(hbuf, "; Previous command "); + strcat(hbuf, itos(location.hist_command)); + if (location.hist_line) + { + strcat(hbuf, " (line "); + strcat(hbuf, itos(location.hist_line+1)); + strcat(hbuf, ")"); + } + } + show_info(ep,hbuf); + return; + } +# if 0 /* debugging, modify as required */ + case cntl('D'): /* ^X^D show debugging info */ + { + char debugbuf[MAXLINE]; + + strcpy(debugbuf, "count="); + strcat(debugbuf, itos(count)); + strcat(debugbuf, " eol="); + strcat(debugbuf, itos(eol)); + strcat(debugbuf, " cur="); + strcat(debugbuf, itos(cur)); + strcat(debugbuf, " crallowed="); + strcat(debugbuf, itos(crallowed)); + strcat(debugbuf, " plen="); + strcat(debugbuf, itos(plen)); + strcat(debugbuf, " w_size="); + strcat(debugbuf, itos(w_size)); + + show_info(ep,debugbuf); + return; + } +# endif /* debugging code */ +# endif /* ESH_BETTER */ +#endif /* KSHELL */ + + default: + beep(); + return; + } +} + +static void search(Emacs_t* ep,genchar *out,int direction) +{ +#ifndef ESH_NFIRST + Histloc_t location; +#endif + register int i,sl; + genchar str_buff[LBUF]; + register genchar *string = drawbuff; + /* save current line */ + int sav_cur = cur; + genncpy(str_buff,string,sizeof(str_buff)/sizeof(*str_buff)); + string[0] = '^'; + string[1] = 'R'; + string[2] = '\0'; + sl = 2; + cur = sl; + draw(ep,UPDATE); + while ((i = ed_getchar(ep->ed,1))&&(i != '\r')&&(i != '\n')) + { + if (i==usrerase || i==DELETE || i=='\b' || i==ERASECHAR) + { + if (sl > 2) + { + string[--sl] = '\0'; + cur = sl; + draw(ep,UPDATE); + } + else + beep(); + continue; + } + if (i==usrkill) + { + beep(); + goto restore; + } + if (i == '\\') + { + string[sl++] = '\\'; + string[sl] = '\0'; + cur = sl; + draw(ep,APPEND); + i = ed_getchar(ep->ed,1); + string[--sl] = '\0'; + } + string[sl++] = i; + string[sl] = '\0'; + cur = sl; + draw(ep,APPEND); + } + i = genlen(string); + + if (direction < 1) + { + ep->prevdirection = -ep->prevdirection; + direction = 1; + } + else + direction = -1; + if (i != 2) + { +#if SHOPT_MULTIBYTE + ed_external(string,(char*)string); +#endif /* SHOPT_MULTIBYTE */ + strncpy(lstring,((char*)string)+2,SEARCHSIZE); + ep->prevdirection = direction; + } + else + direction = ep->prevdirection ; + location = hist_find(sh.hist_ptr,(char*)lstring,hline,1,direction); + i = location.hist_command; + if(i>0) + { + hline = i; +#ifdef ESH_NFIRST + hloff = location.hist_line = 0; /* display first line of multi line command */ +#else + hloff = location.hist_line; +#endif /* ESH_NFIRST */ + hist_copy((char*)out,MAXLINE, hline,hloff); +#if SHOPT_MULTIBYTE + ed_internal((char*)out,out); +#endif /* SHOPT_MULTIBYTE */ + return; + } + if (i < 0) + { + beep(); +#ifdef ESH_NFIRST + location.hist_command = hline; + location.hist_line = hloff; +#else + hloff = 0; + hline = histlines; +#endif /* ESH_NFIRST */ + } +restore: + genncpy(string,str_buff,sizeof(str_buff)/sizeof(*str_buff)); + cur = sav_cur; + return; +} + + +/* Adjust screen to agree with inputs: logical line and cursor */ +/* If 'first' assume screen is blank */ +/* Prompt is always kept on the screen */ + +static void draw(register Emacs_t *ep,Draw_t option) +{ +#define NORMAL ' ' +#define LOWER '<' +#define BOTH '*' +#define UPPER '>' + + register genchar *sptr; /* Pointer within screen */ + genchar nscreen[2*MAXLINE]; /* New entire screen */ + genchar *ncursor; /* New cursor */ + register genchar *nptr; /* Pointer to New screen */ + char longline; /* Line overflow */ + genchar *logcursor; + genchar *nscend; /* end of logical screen */ + register int i; + + nptr = nscreen; + sptr = drawbuff; + logcursor = sptr + cur; + longline = NORMAL; + + if (option == FIRST || option == REFRESH) + { + ep->overflow = NORMAL; + ep->cursor = ep->screen; + ep->offset = 0; + ep->cr_ok = crallowed; + if (option == FIRST) + { + ep->scvalid = 1; + return; + } + *ep->cursor = '\0'; + putstring(ep,Prompt); /* start with prompt */ + } + + /********************* + Do not update screen if pending characters + **********************/ + + if ((lookahead)&&(option != FINAL)) + { + + ep->scvalid = 0; /* Screen is out of date, APPEND will not work */ + + return; + } + + /*************************************** + If in append mode, cursor at end of line, screen up to date, + the previous character was a 'normal' character, + and the window has room for another character. + Then output the character and adjust the screen only. + *****************************************/ + + + i = *(logcursor-1); /* last character inserted */ + + if ((option == APPEND)&&(ep->scvalid)&&(*logcursor == '\0')&& + print(i)&&((ep->cursor-ep->screen)<(w_size-1))) + { + putchar(ep->ed,i); + *ep->cursor++ = i; + *ep->cursor = '\0'; + return; + } + + /* copy the line */ + ncursor = nptr + ed_virt_to_phys(ep->ed,sptr,nptr,cur,0,0); + nptr += genlen(nptr); + sptr += genlen(sptr); + nscend = nptr - 1; + if(sptr == logcursor) + ncursor = nptr; + + /********************* + Does ncursor appear on the screen? + If not, adjust the screen offset so it does. + **********************/ + + i = ncursor - nscreen; + + if ((ep->offset && i<=ep->offset)||(i >= (ep->offset+w_size))) + { + /* Center the cursor on the screen */ + ep->offset = i - (w_size>>1); + if (--ep->offset < 0) + ep->offset = 0; + } + + /********************* + Is the range of screen[0] thru screen[w_size] up-to-date + with nscreen[offset] thru nscreen[offset+w_size] ? + If not, update as need be. + ***********************/ + + nptr = &nscreen[ep->offset]; + sptr = ep->screen; + + i = w_size; + + while (i-- > 0) + { + + if (*nptr == '\0') + { + *(nptr + 1) = '\0'; + *nptr = ' '; + } + if (*sptr == '\0') + { + *(sptr + 1) = '\0'; + *sptr = ' '; + } + if (*nptr == *sptr) + { + nptr++; + sptr++; + continue; + } + setcursor(ep,sptr-ep->screen,*nptr); + *sptr++ = *nptr++; +#if SHOPT_MULTIBYTE + while(*nptr==MARKER) + { + if(*sptr=='\0') + *(sptr + 1) = '\0'; + *sptr++ = *nptr++; + i--; + ep->cursor++; + } +#endif /* SHOPT_MULTIBYTE */ + } + + /****************** + + Screen overflow checks + + ********************/ + + if (nscend >= &nscreen[ep->offset+w_size]) + { + if (ep->offset > 0) + longline = BOTH; + else + longline = UPPER; + } + else + { + if (ep->offset > 0) + longline = LOWER; + } + + /* Update screen overflow indicator if need be */ + + if (longline != ep->overflow) + { + setcursor(ep,w_size,longline); + ep->overflow = longline; + } + i = (ncursor-nscreen) - ep->offset; + setcursor(ep,i,0); + if(option==FINAL && ep->ed->e_multiline) + setcursor(ep,nscend-nscreen,0); + ep->scvalid = 1; + return; +} + +/* + * put the cursor to the <newp> position within screen buffer + * if <c> is non-zero then output this character + * cursor is set to reflect the change + */ + +static void setcursor(register Emacs_t *ep,register int newp,int c) +{ + register int oldp = ep->cursor - ep->screen; + newp = ed_setcursor(ep->ed, ep->screen, oldp, newp, 0); + if(c) + { + putchar(ep->ed,c); + newp++; + } + ep->cursor = ep->screen+newp; + return; +} + +#if SHOPT_MULTIBYTE +static int print(register int c) +{ + return((c&~STRIP)==0 && isprint(c)); +} + +static int _isword(register int c) +{ + return((c&~STRIP) || isalnum(c) || c=='_'); +} +#endif /* SHOPT_MULTIBYTE */ diff --git a/usr/src/lib/libshell/common/edit/hexpand.c b/usr/src/lib/libshell/common/edit/hexpand.c new file mode 100644 index 0000000000..e9dbac5bcb --- /dev/null +++ b/usr/src/lib/libshell/common/edit/hexpand.c @@ -0,0 +1,736 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * bash style history expansion + * + * Author: + * Karsten Fleischer + * Omnium Software Engineering + * An der Luisenburg 7 + * D-51379 Leverkusen + * Germany + * + * <K.Fleischer@omnium.de> + */ + + +#include "defs.h" +#include "edit.h" + +#if ! SHOPT_HISTEXPAND + +NoN(hexpand) + +#else + +#include <ctype.h> + +static char *modifiers = "htrepqxs&"; +static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 }; + +#define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;} + +struct subst +{ + char *str[2]; /* [0] is "old", [1] is "new" string */ +}; + + +/* + * parse an /old/new/ string, delimiter expected as first char. + * if "old" not specified, keep sb->str[0] + * if "new" not specified, set sb->str[1] to empty string + * read up to third delimeter char, \n or \0, whichever comes first. + * return adress is one past the last valid char in s: + * - the address containing \n or \0 or + * - one char beyond the third delimiter + */ + +static char *parse_subst(const char *s, struct subst *sb) +{ + char *cp,del; + int off,n = 0; + + /* build the strings on the stack, mainly for '&' substition in "new" */ + off = staktell(); + + /* init "new" with empty string */ + if(sb->str[1]) + free(sb->str[1]); + sb->str[1] = strdup(""); + + /* get delimiter */ + del = *s; + + cp = (char*) s + 1; + + while(n < 2) + { + if(*cp == del || *cp == '\n' || *cp == '\0') + { + /* delimiter or EOL */ + if(staktell() != off) + { + /* dupe string on stack and rewind stack */ + stakputc('\0'); + if(sb->str[n]) + free(sb->str[n]); + sb->str[n] = strdup(stakptr(off)); + stakseek(off); + } + n++; + + /* if not delimiter, we've reached EOL. Get outta here. */ + if(*cp != del) + break; + } + else if(*cp == '\\') + { + if(*(cp+1) == del) /* quote delimiter */ + { + stakputc(del); + cp++; + } + else if(*(cp+1) == '&' && n == 1) + { /* quote '&' only in "new" */ + stakputc('&'); + cp++; + } + else + stakputc('\\'); + } + else if(*cp == '&' && n == 1 && sb->str[0]) + /* substitute '&' with "old" in "new" */ + stakputs(sb->str[0]); + else + stakputc(*cp); + cp++; + } + + /* rewind stack */ + stakseek(off); + + return cp; +} + +/* + * history expansion main routine + */ + +int hist_expand(const char *ln, char **xp) +{ + int off, /* stack offset */ + q, /* quotation flags */ + p, /* flag */ + c, /* current char */ + flag=0; /* HIST_* flags */ + Sfoff_t n, /* history line number, counter, etc. */ + i, /* counter */ + w[2]; /* word range */ + char *sp, /* stack pointer */ + *cp, /* current char in ln */ + *str, /* search string */ + *evp, /* event/word designator string, for error msgs */ + *cc=0, /* copy of current line up to cp; temp ptr */ + hc[3], /* default histchars */ + *qc="\'\"`"; /* quote characters */ + Sfio_t *ref=0, /* line referenced by event designator */ + *tmp=0, /* temporary line buffer */ + *tmp2=0;/* temporary line buffer */ + Histloc_t hl; /* history location */ + static Namval_t *np = 0; /* histchars variable */ + static struct subst sb = {0,0}; /* substition strings */ + static Sfio_t *wm=0; /* word match from !?string? event designator */ + + if(!wm) + wm = sfopen(NULL, NULL, "swr"); + + hc[0] = '!'; + hc[1] = '^'; + hc[2] = 0; + if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np))) + { + if(cp[0]) + { + hc[0] = cp[0]; + if(cp[1]) + { + hc[1] = cp[1]; + if(cp[2]) + hc[2] = cp[2]; + } + } + } + + /* save shell stack */ + if(off = staktell()) + sp = stakfreeze(0); + + cp = (char*)ln; + + while(cp && *cp) + { + /* read until event/quick substitution/comment designator */ + if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2]) + || (*cp == hc[1] && cp != ln)) + { + if(*cp == '\\') /* skip escaped designators */ + stakputc(*cp++); + else if(*cp == '\'') /* skip quoted designators */ + { + do + stakputc(*cp); + while(*++cp && *cp != '\''); + } + stakputc(*cp++); + continue; + } + + if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */ + { + stakputc(*cp++); + stakputs(cp); + DONE(); + } + + n = -1; + str = 0; + flag &= HIST_EVENT; /* save event flag for returning later */ + evp = cp; + ref = 0; + + if(*cp == hc[1]) /* shortcut substitution */ + { + flag |= HIST_QUICKSUBST; + goto getline; + } + + if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */ + { + cp += 2; + goto getline; + } + + switch(c = *++cp) { + case ' ': + case '\t': + case '\n': + case '\0': + case '=': + case '(': + stakputc(hc[0]); + continue; + case '#': /* the line up to current position */ + flag |= HIST_HASH; + cp++; + n = staktell(); /* terminate string and dup */ + stakputc('\0'); + cc = strdup(stakptr(0)); + stakseek(n); /* remove null byte again */ + ref = sfopen(ref, cc, "s"); /* open as file */ + n = 0; /* skip history file referencing */ + break; + case '-': /* back reference by number */ + if(!isdigit(*(cp+1))) + goto string_event; + cp++; + case '0': /* reference by number */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + while(isdigit(*cp)) + n = n * 10 + (*cp++) - '0'; + if(c == '-') + n = -n; + break; + case '$': + n = -1; + case ':': + break; + case '?': + cp++; + flag |= HIST_QUESTION; + string_event: + default: + /* read until end of string or word designator/modifier */ + str = cp; + while(*cp) + { + cp++; + if((!(flag&HIST_QUESTION) && + (*cp == ':' || isspace(*cp) + || *cp == '^' || *cp == '$' + || *cp == '*' || *cp == '-' + || *cp == '%') + ) + || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n'))) + { + c = *cp; + *cp = '\0'; + } + } + break; + } + +getline: + flag |= HIST_EVENT; + if(str) /* !string or !?string? event designator */ + { + + /* search history for string */ + hl = hist_find(sh.hist_ptr, str, + sh.hist_ptr->histind, + flag&HIST_QUESTION, -1); + if((n = hl.hist_command) == -1) + n = 0; /* not found */ + } + if(n) + { + if(n < 0) /* determine index for backref */ + n = sh.hist_ptr->histind + n; + /* search and use history file if found */ + if(n > 0 && hist_seek(sh.hist_ptr, n) != -1) + ref = sh.hist_ptr->histfp; + + } + if(!ref) + { + /* string not found or command # out of range */ + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp); + *cp = c; + DONE(); + } + + if(str) /* string search: restore orig. line */ + { + if(flag&HIST_QUESTION) + *cp++ = c; /* skip second question mark */ + else + *cp = c; + } + + /* colon introduces either word designators or modifiers */ + if(*(evp = cp) == ':') + cp++; + + w[0] = 0; /* -1 means last word, -2 means match from !?string? */ + w[1] = -1; /* -1 means last word, -2 means suppress last word */ + + if(flag & HIST_QUICKSUBST) /* shortcut substitution */ + goto getsel; + + n = 0; + while(n < 2) + { + switch(c = *cp++) { + case '^': /* first word */ + if(n == 0) + { + w[0] = w[1] = 1; + goto skip; + } + else + goto skip2; + case '$': /* last word */ + w[n] = -1; + goto skip; + case '%': /* match from !?string? event designator */ + if(n == 0) + { + if(!str) + { + w[0] = 0; + w[1] = -1; + ref = wm; + } + else + { + w[0] = -2; + w[1] = sftell(ref) + hl.hist_char; + } + sfseek(wm, 0, SEEK_SET); + goto skip; + } + default: + skip2: + cp--; + n = 2; + break; + case '*': /* until last word */ + if(n == 0) + w[0] = 1; + w[1] = -1; + skip: + flag |= HIST_WORDDSGN; + n = 2; + break; + case '-': /* until last word or specified index */ + w[1] = -2; + flag |= HIST_WORDDSGN; + n = 1; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': /* specify index */ + if((*evp == ':') || w[1] == -2) + { + w[n] = c - '0'; + while(isdigit(c=*cp++)) + w[n] = w[n] * 10 + c - '0'; + flag |= HIST_WORDDSGN; + if(n == 0) + w[1] = w[0]; + n++; + } + else + n = 2; + cp--; + break; + } + } + + if(w[0] != -2 && w[1] > 0 && w[0] > w[1]) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); + *cp = c; + DONE(); + } + + /* no valid word designator after colon, rewind */ + if(!(flag & HIST_WORDDSGN) && (*evp == ':')) + cp = evp; + +getsel: + /* open temp buffer, let sfio do the (re)allocation */ + tmp = sfopen(NULL, NULL, "swr"); + + /* push selected words into buffer, squash + whitespace into single blank or a newline */ + n = i = q = 0; + + while((c = sfgetc(ref)) > 0) + { + if(isspace(c)) + { + flag |= (c == '\n' ? HIST_NEWLINE : 0); + continue; + } + + if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1)) + { + if(w[0] < 0) + sfseek(tmp, 0, SEEK_SET); + else + i = sftell(tmp); + + if(i > 0) + sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' '); + + flag &= ~HIST_NEWLINE; + p = 1; + } + else + p = 0; + + do + { + cc = strchr(qc, c); + q ^= cc ? 1<<(int)(cc - qc) : 0; + if(p) + sfputc(tmp, c); + } + while((c = sfgetc(ref)) > 0 && (!isspace(c) || q)); + + if(w[0] == -2 && sftell(ref) > w[1]) + break; + + flag |= (c == '\n' ? HIST_NEWLINE : 0); + n++; + } + if(w[0] != -2 && w[1] >= 0 && w[1] >= n) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); + *cp = c; + DONE(); + } + else if(w[1] == -2) /* skip last word */ + sfseek(tmp, i, SEEK_SET); + + /* remove trailing newline */ + if(sftell(tmp)) + { + sfseek(tmp, -1, SEEK_CUR); + if(sfgetc(tmp) == '\n') + sfungetc(tmp, '\n'); + } + + sfputc(tmp, '\0'); + + if(str) + { + if(wm) + sfclose(wm); + wm = tmp; + } + + if(cc && (flag&HIST_HASH)) + { + /* close !# temp file */ + sfclose(ref); + flag &= ~HIST_HASH; + free(cc); + cc = 0; + } + + evp = cp; + + /* selected line/words are now in buffer, now go for the modifiers */ + while(*cp == ':' || (flag & HIST_QUICKSUBST)) + { + if(flag & HIST_QUICKSUBST) + { + flag &= ~HIST_QUICKSUBST; + c = 's'; + cp--; + } + else + c = *++cp; + + sfseek(tmp, 0, SEEK_SET); + tmp2 = sfopen(tmp2, NULL, "swr"); + + if(c == 'g') /* global substitution */ + { + flag |= HIST_GLOBALSUBST; + c = *++cp; + } + + if(cc = strchr(modifiers, c)) + flag |= mod_flags[cc - modifiers]; + else + { + errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c); + DONE(); + } + + if(c == 'h' || c == 'r') /* head or base */ + { + n = -1; + while((c = sfgetc(tmp)) > 0) + { /* remember position of / or . */ + if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r')) + n = sftell(tmp2); + sfputc(tmp2, c); + } + if(n > 0) + { /* rewind to last / or . */ + sfseek(tmp2, n, SEEK_SET); + /* end string there */ + sfputc(tmp2, '\0'); + } + } + else if(c == 't' || c == 'e') /* tail or suffix */ + { + n = 0; + while((c = sfgetc(tmp)) > 0) + { /* remember position of / or . */ + if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e')) + n = sftell(tmp); + } + /* rewind to last / or . */ + sfseek(tmp, n, SEEK_SET); + /* copy from there on */ + while((c = sfgetc(tmp)) > 0) + sfputc(tmp2, c); + } + else if(c == 's' || c == '&') + { + cp++; + + if(c == 's') + { + /* preset old with match from !?string? */ + if(!sb.str[0] && wm) + sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0)); + cp = parse_subst(cp, &sb); + } + + if(!sb.str[0] || !sb.str[1]) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, + "%s%s: no previous substitution", + (flag & HIST_QUICKSUBST) ? ":s" : "", + evp); + *cp = c; + DONE(); + } + + /* need pointer for strstr() */ + str = sfsetbuf(tmp, (Void_t*)1, 0); + + flag |= HIST_SUBSTITUTE; + while(flag & HIST_SUBSTITUTE) + { + /* find string */ + if(cc = strstr(str, sb.str[0])) + { /* replace it */ + c = *cc; + *cc = '\0'; + sfputr(tmp2, str, -1); + sfputr(tmp2, sb.str[1], -1); + *cc = c; + str = cc + strlen(sb.str[0]); + } + else if(!sftell(tmp2)) + { /* not successfull */ + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, + "%s%s: substitution failed", + (flag & HIST_QUICKSUBST) ? ":s" : "", + evp); + *cp = c; + DONE(); + } + /* loop if g modifier specified */ + if(!cc || !(flag & HIST_GLOBALSUBST)) + flag &= ~HIST_SUBSTITUTE; + } + /* output rest of line */ + sfputr(tmp2, str, -1); + if(*cp) + cp--; + } + + if(sftell(tmp2)) + { /* if any substitions done, swap buffers */ + if(wm != tmp) + sfclose(tmp); + tmp = tmp2; + tmp2 = 0; + } + cc = 0; + if(*cp) + cp++; + } + + /* flush temporary buffer to stack */ + if(tmp) + { + sfseek(tmp, 0, SEEK_SET); + + if(flag & HIST_QUOTE) + stakputc('\''); + + while((c = sfgetc(tmp)) > 0) + { + if(isspace(c)) + { + flag = flag & ~HIST_NEWLINE; + + /* squash white space to either a + blank or a newline */ + do + flag |= (c == '\n' ? HIST_NEWLINE : 0); + while((c = sfgetc(tmp)) > 0 && isspace(c)); + + sfungetc(tmp, c); + + c = (flag & HIST_NEWLINE) ? '\n' : ' '; + + if(flag & HIST_QUOTE_BR) + { + stakputc('\''); + stakputc(c); + stakputc('\''); + } + else + stakputc(c); + } + else if((c == '\'') && (flag & HIST_QUOTE)) + { + stakputc('\''); + stakputc('\\'); + stakputc(c); + stakputc('\''); + } + else + stakputc(c); + } + if(flag & HIST_QUOTE) + stakputc('\''); + } + } + + stakputc('\0'); + +done: + if(cc && (flag&HIST_HASH)) + { + /* close !# temp file */ + sfclose(ref); + free(cc); + cc = 0; + } + + /* error? */ + if(staktell() && !(flag & HIST_ERROR)) + *xp = strdup(stakfreeze(1)); + + /* restore shell stack */ + if(off) + stakset(sp,off); + else + stakseek(0); + + /* drop temporary files */ + + if(tmp && tmp != wm) + sfclose(tmp); + if(tmp2) + sfclose(tmp2); + + return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK); +} + +#endif diff --git a/usr/src/lib/libshell/common/edit/history.c b/usr/src/lib/libshell/common/edit/history.c new file mode 100644 index 0000000000..f98140fc0f --- /dev/null +++ b/usr/src/lib/libshell/common/edit/history.c @@ -0,0 +1,1109 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * History file manipulation routines + * + * David Korn + * AT&T Labs + * + */ + +/* + * Each command in the history file starts on an even byte is null terminated. + * The first byte must contain the special character HIST_UNDO and the second + * byte is the version number. The sequence HIST_UNDO 0, following a command, + * nullifies the previous command. A six byte sequence starting with + * HIST_CMDNO is used to store the command number so that it is not necessary + * to read the file from beginning to end to get to the last block of + * commands. This format of this sequence is different in version 1 + * then in version 0. Version 1 allows commands to use the full 8 bit + * character set. It can understand version 0 format files. + */ + + +#define HIST_MAX (sizeof(int)*HIST_BSIZE) +#define HIST_BIG (0100000-1024) /* 1K less than maximum short */ +#define HIST_LINE 32 /* typical length for history line */ +#define HIST_MARKSZ 6 +#define HIST_RECENT 600 +#define HIST_UNDO 0201 /* invalidate previous command */ +#define HIST_CMDNO 0202 /* next 3 bytes give command number */ +#define HIST_BSIZE 4096 /* size of history file buffer */ +#define HIST_DFLT 512 /* default size of history list */ + +#define _HIST_PRIVATE \ + off_t histcnt; /* offset into history file */\ + off_t histmarker; /* offset of last command marker */ \ + int histflush; /* set if flushed outside of hflush() */\ + int histmask; /* power of two mask for histcnt */ \ + char histbuff[HIST_BSIZE+1]; /* history file buffer */ \ + int histwfail; \ + off_t histcmds[2]; /* offset for recent commands, must be last */ + +#define hist_ind(hp,c) ((int)((c)&(hp)->histmask)) + +#include <ast.h> +#include <sfio.h> +#include "FEATURE/time" +#include <error.h> +#include <ctype.h> +#include <ls.h> +#if KSHELL +# include "defs.h" +# include "variables.h" +# include "path.h" +# include "builtins.h" +# include "io.h" +#endif /* KSHELL */ +#include "history.h" + +#if !KSHELL +# define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x))) +# define NIL(type) ((type)0) +# define path_relative(x) (x) +# ifdef __STDC__ +# define nv_getval(s) getenv(#s) +# else +# define nv_getval(s) getenv("s") +# endif /* __STDC__ */ +# define e_unknown "unknown" +# define sh_translate(x) (x) + char login_sh = 0; + char hist_fname[] = "/.history"; +#endif /* KSHELL */ + +#ifndef O_BINARY +# define O_BINARY 0 +#endif /* O_BINARY */ + +int _Hist = 0; +static void hist_marker(char*,long); +static void hist_trim(History_t*, int); +static int hist_nearend(History_t*,Sfio_t*, off_t); +static int hist_check(int); +static int hist_clean(int); +#ifdef SF_BUFCONST + static ssize_t hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*); + static int hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*); +#else + static int hist_write(Sfio_t*, const void*, int, Sfdisc_t*); + static int hist_exceptf(Sfio_t*, int, Sfdisc_t*); +#endif + + +static int histinit; +static mode_t histmode; +static History_t *wasopen; +static History_t *hist_ptr; + +#if SHOPT_ACCTFILE + static int acctfd; + static char *logname; +# include <pwd.h> + + int acctinit(void) + { + register char *cp, *acctfile; + Namval_t *np = nv_search("ACCTFILE",sh.var_tree,0); + + if(!np || !(acctfile=nv_getval(np))) + return(0); + if(!(cp = getlogin())) + { + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) + cp = userinfo->pw_name; + else + cp = "unknown"; + } + logname = strdup(cp); + + if((acctfd=sh_open(acctfile, + O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && + (unsigned)acctfd < 10) + { + int n; + if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0) + { + close(acctfd); + acctfd = n; + } + } + if(acctfd < 0) + { + acctfd = 0; + return(0); + } + if(strmatch(acctfile,e_devfdNN)) + { + char newfile[16]; + sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd); + nv_putval(np,newfile,NV_RDONLY); + } + else + fcntl(acctfd,F_SETFD,FD_CLOEXEC); + return(1); + } +#endif /* SHOPT_ACCTFILE */ + +static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION }; +static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL}; + +static void hist_touch(void *handle) +{ + touch((char*)handle, (time_t)0, (time_t)0, 0); +} + +/* + * open the history file + * if HISTNAME is not given and userid==0 then no history file. + * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is + * cleaned up. + * hist_open() returns 1, if history file is open + */ +int sh_histinit(void) +{ + register int fd; + register History_t *hp; + register char *histname; + char *fname=0; + int histmask, maxlines, hist_start=0; + register char *cp; + register off_t hsize = 0; + + if(sh.hist_ptr=hist_ptr) + return(1); + if(!(histname = nv_getval(HISTFILE))) + { + int offset = staktell(); + if(cp=nv_getval(HOME)) + stakputs(cp); + stakputs(hist_fname); + stakputc(0); + stakseek(offset); + histname = stakptr(offset); + } +#ifdef future + if(hp=wasopen) + { + /* reuse history file if same name */ + wasopen = 0; + sh.hist_ptr = hist_ptr = hp; + if(strcmp(histname,hp->histname)==0) + return(1); + else + hist_free(); + } +#endif +retry: + cp = path_relative(histname); + if(!histinit) + histmode = S_IRUSR|S_IWUSR; + if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0) + { + hsize=lseek(fd,(off_t)0,SEEK_END); + } + if((unsigned)fd <=2) + { + int n; + if((n=fcntl(fd,F_DUPFD,10))>=0) + { + close(fd); + fd=n; + } + } + /* make sure that file has history file format */ + if(hsize && hist_check(fd)) + { + close(fd); + hsize = 0; + if(unlink(cp)>=0) + goto retry; + fd = -1; + } + if(fd < 0) + { +#if KSHELL + /* don't allow root a history_file in /tmp */ + if(sh.userid) +#endif /* KSHELL */ + { + if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*)))) + return(0); + fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR); + } + } + if(fd<0) + return(0); + /* set the file to close-on-exec */ + fcntl(fd,F_SETFD,FD_CLOEXEC); + if(cp=nv_getval(HISTSIZE)) + maxlines = (unsigned)strtol(cp, (char**)0, 10); + else + maxlines = HIST_DFLT; + for(histmask=16;histmask <= maxlines; histmask <<=1 ); + if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t)))) + { + close(fd); + return(0); + } + sh.hist_ptr = hist_ptr = hp; + hp->histsize = maxlines; + hp->histmask = histmask; + hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE); + memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1)); + hp->histind = 1; + hp->histcmds[1] = 2; + hp->histcnt = 2; + hp->histname = strdup(histname); + hp->histdisc = hist_disc; + if(hsize==0) + { + /* put special characters at front of file */ + sfwrite(hp->histfp,(char*)hist_stamp,2); + sfsync(hp->histfp); + } + /* initialize history list */ + else + { + int first,last; + off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE; + hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size); + hist_eof(hp); /* this sets histind to last command */ + if((hist_start = (last=(int)hp->histind)-maxlines) <=0) + hist_start = 1; + mark = hp->histmarker; + while(first > hist_start) + { + size += size; + first = hist_nearend(hp,hp->histfp,hsize-size); + hp->histind = first; + } + histinit = hist_start; + hist_eof(hp); + if(!histinit) + { + sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET); + hp->histind = last; + hp->histmarker = mark; + } + histinit = 0; + } + if(fname) + { + unlink(fname); + free((void*)fname); + } + if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX) + { +#ifdef DEBUG + sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize); + sfsync(sfstderr); +#endif /* DEBUG */ + hist_trim(hp,(int)hp->histind-maxlines); + } + sfdisc(hp->histfp,&hp->histdisc); +#if KSHELL + (HISTCUR)->nvalue.lp = (&hp->histind); +#endif /* KSHELL */ + sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname); +#if SHOPT_ACCTFILE + if(sh_isstate(SH_INTERACTIVE)) + acctinit(); +#endif /* SHOPT_ACCTFILE */ + return(1); +} + +/* + * close the history file and free the space + */ + +void hist_close(register History_t *hp) +{ + sfclose(hp->histfp); + free((char*)hp); + hist_ptr = 0; + sh.hist_ptr = 0; +#if SHOPT_ACCTFILE + if(acctfd) + { + close(acctfd); + acctfd = 0; + } +#endif /* SHOPT_ACCTFILE */ +} + +/* + * check history file format to see if it begins with special byte + */ +static int hist_check(register int fd) +{ + unsigned char magic[2]; + lseek(fd,(off_t)0,SEEK_SET); + if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO)) + return(1); + return(0); +} + +/* + * clean out history file OK if not modified in HIST_RECENT seconds + */ +static int hist_clean(int fd) +{ + struct stat statb; + return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT); +} + +/* + * Copy the last <n> commands to a new file and make this the history file + */ + +static void hist_trim(History_t *hp, int n) +{ + register char *cp; + register int incmd=1, c=0; + register History_t *hist_new, *hist_old = hp; + char *buff, *endbuff, *tmpname=0; + off_t oldp,newp; + struct stat statb; + unlink(hist_old->histname); + if(access(hist_old->histname,F_OK) >= 0) + { + /* The unlink can fail on windows 95 */ + int fd; + char *last, *name=hist_old->histname; + close(sffileno(hist_old->histfp)); + tmpname = (char*)malloc(strlen(name)+14); + if(last = strrchr(name,'/')) + { + *last = 0; + pathtmp(tmpname,name,"hist",NIL(int*)); + *last = '/'; + } + else + pathtmp(tmpname,".","hist",NIL(int*)); + if(rename(name,tmpname) < 0) + tmpname = name; + fd = open(tmpname,O_RDONLY); + sfsetfd(hist_old->histfp,fd); + if(tmpname==name) + tmpname = 0; + } + hp = hist_ptr = 0; + if(fstat(sffileno(hist_old->histfp),&statb)>=0) + { + histinit = 1; + histmode = statb.st_mode; + } + if(!sh_histinit()) + { + /* use the old history file */ + hist_ptr = hist_old; + return; + } + hist_new = hist_ptr; + hist_ptr = hist_old; + if(--n < 0) + n = 0; + newp = hist_seek(hist_old,++n); + while(1) + { + if(!incmd) + { + c = hist_ind(hist_new,++hist_new->histind); + hist_new->histcmds[c] = hist_new->histcnt; + if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2) + { + char locbuff[HIST_MARKSZ]; + hist_marker(locbuff,hist_new->histind); + sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ); + hist_new->histcnt += HIST_MARKSZ; + hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt; + } + oldp = newp; + newp = hist_seek(hist_old,++n); + if(newp <=oldp) + break; + } + if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0))) + break; + *(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0; + /* copy to null byte */ + incmd = 0; + while(*cp++); + if(cp > endbuff) + incmd = 1; + else if(*cp==0) + cp++; + if(cp > endbuff) + cp = endbuff; + c = cp-buff; + hist_new->histcnt += c; + sfwrite(hist_new->histfp,buff,c); + } + hist_ptr = hist_new; + hist_cancel(hist_ptr); + sfclose(hist_old->histfp); + if(tmpname) + { + unlink(tmpname); + free(tmpname); + } + free((char*)hist_old); +} + +/* + * position history file at size and find next command number + */ +static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size) +{ + register unsigned char *cp, *endbuff; + register int n, incmd=1; + unsigned char *buff, marker[4]; + if(size <= 2L || sfseek(iop,size,SEEK_SET)<0) + goto begin; + /* skip to marker command and return the number */ + /* numbering commands occur after a null and begin with HIST_CMDNO */ + while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR)) + { + n = sfvalue(iop); + *(endbuff=cp+n) = 0; + while(1) + { + /* check for marker */ + if(!incmd && *cp++==HIST_CMDNO && *cp==0) + { + n = cp+1 - buff; + incmd = -1; + break; + } + incmd = 0; + while(*cp++); + if(cp>endbuff) + { + incmd = 1; + break; + } + if(*cp==0 && ++cp>endbuff) + break; + } + size += n; + sfread(iop,(char*)buff,n); + if(incmd < 0) + { + if((n=sfread(iop,(char*)marker,4))==4) + { + n = (marker[0]<<16)|(marker[1]<<8)|marker[2]; + if(n < size/2) + { + hp->histmarker = hp->histcnt = size+4; + return(n); + } + n=4; + } + if(n >0) + size += n; + incmd = 0; + } + } +begin: + sfseek(iop,(off_t)2,SEEK_SET); + hp->histmarker = hp->histcnt = 2L; + return(1); +} + +/* + * This routine reads the history file from the present position + * to the end-of-file and puts the information in the in-core + * history table + * Note that HIST_CMDNO is only recognized at the beginning of a command + * and that HIST_UNDO as the first character of a command is skipped + * unless it is followed by 0. If followed by 0 then it cancels + * the previous command. + */ + +void hist_eof(register History_t *hp) +{ + register char *cp,*first,*endbuff; + register int incmd = 0; + register off_t count = hp->histcnt; + int n,skip=0; + sfseek(hp->histfp,count,SEEK_SET); + while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0)) + { + n = sfvalue(hp->histfp); + *(endbuff = cp+n) = 0; + first = cp += skip; + while(1) + { + while(!incmd) + { + if(cp>first) + { + count += (cp-first); + n = hist_ind(hp, ++hp->histind); +#ifdef future + if(count==hp->histcmds[n]) + { + sfprintf(sfstderr,"count match n=%d\n",n); + if(histinit) + { + histinit = 0; + return; + } + } + else if(n>=histinit) +#endif + hp->histcmds[n] = count; + first = cp; + } + switch(*((unsigned char*)(cp++))) + { + case HIST_CMDNO: + if(*cp==0) + { + hp->histmarker=count+2; + cp += (HIST_MARKSZ-1); + hp->histind--; +#ifdef future + if(cp <= endbuff) + { + unsigned char *marker = (unsigned char*)(cp-4); + int n = ((marker[0]<<16) +|(marker[1]<<8)|marker[2]); + if((n<count/2) && n != (hp->histind+1)) + errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n); + } +#endif + } + break; + case HIST_UNDO: + if(*cp==0) + { + cp+=1; + hp->histind-=2; + } + break; + default: + cp--; + incmd = 1; + } + if(cp > endbuff) + { + cp++; + goto refill; + } + } + first = cp; + while(*cp++); + if(cp > endbuff) + break; + incmd = 0; + while(*cp==0) + { + if(++cp > endbuff) + goto refill; + } + } + refill: + count += (--cp-first); + skip = (cp-endbuff); + if(!incmd && !skip) + hp->histcmds[hist_ind(hp,++hp->histind)] = count; + } + hp->histcnt = count; +} + +/* + * This routine will cause the previous command to be cancelled + */ + +void hist_cancel(register History_t *hp) +{ + register int c; + if(!hp) + return; + sfputc(hp->histfp,HIST_UNDO); + sfputc(hp->histfp,0); + sfsync(hp->histfp); + hp->histcnt += 2; + c = hist_ind(hp,--hp->histind); + hp->histcmds[c] = hp->histcnt; +} + +/* + * flush the current history command + */ + +void hist_flush(register History_t *hp) +{ + register char *buff; + if(hp) + { + if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR)) + { + hp->histflush = sfvalue(hp->histfp)+1; + sfwrite(hp->histfp,buff,0); + } + else + hp->histflush=0; + if(sfsync(hp->histfp)<0) + { + hist_close(hp); + if(!sh_histinit()) + sh_offoption(SH_HISTORY); + } + hp->histflush = 0; + } +} + +/* + * This is the write discipline for the history file + * When called from hist_flush(), trailing newlines are deleted and + * a zero byte. Line sequencing is added as required + */ + +#ifdef SF_BUFCONST +static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle) +#else +static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle) +#endif +{ + register History_t *hp = (History_t*)handle; + register char *bufptr = ((char*)buff)+insize; + register int c,size = insize; + register off_t cur; + int saved=0; + char saveptr[HIST_MARKSZ]; + if(!hp->histflush) + return(write(sffileno(iop),(char*)buff,size)); + if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0) + { + errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno); + return(-1); + } + hp->histcnt = cur; + /* remove whitespace from end of commands */ + while(--bufptr >= (char*)buff) + { + c= *bufptr; + if(!isspace(c)) + { + if(c=='\\' && *(bufptr+1)!='\n') + bufptr++; + break; + } + } + /* don't count empty lines */ + if(++bufptr <= (char*)buff) + return(insize); + *bufptr++ = '\n'; + *bufptr++ = 0; + size = bufptr - (char*)buff; +#if SHOPT_ACCTFILE + if(acctfd) + { + int timechars, offset; + offset = staktell(); + stakputs(buff); + stakseek(staktell() - 1); + timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *))); + lseek(acctfd, (off_t)0, SEEK_END); + write(acctfd, stakptr(offset), size - 2 + timechars); + stakseek(offset); + + } +#endif /* SHOPT_ACCTFILE */ + if(size&01) + { + size++; + *bufptr++ = 0; + } + hp->histcnt += size; + c = hist_ind(hp,++hp->histind); + hp->histcmds[c] = hp->histcnt; + if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2) + { + memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ); + saved=1; + hp->histcnt += HIST_MARKSZ; + hist_marker(bufptr,hp->histind); + hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt; + size += HIST_MARKSZ; + } + errno = 0; + size = write(sffileno(iop),(char*)buff,size); + if(saved) + memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ); + if(size>=0) + { + hp->histwfail = 0; + return(insize); + } + return(-1); +} + +/* + * Put history sequence number <n> into buffer <buff> + * The buffer must be large enough to hold HIST_MARKSZ chars + */ + +static void hist_marker(register char *buff,register long cmdno) +{ + *buff++ = HIST_CMDNO; + *buff++ = 0; + *buff++ = (cmdno>>16); + *buff++ = (cmdno>>8); + *buff++ = cmdno; + *buff++ = 0; +} + +/* + * return byte offset in history file for command <n> + */ +off_t hist_tell(register History_t *hp, int n) +{ + return(hp->histcmds[hist_ind(hp,n)]); +} + +/* + * seek to the position of command <n> + */ +off_t hist_seek(register History_t *hp, int n) +{ + return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET)); +} + +/* + * write the command starting at offset <offset> onto file <outfile>. + * if character <last> appears before newline it is deleted + * each new-line character is replaced with string <nl>. + */ + +void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl) +{ + register int oldc=0; + register int c; + if(offset<0 || !hp) + { + sfputr(outfile,sh_translate(e_unknown),'\n'); + return; + } + sfseek(hp->histfp,offset,SEEK_SET); + while((c = sfgetc(hp->histfp)) != EOF) + { + if(c && oldc=='\n') + sfputr(outfile,nl,-1); + else if(last && (c==0 || (c=='\n' && oldc==last))) + return; + else if(oldc) + sfputc(outfile,oldc); + oldc = c; + if(c==0) + return; + } + return; +} + +/* + * find index for last line with given string + * If flag==0 then line must begin with string + * direction < 1 for backwards search +*/ + +Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction) +{ + register int index2; + off_t offset; + int *coffset=0; + Histloc_t location; + location.hist_command = -1; + location.hist_char = 0; + location.hist_line = 0; + if(!hp) + return(location); + /* leading ^ means beginning of line unless escaped */ + if(flag) + { + index2 = *string; + if(index2=='\\') + string++; + else if(index2=='^') + { + flag=0; + string++; + } + } + if(flag) + coffset = &location.hist_char; + index2 = (int)hp->histind; + if(direction<0) + { + index2 -= hp->histsize; + if(index2<1) + index2 = 1; + if(index1 <= index2) + return(location); + } + else if(index1 >= index2) + return(location); + while(index1!=index2) + { + direction>0?++index1:--index1; + offset = hist_tell(hp,index1); + if((location.hist_line=hist_match(hp,offset,string,coffset))>=0) + { + location.hist_command = index1; + return(location); + } +#if KSHELL + /* allow a search to be aborted */ + if(sh.trapnote&SH_SIGSET) + break; +#endif /* KSHELL */ + } + return(location); +} + +/* + * search for <string> in history file starting at location <offset> + * If coffset==0 then line must begin with string + * returns the line number of the match if successful, otherwise -1 + */ + +int hist_match(register History_t *hp,off_t offset,char *string,int *coffset) +{ + register unsigned char *first, *cp; + register int m,n,c=1,line=0; +#if SHOPT_MULTIBYTE + mbinit(); +#endif /* SHOPT_MULTIBYTE */ + sfseek(hp->histfp,offset,SEEK_SET); + if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0))) + return(-1); + m = sfvalue(hp->histfp); + n = strlen(string); + while(m > n) + { + if(*cp==*string && memcmp(cp,string,n)==0) + { + if(coffset) + *coffset = (cp-first); + return(line); + } + if(!coffset) + break; + if(*cp=='\n') + line++; +#if SHOPT_MULTIBYTE + if((c=mbsize(cp)) < 0) + c = 1; +#endif /* SHOPT_MULTIBYTE */ + cp += c; + m -= c; + } + return(-1); +} + + +#if SHOPT_ESH || SHOPT_VSH +/* + * copy command <command> from history file to s1 + * at most <size> characters copied + * if s1==0 the number of lines for the command is returned + * line=linenumber for emacs copy and only this line of command will be copied + * line < 0 for full command copy + * -1 returned if there is no history file + */ + +int hist_copy(char *s1,int size,int command,int line) +{ + register int c; + register History_t *hp = sh_getinterp()->hist_ptr; + register int count = 0; + register char *s1max = s1+size; + if(!hp) + return(-1); + hist_seek(hp,command); + while ((c = sfgetc(hp->histfp)) && c!=EOF) + { + if(c=='\n') + { + if(count++ ==line) + break; + else if(line >= 0) + continue; + } + if(s1 && (line<0 || line==count)) + { + if(s1 >= s1max) + { + *--s1 = 0; + break; + } + *s1++ = c; + } + + } + sfseek(hp->histfp,(off_t)0,SEEK_END); + if(s1==0) + return(count); + if(count && (c= *(s1-1)) == '\n') + s1--; + *s1 = '\0'; + return(count); +} + +/* + * return word number <word> from command number <command> + */ + +char *hist_word(char *string,int size,int word) +{ + register int c; + register char *s1 = string; + register unsigned char *cp = (unsigned char*)s1; + register int flag = 0; + History_t *hp = hist_ptr; + if(!hp) +#if KSHELL + { + strncpy(string,sh.lastarg,size); + return(string); + } +#else + return(NIL(char*)); +#endif /* KSHELL */ + hist_copy(string,size,(int)hp->histind-1,-1); + for(;c = *cp;cp++) + { + c = isspace(c); + if(c && flag) + { + *cp = 0; + if(--word==0) + break; + flag = 0; + } + else if(c==0 && flag==0) + { + s1 = (char*)cp; + flag++; + } + } + *cp = 0; + if(s1 != string) + strcpy(string,s1); + return(string); +} + +#endif /* SHOPT_ESH */ + +#if SHOPT_ESH +/* + * given the current command and line number, + * and number of lines back or foward, + * compute the new command and line number. + */ + +Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines) +{ + Histloc_t next; + line += lines; + if(!hp) + { + command = -1; + goto done; + } + if(lines > 0) + { + register int count; + while(command <= hp->histind) + { + count = hist_copy(NIL(char*),0, command,-1); + if(count > line) + goto done; + line -= count; + command++; + } + } + else + { + register int least = (int)hp->histind-hp->histsize; + while(1) + { + if(line >=0) + goto done; + if(--command < least) + break; + line += hist_copy(NIL(char*),0, command,-1); + } + command = -1; + } +done: + next.hist_line = line; + next.hist_command = command; + return(next); +} +#endif /* SHOPT_ESH */ + + +/* + * Handle history file exceptions + */ +#ifdef SF_BUFCONST +static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle) +#else +static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle) +#endif +{ + register int newfd,oldfd; + History_t *hp = (History_t*)handle; + if(type==SF_WRITE) + { + if(errno==ENOSPC || hp->histwfail++ >= 10) + return(0); + /* write failure could be NFS problem, try to re-open */ + close(oldfd=sffileno(fp)); + if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0) + { + if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd) + return(-1); + fcntl(oldfd,F_SETFD,FD_CLOEXEC); + close(newfd); + if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt) + { + register int index = hp->histind; + lseek(oldfd,(off_t)2,SEEK_SET); + hp->histcnt = 2; + hp->histind = 1; + hp->histcmds[1] = 2; + hist_eof(hp); + hp->histmarker = hp->histcnt; + hp->histind = index; + } + return(1); + } + errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname); + return(-1); + } + return(0); +} diff --git a/usr/src/lib/libshell/common/edit/vi.c b/usr/src/lib/libshell/common/edit/vi.c new file mode 100644 index 0000000000..5d6fa8e56f --- /dev/null +++ b/usr/src/lib/libshell/common/edit/vi.c @@ -0,0 +1,2635 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* Adapted for ksh by David Korn */ +/*+ VI.C P.D. Sullivan + * + * One line editor for the shell based on the vi editor. + * + * Questions to: + * P.D. Sullivan + * cbosgd!pds +-*/ + + +#if KSHELL +# include "defs.h" +#else +# include <ast.h> +# include "FEATURE/options" +#endif /* KSHELL */ +#include <ctype.h> +#include "io.h" + +#include "history.h" +#include "edit.h" +#include "terminal.h" +#include "FEATURE/time" + +#if SHOPT_OLDTERMIO +# undef ECHOCTL +# define echoctl (vp->ed->e_echoctl) +#else +# ifdef ECHOCTL +# define echoctl ECHOCTL +# else +# define echoctl 0 +# endif /* ECHOCTL */ +#endif /*SHOPT_OLDTERMIO */ + +#ifndef FIORDCHK +# define NTICKS 5 /* number of ticks for typeahead */ +#endif /* FIORDCHK */ + +#define MAXCHAR MAXLINE-2 /* max char per line */ + +#if SHOPT_MULTIBYTE +# include "lexstates.h" +# define gencpy(a,b) ed_gencpy(a,b) +# define genncpy(a,b,n) ed_genncpy(a,b,n) +# define genlen(str) ed_genlen(str) +# define digit(c) ((c&~STRIP)==0 && isdigit(c)) +# define is_print(c) ((c&~STRIP) || isprint(c)) +# if !_lib_iswprint && !defined(iswprint) +# define iswprint(c) ((c&~0177) || isprint(c)) +# endif + static int _isalph(int); + static int _ismetach(int); + static int _isblank(int); +# undef isblank +# define isblank(v) _isblank(virtual[v]) +# define isalph(v) _isalph(virtual[v]) +# define ismetach(v) _ismetach(virtual[v]) +#else + static genchar _c; +# define gencpy(a,b) strcpy((char*)(a),(char*)(b)) +# define genncpy(a,b,n) strncpy((char*)(a),(char*)(b),n) +# define genlen(str) strlen(str) +# define isalph(v) ((_c=virtual[v])=='_'||isalnum(_c)) +# undef isblank +# define isblank(v) isspace(virtual[v]) +# define ismetach(v) ismeta(virtual[v]) +# define digit(c) isdigit(c) +# define is_print(c) isprint(c) +#endif /* SHOPT_MULTIBYTE */ + +#if ( 'a' == 97) /* ASCII? */ +# define fold(c) ((c)&~040) /* lower and uppercase equivalent */ +#else +# define fold(c) ((c)|0100) /* lower and uppercase equivalent */ +#endif + +#ifndef iswascii +#define iswascii(c) (!((c)&(~0177))) +#endif + +typedef struct _vi_ +{ + int direction; + int lastmacro; + char addnl; /* boolean - add newline flag */ + char last_find; /* last find command */ + char last_cmd; /* last command */ + char repeat_set; + char nonewline; + int findchar; /* last find char */ + genchar *lastline; + int first_wind; /* first column of window */ + int last_wind; /* last column in window */ + int lastmotion; /* last motion */ + int long_char; /* line bigger than window */ + int long_line; /* line bigger than window */ + int ocur_phys; /* old current physical position */ + int ocur_virt; /* old last virtual position */ + int ofirst_wind; /* old window first col */ + int o_v_char; /* prev virtual[ocur_virt] */ + int repeat; /* repeat count for motion cmds */ + int lastrepeat; /* last repeat count for motion cmds */ + int u_column; /* undo current column */ + int U_saved; /* original virtual saved */ + genchar *U_space; /* used for U command */ + genchar *u_space; /* used for u command */ +#ifdef FIORDCHK + clock_t typeahead; /* typeahead occurred */ +#else + int typeahead; /* typeahead occurred */ +#endif /* FIORDCHK */ +#if SHOPT_MULTIBYTE + int bigvi; +#endif + Edit_t *ed; /* pointer to edit data */ +} Vi_t; + +#define editb (*vp->ed) + +#undef putchar +#define putchar(c) ed_putchar(vp->ed,c) + +#define crallowed editb.e_crlf +#define cur_virt editb.e_cur /* current virtual column */ +#define cur_phys editb.e_pcur /* current phys column cursor is at */ +#define curhline editb.e_hline /* current history line */ +#define first_virt editb.e_fcol /* first allowable column */ +#define globals editb.e_globals /* local global variables */ +#define histmin editb.e_hismin +#define histmax editb.e_hismax +#define last_phys editb.e_peol /* last column in physical */ +#define last_virt editb.e_eol /* last column */ +#define lsearch editb.e_search /* last search string */ +#define lookahead editb.e_lookahead /* characters in buffer */ +#define previous editb.e_lbuf /* lookahead buffer */ +#define max_col editb.e_llimit /* maximum column */ +#define Prompt editb.e_prompt /* pointer to prompt */ +#define plen editb.e_plen /* length of prompt */ +#define physical editb.e_physbuf /* physical image */ +#define usreof editb.e_eof /* user defined eof char */ +#define usrerase editb.e_erase /* user defined erase char */ +#define usrlnext editb.e_lnext /* user defined next literal */ +#define usrkill editb.e_kill /* user defined kill char */ +#define virtual editb.e_inbuf /* pointer to virtual image buffer */ +#define window editb.e_window /* window buffer */ +#define w_size editb.e_wsize /* window size */ +#define inmacro editb.e_inmacro /* true when in macro */ +#define yankbuf editb.e_killbuf /* yank/delete buffer */ + + +#define ABORT -2 /* user abort */ +#define APPEND -10 /* append chars */ +#define BAD -1 /* failure flag */ +#define BIGVI -15 /* user wants real vi */ +#define CONTROL -20 /* control mode */ +#define ENTER -25 /* enter flag */ +#define GOOD 0 /* success flag */ +#define INPUT -30 /* input mode */ +#define INSERT -35 /* insert mode */ +#define REPLACE -40 /* replace chars */ +#define SEARCH -45 /* search flag */ +#define TRANSLATE -50 /* translate virt to phys only */ + +#define INVALID (-1) /* invalid column */ + +static const char paren_chars[] = "([{)]}"; /* for % command */ + +static void cursor(Vi_t*, int); +static void del_line(Vi_t*,int); +static int getcount(Vi_t*,int); +static void getline(Vi_t*,int); +static int getrchar(Vi_t*); +static int mvcursor(Vi_t*,int); +static void pr_string(Vi_t*,const char*); +static void putstring(Vi_t*,int, int); +static void refresh(Vi_t*,int); +static void replace(Vi_t*,int, int); +static void restore_v(Vi_t*); +static void save_last(Vi_t*); +static void save_v(Vi_t*); +static int search(Vi_t*,int); +static void sync_cursor(Vi_t*); +static int textmod(Vi_t*,int,int); + +/*+ VI_READ( fd, shbuf, nchar ) + * + * This routine implements a one line version of vi and is + * called by _filbuf.c + * +-*/ + +/* + * if reedit is non-zero, initialize edit buffer with reedit chars + */ +int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit) +{ + Edit_t *ed = (Edit_t*)context; + register int i; /* general variable */ + register int term_char; /* read() termination character */ + register Vi_t *vp = ed->e_vi; + char prompt[PRSIZE+2]; /* prompt */ + genchar Physical[2*MAXLINE]; /* physical image */ + genchar Ubuf[MAXLINE]; /* used for U command */ + genchar ubuf[MAXLINE]; /* used for u command */ + genchar Window[MAXLINE]; /* window image */ + int Globals[9]; /* local global variables */ + int esc_or_hang=0; /* <ESC> or hangup */ + char cntl_char=0; /* TRUE if control character present */ +#if SHOPT_RAWONLY +# define viraw 1 +#else + int viraw = (sh_isoption(SH_VIRAW) || sh.st.trap[SH_KEYTRAP]); +# ifndef FIORDCHK + clock_t oldtime, newtime; + struct tms dummy; +# endif /* FIORDCHK */ +#endif /* SHOPT_RAWONLY */ + if(!vp) + { + ed->e_vi = vp = newof(0,Vi_t,1,0); + vp->lastline = (genchar*)malloc(MAXLINE*CHARSIZE); + vp->direction = -1; + vp->ed = ed; + } + + /*** setup prompt ***/ + + Prompt = prompt; + ed_setup(vp->ed,fd, reedit); + shbuf[reedit] = 0; + +#if !SHOPT_RAWONLY + if(!viraw) + { + /*** Change the eol characters to '\r' and eof ***/ + /* in addition to '\n' and make eof an ESC */ + if(tty_alt(ERRIO) < 0) + return(reexit?reedit:ed_read(context, fd, shbuf, nchar,0)); + +#ifdef FIORDCHK + ioctl(fd,FIORDCHK,&vp->typeahead); +#else + /* time the current line to determine typeahead */ + oldtime = times(&dummy); +#endif /* FIORDCHK */ +#if KSHELL + /* abort of interrupt has occurred */ + if(sh.trapnote&SH_SIGSET) + i = -1; + else +#endif /* KSHELL */ + /*** Read the line ***/ + i = ed_read(context, fd, shbuf, nchar, 0); +#ifndef FIORDCHK + newtime = times(&dummy); + vp->typeahead = ((newtime-oldtime) < NTICKS); +#endif /* FIORDCHK */ + if(echoctl) + { + if( i <= 0 ) + { + /*** read error or eof typed ***/ + tty_cooked(ERRIO); + return(i); + } + term_char = shbuf[--i]; + if( term_char == '\r' ) + term_char = '\n'; + if( term_char=='\n' || term_char==ESC ) + shbuf[i--] = '\0'; + else + shbuf[i+1] = '\0'; + } + else + { + register int c = shbuf[0]; + + /*** Save and remove the last character if its an eol, ***/ + /* changing '\r' to '\n' */ + + if( i == 0 ) + { + /*** ESC was typed as first char of line ***/ + esc_or_hang = 1; + term_char = ESC; + shbuf[i--] = '\0'; /* null terminate line */ + } + else if( i<0 || c==usreof ) + { + /*** read error or eof typed ***/ + tty_cooked(ERRIO); + if( c == usreof ) + i = 0; + return(i); + } + else + { + term_char = shbuf[--i]; + if( term_char == '\r' ) + term_char = '\n'; +#if !defined(VEOL2) && !defined(ECHOCTL) + if(term_char=='\n') + { + tty_cooked(ERRIO); + return(i+1); + } +#endif + if( term_char=='\n' || term_char==usreof ) + { + /*** remove terminator & null terminate ***/ + shbuf[i--] = '\0'; + } + else + { + /** terminator was ESC, which is not xmitted **/ + term_char = ESC; + shbuf[i+1] = '\0'; + } + } + } + } + else +#endif /* SHOPT_RAWONLY */ + { + /*** Set raw mode ***/ + +#if !SHOPT_RAWONLY + if( editb.e_ttyspeed == 0 ) + { + /*** never did TCGETA, so do it ***/ + /* avoids problem if user does 'sh -o viraw' */ + tty_alt(ERRIO); + } +#endif /* SHOPT_RAWONLY */ + if(tty_raw(ERRIO,0) < 0 ) + return(reedit?reedit:ed_read(context, fd, shbuf, nchar,0)); + i = last_virt-1; + } + + /*** Initialize some things ***/ + + virtual = (genchar*)shbuf; +#if SHOPT_MULTIBYTE + virtual = (genchar*)roundof((char*)virtual-(char*)0,sizeof(genchar)); + shbuf[i+1] = 0; + i = ed_internal(shbuf,virtual)-1; +#endif /* SHOPT_MULTIBYTE */ + globals = Globals; + cur_phys = i + 1; + cur_virt = i; + first_virt = 0; + vp->first_wind = 0; + last_virt = i; + last_phys = i; + vp->last_wind = i; + vp->long_line = ' '; + vp->long_char = ' '; + vp->o_v_char = '\0'; + vp->ocur_phys = 0; + vp->ocur_virt = MAXCHAR; + vp->ofirst_wind = 0; + physical = Physical; + vp->u_column = INVALID - 1; + vp->U_space = Ubuf; + vp->u_space = ubuf; + window = Window; + window[0] = '\0'; + + if(!yankbuf) + yankbuf = (genchar*)malloc(MAXLINE*CHARSIZE); + if( vp->last_cmd == '\0' ) + { + /*** first time for this shell ***/ + + vp->last_cmd = 'i'; + vp->findchar = INVALID; + vp->lastmotion = '\0'; + vp->lastrepeat = 1; + vp->repeat = 1; + *yankbuf = 0; + } + + /*** fiddle around with prompt length ***/ + if( nchar+plen > MAXCHAR ) + nchar = MAXCHAR - plen; + max_col = nchar - 2; + + if( !viraw ) + { + int kill_erase = 0; + for(i=(echoctl?last_virt:0); i<last_virt; ++i ) + { + /*** change \r to \n, check for control characters, ***/ + /* delete appropriate ^Vs, */ + /* and estimate last physical column */ + + if( virtual[i] == '\r' ) + virtual[i] = '\n'; + if(!echoctl) + { + register int c = virtual[i]; + if( c<=usrerase) + { + /*** user typed escaped erase or kill char ***/ + cntl_char = 1; + if(is_print(c)) + kill_erase++; + } + else if( !is_print(c) ) + { + cntl_char = 1; + + if( c == usrlnext ) + { + if( i == last_virt ) + { + /*** eol/eof was escaped ***/ + /* so replace ^V with it */ + virtual[i] = term_char; + break; + } + + /*** delete ^V ***/ + gencpy((&virtual[i]), (&virtual[i+1])); + --cur_virt; + --last_virt; + } + } + } + } + + /*** copy virtual image to window ***/ + if(last_virt > 0) + last_phys = ed_virt_to_phys(vp->ed,virtual,physical,last_virt,0,0); + if( last_phys >= w_size ) + { + /*** line longer than window ***/ + vp->last_wind = w_size - 1; + } + else + vp->last_wind = last_phys; + genncpy(window, virtual, vp->last_wind+1); + + if( term_char!=ESC && (last_virt==INVALID + || virtual[last_virt]!=term_char) ) + { + /*** Line not terminated with ESC or escaped (^V) ***/ + /* eol, so return after doing a total update */ + /* if( (speed is greater or equal to 1200 */ + /* and something was typed) and */ + /* (control character present */ + /* or typeahead occurred) ) */ + + tty_cooked(ERRIO); + if( editb.e_ttyspeed==FAST && last_virt!=INVALID + && (vp->typeahead || cntl_char) ) + { + refresh(vp,TRANSLATE); + pr_string(vp,Prompt); + putstring(vp,0, last_phys+1); + if(echoctl) + ed_crlf(vp->ed); + else + while(kill_erase-- > 0) + putchar(' '); + } + + if( term_char=='\n' ) + { + if(!echoctl) + ed_crlf(vp->ed); + virtual[++last_virt] = '\n'; + } + vp->last_cmd = 'i'; + save_last(vp); +#if SHOPT_MULTIBYTE + virtual[last_virt+1] = 0; + last_virt = ed_external(virtual,shbuf); + return(last_virt); +#else + return(++last_virt); +#endif /* SHOPT_MULTIBYTE */ + } + + /*** Line terminated with escape, or escaped eol/eof, ***/ + /* so set raw mode */ + + if( tty_raw(ERRIO,0) < 0 ) + { + tty_cooked(ERRIO); + /* + * The following prevents drivers that return 0 on + * causing an infinite loop + */ + if(esc_or_hang) + return(-1); + virtual[++last_virt] = '\n'; +#if SHOPT_MULTIBYTE + virtual[last_virt+1] = 0; + last_virt = ed_external(virtual,shbuf); + return(last_virt); +#else + return(++last_virt); +#endif /* SHOPT_MULTIBYTE */ + } + + if(echoctl) /*** for cntl-echo erase the ^[ ***/ + pr_string(vp,"\b\b\b\b \b\b"); + + + if(crallowed) + { + /*** start over since there may be ***/ + /*** a control char, or cursor might not ***/ + /*** be at left margin (this lets us know ***/ + /*** where we are ***/ + cur_phys = 0; + window[0] = '\0'; + pr_string(vp,Prompt); + if( term_char==ESC && (last_virt<0 || virtual[last_virt]!=ESC)) + refresh(vp,CONTROL); + else + refresh(vp,INPUT); + } + else + { + /*** just update everything internally ***/ + refresh(vp,TRANSLATE); + } + } + + /*** Handle usrintr, usrquit, or EOF ***/ + + i = sigsetjmp(editb.e_env,0); + if( i != 0 ) + { + virtual[0] = '\0'; + tty_cooked(ERRIO); + + switch(i) + { + case UEOF: + /*** EOF ***/ + return(0); + + case UINTR: + /** interrupt **/ + return(-1); + } + return(-1); + } + + /*** Get a line from the terminal ***/ + + vp->U_saved = 0; + if(reedit) + refresh(vp,INPUT); + if(viraw) + getline(vp,APPEND); + else if(last_virt>=0 && virtual[last_virt]==term_char) + getline(vp,APPEND); + else + getline(vp,ESC); + if(vp->ed->e_multiline) + cursor(vp, last_phys); + /*** add a new line if user typed unescaped \n ***/ + /* to cause the shell to process the line */ + tty_cooked(ERRIO); + if(ed->e_nlist) + { + ed->e_nlist = 0; + stakset(ed->e_stkptr,ed->e_stkoff); + } + if( vp->addnl ) + { + virtual[++last_virt] = '\n'; + ed_crlf(vp->ed); + } + if( ++last_virt >= 0 ) + { +#if SHOPT_MULTIBYTE + if(vp->bigvi) + { + vp->bigvi = 0; + shbuf[last_virt-1] = '\n'; + } + else + { + virtual[last_virt] = 0; + last_virt = ed_external(virtual,shbuf); + } +#endif /* SHOPT_MULTIBYTE */ + return(last_virt); + } + else + return(-1); +} + + +/*{ APPEND( char, mode ) + * + * This routine will append char after cur_virt in the virtual image. + * mode = APPEND, shift chars right before appending + * REPLACE, replace char if possible + * +}*/ + +static void append(Vi_t *vp,int c, int mode) +{ + register int i,j; + + if( last_virt<max_col && last_phys<max_col ) + { + if( mode==APPEND || (cur_virt==last_virt && last_virt>=0)) + { + j = (cur_virt>=0?cur_virt:0); + for(i = ++last_virt; i > j; --i) + virtual[i] = virtual[i-1]; + } + virtual[++cur_virt] = c; + } + else + ed_ringbell(); + return; +} + +/*{ BACKWORD( nwords, cmd ) + * + * This routine will position cur_virt at the nth previous word. + * +}*/ + +static void backword(Vi_t *vp,int nwords, register int cmd) +{ + register int tcur_virt = cur_virt; + while( nwords-- && tcur_virt > first_virt ) + { + if( !isblank(tcur_virt) && isblank(tcur_virt-1) + && tcur_virt>first_virt ) + --tcur_virt; + else if(cmd != 'B') + { + register int last = isalph(tcur_virt-1); + register int cur = isalph(tcur_virt); + if((!cur && last) || (cur && !last)) + --tcur_virt; + } + while( isblank(tcur_virt) && tcur_virt>=first_virt ) + --tcur_virt; + if( cmd == 'B' ) + { + while( !isblank(tcur_virt) && tcur_virt>=first_virt ) + --tcur_virt; + } + else + { + if(isalph(tcur_virt)) + while( isalph(tcur_virt) && tcur_virt>=first_virt ) + --tcur_virt; + else + while( !isalph(tcur_virt) && !isblank(tcur_virt) + && tcur_virt>=first_virt ) + --tcur_virt; + } + cur_virt = ++tcur_virt; + } + return; +} + +/*{ CNTLMODE() + * + * This routine implements the vi command subset. + * The cursor will always be positioned at the char of interest. + * +}*/ + +static int cntlmode(Vi_t *vp) +{ + register int c; + register int i; + genchar tmp_u_space[MAXLINE]; /* temporary u_space */ + genchar *real_u_space; /* points to real u_space */ + int tmp_u_column = INVALID; /* temporary u_column */ + int was_inmacro; + + if(!vp->U_saved) + { + /*** save virtual image if never done before ***/ + virtual[last_virt+1] = '\0'; + gencpy(vp->U_space, virtual); + vp->U_saved = 1; + } + + save_last(vp); + + real_u_space = vp->u_space; + curhline = histmax; + first_virt = 0; + vp->repeat = 1; + if( cur_virt > INVALID ) + { + /*** make sure cursor is at the last char ***/ + sync_cursor(vp); + } + + /*** Read control char until something happens to cause a ***/ + /* return to APPEND/REPLACE mode */ + + while( c=ed_getchar(vp->ed,-1) ) + { + vp->repeat_set = 0; + was_inmacro = inmacro; + if( c == '0' ) + { + /*** move to leftmost column ***/ + cur_virt = 0; + sync_cursor(vp); + continue; + } + + if( digit(c) ) + { + c = getcount(vp,c); + if( c == '.' ) + vp->lastrepeat = vp->repeat; + } + + /*** see if it's a move cursor command ***/ + + if(mvcursor(vp,c)) + { + sync_cursor(vp); + vp->repeat = 1; + continue; + } + + /*** see if it's a repeat of the last command ***/ + + if( c == '.' ) + { + c = vp->last_cmd; + vp->repeat = vp->lastrepeat; + i = textmod(vp,c, c); + } + else + { + i = textmod(vp,c, 0); + } + + /*** see if it's a text modification command ***/ + + switch(i) + { + case BAD: + break; + + default: /** input mode **/ + if(!was_inmacro) + { + vp->last_cmd = c; + vp->lastrepeat = vp->repeat; + } + vp->repeat = 1; + if( i == GOOD ) + continue; + return(i); + } + + switch( c ) + { + /***** Other stuff *****/ + + case cntl('L'): /** Redraw line **/ + /*** print the prompt and ***/ + /* force a total refresh */ + if(vp->nonewline==0) + putchar('\n'); + vp->nonewline = 0; + pr_string(vp,Prompt); + window[0] = '\0'; + cur_phys = vp->first_wind; + vp->ofirst_wind = INVALID; + vp->long_line = ' '; + break; + + case cntl('V'): + { + register const char *p = fmtident(e_version); + save_v(vp); + del_line(vp,BAD); + while(c = *p++) + append(vp,c,APPEND); + refresh(vp,CONTROL); + ed_getchar(vp->ed,-1); + restore_v(vp); + break; + } + + case '/': /** Search **/ + case '?': + case 'N': + case 'n': + save_v(vp); + switch( search(vp,c) ) + { + case GOOD: + /*** force a total refresh ***/ + window[0] = '\0'; + goto newhist; + + case BAD: + /*** no match ***/ + ed_ringbell(); + + default: + if( vp->u_column == INVALID ) + del_line(vp,BAD); + else + restore_v(vp); + break; + } + break; + + case 'j': /** get next command **/ + case '+': /** get next command **/ + curhline += vp->repeat; + if( curhline > histmax ) + { + curhline = histmax; + goto ringbell; + } + else if(curhline==histmax && tmp_u_column!=INVALID ) + { + vp->u_space = tmp_u_space; + vp->u_column = tmp_u_column; + restore_v(vp); + vp->u_space = real_u_space; + break; + } + save_v(vp); + cur_virt = INVALID; + goto newhist; + + case 'k': /** get previous command **/ + case '-': /** get previous command **/ + if( curhline == histmax ) + { + vp->u_space = tmp_u_space; + i = vp->u_column; + save_v(vp); + vp->u_space = real_u_space; + tmp_u_column = vp->u_column; + vp->u_column = i; + } + + curhline -= vp->repeat; + if( curhline <= histmin ) + { + curhline += vp->repeat; + goto ringbell; + } + save_v(vp); + cur_virt = INVALID; + newhist: + if(curhline!=histmax || cur_virt==INVALID) + hist_copy((char*)virtual, MAXLINE, curhline,-1); + else + { + strcpy((char*)virtual,(char*)vp->u_space); +#if SHOPT_MULTIBYTE + ed_internal((char*)vp->u_space,vp->u_space); +#endif /* SHOPT_MULTIBYTE */ + } +#if SHOPT_MULTIBYTE + ed_internal((char*)virtual,virtual); +#endif /* SHOPT_MULTIBYTE */ + if((last_virt=genlen(virtual)-1) >= 0 && cur_virt == INVALID) + cur_virt = 0; + break; + + + case 'u': /** undo the last thing done **/ + restore_v(vp); + break; + + case 'U': /** Undo everything **/ + save_v(vp); + if( virtual[0] == '\0' ) + goto ringbell; + else + { + gencpy(virtual, vp->U_space); + last_virt = genlen(vp->U_space) - 1; + cur_virt = 0; + } + break; + +#if KSHELL + case 'v': + if(vp->repeat_set==0) + goto vcommand; +#endif /* KSHELL */ + + case 'G': /** goto command repeat **/ + if(vp->repeat_set==0) + vp->repeat = histmin+1; + if( vp->repeat <= histmin || vp->repeat > histmax ) + { + goto ringbell; + } + curhline = vp->repeat; + save_v(vp); + if(c == 'G') + { + cur_virt = INVALID; + goto newhist; + } + +#if KSHELL + vcommand: + if(ed_fulledit(vp->ed)==GOOD) + return(BIGVI); + else + goto ringbell; +#endif /* KSHELL */ + + case '#': /** insert(delete) # to (no)comment command **/ + if( cur_virt != INVALID ) + { + register genchar *p = &virtual[last_virt+1]; + *p = 0; + /*** see whether first char is comment char ***/ + c = (virtual[0]=='#'); + while(p-- >= virtual) + { + if(*p=='\n' || p<virtual) + { + if(c) /* delete '#' */ + { + if(p[1]=='#') + { + last_virt--; + gencpy(p+1,p+2); + } + } + else + { + cur_virt = p-virtual; + append(vp,'#', APPEND); + } + } + } + if(c) + { + curhline = histmax; + cur_virt = 0; + break; + } + refresh(vp,INPUT); + } + + case '\n': /** send to shell **/ + return(ENTER); + + case ESC: + /* don't ring bell if next char is '[' */ + if(!lookahead) + { + char x; + if(sfpkrd(editb.e_fd,&x,1,'\r',400L,-1)>0) + ed_ungetchar(vp->ed,x); + } + if(lookahead) + { + ed_ungetchar(vp->ed,c=ed_getchar(vp->ed,1)); + if(c=='[') + { + vp->repeat = 1; + continue; + } + } + default: + ringbell: + ed_ringbell(); + vp->repeat = 1; + continue; + } + + refresh(vp,CONTROL); + vp->repeat = 1; + } +/* NOTREACHED */ + return(0); +} + +/*{ CURSOR( new_current_physical ) + * + * This routine will position the virtual cursor at + * physical column x in the window. + * +}*/ + +static void cursor(Vi_t *vp,register int x) +{ +#if SHOPT_MULTIBYTE + while(physical[x]==MARKER) + x++; +#endif /* SHOPT_MULTIBYTE */ + cur_phys = ed_setcursor(vp->ed, physical, cur_phys,x,vp->first_wind); +} + +/*{ DELETE( nchars, mode ) + * + * Delete nchars from the virtual space and leave cur_virt positioned + * at cur_virt-1. + * + * If mode = 'c', do not save the characters deleted + * = 'd', save them in yankbuf and delete. + * = 'y', save them in yankbuf but do not delete. + * +}*/ + +static void cdelete(Vi_t *vp,register int nchars, int mode) +{ + register int i; + register genchar *cp; + + if( cur_virt < first_virt ) + { + ed_ringbell(); + return; + } + if( nchars > 0 ) + { + cp = virtual+cur_virt; + vp->o_v_char = cp[0]; + if( (cur_virt-- + nchars) > last_virt ) + { + /*** set nchars to number actually deleted ***/ + nchars = last_virt - cur_virt; + } + + /*** save characters to be deleted ***/ + + if( mode != 'c' ) + { + i = cp[nchars]; + cp[nchars] = 0; + gencpy(yankbuf,cp); + cp[nchars] = i; + } + + /*** now delete these characters ***/ + + if( mode != 'y' ) + { + gencpy(cp,cp+nchars); + last_virt -= nchars; + } + } + return; +} + +/*{ DEL_LINE( mode ) + * + * This routine will delete the line. + * mode = GOOD, do a save_v() + * +}*/ +static void del_line(register Vi_t *vp, int mode) +{ + if( last_virt == INVALID ) + return; + + if( mode == GOOD ) + save_v(vp); + + cur_virt = 0; + first_virt = 0; + cdelete(vp,last_virt+1, BAD); + refresh(vp,CONTROL); + + cur_virt = INVALID; + cur_phys = 0; + vp->findchar = INVALID; + last_phys = INVALID; + last_virt = INVALID; + vp->last_wind = INVALID; + vp->first_wind = 0; + vp->o_v_char = '\0'; + vp->ocur_phys = 0; + vp->ocur_virt = MAXCHAR; + vp->ofirst_wind = 0; + window[0] = '\0'; + return; +} + +/*{ DELMOTION( motion, mode ) + * + * Delete thru motion. + * + * mode = 'd', save deleted characters, delete + * = 'c', do not save characters, change + * = 'y', save characters, yank + * + * Returns 1 if operation successful; else 0. + * +}*/ + +static int delmotion(Vi_t *vp,int motion, int mode) +{ + register int begin, end, delta; + /* the following saves a register */ + + if( cur_virt == INVALID ) + return(0); + if( mode != 'y' ) + save_v(vp); + begin = cur_virt; + + /*** fake out the motion routines by appending a blank ***/ + + virtual[++last_virt] = ' '; + end = mvcursor(vp,motion); + virtual[last_virt--] = 0; + if(!end) + return(0); + + end = cur_virt; + if( mode=='c' && end>begin && strchr("wW", motion) ) + { + /*** called by change operation, user really expects ***/ + /* the effect of the eE commands, so back up to end of word */ + while( end>begin && isblank(end-1) ) + --end; + if( end == begin ) + ++end; + } + + delta = end - begin; + if( delta >= 0 ) + { + cur_virt = begin; + if( strchr("eE;,TtFf%", motion) ) + ++delta; + } + else + { + delta = -delta + (motion=='%'); + } + + cdelete(vp,delta, mode); + if( mode == 'y' ) + cur_virt = begin; + return(1); +} + + +/*{ ENDWORD( nwords, cmd ) + * + * This routine will move cur_virt to the end of the nth word. + * +}*/ + +static void endword(Vi_t *vp, int nwords, register int cmd) +{ + register int tcur_virt = cur_virt; + while( nwords-- ) + { + if( !isblank(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + while( isblank(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + if( cmd == 'E' ) + { + while( !isblank(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + } + else + { + if( isalph(tcur_virt) ) + while( isalph(tcur_virt) && tcur_virt<=last_virt ) + ++tcur_virt; + else + while( !isalph(tcur_virt) && !isblank(tcur_virt) + && tcur_virt<=last_virt ) + ++tcur_virt; + } + if( tcur_virt > first_virt ) + tcur_virt--; + } + cur_virt = tcur_virt; + return; +} + +/*{ FORWARD( nwords, cmd ) + * + * This routine will move cur_virt forward to the next nth word. + * +}*/ + +static void forward(Vi_t *vp,register int nwords, int cmd) +{ + register int tcur_virt = cur_virt; + while( nwords-- ) + { + if( cmd == 'W' ) + { + while( !isblank(tcur_virt) && tcur_virt < last_virt ) + ++tcur_virt; + } + else + { + if( isalph(tcur_virt) ) + { + while( isalph(tcur_virt) && tcur_virt<last_virt ) + ++tcur_virt; + } + else + { + while( !isalph(tcur_virt) && !isblank(tcur_virt) + && tcur_virt < last_virt ) + ++tcur_virt; + } + } + while( isblank(tcur_virt) && tcur_virt < last_virt ) + ++tcur_virt; + } + cur_virt = tcur_virt; + return; +} + + + +/*{ GETCOUNT(c) + * + * Set repeat to the user typed number and return the terminating + * character. + * +}*/ + +static int getcount(register Vi_t *vp,register int c) +{ + register int i; + + /*** get any repeat count ***/ + + if( c == '0' ) + return(c); + + vp->repeat_set++; + i = 0; + while( digit(c) ) + { + i = i*10 + c - '0'; + c = ed_getchar(vp->ed,-1); + } + + if( i > 0 ) + vp->repeat *= i; + return(c); +} + + +/*{ GETLINE( mode ) + * + * This routine will fetch a line. + * mode = APPEND, allow escape to cntlmode subroutine + * appending characters. + * = REPLACE, allow escape to cntlmode subroutine + * replacing characters. + * = SEARCH, no escape allowed + * = ESC, enter control mode immediately + * + * The cursor will always be positioned after the last + * char printed. + * + * This routine returns when cr, nl, or (eof in column 0) is + * received (column 0 is the first char position). + * +}*/ + +static void getline(register Vi_t* vp,register int mode) +{ + register int c; + register int tmp; + int max_virt=0, last_save=0; + genchar saveline[MAXLINE]; + + vp->addnl = 1; + + if( mode == ESC ) + { + /*** go directly to control mode ***/ + goto escape; + } + + for(;;) + { + if( (c=ed_getchar(vp->ed,mode==SEARCH?1:-2)) == usreof ) + c = UEOF; + else if( c == usrerase ) + c = UERASE; + else if( c == usrkill ) + c = UKILL; + else if( c == editb.e_werase ) + c = UWERASE; + else if( c == usrlnext ) + c = ULNEXT; + + if( c == ULNEXT) + { + /*** implement ^V to escape next char ***/ + c = ed_getchar(vp->ed,2); + append(vp,c, mode); + refresh(vp,INPUT); + continue; + } + + switch( c ) + { + case ESC: /** enter control mode **/ + if(!sh_isoption(SH_VI)) + { + append(vp,c, mode); + break; + } + if( mode == SEARCH ) + { + ed_ringbell(); + continue; + } + else + { + escape: + if( mode == REPLACE ) + { + c = max_virt-cur_virt; + if(c > 0 && last_save>=cur_virt) + { + genncpy((&virtual[cur_virt]),&saveline[cur_virt],c); + if(last_virt>=last_save) + last_virt=last_save-1; + refresh(vp,INPUT); + } + --cur_virt; + } + tmp = cntlmode(vp); + if( tmp == ENTER || tmp == BIGVI ) + { +#if SHOPT_MULTIBYTE + vp->bigvi = (tmp==BIGVI); +#endif /* SHOPT_MULTIBYTE */ + return; + } + if( tmp == INSERT ) + { + mode = APPEND; + continue; + } + mode = tmp; + if(mode==REPLACE) + { + c = last_save = last_virt+1; + if(c >= MAXLINE) + c = MAXLINE-1; + genncpy(saveline, virtual, c); + } + } + break; + + case UERASE: /** user erase char **/ + /*** treat as backspace ***/ + + case '\b': /** backspace **/ + if( virtual[cur_virt] == '\\' ) + { + cdelete(vp,1, BAD); + append(vp,usrerase, mode); + } + else + { + if( mode==SEARCH && cur_virt==0 ) + { + first_virt = 0; + cdelete(vp,1, BAD); + return; + } + if(mode==REPLACE || (last_save>0 && last_virt<=last_save)) + { + if(cur_virt<=first_virt) + ed_ringbell(); + else if(mode==REPLACE) + --cur_virt; + mode = REPLACE; + sync_cursor(vp); + continue; + } + else + cdelete(vp,1, BAD); + } + break; + + case UWERASE: /** delete back word **/ + if( cur_virt > first_virt && + !isblank(cur_virt) && + !ispunct(virtual[cur_virt]) && + isblank(cur_virt-1) ) + { + cdelete(vp,1, BAD); + } + else + { + tmp = cur_virt; + backword(vp,1, 'W'); + cdelete(vp,tmp - cur_virt + 1, BAD); + } + break; + + case UKILL: /** user kill line char **/ + if( virtual[cur_virt] == '\\' ) + { + cdelete(vp,1, BAD); + append(vp,usrkill, mode); + } + else + { + if( mode == SEARCH ) + { + cur_virt = 1; + delmotion(vp, '$', BAD); + } + else if(first_virt) + { + tmp = cur_virt; + cur_virt = first_virt; + cdelete(vp,tmp - cur_virt + 1, BAD); + } + else + del_line(vp,GOOD); + } + break; + + case UEOF: /** eof char **/ + if( cur_virt != INVALID ) + continue; + vp->addnl = 0; + + case '\n': /** newline or return **/ + if( mode != SEARCH ) + save_last(vp); + refresh(vp,INPUT); + return; + + case '\t': /** command completion **/ + if(mode!=SEARCH && last_virt>=0 && (vp->ed->e_tabcount|| !isblank(cur_virt)) && vp->ed->sh->nextprompt) + { + if(vp->ed->e_tabcount==0) + { + ed_ungetchar(vp->ed,'\\'); + vp->ed->e_tabcount=1; + goto escape; + } + else if(vp->ed->e_tabcount==1) + { + ed_ungetchar(vp->ed,'='); + goto escape; + } + vp->ed->e_tabcount = 0; + } + /* FALL THRU*/ + default: + if( mode == REPLACE ) + { + if( cur_virt < last_virt ) + { + replace(vp,c, 1); + if(cur_virt>max_virt) + max_virt = cur_virt; + continue; + } + cdelete(vp,1, BAD); + mode = APPEND; + max_virt = last_virt+3; + } + append(vp,c, mode); + break; + } + refresh(vp,INPUT); + + } +} + +/*{ MVCURSOR( motion ) + * + * This routine will move the virtual cursor according to motion + * for repeat times. + * + * It returns GOOD if successful; else BAD. + * +}*/ + +static int mvcursor(register Vi_t* vp,register int motion) +{ + register int count; + register int tcur_virt; + register int incr = -1; + register int bound = 0; + + switch(motion) + { + /***** Cursor move commands *****/ + + case '0': /** First column **/ + tcur_virt = 0; + break; + + case '^': /** First nonblank character **/ + tcur_virt = first_virt; + while( isblank(tcur_virt) && tcur_virt < last_virt ) + ++tcur_virt; + break; + + case '|': + tcur_virt = vp->repeat-1; + if(tcur_virt <= last_virt) + break; + /* fall through */ + + case '$': /** End of line **/ + tcur_virt = last_virt; + break; + + case '[': + switch(motion=getcount(vp,ed_getchar(vp->ed,-1))) + { + case 'A': + ed_ungetchar(vp->ed,'k'); + return(1); + case 'B': + ed_ungetchar(vp->ed,'j'); + return(1); + case 'C': + motion = last_virt; + incr = 1; + goto walk; + case 'D': + motion = first_virt; + goto walk; + case 'H': + tcur_virt = 0; + break; + case 'Y': + tcur_virt = last_virt; + break; + default: + ed_ungetchar(vp->ed,motion); + return(0); + } + break; + + case 'h': /** Left one **/ + case '\b': + motion = first_virt; + goto walk; + + case ' ': + case 'l': /** Right one **/ + motion = last_virt; + incr = 1; + walk: + tcur_virt = cur_virt; + if( incr*tcur_virt < motion) + { + tcur_virt += vp->repeat*incr; + if( incr*tcur_virt > motion) + tcur_virt = motion; + } + else + return(0); + break; + + case 'B': + case 'b': /** back word **/ + tcur_virt = cur_virt; + backword(vp,vp->repeat, motion); + if( cur_virt == tcur_virt ) + return(0); + return(1); + + case 'E': + case 'e': /** end of word **/ + tcur_virt = cur_virt; + if(tcur_virt >=0) + endword(vp, vp->repeat, motion); + if( cur_virt == tcur_virt ) + return(0); + return(1); + + case ',': /** reverse find old char **/ + case ';': /** find old char **/ + switch(vp->last_find) + { + case 't': + case 'f': + if(motion==';') + { + bound = last_virt; + incr = 1; + } + goto find_b; + + case 'T': + case 'F': + if(motion==',') + { + bound = last_virt; + incr = 1; + } + goto find_b; + + default: + return(0); + } + + + case 't': /** find up to new char forward **/ + case 'f': /** find new char forward **/ + bound = last_virt; + incr = 1; + + case 'T': /** find up to new char backward **/ + case 'F': /** find new char backward **/ + vp->last_find = motion; + if((vp->findchar=getrchar(vp))==ESC) + return(1); +find_b: + tcur_virt = cur_virt; + count = vp->repeat; + while( count-- ) + { + while( incr*(tcur_virt+=incr) <= bound + && virtual[tcur_virt] != vp->findchar ); + if( incr*tcur_virt > bound ) + { + return(0); + } + } + if( fold(vp->last_find) == 'T' ) + tcur_virt -= incr; + break; + + case '%': + { + int nextmotion; + int nextc; + tcur_virt = cur_virt; + while( tcur_virt <= last_virt + && strchr(paren_chars,virtual[tcur_virt])==(char*)0) + tcur_virt++; + if(tcur_virt > last_virt ) + return(0); + nextc = virtual[tcur_virt]; + count = strchr(paren_chars,nextc)-paren_chars; + if(count < 3) + { + incr = 1; + bound = last_virt; + nextmotion = paren_chars[count+3]; + } + else + nextmotion = paren_chars[count-3]; + count = 1; + while(count >0 && incr*(tcur_virt+=incr) <= bound) + { + if(virtual[tcur_virt] == nextmotion) + count--; + else if(virtual[tcur_virt]==nextc) + count++; + } + if(count) + return(0); + break; + } + + case 'W': + case 'w': /** forward word **/ + tcur_virt = cur_virt; + forward(vp,vp->repeat, motion); + if( tcur_virt == cur_virt ) + return(0); + return(1); + + default: + return(0); + } + cur_virt = tcur_virt; + + return(1); +} + +/* + * print a string + */ + +static void pr_string(register Vi_t *vp, register const char *sp) +{ + /*** copy string sp ***/ + register char *ptr = editb.e_outptr; + while(*sp) + *ptr++ = *sp++; + editb.e_outptr = ptr; + return; +} + +/*{ PUTSTRING( column, nchars ) + * + * Put nchars starting at column of physical into the workspace + * to be printed. + * +}*/ + +static void putstring(register Vi_t *vp,register int col, register int nchars) +{ + while( nchars-- ) + putchar(physical[col++]); + return; +} + +/*{ REFRESH( mode ) + * + * This routine will refresh the crt so the physical image matches + * the virtual image and display the proper window. + * + * mode = CONTROL, refresh in control mode, ie. leave cursor + * positioned at last char printed. + * = INPUT, refresh in input mode; leave cursor positioned + * after last char printed. + * = TRANSLATE, perform virtual to physical translation + * and adjust left margin only. + * + * +-------------------------------+ + * | | | virtual | | | + * +-------------------------------+ + * cur_virt last_virt + * + * +-----------------------------------------------+ + * | | | physical | | | + * +-----------------------------------------------+ + * cur_phys last_phys + * + * 0 w_size - 1 + * +-----------------------+ + * | | | window | + * +-----------------------+ + * cur_window = cur_phys - first_wind +}*/ + +static void refresh(register Vi_t* vp, int mode) +{ + register int p; + register int regb; + register int first_w = vp->first_wind; + int p_differ; + int new_lw; + int ncur_phys; + int opflag; /* search optimize flag */ + +# define w regb +# define v regb + + /*** find out if it's necessary to start translating at beginning ***/ + + if(lookahead>0) + { + p = previous[lookahead-1]; + if(p != ESC && p != '\n' && p != '\r') + mode = TRANSLATE; + } + v = cur_virt; + if( v<vp->ocur_virt || vp->ocur_virt==INVALID + || ( v==vp->ocur_virt + && (!is_print(virtual[v]) || !is_print(vp->o_v_char))) ) + { + opflag = 0; + p = 0; + v = 0; + } + else + { + opflag = 1; + p = vp->ocur_phys; + v = vp->ocur_virt; + if( !is_print(virtual[v]) ) + { + /*** avoid double ^'s ***/ + ++p; + ++v; + } + } + virtual[last_virt+1] = 0; + ncur_phys = ed_virt_to_phys(vp->ed,virtual,physical,cur_virt,v,p); + p = genlen(physical); + if( --p < 0 ) + last_phys = 0; + else + last_phys = p; + + /*** see if this was a translate only ***/ + + if( mode == TRANSLATE ) + return; + + /*** adjust left margin if necessary ***/ + + if( ncur_phys<first_w || ncur_phys>=(first_w + w_size) ) + { + cursor(vp,first_w); + first_w = ncur_phys - (w_size>>1); + if( first_w < 0 ) + first_w = 0; + vp->first_wind = cur_phys = first_w; + } + + /*** attempt to optimize search somewhat to find ***/ + /*** out where physical and window images differ ***/ + + if( first_w==vp->ofirst_wind && ncur_phys>=vp->ocur_phys && opflag==1 ) + { + p = vp->ocur_phys; + w = p - first_w; + } + else + { + p = first_w; + w = 0; + } + + for(; (p<=last_phys && w<=vp->last_wind); ++p, ++w) + { + if( window[w] != physical[p] ) + break; + } + p_differ = p; + + if( (p>last_phys || p>=first_w+w_size) && w>vp->last_wind + && cur_virt==vp->ocur_virt ) + { + /*** images are identical ***/ + return; + } + + /*** copy the physical image to the window image ***/ + + if( last_virt != INVALID ) + { + while( p <= last_phys && w < w_size ) + window[w++] = physical[p++]; + } + new_lw = w; + + /*** erase trailing characters if needed ***/ + + while( w <= vp->last_wind ) + window[w++] = ' '; + vp->last_wind = --w; + + p = p_differ; + + /*** move cursor to start of difference ***/ + + cursor(vp,p); + + /*** and output difference ***/ + + w = p - first_w; + while( w <= vp->last_wind ) + putchar(window[w++]); + + cur_phys = w + first_w; + vp->last_wind = --new_lw; + + if( last_phys >= w_size ) + { + if( first_w == 0 ) + vp->long_char = '>'; + else if( last_phys < (first_w+w_size) ) + vp->long_char = '<'; + else + vp->long_char = '*'; + } + else + vp->long_char = ' '; + + if( vp->long_line != vp->long_char ) + { + /*** indicate lines longer than window ***/ + while( w++ < w_size ) + { + putchar(' '); + ++cur_phys; + } + putchar(vp->long_char); + ++cur_phys; + vp->long_line = vp->long_char; + } + + vp->ocur_phys = ncur_phys; + vp->ocur_virt = cur_virt; + vp->ofirst_wind = first_w; + + if( mode==INPUT && cur_virt>INVALID ) + ++ncur_phys; + + cursor(vp,ncur_phys); + ed_flush(vp->ed); + return; +} + +/*{ REPLACE( char, increment ) + * + * Replace the cur_virt character with char. This routine attempts + * to avoid using refresh(). + * + * increment = 1, increment cur_virt after replacement. + * = 0, leave cur_virt where it is. + * +}*/ + +static void replace(register Vi_t *vp, register int c, register int increment) +{ + register int cur_window; + + if( cur_virt == INVALID ) + { + /*** can't replace invalid cursor ***/ + ed_ringbell(); + return; + } + cur_window = cur_phys - vp->first_wind; + if( vp->ocur_virt == INVALID || !is_print(c) + || !is_print(virtual[cur_virt]) + || !is_print(vp->o_v_char) +#if SHOPT_MULTIBYTE + || !iswascii(c) || mbwidth(vp->o_v_char)>1 + || !iswascii(virtual[cur_virt]) +#endif /* SHOPT_MULTIBYTE */ + || (increment && (cur_window==w_size-1) + || !is_print(virtual[cur_virt+1])) ) + { + /*** must use standard refresh routine ***/ + + cdelete(vp,1, BAD); + append(vp,c, APPEND); + if( increment && cur_virt<last_virt ) + ++cur_virt; + refresh(vp,CONTROL); + } + else + { + virtual[cur_virt] = c; + physical[cur_phys] = c; + window[cur_window] = c; + putchar(c); + if(increment) + { + c = virtual[++cur_virt]; + ++cur_phys; + } + else + { + putchar('\b'); + } + vp->o_v_char = c; + ed_flush(vp->ed); + } + return; +} + +/*{ RESTORE_V() + * + * Restore the contents of virtual space from u_space. + * +}*/ + +static void restore_v(register Vi_t *vp) +{ + register int tmpcol; + genchar tmpspace[MAXLINE]; + + if( vp->u_column == INVALID-1 ) + { + /*** never saved anything ***/ + ed_ringbell(); + return; + } + gencpy(tmpspace, vp->u_space); + tmpcol = vp->u_column; + save_v(vp); + gencpy(virtual, tmpspace); + cur_virt = tmpcol; + last_virt = genlen(tmpspace) - 1; + vp->ocur_virt = MAXCHAR; /** invalidate refresh optimization **/ + return; +} + +/*{ SAVE_LAST() + * + * If the user has typed something, save it in last line. + * +}*/ + +static void save_last(register Vi_t* vp) +{ + register int i; + + if( (i = cur_virt - first_virt + 1) > 0 ) + { + /*** save last thing user typed ***/ + if(i >= MAXLINE) + i = MAXLINE-1; + genncpy(vp->lastline, (&virtual[first_virt]), i); + vp->lastline[i] = '\0'; + } + return; +} + +/*{ SAVE_V() + * + * This routine will save the contents of virtual in u_space. + * +}*/ + +static void save_v(register Vi_t *vp) +{ + if(!inmacro) + { + virtual[last_virt + 1] = '\0'; + gencpy(vp->u_space, virtual); + vp->u_column = cur_virt; + } + return; +} + +/*{ SEARCH( mode ) + * + * Search history file for regular expression. + * + * mode = '/' require search string and search new to old + * mode = '?' require search string and search old to new + * mode = 'N' repeat last search in reverse direction + * mode = 'n' repeat last search + * +}*/ + +/* + * search for <string> in the current command + */ +static int curline_search(Vi_t *vp, const char *string) +{ + register int len=strlen(string); + register const char *dp,*cp=string, *dpmax; +#if SHOPT_MULTIBYTE + ed_external(vp->u_space,(char*)vp->u_space); +#endif /* SHOPT_MULTIBYTE */ + for(dp=(char*)vp->u_space,dpmax=dp+strlen(dp)-len; dp<=dpmax; dp++) + { + if(*dp==*cp && memcmp(cp,dp,len)==0) + return(dp-(char*)vp->u_space); + } +#if SHOPT_MULTIBYTE + ed_internal((char*)vp->u_space,vp->u_space); +#endif /* SHOPT_MULTIBYTE */ + return(-1); +} + +static int search(register Vi_t* vp,register int mode) +{ + register int new_direction; + register int oldcurhline; + register int i; + Histloc_t location; + + if( mode == '/' || mode == '?') + { + /*** new search expression ***/ + del_line(vp,BAD); + append(vp,mode, APPEND); + refresh(vp,INPUT); + first_virt = 1; + getline(vp,SEARCH); + first_virt = 0; + virtual[last_virt + 1] = '\0'; /*** make null terminated ***/ + vp->direction = mode=='/' ? -1 : 1; + } + + if( cur_virt == INVALID ) + { + /*** no operation ***/ + return(ABORT); + } + + if( cur_virt==0 || fold(mode)=='N' ) + { + /*** user wants repeat of last search ***/ + del_line(vp,BAD); + strcpy( ((char*)virtual)+1, lsearch); +#if SHOPT_MULTIBYTE + *((char*)virtual) = '/'; + ed_internal((char*)virtual,virtual); +#endif /* SHOPT_MULTIBYTE */ + } + + if( mode == 'N' ) + new_direction = -vp->direction; + else + new_direction = vp->direction; + + + /*** now search ***/ + + oldcurhline = curhline; +#if SHOPT_MULTIBYTE + ed_external(virtual,(char*)virtual); +#endif /* SHOPT_MULTIBYTE */ + if(mode=='?' && (i=curline_search(vp,((char*)virtual)+1))>=0) + { + location.hist_command = curhline; + location.hist_char = i; + } + else + { + i = INVALID; + if( new_direction==1 && curhline >= histmax ) + curhline = histmin + 1; + location = hist_find(sh.hist_ptr,((char*)virtual)+1, curhline, 1, new_direction); + } + cur_virt = i; + strncpy(lsearch, ((char*)virtual)+1, SEARCHSIZE); + if( (curhline=location.hist_command) >=0 ) + { + vp->ocur_virt = INVALID; + return(GOOD); + } + + /*** could not find matching line ***/ + + curhline = oldcurhline; + return(BAD); +} + +/*{ SYNC_CURSOR() + * + * This routine will move the physical cursor to the same + * column as the virtual cursor. + * +}*/ + +static void sync_cursor(register Vi_t *vp) +{ + register int p; + register int v; + register int c; + int new_phys; + + if( cur_virt == INVALID ) + return; + + /*** find physical col that corresponds to virtual col ***/ + + new_phys = 0; + if(vp->first_wind==vp->ofirst_wind && cur_virt>vp->ocur_virt && vp->ocur_virt!=INVALID) + { + /*** try to optimize search a little ***/ + p = vp->ocur_phys + 1; +#if SHOPT_MULTIBYTE + while(physical[p]==MARKER) + p++; +#endif /* SHOPT_MULTIBYTE */ + v = vp->ocur_virt + 1; + } + else + { + p = 0; + v = 0; + } + for(; v <= last_virt; ++p, ++v) + { +#if SHOPT_MULTIBYTE + int d; + c = virtual[v]; + if((d = mbwidth(c)) > 1) + { + if( v != cur_virt ) + p += (d-1); + } + else if(!iswprint(c)) +#else + c = virtual[v]; + if(!isprint(c)) +#endif /* SHOPT_MULTIBYTE */ + { + if( c == '\t' ) + { + p -= ((p+editb.e_plen)%TABSIZE); + p += (TABSIZE-1); + } + else + { + ++p; + } + } + if( v == cur_virt ) + { + new_phys = p; + break; + } + } + + if( new_phys < vp->first_wind || new_phys >= vp->first_wind + w_size ) + { + /*** asked to move outside of window ***/ + + window[0] = '\0'; + refresh(vp,CONTROL); + return; + } + + cursor(vp,new_phys); + ed_flush(vp->ed); + vp->ocur_phys = cur_phys; + vp->ocur_virt = cur_virt; + vp->o_v_char = virtual[vp->ocur_virt]; + + return; +} + +/*{ TEXTMOD( command, mode ) + * + * Modify text operations. + * + * mode != 0, repeat previous operation + * +}*/ + +static int textmod(register Vi_t *vp,register int c, int mode) +{ + register int i; + register genchar *p = vp->lastline; + register int trepeat = vp->repeat; + genchar *savep; + + if(mode && (fold(vp->lastmotion)=='F' || fold(vp->lastmotion)=='T')) + vp->lastmotion = ';'; + + if( fold(c) == 'P' ) + { + /*** change p from lastline to yankbuf ***/ + p = yankbuf; + } + +addin: + switch( c ) + { + /***** Input commands *****/ + +#if KSHELL + case '\t': + if(vp->ed->e_tabcount!=1) + return(BAD); + c = '='; + case '*': /** do file name expansion in place **/ + case '\\': /** do file name completion in place **/ + if( cur_virt == INVALID ) + return(BAD); + case '=': /** list file name expansions **/ + save_v(vp); + i = last_virt; + ++last_virt; + mode = cur_virt-1; + virtual[last_virt] = 0; + if(ed_expand(vp->ed,(char*)virtual, &cur_virt, &last_virt, c, vp->repeat_set?vp->repeat:-1)<0) + { + if(vp->ed->e_tabcount) + { + vp->ed->e_tabcount=2; + ed_ungetchar(vp->ed,'\t'); + --last_virt; + return(APPEND); + } + last_virt = i; + ed_ringbell(); + } + else if(c == '=' && !vp->repeat_set) + { + last_virt = i; + vp->nonewline++; + ed_ungetchar(vp->ed,cntl('L')); + return(GOOD); + } + else + { + --cur_virt; + --last_virt; + vp->ocur_virt = MAXCHAR; + if(c=='=' || (mode<cur_virt && (virtual[cur_virt]==' ' || virtual[cur_virt]=='/'))) + vp->ed->e_tabcount = 0; + return(APPEND); + } + break; + + case '@': /** macro expansion **/ + if( mode ) + c = vp->lastmacro; + else + if((c=getrchar(vp))==ESC) + return(GOOD); + if(!inmacro) + vp->lastmacro = c; + if(ed_macro(vp->ed,c)) + { + save_v(vp); + inmacro++; + return(GOOD); + } + ed_ringbell(); + return(BAD); + +#endif /* KSHELL */ + case '_': /** append last argument of prev command **/ + save_v(vp); + { + genchar tmpbuf[MAXLINE]; + if(vp->repeat_set==0) + vp->repeat = -1; + p = (genchar*)hist_word((char*)tmpbuf,MAXLINE,vp->repeat); +#if !KSHELL + if(p==0) + { + ed_ringbell(); + break; + } +#endif /* KSHELL */ +#if SHOPT_MULTIBYTE + ed_internal((char*)p,tmpbuf); + p = tmpbuf; +#endif /* SHOPT_MULTIBYTE */ + i = ' '; + do + { + append(vp,i,APPEND); + } + while(i = *p++); + return(APPEND); + } + + case 'A': /** append to end of line **/ + cur_virt = last_virt; + sync_cursor(vp); + + case 'a': /** append **/ + if( fold(mode) == 'A' ) + { + c = 'p'; + goto addin; + } + save_v(vp); + if( cur_virt != INVALID ) + { + first_virt = cur_virt + 1; + cursor(vp,cur_phys + 1); + ed_flush(vp->ed); + } + return(APPEND); + + case 'I': /** insert at beginning of line **/ + cur_virt = first_virt; + sync_cursor(vp); + + case 'i': /** insert **/ + if( fold(mode) == 'I' ) + { + c = 'P'; + goto addin; + } + save_v(vp); + if( cur_virt != INVALID ) + { + vp->o_v_char = virtual[cur_virt]; + first_virt = cur_virt--; + } + return(INSERT); + + case 'C': /** change to eol **/ + c = '$'; + goto chgeol; + + case 'c': /** change **/ + if( mode ) + c = vp->lastmotion; + else + c = getcount(vp,ed_getchar(vp->ed,-1)); +chgeol: + vp->lastmotion = c; + if( c == 'c' ) + { + del_line(vp,GOOD); + return(APPEND); + } + + if(!delmotion(vp, c, 'c')) + return(BAD); + + if( mode == 'c' ) + { + c = 'p'; + trepeat = 1; + goto addin; + } + first_virt = cur_virt + 1; + return(APPEND); + + case 'D': /** delete to eol **/ + c = '$'; + goto deleol; + + case 'd': /** delete **/ + if( mode ) + c = vp->lastmotion; + else + c = getcount(vp,ed_getchar(vp->ed,-1)); +deleol: + vp->lastmotion = c; + if( c == 'd' ) + { + del_line(vp,GOOD); + break; + } + if(!delmotion(vp, c, 'd')) + return(BAD); + if( cur_virt < last_virt ) + ++cur_virt; + break; + + case 'P': + if( p[0] == '\0' ) + return(BAD); + if( cur_virt != INVALID ) + { + i = virtual[cur_virt]; + if(!is_print(i)) + vp->ocur_virt = INVALID; + --cur_virt; + } + + case 'p': /** print **/ + if( p[0] == '\0' ) + return(BAD); + + if( mode != 's' && mode != 'c' ) + { + save_v(vp); + if( c == 'P' ) + { + /*** fix stored cur_virt ***/ + ++vp->u_column; + } + } + if( mode == 'R' ) + mode = REPLACE; + else + mode = APPEND; + savep = p; + for(i=0; i<trepeat; ++i) + { + while(c= *p++) + append(vp,c,mode); + p = savep; + } + break; + + case 'R': /* Replace many chars **/ + if( mode == 'R' ) + { + c = 'P'; + goto addin; + } + save_v(vp); + if( cur_virt != INVALID ) + first_virt = cur_virt; + return(REPLACE); + + case 'r': /** replace **/ + if( mode ) + c = *p; + else + if((c=getrchar(vp))==ESC) + return(GOOD); + *p = c; + save_v(vp); + while(trepeat--) + replace(vp,c, trepeat!=0); + return(GOOD); + + case 'S': /** Substitute line - cc **/ + c = 'c'; + goto chgeol; + + case 's': /** substitute **/ + save_v(vp); + cdelete(vp,vp->repeat, BAD); + if( mode ) + { + c = 'p'; + trepeat = 1; + goto addin; + } + first_virt = cur_virt + 1; + return(APPEND); + + case 'Y': /** Yank to end of line **/ + c = '$'; + goto yankeol; + + case 'y': /** yank thru motion **/ + if( mode ) + c = vp->lastmotion; + else + c = getcount(vp,ed_getchar(vp->ed,-1)); +yankeol: + vp->lastmotion = c; + if( c == 'y' ) + { + gencpy(yankbuf, virtual); + } + else if(!delmotion(vp, c, 'y')) + { + return(BAD); + } + break; + + case 'x': /** delete repeat chars forward - dl **/ + c = 'l'; + goto deleol; + + case 'X': /** delete repeat chars backward - dh **/ + c = 'h'; + goto deleol; + + case '~': /** invert case and advance **/ + if( cur_virt != INVALID ) + { + save_v(vp); + i = INVALID; + while(trepeat-->0 && i!=cur_virt) + { + i = cur_virt; + c = virtual[cur_virt]; +#if SHOPT_MULTIBYTE + if((c&~STRIP)==0) +#endif /* SHOPT_MULTIBYTE */ + if( isupper(c) ) + c = tolower(c); + else if( islower(c) ) + c = toupper(c); + replace(vp,c, 1); + } + return(GOOD); + } + else + return(BAD); + + default: + return(BAD); + } + refresh(vp,CONTROL); + return(GOOD); +} + + +#if SHOPT_MULTIBYTE + static int _isalph(register int v) + { +#ifdef _lib_iswalnum + return(iswalnum(v) || v=='_'); +#else + return((v&~STRIP) || isalnum(v) || v=='_'); +#endif + } + + + static int _isblank(register int v) + { + return((v&~STRIP)==0 && isspace(v)); + } + + static int _ismetach(register int v) + { + return((v&~STRIP)==0 && ismeta(v)); + } + +#endif /* SHOPT_MULTIBYTE */ + +/* + * get a character, after ^V processing + */ +static int getrchar(register Vi_t *vp) +{ + register int c; + if((c=ed_getchar(vp->ed,1))== usrlnext) + c = ed_getchar(vp->ed,2); + return(c); +} diff --git a/usr/src/lib/libshell/common/features/cmds b/usr/src/lib/libshell/common/features/cmds new file mode 100644 index 0000000000..106198caa1 --- /dev/null +++ b/usr/src/lib/libshell/common/features/cmds @@ -0,0 +1 @@ +cmd newgrp,test,id,wc,cut,logname,universe,pfexec,tput diff --git a/usr/src/lib/libshell/common/features/dynamic b/usr/src/lib/libshell/common/features/dynamic new file mode 100644 index 0000000000..1835d41282 --- /dev/null +++ b/usr/src/lib/libshell/common/features/dynamic @@ -0,0 +1,12 @@ +hdr,sys dlfcn,dl,ldr,dll +hdr dlldefs +lib dlopen,shl_load,loadbind,dllload,dllfind +cat{ + #if !defined(SHOPT_FS_3D) && ( _lib_dllfind || _lib_dlopen || _lib_shl_load || _lib_loadbind ) + # define SHOPT_FS_3D 1 + #endif /* !SHOPT_FS_3D */ + #if SHOPT_FS_3D + # undef mount + # include <fs3d.h> + #endif /* SHOPT_FS_3D */ +}end diff --git a/usr/src/lib/libshell/common/features/externs b/usr/src/lib/libshell/common/features/externs new file mode 100644 index 0000000000..58701306be --- /dev/null +++ b/usr/src/lib/libshell/common/features/externs @@ -0,0 +1,12 @@ +set prototyped +hdr nc,exec_attr +mem exception.name,_exception.name math.h +lib setreuid,setregid,nice,sigflag,fork,spawnveg +lib pathnative,pathposix,uwin_path,uwin_unpath,fts_notify +lib fchdir + +reference unistd.h + +extern nice int (int) +extern setreuid int (uid_t,uid_t) +extern setregid int (gid_t,gid_t) diff --git a/usr/src/lib/libshell/common/features/locale b/usr/src/lib/libshell/common/features/locale new file mode 100644 index 0000000000..ee9bcae6fe --- /dev/null +++ b/usr/src/lib/libshell/common/features/locale @@ -0,0 +1,25 @@ +hdr locale,wchar +lib locale,localeconv,wctype,iswctype,iswblank +cat{ + #if _PACKAGE_ast + # undef _hdr_locale + # define _hdr_locale 1 + #else + # ifdef _hdr_locale + # include <locale.h> + # ifndef LC_MESSAGES + # define LC_MESSAGES LC_ALL + # endif /* LC_MESSAGES */ + # endif /* _hdr_locale */ + #endif /* _PACKAGE_ast */ + #ifdef _hdr_locale + # ifdef _lib_localeconv + static struct lconv *lp; + # define GETDECIMAL(x) (((lp=localeconv()) && lp->decimal_point && *lp->decimal_point) ? *lp->decimal_point : '.' ) + # else + # define GETDECIMAL(x) ('.') + # endif /* _lib_localeconv */ + #else + # define GETDECIMAL(x) ('.') + #endif /* _hdr_locale */ +}end diff --git a/usr/src/lib/libshell/common/features/math b/usr/src/lib/libshell/common/features/math new file mode 100644 index 0000000000..2a720e673c --- /dev/null +++ b/usr/src/lib/libshell/common/features/math @@ -0,0 +1,4 @@ +lib,npt absl,acosl,acoshl,asinl,asinhl,atanl,atan2l,expl,cosl,coshl math.h +lib,npt fabsl,powl,hypotl,fmodl,logl,sinl,sinhl,sqrtl,tanl,tanhl math.h +lib,npt strtold,isinf,isinfl,isnan,isnanl,isnormal,isnormall math.h +lib,npt finite,finitel,isfinite math.h diff --git a/usr/src/lib/libshell/common/features/math.sh b/usr/src/lib/libshell/common/features/math.sh new file mode 100644 index 0000000000..1e407e57f7 --- /dev/null +++ b/usr/src/lib/libshell/common/features/math.sh @@ -0,0 +1,158 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +: generate the ksh math builtin table +: include math.tab + +# @(#)math.sh (AT&T Research) 2007-02-02 + +command=$0 +iffeflags="-n -v -F ast_standards.h" +iffehdrs="math.h ieeefp.h" +iffelibs="-lm" +table=/dev/null + +eval $1 +shift +table=$1 + +names= +tests= + +: read the table + +exec < $table +while read type args name aka comment +do case $type in + [fi]) names="$names $name" + tests="$tests,$name,${name}l" + eval TYPE_$name=$type ARGS_$name=$args AKA_$name=$aka + ;; + esac +done + +: check the math library + +eval `iffe $iffeflags -c "$cc" - typ long.double : lib $tests $iffehdrs $iffelibs 2>&$stderr` +lib= +for name in $names +do eval x='$'_lib_${name}l y='$'_lib_${name} + case $x in + 1) lib="$lib,${name}l" ;; + esac + case $y in + 1) case $x in + '') lib="$lib,${name}" ;; + esac + ;; + esac +done +eval `iffe $iffeflags -c "$cc" - dat,npt,mac $lib $iffehdrs $iffelibs 2>&$stderr` + +cat <<! +#pragma prototyped + +/* : : generated by $command from $table : : */ + +typedef Sfdouble_t (*Math_f)(Sfdouble_t,...); + +! +echo "#include <ast_standards.h>" +echo "#include <math.h>" +case $_hdr_ieeefp in +1) echo "#include <ieeefp.h>" + echo + ;; +esac + +: generate the intercept functions and table entries + +nl=' +' +ht=' ' +tab= +for name in $names +do eval x='$'_lib_${name}l y='$'_lib_${name} r='$'TYPE_${name} a='$'ARGS_${name} aka='$'AKA_${name} + case $x:$y in + 1:*) f=${name}l + t=Sfdouble_t + local= + ;; + *:1) f=${name} + t=double + local=$_typ_long_double + ;; + *) continue + ;; + esac + eval n='$'_npt_$f m='$'_mac_$f d='$'_dat_$f + case $r in + i) L=int r=int R=1 ;; + *) L=Sfdouble_t r=$t R=0 ;; + esac + case $d:$m:$n in + 1:*:*|*:1:*) + ;; + *:*:1) code="extern $r $f(" + sep= + for p in 1 2 3 4 5 6 7 + do code="$code${sep}$t" + case $a in + $p) break ;; + esac + sep="," + done + code="$code);" + echo "$code" + ;; + esac + case $local:$m:$n:$d in + 1:*:*:*|*:1:*:*|*:*::) + args= + code="static $L local_$f(" + sep= + for p in 1 2 3 4 5 6 7 8 9 + do args="$args${sep}a$p" + code="$code${sep}Sfdouble_t a$p" + case $a in + $p) break ;; + esac + sep="," + done + code="$code){return $f($args);}" + echo "$code" + f=local_$f + ;; + esac + for x in $name $aka + do tab="$tab$nl$ht\"\\0${R}${a}${x}\",$ht(Math_f)$f," + done +done +tab="$tab$nl$ht\"\",$ht$ht(Math_f)0" + +cat <<! + +/* + * first byte is two-digit octal number. Last digit is number of args + * first digit is 0 if return value is double, 1 for integer + */ +const struct mathtab shtab_math[] = +{$tab +}; +! diff --git a/usr/src/lib/libshell/common/features/options b/usr/src/lib/libshell/common/features/options new file mode 100644 index 0000000000..9c713d150e --- /dev/null +++ b/usr/src/lib/libshell/common/features/options @@ -0,0 +1,46 @@ +# SHOPT_* option probe + +tst cross{ + : check for shell magic #! + cat > /tmp/file$$ <<! + #! /bin/echo + exit 1 + ! + chmod 755 /tmp/file$$ + if /tmp/file$$ > /dev/null + then echo "#define SHELLMAGIC 1" + fi + rm -f /tmp/file$$ + + option() # name value + { + case $2 in + 0) echo "#ifndef SHOPT_$1" + echo "# define SHOPT_$1 1" + echo "#endif" + ;; + *) echo "#undef SHOPT_$1" + ;; + esac + } + + test -d /dev/fd + option DEVFD $? + case `echo a | tr a '\012' | wc -l` in + *1*) option MULTIBYTE 0 ;; + esac + test -x /bin/pfexec -o -x /usr/bin/pfexec + option PFSH $? + /bin/test ! -l . 2> /dev/null + option TEST_L $? + test -f /etc/ksh.kshrc -o -f /etc/bash.bashrc && + option SYSRC 0 + test -f /bin/universe && univ=`/bin/universe` > /dev/null 2>&1 -a ucb = "$univ" + option UCB $? +}end + +cat{ + #if !_PACKAGE_ast && ( (MB_LEN_MAX-1)<=0 || !defined(_lib_mbtowc) ) + # undef SHOPT_MULTIBYTE + #endif +}end diff --git a/usr/src/lib/libshell/common/features/poll b/usr/src/lib/libshell/common/features/poll new file mode 100644 index 0000000000..7211a47806 --- /dev/null +++ b/usr/src/lib/libshell/common/features/poll @@ -0,0 +1,149 @@ +hdr,sys poll,socket,netinet/in +lib select,poll,socket +lib htons,htonl sys/types.h sys/socket.h netinet/in.h +lib getaddrinfo sys/types.h sys/socket.h netdb.h +typ fd_set sys/socket.h sys/select.h +tst pipe_socketpair note{ use socketpair() for peekable pipe() }end execute{ + #include <ast.h> + #include <signal.h> + #include <sys/types.h> + #include <sys/socket.h> + static void handler(sig) + int sig; + { + _exit(0); + } + int main() + { + int n; + int pfd[2]; + int sfd[2]; + char buf[256]; + pid_t pid; + static char msg[] = "hello world\n"; + close(0); + if (pipe(pfd) < 0 || + socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0 || + shutdown(sfd[0], 1) < 0 || + shutdown(sfd[1], 0) < 0) + return(1); + if ((pid = fork()) < 0) + return(1); + if (pid) + { + close(pfd[1]); + close(sfd[1]); + wait(&n); + if (sfpkrd(pfd[0], buf, sizeof(buf), '\n', -1, 1) >= 0 || + sfpkrd(sfd[0], buf, sizeof(buf), '\n', -1, 1) < 0) + return(1); + } + else + { + close(pfd[0]); + close(sfd[0]); + write(pfd[1], msg, sizeof(msg) - 1); + write(sfd[1], msg, sizeof(msg) - 1); + return(0); + } + close(pfd[0]); + close(sfd[0]); + signal(SIGPIPE, handler); + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0 || + shutdown(sfd[0], 1) < 0 || + shutdown(sfd[1], 0) < 0) + return(1); + close(sfd[0]); + write(sfd[1], msg, sizeof(msg) - 1); + return(1); + } +}end +tst socketpair_devfd note{ /dev/fd/N handles socketpair() }end execute{ + #include <ast.h> + #include <fs3d.h> + #include <sys/types.h> + #include <sys/socket.h> + int main() + { + int devfd; + int n; + int sfd[2]; + fs3d(FS3D_OFF); + close(0); + open("/dev/null", O_RDONLY); + if ((n = open("/dev/fd/0", O_RDONLY)) < 0) + return(1); + close(n); + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0 || + shutdown(sfd[0], 1) < 0 || + shutdown(sfd[1], 0) < 0) + return(1); + close(0); + dup(sfd[0]); + close(sfd[0]); + if ((n = open("/dev/fd/0", O_RDONLY)) < 0) + return(1); + return(0); + } +}end +tst socketpair_shutdown_mode note{ fchmod() after socketpair() shutdown() }end execute{ + #include <ast.h> + #include <sys/types.h> + #include <sys/stat.h> + #include <sys/socket.h> + int main() + { + int sfd[2]; + struct stat st0; + struct stat st1; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0 || + shutdown(sfd[0], 1) < 0 || + shutdown(sfd[1], 0) < 0) + return(1); + if (fstat(sfd[0], &st0) < 0 || fstat(sfd[1], &st1) < 0) + return(1); + if ((st0.st_mode & (S_IRUSR|S_IWUSR)) == S_IRUSR && + (st1.st_mode & (S_IRUSR|S_IWUSR)) == S_IWUSR) + return(1); + if (fchmod(sfd[0], S_IRUSR) < 0 || + fstat(sfd[0], &st0) < 0 || + (st0.st_mode & (S_IRUSR|S_IWUSR)) != S_IRUSR) + return(1); + if (fchmod(sfd[1], S_IWUSR) < 0 || + fstat(sfd[1], &st1) < 0 || + (st1.st_mode & (S_IRUSR|S_IWUSR)) != S_IWUSR) + return(1); + return(0); + } +}end +cat{ + #pragma prototyped + #ifdef _lib_poll + # define poll _SYS_poll + #else + # undef _hdr_poll + # undef _sys_poll + #endif /* _lib_poll */ + #ifdef _hdr_poll + # include <poll.h> + #else + # ifdef _sys_poll + # include <sys/poll.h> + # endif /* _sys_poll */ + #endif /* _hdr_poll */ + #ifdef _lib_poll + # undef poll + extern int poll(struct pollfd*,unsigned long,int); + #endif /* _lib_poll */ + #ifdef _lib_select + # ifndef FD_ZERO + # define FD_ZERO(x) (*(x)=0) + # endif /* FD_ZERO */ + # ifndef FD_SET + # define FD_SET(n,x) (*(x)|=(1L<<(n))) + # endif /* FD_SET */ + # ifndef _typ_fd_set + typedef long fd_set; + # endif /*_typ_fd_set */ + #endif /* _lib_select */ +}end diff --git a/usr/src/lib/libshell/common/features/rlimits b/usr/src/lib/libshell/common/features/rlimits new file mode 100644 index 0000000000..a9e4294ed1 --- /dev/null +++ b/usr/src/lib/libshell/common/features/rlimits @@ -0,0 +1,3 @@ +hdr,sys resource,vlimit +lib getrlimit,ulimit,vlimit +typ rlim_t sys/resource.h diff --git a/usr/src/lib/libshell/common/features/setjmp b/usr/src/lib/libshell/common/features/setjmp new file mode 100644 index 0000000000..a3504ee3e2 --- /dev/null +++ b/usr/src/lib/libshell/common/features/setjmp @@ -0,0 +1,18 @@ +lib sigsetjmp,_setjmp,_longjmp +typ sigjmp_buf setjmp.h +cat{ + #undef sigsetjmp + #undef siglongjmp + #undef sigjmp_buf + #define sigjmp_buf jmp_buf + #ifdef _lib__setjmp + # define sigsetjmp(a,b) _setjmp(a) + #else + # define sigsetjmp(a,b) setjmp(a) + #endif /* _lib__setjmp */ + #ifdef _lib__longjmp + # define siglongjmp(a,b) _longjmp(a,b) + #else + # define siglongjmp(a,b) longjmp(a,b) + #endif /* _lib__longjmp */ +}end diff --git a/usr/src/lib/libshell/common/features/sigfeatures b/usr/src/lib/libshell/common/features/sigfeatures new file mode 100644 index 0000000000..1999dc11bb --- /dev/null +++ b/usr/src/lib/libshell/common/features/sigfeatures @@ -0,0 +1,49 @@ +lib sigblock,sigrelse,sigsetmask,sigprocmask,sigvec +typ sigset_t ast.h signal.h +mem sigvec.sv_mask signal.h +cat{ + #ifndef _mem_sigvec_sv_mask + # undef _lib_sigvec + #endif + #ifdef _lib_sigprocmask + # define sh_sigaction(s,action) do { sigset_t ss;\ + sigemptyset(&ss);\ + sigaddset(&ss,(s));\ + sigprocmask(action,&ss,0); \ + }while(0) + # define sigrelease(s) sh_sigaction(s,SIG_UNBLOCK) + # define sigblock(s) sh_sigaction(s,SIG_BLOCK) + # define sig_begin() sh_sigaction(0,SIG_SETMASK) + #else + # ifndef _lib_sigblock + # define sigblock(s) + # endif + # ifdef _lib_sigsetmask + # define sigrelease(s) sigsetmask(0) + # define sig_begin() sigsetmask(0) + # else + # ifdef _lib_sigrelse + # define sigrelease sigrelse + # define sig_begin() + # else + # define sig_begin() (0) + # define sigrelease(s) (0) + # endif /* _lib_sigrelse */ + # endif /* _lib_sigsetmask */ + #endif /* _lib_sigprocmask */ +}end +tst output{ + #include <signal.h> + int main() + { + #ifdef SIGRTMIN + printf("#undef _SIGRTMIN\n"); + printf("#define _SIGRTMIN %d\n", SIGRTMIN); + #endif + #ifdef SIGRTMAX + printf("#undef _SIGRTMAX\n"); + printf("#define _SIGRTMAX %d\n", SIGRTMAX); + #endif + return 0; + } +}end diff --git a/usr/src/lib/libshell/common/features/time b/usr/src/lib/libshell/common/features/time new file mode 100644 index 0000000000..f8d2129841 --- /dev/null +++ b/usr/src/lib/libshell/common/features/time @@ -0,0 +1,35 @@ +hdr utime +lib gettimeofday,setitimer +mem timeval.tv_usec sys/time.h +tst lib_2_timeofday note{ 2 arg gettimeofday() }end link{ + #include <sys/types.h> + #include <sys/time.h> + int main() + { + struct timeval tv; + struct timezone tz; + return gettimeofday(&tv, &tz); + } +}end +tst lib_1_timeofday note{ 1 arg gettimeofday() }end link{ + #include <sys/types.h> + #include <sys/time.h> + int main() + { + struct timeval tv; + return gettimeofday(&tv); + } +}end +cat{ + #undef _def_time + #include <times.h> + #define _def_time 1 + #undef timeofday + #if _lib_2_timeofday + #define timeofday(p) gettimeofday(p,(struct timezone*)0) + #else + #if _lib_1_timeofday + #define timeofday(p) gettimeofday(p) + #endif + #endif +}end diff --git a/usr/src/lib/libshell/common/features/ttys b/usr/src/lib/libshell/common/features/ttys new file mode 100644 index 0000000000..693dbd1be1 --- /dev/null +++ b/usr/src/lib/libshell/common/features/ttys @@ -0,0 +1,3 @@ +hdr termios,termio,sgtty +sys termios,termio,ioctl,bsdtty,nttyio,filio +lib tcgetattr,tcgetpgrp termios.h diff --git a/usr/src/lib/libshell/common/fun/dirs b/usr/src/lib/libshell/common/fun/dirs new file mode 100644 index 0000000000..0329970f3b --- /dev/null +++ b/usr/src/lib/libshell/common/fun/dirs @@ -0,0 +1,108 @@ +# +# DIRECTORY MANIPULATION FUNCTIONS, REPLACES CD +# +# Uses global parameters _push_max _push_top _push_stack +integer _push_max=${CDSTACK-32} _push_top=${CDSTACK-32} +unalias cd +alias cd=_cd +# Display directory stack -- $HOME displayed as ~ +function dirs +{ + typeset dir="${PWD#$HOME/}" + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + PS3= + select i in "$dir" "${_push_stack[@]}" + do : + done < /dev/null +} + +# Change directory and put directory on front of stack +function _cd +{ + typeset dir= + integer n=0 type=4 + case $1 in + -|-1|2) # \cd - + n=_push_top type=1 + ;; + -[1-9]*([0-9])) # \cd -n + n=_push_top+${1#-}-1 type=2 + ;; + 1) # keep present directory + print -r - "$PWD" + return + ;; + [1-9]*([0-9])) # \cd n + n=_push_top+${1}-2 type=2 + ;; + *) if ((_push_top <= 0)) + then type=3 n=_push_max + fi + esac + if ((type<3)) + then if ((n >= _push_max+1)) + then print -u2 cd: Directory stack not that deep. + return 1 + else dir=${_push_stack[n]} + fi + fi + case $dir in + \~*) dir=$HOME${dir#\~} + esac + \cd "${dir:-$@}" >| /dev/null || return 1 + dir=${OLDPWD#$HOME/} + case $TERM in + 630) + print "\033[?${#PWD};2v$PWD\c" + ;; + esac + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + case $type in + 1) # swap first two elements + _push_stack[_push_top]=$dir + ;; + 2|3) # put $dir on top and shift down by one until top + integer i=_push_top + for dir in "$dir" "${_push_stack[@]}" + do ((i > n)) && break + _push_stack[i]=$dir + i=i+1 + done + ;; + 4) # push name + _push_stack[_push_top=_push_top-1]=$dir + ;; + esac + print -r - "$PWD" +} + +# Menu driven change directory command +function mcd +{ + typeset dir="${PWD#$HOME/}" + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + PS3='Select by number or enter a name: ' + select dir in "$dir" "${_push_stack[@]}" + do if _cd $REPLY + then return + fi + done +} diff --git a/usr/src/lib/libshell/common/fun/gnaw b/usr/src/lib/libshell/common/fun/gnaw new file mode 100644 index 0000000000..159d420207 --- /dev/null +++ b/usr/src/lib/libshell/common/fun/gnaw @@ -0,0 +1,1029 @@ +#!/bin/ksh93 + +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +# +# gnaw - a simple ksh93 technology demo +# +# Note that this script has been written with the main idea to show +# many of ksh93's new features (comparing to ksh88/bash) and not +# as an example of efficient&&clean script code. +# + +# Solaris needs /usr/xpg4/bin/ because the tools in /usr/bin are not POSIX-conformant +export PATH=/usr/xpg4/bin:/bin:/usr/bin + +function print_setcursorpos +{ + print -n "${vtcode[cup_${1}_${2}]}" +} + +function beep +{ + ${quiet} || print -n "${vtcode["bel"]}" +} + +function fatal_error +{ + print -u 2 "${progname}: $@" + exit 1 +} + +function print_levelmap +{ + integer screen_y_offset=$1 + integer start_y_pos=$2 # start at this line in the map + integer max_numlines=$3 # maximum lines we're allowed to render + integer x + integer y + line="" + + print_setcursorpos 0 ${screen_y_offset} + + for (( y=start_y_pos; (y-start_y_pos) < max_numlines && y < levelmap["max_y"] ; y++ )) ; do + line="" + for (( x=0 ; x < levelmap["max_x"] ; x++ )) ; do + line+="${levelmap["${x}_${y}"]}" + done + + print "${line} " + done + + # print lines filled with spaces for each line not filled + # by the level map + line="${vtcode["spaceline"]:0:${levelmap["max_x"]}}" + for (( ; (y-start_y_pos) < max_numlines ; y++ )) ; do + print "${line} " + done +} + +function level_completed +{ + render_buffer="$( + print -n "${vtcode["clear"]}" + cat <<ENDOFTEXT + + # ###### # # ###### # + # # # # # # + # ##### # # ##### # + # # # # # # + # # # # # # + ###### ###### ## ###### ###### + + (Good job) + + ##### #### # # ###### + # # # # ## # # + # # # # # # # ##### + # # # # # # # # + # # # # # ## # + ##### #### # # ###### + + +ENDOFTEXT + + printf " SCORE: --> %s <--\n" "${player["score"]}" + printf " LIVES: --> %s <--\n" "${player["lives"]}" + )" + print "${render_buffer}" + + # wait five seconds and swallow any user input + for (( i=0 ; i < 50 ; i++ )) ; do + read -t 0.1 -n 1 dummy + done + + print "Press any key to continue..." + # wait five secs or for a key + read -t 5 -n 1 dummy +} + +function game_over +{ + render_buffer="$( + print -n "${vtcode["clear"]}" + cat <<ENDOFTEXT + + #### ## # # ###### + # # # # ## ## # + # # # # ## # ##### + # ### ###### # # # + # # # # # # # + #### # # # # ###### + + (LOSER!) + + #### # # ###### ##### + # # # # # # # + # # # # ##### # # + # # # # # ##### + # # # # # # # + #### ## ###### # # + +ENDOFTEXT + + printf "\n SCORE: --> %s <--\n" "${player["score"]}" + )" + print "${render_buffer}" + + # wait five seconds and swallow any user input + for (( i=0 ; i < 50 ; i++ )) ; do + read -t 0.1 -n 1 dummy + done + + print "Press any key to continue..." + # wait five secs or for a key + read -t 5 -n 1 dummy +} + +function run_logo +{ + render_buffer="$( + cat <<ENDOFTEXT + + ##### # # # # # ### +# # ## # # # # # # ### +# # # # # # # # # ### +# #### # # # # # # # # # +# # # # # ####### # # # +# # # ## # # # # # ### + ##### # # # # ## ## ### +ENDOFTEXT + )" + print "${vtcode["clear"]}${render_buffer}" + + # wait two seconds and swallow any user input + for (( i=0 ; i < 20 ; i++ )) ; do + read -t 0.1 -n 1 dummy + done + + print "\n (The KornShell 93 maze game)" + + attract_mode +} + +function attract_mode +{ +( + # Now present some info, line-by-line in an endless loop + # until the user presses a key (we turn the "magic" return + # code for that) + magic_return_code=69 + IFS="|" ; # Make sure we do not swallow whitespaces + while true ; do + ( + exec 5<&0 + + (cat <<ENDOFTEXT + + + + + + ################ + ######################## + ############################ + ####### ###### ####### + ###### ###### ######## + ####### ###### ####### + ############################## + ############################## + ############################## + ############################## + ############################## + ######### ######## ######### + # #### #### #### # + + + + + + + Written by + + Roland Mainz + (roland.mainz@nrubsig.org) + + + + + + + ############## + ######################## + #################**############ + ################################ + ############################ + ###################### + ################ + ###################### + ############################ + ################################ + ############################## + ######################## + ############## + + + + + + + + High scores: + + * 'chin' 8200 pt + * 'gisburn' 7900 pt + * 'tpenta' 5520 pt + * 'kupfer' 5510 pt + * 'noname' 5000 pt + * 'noname' 4000 pt + * 'livad' 3120 pt + * 'noname' 3000 pt + * 'noname' 2000 pt + * 'noname' 1000 pt + +ENDOFTEXT + + # clear screen, line-by-line + for (( i=0 ; i < LINES ; i++ )) ; do print "" ; done + ) | (while read line ; do + read -t 0.3 -n 1 c <&5 + [ "$c" != "" ] && exit ${magic_return_code} + print "${line}" + done) + [ $? -eq ${magic_return_code} ] && exit ${magic_return_code} + ) + [ $? -eq ${magic_return_code} ] && return 0 + + sleep 2 + done +) +} + +function run_menu +{ + integer numlevels=0 + integer selected_level=0 + + # built list of available levels based on the "function levelmap_.*" + # built into this script + typeset -f | egrep "^function.*levelmap_.*" | sed 's/^function //' | + while read l ; do + levellist[numlevels]="$l" + numlevels+=1 + done + + # swallow any queued user input (e.g. drain stdin) + read -t 0.1 -n 100 dummy + + while true ; do + # menu loop with timeout (which switches to "attract mode") + while true ; do + print -n "${vtcode["clear"]}" + + cat <<ENDOFTEXT +>======================================\ +> /-\ .--. | +> | OO| / _.-' .-. .-. .-. .-. | +> | | \ '-. '-' '-' '-' '-' | +> ^^^^^ '--' | +>======\ /================\ .-. | +> | | | '-' | + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +ENDOFTEXT + print " GNAW - the ksh93 maze game" + print "\n\tMenu:" + + print "\t - [L]evels:" + for (( i=0 ; i < numlevels ; i++ )) ; do + printf "\t %s %s \n" "$([ $i -eq $selected_level ] && print -n "*" || print -n " ")" "${levellist[i]##levelmap_}" + done + + print "\t - Rendering options:" + printf "\t [%s] Use [U]nicode\n" "$([ $game_use_unicode -eq 1 ] && print -n "x" || print -n "_")" + printf "\t [%s] Use [C]olors\n" "$([ $game_use_colors -eq 1 ] && print -n "x" || print -n "_")" + + print "\t - [S]tart - [Q]uit" + + # wait 30 secs (before we switch to "attract mode") + c="" ; read -t 30 -n 1 c + case "$c" in + 'l') selected_level=$(((selected_level+numlevels+1) % numlevels)) ;; + 'L') selected_level=$(((selected_level+numlevels-1) % numlevels)) ;; + ~(Ei)s) + [ ${game_use_colors} -eq 1 ] && print "${vtcode["bg_black"]}" + case "${game_use_colors}${game_use_unicode}" in + "00") main_loop "${levellist[selected_level]}" ;; + "01") main_loop "${levellist[selected_level]}" | map_filter 0 1 ;; + "10") main_loop "${levellist[selected_level]}" | map_filter 1 0 ;; + "11") main_loop "${levellist[selected_level]}" | map_filter 1 1 ;; + esac + print "${vtcode["vtreset"]}" + ;; + ~(Ei)q|$'\E') + # make sure we do not exit on a cursor key (e.g. <esc>[A,B,C,D) + read -t 0.01 -n 1 c + if [ "$c" = "[" ] ; then + # this was a cursor key sequence, just eat the 3rd charcater + read -t 0.01 -n 1 c + else + exit 0 + fi + ;; + ~(Ei)u) game_use_unicode=$(((game_use_unicode+2+1) % 2)) ;; + ~(Ei)c) game_use_colors=$(((game_use_colors+2+1) % 2)) ;; + "") break ;; # timeout, switch to attract mode + *) beep ;; + esac + done + + print -n "${vtcode["clear"]}" + attract_mode + done +} + +function levelmap_stripes +{ +cat <<ENDOFLEVEL +################################### +#....... ............... P # +#########..#################..### # +#########..#################..### # +#....... .. ..............# # +############### ################ # +############### ################ # +#............. M ..............# # +##..##################### ###### # +##..##################### ###### # +#....... ........... .......# # +######## ############ ######### # +# #### ############ ######### # +# #.................. ......# # +# ############################### # +# # +################################### +ENDOFLEVEL +} + +function levelmap_livad +{ +cat <<ENDOFLEVEL +##################################################### +# # +# ############## ############### ################ # +# #............ P ..............# # +# .#############################################.# # +# #.#.......... ............#. # +# #.#.########## ############### ############.#.# # +# #...#........ ..........#...# # +# #...#.#####################################.#.#.# # +# #...#.#...... ........#...#.# # +# #.#.#...###### #########################.#.#.#.# # +# .#.....#.... M ......#...#.#.# # +# #.#.#...####################### ########.#.#.#.# # +# #...#.#...... ........#...#.# # +# #...#.######## ############### ##########.#.#.# # +# #...#........ ..........#...# # +# #.#.#########################################.#.# # +# #.#.......... ............#. # +# .############ ############### ##############.# # +# #............ ..............# # +# ################################################# # +# # +##################################################### +ENDOFLEVEL +} + +function levelmap_classic1 +{ +cat <<ENDOFLEVEL +######################### +#.P.........#...........# +#.####.####.#.####.####.# +#.# #.# #.#.# #.# #.# +#.# #.# #.#.# #.# #.# +#.####.####.#.####.####.# +#.......................# +#.####.#.#######.#.####.# +#.# #.#.# #.#.# #.# +#.####.#.#######.#.####.# +#......#....#....#......# +######.####.#.####.###### +###### # # ###### +###### # ## ## # ###### +###### # # # # ###### +# # M # # +###### # ####### # ###### +###### # # ###### +###### # ####### # ###### +###### # # # # ###### +######.#.#######.#.###### +#...........#...........# +#.###.###...#...###.###.# +#...#...............#...# +###.#....#######....#.### +# #.#..#.# #.#..#.# # +###....#.#######.#....### +#......#....#....#......# +#.#########.#.#########.# +#.......................# +######################### +ENDOFLEVEL +} + +function levelmap_classic2 +{ +cat <<ENDOFLEVEL +####################### +#.P...#.........#.....# +#.###.#.#######.#.###.# +#.....................# +###.#.####.#.####.#.### +###.#......#......#.### +###.###.#######.###.### +###.................### +###.###.### ###.###.### +###.#...#M #...#.### +###.#.#.#######.#.#.### +#.....#.........#.....# +###.#####..#..#####.### +###........#........### +###.###.#######.###.### +#.....................# +#.###.####.#.####.###.# +#.###.#....#....#.###.# +#.###.#.#######.#.###.# +#.....................# +####################### +ENDOFLEVEL +} + +function levelmap_easy +{ +cat <<ENDOFLEVEL +################## +# .............. # +# . ###### # +# . # M # # +# . # # # +# . ### ## # +# . # # +# . ### # +# . # +# .......... # +# .......... P # +################## +ENDOFLEVEL +} + +function levelmap_sunsolaristext +{ +cat <<ENDOFLEVEL +################################################ +# .#### . # #....# # +# # # # #....# # +# #### # # #.#..# M # +# # # # #..#.# # +# # # # # #...## # +# #### #### #....# # +# # +# #### #### # ## ##### # #### # +# # #. .# # # # #....# # # # +# #### # # # # P # #....# # #### # +# # # ### #.#### #.### # # # +# # .# #. .. # # #...# # # # # +# #### #### ###### . # ....# # ####. # +################################################ +ENDOFLEVEL +} + +function read_levelmap +{ + map="$( $1 )" + + integer y=0 + integer x=0 + integer maxx=0 + integer numdots=0 + + print "$map" | + while read line ; do + x=0 + while (( x < ${#line} )) ; do + c="${line:x:1}" + + case $c in + ".") numdots+=1 ;; + "M") + levelmap["monsterstartpos_x"]="$x" + levelmap["monsterstartpos_y"]="$y" + c=" " + ;; + "P") + levelmap["playerstartpos_x"]="$x" + levelmap["playerstartpos_y"]="$y" + c=" " + ;; + esac + + levelmap["${x}_${y}"]="$c" + let x++ + done + maxx=$x + let y++ + done + + levelmap["max_x"]=${maxx} + levelmap["max_y"]=${y} + levelmap["numdots"]=${numdots} + + if [ "${levelmap["monsterstartpos_x"]}" = "" ] ; then + fatal_error "read_levelmap: monsterstartpos_x is empty." + fi + if [ "${levelmap["playerstartpos_x"]}" = "" ] ; then + fatal_error "read_levelmap: playerstartpos_x is empty." + fi + + return 0 +} + +function player.set +{ + case "${.sh.subscript}" in + pos_y) + if [ "${levelmap["${player["pos_x"]}_${.sh.value}"]}" = "#" ] ; then + .sh.value=${player["pos_y"]} + beep + fi + ;; + + pos_x) + if [ "${levelmap["${.sh.value}_${player["pos_y"]}"]}" = "#" ] ; then + .sh.value=${player["pos_x"]} + beep + fi + ;; + esac +} + +function monster.set +{ + case "${.sh.subscript}" in + *_pos_y) + if [ "${levelmap["${monster[${currmonster}_"pos_x"]}_${.sh.value}"]}" = "#" ] ; then + .sh.value=${monster[${currmonster}_"pos_y"]} + # turn homing off when the monster hit a wall + monster[${currmonster}_"homing"]=0 + fi + ;; + + *_pos_x) + if [ "${levelmap["${.sh.value}_${monster[${currmonster}_"pos_y"]}"]}" = "#" ] ; then + .sh.value=${monster[${currmonster}_"pos_x"]} + # turn homing off when the monster hit a wall + monster[${currmonster}_"homing"]=0 + fi + ;; + esac +} + +function render_game +{ + # render_buffer is some kind of "background buffer" to "double buffer" + # all output and combine it in one write to reduce flickering in the + # terminal + render_buffer="$( + screen_y_offset=1 + start_y_pos=0 + render_num_lines=${levelmap["max_y"]} + + if (( (LINES-3) < levelmap["max_y"] )) ; then + start_y_pos=$((player["pos_y"] / 2)) + render_num_lines=$((LINES-5)) + fi + + #print -n "${vtcode["clear"]}" + print_setcursorpos 0 0 + + # print score (note the " " around "%d" are neccesary to clean up cruft + # when we overwrite the level + printf "SCORE: %05d DOTS: %.3d LIVES: %2.d " "${player["score"]}" "${levelmap["numdots"]}" "${player["lives"]}" + print_levelmap ${screen_y_offset} ${start_y_pos} ${render_num_lines} + + # render player + print_setcursorpos ${player["pos_x"]} $((player["pos_y"]+screen_y_offset-start_y_pos)) + print -n "@" + + # render monsters + for currmonster in ${monsterlist} ; do + let m_pos_x=monster[${currmonster}_"pos_x"] + let m_pos_y=monster[${currmonster}_"pos_y"]+screen_y_offset-start_y_pos + + if (( m_pos_y >= screen_y_offset && m_pos_y < render_num_lines )) ; then + print_setcursorpos ${m_pos_x} ${m_pos_y} + print -n "x" + fi + done + + # status block + print_setcursorpos 0 $((render_num_lines+screen_y_offset)) + emptyline=" " + print -n " >> ${player["message"]} <<${emptyline:0:${#emptyline}-${#player["message"]}}" + )" + print "${render_buffer}" +# print "renderbuffersize=$(print "${render_buffer}" | wc -c) " +} + +function main_loop +{ + float sleep_per_cycle=0.2 + float seconds_before_read + integer num_cycles=0 + float rs + + print -n "${vtcode["clear"]}" + + read_levelmap "$1" + + # player init + player["pos_x"]=${levelmap["playerstartpos_x"]} + player["pos_y"]=${levelmap["playerstartpos_y"]} + player["score"]=0 # player score + player["lives"]=5 # number of lives + player["invulnerable"]=10 # cycles how long the player remains invulnerable + player["message"]="Go..." + + monsterlist="maw claw jitterbug tentacle grendel" + + for currmonster in ${monsterlist} ; do + monster[${currmonster}_"pos_x"]=${levelmap["monsterstartpos_x"]} + monster[${currmonster}_"pos_y"]=${levelmap["monsterstartpos_y"]} + monster[${currmonster}_"xstep"]=0 + monster[${currmonster}_"ystep"]=0 + monster[${currmonster}_"homing"]=0 + done + + # main game cycle loop + while true ; do + num_cycles+=1 + seconds_before_read=${SECONDS} + c="" ; read -t ${sleep_per_cycle} -n 1 c + + if [ "$c" != "" ] ; then + # special case handling for cursor keys which are usually composed + # of three characters (e.g. "<ESC>[D"). If only <ESC> is hit we + # quicky exit + if [ "$c" = $'\E' ] ; then + read -t 0.1 -n 1 c + if [ "$c" != "[" ] ; then + return 0 + fi + + # we assume the user is using the cursor keys, this |read| + # should fetch the 3rd byte of the three-character sequence + # for the cursor keys + read -t 0.1 -n 1 c + fi + + # if the user hit a key the "read" above was interrupted + # and didn't wait exactly |sleep_per_cycle| seconds. + # We wait here some moments (|rs|="remaining seconds") to + # avoid that the game gets "faster" when more user input + # is given. + rs=$((sleep_per_cycle-(SECONDS-seconds_before_read))) + (( rs > 0.001 )) && sleep ${rs} + + player["message"]="" + + case "$c" in + j|D|4) let player["pos_x"]-=1 ;; + k|C|6) let player["pos_x"]+=1 ;; + i|A|8) let player["pos_y"]-=1 ;; + m|B|2) let player["pos_y"]+=1 ;; + + q) return 0 ;; + esac + + if [ "${levelmap["${player["pos_x"]}_${player["pos_y"]}"]}" = "." ] ; then + levelmap["${player["pos_x"]}_${player["pos_y"]}"]=" " + let levelmap["numdots"]-=1 + + let player["score"]+=10 + player["message"]='GNAW!!' + + if [ ${levelmap["numdots"]} -le 0 ] ; then + level_completed + return 0 + fi + fi + fi + + # generic player status change + if [ ${player["invulnerable"]} -gt 0 ] ; then + let player["invulnerable"]-=1 + fi + if [ ${player["lives"]} -le 0 ] ; then + game_over + return 0 + fi + + # move monsters + for currmonster in ${monsterlist} ; do + # make monster as half as slow then the others when it is following the user + if [ ${monster[${currmonster}_"homing"]} -gt 0 ] ; then + [ $((num_cycles % 2)) -gt 0 ] && continue + fi + + if [ ${monster[${currmonster}_"pos_x"]} = ${player["pos_x"]} ] ; then + if [ $((monster[${currmonster}_"pos_y"]-player["pos_y"])) -gt 0 ] ; then + let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=-1 + else + let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=+1 + fi + monster[${currmonster}_"homing"]=1 + if [ ${player["invulnerable"]} -le 0 ] ; then + player["message"]="Attention: ${currmonster} is chasing you" + fi + elif [ ${monster[${currmonster}_"pos_y"]} = ${player["pos_y"]} ] ; then + if [ $((monster[${currmonster}_"pos_x"]-player["pos_x"])) -gt 0 ] ; then + let monster[${currmonster}_"xstep"]=-1 monster[${currmonster}_"ystep"]=-0 + else + let monster[${currmonster}_"xstep"]=+1 monster[${currmonster}_"ystep"]=+0 + fi + monster[${currmonster}_"homing"]=1 + if [ ${player["invulnerable"]} -le 0 ] ; then + player["message"]="Attention: ${currmonster} is chasing you" + fi + else + if [ ${monster[${currmonster}_"homing"]} -eq 0 ] ; then + case $((SECONDS % 6 + RANDOM % 4)) in + 0) let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=+0 ;; + 2) let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=+1 ;; + 3) let monster[${currmonster}_"xstep"]=+1 monster[${currmonster}_"ystep"]=+0 ;; + 5) let monster[${currmonster}_"xstep"]=+0 monster[${currmonster}_"ystep"]=-1 ;; + 6) let monster[${currmonster}_"xstep"]=-1 monster[${currmonster}_"ystep"]=+0 ;; + esac + fi + fi + + let monster[${currmonster}_"pos_x"]=monster[${currmonster}_"pos_x"]+monster[${currmonster}_"xstep"] + let monster[${currmonster}_"pos_y"]=monster[${currmonster}_"pos_y"]+monster[${currmonster}_"ystep"] + + # check if a monster hit the player + if [ ${player["invulnerable"]} -le 0 ] ; then + if [ ${monster[${currmonster}_"pos_x"]} -eq ${player["pos_x"]} -a \ + ${monster[${currmonster}_"pos_y"]} -eq ${player["pos_y"]} ] ; then + # if player was hit by a monster take one life and + # make him invulnerable for 10 cycles to avoid that + # the next cycle steals more lives + player["message"]="Ouuuchhhh" + player["invulnerable"]=10 + let player["lives"]-=1 + + beep ; beep ; sleep 0.3 ; beep ; beep + fi + fi + done + + render_game + done +} + +# program start +function map_filter +{ +# Choose between the old "sed"-based codepath and the new ksh93-native one +# The old codepath no longer used except for the unicode mode because +# we do not have control over the point where "sed" flushes it's buffer +# which completely defeats the doube-buffering code. Unfortunately the new +# codepath has problems in UTF-8 mode (bug in ksh93 ?) which forces us to +# use the old codepath in this case. +if [ $2 -eq 1 ] ; then +( + filter1="" + filter2="" + + # should we add the color map ? + if [ $1 -eq 1 ] ; then + filter1="s/#/${vtcode["fg_blue"]}#/g;\ + s/x/${vtcode["fg_red"]}x/g;\ + s/@/${vtcode["fg_yellow"]}@/g;\ + s/ /${vtcode["fg_grey"]} /g;\ + s/\./${vtcode["fg_lightred"]}./g;" + fi + + # should we add the unicode map ? + if [ $2 -eq 1 ] ; then + filter2="s/@/$(printf '\u[24d2]')/g;s/x/$(printf '\u[2605]')/g;s/#/$(printf '\u[25a6]')/g" + fi + + sed -e "${filter1}" -e "${filter2}" +) +else +( + if [ $1 -eq 1 ] ; then + ch_player="${vtcode["fg_yellow"]}" + ch_monster="${vtcode["fg_red"]}" + ch_wall="${vtcode["fg_blue"]}" + else + ch_player="" + ch_monster="" + ch_wall="" + fi + + if [ $2 -eq 1 ] ; then + # unicode map + ch_player+="$(printf '\u[24d2]')" + ch_monster+="$(printf '\u[2605]')" + ch_wall+="$(printf '\u[25a6]')" + else + # ascii map + ch_player+="@" + ch_monster+="x" + ch_wall+="#" + fi + + IFS="|" # make sure we don't swallow spaces/tabs + while read var ; do + var="${var// /${vtcode["fg_grey"]} }" + var="${var//\./${vtcode["fg_lightred"]}.}" + var="${var//@/${ch_player}}" + var="${var//x/${ch_monster}}" + var="${var//#/${ch_wall}}" + + print "${var}" + done +) +fi +} + +function exit_trap +{ + # restore stty settings + stty ${SAVED_STTY} + + print "bye." +} + +function usage +{ + OPTIND=0 + getopts -a "${progname}" "${USAGE}" OPT '-?' + exit 2 +} + +# program start +progname="${0}" +quiet=false + +# make sure we use the ksh93 "cat" builtin which supports the "-u" option +builtin cat +builtin wc +builtin printf # we need this for positional parameters ('printf "%2\$s %1\$s" hello world' = "world hello") +builtin sleep + +# global variables +typeset -A levelmap +typeset -A player +typeset -A monster +# global rendering options +integer game_use_colors=0 +integer game_use_unicode=0 + +USAGE=$' +[-? +@(#)\$Id: gnaw (Roland Mainz) 2007-06-05 \$ +] +[+NAME?gnaw - maze game written in ksh93] +[+DESCRIPTION?\bgnaw\b is a maze game. + The player maneuvers a yellow '@' sign to navigate a maze while eating + small dots. A level is finished when all the dots are eaten. Five monsters + (maw, claw, jitterbug, tentacle and grendel) also wander the maze in an attempt + to catch the '@'. Each level begins with all ghosts in their home, and '@' near + the bottom of the maze. The monsters are released from the home one by one at the + start of each level and start their rentless hunt after the player.] +[q:quiet?Disable use of terminal bell.] +[+SEE ALSO?\bksh93\b(1)] +' + +while getopts -a "${progname}" "${USAGE}" OPT ; do +# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" + case ${OPT} in + q) quiet=true ;; + *) usage ;; + esac +done +shift ${OPTIND}-1 + +# save stty values and register the exit trap which restores these values on exit +SAVED_STTY="$(stty -g)" +trap exit_trap EXIT + +print "Loading..." + +# set stty values, "-icanon min 1 time 0 -inpck" should improve input latency, +# "-echo" turns the terminal echo off +stty -icanon min 1 time 0 -inpck -echo + +# "resize" cannot fetch the terminal width/height for some terminals +case ${TERM} in + sun | sun-color) + export COLUMNS=80 LINES=25 + ;; + vt52) + export COLUMNS=80 LINES=24 + ;; + *) + # get width/height from current terminal + [ -x "/usr/X11/bin/resize" ] && eval "$(/usr/X11/bin/resize -u)" || + [ -x "/usr/X11R6/bin/resize" ] && eval "$(/usr/X11R6/bin/resize -u)" || + [ -x "/usr/openwin/bin/resize" ] && eval "$(/usr/openwin/bin/resize -u)" || + fatal_error "resize not found." + ;; +esac + +# prechecks +(( COLUMNS < 60 )) && fatal_error "Terminal width must be larger than 60 columns (currently ${COLUMNS})." + +typeset -A vtcode +# color values taken from http://frexx.de/xterm-256-notes/, other +# codes from http://vt100.net/docs/vt100-tm/ +vtcode=( + ["bg_black"]="$(print -n "\E[40m")" + ["fg_black"]="$(print -n "\E[30m")" + ["fg_red"]="$(print -n "\E[31m")" + ["fg_lightred"]="$(print -n "\E[1;31m")" + ["fg_green"]="$(print -n "\E[32m")" + ["fg_lightgreen"]="$(print -n "\E[1;32m")" + ["fg_yellow"]="$(print -n "\E[33m")" + ["fg_lightyellow"]="$(print -n "\E[1;33m")" + ["fg_blue"]="$(print -n "\E[34m")" + ["fg_lightblue"]="$(print -n "\E[1;34m")" + ["fg_grey"]="$(print -n "\E[1;37m")" + ["fg_white"]="$(print -n "\E[37m")" + + # misc other vt stuff + ["vtreset"]="$(tput reset)" + ["clear"]="$(tput clear)" + ["bel"]="$(tput bel)" + ["spaceline"]="$(for (( i=0 ; i < COLUMNS ; i++ )) ; do print -n " " ; done)" +) + +# get terminal sequence to move cursor to position x,y +# (see http://vt100.net/docs/vt100-ug/chapter3.html#CPR) +case ${TERM} in + xterm | xterm-color | vt100 | vt220 | dtterm | sun | sun-color) + cup="$(infocmp -1 | \ + egrep '^[[:space:]]*cup=' | \ + sed -e 's/.*cup=//' \ + -e 's/%[%id]*p1[%id]*/%2\\\$d/g' \ + -e 's/%[%id]*p2[%id]*/%1\\\$d/g' \ + -e 's/,$//')" + for (( x=0 ; x < COLUMNS ; x++ )) ; do + for (( y=0 ; y < LINES ; y++ )) ; do + vtcode[cup_${x}_${y}]="$(printf "${cup}" $((x + 1)) $((y + 1)) )" + done + done + ;; + *) + printf "# Unrecognised terminal type '%s', fetching %dx%d items from terminfo database, please wait...\n" "${TERM}" "${COLUMNS}" "${LINES}" + for (( x=0 ; x < COLUMNS ; x++ )) ; do + for (( y=0 ; y < LINES ; y++ )) ; do + vtcode[cup_${x}_${y}]="$(tput cup ${y} ${x})" + done + done + ;; +esac + +print "${vtcode["vtreset"]}" + +run_logo +run_menu + +# EOF. + diff --git a/usr/src/lib/libshell/common/fun/mandelbrotset1 b/usr/src/lib/libshell/common/fun/mandelbrotset1 new file mode 100644 index 0000000000..fd39f387ec --- /dev/null +++ b/usr/src/lib/libshell/common/fun/mandelbrotset1 @@ -0,0 +1,234 @@ +#!/bin/ksh93 + +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +# +# mandelbrotset1 - a simple mandelbrot set generation and +# parallel execution demo +# + +# Solaris needs /usr/xpg4/bin/ because the tools in /usr/bin are not POSIX-conformant +export PATH=/usr/xpg4/bin:/bin:/usr/bin:/usr/X11/bin:/usr/X11R6/bin:/usr/openwin/bin + +function printmsg +{ + print -u 2 "$@" +} + +function fatal_error +{ + print -u 2 "${progname}: $@" + exit 1 +} + +function print_color +{ + print -n "${symbollist:${1}:1}" +} + +function mandelbrot +{ + float x=$1 + float y=$2 + float xx + float yy + float x1=$3 + float y1=$4 + integer iteration=$5 + integer max_iteration=$6 + float mag + + for (( mag=0 ; mag < max_mag && iteration < max_iteration ; iteration++ )) ; do + (( xx=x*x )) + (( yy=y*y )) + (( mag=xx+yy )) + + (( y=x*y*2+y1 )) + (( x=xx-yy+x1 )) + done + + print ${iteration} + + return 0 +} + +function loop_serial +{ + for (( y=y_min ; y < y_max ; y+=stepwidth )) ; do + for (( x=x_min ; x < x_max ; x+=stepwidth )) ; do + print_color $(mandelbrot ${x} ${y} ${x} ${y} 1 ${symbollistlen}) + done + + print + done +} + +function loop_parallel +{ + integer numjobs=0 + # the following calculation suffers from rounding errors + integer lines_per_job=$(( ((m_height+(numcpus-1)) / numcpus) )) + + printmsg "# lines_per_job=${lines_per_job}" + printmsg "# numcpus=${numcpus}" + + # "renice" worker jobs + set -o bgnice + + if [ "${TMPDIR}" = "" ] ; then + TMPDIR="/tmp" + fi + + # try to generate a job identifer prefix which is unique across multiple hosts + jobident="job_host_$(uname -n)pid_$$_ppid${PPID}" + + printmsg $"## prepare..." + for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do + rm -f "${TMPDIR}/mandelbrot_${jobident}_child_$y.joboutput" + + let numjobs++ + done + + printmsg $"## running ${numjobs} children..." + for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do + ( + for (( ; y < y_max && lines_per_job-- > 0 ; y+=stepwidth )) ; do + for (( x=x_min ; x < x_max ; x+=stepwidth )) ; do + print_color $(mandelbrot ${x} ${y} ${x} ${y} 1 ${symbollistlen}) + done + + print + done >"${TMPDIR}/mandelbrot_${jobident}_child_$y.joboutput" + ) & + done + + printmsg $"## waiting for ${numjobs} children..." + wait + + printmsg $"## output:" + for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do + print "$(cat "${TMPDIR}/mandelbrot_${jobident}_child_$y.joboutput")" + rm "${TMPDIR}/mandelbrot_${jobident}_child_$y.joboutput" + done +} + +function usage +{ + OPTIND=0 + getopts -a "${progname}" "${USAGE}" OPT '-?' + exit 2 +} + +# main +builtin printf +builtin cat +builtin rm +builtin sleep +builtin uname # loop_parallel needs the ksh93 builtin version to generate unique job file names + +float x_max +float x_min +float y_max +float y_min +float m_width +float m_height +float max_mag +float stepwidth +integer numcpus + +# make sure ${COLUMN} and ${LINES} are set +eval $(resize -u) + +symbollist=' .:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%#' +symbollistlen=$(( ${#symbollist} - 1)) +mode="parallel" +progname="${0}" +max_mag=400 +stepwidth=0.1 +numcpus=16 + +let m_width=COLUMNS-1 m_height=LINES-2 + +progname="${0}" + +USAGE=$' +[-? +@(#)\$Id: mandelbrotset1 (Roland Mainz) 2007-06-05 \$ +] +[+NAME?mandelbrotset1 - generate mandelbrot set fractals with ksh93] +[+DESCRIPTION?\bmandelbrotset1\b mandelbrot set fractal generator + which runs either in serial or parallel mode (using multiple worker jobs).] +[w:width?Width of fractal.]:[width] +[h:height?Height of fractal.]:[height] +[s:symbols?Symbols to build the fractal from.]:[symbolstring] +[m:mag?Magnification level.]:[magnificationlevel] +[p:stepwidth?Width per step.]:[widthperstep] +[S:serial?Run in serial mode.] +[P:parallel?Run in parallel mode.] +[M:mode?Execution mode.]:[mode] +[C:numcpus?Number of processors used for parallel execution.]:[numcpus] +[+SEE ALSO?\bjuliaset1\b(1), \bksh93\b(1)] +' + +while getopts -a "${progname}" "${USAGE}" OPT ; do +# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" + case ${OPT} in + w) m_width="${OPTARG}" ;; + h) m_height="${OPTARG}" ;; + s) symbollist="${OPTARG}" ;; + m) max_mag="${OPTARG}" ;; + p) stepwidth="${OPTARG}" ;; + S) mode="serial" ;; + P) mode="parallel" ;; + M) mode="${OPTARG}" ;; + C) numcpus="${OPTARG}" ;; + *) usage ;; + esac +done +shift ${OPTIND}-1 + +printmsg "# width=${m_width}" +printmsg "# height=${m_height}" +printmsg "# max_mag=${max_mag}" +printmsg "# stepwidth=${stepwidth}" +printmsg "# symbollist='${symbollist}'" +printmsg "# mode=${mode}" + +symbollistlen=$(( ${#symbollist} - 1)) + +let x_max=m_width*stepwidth/2. x_min=-x_max +let y_max=m_height*stepwidth/2. y_min=-y_max + +case "${mode}" in + parallel) loop_parallel ;; + serial) loop_serial ;; + *) fatal_error $"Unknown mode \"${mode}\"." +esac + +# EOF. diff --git a/usr/src/lib/libshell/common/fun/popd b/usr/src/lib/libshell/common/fun/popd new file mode 100644 index 0000000000..1bc9346f40 --- /dev/null +++ b/usr/src/lib/libshell/common/fun/popd @@ -0,0 +1,111 @@ +# +# DIRECTORY MANIPULATION FUNCTIONS PUSHD, POPD AND DIRS +# +# Uses global parameters _push_max _push_top _push_stack +integer _push_max=100 _push_top=100 +# Display directory stack -- $HOME displayed as ~ +function dirs +{ + typeset dir="${PWD#$HOME/}" + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + print -r - "$dir ${_push_stack[@]}" +} + +# Change directory and put directory on front of stack +function pushd +{ + typeset dir= type=0 + integer i + case $1 in + "") # pushd + if ((_push_top >= _push_max)) + then print pushd: No other directory. + return 1 + fi + type=1 dir=${_push_stack[_push_top]} + ;; + +[1-9]|+[1-9][0-9]) # pushd +n + integer i=_push_top$1-1 + if ((i >= _push_max)) + then print pushd: Directory stack not that deep. + return 1 + fi + type=2 dir=${_push_stack[i]} + ;; + *) if ((_push_top <= 0)) + then print pushd: Directory stack overflow. + return 1 + fi + esac + case $dir in + \~*) dir=$HOME${dir#\~} + esac + cd "${dir:-$1}" > /dev/null || return 1 + dir=${OLDPWD#$HOME/} + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + case $type in + 0) # pushd name + _push_stack[_push_top=_push_top-1]=$dir + ;; + 1) # pushd + _push_stack[_push_top]=$dir + ;; + 2) # push +n + type=${1#+} i=_push_top-1 + set -- "${_push_stack[@]}" "$dir" "${_push_stack[@]}" + shift $type + for dir + do (((i=i+1) < _push_max)) || break + _push_stack[i]=$dir + done + esac + dirs +} + +# Pops the top directory +function popd +{ + typeset dir + if ((_push_top >= _push_max)) + then print popd: Nothing to pop. + return 1 + fi + case $1 in + "") + dir=${_push_stack[_push_top]} + case $dir in + \~*) dir=$HOME${dir#\~} + esac + cd "$dir" || return 1 + ;; + +[1-9]|+[1-9][0-9]) + typeset savedir + integer i=_push_top$1-1 + if ((i >= _push_max)) + then print pushd: Directory stack not that deep. + return 1 + fi + while ((i > _push_top)) + do _push_stack[i]=${_push_stack[i-1]} + i=i-1 + done + ;; + *) print pushd: Bad directory. + return 1 + esac + unset '_push_stack[_push_top]' + _push_top=_push_top+1 + dirs +} diff --git a/usr/src/lib/libshell/common/fun/pushd b/usr/src/lib/libshell/common/fun/pushd new file mode 100644 index 0000000000..1bc9346f40 --- /dev/null +++ b/usr/src/lib/libshell/common/fun/pushd @@ -0,0 +1,111 @@ +# +# DIRECTORY MANIPULATION FUNCTIONS PUSHD, POPD AND DIRS +# +# Uses global parameters _push_max _push_top _push_stack +integer _push_max=100 _push_top=100 +# Display directory stack -- $HOME displayed as ~ +function dirs +{ + typeset dir="${PWD#$HOME/}" + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + print -r - "$dir ${_push_stack[@]}" +} + +# Change directory and put directory on front of stack +function pushd +{ + typeset dir= type=0 + integer i + case $1 in + "") # pushd + if ((_push_top >= _push_max)) + then print pushd: No other directory. + return 1 + fi + type=1 dir=${_push_stack[_push_top]} + ;; + +[1-9]|+[1-9][0-9]) # pushd +n + integer i=_push_top$1-1 + if ((i >= _push_max)) + then print pushd: Directory stack not that deep. + return 1 + fi + type=2 dir=${_push_stack[i]} + ;; + *) if ((_push_top <= 0)) + then print pushd: Directory stack overflow. + return 1 + fi + esac + case $dir in + \~*) dir=$HOME${dir#\~} + esac + cd "${dir:-$1}" > /dev/null || return 1 + dir=${OLDPWD#$HOME/} + case $dir in + $HOME) + dir=\~ + ;; + /*) ;; + *) dir=\~/$dir + esac + case $type in + 0) # pushd name + _push_stack[_push_top=_push_top-1]=$dir + ;; + 1) # pushd + _push_stack[_push_top]=$dir + ;; + 2) # push +n + type=${1#+} i=_push_top-1 + set -- "${_push_stack[@]}" "$dir" "${_push_stack[@]}" + shift $type + for dir + do (((i=i+1) < _push_max)) || break + _push_stack[i]=$dir + done + esac + dirs +} + +# Pops the top directory +function popd +{ + typeset dir + if ((_push_top >= _push_max)) + then print popd: Nothing to pop. + return 1 + fi + case $1 in + "") + dir=${_push_stack[_push_top]} + case $dir in + \~*) dir=$HOME${dir#\~} + esac + cd "$dir" || return 1 + ;; + +[1-9]|+[1-9][0-9]) + typeset savedir + integer i=_push_top$1-1 + if ((i >= _push_max)) + then print pushd: Directory stack not that deep. + return 1 + fi + while ((i > _push_top)) + do _push_stack[i]=${_push_stack[i-1]} + i=i-1 + done + ;; + *) print pushd: Bad directory. + return 1 + esac + unset '_push_stack[_push_top]' + _push_top=_push_top+1 + dirs +} diff --git a/usr/src/lib/libshell/common/fun/rssread b/usr/src/lib/libshell/common/fun/rssread new file mode 100644 index 0000000000..e561cb3587 --- /dev/null +++ b/usr/src/lib/libshell/common/fun/rssread @@ -0,0 +1,414 @@ +#!/bin/ksh93 + +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +# +# rssread - a simple RSS2.0 reader with RSS to XHTML to +# plaintext conversion. +# + +# Solaris needs /usr/xpg4/bin/ because the tools in /usr/bin are not POSIX-conformant +export PATH=/usr/xpg4/bin:/bin:/usr/bin + +function printmsg +{ + print -u 2 "$@" +} + +function debugmsg +{ +# printmsg "$@" +true +} + +function fatal_error +{ + print -u 2 "${progname}: $@" + exit 1 +} + +function cat_http +{ +( + protocol="${1%://*}" + path1="${1#*://}" # "http://foo.bat.net/x/y.html" ----> "foo.bat.net/x/y.html" + + host="${path1%%/*}" + path="${path1#*/}" + port="${host##*:}" + + # If URL did not contain a port number in the host part then look at the + # protocol to get the port number + if [ "${port}" = "${host}" ] ; then + case "${protocol}" in + "http") port=80 ;; + *) port="$(getent services "${protocol}" | sed 's/[^0-9]*//;s/\/.*//')" ;; + esac + else + host="${host%:*}" + fi + + printmsg "protocol=${protocol} port=${port} host=${host} path=${path}" + + # prechecks + [ "${protocol}" = "" ] && fatal_error "protocol not set." + [ "${port}" = "" ] && fatal_error "port not set." + [ "${host}" = "" ] && fatal_error "host not set." + [ "${path}" = "" ] && fatal_error "path not set." + + # open TCP channel + exec 3<>"/dev/tcp/${host}/${port}" + + # send HTTP request + request="GET /${path} HTTP/1.0\n" + request+="Host: ${host}\n" + request+="User-Agent: ksh93/rssread (2007-01-16; $(uname -s -r -p))\n" + print "${request}\n" >&3 + + # collect response and send it to stdout + cat <&3 +) +} + +function html_entity_to_ascii +{ + typeset -A entity_cache + + # Todo: Add more HTML/MathML entities here + entity_cache["nbsp"]=" " + entity_cache["lt"]="<" + entity_cache["gt"]=">" + entity_cache["amp"]="&" + entity_cache["quot"]="\"" + entity_cache["apos"]="'" + + buf="" + while read -r -N 1 c ; do + if [ "$c" != "&" ] ; then + printf "%s" "${c}" + continue + fi + + entity="" + while read -r -N 1 c ; do + case "$c" in + ";") + break + ;; + ~(Eilr)[a-z0-9#]) + entity+="$c" + continue + ;; + *) + debugmsg "error &${entity}${c}#" + + print -n "${entity}${c}" + entity="" + continue 2 + ;; + esac + done + + value="" + if [ "${entity_cache["${entity}"]}" != "" ] ; then + debugmsg "match #${entity}# = #${entity_cache["${entity}"]}#" + value="${entity_cache["${entity}"]}" + else + if [ "${entity:0:1}" = "#" ] ; then + # decimal literal + value="$(printf "\u[$(printf "%x" "${entity:1:8}")]")" + elif [[ "${entity:0:7}" = ~(Eilr)[0-9a-f]* ]] ; then + # hexadecimal literal + value="$(printf "\u[${entity:0:7}]")" + else + # unknown literal - pass-through + value="<ENT=${entity}>" + fi + + entity_cache["${entity}"]="${value}" + + debugmsg "lookup #${entity}# = #${entity_cache["${entity}"]}#" + fi + + printf "%s" "$value" + done +} + +# dumb xhtml handler - no CSS, tables, images, iframes or nested +# structures are supported (and we assume that the input is correct +# xhtml). The code was written in a trial&&error manner and should be +# rewritten to parse xhtml correctly. +function handle_html +{ + # we can't use global variables here when multiple callbacks use the same + # callback function - but we can use the callback associative array for + # variable storage instead + nameref callbacks=${1} + tag_type="$2" + tag_value="$3" + + case "${tag_type}" in + tag_begin) + case "${tag_value}" in + br*) printf "\n" ;; + hr*) printf "\n-------------------------------------\n" ;; + pre*) callbacks["html_pre"]=1 ;; + p*) printf "\n" ;; + esac + ;; + + tag_end) + case "${tag_value}" in + pre*) callbacks["html_pre"]=0 ;; + esac + ;; + + tag_text) + if [ ${callbacks["html_pre"]} -eq 1 ] ; then + printf "%s" "${tag_value}" + else + # compress spaces/newlines/tabs/etc. + printf "%s" "${tag_value/+([\n\r\t\v[:space:][:blank:]])/ }" + fi + ;; + + document_start) + callbacks["html_pre"]=0 + ;; + document_end) ;; + esac +} + +function handle_rss +{ + # we can't use global variables here when multiple callbacks use the same + # callback function - but we can use the callback associative array for + # variable storage instead + nameref callbacks=${1} + tag_type="$2" + tag_value="$3" + + case "${tag_type}" in + tag_begin) + case "${tag_value}" in + item*) + item["title"]="" + item["link"]="" + item["tag"]="" + item["description"]="" + ;; + esac + callbacks["textbuf"]="" + ;; + tag_end) + case "${tag_value}" in + item*) + # note that each RSS item needs to be converted seperately from RSS to HTML to plain text + # to make sure that the state of one RSS item doesn't affect others + ( + printf $"<br />#### RSS item: title: %s ####" "${item["title"]}" + printf $"<br />## author: %s" "${item["author"]}" + printf $"<br />## link: %s" "${item["link"]}" + printf $"<br />## date: %s" "${item["pubDate"]}" + printf $"<br />## begin description:" + printf $"<br />%s<br />" "${item["description"]}" + printf $"<br />## end description<br />" + print # extra newline to make sure the sed pipeline gets flushed + ) | + html_entity_to_ascii | # convert XML entities (e.g. decode RSS content to HTML code) + xml_tok "xhtmltok_cb" | # convert HTML to plain text + html_entity_to_ascii # convert HTML entities + ;; + title*) item["title"]="${callbacks["textbuf"]}" ; callbacks["textbuf"]="" ;; + link*) item["link"]="${callbacks["textbuf"]}" ; callbacks["textbuf"]="" ;; + dc:creator* | author*) item["author"]="${callbacks["textbuf"]}" ; callbacks["textbuf"]="" ;; + dc:date* | pubDate*) item["pubDate"]="${callbacks["textbuf"]}" ; callbacks["textbuf"]="" ;; + description*) item["description"]="${callbacks["textbuf"]}" ; callbacks["textbuf"]="" ;; + esac + callbacks["textbuf"]="" + ;; + tag_text) + callbacks["textbuf"]+="${tag_value}" + ;; + document_start) ;; + document_end) ;; + esac +} + +function xml_tok +{ + typeset buf="" + typeset c="" + + nameref callbacks=${1} + + [ ! -z "${callbacks["document_start"]}" ] && ${callbacks["document_start"]} "${1}" "document_start" + + while read -N 1 c -d '\0'; do + isendtag=false + + if [ "$c" = "<" ] ; then + if [ "$buf" != "" ] ; then + [ ! -z "${callbacks["tag_text"]}" ] && ${callbacks["tag_text"]} "${1}" "tag_text" "$buf" + buf="" + fi + + read -N 1 c -d '\0' + if [ "$c" = "/" ] ; then + isendtag=true + else + buf="$c" + fi + read -d '>' c + buf+="$c" + + if ${isendtag} ; then + [ ! -z "${callbacks["tag_end"]}" ] && ${callbacks["tag_end"]} "${1}" "tag_end" "$buf" + else + [ ! -z "${callbacks["tag_begin"]}" ] && ${callbacks["tag_begin"]} "${1}" "tag_begin" "$buf" + + # handle tags like <br/> (which are start- and end-tag in one piece) + if [[ "${buf}" = ~(Er).*/ ]] ; then + [ ! -z "${callbacks["tag_end"]}" ] && ${callbacks["tag_end"]} "${1}" "tag_end" "$buf" + fi + fi + buf="" + else + buf+="$c" + fi + done + + [ ! -z "${callbacks["document_end"]}" ] && ${callbacks["document_start"]} "${1}" "document_end" "exit_success" + + print # final newline to make filters like "sed" happy +} + +# return the value of LC_MESSAGES needed for subprocesses which +# want to run in a different locale/encoding +function get_lc_messages +{ + [ "${LC_ALL}" != "" ] && { print "${LC_ALL}" ; return 0 ; } + [ "${LC_MESSAGES}" != "" ] && { print "${LC_MESSAGES}" ; return 0 ; } + [ "${LANG}" != "" ] && { print "${LANG}" ; return 0 ; } + print "C" ; return 0 +} + +function usage +{ + OPTIND=0 + getopts -a "${progname}" "${USAGE}" OPT '-?' + exit 2 +} + +# make sure we use the ksh93 builtin versions +builtin cat +builtin printf + +typeset -A rsstok_cb # callbacks for xml_tok +rsstok_cb["tag_begin"]="handle_rss" +rsstok_cb["tag_end"]="handle_rss" +rsstok_cb["tag_text"]="handle_rss" +rsstok_cb["textbuf"]="" + +typeset -A xhtmltok_cb # callbacks for xml_tok +xhtmltok_cb["tag_begin"]="handle_html" +xhtmltok_cb["tag_end"]="handle_html" +xhtmltok_cb["tag_text"]="handle_html" +xhtmltok_cb["textbuf"]="" +xhtmltok_cb["html_pre"]=0 + +typeset -A item + +typeset -A bookmark_urls + +# "ramdom" urls for testing +bookmark_urls=( + ["google_blogs_ksh"]="http://blogsearch.google.com/blogsearch_feeds?hl=en&scoring=d&q=ksh&ie=utf-8&num=100&output=rss" + ["ksh93_integration"]="http://www.opensolaris.org/rss/os/project/ksh93-integration/announcements/rss2.xml" + ["blogs_sun_com"]="http://blogs.sun.com/main/feed/entries/rss" + ["jmcp"]="http://www.jmcp.homeunix.com/roller/rss/jmcp" + ["katakai"]="http://blogs.sun.com/katakai/feed/entries/rss" + ["planetsun"]="http://www.planetsun.org/rss20.xml" + ["planetsolaris"]="http://www.planetsolaris.org/rss20.xml" + ["planetopensolaris"]="http://planet.opensolaris.org/rss20.xml" +) + +progname="${0}" + +USAGE=$' +[-? +@(#)\$Id: rssread (Roland Mainz) 2007-06-05 \$ +] +[+NAME?rssread - fetch RSS messages and convert them to plain text] +[+DESCRIPTION?\brssread\b RSS to plain text converter + which fetches RSS streams via HTTP and converts them from RSS to HTML to plain UTF-8 text.] + +[ url ] + +[+SEE ALSO?\bksh93\b(1)] +' + +while getopts -a "${progname}" "${USAGE}" OPT ; do +# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" + case ${OPT} in + *) usage ;; + esac +done +shift ${OPTIND}-1 + +url="$1" + +if [ "$url" = "" ] ; then + fatal_error $"No url given." +fi + +if [ "${bookmark_urls[${url}]}" != "" ] ; then + printmsg $"Using bookmark ${url} = ${bookmark_urls[${url}]}" + url="${bookmark_urls[${url}]}" +fi + +( + # set unicode locale since RSS is encoded in UTF-8 + # (and make sure $LC_MESSAGES is set to the parent + # process's locale that all error messages are using + # the callers locale/encoding) + export \ + LC_MESSAGES="$(get_lc_messages)" \ + LC_MONETARY="en_US.UTF-8" \ + LC_NUMERIC="en_US.UTF-8" \ + LC_COLLATE="en_US.UTF-8" \ + LC_CTYPE="en_US.UTF-8" \ + LC_TIME="en_US.UTF-8" \ + LANG="en_US.UTF-8" + + cat_http "$url" | + xml_tok "rsstok_cb" +) # | iconv -f "UTF-8" - - + +#EOF. diff --git a/usr/src/lib/libshell/common/fun/termclock b/usr/src/lib/libshell/common/fun/termclock new file mode 100644 index 0000000000..9bf0cf6d85 --- /dev/null +++ b/usr/src/lib/libshell/common/fun/termclock @@ -0,0 +1,267 @@ +#!/bin/ksh93 + +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +# +# termclock - a simple analog clock for terminals +# + +# Solaris needs /usr/xpg4/bin/ because the tools in /usr/bin are not POSIX-conformant +export PATH=/usr/xpg4/bin:/bin:/usr/bin + +function fatal_error +{ + print -u 2 "${progname}: $@" + exit 1 +} + +# cache tput values (to avoid |fork()|'ing a "tput" child every second) +function tput_cup +{ + integer y="$1" x="$2" + nameref c=tput_cup_cache["${y}_${x}"] + + if [ "$c" == "" ] ; then + # fast path for known terminal types + if [[ ${TERM} = ~(Elr)(vt100|vt220|xterm|xterm-color|dtterm) ]] ; then + c="$(printf "\E[%d;%dH" $((y+1)) $((x+1)))" + else + c="$(tput cup $y $x)" + fi + fi + + print -n "$c" +} + +function draw_clock +{ + float angle a + float x y + + for(( angle=0.0 ; angle < 360. ; angle+=6 )) ; do + (( a=angle/360.*(2*M_PI) )) + + (( x=clock.len_x*cos(a) )) + (( y=clock.len_y*sin(a) )) + tput_cup $(( y+clock.middle_y )) $(( x+clock.middle_x )) + + # add "mark" every 30 degrees + if (( int(angle)%30 == 0 )) ; then + print -n "0" + else + print -n "x" + fi + done +} + +function draw_hand +{ + float angle="$1" a + typeset ch="$2" + float length="$3" + float x y + + (( a=angle/360.*(2*M_PI) )) + + for(( s=0.0 ; s < 10. ; s+=0.5 )) ; do + (( x=(clock.len_x*(s/10.)*(length/100.))*cos(a) )) + (( y=(clock.len_y*(s/10.)*(length/100.))*sin(a) )) + + tput_cup $(( y+clock.middle_y )) $(( x+clock.middle_x )) + print -n "${ch}" + done +} + +function draw_clock_hand +{ + nameref hand=$1 + + draw_hand $(( 360.*(hand.val/hand.scale)-90. )) "${hand.ch}" ${hand.length} +} + +function clear_clock_hand +{ + nameref hand=$1 + + draw_hand $(( 360.*(hand.val/hand.scale)-90. )) " " ${hand.length} +} + +function main_loop +{ + typeset c + + # note: we can't use subshells when writing to the double-buffer file because this + # will render the tput value cache useless + while true ; do + if ${init_screen} ; then + init_screen="false" + + # "resize" is needed because older versions of ksh93 may have + # trouble with getting the right terminal size at startup + [ -x "/usr/X11/bin/resize" ] && eval "$(/usr/X11/bin/resize -u)" || + [ -x "/usr/X11R6/bin/resize" ] && eval "$(/usr/X11R6/bin/resize -u)" || + [ -x "/usr/openwin/bin/resize" ] && eval "$(/usr/openwin/bin/resize -u)" || + fatal_error "resize not found." + + (( clock.middle_x=COLUMNS/2.-.5 )) + (( clock.middle_y=LINES/2.-.5 )) + (( clock.len_x=COLUMNS/2-2 )) + (( clock.len_y=LINES/2-2 )) + + { + clear + draw_clock + } >&6 + fi + + { + (( $(date +"hours.val=%H , minutes.val=%M , seconds.val=%S") )) + + # small trick to get a smooth "analog" flair + (( hours.val+=minutes.val/60. )) + (( minutes.val+=seconds.val/60. )) + + draw_clock_hand seconds + draw_clock_hand minutes + draw_clock_hand hours + + # move cursor to home position + tput_cup 0 0 + } >&6 + + 6<#((0)) + cat <&6 + + 6<&- ; rm -f "${scratchfile}" ; exec 6<>"${scratchfile}" + + c="" ; read -t ${update_interval} -n 1 c + if [ "$c" != "" ] ; then + case "$c" in + ~(Ei)q | $'\E') return 0 ;; + esac + fi + + { + clear_clock_hand hours + clear_clock_hand minutes + clear_clock_hand seconds + } >&6 + done +} + +function usage +{ + OPTIND=0 + getopts -a "${progname}" "${USAGE}" OPT '-?' + exit 2 +} + +# program start +progname="${0}" + +builtin date +builtin rm +builtin printf + +typeset -A tput_cup_cache + +float -r M_PI=3.14159265358979323846 + +clock=( + float middle_x + float middle_y + integer len_x + integer len_y +) + +typeset init_screen="true" + +# set clock properties +seconds=( float val + typeset ch + float scale + integer length ) +minutes=( float val + typeset ch + float scale + integer length ) +hours=( float val + typeset ch + float scale + integer length ) + +seconds.length=90 seconds.scale=60 seconds.ch="s" +minutes.length=75 minutes.scale=60 minutes.ch="m" +hours.length=50 hours.scale=12 hours.ch="h" + +float update_interval=0.9 + +USAGE=$' +[-? +@(#)\$Id: termclock (Roland Mainz) 2007-06-05 \$ +] +[+NAME?termclock - analog clock for terminals] +[+DESCRIPTION?\btermclock\b is an analog clock for terminals. + The termclock program displays the time in analog or digital + form. The time is continuously updated at a frequency which + may be specified by the user.] +[u:update?Update interval (defaults to 0.9 seconds).]:[interval] +[+SEE ALSO?\bksh93\b(1), \bxclock\b(1)] +' + +while getopts -a "${progname}" "${USAGE}" OPT ; do +# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|" + case ${OPT} in + u) update_interval=${OPTARG} ;; + *) usage ;; + esac +done +shift ${OPTIND}-1 + +# prechecks +which tput >/dev/null || fatal_error "tput not found." +which mktemp >/dev/null || fatal_error "mktemp not found." +(( update_interval < 0. || update_interval > 7200. )) && fatal_error "invalid update_interval value." + +# create temporary file for double-buffering and register an EXIT trap +# to remove this file when the shell interpreter exits +scratchfile="$(mktemp /tmp/termclock.pid$$.XXXXXX)" +if [ -z "${scratchfile}" ]; then exit 1; fi +trap 'rm -f "${scratchfile}"' EXIT +rm -f "${scratchfile}" ; exec 6<>"${scratchfile}" + +# regiter trap to handle window size changes +trap 'init_screen="true"' WINCH + +main_loop + +# exiting - put cursor below clock +tput_cup $((LINES-2)) 0 + +# EOF. diff --git a/usr/src/lib/libshell/common/fun/title b/usr/src/lib/libshell/common/fun/title new file mode 100644 index 0000000000..cf3efdcb00 --- /dev/null +++ b/usr/src/lib/libshell/common/fun/title @@ -0,0 +1,57 @@ +# add to (+), delete from (-), set [=], or print (.) window title +# arguments are eval'd before printing +# title text string exported in TITLE_TEXT + +function title # [ + | - | = | . ] title ... +{ + typeset x t="$TITLE_TEXT" + + case $1 in + +) shift + case $# in + 0) ;; + *) for x + do case " $t " in + *" $x "*) ;; + " ") t=$x ;; + *) t="$t $x" ;; + esac + done + case $t in + $TITLE_TEXT) return 1 ;; + esac + ;; + esac + ;; + -) shift + case $# in + 0) ;; + *) for x + do case " $t " in + *" $x "*) t="${t%?( )$x*}${t##*$x?( )}" ;; + esac + done + case $t in + $TITLE_TEXT) return 1 ;; + esac + ;; + esac + ;; + .) print -r -- "$TITLE_TEXT" + return 0 + ;; + =) shift + t="$*" + ;; + *) t="$*" + ;; + esac + export TITLE_TEXT="$t" + eval x=\"$t\" + case $TERM in + 630*) print -nr -- $'\E[?'"${#x}"$';0v'"${x}" ;; + uwin*|*vt100|xterm*) print -nr -- $'\E]0;'"${x}"$'\a' ;; + *) return 1 ;; + esac + return 0 +} diff --git a/usr/src/lib/libshell/common/include/argnod.h b/usr/src/lib/libshell/common/include/argnod.h new file mode 100644 index 0000000000..fd93e5b215 --- /dev/null +++ b/usr/src/lib/libshell/common/include/argnod.h @@ -0,0 +1,147 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef ARG_RAW +/* + * struct to hold a word argument + * Written by David Korn + * + */ + +#include <stak.h> + +struct ionod +{ + unsigned iofile; + char *ioname; + struct ionod *ionxt; + struct ionod *iolst; + char *iodelim; + off_t iooffset; + long iosize; + char *iovname; +}; + +struct comnod +{ + int comtyp; + struct ionod *comio; + struct argnod *comarg; + struct argnod *comset; + void *comnamp; + void *comnamq; + void *comstate; + int comline; +}; + +#define COMBITS 4 +#define COMMSK ((1<<COMBITS)-1) +#define COMSCAN (01<<COMBITS) +#define COMFIXED (02<<COMBITS) + +struct slnod /* struct for link list of stacks */ +{ + struct slnod *slnext; + struct slnod *slchild; + Stak_t *slptr; +}; + +/* + * This struct is use to hold $* lists and arrays + */ + +struct dolnod +{ + short dolrefcnt; /* reference count */ + short dolmax; /* size of dolval array */ + short dolnum; /* number of elements */ + short dolbot; /* current first element */ + struct dolnod *dolnxt; /* used when list are chained */ + char *dolval[1]; /* array of value pointers */ +}; + +/* + * This struct is used to hold word arguments of variable size during + * parsing and during expansion. The flags indicate what processing + * is required on the argument. + */ + +struct argnod +{ + union + { + struct argnod *ap; + char *cp; + } argnxt; + union + { + struct argnod *ap; + char *cp; + int len; + } argchn; + unsigned char argflag; + char argval[4]; +}; + + + +/* The following should evaluate to the offset of argval in argnod */ +#define ARGVAL offsetof(struct argnod,argval[0]) +#define sh_argstr(ap) ((ap)->argflag&ARG_RAW?sh_fmtq((ap)->argval):(ap)->argval) +#define ARG_SPARE 1 + + +/* legal argument flags */ +#define ARG_RAW 0x1 /* string needs no processing */ +#define ARG_MAKE 0x2 /* bit set during argument expansion */ +#define ARG_COMSUB 0x2 /* command sub */ +#define ARG_MAC 0x4 /* string needs macro expansion */ +#define ARG_EXP 0x8 /* string needs file expansion */ +#define ARG_ASSIGN 0x10 /* argument is an assignment */ +#define ARG_QUOTED 0x20 /* word contained quote characters */ +#define ARG_MESSAGE 0x40 /* contains international string */ +#define ARG_APPEND 0x80 /* for += assignment */ +/* The following can be passed as options to sh_macexpand() */ +#define ARG_ARITH 0x100 /* arithmetic expansion */ +#define ARG_OPTIMIZE 0x200 /* try to optimize */ +#define ARG_NOGLOB 0x400 /* no file name expansion */ +#define ARG_LET 0x800 /* processing let command arguments */ + +extern char **sh_argbuild(int*,const struct comnod*,int); +extern struct dolnod *sh_argcreate(char*[]); +extern char *sh_argdolminus(void); +extern struct dolnod *sh_argfree(struct dolnod*,int); +extern struct dolnod *sh_argnew(char*[],struct dolnod**); +extern int sh_argopts(int,char*[]); +extern void sh_argreset(struct dolnod*,struct dolnod*); +extern void sh_argset(char*[]); +extern struct dolnod *sh_arguse(void); + +extern const char e_heading[]; +extern const char e_off[]; +extern const char e_on[]; +extern const char e_sptbnl[]; +extern const char e_subst[]; +extern const char e_option[]; +extern const char e_exec[]; +extern const char e_devfdNN[]; +extern const char e_devfdstd[]; + +#endif /* ARG_RAW */ diff --git a/usr/src/lib/libshell/common/include/builtins.h b/usr/src/lib/libshell/common/include/builtins.h new file mode 100644 index 0000000000..8c79075fce --- /dev/null +++ b/usr/src/lib/libshell/common/include/builtins.h @@ -0,0 +1,200 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#ifndef SYSDECLARE + +#include <option.h> +#include "FEATURE/options" +#include "FEATURE/dynamic" +#include "shtable.h" + +#define SYSLOGIN (sh.bltin_cmds) +#define SYSEXEC (sh.bltin_cmds+1) +#define SYSSET (sh.bltin_cmds+2) +#define SYSTRUE (sh.bltin_cmds+4) +#define SYSCOMMAND (sh.bltin_cmds+5) +#define SYSCD (sh.bltin_cmds+6) +#define SYSBREAK (sh.bltin_cmds+7) +#define SYSCONT (sh.bltin_cmds+8) +#define SYSTYPESET (sh.bltin_cmds+9) +#define SYSTEST (sh.bltin_cmds+10) +#define SYSBRACKET (sh.bltin_cmds+11) +#define SYSLET (sh.bltin_cmds+12) +#define SYSEXPORT (sh.bltin_cmds+13) +#if SHOPT_BASH +# define SYSLOCAL (sh.bltin_cmds+14) +#else +# define SYSLOCAL 0 +#endif + +/* entry point for shell special builtins */ + +#if _BLD_shell && defined(__EXPORT__) +# define extern __EXPORT__ +#endif + +extern int b_alias(int, char*[],void*); +extern int b_break(int, char*[],void*); +extern int b_dot_cmd(int, char*[],void*); +extern int b_exec(int, char*[],void*); +extern int b_eval(int, char*[],void*); +extern int b_return(int, char*[],void*); +extern int B_login(int, char*[],void*); +extern int b_true(int, char*[],void*); +extern int b_false(int, char*[],void*); +extern int b_readonly(int, char*[],void*); +extern int b_set(int, char*[],void*); +extern int b_shift(int, char*[],void*); +extern int b_trap(int, char*[],void*); +extern int b_typeset(int, char*[],void*); +extern int b_unset(int, char*[],void*); +extern int b_unalias(int, char*[],void*); + +/* The following are for job control */ +#if defined(SIGCLD) || defined(SIGCHLD) + extern int b_jobs(int, char*[],void*); + extern int b_kill(int, char*[],void*); +# ifdef SIGTSTP + extern int b_bg(int, char*[],void*); +# endif /* SIGTSTP */ +#endif + +/* The following utilities are built-in because of side-effects */ +extern int b_builtin(int, char*[],void*); +extern int b_cd(int, char*[],void*); +extern int b_command(int, char*[],void*); +extern int b_getopts(int, char*[],void*); +extern int b_hist(int, char*[],void*); +extern int b_let(int, char*[],void*); +extern int b_read(int, char*[],void*); +extern int b_ulimit(int, char*[],void*); +extern int b_umask(int, char*[],void*); +#ifdef _cmd_universe + extern int b_universe(int, char*[],void*); +#endif /* _cmd_universe */ +#if SHOPT_FS_3D + extern int b_vpath(int, char*[],void*); +#endif /* SHOPT_FS_3D */ +extern int b_wait(int, char*[],void*); +extern int b_whence(int, char*[],void*); + +extern int b_alarm(int, char*[],void*); +extern int b_print(int, char*[],void*); +extern int b_printf(int, char*[],void*); +extern int b_pwd(int, char*[],void*); +extern int b_sleep(int, char*[],void*); +extern int b_test(int, char*[],void*); +#if !SHOPT_ECHOPRINT + extern int B_echo(int, char*[],void*); +#endif /* SHOPT_ECHOPRINT */ + +#undef extern + +extern const char e_alrm1[]; +extern const char e_alrm2[]; +extern const char e_badfun[]; +extern const char e_baddisc[]; +extern const char e_nofork[]; +extern const char e_nosignal[]; +extern const char e_nolabels[]; +extern const char e_notimp[]; +extern const char e_nosupport[]; +extern const char e_badbase[]; +extern const char e_overlimit[]; + +extern const char e_eneedsarg[]; +extern const char e_toodeep[]; +extern const char e_badname[]; +extern const char e_badwrite[]; +extern const char e_badsyntax[]; +#ifdef _cmd_universe + extern const char e_nouniverse[]; +#endif /* _cmd_universe */ +extern const char e_histopen[]; +extern const char e_condition[]; +extern const char e_badrange[]; +extern const char e_numeric[]; +extern const char e_trap[]; +extern const char e_direct[]; +extern const char e_defedit[]; +extern const char e_cneedsarg[]; +extern const char e_defined[]; +#if SHOPT_FS_3D + extern const char e_cantset[]; + extern const char e_cantget[]; + extern const char e_mapping[]; + extern const char e_versions[]; +#endif /* SHOPT_FS_3D */ + +/* for option parsing */ +extern const char sh_set[]; +extern const char sh_optalarm[]; +extern const char sh_optalias[]; +extern const char sh_optbreak[]; +extern const char sh_optbuiltin[]; +extern const char sh_optcd[]; +extern const char sh_optcommand[]; +extern const char sh_optcont[]; +extern const char sh_optdot[]; +#ifndef ECHOPRINT + extern const char sh_optecho[]; +#endif /* !ECHOPRINT */ +extern const char sh_opteval[]; +extern const char sh_optexec[]; +extern const char sh_optexit[]; +extern const char sh_optexport[]; +extern const char sh_optgetopts[]; +extern const char sh_optbg[]; +extern const char sh_optdisown[]; +extern const char sh_optfg[]; +extern const char sh_opthist[]; +extern const char sh_optjobs[]; +extern const char sh_optkill[]; +extern const char sh_optksh[]; +extern const char sh_optlet[]; +extern const char sh_optprint[]; +extern const char sh_optprintf[]; +extern const char sh_optpwd[]; +extern const char sh_optread[]; +extern const char sh_optreadonly[]; +extern const char sh_optreturn[]; +extern const char sh_optset[]; +extern const char sh_optshift[]; +extern const char sh_optsleep[]; +extern const char sh_opttrap[]; +extern const char sh_opttypeset[]; +extern const char sh_optulimit[]; +extern const char sh_optumask[]; +extern const char sh_optunalias[]; +extern const char sh_optwait[]; +#ifdef _cmd_universe + extern const char sh_optuniverse[]; +#endif /* _cmd_universe */ +extern const char sh_optunset[]; +#if SHOPT_FS_3D + extern const char sh_optvpath[]; + extern const char sh_optvmap[]; +#endif /* SHOPT_FS_3D */ +extern const char sh_optwhence[]; +#endif /* SYSDECLARE */ + +extern const char e_dict[]; + diff --git a/usr/src/lib/libshell/common/include/defs.h b/usr/src/lib/libshell/common/include/defs.h new file mode 100644 index 0000000000..91a3d2c3e1 --- /dev/null +++ b/usr/src/lib/libshell/common/include/defs.h @@ -0,0 +1,377 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * David Korn + * AT&T Labs + * + * Shell interface private definitions + * + */ + +#include <ast.h> +#include <sfio.h> +#include <error.h> +#include "FEATURE/options" +#include <cdt.h> +#include <history.h> +#include "fault.h" +#include "argnod.h" + +#ifndef pointerof +#define pointerof(x) ((void*)((char*)0+(x))) +#endif + +#define env_change() (++ast.env_serial) +#if SHOPT_ENV +# include <env.h> +#else +# define Env_t void +# define sh_envput(e,p) env_change() +# define env_delete(e,p) env_change() +#endif + +/* + * note that the first few fields have to be the same as for + * Shscoped_t in <shell.h> + */ +struct sh_scoped +{ + struct sh_scoped *prevst; /* pointer to previous state */ + int dolc; + char **dolv; + char *cmdname; + char *filename; + int lineno; + Dt_t *save_tree; /* var_tree for calling function */ + struct sh_scoped *self; /* pointer to copy of this scope*/ + Dt_t *var_local; /* local level variables for name() */ + struct slnod *staklist; /* link list of function stacks */ + int states; + int breakcnt; + int execbrk; + int loopcnt; + int firstline; + int32_t optindex; + int32_t optnum; + int32_t tmout; /* value for TMOUT */ + short optchar; + short opterror; + int ioset; + unsigned short trapmax; + char *trap[SH_DEBUGTRAP+1]; + char **trapcom; + char **otrapcom; + void *timetrap; +}; + +struct limits +{ + long arg_max; /* max arg+env exec() size */ + int open_max; /* maximum number of file descriptors */ + int clk_tck; /* number of ticks per second */ + int child_max; /* maxumum number of children */ + int ngroups_max; /* maximum number of process groups */ + unsigned char posix_version; /* posix version number */ + unsigned char posix_jobcontrol;/* non-zero for job control systems */ + unsigned char fs3d; /* non-zero for 3-d file system */ +}; + +#define _SH_PRIVATE \ + struct sh_scoped st; /* scoped information */ \ + struct limits lim; /* run time limits */ \ + Sfio_t *heredocs; /* current here-doc temp file */ \ + Sfio_t *funlog; /* for logging function definitions */ \ + int **fdptrs; /* pointer to file numbers */ \ + int savexit; \ + char *lastarg; \ + char *lastpath; /* last alsolute path found */ \ + int path_err; /* last error on path search */ \ + Dt_t *track_tree; /* for tracked aliases*/ \ + Namval_t *bltin_nodes; /* pointer to built-in variables */ \ + Dt_t *var_base; /* global level variables */ \ + Namval_t *namespace; /* current active namespace*/ \ + Namval_t *last_table; /* last table used in last nv_open */ \ + Sfio_t *outpool; /* ouput stream pool */ \ + long timeout; /* read timeout */ \ + short curenv; /* current subshell number */ \ + short jobenv; /* subshell number for jobs */ \ + int nextprompt; /* next prompt is PS<nextprompt> */ \ + Namval_t *bltin_cmds; /* pointer to built-in commands */ \ + Namval_t *posix_fun; /* points to last name() function */ \ + int infd; /* input file descriptor */ \ + char *outbuff; /* pointer to output buffer */ \ + char *errbuff; /* pointer to stderr buffer */ \ + char *prompt; /* pointer to prompt string */ \ + char *shname; /* shell name */ \ + char *shpath; /* path name of shell */ \ + char *user; /* name of real user for pfsh */ \ + char *comdiv; /* points to sh -c argument */ \ + char *prefix; /* prefix for compound assignment */ \ + sigjmp_buf *jmplist; /* longjmp return stack */ \ + char **sigmsg; /* points to signal messages */ \ + int oldexit; \ + uid_t userid,euserid; /* real and effective user id */ \ + gid_t groupid,egroupid;/* real and effective group id */ \ + pid_t pid; /* process id of shell */ \ + pid_t bckpid; /* background process id */ \ + pid_t cpid; \ + int32_t ppid; /* parent process id of shell */ \ + int topfd; \ + int sigmax; /* maximum number of signals */ \ + int savesig; \ + unsigned char *sigflag; /* pointer to signal states */ \ + char intrap; \ + char login_sh; \ + char lastbase; \ + char forked; \ + char binscript; \ + char deftype; \ + char used_pos; /* used postional parameter */\ + unsigned char lastsig; /* last signal received */ \ + char *readscript; /* set before reading a script */ \ + int *inpipe; /* input pipe pointer */ \ + int *outpipe; /* output pipe pointer */ \ + int cpipe[2]; \ + int coutpipe; \ + int inuse_bits; \ + struct argnod *envlist; \ + struct dolnod *arglist; \ + int fn_depth; \ + int dot_depth; \ + int hist_depth; \ + int xargmin; \ + int xargmax; \ + int xargexit; \ + mode_t mask; \ + long nforks; \ + Env_t *env; \ + void *init_context; \ + void *mac_context; \ + void *lex_context; \ + void *arg_context; \ + void *ed_context; \ + void *job_context; \ + void *pathlist; \ + void *defpathlist; \ + void *cdpathlist; \ + char **argaddr; \ + void *optlist; \ + int optcount ; \ + struct sh_scoped global; \ + struct checkpt checkbase; \ + Shinit_f userinit; \ + Shbltin_f bltinfun; \ + Shwait_f waitevent; \ + char *cur_line; \ + char *rcfile; \ + char **login_files; \ + short offsets[10]; \ + Sfio_t **sftable; \ + unsigned char *fdstatus; \ + const char *pwd; \ + History_t *hist_ptr; \ + char universe; \ + void *jmpbuffer; \ + void *mktype; \ + Sfio_t *strbuf; \ + Dt_t *last_root; \ + char ifstable[256]; \ + Shopt_t offoptions; + +#include <shell.h> + + +/* error exits from various parts of shell */ +#define NIL(type) ((type)0) + +#define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x))) + +#define exitset() (sh.savexit=sh.exitval) + +#ifndef SH_DICT +#define SH_DICT (void*)e_dict +#endif + +#ifndef SH_CMDLIB_DIR +#define SH_CMDLIB_DIR "/opt/ast/bin" +#endif + +/* states */ +/* low numbered states are same as options */ +#define SH_NOFORK 0 /* set when fork not necessary, not a state */ +#define SH_COMPLETE 0 /* set for command completion */ +#define SH_FORKED 7 /* set when process has been forked */ +#define SH_PROFILE 8 /* set when processing profiles */ +#define SH_NOALIAS 9 /* do not expand non-exported aliases */ +#define SH_NOTRACK 10 /* set to disable sftrack() function */ +#define SH_STOPOK 11 /* set for stopable builtins */ +#define SH_GRACE 12 /* set for timeout grace period */ +#define SH_TIMING 13 /* set while timing pipelines */ +#define SH_DEFPATH 14 /* set when using default path */ +#define SH_INIT 15 /* set when initializing the shell */ +#define SH_TTYWAIT 16 /* waiting for keyboard input */ +#define SH_FCOMPLETE 17 /* set for filename completion */ +#define SH_PREINIT 18 /* set with SH_INIT before parsing options */ + +#define SH_BASH 41 +#define SH_BRACEEXPAND 42 +#define SH_POSIX 46 +#define SH_MULTILINE 47 + +#define SH_NOPROFILE 78 +#define SH_NOUSRPROFILE 79 +#define SH_LOGIN_SHELL 67 +#define SH_COMMANDLINE 0x100 +#define SH_BASHEXTRA 0x200 +#define SH_BASHOPT 0x400 + +#define SH_ID "ksh" /* ksh id */ +#define SH_STD "sh" /* standard sh id */ + +/* defines for sh_type() */ + +#define SH_TYPE_SH 001 +#define SH_TYPE_KSH 002 +#define SH_TYPE_BASH 004 +#define SH_TYPE_LOGIN 010 +#define SH_TYPE_PROFILE 020 +#define SH_TYPE_RESTRICTED 040 + +#if SHOPT_BASH +# ifndef SHOPT_HISTEXPAND +# define SHOPT_HISTEXPAND 1 +# endif +/* + * define for all the bash options + */ +# define SH_CDABLE_VARS 51 +# define SH_CDSPELL 52 +# define SH_CHECKHASH 53 +# define SH_CHECKWINSIZE 54 +# define SH_CMDHIST 55 +# define SH_DOTGLOB 56 +# define SH_EXECFAIL 57 +# define SH_EXPAND_ALIASES 58 +# define SH_EXTGLOB 59 +# define SH_HOSTCOMPLETE 63 +# define SH_HUPONEXIT 64 +# define SH_INTERACTIVE_COMM 65 +# define SH_LITHIST 66 +# define SH_MAILWARN 68 +# define SH_NOEMPTYCMDCOMPL 69 +# define SH_NOCASEGLOB 70 +# define SH_NULLGLOB 71 +# define SH_PHYSICAL 45 +# define SH_PROGCOMP 72 +# define SH_PROMPTVARS 73 +# define SH_RESTRICTED2 74 +# define SH_SHIFT_VERBOSE 75 +# define SH_SOURCEPATH 76 +# define SH_XPG_ECHO 77 +#endif + +#if SHOPT_HISTEXPAND +# define SH_HISTAPPEND 60 +# define SH_HISTEXPAND 43 +# define SH_HISTORY2 44 +# define SH_HISTREEDIT 61 +# define SH_HISTVERIFY 62 +#endif + +#ifndef PIPE_BUF +# define PIPE_BUF 512 +#endif + +#define MATCH_MAX 64 + +extern int sh_addlib(void*); +extern void *sh_argopen(Shell_t*); +extern Namval_t *sh_assignok(Namval_t*,int); +extern char *sh_checkid(char*,char*); +extern int sh_debug(const char*,const char*,const char*,char *const[],int); +extern int sh_echolist(Sfio_t*, int, char**); +extern struct argnod *sh_endword(int); +extern char **sh_envgen(void); +#if SHOPT_ENV +extern void sh_envput(Env_t*, Namval_t*); +#endif +extern void sh_envnolocal(Namval_t*,void*); +extern Sfdouble_t sh_arith(const char*); +extern void *sh_arithcomp(char*); +extern pid_t sh_fork(int,int*); +extern pid_t _sh_fork(pid_t, int ,int*); +extern char *sh_mactrim(char*,int); +extern int sh_macexpand(struct argnod*,struct argnod**,int); +extern void sh_machere(Sfio_t*, Sfio_t*, char*); +extern void *sh_macopen(Shell_t*); +extern char *sh_macpat(struct argnod*,int); +extern char *sh_mactry(char*); +extern void sh_printopts(Shopt_t,int,Shopt_t*); +extern int sh_readline(Shell_t*,char**,int,int,long); +extern Sfio_t *sh_sfeval(char*[]); +extern void sh_setmatch(const char*,int,int,int[]); +extern Dt_t *sh_subaliastree(int); +extern Dt_t *sh_subfuntree(int); +extern int sh_subsavefd(int); +extern void sh_subtmpfile(void); +extern char *sh_substitute(const char*,const char*,char*); +extern const char *_sh_translate(const char*); +extern int sh_trace(char*[],int); +extern void sh_trim(char*); +extern int sh_type(const char*); +extern void sh_utol(const char*, char*); +extern int sh_whence(char**,int); + +#ifndef ERROR_dictionary +# define ERROR_dictionary(s) (s) +#endif +#define sh_translate(s) _sh_translate(ERROR_dictionary(s)) + +#define WBITS (sizeof(long)*8) +#define WMASK (0xff) + +#define is_option(s,x) ((s)->v[((x)&WMASK)/WBITS] & (1L << ((x) % WBITS))) +#define on_option(s,x) ((s)->v[((x)&WMASK)/WBITS] |= (1L << ((x) % WBITS))) +#define off_option(s,x) ((s)->v[((x)&WMASK)/WBITS] &= ~(1L << ((x) % WBITS))) +#define sh_isoption(x) is_option(&sh.options,x) +#define sh_onoption(x) on_option(&sh.options,x) +#define sh_offoption(x) off_option(&sh.options,x) + + +#define sh_state(x) ( 1<<(x)) +#define sh_isstate(x) (sh.st.states&sh_state(x)) +#define sh_onstate(x) (sh.st.states |= sh_state(x)) +#define sh_offstate(x) (sh.st.states &= ~sh_state(x)) +#define sh_getstate() (sh.st.states) +#define sh_setstate(x) (sh.st.states = (x)) + +#define sh_sigcheck() do{if(sh.trapnote&SH_SIGSET)sh_exit(SH_EXITSIG);} while(0) + +extern int32_t sh_mailchk; +extern const char e_dict[]; + +/* sh_printopts() mode flags -- set --[no]option by default */ + +#define PRINT_VERBOSE 0x01 /* option on|off list */ +#define PRINT_ALL 0x02 /* list unset iptions too */ +#define PRINT_NO_HEADER 0x04 /* omit listing header */ +#define PRINT_SHOPT 0x08 /* shopt -s|-u */ +#define PRINT_TABLE 0x10 /* table of all options */ diff --git a/usr/src/lib/libshell/common/include/edit.h b/usr/src/lib/libshell/common/include/edit.h new file mode 100644 index 0000000000..ddbd45c24b --- /dev/null +++ b/usr/src/lib/libshell/common/include/edit.h @@ -0,0 +1,257 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef SEARCHSIZE +/* + * edit.h - common data structure for vi and emacs edit options + * + * David Korn + * AT&T Labs + * + */ + +#define SEARCHSIZE 80 + +#include "FEATURE/options" +#include "FEATURE/locale" +#if !SHOPT_VSH && !SHOPT_ESH +# define ed_winsize() (SEARCHSIZE) +#else + +#if !KSHELL +# include <setjmp.h> +# include <sig.h> +# include <ctype.h> +#endif /* KSHELL */ + +#include "FEATURE/setjmp" +#include "terminal.h" + +#define STRIP 0377 +#define LOOKAHEAD 80 + +#if SHOPT_MULTIBYTE +# ifndef ESS_MAXCHAR +# include "national.h" +# endif /* ESS_MAXCHAR */ + typedef wchar_t genchar; +# define CHARSIZE (sizeof(wchar_t)<=2?3:sizeof(wchar_t)) +#else + typedef char genchar; +# define CHARSIZE 1 +#endif /* SHOPT_MULTIBYTE */ + +#define TABSIZE 8 +#define PRSIZE 160 +#define MAXLINE 1024 /* longest edit line permitted */ + +typedef struct _edit_pos +{ + unsigned short line; + unsigned short col; +} Edpos_t; + +typedef struct edit +{ + sigjmp_buf e_env; + int e_kill; + int e_erase; + int e_werase; + int e_eof; + int e_lnext; + int e_fchar; + int e_plen; /* length of prompt string */ + int e_crlf; /* zero if cannot return to beginning of line */ + int e_llimit; /* line length limit */ + int e_hline; /* current history line number */ + int e_hloff; /* line number offset for command */ + int e_hismin; /* minimum history line number */ + int e_hismax; /* maximum history line number */ + int e_raw; /* set when in raw mode or alt mode */ + int e_cur; /* current line position */ + int e_eol; /* end-of-line position */ + int e_pcur; /* current physical line position */ + int e_peol; /* end of physical line position */ + int e_mode; /* edit mode */ + int e_lookahead; /* index in look-ahead buffer */ + int e_repeat; + int e_saved; + int e_fcol; /* first column */ + int e_ucol; /* column for undo */ + int e_wsize; /* width of display window */ + char *e_outbase; /* pointer to start of output buffer */ + char *e_outptr; /* pointer to position in output buffer */ + char *e_outlast; /* pointer to end of output buffer */ + genchar *e_inbuf; /* pointer to input buffer */ + char *e_prompt; /* pointer to buffer containing the prompt */ + genchar *e_ubuf; /* pointer to the undo buffer */ + genchar *e_killbuf; /* pointer to delete buffer */ + char e_search[SEARCHSIZE]; /* search string */ + genchar *e_Ubuf; /* temporary workspace buffer */ + genchar *e_physbuf; /* temporary workspace buffer */ + int e_lbuf[LOOKAHEAD];/* pointer to look-ahead buffer */ + int e_fd; /* file descriptor */ + int e_ttyspeed; /* line speed, also indicates tty parms are valid */ + int e_tabcount; +#ifdef _hdr_utime + ino_t e_tty_ino; + dev_t e_tty_dev; + char *e_tty; +#endif +#if SHOPT_OLDTERMIO + char e_echoctl; + char e_tcgeta; + struct termio e_ott; +#endif +#if SHOPT_MULTIBYTE + int e_curchar; + int e_cursize; +#endif + int *e_globals; /* global variables */ + genchar *e_window; /* display window image */ + char e_inmacro; /* processing macro expansion */ +#if KSHELL + char e_vi_insert[2]; /* for sh_keytrap */ + int32_t e_col; /* for sh_keytrap */ +#else + char e_prbuff[PRSIZE]; /* prompt buffer */ +#endif /* KSHELL */ + struct termios e_ttyparm; /* initial tty parameters */ + struct termios e_nttyparm; /* raw tty parameters */ + struct termios e_savetty; /* saved terminal state */ + int e_savefd; /* file descriptor for saved terminal state */ + char e_macro[4]; /* macro buffer */ + void *e_vi; /* vi specific data */ + void *e_emacs; /* emacs specific data */ + Shell_t *sh; /* interpreter pointer */ + char *e_stkptr; /* saved stack pointer */ + int e_stkoff; /* saved stack offset */ + char **e_clist; /* completion list after <ESC>= */ + int e_nlist; /* number of elements on completion list */ + int e_multiline; /* allow multiple lines for editing */ + int e_winsz; /* columns in window */ + Edpos_t e_curpos; /* cursor line and column */ + Namval_t *e_default; /* variable containing default value */ + Namval_t *e_term; /* TERM variable */ + char e_termname[80]; /* terminal name */ +} Edit_t; + +#undef MAXWINDOW +#define MAXWINDOW 300 /* maximum width window */ +#define FAST 2 +#define SLOW 1 +#define ESC cntl('[') +#define UEOF -2 /* user eof char synonym */ +#define UINTR -3 /* user intr char synonym */ +#define UERASE -4 /* user erase char synonym */ +#define UKILL -5 /* user kill char synonym */ +#define UWERASE -6 /* user word erase char synonym */ +#define ULNEXT -7 /* user next literal char synonym */ + +#if ( 'a' == 97) /* ASCII? */ +# define cntl(x) (x&037) +#else +# define cntl(c) (c=='D'?55:(c=='E'?45:(c=='F'?46:(c=='G'?'\a':(c=='H'?'\b': \ + (c=='I'?'\t':(c=='J'?'\n':(c=='T'?60:(c=='U'?61:(c=='V'?50: \ + (c=='W'?38:(c=='Z'?63:(c=='['?39:(c==']'?29: \ + (c<'J'?c+1-'A':(c+10-'J')))))))))))))))) +#endif + +#if !KSHELL +# define STRIP 0377 +# define GMACS 1 +# define EMACS 2 +# define VIRAW 4 +# define EDITVI 8 +# define NOHIST 16 +# define EDITMASK 15 +# define is_option(m) (opt_flag&(m)) + extern char opt_flag; +# ifdef SYSCALL +# define read(fd,buff,n) syscall(3,fd,buff,n) +# else +# define read(fd,buff,n) rEAd(fd,buff,n) +# endif /* SYSCALL */ +#endif /* KSHELL */ + +extern void ed_crlf(Edit_t*); +extern void ed_putchar(Edit_t*, int); +extern void ed_ringbell(void); +extern void ed_setup(Edit_t*,int, int); +extern void ed_flush(Edit_t*); +extern int ed_getchar(Edit_t*,int); +extern int ed_virt_to_phys(Edit_t*,genchar*,genchar*,int,int,int); +extern int ed_window(void); +extern void ed_ungetchar(Edit_t*,int); +extern int ed_viread(void*, int, char*, int, int); +extern int ed_read(void*, int, char*, int, int); +extern int ed_emacsread(void*, int, char*, int, int); +extern Edpos_t ed_curpos(Edit_t*, genchar*, int, int, Edpos_t); +extern int ed_setcursor(Edit_t*, genchar*, int, int, int); +#if KSHELL + extern int ed_macro(Edit_t*,int); + extern int ed_expand(Edit_t*, char[],int*,int*,int,int); + extern int ed_fulledit(Edit_t*); + extern void *ed_open(Shell_t*); +#endif /* KSHELL */ +# if SHOPT_MULTIBYTE + extern int ed_internal(const char*, genchar*); + extern int ed_external(const genchar*, char*); + extern void ed_gencpy(genchar*,const genchar*); + extern void ed_genncpy(genchar*,const genchar*,int); + extern int ed_genlen(const genchar*); + extern int ed_setwidth(const char*); +# endif /* SHOPT_MULTIBYTE */ + +extern const char e_runvi[]; +#if !KSHELL + extern const char e_version[]; +#endif /* KSHELL */ + +#if SHOPT_HISTEXPAND + +/* flags */ + +#define HIST_EVENT 0x1 /* event designator seen */ +#define HIST_QUESTION 0x2 /* question mark event designator */ +#define HIST_HASH 0x4 /* hash event designator */ +#define HIST_WORDDSGN 0x8 /* word designator seen */ +#define HIST_QUICKSUBST 0x10 /* quick substition designator seen */ +#define HIST_SUBSTITUTE 0x20 /* for substition loop */ +#define HIST_NEWLINE 0x40 /* newline in squashed white space */ + +/* modifier flags */ + +#define HIST_PRINT 0x100 /* print new command */ +#define HIST_QUOTE 0x200 /* quote resulting history line */ +#define HIST_QUOTE_BR 0x400 /* quote every word on space break */ +#define HIST_GLOBALSUBST 0x800 /* apply substition globally */ + +#define HIST_ERROR 0x1000 /* an error ocurred */ + +/* flags to be returned */ + +#define HIST_FLAG_RETURN_MASK (HIST_EVENT|HIST_PRINT|HIST_ERROR) + +extern int hist_expand(const char *, char **); +#endif + +#endif +#endif diff --git a/usr/src/lib/libshell/common/include/env.h b/usr/src/lib/libshell/common/include/env.h new file mode 100644 index 0000000000..f888913c34 --- /dev/null +++ b/usr/src/lib/libshell/common/include/env.h @@ -0,0 +1,50 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef _ENV_H +#define _ENV_H 1 + +#ifdef _BLD_env +# ifdef __EXPORT__ +# define export __EXPORT__ +# endif +#else + typedef void *Env_t; +#endif + +/* for use with env_open */ +#define ENV_STABLE (-1) + +/* for third agument to env_add */ +#define ENV_MALLOCED 1 +#define ENV_STRDUP 2 + +extern void env_close(Env_t*); +extern int env_add(Env_t*, const char*, int); +extern int env_delete(Env_t*, const char*); +extern char **env_get(Env_t*); +extern Env_t *env_open(char**,int); +extern Env_t *env_scope(Env_t*,int); + +#undef extern + +#endif + + diff --git a/usr/src/lib/libshell/common/include/fault.h b/usr/src/lib/libshell/common/include/fault.h new file mode 100644 index 0000000000..107b337f68 --- /dev/null +++ b/usr/src/lib/libshell/common/include/fault.h @@ -0,0 +1,124 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef SH_SIGBITS +/* + * UNIX shell + * S. R. Bourne + * Rewritten by David Korn + * + */ + +#include <sig.h> +#include <setjmp.h> +#include <error.h> +#include <sfio.h> +#include "FEATURE/setjmp" +#include "FEATURE/sigfeatures" + +#ifndef SIGWINCH +# ifdef SIGWIND +# define SIGWINCH SIGWIND +# else +# ifdef SIGWINDOW +# define SIGWINCH SIGWINDOW +# endif +# endif +#endif + +typedef void (*SH_SIGTYPE)(int,void(*)(int)); + +#define SH_FORKLIM 16 /* fork timeout interval */ + +#define SH_TRAP 0200 /* bit for internal traps */ +#define SH_ERRTRAP 0 /* trap for non-zero exit status */ +#define SH_KEYTRAP 1 /* trap for keyboard event */ +#define SH_DEBUGTRAP 4 /* must be last internal trap */ + +#define SH_SIGBITS 8 +#define SH_SIGFAULT 1 /* signal handler is sh_fault */ +#define SH_SIGOFF 2 /* signal handler is SIG_IGN */ +#define SH_SIGSET 4 /* pending signal */ +#define SH_SIGTRAP 010 /* pending trap */ +#define SH_SIGDONE 020 /* default is exit */ +#define SH_SIGIGNORE 040 /* default is ingore signal */ +#define SH_SIGINTERACTIVE 0100 /* handle interactive specially */ +#define SH_SIGTSTP 0200 /* tstp signal received */ +#define SH_SIGALRM 0200 /* timer alarm received */ +#define SH_SIGTERM SH_SIGOFF /* term signal received */ + +/* + * These are longjmp values + */ + +#define SH_JMPDOT 2 +#define SH_JMPEVAL 3 +#define SH_JMPTRAP 4 +#define SH_JMPIO 5 +#define SH_JMPCMD 6 +#define SH_JMPFUN 7 +#define SH_JMPERRFN 8 +#define SH_JMPSUB 9 +#define SH_JMPERREXIT 10 +#define SH_JMPEXIT 11 +#define SH_JMPSCRIPT 12 + +struct openlist +{ + Sfio_t *strm; + struct openlist *next; +}; + +struct checkpt +{ + sigjmp_buf buff; + sigjmp_buf *prev; + int topfd; + int mode; + struct openlist *olist; +#if (ERROR_VERSION >= 20030214L) + Error_context_t err; +#else + struct errorcontext err; +#endif +}; + +#define sh_pushcontext(bp,n) ( (bp)->mode=(n) , (bp)->olist=0, \ + (bp)->topfd=sh.topfd, (bp)->prev=sh.jmplist, \ + (bp)->err = *ERROR_CONTEXT_BASE, \ + sh.jmplist = (sigjmp_buf*)(&(bp)->buff) \ + ) +#define sh_popcontext(bp) (sh.jmplist=(bp)->prev, errorpop(&((bp)->err))) + +extern void sh_fault(int); +extern void sh_done(int); +extern void sh_chktrap(void); +extern void sh_sigclear(int); +extern void sh_sigdone(void); +extern void sh_siginit(void); +extern void sh_sigtrap(int); +extern void sh_sigreset(int); +extern void sh_timetraps(void); +extern void *sh_timeradd(unsigned long,int ,void (*)(void*),void*); +extern void timerdel(void*); + +extern const char e_alarm[]; + +#endif /* !SH_SIGBITS */ diff --git a/usr/src/lib/libshell/common/include/fcin.h b/usr/src/lib/libshell/common/include/fcin.h new file mode 100644 index 0000000000..c147a8fdef --- /dev/null +++ b/usr/src/lib/libshell/common/include/fcin.h @@ -0,0 +1,62 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef fcgetc +/* + * David Korn + * AT&T Labs + * + * Fast character input with sfio text streams and strings + * + */ + +#include <sfio.h> + +typedef struct _fcin +{ + Sfio_t *_fcfile; /* input file pointer */ + unsigned char *fcbuff; /* pointer to input buffer */ + unsigned char *fclast; /* pointer to end of input buffer */ + unsigned char *fcptr; /* pointer to next input char */ + unsigned char fcchar; /* saved character */ + void (*fcfun)(Sfio_t*,const char*,int); /* advance function */ + int fcleft; /* for multibyte boundary */ + Sfoff_t fcoff; /* offset for last read */ +} Fcin_t; + +#define fcfile() (_Fcin._fcfile) +#define fcgetc(c) (((c=fcget()) || (c=fcfill())), c) +#define fcget() ((int)(*_Fcin.fcptr++)) +#define fcpeek(n) ((int)_Fcin.fcptr[n]) +#define fcseek(n) ((char*)(_Fcin.fcptr+=(n))) +#define fcfirst() ((char*)_Fcin.fcbuff) +#define fcsopen(s) (_Fcin._fcfile=(Sfio_t*)0,_Fcin.fcbuff=_Fcin.fcptr=(unsigned char*)(s)) +#define fctell() (_Fcin.fcoff + (_Fcin.fcptr-_Fcin.fcbuff)) +#define fcsave(x) (*(x) = _Fcin) +#define fcrestore(x) (_Fcin = *(x)) +extern int fcfill(void); +extern int fcfopen(Sfio_t*); +extern int fcclose(void); +void fcnotify(void(*)(Sfio_t*,const char*,int)); +extern int fcmbstate(const char*,int*,int*); + +extern Fcin_t _Fcin; /* used by macros */ + +#endif /* fcgetc */ diff --git a/usr/src/lib/libshell/common/include/history.h b/usr/src/lib/libshell/common/include/history.h new file mode 100644 index 0000000000..f97cd8b4a4 --- /dev/null +++ b/usr/src/lib/libshell/common/include/history.h @@ -0,0 +1,75 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef HIST_VERSION +/* + * Interface for history mechanism + * written by David Korn + * + */ + +#include <ast.h> + +#define HIST_CHAR '!' +#define HIST_VERSION 1 /* history file format version no. */ + +typedef struct +{ + Sfdisc_t histdisc; /* discipline for history */ + Sfio_t *histfp; /* history file stream pointer */ + char *histname; /* name of history file */ + int32_t histind; /* current command number index */ + int histsize; /* number of accessible history lines */ +#ifdef _HIST_PRIVATE + _HIST_PRIVATE +#endif /* _HIST_PRIVATE */ +} History_t; + +typedef struct +{ + int hist_command; + int hist_line; + int hist_char; +} Histloc_t; + +/* the following are readonly */ +extern const char hist_fname[]; + +extern int _Hist; +#define hist_min(hp) ((_Hist=((int)((hp)->histind-(hp)->histsize)))>=0?_Hist:0) +#define hist_max(hp) ((int)((hp)->histind)) +/* these are the history interface routines */ +extern int sh_histinit(void); +extern void hist_cancel(History_t*); +extern void hist_close(History_t*); +extern int hist_copy(char*, int, int, int); +extern void hist_eof(History_t*); +extern Histloc_t hist_find(History_t*,char*,int, int, int); +extern void hist_flush(History_t*); +extern void hist_list(History_t*,Sfio_t*, off_t, int, char*); +extern int hist_match(History_t*,off_t, char*, int*); +extern off_t hist_tell(History_t*,int); +extern off_t hist_seek(History_t*,int); +extern char *hist_word(char*, int, int); +#if SHOPT_ESH + extern Histloc_t hist_locate(History_t*,int, int, int); +#endif /* SHOPT_ESH */ + +#endif /* HIST_VERSION */ diff --git a/usr/src/lib/libshell/common/include/io.h b/usr/src/lib/libshell/common/include/io.h new file mode 100644 index 0000000000..b8881bee84 --- /dev/null +++ b/usr/src/lib/libshell/common/include/io.h @@ -0,0 +1,123 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * UNIX shell + * David Korn + * + */ + +#include <ast.h> +#include <sfio.h> + +#ifndef IOBSIZE +# define IOBSIZE SF_BUFSIZE +#endif /* IOBSIZE */ +#define IOMAXTRY 20 + +#ifndef SF_CLOSING +#define SF_CLOSING SF_CLOSE +#endif +#ifndef SF_APPENDWR +#define SF_APPENDWR SF_APPEND +#endif + +/* used for output of shell errors */ +#define ERRIO 2 + +#define IOREAD 001 +#define IOWRITE 002 +#define IODUP 004 +#define IOSEEK 010 +#define IONOSEEK 020 +#define IOTTY 040 +#define IOCLEX 0100 +#define IOCLOSE (IOSEEK|IONOSEEK) + +#define IOSUBSHELL 0x8000 /* must be larger than any file descriptor */ + +/* + * The remainder of this file is only used when compiled with shell + */ + +#if KSHELL + +#ifndef ARG_RAW + struct ionod; +#endif /* !ARG_RAW */ + +#define sh_inuse(f2) (sh.fdptrs[f2]) + +extern int sh_iocheckfd(int); +extern void sh_ioinit(void); +extern int sh_iomovefd(int); +extern int sh_iorenumber(int,int); +extern void sh_pclose(int[]); +extern void sh_iorestore(int,int); +#if defined(__EXPORT__) && defined(_BLD_DLL) && defined(_BLD_shell) + __EXPORT__ +#endif +extern Sfio_t *sh_iostream(int); +extern int sh_redirect(struct ionod*,int); +extern void sh_iosave(int,int); +extern void sh_iounsave(void); +extern int sh_chkopen(const char*); +extern int sh_ioaccess(int,int); +extern int sh_devtofd(const char*); +extern int sh_source(Shell_t*, Sfio_t*, const char*); + +/* the following are readonly */ +extern const char e_pexists[]; +extern const char e_query[]; +extern const char e_history[]; +extern const char e_argtype[]; +extern const char e_create[]; +extern const char e_tmpcreate[]; +extern const char e_exists[]; +extern const char e_file[]; +extern const char e_formspec[]; +extern const char e_badregexp[]; +extern const char e_open[]; +extern const char e_notseek[]; +extern const char e_noread[]; +extern const char e_badseek[]; +extern const char e_badpattern[]; +extern const char e_toomany[]; +extern const char e_pipe[]; +extern const char e_unknown[]; +extern const char e_devnull[]; +extern const char e_profile[]; +extern const char e_sysprofile[]; +#if SHOPT_SYSRC +extern const char e_sysrc[]; +#endif +#if SHOPT_BASH +#if SHOPT_SYSRC +extern const char e_bash_sysrc[]; +#endif +extern const char e_bash_rc[]; +extern const char e_bash_login[]; +extern const char e_bash_logout[]; +extern const char e_bash_profile[]; +#endif +extern const char e_stdprompt[]; +extern const char e_supprompt[]; +extern const char e_ambiguous[]; +#endif /* KSHELL */ diff --git a/usr/src/lib/libshell/common/include/jobs.h b/usr/src/lib/libshell/common/include/jobs.h new file mode 100644 index 0000000000..c4998eabca --- /dev/null +++ b/usr/src/lib/libshell/common/include/jobs.h @@ -0,0 +1,159 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef JOB_NFLAG +/* + * Interface to job control for shell + * written by David Korn + * + */ + +#define JOBTTY 2 + +#include <ast.h> +#include <sfio.h> +#ifndef SIGINT +# include <signal.h> +#endif /* !SIGINT */ +#include "FEATURE/options" + +#undef JOBS +#if defined(SIGCLD) && !defined(SIGCHLD) +# define SIGCHLD SIGCLD +#endif +#ifdef SIGCHLD +# define JOBS 1 +# include "terminal.h" +# ifdef FIOLOOKLD + /* Ninth edition */ + extern int tty_ld, ntty_ld; +# define OTTYDISC tty_ld +# define NTTYDISC ntty_ld +# endif /* FIOLOOKLD */ +#else +# undef SIGTSTP +# undef SH_MONITOR +# define SH_MONITOR 0 +# define job_set(x) +# define job_reset(x) +#endif + +struct process +{ + struct process *p_nxtjob; /* next job structure */ + struct process *p_nxtproc; /* next process in current job */ + pid_t p_pid; /* process id */ + pid_t p_pgrp; /* process group */ + pid_t p_fgrp; /* process group when stopped */ + short p_job; /* job number of process */ + unsigned short p_exit; /* exit value or signal number */ + unsigned short p_flag; /* flags - see below */ + int p_env; /* subshell environment number */ +#ifdef JOBS + off_t p_name; /* history file offset for command */ + struct termios p_stty; /* terminal state for job */ +#endif /* JOBS */ +}; + +struct jobs +{ + struct process *pwlist; /* head of process list */ + pid_t curpgid; /* current process gid id */ + pid_t parent; /* set by fork() */ + pid_t mypid; /* process id of shell */ + pid_t mypgid; /* process group id of shell */ + pid_t mytgid; /* terminal group id of shell */ + unsigned int in_critical; /* >0 => in critical region */ + int savesig; /* active signal */ + int numpost; /* number of posted jobs */ + short fd; /* tty descriptor number */ +#ifdef JOBS + int suspend; /* suspend character */ + int linedisc; /* line dicipline */ +#endif /* JOBS */ + char jobcontrol; /* turned on for real job control */ + char waitsafe; /* wait will not block */ + char waitall; /* wait for all jobs in pipe */ + char toclear; /* job table needs clearing */ + unsigned char *freejobs; /* free jobs numbers */ +}; + +/* flags for joblist */ +#define JOB_LFLAG 1 +#define JOB_NFLAG 2 +#define JOB_PFLAG 4 +#define JOB_NLFLAG 8 + +extern struct jobs job; + +#ifdef JOBS + +#define job_lock() (job.in_critical++) +#define job_unlock() do{if(!--job.in_critical&&job.savesig)job_reap(job.savesig);}while(0) + +extern const char e_jobusage[]; +extern const char e_done[]; +extern const char e_running[]; +extern const char e_coredump[]; +extern const char e_no_proc[]; +extern const char e_no_job[]; +extern const char e_jobsrunning[]; +extern const char e_nlspace[]; +extern const char e_access[]; +extern const char e_terminate[]; +extern const char e_no_jctl[]; +extern const char e_signo[]; +#ifdef SIGTSTP + extern const char e_no_start[]; +#endif /* SIGTSTP */ +#ifdef NTTYDISC + extern const char e_newtty[]; + extern const char e_oldtty[]; +#endif /* NTTYDISC */ +#endif /* JOBS */ + +/* + * The following are defined in jobs.c + */ + +extern void job_clear(void); +extern void job_bwait(char**); +extern int job_walk(Sfio_t*,int(*)(struct process*,int),int,char*[]); +extern int job_kill(struct process*,int); +extern void job_wait(pid_t); +extern int job_post(pid_t,pid_t); +extern void *job_subsave(void); +extern void job_subrestore(void*); +#ifdef JOBS + extern void job_init(int); + extern int job_close(void); + extern int job_list(struct process*,int); + extern int job_terminate(struct process*,int); + extern int job_switch(struct process*,int); + extern void job_fork(pid_t); + extern int job_reap(int); +#else +# define job_init(flag) +# define job_close() (0) +# define job_fork(p) +#endif /* JOBS */ + + +#endif /* !JOB_NFLAG */ diff --git a/usr/src/lib/libshell/common/include/lexstates.h b/usr/src/lib/libshell/common/include/lexstates.h new file mode 100644 index 0000000000..8e3cdbfef6 --- /dev/null +++ b/usr/src/lib/libshell/common/include/lexstates.h @@ -0,0 +1,151 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef S_BREAK +#define S_BREAK 1 /* end of token */ +#define S_EOF 2 /* end of buffer */ +#define S_NL 3 /* new-line when not a token */ +#define S_RES 4 /* first character of reserved word */ +#define S_NAME 5 /* other identifier characters */ +#define S_REG 6 /* non-special characters */ +#define S_TILDE 7 /* first char is tilde */ +#define S_PUSH 8 +#define S_POP 9 +#define S_BRACT 10 +#define S_LIT 11 /* literal quote character */ +#define S_NLTOK 12 /* new-line token */ +#define S_OP 13 /* operator character */ +#define S_PAT 14 /* pattern characters * and ? */ +#define S_EPAT 15 /* pattern char when followed by ( */ +#define S_EQ 16 /* assignment character */ +#define S_COM 17 /* comment character */ +#define S_MOD1 18 /* ${...} modifier character - old quoting */ +#define S_MOD2 19 /* ${...} modifier character - new quoting */ +#define S_ERR 20 /* invalid character in ${...} */ +#define S_SPC1 21 /* special prefix characters after $ */ +#define S_SPC2 22 /* special characters after $ */ +#define S_DIG 23 /* digit character after $*/ +#define S_ALP 24 /* alpahbetic character after $ */ +#define S_LBRA 25 /* left brace after $ */ +#define S_RBRA 26 /* right brace after $ */ +#define S_PAR 27 /* set for $( */ +#define S_ENDCH 28 /* macro expansion terminator */ +#define S_SLASH 29 /* / character terminates ~ expansion */ +#define S_COLON 30 /* for character : */ +#define S_LABEL 31 /* for goto label */ +#define S_EDOL 32 /* ends $identifier */ +#define S_BRACE 33 /* left brace */ +#define S_DOT 34 /* . char */ +#define S_META 35 /* | & ; < > inside ${...} reserved for future use */ +#define S_SPACE S_BREAK /* IFS space characters */ +#define S_DELIM S_RES /* IFS delimter characters */ +#define S_MBYTE S_NAME /* IFS first byte of multi-byte char */ +#define S_BLNK 36 /* space or tab */ +/* The following must be the highest numbered states */ +#define S_QUOTE 37 /* double quote character */ +#define S_GRAVE 38 /* old comsub character */ +#define S_ESC 39 /* escape character */ +#define S_DOL 40 /* $ subsitution character */ +#define S_ESC2 41 /* escape character inside '...' */ + +/* These are the lexical state table names */ +#define ST_BEGIN 0 +#define ST_NAME 1 +#define ST_NORM 2 +#define ST_LIT 3 +#define ST_QUOTE 4 +#define ST_NESTED 5 +#define ST_DOL 6 +#define ST_BRACE 7 +#define ST_DOLNAME 8 +#define ST_MACRO 9 +#define ST_QNEST 10 +#define ST_NONE 11 + +#include "FEATURE/locale" + +#if _hdr_wchar +# include <wchar.h> +# if _hdr_wctype +# include <wctype.h> +# undef isalpha +# define isalpha(x) iswalpha(x) +# if defined(iswblank) || _lib_iswblank +# undef isblank +# define isblank(x) iswblank(x) +# else +# if _lib_wctype && _lib_iswctype +# define _lib_iswblank -1 +# undef isblank +# define isblank(x) local_iswblank(x) + extern int local_iswblank(wchar_t); +# endif +# endif +# endif +#endif +#ifndef isblank +# define isblank(x) ((x)==' '||(x)=='\t') +#endif + +#undef LEN +#if SHOPT_MULTIBYTE + static int NXT, LEN; +# define isaname(c) ((c)>0xff?isalpha(c): sh_lexstates[ST_NAME][(c)]==0) +# define isaletter(c) ((c)>0xff?isalpha(c): sh_lexstates[ST_DOL][(c)]==S_ALP && (c)!='.') +#else +# undef mbwide +# define mbwide() (0) +# define LEN 1 +# define isaname(c) (sh_lexstates[ST_NAME][c]==0) +# define isaletter(c) (sh_lexstates[ST_DOL][c]==S_ALP && (c)!='.') +#endif +#define STATE(s,c) (mbwide()?(c=fcmbstate(s,&NXT,&LEN),NXT):s[c=fcget()]) +#define isadigit(c) (sh_lexstates[ST_DOL][c]==S_DIG) +#define isastchar(c) ((c)=='@' || (c)=='*') +#define isexp(c) (sh_lexstates[ST_MACRO][c]==S_PAT||(c)=='$'||(c)=='`') +#define ismeta(c) (sh_lexstates[ST_NAME][c]==S_BREAK) + +extern char *sh_lexstates[ST_NONE]; +extern const char *sh_lexrstates[ST_NONE]; +extern const char e_lexversion[]; +extern const char e_lexspace[]; +extern const char e_lexslash[]; +extern const char e_lexlabignore[]; +extern const char e_lexlabunknown[]; +extern const char e_lexsyntax1[]; +extern const char e_lexsyntax2[]; +extern const char e_lexsyntax3[]; +extern const char e_lexobsolete1[]; +extern const char e_lexobsolete2[]; +extern const char e_lexobsolete3[]; +extern const char e_lexobsolete4[]; +extern const char e_lexobsolete5[]; +extern const char e_lexobsolete6[]; +extern const char e_lexusebrace[]; +extern const char e_lexusequote[]; +extern const char e_lexescape[]; +extern const char e_lexquote[]; +extern const char e_lexnested[]; +extern const char e_lexbadchar[]; +extern const char e_lexlongquote[]; +extern const char e_lexfuture[]; +extern const char e_lexzerobyte[]; +extern const char e_lexemptyfor[]; +#endif diff --git a/usr/src/lib/libshell/common/include/name.h b/usr/src/lib/libshell/common/include/name.h new file mode 100644 index 0000000000..04ed23e7d0 --- /dev/null +++ b/usr/src/lib/libshell/common/include/name.h @@ -0,0 +1,208 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef _NV_PRIVATE +/* + * This is the implementation header file for name-value pairs + */ + +#define _NV_PRIVATE \ + Namfun_t *nvfun; /* pointer to trap functions */ \ + union Value nvalue; /* value field */ \ + char *nvenv; /* pointer to environment name */ + +#include <ast.h> +#include <cdt.h> +#include "shtable.h" + +/* Nodes can have all kinds of values */ +union Value +{ + const char *cp; + int *ip; + char c; + int i; + unsigned int u; + int32_t *lp; + Sflong_t *llp; /* for long long arithmetic */ + int16_t s; + double *dp; /* for floating point arithmetic */ + Sfdouble_t *ldp; /* for long floating point arithmetic */ + struct Namarray *array; /* for array node */ + struct Namval *np; /* for Namval_t node */ + union Value *up; /* for indirect node */ + struct Ufunction *rp; /* shell user defined functions */ + struct Namfun *funp; /* discipline pointer */ + struct Namref *nrp; /* name reference */ + int (*bfp)(int,char*[],void*);/* builtin entry point function pointer */ +}; + +#include "nval.h" + +/* used for arrays */ + +#define ARRAY_MAX (1L<<ARRAY_BITS) /* maximum number of elements in an array */ +#define ARRAY_MASK (ARRAY_MAX-1) /* For index values */ + +#define ARRAY_INCR 32 /* number of elements to grow when array + bound exceeded. Must be a power of 2 */ +#define ARRAY_FILL (8L<<ARRAY_BITS) /* used with nv_putsub() */ +#define ARRAY_NOCLONE (16L<<ARRAY_BITS) /* do not clone array disc */ +#define ARRAY_NOCHILD (32L<<ARRAY_BITS) /* skip compound arrays */ +#define ARRAY_SETSUB (64L<<ARRAY_BITS) /* set subscript */ +#define NV_ASETSUB 8 /* set subscript */ + +/* These flags are used as options to array_get() */ +#define ARRAY_ASSIGN 0 +#define ARRAY_LOOKUP 1 +#define ARRAY_DELETE 2 + + +struct Namref +{ + Namval_t *np; + Namval_t *table; + Dt_t *root; + char *sub; +}; + +/* This describes a user shell function node */ +struct Ufunction +{ + int *ptree; /* address of parse tree */ + int lineno; /* line number of function start */ + off_t hoffset; /* offset into source or history file */ + Namval_t *nspace; /* pointer to name space */ + char *fname; /* file name where function defined */ +}; + +#ifndef ARG_RAW + struct argnod; +#endif /* !ARG_RAW */ + +/* attributes of Namval_t items */ + +/* The following attributes are for internal use */ +#define NV_NOCHANGE (NV_EXPORT|NV_IMPORT|NV_RDONLY|NV_TAGGED|NV_NOFREE) +#define NV_ATTRIBUTES (~(NV_NOSCOPE|NV_ARRAY|NV_NOARRAY|NV_IDENT|NV_ASSIGN|NV_REF|NV_VARNAME)) +#define NV_PARAM NV_NODISC /* expansion use positional params */ + +/* This following are for use with nodes which are not name-values */ +#define NV_TYPE 0x1000000 +#define NV_FUNCTION (NV_RJUST|NV_FUNCT) /* value is shell function */ +#define NV_FPOSIX NV_LJUST /* posix function semantics */ +#define NV_FTMP NV_ZFILL /* function source in tmpfile */ + +#define NV_NOPRINT (NV_LTOU|NV_UTOL) /* do not print */ +#define NV_NOALIAS (NV_NOPRINT|NV_IMPORT) +#define NV_NOEXPAND NV_RJUST /* do not expand alias */ +#define NV_BLTIN (NV_NOPRINT|NV_EXPORT) +#define BLT_ENV (NV_RDONLY) /* non-stoppable, + * can modify enviornment */ +#define BLT_SPC (NV_LJUST) /* special built-ins */ +#define BLT_EXIT (NV_RJUST) /* exit value can be > 255 */ +#define BLT_DCL (NV_TAGGED) /* declaration command */ +#define nv_isref(n) (nv_isattr((n),NV_REF)==NV_REF) +#define nv_istable(n) (nv_isattr((n),NV_TABLE|NV_LJUST|NV_RJUST)==NV_TABLE) +#define is_abuiltin(n) (nv_isattr(n,NV_BLTIN)==NV_BLTIN) +#define is_afunction(n) (nv_isattr(n,NV_FUNCTION)==NV_FUNCTION) +#define nv_funtree(n) ((n)->nvalue.rp->ptree) +#define funptr(n) ((n)->nvalue.bfp) + +#define NV_SUBQUOTE (NV_ADD<<1) /* used with nv_endsubscript */ + +/* NAMNOD MACROS */ +/* ... for attributes */ + +#define nv_setattr(n,f) ((n)->nvflag = (f)) +#define nv_context(n) ((void*)(n)->nvfun) /* for builtins */ +/* The following are for name references */ +#define nv_refnode(n) ((n)->nvalue.nrp->np) +#define nv_reftree(n) ((n)->nvalue.nrp->root) +#define nv_reftable(n) ((n)->nvalue.nrp->table) +#define nv_refsub(n) ((n)->nvalue.nrp->sub) +#if SHOPT_OO +# define nv_class(np) (nv_isattr(np,NV_REF|NV_IMPORT)?0:(Namval_t*)((np)->nvenv)) +#endif /* SHOPT_OO */ + +/* ... etc */ + +#define nv_setsize(n,s) ((n)->nvsize = (s)) +#undef nv_size +#define nv_size(np) ((np)->nvsize) +#define nv_isnull(np) (!(np)->nvalue.cp && !(np)->nvfun && !nv_isattr(np,NV_SHORT)) + +/* ... for arrays */ + +#define array_elem(ap) ((ap)->nelem&ARRAY_MASK) +#define array_assoc(ap) ((ap)->fun) + +extern int array_maxindex(Namval_t*); +extern char *nv_endsubscript(Namval_t*, char*, int); +extern Namfun_t *nv_cover(Namval_t*); +extern Namarr_t *nv_arrayptr(Namval_t*); +extern int nv_setnotify(Namval_t*,char **); +extern int nv_unsetnotify(Namval_t*,char **); +extern void nv_setlist(struct argnod*, int); +extern void nv_optimize(Namval_t*); +extern void nv_outname(Sfio_t*,char*, int); +extern void nv_scope(struct argnod*); +extern void nv_unref(Namval_t*); +extern void _nv_unset(Namval_t*,int); +extern int nv_clone(Namval_t*, Namval_t*, int); +extern void *nv_diropen(const char*); +extern char *nv_dirnext(void*); +extern void nv_dirclose(void*); +extern char *nv_getvtree(Namval_t*, Namfun_t*); +extern void nv_attribute(Namval_t*, Sfio_t*, char*, int); +extern Namval_t *nv_bfsearch(const char*, Dt_t*, Namval_t**, char**); +extern Namval_t *nv_mkclone(Namval_t*); +extern Namval_t *nv_mktype(Namval_t**, int); +extern void nv_addnode(Namval_t*, int); +extern Namval_t *nv_parent(Namval_t*); +extern char *nv_getbuf(size_t); +extern Namval_t *nv_mount(Namval_t*, const char *name, Dt_t*); +extern Namval_t *nv_arraychild(Namval_t*, Namval_t*, int); +extern int nv_compare(Dt_t*, Void_t*, Void_t*, Dtdisc_t*); + +extern const Namdisc_t RESTRICTED_disc; +extern char nv_local; +extern Dtdisc_t _Nvdisc; +extern const char e_subscript[]; +extern const char e_nullset[]; +extern const char e_notset[]; +extern const char e_noparent[]; +extern const char e_readonly[]; +extern const char e_badfield[]; +extern const char e_restricted[]; +extern const char e_ident[]; +extern const char e_varname[]; +extern const char e_noalias[]; +extern const char e_noarray[]; +extern const char e_aliname[]; +extern const char e_badexport[]; +extern const char e_badref[]; +extern const char e_noref[]; +extern const char e_selfref[]; +extern const char e_envmarker[]; +extern const char e_badlocale[]; +extern const char e_loop[]; +extern const char e_redef[]; +#endif /* _NV_PRIVATE */ diff --git a/usr/src/lib/libshell/common/include/national.h b/usr/src/lib/libshell/common/include/national.h new file mode 100644 index 0000000000..837015011a --- /dev/null +++ b/usr/src/lib/libshell/common/include/national.h @@ -0,0 +1,37 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * national.h - definitions for multibyte character sets + * + * David Korn + * AT&T Labs + * + */ + +#if SHOPT_MULTIBYTE + +# ifndef MARKER +# define MARKER 0xdfff /* Must be invalid character */ +# endif + + extern int sh_strchr(const char*,const char*); + +#endif /* SHOPT_MULTIBYTE */ diff --git a/usr/src/lib/libshell/common/include/nval.h b/usr/src/lib/libshell/common/include/nval.h new file mode 100644 index 0000000000..6d02dc0a3b --- /dev/null +++ b/usr/src/lib/libshell/common/include/nval.h @@ -0,0 +1,305 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef NV_DEFAULT +/* + * David Korn + * AT&T Labs + * + * Interface definitions of structures for name-value pairs + * These structures are used for named variables, functions and aliases + * + */ + + +#include <ast.h> +#include <cdt.h> + +/* for compatibility with old hash library */ +#define Hashtab_t Dt_t +#define HASH_BUCKET 1 +#define HASH_NOSCOPE 2 +#define HASH_SCOPE 4 +#define hashscope(x) dtvnext(x) + +typedef struct Namval Namval_t; +typedef struct Namfun Namfun_t; +typedef struct Namdisc Namdisc_t; +typedef struct Nambfun Nambfun_t; +typedef struct Namarray Namarr_t; +typedef struct Nambltin Nambltin_t; +typedef struct Namtype Namtype_t; + +/* + * This defines the template for nodes that have their own assignment + * and or lookup functions + */ +struct Namdisc +{ + size_t dsize; + void (*putval)(Namval_t*, const char*, int, Namfun_t*); + char *(*getval)(Namval_t*, Namfun_t*); + Sfdouble_t (*getnum)(Namval_t*, Namfun_t*); + char *(*setdisc)(Namval_t*, const char*, Namval_t*, Namfun_t*); + Namval_t *(*createf)(Namval_t*, const char*, int, Namfun_t*); + Namfun_t *(*clonef)(Namval_t*, Namval_t*, int, Namfun_t*); + char *(*namef)(Namval_t*, Namfun_t*); + Namval_t *(*nextf)(Namval_t*, Dt_t*, Namfun_t*); + Namval_t *(*typef)(Namval_t*, Namfun_t*); + int (*readf)(Namval_t*, Sfio_t*, int, Namfun_t*); +}; + +struct Namfun +{ + const Namdisc_t *disc; + char nofree; + char funs; + unsigned short dsize; + Namfun_t *next; + char *last; + Namval_t *type; +}; + +struct Nambfun +{ + Namfun_t fun; + int num; + const char **bnames; + Namval_t *bltins[1]; +}; + +/* This is an array template header */ +struct Namarray +{ + Namfun_t hdr; + long nelem; /* number of elements */ + void *(*fun)(Namval_t*,const char*,int); /* associative arrays */ + Namval_t *parent; /* for multi-dimensional */ +}; + +/* Passed as third argument to a builtin when NV_BLTINOPT is set on node */ +struct Nambltin +{ + void *shp; + Namval_t *np; + void *ptr; + void *data; + int flags; +}; + +struct Namtype +{ + void *shp; + Namval_t *np; + const char *optstring; + void *optinfof; +}; + +/* attributes of name-value node attribute flags */ + +#define NV_DEFAULT 0 +/* This defines the attributes for an attributed name-value pair node */ +struct Namval +{ + Dtlink_t nvlink; /* space for cdt links */ + char *nvname; /* pointer to name of the node */ + unsigned short nvflag; /* attributes */ + unsigned short nvsize; /* size or base */ +#ifdef _NV_PRIVATE + _NV_PRIVATE +#else + Namfun_t *nvfun; + char *nvalue; + char *nvprivate; +#endif /* _NV_PRIVATE */ +}; + +#define NV_CLASS ".sh.type" +#define NV_MINSZ (sizeof(struct Namval)-sizeof(Dtlink_t)-sizeof(char*)) +#define nv_namptr(p,n) ((Namval_t*)((char*)(p)+(n)*NV_MINSZ-sizeof(Dtlink_t))) + +/* The following attributes are for internal use */ +#define NV_NOFREE 0x200 /* don't free the space when releasing value */ +#define NV_ARRAY 0x400 /* node is an array */ +#define NV_REF 0x4000 /* reference bit */ +#define NV_TABLE 0x800 /* node is a dictionary table */ +#define NV_IMPORT 0x1000 /* value imported from environment */ +#define NV_MINIMAL NV_IMPORT /* node does not contain all fields */ + +#define NV_INTEGER 0x2 /* integer attribute */ +/* The following attributes are valid only when NV_INTEGER is off */ +#define NV_LTOU 0x4 /* convert to uppercase */ +#define NV_UTOL 0x8 /* convert to lowercase */ +#define NV_ZFILL 0x10 /* right justify and fill with leading zeros */ +#define NV_RJUST 0x20 /* right justify and blank fill */ +#define NV_LJUST 0x40 /* left justify and blank fill */ +#define NV_BINARY 0x100 /* fixed size data buffer */ +#define NV_RAW NV_LJUST /* used only with NV_BINARY */ +#define NV_HOST (NV_RJUST|NV_LJUST) /* map to host filename */ + +/* The following attributes do not effect the value */ +#define NV_RDONLY 0x1 /* readonly bit */ +#define NV_EXPORT 0x2000 /* export bit */ +#define NV_TAGGED 0x8000 /* user define tag bit */ + +/* The following are used with NV_INTEGER */ +#define NV_SHORT (NV_RJUST) /* when integers are not long */ +#define NV_LONG (NV_UTOL) /* for long long and long double */ +#define NV_UNSIGN (NV_LTOU) /* for unsigned quantities */ +#define NV_DOUBLE (NV_ZFILL) /* for floating point */ +#define NV_EXPNOTE (NV_LJUST) /* for scientific notation */ + +/* options for nv_open */ + +#define NV_APPEND 0x10000 /* append value */ +#define NV_MOVE 0x20000 /* for use with nv_clone */ +#define NV_ADD 8 + /* add node if not found */ +#define NV_ASSIGN NV_NOFREE /* assignment is possible */ +#define NV_NOASSIGN 0 /* backward compatibility */ +#define NV_NOARRAY 0x200000 /* array name not possible */ +#define NV_IARRAY 0x400000 /* for indexed array */ +#define NV_NOREF NV_REF /* don't follow reference */ +#define NV_IDENT 0x80 /* name must be identifier */ +#define NV_VARNAME 0x20000 /* name must be ?(.)id*(.id) */ +#define NV_NOADD 0x40000 /* do not add node */ +#define NV_NOSCOPE 0x80000 /* look only in current scope */ +#define NV_NOFAIL 0x100000 /* return 0 on failure, no msg */ +#define NV_NODISC NV_IDENT /* ignore disciplines */ + +#define NV_FUNCT NV_IDENT /* option for nv_create */ +#define NV_BLTINOPT NV_ZFILL /* save state for optimization*/ + +#define NV_PUBLIC (~(NV_NOSCOPE|NV_ASSIGN|NV_IDENT|NV_VARNAME|NV_NOADD)) + +/* numeric types */ +#define NV_INT16 (NV_SHORT|NV_INTEGER) +#define NV_UINT16 (NV_UNSIGN|NV_SHORT|NV_INTEGER) +#define NV_INT32 (NV_INTEGER) +#define NV_UNT32 (NV_UNSIGN|NV_INTEGER) +#define NV_INT64 (NV_LONG|NV_INTEGER) +#define NV_UINT64 (NV_UNSIGN|NV_LONG|NV_INTEGER) +#define NV_FLOAT (NV_SHORT|NV_DOUBLE|NV_INTEGER) +#define NV_LDOUBLE (NV_LONG|NV_DOUBLE|NV_INTEGER) + +/* name-value pair macros */ +#define nv_isattr(np,f) ((np)->nvflag & (f)) +#define nv_onattr(n,f) ((n)->nvflag |= (f)) +#define nv_offattr(n,f) ((n)->nvflag &= ~(f)) +#define nv_isarray(np) (nv_isattr((np),NV_ARRAY)) + +/* The following are operations for associative arrays */ +#define NV_AINIT 1 /* initialize */ +#define NV_AFREE 2 /* free array */ +#define NV_ANEXT 3 /* advance to next subscript */ +#define NV_ANAME 4 /* return subscript name */ +#define NV_ADELETE 5 /* delete current subscript */ +#define NV_AADD 6 /* add subscript if not found */ +#define NV_ACURRENT 7 /* return current subscript Namval_t* */ + +/* The following are for nv_disc */ +#define NV_FIRST 1 +#define NV_LAST 2 +#define NV_POP 3 +#define NV_CLONE 4 + +/* The following are operations for nv_putsub() */ +#define ARRAY_BITS 24 +#define ARRAY_ADD (1L<<ARRAY_BITS) /* add subscript if not found */ +#define ARRAY_SCAN (2L<<ARRAY_BITS) /* For ${array[@]} */ +#define ARRAY_UNDEF (4L<<ARRAY_BITS) /* For ${array} */ + + +/* These are disciplines provided by the library for use with nv_discfun */ +#define NV_DCADD 0 /* used to add named disciplines */ +#define NV_DCRESTRICT 1 /* variable that are restricted in rsh */ + +#if defined(__EXPORT__) && defined(_DLL) +# ifdef _BLD_shell +# define extern __EXPORT__ +# else +# define extern __IMPORT__ +# endif /* _BLD_shell */ +#endif /* _DLL */ +/* prototype for array interface*/ +extern Namarr_t *nv_setarray(Namval_t*,void*(*)(Namval_t*,const char*,int)); +extern void *nv_associative(Namval_t*,const char*,int); +extern int nv_aindex(Namval_t*); +extern int nv_nextsub(Namval_t*); +extern char *nv_getsub(Namval_t*); +extern Namval_t *nv_putsub(Namval_t*, char*, long); +extern Namval_t *nv_opensub(Namval_t*); + +/* name-value pair function prototypes */ +extern int nv_adddisc(Namval_t*, const char**, Namval_t**); +extern int nv_clone(Namval_t*, Namval_t*, int); +extern void nv_close(Namval_t*); +extern void *nv_context(Namval_t*); +extern Namval_t *nv_create(const char*, Dt_t*, int,Namfun_t*); +extern Dt_t *nv_dict(Namval_t*); +extern Sfdouble_t nv_getn(Namval_t*, Namfun_t*); +extern Sfdouble_t nv_getnum(Namval_t*); +extern char *nv_getv(Namval_t*, Namfun_t*); +extern char *nv_getval(Namval_t*); +extern Namfun_t *nv_hasdisc(Namval_t*, const Namdisc_t*); +extern int nv_isnull(Namval_t*); +extern Namval_t *nv_lastdict(void); +extern void nv_newattr(Namval_t*,unsigned,int); +extern Namval_t *nv_open(const char*,Dt_t*,int); +extern void nv_putval(Namval_t*,const char*,int); +extern void nv_putv(Namval_t*,const char*,int,Namfun_t*); +extern int nv_scan(Dt_t*,void(*)(Namval_t*,void*),void*,int,int); +extern Namval_t *nv_scoped(Namval_t*); +extern char *nv_setdisc(Namval_t*,const char*,Namval_t*,Namfun_t*); +extern void nv_setref(Namval_t*, Dt_t*,int); +extern int nv_settype(Namval_t*, Namval_t*, int); +extern void nv_setvec(Namval_t*,int,int,char*[]); +extern void nv_setvtree(Namval_t*); +extern int nv_setsize(Namval_t*,int); +extern Namfun_t *nv_disc(Namval_t*,Namfun_t*,int); +extern void nv_unset(Namval_t*); +extern Namval_t *nv_search(const char *, Dt_t*, int); +extern void nv_unscope(void); +extern char *nv_name(Namval_t*); +extern Namval_t *nv_type(Namval_t*); +extern const Namdisc_t *nv_discfun(int); + +#ifdef _DLL +# undef extern +#endif /* _DLL */ + +#define nv_size(np) nv_setsize((np),-1) +#define nv_stack(np,nf) nv_disc(np,nf,0) + +#if 0 +/* + * The names of many functions were changed in early '95 + * Here is a mapping to the old names + */ +# define nv_istype(np) nv_isattr(np) +# define nv_newtype(np) nv_newattr(np) +# define nv_namset(np,a,b) nv_open(np,a,b) +# define nv_free(np) nv_unset(np) +# define nv_settype(np,a,b,c) nv_setdisc(np,a,b,c) +# define nv_search(np,a,b) nv_open(np,a,((b)?0:NV_NOADD)) +# define settype setdisc +#endif + +#endif /* NV_DEFAULT */ diff --git a/usr/src/lib/libshell/common/include/path.h b/usr/src/lib/libshell/common/include/path.h new file mode 100644 index 0000000000..2460be3cbf --- /dev/null +++ b/usr/src/lib/libshell/common/include/path.h @@ -0,0 +1,140 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef PATH_OFFSET + +/* + * UNIX shell path handling interface + * Written by David Korn + * These are the definitions for the lexical analyzer + */ + +#include "FEATURE/options" +#include <nval.h> + +#if !defined(SHOPT_SPAWN) +# if _UWIN || _use_spawnveg || !_lib_fork +# define SHOPT_SPAWN 1 +# endif +#endif /* !SHOPT_SPAWN */ + +#define PATH_PATH 0001 +#define PATH_FPATH 0002 +#define PATH_CDPATH 0004 +#define PATH_BFPATH 0010 +#define PATH_SKIP 0020 +#define PATH_BUILTIN_LIB 0040 +#define PATH_STD_DIR 0100 /* directory is on $(getconf PATH) */ + +#define PATH_OFFSET 2 /* path offset for path_join */ +#define MAXDEPTH (sizeof(char*)==2?64:4096) /* maximum recursion depth*/ + +/* + * path component structure for path searching + */ +typedef struct pathcomp +{ + struct pathcomp *next; + int refcount; + dev_t dev; + ino_t ino; + char *name; + char *lib; + char *blib; + void *bltin_lib; + unsigned short len; + unsigned short flags; + Shell_t *shp; +} Pathcomp_t; + +#ifndef ARG_RAW + struct argnod; +#endif /* !ARG_RAW */ + +/* pathname handling routines */ +extern void path_newdir(Pathcomp_t*); +extern Pathcomp_t *path_dirfind(Pathcomp_t*,const char*,int); +extern Pathcomp_t *path_unsetfpath(Pathcomp_t*); +extern Pathcomp_t *path_addpath(Pathcomp_t*,const char*,int); +extern Pathcomp_t *path_dup(Pathcomp_t*); +extern void path_delete(Pathcomp_t*); +extern void path_alias(Namval_t*,Pathcomp_t*); +extern Pathcomp_t *path_absolute(const char*, Pathcomp_t*); +extern char *path_basename(const char*); +extern char *path_fullname(const char*); +extern int path_expand(const char*, struct argnod**); +extern void path_exec(const char*,char*[],struct argnod*); +extern pid_t path_spawn(const char*,char*[],char*[],Pathcomp_t*,int); +#if defined(__EXPORT__) && defined(_BLD_DLL) && defined(_BLD_shell) +# define extern __EXPORT__ +#endif +extern int path_open(const char*,Pathcomp_t*); +extern Pathcomp_t *path_get(const char*); +#undef extern +extern char *path_pwd(int); +extern Pathcomp_t *path_nextcomp(Pathcomp_t*,const char*,Pathcomp_t*); +extern int path_search(const char*,Pathcomp_t*,int); +extern char *path_relative(const char*); +extern int path_complete(const char*, const char*,struct argnod**); +#if SHOPT_BRACEPAT + extern int path_generate(struct argnod*,struct argnod**); +#endif /* SHOPT_BRACEPAT */ + +/* constant strings needed for whence */ +extern const char e_timeformat[]; +extern const char e_badtformat[]; +extern const char e_dot[]; +extern const char e_pfsh[]; +extern const char e_pwd[]; +extern const char e_logout[]; +extern const char e_alphanum[]; +extern const char e_mailmsg[]; +extern const char e_suidprofile[]; +extern const char e_sysprofile[]; +extern const char e_traceprompt[]; +extern const char e_crondir[]; +#if SHOPT_SUID_EXEC + extern const char e_suidexec[]; +#endif /* SHOPT_SUID_EXEC */ +extern const char is_alias[]; +extern const char is_builtin[]; +extern const char is_builtver[]; +extern const char is_reserved[]; +extern const char is_talias[]; +extern const char is_xalias[]; +extern const char is_function[]; +extern const char is_ufunction[]; +#ifdef SHELLMAGIC + extern const char e_prohibited[]; +#endif /* SHELLMAGIC */ + +#if SHOPT_ACCT +# include "FEATURE/acct" +# ifdef _sys_acct + extern void sh_accinit(void); + extern void sh_accbegin(const char*); + extern void sh_accend(void); + extern void sh_accsusp(void); +# else +# undef SHOPT_ACCT +# endif /* _sys_acct */ +#endif /* SHOPT_ACCT */ + +#endif /*! PATH_OFFSET */ diff --git a/usr/src/lib/libshell/common/include/shell.h b/usr/src/lib/libshell/common/include/shell.h new file mode 100644 index 0000000000..66ccd20436 --- /dev/null +++ b/usr/src/lib/libshell/common/include/shell.h @@ -0,0 +1,245 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef SH_INTERACTIVE +/* + * David Korn + * AT&T Labs + * + * Interface definitions for shell command language + * + */ + +#include <cmd.h> +#include <cdt.h> +#ifdef _SH_PRIVATE +# include "name.h" +#else +# include <nval.h> +#endif /* _SH_PRIVATE */ + +#define SH_VERSION 20060510 + +#undef NOT_USED +#define NOT_USED(x) (&x,1) + +/* options */ +typedef struct +{ + unsigned long v[4]; +} +Shopt_t; + +typedef void (*Shinit_f)(int); +typedef int (*Shbltin_f)(int, char*[], void*); +typedef int (*Shwait_f)(int, long, int); + +union Shnode_u; +typedef union Shnode_u Shnode_t; + +#define SH_CFLAG 0 +#define SH_HISTORY 1 /* used also as a state */ +#define SH_ERREXIT 2 /* used also as a state */ +#define SH_VERBOSE 3 /* used also as a state */ +#define SH_MONITOR 4 /* used also as a state */ +#define SH_INTERACTIVE 5 /* used also as a state */ +#define SH_RESTRICTED 6 +#define SH_XTRACE 7 +#define SH_KEYWORD 8 +#define SH_NOUNSET 9 +#define SH_NOGLOB 10 +#define SH_ALLEXPORT 11 +#define SH_PFSH 12 +#define SH_IGNOREEOF 13 +#define SH_NOCLOBBER 14 +#define SH_MARKDIRS 15 +#define SH_BGNICE 16 +#define SH_VI 17 +#define SH_VIRAW 18 +#define SH_TFLAG 19 +#define SH_TRACKALL 20 +#define SH_SFLAG 21 +#define SH_NOEXEC 22 +#define SH_GMACS 24 +#define SH_EMACS 25 +#define SH_PRIVILEGED 26 +#define SH_SUBSHARE 27 /* subshell shares state with parent */ +#define SH_NOLOG 28 +#define SH_NOTIFY 29 +#define SH_DICTIONARY 30 +#define SH_PIPEFAIL 32 +#define SH_GLOBSTARS 33 +#define SH_XARGS 34 +#define SH_RC 35 +#define SH_SHOWME 36 + +/* + * passed as flags to builtins in Nambltin_t struct when BLT_OPTIM is on + */ +#define SH_BEGIN_OPTIM 0x1 +#define SH_END_OPTIM 0x2 + +/* The following type is used for error messages */ + +/* error messages */ +extern const char e_defpath[]; +extern const char e_found[]; +extern const char e_nospace[]; +extern const char e_format[]; +extern const char e_number[]; +extern const char e_restricted[]; +extern const char e_recursive[]; +extern char e_version[]; + +typedef struct sh_scope +{ + struct sh_scope *par_scope; + int argc; + char **argv; + char *cmdname; + char *filename; + int lineno; + Dt_t *var_tree; + struct sh_scope *self; +} Shscope_t; + +/* + * Saves the state of the shell + */ + +typedef struct sh_static +{ + Shopt_t options; /* set -o options */ + Dt_t *var_tree; /* for shell variables */ + Dt_t *fun_tree; /* for shell functions */ + Dt_t *alias_tree; /* for alias names */ + Dt_t *bltin_tree; /* for builtin commands */ + Shscope_t *topscope; /* pointer to top-level scope */ + int inlineno; /* line number of current input file */ + int exitval; /* most recent exit value */ + unsigned char trapnote; /* set when trap/signal is pending */ + char subshell; /* set for virtual subshell */ +#ifdef _SH_PRIVATE + _SH_PRIVATE +#endif /* _SH_PRIVATE */ +} Shell_t; + +/* flags for sh_parse */ +#define SH_NL 1 /* Treat new-lines as ; */ +#define SH_EOF 2 /* EOF causes syntax error */ + +/* symbolic values for sh_iogetiop */ +#define SH_IOCOPROCESS (-2) +#define SH_IOHISTFILE (-3) + +/* symbolic value for sh_fdnotify */ +#define SH_FDCLOSE (-1) + +#if defined(__EXPORT__) && defined(_DLL) +# ifdef _BLD_shell +# define extern __EXPORT__ +# endif /* _BLD_shell */ +#endif /* _DLL */ + +extern Dt_t *sh_bltin_tree(void); +extern void sh_subfork(void); +extern Shell_t *sh_init(int,char*[],Shinit_f); +extern int sh_reinit(char*[]); +extern int sh_eval(Sfio_t*,int); +extern void sh_delay(double); +extern void *sh_parse(Shell_t*, Sfio_t*,int); +extern int sh_trap(const char*,int); +extern int sh_fun(Namval_t*,Namval_t*, char*[]); +extern int sh_funscope(int,char*[],int(*)(void*),void*,int); +extern Sfio_t *sh_iogetiop(int,int); +extern int sh_main(int, char*[], void(*)(int)); +extern void sh_menu(Sfio_t*, int, char*[]); +extern Namval_t *sh_addbuiltin(const char*, int(*)(int, char*[],void*), void*); +extern char *sh_fmtq(const char*); +extern char *sh_fmtqf(const char*, int, int); +extern Sfdouble_t sh_strnum(const char*, char**, int); +extern int sh_access(const char*,int); +extern int sh_close(int); +extern int sh_dup(int); +extern void sh_exit(int); +extern int sh_fcntl(int, int, ...); +extern Sfio_t *sh_fd2sfio(int); +extern int (*sh_fdnotify(int(*)(int,int)))(int,int); +extern Shell_t *sh_getinterp(void); +extern int sh_open(const char*, int, ...); +extern int sh_openmax(void); +extern Sfio_t *sh_pathopen(const char*); +extern ssize_t sh_read(int, void*, size_t); +extern ssize_t sh_write(int, const void*, size_t); +extern off_t sh_seek(int, off_t, int); +extern int sh_pipe(int[]); +extern mode_t sh_umask(mode_t); +extern void *sh_waitnotify(Shwait_f); +extern Shscope_t *sh_getscope(int,int); +extern Shscope_t *sh_setscope(Shscope_t*); +extern void sh_sigcheck(void); +extern unsigned long sh_isoption(int); +extern unsigned long sh_onoption(int); +extern unsigned long sh_offoption(int); +extern int sh_waitsafe(void); +extern int sh_exec(const Shnode_t*,int); + +#if SHOPT_DYNAMIC + extern void **sh_getliblist(void); +#endif /* SHOPT_DYNAMIC */ + +/* + * direct access to sh is obsolete, use sh_getinterp() instead + */ +#if !defined(_SH_PRIVATE) && defined(__IMPORT__) && !defined(_BLD_shell) + extern __IMPORT__ Shell_t sh; +#else + extern Shell_t sh; +#endif + +#ifdef _DLL +# undef extern +#endif /* _DLL */ + +#ifndef _SH_PRIVATE +# define access(a,b) sh_access(a,b) +# define close(a) sh_close(a) +# define exit(a) sh_exit(a) +# define fcntl(a,b,c) sh_fcntl(a,b,c) +# define pipe(a) sh_pipe(a) +# define read(a,b,c) sh_read(a,b,c) +# define write(a,b,c) sh_write(a,b,c) +# define umask(a) sh_umask(a) +# define dup sh_dup +# if _lib_lseek64 +# define open64 sh_open +# define lseek64 sh_seek +# else +# define open sh_open +# define lseek sh_seek +# endif +#endif /* !_SH_PRIVATE */ + +#define SH_SIGSET 4 +#define SH_EXITSIG 0400 /* signal exit bit */ +#define SH_EXITMASK (SH_EXITSIG-1) /* normal exit status bits */ +#define SH_RUNPROG -1022 /* needs to be negative and < 256 */ + +#endif /* SH_INTERACTIVE */ diff --git a/usr/src/lib/libshell/common/include/shlex.h b/usr/src/lib/libshell/common/include/shlex.h new file mode 100644 index 0000000000..56de279d6c --- /dev/null +++ b/usr/src/lib/libshell/common/include/shlex.h @@ -0,0 +1,152 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef NOTSYM +/* + * UNIX shell + * Written by David Korn + * These are the definitions for the lexical analyzer + */ + +#include <cdt.h> +#include "FEATURE/options" +#include "shnodes.h" +#include "shtable.h" +#include "lexstates.h" + +struct shlex_t +{ + Shell_t *sh; /* pointer to the interpreter */ + struct argnod *arg; /* current word */ + struct ionod *heredoc; /* pending here document list */ + int token; /* current token number */ + int lastline; /* last line number */ + int lasttok; /* previous token number */ + int digits; /* numerical value with word token */ + char aliasok; /* on when alias is legal */ + char assignok; /* on when name=value is legal */ + int inlineno; /* saved value of sh.inlineno */ + int firstline; /* saved value of sh.st.firstline */ + int comsub; /* parsing command substitution */ +#if SHOPT_KIA + Sfio_t *kiafile; /* kia output file */ + Sfio_t *kiatmp; /* kia reference file */ + unsigned long script; /* script entity number */ + unsigned long fscript; /* script file entity number */ + unsigned long current; /* current entity number */ + unsigned long unknown; /* <unknown> entity number */ + off_t kiabegin; /* offset of first entry */ + char *scriptname; /* name of script file */ + Dt_t *entity_tree; /* for entity ids */ +#endif /* SHOPT_KIA */ +}; + +/* symbols for parsing */ +#define NL '\n' +#define NOTSYM '!' +#define SYMRES 0400 /* reserved word symbols */ +#define DOSYM (SYMRES|01) +#define FISYM (SYMRES|02) +#define ELIFSYM (SYMRES|03) +#define ELSESYM (SYMRES|04) +#define INSYM (SYMRES|05) +#define THENSYM (SYMRES|06) +#define DONESYM (SYMRES|07) +#define ESACSYM (SYMRES|010) +#define IFSYM (SYMRES|011) +#define FORSYM (SYMRES|012) +#define WHILESYM (SYMRES|013) +#define UNTILSYM (SYMRES|014) +#define CASESYM (SYMRES|015) +#define FUNCTSYM (SYMRES|016) +#define SELECTSYM (SYMRES|017) +#define TIMESYM (SYMRES|020) +#define NSPACESYM (SYMRES|021) + +#define SYMREP 01000 /* symbols for doubled characters */ +#define BREAKCASESYM (SYMREP|';') +#define ANDFSYM (SYMREP|'&') +#define ORFSYM (SYMREP|'|') +#define IOAPPSYM (SYMREP|'>') +#define IODOCSYM (SYMREP|'<') +#define EXPRSYM (SYMREP|'(') +#define BTESTSYM (SYMREP|'[') +#define ETESTSYM (SYMREP|']') + +#define SYMMASK 0170000 +#define SYMPIPE 010000 /* trailing '|' */ +#define SYMLPAR 020000 /* trailing LPAREN */ +#define SYMAMP 040000 /* trailing '&' */ +#define SYMGT 0100000 /* trailing '>' */ +#define SYMSEMI 0110000 /* trailing ';' */ +#define SYMSHARP 0120000 /* trailing '#' */ +#define IOSEEKSYM (SYMSHARP|'<') +#define IOMOV0SYM (SYMAMP|'<') +#define IOMOV1SYM (SYMAMP|'>') +#define FALLTHRUSYM (SYMAMP|';') +#define COOPSYM (SYMAMP|'|') +#define IORDWRSYM (SYMGT|'<') +#define IOCLOBSYM (SYMPIPE|'>') +#define IPROCSYM (SYMLPAR|'<') +#define OPROCSYM (SYMLPAR|'>') +#define EOFSYM 04000 /* end-of-file */ +#define TESTUNOP 04001 +#define TESTBINOP 04002 +#define LABLSYM 04003 +#define IOVNAME 04004 + +/* additional parser flag, others in <shell.h> */ +#define SH_EMPTY 04 +#define SH_NOIO 010 +#define SH_ASSIGN 020 +#define SH_FUNDEF 040 +#define SH_ARRAY 0100 +#define SH_SEMI 0200 /* semi-colon after NL ok */ + +#define SH_COMPASSIGN 010 /* allow compound assignments only */ + +typedef struct _shlex_ +{ + struct shlex_t _shlex; +#ifdef _SHLEX_PRIVATE + _SHLEX_PRIVATE +#endif +} Lex_t; + +#define shlex (((Lex_t*)(sh.lex_context))->_shlex) +extern const char e_unexpected[]; +extern const char e_unmatched[]; +extern const char e_endoffile[]; +extern const char e_newline[]; + +/* odd chars */ +#define LBRACE '{' +#define RBRACE '}' +#define LPAREN '(' +#define RPAREN ')' +#define LBRACT '[' +#define RBRACT ']' + +extern int sh_lex(); +extern Lex_t *sh_lexopen(Lex_t*, Shell_t*, int); +extern void sh_lexskip(int,int,int); +extern void sh_syntax(void); + +#endif /* !NOTSYM */ diff --git a/usr/src/lib/libshell/common/include/shnodes.h b/usr/src/lib/libshell/common/include/shnodes.h new file mode 100644 index 0000000000..b688edb4c5 --- /dev/null +++ b/usr/src/lib/libshell/common/include/shnodes.h @@ -0,0 +1,222 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef _SHNODES_H +#define _SHNODES_H 1 +/* + * UNIX shell + * Written by David Korn + * + */ + + +#include <ast.h> +#include "argnod.h" + +/* command tree for tretyp */ +#define FINT (02<<COMBITS) /* non-interruptable */ +#define FAMP (04<<COMBITS) /* background */ +#define FPIN (010<<COMBITS) /* input is a pipe */ +#define FPOU (040<<COMBITS) /* output is a pipe */ +#define FPCL (0100<<COMBITS) /* close the pipe */ +#define FCOOP (0200<<COMBITS) /* cooperating process */ +#define FSHOWME (0400<<COMBITS) /* set for showme commands */ +#define FPOSIX (02<<COMBITS) /* posix semantics function */ +#define FLINENO (04<<COMBITS) /* for/case has line number */ + +#define TNEGATE (01<<COMBITS) /* ! inside [[...]] */ +#define TBINARY (02<<COMBITS) /* binary operator in [[...]] */ +#define TUNARY (04<<COMBITS) /* unary operator in [[...]] */ +#define TTEST (010<<COMBITS) +#define TPAREN (TBINARY|TUNARY) +#define TSHIFT (COMBITS+4) +#define TNSPACE (TFUN|COMSCAN) + +#define TCOM 0 +#define TPAR 1 +#define TFIL 2 +#define TLST 3 +#define TIF 4 +#define TWH 5 +#define TUN (TWH|COMSCAN) +#define TTST 6 +#define TSW 7 +#define TAND 8 +#define TORF 9 +#define TFORK 10 +#define TFOR 11 +#define TSELECT (TFOR|COMSCAN) +#define TARITH 12 +#define TTIME 13 +#define TSETIO 14 +#define TFUN 15 + +/* this node is a proforma for those that follow */ + +struct trenod +{ + int tretyp; + struct ionod *treio; +}; + + +struct forknod +{ + int forktyp; + struct ionod *forkio; + Shnode_t *forktre; + int forkline; +}; + + +struct ifnod +{ + int iftyp; + Shnode_t *iftre; + Shnode_t *thtre; + Shnode_t *eltre; +}; + +struct whnod +{ + int whtyp; + Shnode_t *whtre; + Shnode_t *dotre; + struct arithnod *whinc; +}; + +struct fornod +{ + int fortyp; + char *fornam; + Shnode_t *fortre; + struct comnod *forlst; + int forline; +}; + +struct swnod +{ + int swtyp; + struct argnod *swarg; + struct regnod *swlst; + struct ionod *swio; + int swline; +}; + +struct regnod +{ + struct argnod *regptr; + Shnode_t *regcom; + struct regnod *regnxt; + char regflag; +}; + +struct parnod +{ + int partyp; + Shnode_t *partre; +}; + +struct lstnod +{ + int lsttyp; + Shnode_t *lstlef; + Shnode_t *lstrit; +}; + +/* tst is same as lst, but with extra field for line number */ +struct tstnod +{ + struct lstnod tstlst; + int tstline; +}; + +struct functnod +{ + int functtyp; + char *functnam; + Shnode_t *functtre; + int functline; + off_t functloc; + struct slnod *functstak; + struct comnod *functargs; +}; + +struct arithnod +{ + int artyp; + int arline; + struct argnod *arexpr; + void *arcomp; +}; + + +/* types of ionodes stored in iofile */ +#define IOUFD 0x3f /* file descriptor number mask */ +#define IOPUT 0x40 /* > redirection operator */ +#define IOAPP 0x80 /* >> redirection operator */ +#define IODOC 0x100 /* << redirection operator */ +#define IOMOV 0x200 /* <& or >& operators */ +#define IOCLOB 0x400 /* noclobber bit */ +#define IORDW 0x800 /* <> redirection operator */ +#define IORAW 0x1000 /* no expansion needed for filename */ +#define IOSTRG 0x2000 /* here-document stored as incore string */ +#define IOSTRIP 0x4000 /* strip leading tabs for here-document */ +#define IOQUOTE 0x8000 /* here-document delimiter was quoted */ +#define IOVNM 0x10000 /* iovname field is non-zero */ +#define IOLSEEK 0x20000 /* seek operators <# or ># */ +#define IOARITH 0x40000 /* arithmetic seek <# ((expr)) */ +#define IOCOPY IOCLOB /* copy skipped lines onto standard output */ + +union Shnode_u +{ + struct argnod arg; + struct ionod io; + struct whnod wh; + struct swnod sw; + struct ifnod if_; + struct dolnod dol; + struct comnod com; + struct trenod tre; + struct forknod fork; + struct fornod for_; + struct regnod reg; + struct parnod par; + struct lstnod lst; + struct tstnod tst; + struct functnod funct; + struct arithnod ar; +}; + +extern void sh_freeup(void); +extern void sh_funstaks(struct slnod*,int); +extern Sfio_t *sh_subshell(Shnode_t*, int, int); +#if defined(__EXPORT__) && defined(_BLD_DLL) && defined(_BLD_shell) + __EXPORT__ +#endif +extern int sh_tdump(Sfio_t*, const Shnode_t*); +extern Shnode_t *sh_dolparen(void); +extern Shnode_t *sh_trestore(Sfio_t*); +#if SHOPT_KIA + extern int kiaclose(void); + extern unsigned long kiaentity(const char*,int,int,int,int,unsigned long,int,int,const char*); +#endif /* SHOPT_KIA */ + +#endif /* _SHNODES_H */ diff --git a/usr/src/lib/libshell/common/include/shtable.h b/usr/src/lib/libshell/common/include/shtable.h new file mode 100644 index 0000000000..fc4e8e471f --- /dev/null +++ b/usr/src/lib/libshell/common/include/shtable.h @@ -0,0 +1,65 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef _SHTABLE_H + +/* + * David Korn + * AT&T Labs + * + * Interface definitions read-only data tables for shell + * + */ + +#define _SHTABLE_H 1 + +typedef struct shtable1 +{ + const char *sh_name; + unsigned sh_number; +} Shtable_t; + +struct shtable2 +{ + const char *sh_name; + unsigned sh_number; + const char *sh_value; +}; + +struct shtable3 +{ + const char *sh_name; + unsigned sh_number; + int (*sh_value)(int, char*[], void*); +}; + +#define sh_lookup(name,value) (sh_locate(name,(Shtable_t*)(value),sizeof(*(value)))->sh_number) +extern const Shtable_t shtab_testops[]; +extern const Shtable_t shtab_options[]; +extern const Shtable_t shtab_attributes[]; +extern const struct shtable2 shtab_variables[]; +extern const struct shtable2 shtab_aliases[]; +extern const struct shtable2 shtab_signals[]; +extern const struct shtable3 shtab_builtins[]; +extern const Shtable_t shtab_reserved[]; +extern const Shtable_t *sh_locate(const char*, const Shtable_t*, int); +extern int sh_lookopt(const char*, int*); + +#endif /* SH_TABLE_H */ diff --git a/usr/src/lib/libshell/common/include/streval.h b/usr/src/lib/libshell/common/include/streval.h new file mode 100644 index 0000000000..bbf51bd9d7 --- /dev/null +++ b/usr/src/lib/libshell/common/include/streval.h @@ -0,0 +1,195 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef SEQPOINT +/* + * D. G. Korn + * + * arithmetic expression evaluator + */ + +/* The following only is needed for const */ +#include <ast.h> +#include <math.h> +#if _AST_VERSION >= 20030127L +# include <ast_float.h> +#endif + +#if _ast_fltmax_double +#define LDBL_LLONG_MAX DBL_LLONG_MAX +#define LDBL_ULLONG_MAX DBL_ULLONG_MAX +#define LDBL_LLONG_MIN DBL_LLONG_MIN +#endif + +#ifndef LDBL_LLONG_MAX +# ifdef LLONG_MAX +# define LDBL_LLONG_MAX ((Sfdouble_t)LLONG_MAX) +# else +# ifdef LLONG_MAX +# define LDBL_LLONG_MAX ((Sfdouble_t)LLONG_MAX) +# else +# define LDBL_LLONG_MAX ((Sfdouble_t)((((Sflong_t)1) << (8*sizeof(Sflong_t)-1)) -1 )) +# endif +# endif +#endif +#ifndef LDBL_ULLONG_MAX +# ifdef ULLONG_MAX +# define LDBL_ULLONG_MAX ((Sfdouble_t)ULLONG_MAX) +# else +# define LDBL_ULLONG_MAX (2.*((Sfdouble_t)LDBL_LLONG_MAX)) +# endif +#endif +#ifndef LDBL_LLONG_MIN +# ifdef LLONG_MIN +# define LDBL_LLONG_MIN ((Sfdouble_t)LLONG_MIN) +# else +# define LDBL_LLONG_MIN (-LDBL_LLONG_MAX) +# endif +#endif +#ifndef LDBL_DIG +# define LDBL_DIG DBL_DIG +#endif + +struct lval +{ + char *value; + Sfdouble_t (*fun)(Sfdouble_t,...); + const char *expr; + short flag; + char isfloat; + char nargs; + short emode; + short level; + short elen; +}; + +struct mathtab +{ + char fname[16]; + Sfdouble_t (*fnptr)(Sfdouble_t,...); +}; + +typedef struct _arith_ +{ + unsigned char *code; + const char *expr; + Sfdouble_t (*fun)(const char**,struct lval*,int,Sfdouble_t); + short size; + short staksize; + short emode; + short elen; +} Arith_t; +#define ARITH_COMP 04 /* set when compile separate from execute */ + +#define MAXPREC 15 /* maximum precision level */ +#define SEQPOINT 0200 /* sequence point */ +#define NOASSIGN 0100 /* assignment legal with this operator */ +#define RASSOC 040 /* right associative */ +#define NOFLOAT 020 /* illegal with floating point */ +#define PRECMASK 017 /* precision bit mask */ + +#define A_EOF 1 +#define A_NEQ 2 +#define A_NOT 3 +#define A_MOD 4 +#define A_ANDAND 5 +#define A_AND 6 +#define A_LPAR 7 +#define A_RPAR 8 +#define A_POW 9 +#define A_TIMES 10 +#define A_PLUSPLUS 11 +#define A_PLUS 12 +#define A_COMMA 13 +#define A_MINUSMINUS 14 +#define A_MINUS 15 +#define A_DIV 16 +#define A_LSHIFT 17 +#define A_LE 18 +#define A_LT 19 +#define A_EQ 20 +#define A_ASSIGN 21 +#define A_COLON 22 +#define A_RSHIFT 23 +#define A_GE 24 +#define A_GT 25 +#define A_QCOLON 26 +#define A_QUEST 27 +#define A_XOR 28 +#define A_OROR 29 +#define A_OR 30 +#define A_TILDE 31 +#define A_REG 32 +#define A_DIG 33 +#define A_INCR 34 +#define A_DECR 35 +#define A_PUSHV 36 +#define A_PUSHL 37 +#define A_PUSHN 38 +#define A_PUSHF 39 +#define A_STORE 40 +#define A_POP 41 +#define A_SWAP 42 +#define A_UMINUS 43 +#define A_JMPZ 44 +#define A_JMPNZ 45 +#define A_JMP 46 +#define A_CALL0 47 +#define A_CALL1 48 +#define A_CALL2 49 +#define A_CALL3 50 +#define A_DOT 51 +#define A_LIT 52 +#define A_NOTNOT 53 + + +/* define error messages */ +extern const unsigned char strval_precedence[35]; +extern const char strval_states[64]; +extern const char e_moretokens[]; +extern const char e_argcount[]; +extern const char e_paren[]; +extern const char e_badnum[]; +extern const char e_badcolon[]; +extern const char e_recursive[]; +extern const char e_divzero[]; +extern const char e_synbad[]; +extern const char e_notlvalue[]; +extern const char e_function[]; +extern const char e_questcolon[]; +extern const char e_incompatible[]; +extern const char e_domain[]; +extern const char e_overflow[]; +extern const char e_singularity[]; +extern const char e_dict[]; +extern const char e_charconst[]; +extern const struct mathtab shtab_math[]; + +/* function code for the convert function */ + +#define LOOKUP 0 +#define ASSIGN 1 +#define VALUE 2 +#define MESSAGE 3 + +extern Sfdouble_t strval(const char*,char**,Sfdouble_t(*)(const char**,struct lval*,int,Sfdouble_t),int); +extern Arith_t *arith_compile(const char*,char**,Sfdouble_t(*)(const char**,struct lval*,int,Sfdouble_t),int); +extern Sfdouble_t arith_exec(Arith_t*); +#endif /* !SEQPOINT */ diff --git a/usr/src/lib/libshell/common/include/terminal.h b/usr/src/lib/libshell/common/include/terminal.h new file mode 100644 index 0000000000..1712c12bab --- /dev/null +++ b/usr/src/lib/libshell/common/include/terminal.h @@ -0,0 +1,195 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#ifndef _terminal_ +#define _terminal_ 1 + +#include "FEATURE/ttys" +/* + * terminal interface + * complicated by the fact that there are so many variations + * This will use POSIX <termios.h> interface where available + */ + +#ifdef _hdr_termios +# include <termios.h> +# if __sgi__ || sgi /* special hack to eliminate ^M problem */ +# ifndef ECHOCTL +# define ECHOCTL ECHOE +# endif /* ECHOCTL */ +# ifndef CNSUSP +# define CNSUSP CNSWTCH +# endif /* CNSUSP */ +# endif /* sgi */ +# ifdef _NEXT_SOURCE +# define _lib_tcgetattr 1 +# define _lib_tcgetpgrp 1 +# endif /* _NEXT_SOURCE */ +#else +# if defined(_sys_termios) && defined(_lib_tcgetattr) +# include <sys/termios.h> +# define _hdr_termios +# else +# undef _sys_termios +# endif /* _sys_termios */ +#endif /* _hdr_termios */ + +#ifdef _hdr_termios +# undef _hdr_sgtty +# undef tcgetattr +# undef tcsetattr +# undef tcgetpgrp +# undef tcsetpgrp +# undef cfgetospeed +# ifndef TCSANOW +# define TCSANOW TCSETS +# define TCSADRAIN TCSETSW +# define TCSAFLUSH TCSETSF +# endif /* TCSANOW */ + /* The following corrects bugs in some implementations */ +# if defined(TCSADFLUSH) && !defined(TCSAFLUSH) +# define TCSAFLUSH TCSADFLUSH +# endif /* TCSADFLUSH */ +# ifndef _lib_tcgetattr +# undef tcgetattr +# define tcgetattr(fd,tty) ioctl(fd, TCGETS, tty) +# undef tcsetattr +# define tcsetattr(fd,action,tty) ioctl(fd, action, tty) +# undef cfgetospeed +# define cfgetospeed(tp) ((tp)->c_cflag & CBAUD) +# endif /* _lib_tcgetattr */ +# undef TIOCGETC +# if SHOPT_OLDTERMIO /* use both termios and termio */ +# ifdef _hdr_termio +# include <termio.h> +# else +# ifdef _sys_termio +# include <sys/termio.h> +# define _hdr_termio 1 +# else +# undef SHOPT_OLDTERMIO +# endif /* _sys_termio */ +# endif /* _hdr_termio */ +# endif /* SHOPT_OLDTERMIO */ +#else +# define cfgetospeed(tp) ((tp)->c_cflag & CBAUD) +# undef SHOPT_OLDTERMIO +# ifdef _hdr_termio +# include <termio.h> +# else +# ifdef _sys_termio +# include <sys/termio.h> +# define _hdr_termio 1 +# endif /* _sys_termio */ +# endif /* _hdr_termio */ +# ifdef _hdr_termio +# define termios termio +# undef TIOCGETC +# define tcgetattr(fd,tty) ioctl(fd, TCGETA, tty) +# define tcsetattr(fd,action,tty) ioctl(fd, action, tty) + +# ifdef _sys_bsdtty +# include <sys/bsdtty.h> +# endif /* _sys_bsdtty */ +# else +# ifdef _hdr_sgtty +# include <sgtty.h> +# ifndef LPENDIN +# ifdef _sys_nttyio +# include <sys/nttyio.h> +# endif /* _sys_nttyio */ +# endif /* LPENDIN */ +# define termios sgttyb +# ifdef TIOCSETN +# undef TCSETAW +# endif /* TIOCSETN */ +# ifdef TIOCGETP +# define tcgetattr(fd,tty) ioctl(fd, TIOCGETP, tty) +# define tcsetattr(fd,action,tty) ioctl(fd, action, tty) +# else +# define tcgetattr(fd,tty) gtty(fd, tty) +# define tcsetattr(fd,action,tty) stty(fd, tty) +# endif /* TIOCGETP */ +# endif /* _hdr_sgtty */ +# endif /* hdr_termio */ + +# ifndef TCSANOW +# ifdef TCSETAW +# define TCSANOW TCSETA +# ifdef u370 + /* delays are too long, don't wait for output to drain */ +# define TCSADRAIN TCSETA +# else +# define TCSADRAIN TCSETAW +# endif /* u370 */ +# define TCSAFLUSH TCSETAF +# else +# ifdef TIOCSETN +# define TCSANOW TIOCSETN +# define TCSADRAIN TIOCSETN +# define TCSAFLUSH TIOCSETP +# endif /* TIOCSETN */ +# endif /* TCSETAW */ +# endif /* TCSANOW */ +#endif /* _hdr_termios */ + +/* set ECHOCTL if driver can echo control charaters as ^c */ +#ifdef LCTLECH +# ifndef ECHOCTL +# define ECHOCTL LCTLECH +# endif /* !ECHOCTL */ +#endif /* LCTLECH */ +#ifdef LNEW_CTLECH +# ifndef ECHOCTL +# define ECHOCTL LNEW_CTLECH +# endif /* !ECHOCTL */ +#endif /* LNEW_CTLECH */ +#ifdef LNEW_PENDIN +# ifndef PENDIN +# define PENDIN LNEW_PENDIN +# endif /* !PENDIN */ +#endif /* LNEW_PENDIN */ +#ifndef ECHOCTL +# ifndef VEOL +# define RAWONLY 1 +# endif /* !VEOL */ +#endif /* !ECHOCTL */ + +#ifdef _sys_filio +# ifndef FIONREAD +# include <sys/filio.h> +# endif /* FIONREAD */ +#endif /* _sys_filio */ +/* set FIORDCHK if you can check for characters in input queue */ +#ifdef FIONREAD +# ifndef FIORDCHK +# define FIORDCHK FIONREAD +# endif /* !FIORDCHK */ +#endif /* FIONREAD */ + +extern int tty_alt(int); +extern void tty_cooked(int); +extern int tty_get(int,struct termios*); +extern int tty_raw(int,int); +extern int tty_check(int); +extern int tty_set(int, int, struct termios*); + +#endif /* _terminal_ */ diff --git a/usr/src/lib/libshell/common/include/test.h b/usr/src/lib/libshell/common/include/test.h new file mode 100644 index 0000000000..b1ece6fcfa --- /dev/null +++ b/usr/src/lib/libshell/common/include/test.h @@ -0,0 +1,71 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef TEST_ARITH +/* + * UNIX shell + * David Korn + * AT&T Labs + * + */ + +#include "FEATURE/options" +#include "shtable.h" +/* + * These are the valid test operators + */ + +#define TEST_ARITH 040 /* arithmetic operators */ +#define TEST_BINOP 0200 /* binary operator */ +#define TEST_PATTERN 0100 /* turn off bit for pattern compares */ + +#define TEST_NE (TEST_ARITH|9) +#define TEST_EQ (TEST_ARITH|4) +#define TEST_GE (TEST_ARITH|5) +#define TEST_GT (TEST_ARITH|6) +#define TEST_LE (TEST_ARITH|7) +#define TEST_LT (TEST_ARITH|8) +#define TEST_OR (TEST_BINOP|1) +#define TEST_AND (TEST_BINOP|2) +#define TEST_SNE (TEST_PATTERN|1) +#define TEST_SEQ (TEST_PATTERN|14) +#define TEST_PNE 1 +#define TEST_PEQ 14 +#define TEST_EF 3 +#define TEST_NT 10 +#define TEST_OT 12 +#define TEST_SLT 15 +#define TEST_SGT 16 +#define TEST_END 8 +#define TEST_REP 20 + +extern int test_unop(int, const char*); +extern int test_inode(const char*, const char*); +extern int test_binop(int, const char*, const char*); + +extern const char sh_opttest[]; +extern const char test_opchars[]; +extern const char e_argument[]; +extern const char e_missing[]; +extern const char e_badop[]; +extern const char e_tstbegin[]; +extern const char e_tstend[]; + +#endif /* TEST_ARITH */ diff --git a/usr/src/lib/libshell/common/include/timeout.h b/usr/src/lib/libshell/common/include/timeout.h new file mode 100644 index 0000000000..3248b562c1 --- /dev/null +++ b/usr/src/lib/libshell/common/include/timeout.h @@ -0,0 +1,30 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +/* + * UNIX shell + * + * AT&T Labs + * + */ + +#define TGRACE 60 /* grace period before termination */ + /* The time_warn message contains this number */ +extern const char e_timeout[]; +extern const char e_timewarn[]; diff --git a/usr/src/lib/libshell/common/include/ulimit.h b/usr/src/lib/libshell/common/include/ulimit.h new file mode 100644 index 0000000000..b31d12b494 --- /dev/null +++ b/usr/src/lib/libshell/common/include/ulimit.h @@ -0,0 +1,175 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +#ifndef _ULIMIT_H +#define _ULIMIT_H 1 +/* + * This is for the ulimit built-in command + */ + +#include "FEATURE/time" +#include "FEATURE/rlimits" +#if defined(_sys_resource) && defined(_lib_getrlimit) +# include <sys/resource.h> +# if !defined(RLIMIT_FSIZE) && defined(_sys_vlimit) + /* This handles hp/ux problem */ +# include <sys/vlimit.h> +# define RLIMIT_FSIZE (LIM_FSIZE-1) +# define RLIMIT_DATA (LIM_DATA-1) +# define RLIMIT_STACK (LIM_STACK-1) +# define RLIMIT_CORE (LIM_CORE-1) +# define RLIMIT_CPU (LIM_CPU-1) +# ifdef LIM_MAXRSS +# define RLIMIT_RSS (LIM_MAXRSS-1) +# endif /* LIM_MAXRSS */ +# endif +# undef _lib_ulimit +#else +# ifdef _sys_vlimit +# include <sys/vlimit.h> +# undef _lib_ulimit +# define RLIMIT_FSIZE LIM_FSIZE +# define RLIMIT_DATA LIM_DATA +# define RLIMIT_STACK LIM_STACK +# define RLIMIT_CORE LIM_CORE +# define RLIMIT_CPU LIM_CPU +# ifdef LIM_MAXRSS +# define RLIMIT_RSS LIM_MAXRSS +# endif /* LIM_MAXRSS */ +# else +# ifdef _lib_ulimit +# define vlimit ulimit +# endif /* _lib_ulimit */ +# endif /* _lib_vlimit */ +#endif + +#ifdef RLIM_INFINITY +# define INFINITY RLIM_INFINITY +#else +# ifndef INFINITY +# define INFINITY ((rlim_t)-1L) +# endif /* INFINITY */ +#endif /* RLIM_INFINITY */ + +#if defined(_lib_getrlimit) || defined(_lib_vlimit) || defined(_lib_ulimit) +# ifndef RLIMIT_CPU +# define RLIMIT_CPU 0 +# endif /* !RLIMIT_CPU */ +# ifndef RLIMIT_DATA +# define RLIMIT_DATA 0 +# endif /* !RLIMIT_DATA */ +# ifndef RLIMIT_RSS +# define RLIMIT_RSS 0 +# endif /* !RLIMIT_RSS */ +# ifndef RLIMIT_STACK +# define RLIMIT_STACK 0 +# endif /* !RLIMIT_STACK */ +# ifndef RLIMIT_CORE +# define RLIMIT_CORE 0 +# endif /* !RLIMIT_CORE */ +# ifndef RLIMIT_VMEM +# define RLIMIT_VMEM 0 +# endif /* !RLIMIT_VMEM */ +# ifndef RLIMIT_NOFILE +# define RLIMIT_NOFILE 0 +# endif /* !RLIMIT_NOFILE */ +#else +# define _no_ulimit +#endif +#ifndef _typ_rlim_t + typedef long rlim_t; +#endif + +#if !defined(RLIMIT_NOFILE) && defined(RLIMIT_OFILE) +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif + +#ifndef RLIMIT_UNKNOWN +#define RLIMIT_UNKNOWN (-9999) +#endif +#ifndef RLIMIT_AS +#define RLIMIT_AS RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_CORE +#define RLIMIT_CORE RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_CPU +#define RLIMIT_CPU RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_DATA +#define RLIMIT_DATA RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_FSIZE +#define RLIMIT_FSIZE RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_LOCKS +#define RLIMIT_LOCKS RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_MEMLOCK +#define RLIMIT_MEMLOCK RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_NOFILE +#define RLIMIT_NOFILE RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_NPROC +#define RLIMIT_NPROC RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_PIPE +#define RLIMIT_PIPE RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_RSS +#define RLIMIT_RSS RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_SBSIZE +#define RLIMIT_SBSIZE RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_STACK +#define RLIMIT_STACK RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_PTHREAD +#define RLIMIT_PTHREAD RLIMIT_UNKNOWN +#endif +#ifndef RLIMIT_VMEM +#define RLIMIT_VMEM RLIMIT_UNKNOWN +#endif + +#define LIM_COUNT 0 +#define LIM_BLOCK 1 +#define LIM_BYTE 2 +#define LIM_KBYTE 3 +#define LIM_SECOND 4 + +typedef struct Limit_s +{ + const char name[8]; + const char* description; + int index; + const char* conf; + unsigned char option; + unsigned char type; +} Limit_t; + +extern const Limit_t shtab_limits[]; +extern const int shtab_units[]; + +extern const char e_unlimited[]; +extern const char* e_units[]; + +#endif /* _ULIMIT_H */ diff --git a/usr/src/lib/libshell/common/include/variables.h b/usr/src/lib/libshell/common/include/variables.h new file mode 100644 index 0000000000..2ceefcf9f7 --- /dev/null +++ b/usr/src/lib/libshell/common/include/variables.h @@ -0,0 +1,104 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#ifndef SH_VALNOD + +#include <option.h> +#include "FEATURE/options" +#include "FEATURE/dynamic" + +/* The following defines are coordinated with data in data/variables.c */ + +#define PATHNOD (sh.bltin_nodes) +#define PS1NOD (sh.bltin_nodes+1) +#define PS2NOD (sh.bltin_nodes+2) +#define IFSNOD (sh.bltin_nodes+3) +#define PWDNOD (sh.bltin_nodes+4) +#define HOME (sh.bltin_nodes+5) +#define MAILNOD (sh.bltin_nodes+6) +#define REPLYNOD (sh.bltin_nodes+7) +#define SHELLNOD (sh.bltin_nodes+8) +#define EDITNOD (sh.bltin_nodes+9) +#define MCHKNOD (sh.bltin_nodes+10) +#define RANDNOD (sh.bltin_nodes+11) +#define ENVNOD (sh.bltin_nodes+12) +#define HISTFILE (sh.bltin_nodes+13) +#define HISTSIZE (sh.bltin_nodes+14) +#define HISTEDIT (sh.bltin_nodes+15) +#define HISTCUR (sh.bltin_nodes+16) +#define FCEDNOD (sh.bltin_nodes+17) +#define CDPNOD (sh.bltin_nodes+18) +#define MAILPNOD (sh.bltin_nodes+19) +#define PS3NOD (sh.bltin_nodes+20) +#define OLDPWDNOD (sh.bltin_nodes+21) +#define VISINOD (sh.bltin_nodes+22) +#define COLUMNS (sh.bltin_nodes+23) +#define LINES (sh.bltin_nodes+24) +#define PPIDNOD (sh.bltin_nodes+25) +#define L_ARGNOD (sh.bltin_nodes+26) +#define TMOUTNOD (sh.bltin_nodes+27) +#define SECONDS (sh.bltin_nodes+28) +#define LINENO (sh.bltin_nodes+29) +#define OPTARGNOD (sh.bltin_nodes+30) +#define OPTINDNOD (sh.bltin_nodes+31) +#define PS4NOD (sh.bltin_nodes+32) +#define FPATHNOD (sh.bltin_nodes+33) +#define LANGNOD (sh.bltin_nodes+34) +#define LCALLNOD (sh.bltin_nodes+35) +#define LCCOLLNOD (sh.bltin_nodes+36) +#define LCTYPENOD (sh.bltin_nodes+37) +#define LCMSGNOD (sh.bltin_nodes+38) +#define LCNUMNOD (sh.bltin_nodes+39) +#define FIGNORENOD (sh.bltin_nodes+40) +#define DOTSHNOD (sh.bltin_nodes+41) +#define ED_CHRNOD (sh.bltin_nodes+42) +#define ED_COLNOD (sh.bltin_nodes+43) +#define ED_TXTNOD (sh.bltin_nodes+44) +#define ED_MODENOD (sh.bltin_nodes+45) +#define SH_NAMENOD (sh.bltin_nodes+46) +#define SH_SUBSCRNOD (sh.bltin_nodes+47) +#define SH_VALNOD (sh.bltin_nodes+48) +#define SH_VERSIONNOD (sh.bltin_nodes+49) +#define SH_DOLLARNOD (sh.bltin_nodes+50) +#define SH_MATCHNOD (sh.bltin_nodes+51) +#define SH_COMMANDNOD (sh.bltin_nodes+52) +#define SH_PATHNAMENOD (sh.bltin_nodes+53) +#define SH_FUNNAMENOD (sh.bltin_nodes+54) +#define SH_SUBSHELLNOD (sh.bltin_nodes+55) +#define SH_LEVELNOD (sh.bltin_nodes+56) +#if SHOPT_FS_3D +# define VPATHNOD (sh.bltin_nodes+57) +# define NFS_3D 1 +#else +# define NFS_3D 0 +#endif /* SHOPT_FS_3D */ +#if SHOPT_VPIX +# define DOSPATHNOD (sh.bltin_nodes+57+NFS_3D) +# define VPIXNOD (sh.bltin_nodes+58+NFS_3D) +# define NVPIX (NFS_3D+2) +#else +# define NVPIX NFS_3D +#endif /* SHOPT_VPIX */ +#ifdef apollo +# define SYSTYPENOD (sh.bltin_nodes+57+NVPIX) +#endif /* apollo */ + +#endif /* SH_VALNOD */ diff --git a/usr/src/lib/libshell/common/include/version.h b/usr/src/lib/libshell/common/include/version.h new file mode 100644 index 0000000000..dafdfcbd68 --- /dev/null +++ b/usr/src/lib/libshell/common/include/version.h @@ -0,0 +1,20 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#define SH_RELEASE "1993-12-28 s+" diff --git a/usr/src/lib/libshell/common/llib-lshell b/usr/src/lib/libshell/common/llib-lshell new file mode 100644 index 0000000000..49bb9889e7 --- /dev/null +++ b/usr/src/lib/libshell/common/llib-lshell @@ -0,0 +1,138 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * lib/libshell/common/llib-lshell + * + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <shell.h> +#include <nval.h> + +/* automatically generated data start here */ +extern const char e_defpath[]; +extern const char e_found[]; +extern const char e_nospace[]; +extern const char e_format[]; +extern const char e_number[]; +extern const char e_restricted[]; +extern const char e_recursive[]; +extern char e_version[]; +extern Dt_t *sh_bltin_tree (void); +extern void sh_subfork (void); +extern Shell_t *sh_init (int,char*[],Shinit_f); +extern int sh_reinit (char*[]); +extern int sh_eval (Sfio_t*,int); +extern void sh_delay (double); +extern void *sh_parse (Shell_t*, Sfio_t*,int); +extern int sh_trap (const char*,int); +extern int sh_fun (Namval_t*,Namval_t*, char*[]); +extern int sh_funscope (int,char*[],int(*)(void*),void*,int); +extern Sfio_t *sh_iogetiop (int,int); +extern int sh_main (int, char*[], void(*)(int)); +extern void sh_menu (Sfio_t*, int, char*[]); +extern Namval_t *sh_addbuiltin (const char*, int(*)(int, char*[],void*), void*); +extern char *sh_fmtq (const char*); +extern char *sh_fmtqf (const char*, int, int); +extern Sfdouble_t sh_strnum (const char*, char**, int); +extern int sh_access (const char*,int); +extern int sh_close (int); +extern int sh_dup (int); +extern void sh_exit (int); +extern int sh_fcntl (int, int, ...); +extern Sfio_t *sh_fd2sfio (int); +extern Shell_t *sh_getinterp (void); +extern int sh_open (const char*, int, ...); +extern int sh_openmax (void); +extern Sfio_t *sh_pathopen (const char*); +extern ssize_t sh_read (int, void*, size_t); +extern ssize_t sh_write (int, const void*, size_t); +extern off_t sh_seek (int, off_t, int); +extern int sh_pipe (int[]); +extern mode_t sh_umask (mode_t); +extern void *sh_waitnotify (Shwait_f); +extern Shscope_t *sh_getscope (int,int); +extern Shscope_t *sh_setscope (Shscope_t*); +extern void sh_sigcheck (void); +extern unsigned long sh_isoption (int); +extern unsigned long sh_onoption (int); +extern unsigned long sh_offoption (int); +extern int sh_waitsafe (void); +extern int sh_exec (const Shnode_t*,int); +extern int sh_waitsafe(void); +extern int sh_exec(const Shnode_t*,int); +extern void **sh_getliblist(void); +extern Shell_t sh; +extern Namarr_t *nv_setarray (Namval_t*,void*(*)(Namval_t*,const char*,int)); +extern void *nv_associative (Namval_t*,const char*,int); +extern int nv_aindex (Namval_t*); +extern int nv_nextsub (Namval_t*); +extern char *nv_getsub (Namval_t*); +extern Namval_t *nv_putsub (Namval_t*, char*, long); +extern Namval_t *nv_opensub (Namval_t*); +extern int nv_adddisc (Namval_t*, const char**, Namval_t**); +extern int nv_clone (Namval_t*, Namval_t*, int); +extern void nv_close (Namval_t*); +extern void *nv_context (Namval_t*); +extern Namval_t *nv_create (const char*, Dt_t*, int,Namfun_t*); +extern Dt_t *nv_dict (Namval_t*); +extern Sfdouble_t nv_getn (Namval_t*, Namfun_t*); +extern Sfdouble_t nv_getnum (Namval_t*); +extern char *nv_getv (Namval_t*, Namfun_t*); +extern char *nv_getval (Namval_t*); +extern Namfun_t *nv_hasdisc (Namval_t*, const Namdisc_t*); +extern int nv_isnull (Namval_t*); +extern Namval_t *nv_lastdict (void); +extern void nv_newattr (Namval_t*,unsigned,int); +extern Namval_t *nv_open (const char*,Dt_t*,int); +extern void nv_putval (Namval_t*,const char*,int); +extern void nv_putv (Namval_t*,const char*,int,Namfun_t*); +extern int nv_scan (Dt_t*,void(*)(Namval_t*,void*),void*,int,int); +extern Namval_t *nv_scoped (Namval_t*); +extern char *nv_setdisc (Namval_t*,const char*,Namval_t*,Namfun_t*); +extern void nv_setref (Namval_t*, Dt_t*,int); +extern int nv_settype (Namval_t*, Namval_t*, int); +extern void nv_setvec (Namval_t*,int,int,char*[]); +extern void nv_setvtree (Namval_t*); +extern int nv_setsize (Namval_t*,int); +extern Namfun_t *nv_disc (Namval_t*,Namfun_t*,int); +extern void nv_unset (Namval_t*); +extern Namval_t *nv_search (const char *, Dt_t*, int); +extern void nv_unscope (void); +extern char *nv_name (Namval_t*); +extern Namval_t *nv_type (Namval_t*); +extern const Namdisc_t *nv_discfun (int); +/* end of automatically generated data */ + +/* Manually added based on libshell/common/include/builtins.h */ +extern int b_printf(int, char*[],void*); +extern int B_echo(int, char*[],void*); +extern int b_print(int, char*[],void*); +extern int b_pwd(int, char*[],void*); +extern int b_sleep(int, char*[],void*); +extern int b_test(int, char*[],void*); diff --git a/usr/src/lib/libshell/common/nval.3 b/usr/src/lib/libshell/common/nval.3 new file mode 100644 index 0000000000..4acfc70b64 --- /dev/null +++ b/usr/src/lib/libshell/common/nval.3 @@ -0,0 +1,619 @@ +.fp 5 CW +.TH NVAL 3 "12 Feb 2003" +.PP +.SH NAME +.PP +\fBnval\fR \- the \f5ksh\fP name/value library +.PP +.SH SYNOPSIS +.ta .8i 1.6i 2.4i 3.2i 4.0i +.SS "HEADERS/LIBRARIES" +.nf +.ft 5 +#include <nval.h> +libshell.a -lshell +.ft R +.fi +.SS "DATA TYPES" +.nf +.ft 5 +Namval_t; +Namfun_t; +Namarr_t; +Namdisc_t; +.ft R +.fi +.SS "OPENING/CLOSING" +.nf +.ft 5 +Namval_t *nv_open(const char *\fIname\fP, Dt_t *\fIdict\fP, int \fIflags\fP); +Namval_t *nv_create(const char *\fIname\fP, Dt_t *\fIdict\fP, int \fIflags\fP, Namfun_t *\fIfp\fP); +void nv_close(Namval_t *\fInp\fP); +.ft R +.fi +.SS "GETTING AND SETTING VALUES" +.nf +.ft 5 +char *nv_getval(Namval_t *\fInp\fP); +Sfdouble_t nv_getnum(Namval_t *\fInp\fP); +char *nv_name(Namval_t *\fInp\fP); +void nv_putval(Namval_t *\fInp\fP, const char *\fIval\fP, int \fIflags\fP); +void nv_unset(Namval_t *\fInp\fP); +int nv_clone(Namval_t *\fIsrc\fP, Namval_t *\fIdest\fP, int \fIflags\fP); +.ft R +.fi +.SS "ATTRIBUTES AND SIZE" +.nf +.ft 5 +int nv_isnull(Namval_t *\fInp\fP); +int nv_setsize(Namval_t *\fInp\fP, int \fIsize\fP); +int nv_size(Namval_t *\fInp\fP); +unsigned nv_isattr(Namval_t *\fInp\fP, unsigned \fIflags\fP); +unsigned nv_onattr(Namval_t *\fInp\fP, unsigned \fIflags\fP); +unsigned nv_offattr(Namval_t *\fInp\fP, unsigned \fIflags\fP); +void nv_newattr(Namval_t *\fInp\fP, unsigned \fIflags\fP, int \fIsize\fP); +.ft R +.fi + +.SS "ARRAY HANDLING" +.nf +.ft 5 +unsigned nv_isarray(Namval_t *\fInp\fP); +Namarr_t *nv_setarray(Namval_t *\fInp\fP,void*(*\fIfun\fP)(Namval_t*,const char*,int)); +Namval_t *nv_putsub(Namval_t *\fInp\fP, char *\fIname\fP, long \fImode\fP); +Namval_t *nv_opensub(Namval_t *\fInp\fP); +void nv_setvec(Namval_t *\fInp\fP, int \fIappend\fP, int \fIargc\fP, char *\fIargv\fP[]); +char *nv_getsub(Namval_t *\fInp\fP); +int nv_nextsub(Namval_t *\fInp\fP); +int nv_aindex(Namval_t *\fInp\fP); +.ft R +.fi +.SS "DISCIPLINES" +.nf +.ft 5 +Namfun_t *nv_disc(Namval_t *\fInp\fP, Namfun_t *\fIfp\fP, int \fIflags\fP); +Namfun_t *nv_hasdisc(Namval_t *\fInp\fP, const Namdisc_t *\fIdp\fP); +char *nv_getv(Namval_t *\fInp\fP, Namfun_t *\fIfp\fP); +Sfdouble_t nv_getn(Namval_t *\fInp\fP, Namfun_t *\fIfp\fP); +void nv_putv(Namval_t *\fInp\fP, const char *\fIval\fP, int \fIflags\fP, Namfun_t *\fIfp\fP); +char *nv_setdisc(Namval_t *\fInp\fP, const char *\fIa\fP, Namval_t *\fIf\fP, Namfun_t *\fIfp\fP); +char *nv_adddisc(Namval_t *\fInp\fP, const char **\fInames\fP); +const Namdisc_t *nv_discfun(int \fIwhich\fP); +.ft R +.fi +.SS "MISCELLANEOUS FUNCTIONS" +.nf +.ft 5 +int nv_scan(Dt_t *\fIdict\fP, void(*\fIfn\fP)(Namval_t*,void*), void *\fIdata\fP, int \fImask\fP, int \fIflags\fP); +Dt_t nv_dict(Namval_t *\fInp\fP); +void nv_setvtree(Namval_t *\fInp\fP); +void nv_setref(Namval_t *\fInp\fP, Dt_t *\fIdp\fP, int \fIflags\fP); +Namval_t *nv_lastdict(void); +.ft R +.fi +.PP +.SH DESCRIPTION +.PP +\fINval\fP is a library of functions for interacting with name-value +pairs as used in \f5ksh\fP. +It is built on top the container dictionary type library facility +in \f5libcdt\fP. (See cdt(3)). +Each name-value pair is represented by a +type named \f5Namval_t\fP. +A \f5Namval_t\fP contains the name, value and +attributes of a variable. +Some attributes can have an associated number that +represents the field width, arithmetic base, or precision. +Additionally, each name-value pair can be associated with +one or more processing disciplines that affect +its behavior. +.PP +The function \f5nv_open()\fP returns a pointer to a name-value +pair corresponding to the given \fIname\fP. +It can also assign a value and give attributes to a name-value pair. +The argument \fIdict\fP defines the dictionary to search. +A \f5NULL\fP value causes the shell global variable dictionary to be searched. +.PP +The \fIflags\fP argument consists of the bitwise-or of zero or more +of the attributes listed later and zero or more of the following: +.IP +\f5NV_VARNAME\fP: +An invalid variable name causes an error. +.IP +\f5NV_IDENTIFIER\fP: +A variable name that is not an identifier causes an error. +.IP +\f5NV_ASSIGN\fP: +The \fIname\fP argument can contain an assignment. +.IP +\f5NV_NOARRAY\fP: +The \fIname\fP argument cannot contain a subscript. +.IP +\f5NV_NOREF\fP: +Do not follow references when finding the name-value pair. +.IP +\f5NV_NOADD\fP: +The name-value pair will not be added if it doesn't exist. +Instead, a \f5NULL\fP pointer will be returned. +.IP +\f5NV_NOSCOPE\fP: +Only the top level scope is used. +.IP +\f5NV_NOFAIL\fP: +Just return \f5NULL\fP when an error occurs. +By default an error message is displayed and the current command +is aborted. +.IP +.PP +If a name-value pair by this name does not already exist, it is +created unless \fIflags\fP contains the \f5NV_NOADD\fP flag. +If \f5NV_VARNAME\fP, \f5NV_IDENTIFIER\fP and \f5NV_ASSIGN\fP are +all not specified, then no validity check is performed on the \fIname\fP +argument and no further processing is performed. +Otherwise, if \f5NV_ASSIGN\fP is specified, then the characters up +to the first \f5=\fP or \f5+=\fP are used to find the name-value pair, +and the characters after the \f5=\fP are used to define +the value that will be assigned to this name-value pair. +If \fIname\fP does not contain an \f5=\fP, than no assignment +will be made. +If the first identifier in \fIname\fP is a reference and is not +preceded by a \fB.\fP, +it will be replaced by the value of the reference +to find the name of a variable. +Unless \fIflags\fP contains the \f5NV_NOREF\fP flag, +if the name-value pair give by \fIname\fP has the \f5NV_REF\fP +attribute, it will be replaced by the variable whose name +is the value of this name-value pair. +If \f5NV_ASSIGN\fP is set in the \fIflags\fP argument, +the \fIname\fP variable can contain an \f5=\fP +and a value that will be assigned to the name-value pair. +Any attributes appearing in the \fIflags\fP argument +will be applied to the name-value pair after any value is assigned. +.PP +It is possible for an application to create additional dictionaries +with the cdt library and associate them with name-value pairs. +The \f5nv_dict()\fP function returns the dictionary associated with +the specified name-value pair, or if no dictionary was specified, +\f5NULL\fP is returned. +The \f5nv_lastdict()\fP function returns a pointer the the +name-value pair that contains +the last dictionary searched on the previous \f5nv_open()\fP. +.PP +The \f5nv_close()\fP indicates that the pointer returned by +\f5nv_open()\fP or \f5nv_opensub()\fP will not be referenced again. If the +name-value pair is unset, and not referenced elsewhere, +the name-value pair may be freed. +.PP +The \f5nv_name()\fP function returns the name of the given name-value +pair \fInp\fP. +The \f5nv_setsize()\fP function returns the size of the field for +justified variables, the arithmetic base for integer variables, +and the precision or number of places after the decimal point +for floating point variables. If \fIsize\fP is greater than or +equal to zero, the current size is changed to this value. +The \f5nv_size()\fP function is equivalent to \f5nv_setsize()\fP +with the second argument negative. +.PP +The \f5nv_getval()\fP function returns the value of the given +name-value pair as a string. A \f5NULL\fP return value indicates +that the name-value pair is unset. +The \f5nv_getnum()\fP function returns the value of the given +name-value pair as a double precision number using the \f5Sfio\fP +library (See Sfio(3)) type \f5Sfdouble_t\fP. +For name-value pairs without the \f5NV_INTEGER\fP attribute, +the string value is evaluated as an arithmetic expression to +arrive at a numerical value. +.PP +The \f5nv_putval()\fP function is used to assign a \fIvalue\fP to +the name-value pair \fInp\fP. +The \fIflags\fP argument consists zero or more of the bitwise-or +of \f5NV_LONG\fP, \f5NV_SHORT\fP, \f5NV_DOUBLE\fP, \f5NV_INTEGER\fP, +\f5NV_RDONLY\fP, \f5NV_REF\fP, \f5NV_BINARY\fP, and \f5NV_NOFREE\fP. +The presence of \f5NV_RDONLY\fP allows the assignment to occur +even if the name-value pair has the \f5NV_RDONLY\fP attribute. +The presence of \f5NV_INTEGER\fP indicates that the \fIvalue\fP +argument is actually a pointer to a numerical type. +By default this type is \f5long\fP, but can be modified with +\f5NV_LONG\fP, \f5NV_SHORT\fP, and \f5NV_DOUBLE\fP +to represent \f5long long\fP, \f5short\fP, \f5double\fP, \f5long double\fP, +and \f5float\fP. +The presence of \f5NV_REF\fP indicates that the \fIvalue\fP +argument is actually a pointer to a name-value pair +and \f5np\fP should become a reference to this name-value pair. +If \f5NV_NOFREE\fP is specified, \fIvalue\fP itself becomes +the value of the name-value pair \fInp\fP. +Otherwise, a copy of the value is stored +as the value for \fInp\fP. +.PP +The \f5nv_unset()\fP function clears out the value and attributes +of the given name-value function but does not free the name-value +pair. +.PP +The following attributes can be associated with a name-value pair: +.IP +\f5NV_EXPORT\fP: +The export attribute. +.IP +\f5NV_RDONLY\fP: +The readonly attribute. +.IP +\f5NV_LTOU\fP: +Lower case characters are converted to upper case characters. +.IP +\f5NV_UTOL\fP: +Upper case characters are converted to lower case characters. +.IP +\f5NV_RJUST\fP: +Right justify and blank fill. +This attribute has an associated size that defines the +string length of the value. +.IP +\f5NV_LJUST\fP: +Left justify and blank fill. +This attribute has an associated size that defines the +string length of the value. +.IP +\f5NV_ZFILL\fP: +Without \f5NV_LJUST\fP, right justifies and fills with leading zeros. +With \f5NV_LJUST\fP, left justify and strip leading zeros. +Left justify and blank fill. +This attribute has an associated size that defines the +string length of the value. +.IP +\f5NV_TAGGED\fP: +Indicates the tagged attribute. +.IP +\f5NV_INTEGER\fP: +Causes value to be represented by a number. +This attribute has an associated number that defines the +arithmetic base to be used when the value is expanded as a string. +.IP +\f5NV_DOUBLE\fP: +Used in conjunction with \f5NV_INTEGER\fP to cause value +to be stored as a double precision floating point number. +This attribute has an associated number that defines the +number of places after the decimal point to be used when +the value is expanded as a string. +.IP +\f5NV_EXPNOTE\fP: +Used in conjunction with \f5NV_INTEGER\fP and \f5NV_DOUBLE\fP to +cause the value to be represented in scientific notation when +expanded as a string. +This attribute has an associated number that defines the +the precision of the mantissa. +.IP +\f5NV_BINARY\fP: +The name-value pair contains a buffer of binary data and \f5nv_size()\fP +is the number of bytes for this data. By default the value +will be represented by the base64 encoding of the buffer. +The \f5NV_LJUST\fP flag may also be specified and causes the buffer +size to be fixed and data either truncated or filled with \f50\fP bytes. +.IP +\f5NV_REF\fP: +The name-value pair is a name reference variable. +.IP +\f5NV_NODISC\fP: +All discipline functions are ignored when performing assignments +and lookups. +.PP +The \f5nv_isattr()\fP function can test whether or not any of +the attributes given by \fIflags\fP is set. +The \f5nv_onattr()\fP and \f5nv_offattr()\fP functions turn attributes +on or off respectively. Only attributes that do not affect the +value can be set in this way. +The \f5nv_newattr()\fP function can be used to change the +attributes and size of the given name-value pair which may result +in the value being changed to conform to the new attributes and size. +The \fIsize\fP argument is needed for attributes that require +an additional argument such as justifies variables. +Changing the attribute may require changing the value +to agree with the new attributes. +For an array variable, the values for each of the +subscripts will be changed. +.PP +The \f5nv_isarray()\fP function returns a non-zero value if the specified +name-value pair is an array. +.PP +The \f5nv_scan()\fP function is used to walk through +all name-value pairs in the dictionary given by \fIdict\fP. +If the \f5flags\fP variable contains the \f5NV_NOSCOPE\fP +flag, then only the top scope will be examined. +The remaining flags will be used in conjunction with \fImask\fP +to further restrict the walk. +If \fImask\fP is non-zero, only the nodes for which +\f5nv_isattr(\fP\fInode\fP\f5,\fP\fImask\fP\f5)\fP +is equal to \fIflags\fP will be visited. +If \fIfn\fP is non-zero, then this function will be executed +for each name-value pair in the walk. +The arguments to \fIfn\fP will be a pointer to the name-value pair +and the \fIdata\fP pointer passed to \f5nv_scan()\fP. +The number of elements visited will be returned. +.PP +The \f5nv_clone()\fP function is used make a copy of the contents of +name-value pair \fIsrc\fP to another name-value pair \fIdest\fP. +.PP +Disciplines provide a way to +intercept the lookup and assignment operations, +to manage the creation of sub-variables, +and to extend the operations permitted on a name-value pair. +A discipline consists of a set of one or more functions and related +data that are used to override and extend the operations +on a name-value pair. +A discipline is defined by the types +\f5Namfun_t\fP and \f5Namdisc_t\fP. +The \f5Namdisc_t\fP is not modified by any of these functions and +can therefore be shared by several name-value pairs. +It contains following public fields in the order listed: +.nf + \f5size_t dsize;\fP + \f5void (*putval)(Namval_t*,const char*,int,Namfun_t*);\fP + \f5char *(*getval)(Namval_t*,Namfun_t*);\fP + \f5double (*getnum)(Namval_t*,Namfun_t*);\fP + \f5char *(*setdisc)(Namval_t*,const char*,Namval_t*,Namfun_t*);\fP + \f5Namval_t *(*createf)(Namval_t*,const char*,Namfun_t*);\fP + \f5Namfun_t *(*clonef)(Namval_t*,Namval_t*,int,Namfun_t*);\fP + \f5char *(*namef)(Namval_t*,Namfun_t*);\fP + \f5Namval_t *(*nextf)(Namval_t*,Dt_t*,Namfun_t*);\fP + \f5Namval_t *(*typef)(Namval_t*,Namfun_t*);\fP +.fi +The \f5Namfun_t\fP type contains a member named +\f5disc\fP which points to a \f5Namdisc_t\fP structure. +To create a discipline with additional user data, +define a structure with an instance of \f5Namfun_t\fP +as the first element. +An application must initialize the \f5Namfun_t\fP portion of +the structure to zero and then set the \fIdisc\fP field to point +to the \f5Namdisc_t\fP structure. +The \f5dsize\fP field of the \f5Namdisc_t\fP structure must be +the size of this structure. A value of 0, +indicates that there are no additional fields and is equivalent +to \f5sizeof(Namfun_t)\fP. +If different instances of this structure uses different sizes, then +the \f5size\fP field in the \f5Namfun_t\fP can must be set to +this size and overrides the value in the \f5Namdisc_t\fP structure. +.PP +When a variable is referenced by calling the \f5nv_getval()\fP function, +and the \f5NV_NODISC\fP attribute is not set, +the \f5getval()\fP discipline function is called with a pointer +to the name-value pair, \fInp\fP, and a pointer to the discipline, +\fIfp\fP. +Inside the \f5getval()\fP function, the \f5nv_getv()\fP function +can be called to get the value of the name-value pair that +would have resulted if the discipline were not used. +The \f5getnum()\fP discipline is called whenever a numerical +value is needed for the name-value pair \fInp\fP +and the \f5NV_NODISC\fP attribute is not set, +The \f5nv_getn()\fP function can be called from within +the \f5getnum()\fP discipline to get the value that would +have resulted if there were no \f5getnum()\fP discipline. +.PP +The \f5putval\fP\f5()\fP discipline function is used to +override the assignment of values +to a name-value pair. +It is called whenever a value is assigned with \f5nv_putval()\fP +and the \f5NV_NODISC\fP attribute is not set, +or when a name-value pair is unset with \f5nv_unset()\fP. +When a name-value pair is unset, \f5putval\fP\f5()\fP +is called with \fIvalue\fP set to \f5NULL\fP. +The \f5nv_putv()\fP function is used within the \f5putval()\fP +to perform the assignment or unset that would have occurred +if the discipline had not been installed. +.PP +The \f5createf()\fP discipline function is called from +\f5nv_open()\fP or \f5nv_create()\fP when the name-value pair preceding a +.B \s+2.\s-2 +is found. +This function is passed the name-value pointer plus the remaining string and +the current \fIflags\fP argument. +The \f5createf()\fP discipline function +must return the created name-value pair, otherwise the default action +will be taken. +If the name-value pair that is returned is the same as the +one given, then the the behavior will be the same as if +an invalid name had been given to \f5nv_open()\fP. +The \f5nv_create()\fP function may be called within +the \f5createf()\fP discipline function +to perform the action that would have occurred +by an earlier \f5nv_open()\fP function. +.PP +The \f5setdisc()\fP discipline function is used +to extend the set of available shell level discipline functions +associated with a name-value pair by allowing +builtins or functions whose name is of the +form \fIvarname\fP\f5.\fP\fIaction\fP to be defined. +By default, each name-value pair can have a \f5get\fP, +\f5set\fP, and \f5unset\fP discipline associated with it. +Whenever a builtin or function whose name is of the +form \fIvarname\fP\f5.\fP\fIaction\fP is defined or is unset, +and \fIaction\fP is not \f5get\fP, +\f5set\fP, or \f5unset\fP, the \fIsetdisc\fP\f5()\fP function is invoked +with the same argument format as \f5nv_setdisc\fP\f5()\fP. +The argument \fIf\fP points to the name-value pair associated +with the function being defined, or \f5NULL\fP if the function is +being unset. +If the given action \fIa\fP is not known by this discipline, +it should return the value returned by calling +\f5nv_setdisc(\fP\fInp\fP\f5,\fP\fIa\fP\f5,\fP\fIf\fP\f5,\fP\fIfp\fP\f5)\fP +so that it can be searched for in previously stacked disciplines. +Otherwise, the \fIsetdisc\fP\f5()\fP function should save the function +name-value pair pointer, and return a non-\f5NULL\fP value. +The name-value pointer to the function can be used to invoke +the function at an application defined point. +If the action \fIa\fP is \f5NULL\fP, then \fIf\fP points to +an action name instead of a name-value pair pointer. +The \fIsetdisc\fP\f5()\fP must return the +name of the action that follows the action name given by +\fIf\fP. If \fIf\fP is also \f5NULL\fP, the name of the first action +must be returned. +This allows an application to get the list of valid discipline +action names allowed by a given name-value pair. +.PP +The \f5nv_adddisc()\fP function is a higher level function that +adds a \fIsetdisc\fP discipline to the name-value pair that allows +shell level disciplines to be created for each of the name specified +in \f5names\fP. +.PP +The \f5nv_discfun()\fP function can be used to get a pointer to +discipline functions that are provided by the library. +Currently, the only one that is provided is the ones used to +implement \f5nv_adddisc()\fP which can be returned with an +argument of \f5NV_DCADD\fP. +.PP +The \f5clonef()\fP discipline function is called by \f5nv_clone()\fP +when making a copy of the \f5Namfun_t\fP discipline to the new node. +The first argument is the original node, the second argument is +the new node, and the third argument is the flags that were passed +down to \f5nv_clone()\fP. +It must return a new instance of the \f5Namfun_t*\fP \f5fp\fP. +If omitted, then memory whose size is determinated by the \f5size\fP +field of \f5fp\fP, if non-zero, or \f5fp->disc\fP, will be allocated +and copied from \f5fp\fP. +.PP +The \f5namef()\fP discipline function returns the name for this name-value pair. +.PP +The \f5nextf()\fP is used for walking through the list of sub-variables +associated with this name-value pair. If the dictionary argument is +\f5NULL\fP, it must return the first sub-variable. Otherwise, +it must return the next sub-variable, or \f5NULL\fP if there are +no more variables. +.PP +A discipline is installed or removed with the +\f5nv_disc()\fP function. +The following flags can be specified: +.IP +\f5NV_FIRST\fP: +If \fIfp\fP is non-\f5NULL\fP, the discipline is moved to the top +of the stack or pushed onto the top of the stack of disciplines +associated with the given name-value +pair \fInp\fP if not already present. +Otherwise, the top of the discipline stack is returned. +.IP +\f5NV_LAST\fP: +If \fIfp\fP is non-\f5NULL\fP, the discipline is moved to the bottom +of the stack or pushed onto the bottom of the stack of disciplines +associated with the given name-value +pair \fInp\fP if not already present. +Otherwise, the bottom of the discipline stack is returned. +.IP +\f5NV_POP\fP: +If \fIfp\fP is non-\f5NULL\fP and it is on the stack, +it is removed and \fIfp\fP is returned. If \fIfp\fP is non-\f5NULL\fP +and is not on the stack, \f5NULL\fP is returned. +Otherwise, the the top discipline is popped +and returned. +.IP +\f5NV_CLONE\fP: +If \fIfp\fP is non-\f5NULL\fP and it is on the stack, +it is replace by a copy created by \f5malloc\fP(3). +The \f5nofree\fP field is set to \f50\fP. +The new discipline is returned. +Otherwise, \f5NULL\fP is returned. +.IP +\f50\fP: +If \fIfp\fP is non-\f5NULL\fP then it is equivalent to \f5NV_FIRST\fP. +Otherwise, it is equivalent to \f5NV_POP\fP. +.PP +The +\f5nv_hasdisc()\fP function can be used to tell whether a discipline +whose discipline functions are those defined in \fIdp\fP. +A pointer to this discipline is returned. +.PP +The \f5nv_aindex()\fP function returns +the current index for +the indexed array given by the name-value pair pointer \fInp\fP. +The return value is negative if \fInp\fP refers to +an associative array. +.PP +The \f5nv_setarray()\fP function is used to create an associative array +from a name-value pair node. +The function \fIfun\fP defines the semantics of the associative +array. +Using \fIfun\fP equal to \f5nv_associative()\fP implements the default +associative array semantics +that are used with \f5typeset\ -A\fP. +The function \fIfun\fP will be called with third argument as follows: +.IP +\f5NV_AINIT\fP: +This will be called at initialization. +The function you supply must return a pointer to a structure +that contains the type \f5Namarr_t\fP as the first element. +All other calls receive this value as an argument. +.IP +\f5NV_AFREE\fP: +This will be called after all elements of the name-value pair have been +deleted and the array is to be freed. +.IP +\f5NV_ADELETE\fP: +The current element should be deleted. +.IP +\f5NV_ANEXT\fP: +This means that the array subscript should be advanced to the +next subscript. A \f5NULL\fP return indicates that there are +no more subscripts. +.IP +\f5NV_ANAME\fP: +The name of the current subscript must be returned. +.IP +\f5NV_ACURRENT\fP: +Returns a pointer to a name-value pair corresponding to the +current subscript, or \f5NULL\fP if this array type doesn't +create represent each element as a name-value pair. +.PP +If \fInp\fP refers to an array, +the \f5nv_getsub()\fP returns a pointer to +the name of the current subscript. +Otherwise, \f5nv_getsub()\fP +returns \f5NULL\fP. +.PP +The \f5nv_opensub()\fP function returns +a pointer to the name-value pair corresponding +to the current subscript in an associative array. +Note that the \f5nv_close()\fP function should be called +when the pointer is no longer needed. +.PP +The \f5nv_putsub()\fP function is used to +set the subscript for the next reference to \f5np\fP. +If the \f5name\fP argument is not \f5NULL\fP, +it defines the value of the next subscript. +The \f5mode\fP argument can contain one or more of the following flags: +.IP +\f5ARRAY_ADD\fP: +Add the subscript if not found. +Otherwise, \f5nv_putsub()\fP returns \f5NULL\fP if the +given subscript is not found. +.IP +\f5ARRAY_SCAN\fP: +Begin a walk through the subscripts starting at the subscript +given by \f5name\fP. If \f5name\fP is \f5NULL\fP +the walk is started from the beginning. +.IP +\f5ARRAY_UNDEF\fP: +This causes any current scan to terminate and leaves the +subscript in an undefined state. +.PP +If \f5ARRAY_ADD\fP is not given and the subscript +does not exist, a \f5NULL\fP value is returned. +.PP +The \f5nv_nextsub()\fP function is used to advance to the +next subscript. +It returns 0 if there are no more subscripts or if called +when not in a scan. +.PP +The \f5nv_setref()\fP function makes the name-value pair \f5np\fP +into a reference to the variable whose name is given by +the value of \f5np\fP. The \f5nv_open()\fP open function is +called with this name, the dictionary given by \f5dp\fP, +and the \f5flags\fP argument. +A \f5NULL\fP value causes the shell global variable dictionary to be searched. +.PP +The \f5nv_setvtree()\fP function makes the name-value pair \f5np\fP +into a tree structured variable so that \f5nv_getval()\fP +will return a string containing all the names and values of +children nodes in a format that can be used in +a shell compound assignment. +.PP +.SH SEE ALSO +cdt(3) +shell(3) +.SH AUTHOR +David G. Korn (dgk@research.att.com). diff --git a/usr/src/lib/libshell/common/sh.1 b/usr/src/lib/libshell/common/sh.1 new file mode 100644 index 0000000000..61393046b6 --- /dev/null +++ b/usr/src/lib/libshell/common/sh.1 @@ -0,0 +1,7453 @@ +.\" +.\" David Korn +.\" AT&T Bell Laboratories +.\" +.\" @(#)sh.1 (dgk@research.att.com) 12/28/93 +.\" +.nr Z 1 \" set to 1 when command name is ksh, 2 for ksh93 +.ds OK [\| +.ds CK \|] +.ds ' \s+4\v@.3m@\'\v@-.3m@\s-4 +.ds ` \s+4\v@.3m@\`\v@-.3m@\s-4 +.if \nZ=0 \{\ +.TH SH 1 +.\} +.if \nZ=1 \{\ +.TH KSH 1 "User Environment Utilities" "RDS Standard" +.\} +.if \nZ=2 \{\ +.TH KSH93 1 +.\} +.SH NAME +.if \nZ=0 \{\ +sh, rsh, pfsh \- shell, the +.\} +.if \nZ=1 \{\ +.\} +ksh, rksh, pfksh \- KornShell, a +.if \nZ=2 \{\ +ksh93, rksh93, pfksh93 \- KornShell, a +.\} +standard/restricted command and programming language +.SH SYNOPSIS +.if \nZ=0 \{\ +.B sh +.if \nZ=1 \{\ +.\} +.B ksh +.\} +.if \nZ=2 \{\ +.B ksh93 +.\} +[ +.B \(+-abcefhikmnoprstuvxBCDP +] [ +.B \-R +file ] [ +.B \(+-o +option ] .\|.\|. [ +.B \- +] [ arg .\|.\|. ] +.br +.if \nZ=0 \{\ +.B rsh +.\} +.if \nZ=1 \{\ +.B rksh +.\} +.if \nZ=2 \{\ +.B rksh93 +.\} +[ +.B \(+-abcefhikmnoprstuvxBCD +] [ +.B \-R +file ] [ +.B \(+-o +option ] .\|.\|. [ +.B \- +] [ arg .\|.\|. ] +.SH DESCRIPTION +.if \nZ=0 .I Sh\^ +.if \nZ=1 .I Ksh\^ +.if \nZ=2 .I Ksh93\^ +is a command and programming language +that executes commands read from a terminal +or a file. +.if \nZ=0 .I Rsh\^ +.if \nZ=1 .I Rksh\^ +.if \nZ=2 .I Rksh93\^ +is a restricted version of the +.if \nZ=0 standard +command interpreter +.if \nZ=0 .IR sh ; +.if \nZ=1 .IR ksh ; +.if \nZ=2 .IR ksh93 ; +it is used to set up login names and execution environments whose +capabilities are more controlled than those of the standard shell. +.if \nZ=0 .I Rpfsh\^ +.if \nZ=1 .I Rpfksh\^ +.if \nZ=2 .I Rpfksh93\^ +is a profile shell version of the +.if \nZ=0 standard +command interpreter +.if \nZ=0 .IR sh ; +.if \nZ=1 .IR ksh ; +.if \nZ=2 .IR ksh93 ; +it is used to to execute commands with the attributes specified by +the user's profiles (see +.IR pfexec (1)). +See +.I Invocation\^ +below +for the meaning of arguments to the shell. +.SS Definitions. +A +.I metacharacter\^ +is one of the following characters: +.PP +.RS +\f3; & ( ) \(bv < > new-line space tab\fP +.RE +.PP +A +.I blank\^ +is a +.B tab +or a +.BR space . +An +.I identifier\^ +is a sequence of letters, digits, or underscores +starting with a letter or underscore. +Identifiers are used as components of +.I variable\^ +names. +A +.I vname\^ +is a sequence of one or more identifiers +separated by a \fB\s+2.\s-2\fP and optionally preceded +by a \fB\s+2.\s-2\fP. +Vnames are used as function and variable names. +A +.I word\^ +is a sequence of +.I characters\^ +from the character set defined by the current locale, +excluding non-quoted +.IR metacharacters . +.PP +A +.I command\^ +is a sequence of characters in the syntax +of the shell language. +The shell reads each command and +carries out the desired action either directly or by invoking +separate utilities. +A built-in command is a command that is carried out by the +shell itself without creating a separate process. +Some commands are built-in purely for convenience +and are not documented here. +Built-ins that cause +side effects in the shell environment and +built-ins that are found before performing a +path search (see +.I Execution\^ +below) +are documented here. +For historical reasons, some of +these built-ins behave differently than +other built-ins and are called +.IR "special built-ins" . +.SS Commands. +A +.I simple-command\^ +is a list of variable assignments +(see +.I Variable Assignments\^ +below) +or a sequence of +.I blank\^ +separated words +which may be preceded by a list of variable assignments +(see +.I Environment\^ +below). +The first word specifies the name of the command to +be executed. +Except as specified below, +the remaining words are passed as arguments +to the invoked command. +The command name is passed as argument 0 +(see +.IR exec (2)). +The +.I value\^ +of a simple-command is its exit status; 0-255 +if it terminates normally; 256+\f2signum\^\fP if +it terminates abnormally (the name of the signal corresponding +to the exit status can be +obtained via the +.B \-l +option of the +.B kill\^ +built-in utility). +.PP +A +.I pipeline\^ +is a sequence of one or more +.I commands\^ +separated by +.BR \(bv . +The standard output of each command but the last +is connected by a +.IR pipe (2) +to the standard input of the next command. +Each command, +except possibly the last, +is run as a separate process; +the shell waits for the last command to terminate. +The exit status of a pipeline is the exit +status of the last command unless the +.B pipefail +option is enabled. +Each pipeline can be preceded by the +.I "reserved word" +.B ! +which causes the exit status of the pipeline to become +0 if the exit status of the last command is non-zero, and +1 if the exit status of the last command is 0. +.PP +A +.I list\^ +is a sequence of one or more +pipelines +separated by +.BR ; , +.BR & , +.BR \(bv& , +.BR && , +or +.BR \(bv\|\(bv , +and optionally terminated by +.BR ; , +.BR & , +or +.BR \(bv& . +Of these five symbols, +.BR ; , +.BR & , +and +.B \(bv& +have equal precedence, +which is lower than that of +.B && +and +.BR \(bv\|\(bv . +The symbols +.B && +and +.B \(bv\|\(bv +also have equal precedence. +A semicolon +.RB ( ; ) +causes sequential execution of the preceding pipeline; an ampersand +.RB ( & ) +causes asynchronous execution of the preceding pipeline (i.e., the shell does +.I not\^ +wait for that pipeline to finish). +The symbol +.B \(bv& +causes asynchronous execution of the preceding pipeline +with a two-way pipe established to the parent shell; +the standard input and output of the spawned pipeline +can be written to and read from by the parent shell +by applying +the redirection operators +.B <& +and +.B >& +with arg +.B p +to commands and by using +.B \-p +option of +the built-in commands +.B read +and +.B print +described later. +The symbol +.B && +.RB (\| \(bv\|\(bv \^) +causes the +.I list\^ +following it to be executed only if the preceding +pipeline +returns a zero (non-zero) value. +One or more new-lines may appear in a +.I list\^ +instead of a semicolon, +to delimit a command. +The first +.I item \^ +of the first +.I pipeline\^ +of a +.I list\^ +that is a simple command not beginning +with a redirection, and not occuring within a +.BR while , +.BR until , +or +.B if +.IR list , +can be prededed by a semicolon. +This semicolon +is ignored unless the +.B showme +option is enabled as described with +the +.B set +built-in below. +.PP +A +.I command\^ +is either a simple-command +or one of the following. +Unless otherwise stated, +the value returned by a command is that of the +last simple-command executed in the command. +.TP +\f3for\fP \f2vname\^\fP \*(OK \f3in\fP \f2word\^\fP .\|.\|. \*(CK \f3;do\fP \f2list\^\fP \f3;done\fP +Each time a +.B for +command is executed, +.I vname\^ +is set to the next +.I word\^ +taken from the +.B in +.I word\^ +list. +If +.BI in " word\^" +\&.\|.\|. +is omitted, then +the +.B for +command executes the \f3do\fP \f2list\^\fP once for each positional parameter +that is set starting from +.B 1 +(see +.I "Parameter Expansion\^" +below). +Execution ends when there are no more words in the list. +.TP +\f3for ((\fP \*(OK\f2expr1\^\fP\*(CK \f3;\fP \*(OK\f2expr2\^\fP\*(CK \f3;\fP \*(OK\f2expr3\^\fP\*(CK \f3))\fP \f3;do\fP \f2list\^\fP \f3;done\fP +The arithmetic expression +.I expr1 +is evaluated first +(see +.I "Arithmetic evaluation" +below). +The arithmetic expression +.I expr2 +is repeatedly evaluated until it evaluates to zero and when non-zero, +.I list +is executed and the arithmetic expression +.I expr3 +evaluated. +If any expression +is omitted, then it behaves as if it evaluated to 1. +.TP +\f3select\fP \f2vname\^\fP \*(OK \f3in\fP \f2word\^\fP .\|.\|. \*(CK \f3;do\fP \f2list\^\fP \f3;done\fP +A +.B select +command prints on standard error (file descriptor 2) the set of +.IR word s, +each preceded by a number. +If +.BI in " word\^" +\&.\|.\|. +is omitted, then +the +positional parameters starting from +.B 1 +are used instead +(see +.I "Parameter Expansion\^" +below). +The +.SM +.B PS3 +prompt is printed +and a line is read from the standard input. +If this line consists of the number +of one of the listed +.IR word s, +then the value of the variable +.I vname\^ +is set to the +.I word\^ +corresponding to this number. +If this line is empty, the selection list is +printed again. +Otherwise the value of the variable +.I vname\^ +is set to +.IR null . +The contents of the line read from standard input is +saved in +the variable +.SM +.BR REPLY . +The +.I list\^ +is executed for each selection until a +.B break\^ +or +.I end-of-file\^ +is encountered. +If the +.SM +.B REPLY +variable is set to +.I null\^ +by the execution of +.IR list , +then the selection list is printed before +displaying the +.SM +.B PS3 +prompt for the next selection. +.TP +\f3case\fP \f2word\^\fP \f3in\fP \*(OK \*(OK\f3(\fP\*(CK\f2pattern\^\fP \*(OK \(bv \f2pattern\^\fP \*(CK .\|.\|. \f3)\fP \f2list\^\fP \f3;;\fP \*(CK .\|.\|. \f3esac\fP +A +.B case +command executes the +.I list\^ +associated with the first +.I pattern\^ +that matches +.IR word . +The form of the patterns is +the same as that used for +file-name generation (see +.I "File Name Generation\^" +below). +The +.B ;; +operator causes execution of +.B case +to terminate. +If +.B ;& +is used in place of +.B ;; +the next subsequent list, if any, is executed. +.TP +\f3if\fP \f2list\^\fP \f3;then\fP \f2list\^\fP \*(OK \ +\f3;elif\fP \f2list\^\fP \f3;then\fP \f2list\^\fP \*(CK .\|.\|. \ +\*(OK \f3;else\fP \f2list\^\fP \*(CK \f3;f\&i\fP +The +.I list\^ +following \f3if\fP is executed and, +if it +returns a zero exit status, the +.I list\^ +following +the first +.B then +is executed. +Otherwise, the +.I list\^ +following \f3elif\fP +is executed and, if its value is zero, +the +.I list\^ +following +the next +.B then +is executed. +Failing each successive +.B elif +.IR list\^ , +the +.B else +.I list\^ +is executed. +If the +.B if +.I list\^ +has non-zero exit status +and there is no +.B else +.IR list , +then the +.B if +command returns a zero exit status. +.TP +.PD 0 +\f3while\fP \f2list\^\fP \f3;do\fP \f2list\^\fP \f3;done\fP +.TP +\f3until\fP \f2list\^\fP \f3;do\fP \f2list\^\fP \f3;done\fP +.PD +A +.B while +command repeatedly executes the +.B while +.I list\^ +and, if the exit status of the last command in the list is zero, executes +the +.B do +.IR list ; +otherwise the loop terminates. +If no commands in the +.B do +.I list\^ +are executed, then the +.B while +command returns a zero exit status; +.B until +may be used in place of +.B while +to negate +the loop termination test. +.TP +\f3((\fP\f2expression\^\fP\f3))\fP +.br +The +.I expression\^ +is evaluated using the rules for arithmetic evaluation described below. +If the value of the arithmetic expression is non-zero, the exit +status is 0, otherwise the exit status is 1. +.TP +\f3(\fP\f2list\^\fP\f3)\fP +.br +Execute +.I list\^ +in a separate environment. +Note, that if two adjacent open parentheses are +needed for nesting, a space must be inserted to avoid +evaluation as an arithmetic command as described above. +.TP +\f3{ \fP\f2list\^\fP\f3;}\fP +.br +.I list\^ +is simply executed. +Note that unlike the metacharacters +.B ( +and +.BR ) , +.B { +and +.B } +are +.IR "reserved word" s +and must occur +at the beginning of a line or after a +.B ; +in order to be recognized. +.TP +\f3[[\fP\f2 expression \^\fP\f3]]\fP +.br +Evaluates +.I expression\^ +and returns a zero exit status when +.I expression\^ +is true. +See +.I "Conditional Expressions\^" +below, for a description of +.IR expression . +.TP +.PD 0 +\f3function\fP \f2varname\^\fP \f3{\fP \f2list\^\fP \f3;}\fP +.TP +\f2varname\^\fP \f3() {\fP \f2list\^\fP \f3;}\fP +.PD +Define a function which is referenced by +.IR varname . +A function whose +.I varname\^ +contains a +.B \s+2.\s-2 +is called a discipline function and the portion +of the +.I varname\^ +preceding the last +.B \s+2.\s-2 +must refer to an existing variable. +The body of the function is the +.I list\^ +of commands between +.B { +and +.BR } . +A function defined with the \f3function\fP \f2varname\^\fP +syntax can also be used as an argument to the \f3.\fP +special built-in command to get the equivalent behavior +as if the \f2varname\^\fP\f3()\fP syntax were used to define it. +(See +.I Functions\^ +below.) +.TP +\f3time\fP \*(OK \f2pipeline\^\fP \*(CK +.br +If \f2pipeline\^\fP is omitted the user and system time for +the current shell and completed child processes is printed +on standard error. +Otherwise, +.I pipeline\^ +is executed and the elapsed time as well as +the user and system time are printed on standard error. +The +.SM +.B TIMEFORMAT +variable may be set to a format string that specifies how the timing +information should be displayed. +See +.B "Shell Variables" +below +for a description of the +.SM +.B TIMEFORMAT +variable. +.PP +The following reserved words +are recognized as reserved only when they are the first word of a command +and are not quoted: +.PP +.if t .RS +.B +.if n if then else elif fi case esac for while until do done { } function select time [[ ]] ! +.if t if then else elif fi case esac for while until do done { } function select time [[ ]] ! +.if t .RE +.SS Variable Assignments. +One or more variable assignments can start a simple command +or can be arguments to the +.BR typeset , +.BR export , +or +.B readonly +special built-in commands. +The syntax for an \f2assignment\^\fP is of the form: +.TP +.PD 0 +\f2varname\^\fP\f3=\fP\f2word\^\fP +.TP +\f2varname\^\fP\f3[\fP\f2word\^\fP\f3]\fP=\fP\f2word\^\fP +.PD +No space is permitted between \f2varname\^\fP and the \f3=\fP or +between \f3=\fP and \fIword\^\fP. +.TP +\f2varname\^\fP\f3=(\fP\f2assign_list\^\fP\f3)\fP +No space is permitted between \f2varname\^\fP and the \f3=\fP. +An \f2assign_list\^\fP can be one of the following: +.RS 15 +.PD 0 +.TP +\f2word\^\fP ... +Indexed array assignment. +.TP +\f3[\fP\f2word\^\fP\f3]=\fP\f2word\^\fP .\|.\|. +Associative array assignment. +If preceded by +.B typeset \-a +this will create an indexed array instead. +.TP +\f2assignment\^\fP .\|.\|. +Compound variable assignment. +This creates a compound variable \f2varname\^\fP with +sub-variables of the form \f2varname\^\fP\f3.\fP\f2name\^\fP, +where \f2name\^\fP is the name portion of \f2assignment\^\fP. +The value of \f2varname\^\fP will contain all the assignment elements. +Additional assignments made to sub-variables of \f2varname\^\fP +will also be displayed as part of the value of \f2varname\^\fP. +If no \f2assignment\fPs are specified, \f2varname\^\fP will be +a compound variable allowing subsequence child elements to be defined. +.TP +\f3typeset\fP \*(OK\f2options\fP\*(CK \f2assignment\^\fP .\|.\|. +Nested variable assignment. Multiple assignments +can be specified by separating each of them with a \f3;\fP. +The previous value is unset before the assignment. +.PD +.RE +.P +In addition, a \f3+=\fP can be used in place of the \f3=\fP +to signify adding to or appending to the previous value. +When \f3+=\fP is applied to an arithmetic type, \f2word\^\fP +is evaluated as an arithmetic expression and added to the current value. +When applied to a string variable, the value defined by \f2word\^\fP +is appended to the value. For compound assignments, the previous +value is not unset and the new values are appended to the +current ones provided that the types are compatible. +.SS Comments. +.PD 0 +A word beginning with +.B # +causes that word and all the following characters up to a new-line +to be ignored. +.SS Aliasing. +The first word of each command is replaced by the text of an +.B alias +if an +.B alias +for this word has been defined. +An +.B alias +name consists of any number of characters excluding metacharacters, +quoting characters, +file expansion characters, +parameter expansion and command substitution +characters, +and +.BR = . +The replacement string can contain any +valid shell script +including the metacharacters listed above. +The first word of each command in the +replaced text, +other than +any that are in the process of being replaced, +will be tested for aliases. +If the last character of the alias value is a +.I blank\^ +then the word following the alias will also be checked for alias +substitution. +Aliases can be used to redefine +built-in commands but cannot be used to redefine +the reserved words listed above. +Aliases can be created and listed with the +.B alias +command and can be removed with the +.B unalias +command. +.PP +.I Aliasing\^ +is performed when +scripts are read, +not while they are executed. +Therefore, +for an alias to take effect, +the +.B +alias +definition command has to be executed before +the command which references the alias is read. +.PP +The following aliases +are compiled into the shell +but can be unset or redefined: +.RS 20 +.PD 0 +.TP +.B "autoload=\(fmtypeset \-fu\(fm" +.TP +.B "command=\(fmcommand \(fm" +.TP +.B "fc=hist" +.TP +.B "float=\(fmtypeset \-lE\(fm" +.TP +.B "functions=\(fmtypeset \-f\(fm" +.TP +.B "hash=\(fmalias \-t \-\^\-\(fm" +.TP +.B "history=\(fmhist \-l\(fm" +.TP +.B "integer=\(fmtypeset \-li\(fm" +.TP +.B "nameref=\(fmtypeset \-n\(fm" +.TP +.B "nohup=\(fmnohup \(fm" +.TP +.B "r=\(fmhist \-s\(fm" +.TP +.B "redirect=\(fmcommand exec\(fm" +.TP +.B "source=\(fmcommand \s+2.\s-2\(fm" +.TP +.B "stop=\(fmkill \-s \s-1STOP\s+1\(fm" +.TP +.B "suspend=\(fmkill \-s \s-1STOP\s+1 $$\(fm" +.TP +.B "times=\(fm{ { time;} 2>&1;}\(fm" +.TP +.B "type=\(fmwhence \-v\(fm" +.PD +.RE +.SS Tilde Substitution. +After alias substitution is performed, each word +is checked to see if it begins with an unquoted +.BR \(ap . +For tilde substitution, +.I word\^ +also refers to the +.I word\^ +portion of parameter expansion +(see +.I "Parameter Expansion\^" +below). +If it does, then the word up to a +.B / +is checked to see if it matches a user name in the +password database (See +.IR getpwname (3).) +If a match is found, the +.B \(ap +and the matched login name are replaced by the +login directory of the matched user. +If no match is found, the original text is left unchanged. +A +.B \(ap +by itself, or in front of a +.BR / , +is replaced by +.SM +.BR $HOME . +A +.B \(ap +followed by a +.B + +or +.B \- +is replaced by the value of +.B +.SM $PWD +and +.B +.SM $OLDPWD +respectively. +.PP +In addition, +when expanding a +.IR "variable assignment" , +.I tilde +substitution is attempted when +the value of the assignment +begins with a +.BR \(ap , +and when a +.B \(ap +appears after a +.BR : . +The +.B : +also terminates a +.B \(ap +login name. +.SS Command Substitution. +The standard output from a command enclosed in +parentheses preceded by a dollar sign ( +.B $(\|) +) +or a pair of grave accents (\^\f3\*`\^\*`\fP\^) +may be used as part or all +of a word; +trailing new-lines are removed. +In the second (obsolete) form, the string between the quotes is processed +for special quoting characters before the command is executed (see +.I Quoting\^ +below). +The command substitution +\^\f3$(\^cat file\^)\fP\^ +can be replaced by the equivalent but faster +\^\f3$(\^<file\^)\fP\^. +The command substitution +\^\f3$(\^\fP\f2n\^\fP\f3<#\^)\fP +will expand to the current byte offset for file descriptor +.IR n . +.SS Arithmetic Substitution. +An arithmetic expression enclosed in double +parentheses preceded by a dollar sign ( +.B $((\|)) +) +is replaced by the value of the arithmetic expression +within the double parentheses. +.SS Process Substitution. +This feature is only available on +versions of the UNIX operating system that support the +.B /dev/fd +directory for naming open files. +Each command argument of the form +\f3<(\fP\f2list\^\fP\f3)\fP +or +\f3>(\fP\f2list\^\fP\f3)\fP +will run process +.I list +asynchronously connected to some file in +.BR /dev/fd . +The name of this file will become the argument to the command. +If the form with +.B > +is selected then writing on this file will provide input for +.IR list . +If +.B < +is used, +then the file passed as an argument will contain the output of the +.I list +process. +For example, +.PP +.RS +\f3paste <(cut \-f1\fP \f2file1\fP\f3) <(cut \-f3\fP \f2file2\f3) | tee >(\fP\f2process1\fP\f3) >(\fP\f2process2\fP\f3)\fP +.RE +.PP +.I cuts +fields 1 and 3 from +the files +.I file1 +and +.I file2 +respectively, +.I pastes +the results together, and +sends it +to the processes +.I process1 +and +.IR process2 , +as well as putting it onto the standard output. +Note that the file, which is passed as an argument to the command, +is a UNIX +.IR pipe (2) +so programs that expect to +.IR lseek (2) +on the file will not work. +.SS Parameter Expansion. +A +.I parameter\^ +is a +.IR variable , +one or more digits, +or any of the characters +.BR \(** , +.BR @ , +.BR # , +.BR ? , +.BR \- , +.BR $ , +and +.BR !\\^ . +A +.I variable\^ +is denoted by a \f2vname\fP. +To create a variable whose +.I vname\^ +contains a \f3\s+2.\s-2\fP, +a variable whose +.I vname\^ +consists of everything before the last \f3\s+2.\s-2\fP must already exist. +A +.I variable\^ +has a +.I value\^ +and zero or more +.IR attributes . +.I Variables\^ +can be assigned +.I values\^ +and +.I attributes +by using the +.B typeset\^ +special built-in command. +The attributes supported by the shell are described +later with the +.B typeset\^ +special built-in command. +Exported variables pass values and attributes to +the environment. +.PP +The shell supports both indexed and associative arrays. +An element of an array variable is referenced by a +.IR subscript . +A +.I subscript\^ +for an indexed array is denoted by +an +.I arithmetic expression\^ +(see +.I "Arithmetic evaluation" +below) +between a +.B [ +and a +.BR ] . +To assign values to an indexed array, use +\f3set \-A\fP \f2vname\fP \f2value\fP .\|.\|. . +The value of all +subscripts must be in the +range of +0 through 1,048,575. +Indexed arrays need not be declared. +Any reference to a variable +with a valid subscript is +legal and an array will be created if necessary. +.PP +An associative array is created with the +.B \-A +option to +.BR typeset. +A +.I subscript\^ +for an associative array is denoted by +a string enclosed between +.B [ +and +.BR ] . +.PP +Referencing any array without a subscript +is equivalent to referencing the array with subscript 0. +.PP +The +.I value\^ +of a +.I variable\^ +may be assigned by writing: +.PP +.RS +.IB vname = value\^\| +\*(OK +.IB vname = value\^ +\*(CK .\|.\|. +.RE +.PP +.PD 0 +or +.PP +.RS +.IB vname [ subscript ]= value\^\| +\*(OK +.IB vname [ subscript ]= value\^ +\*(CK .\|.\|. +.sp .5 +.RE +Note that no space is allowed before or after the +.BR = . +.sp .5 +.PP +.PD 0 +A +.I nameref\^ +is a variable that is a reference to another variable. +A nameref is created with the +.B \-n +attribute of +.BR typeset . +The value of the variable at the time of the +.B typeset +command becomes the variable that will be referenced whenever +the nameref variable is used. +The name of a nameref cannot contain a \fB\s+2.\s-2\fP. +When a variable or function name contains a \fB\s+2.\s-2\fP, and the portion +of the name up to the first \fB\s+2.\s-2\fP matches the +name of a nameref, the variable referred to is obtained by +replacing the nameref portion with the name of the variable +referenced by the nameref. +If a nameref is used as the index of a \fBfor\fP loop, +a name reference is established for each item in the list. +A nameref provides a convenient way to refer to the variable +inside a function whose name is passed as an argument to a function. +For example, if the name of a variable is passed as the first +argument to a function, the command +.PP +.RS +\fBtypeset \-n var=$1\fR +.RE +.PP +inside the function causes references and assignments to +.B var +to be references and assignments to the variable whose +name has been passed to the function. +.sp .5 +.PP +If either of the floating point attributes, +.BR \-E , +or +.BR \-F , +or the integer attribute, +.BR \-i , +is set for +.IR vname , +then the +.I value\^ +is subject to arithmetic evaluation as described below. +.sp .5 +.PP +Positional parameters, +parameters denoted by a number, +may be assigned values with the +.B set\^ +special built-in command. +Parameter +.B $0 +is set from argument zero when the shell +is invoked. +.sp .5 +.PP +The character +.B $ +is used to introduce substitutable +.IR parameters . +.TP +\f3${\fP\f2parameter\^\fP\f3}\fP +The shell +reads all the characters from +.B ${ +to the matching +.B } +as part of the same word even if it contains +braces or metacharacters. +The value, if any, of the parameter is substituted. +The braces are required when +.I parameter\^ +is followed by a letter, digit, or underscore +that is not to be interpreted as part of its name, +when the variable name contains a \fB\s+2.\s-2\fP, +or when a variable is subscripted. +If +.I parameter\^ +is one or more digits then it is a positional parameter. +A positional parameter of more than one digit must be +enclosed in braces. +If +.I parameter\^ +is +.B \(** +or +.BR @ , +then all the positional +parameters, starting with +.BR $1 , +are substituted +(separated by a field separator character). +If an array +.I vname\^ +with subscript +.B \(** +or +.B @ +is used, +then the value +for each of the +elements +is substituted, +separated by +the first character of +the value of +.SM +.BR IFS . +.TP +\f3${#\fP\f2parameter\^\fP\f3}\fP +If +.I parameter\^ +is +.B \(** +or +.BR @ , +the number of positional parameters is substituted. +Otherwise, the length of the value of the +.I parameter\^ +is substituted. +.TP +.PD 0 +\f3${#\fP\f2vname\fP\f3[*]}\fP +.TP +.PD +\f3${#\fP\f2vname\fP\f3[@]}\fP +The number of elements in the array +.I vname\^ +is substituted. +.TP +\f3${!\fP\f2vname\^\fP\f3}\fP +Expands to the name of the variable referred to by +.IR vname . +This will be +.I vname\^ +except when +.I vname\^ +is a name reference. +.TP +\f3${!\fP\f2vname\^\fP\f3[\f2subscript\^\f3]}\fP +Expands to name of the subscript unless +.I subscript\^ +is +.B * +or +.BR @ . +When +.I subscript\^ +is +.BR * , +the list of array subscripts for \f2vname\^\fP +is generated. +For a variable that is not an array, the value is 0 if the variable +is set. Otherwise it is null. +When +.I subscript\^ +is +.BR @ , +same as above, except that when used in double quotes, +each array subscript yields a separate +argument. +.TP +\f3${!\fP\f2prefix\^\fP\f3*}\fP +Expands to the names of the variables whose names begin with +.IR prefix . +.TP +\f3${\fP\f2parameter\^\fP\f3:\-\fP\f2word\^\fP\f3}\fP +If +.I parameter\^ +is set and is non-null then substitute its value; +otherwise substitute +.IR word . +.TP +\f3${\fP\f2parameter\^\fP\f3:=\fP\f2word\^\fP\f3}\fP +If +.I parameter\^ +is not set or is null then set it to +.IR word ; +the value of the parameter is then substituted. +Positional parameters may not be assigned to +in this way. +.TP +\f3${\fP\f2parameter\^\fP\f3:?\fP\f2word\^\fP\f3}\fP +If +.I parameter\^ +is set and is non-null then substitute its value; +otherwise, print +.I word\^ +and exit from the shell (if not interactive). +If +.I word\^ +is omitted then a standard message is printed. +.TP +\f3${\fP\f2parameter\^\fP\f3:+\fP\f2word\^\fP\f3}\fP +If +.I parameter\^ +is set and is non-null then substitute +.IR word ; +otherwise substitute nothing. +.PP +In the above, +.I word\^ +is not evaluated unless it is +to be used as the substituted string, +so that, in the following example, +.B pwd +is executed only if +.B d +is not set or is null: +.PP +.RS +print \|${d:\-\^$(\^pwd\^)\^} +.RE +.PP +If the colon ( +.B : ) +is omitted from the above expressions, +then the shell only checks whether +.I parameter\^ +is set or not. +.TP +.PD 0 +\f3${\fP\f2parameter\^\fP\f3:\fP\f2offset\^\fP\f3:\fP\f2length\^\fP\f3}\fP +.TP +\f3${\fP\f2parameter\^\fP\f3:\fP\f2offset\^\fP\f3}\fP +Expands to the portion of the value of +.I parameter\^ +starting at the character (counting from +.BR 0\^ ) +determined by expanding +.I offset\^ +as an arithmetic expression and consisting of the +number of characters determined by the arithmetic expression +defined by +.IR length. +In the second form, the remainder of the value is used. +If +A negative +.I offset\^ +counts backwards from the end of +.IR parameter . +Note that one or more +.IR blank s +is required in front of a minus sign +to prevent the shell from interpreting the operator as +.BR :\- . +If +.I parameter\^ +is +.B \(** +or +.BR @ , +or is an array name indexed by +.B \(** +or +.BR @ , +then +.I offset\^ +and +.I length\^ +refer to the array index and number +of elements respectively. +A negative +.I offset\^ +is taken relative to one greater than the highest subscript +for indexed arrays. +The order for associate arrays is unspecified. +.TP +.PD 0 +\f3${\fP\f2parameter\^\fP\f3#\fP\f2pattern\^\fP\f3}\fP +.TP +\f3${\fP\f2parameter\^\fP\f3##\fP\f2pattern\^\fP\f3}\fP +.PD +If +the shell +.I pattern\^ +matches the beginning of the value of +.IR parameter , +then the value of +this expansion is the value of the +.I parameter\^ +with the matched portion deleted; +otherwise the value of this +.I parameter\^ +is substituted. +In the first form the smallest matching pattern is deleted and in the +second form the largest matching pattern is deleted. +When +.I parameter\^ +is +.BR @ , +.BR * , +or an array variable with subscript +.B @ +or +.BR * , +the substring operation is applied to each element in turn. +.TP +.PD 0 +\f3${\fP\f2parameter\^\fP\f3%\fP\f2pattern\^\fP\f3}\fP +.TP +\f3${\fP\f2parameter\^\fP\f3%%\fP\f2pattern\^\fP\f3}\fP +.PD +If +the shell +.I pattern\^ +matches the end of the value of +.IR parameter , +then the value of +this expansion is the value of the +.I parameter\^ +with the matched part deleted; +otherwise substitute the value of +.IR parameter . +In the first form the smallest matching pattern is deleted and in the +second form the largest matching pattern is deleted. +When +.I parameter\^ +is +.BR @ , +.BR * , +or an array variable with subscript +.B @ +or +.BR * , +the substring operation is applied to each element in turn. +.TP +.PD 0 +\f3${\fP\f2parameter\^\fP\f3/\fP\f2pattern\^\fP\f3/\f2string\^\fP\f3}\fP +.TP +\f3${\fP\f2parameter\^\fP\f3//\fP\f2pattern\^\fP\f3/\f2string\^\fP\f3}\fP +.TP +\f3${\fP\f2parameter\^\fP\f3/#\fP\f2pattern\^\fP\f3/\f2string\^\fP\f3}\fP +.TP +\f3${\fP\f2parameter\^\fP\f3/%\fP\f2pattern\^\fP\f3/\f2string\^\fP\f3}\fP +.PD +Expands +.I parameter\^ +and replaces the longest match of +.I pattern\^ +with the given +.IR string. +Each occurrence of \f3\e\fP\f2n\^\fP in +.I string +is replaced by the portion of \f2parameter\^\fP +that matches the \f2n\^\fP-th sub-pattern. +In the first form, +only the first occurrence of +.I pattern\^ +is replaced. +In the second form, +each match for +.I pattern\^ +is replaced by the given +.IR string. +The third form restricts the pattern match to the beginning of the string +while the fourth form restricts the pattern match to the end of +the string. +When +.I string\^ +is null, the +.I pattern\^ +will be deleted and the +.B / +in front of +.I string\^ +may be omitted. +When +.I parameter\^ +is +.BR @ , +.BR * , +or an array variable with subscript +.B @ +or +.BR * , +the substitution operation is applied to each element in turn. +In this case, the +.I string\^ +portion of +.I word\^ +will be re-evaluated for each element. +.PP +The following +parameters +are automatically set by the shell: +.RS +.PD 0 +.TP +.B # +The number of positional parameters in decimal. +.TP +.B \- +Options supplied to the shell on invocation or by +the +.B set +command. +.TP +.B ? +The decimal value returned by the last executed command. +.TP +.B $ +The process number of this shell. +.TP +.B _ +Initially, the value of +.B _ +is an absolute pathname of the shell or script being executed +as passed in the +.IR environment . +Subsequently it is assigned the last argument of the previous command. +This parameter is not set for commands which are asynchronous. +This parameter is also used to hold the name of the matching +.B +.SM MAIL +file when checking for mail. +.TP +.B ! +The process number of the last background command invoked or +the most recent job put in the background with the +.B bg +built-in command. +.TP +.B .sh.command +When processing a +.SM +.B DEBUG +trap, this variable contains the current command line +that is about to run. +.TP +.B .sh.edchar +This variable contains the value of the keyboard character +(or sequence of characters if the first character is an ESC, ascii +.BR 033\^ ) +that has +been entered when processing a +.B +.SM KEYBD +trap +(see +.I "Key Bindings\^" +below). +If the value is changed as part of the trap action, then the new +value replaces the key (or key sequence) that caused the trap. +.TP +.B .sh.edcol +The character position of the cursor at the time of the most recent +.B +.SM KEYBD +trap. +.TP +.B .sh.edmode +The value is set to ESC when processing a +.B +.SM KEYBD +trap while in +.B vi +insert mode. (See +.I "Vi Editing Mode"\^ +below.) +Otherwise, +.B .sh.edmode +is null when processing a +.B +.SM KEYBD +trap. +.TP +.B .sh.edtext +The characters in the input buffer at the time of the most recent +.B +.SM KEYBD +trap. +The value is null when not processing a +.B +.SM KEYBD +trap. +.TP +.B .sh.file +The pathname of the file than contains the current command. +.TP +.B .sh.fun +The name of the current function that is being executed. +.TP +.B .sh.match +An indexed array which stores the most recent match and sub-pattern +matches after conditional pattern matches that match and after +variables expansions using the operators +.BR # , +.BR % , +or +.BR / . +The +.BR 0 -th +element stores the complete match and the +.IR i\^ -th. +element stores the +.IR i\^ -th +submatch. +The +.B .sh.match +variable +becomes unset when the variable that has expanded +is assigned a new value. +.TP +.B .sh.name +Set to the name of the variable at the time that a +discipline function is invoked. +.TP +.B .sh.subscript +Set to the name subscript of the variable at the time that a +discipline function is invoked. +.TP +.B .sh.subshell +The current depth for subshells and command substitution. +.TP +.B .sh.value +Set to the value of the variable at the time that the +.B set +or +.B append +discipline function is invoked. +.TP +.B .sh.version +Set to a value that identifies the version of this shell. +.TP +.B +.SM LINENO +The current line number within the script or +function being executed. +.TP +.B +.SM OLDPWD +The previous working directory set by the +.B cd +command. +.TP +.B +.SM OPTARG +The value of the last option argument processed by the +.B getopts +built-in command. +.TP +.B +.SM OPTIND +The index of the last option argument processed by the +.B getopts +built-in command. +.TP +.B +.SM PPID +The process number of the parent of the shell. +.TP +.B +.SM PWD +The present working directory set by the +.B cd +command. +.TP +.B +.SM RANDOM +Each time this variable is referenced, a random integer, +uniformly distributed between 0 and 32767, is generated. +The sequence of random numbers can be initialized by assigning +a numeric value to +.SM +.BR RANDOM . +.TP +.B +.SM REPLY +This variable is set by the +.B select +statement and by +the +.B read +built-in command when no arguments are supplied. +.TP +.B +.SM SECONDS +Each time this variable is referenced, the number of +seconds since shell invocation is returned. +If this variable is +assigned a value, then the value returned upon reference will +be the value that was assigned plus the number of seconds since the assignment. +.PD +.RE +.PP +The following +variables +are used by the shell: +.RS +.PD 0 +.TP +.B +.SM CDPATH +The search path for the +.B cd +command. +.TP +.B +.SM COLUMNS +If this variable is set, +the value is used to define the width of the edit window +for the shell edit modes and for printing +.B select +lists. +.TP +.B +.SM EDITOR +If the +.B +.SM VISUAL +variable is not set, +the value of this variable will be checked for the patterns +as described with +.B +.SM VISUAL +below and the corresponding editing option +(see Special Command +.B set +below) +will be turned on. +.TP +.SM +.B ENV +If this variable is set, then +parameter expansion, command substitution, and arithmetic substitution +are performed on +the value to generate +the pathname of the script that will be +executed when the shell +is invoked +(see +.I Invocation\^ +below). +This file is typically used for +.B alias +and +.B function +definitions. +The default value is \fB$HOME/.kshrc\fP. +.TP +.B +.SM FCEDIT +Obsolete name for +the default editor name for the +.B hist +command. +.B +.SM FCEDIT +is not used when +.B +.SM HISTEDIT +is set. +.TP +.SM +.B FIGNORE +A pattern that defines the set of filenames that will be +ignored when performing filename matching. +.TP +.SM +.B FPATH +The search path for function definitions. +The directories in this path are searched for a file with the same name +as the function or command when a function with the +.B \-u +attribute is referenced and when a command is not found. +If an executable file with the name of that command is found, +then it is read and executed +in the current environment. +Unlike +.SM +.BR PATH , +the current directory must be represented +explictily by +.B . +rather than by adjacent +.B : +characters or a beginning or ending +.BR : . +.TP +.B +.SM HISTCMD +Number of the current command in the history file. +.TP +.B +.SM HISTEDIT +Name for +the default editor name for the +.B hist +command. +.TP +.SM +.B HISTFILE +If this variable is set when the shell is invoked, then +the value is the pathname of the file that will be +used to store the command history (see +.I "Command Re-entry\^" +below). +.TP +.SM +.B HISTSIZE +If this variable is set when the shell is invoked, then +the number of previously entered commands that +are accessible by this shell +will be greater than or equal to this number. +The default is 512. +.TP +.B +.SM HOME +The default argument (home directory) for the +.B cd +command. +.TP +.SM +.B IFS +Internal field separators, +normally +.BR space , +.BR tab , +and +.B new-line +that are used to separate the results of +command substitution or parameter expansion +and to separate fields with the built-in command +.BR read . +The first character of the +.SM +.B IFS +variable is used to separate arguments for the +.B +"$\(**" +substitution (see +.I Quoting +below). +Each single occurrence of +an +.SM +.B IFS +character in the string to be split, +that is not in the \f2isspace\^\fP character class, and any +adjacent characters in +.SM +.B IFS +that are in the \f2isspace\^\fP character class, delimit a field. +One or more +characters in +.SM +.B IFS +that belong to the \f2isspace\^\fP character class, +delimit a field. +In addition, if the same \f2isspace\^\fP character appears +consecutively inside +.SM +.BR IFS , +this character is treated as if it were not in the \f2isspace\^\fP +class, so that if +.SM +.BR IFS +consists of two +.B tab +characters, +then two adjacent +.B tab +characters delimit a null field. +.TP +.B +.SM LANG +This variable determines the locale category for any +category not specifically selected with a variable +starting with +.B +.SM LC_ +or +.SM +.BR LANG . +.TP +.B +.SM LC_ALL +This variable overrides the value of the +.B +.SM LANG +variable and any other +.B +.SM LC_ +variable. +.TP +.B +.SM LC_COLLATE +This variable determines the locale category for character +collation information. +.TP +.B +.SM LC_CTYPE +This variable determines the locale category for character +handling functions. +It determines the character classes for pattern matching (see +.I "File Name Generation\^" +below). +.TP +.B +.SM LC_NUMERIC +This variable determines the locale category for the +decimal point character. +.TP +.B +.SM LINES +If this variable is set, +the value is used to determine the column length for printing +.B select +lists. +Select lists will print vertically until about two-thirds of +.B +.SM LINES +lines are filled. +.TP +.B +.SM MAIL +If this variable is set to the name of a mail file +.I and\^ +the +.B +.SM MAILPATH +variable is not set, +then the shell informs the user of arrival of mail +in the specified file. +.TP +.B +.SM MAILCHECK +This variable specifies how often (in seconds) the +shell will check for changes in the modification time +of any of the files specified by the +.B +.SM MAILPATH +or +.B +.SM MAIL +variables. +The default value is 600 seconds. +When the time has elapsed +the shell will check before issuing the next prompt. +.TP +.B +.SM MAILPATH +A colon ( +.B : +) +separated list of file names. +If this variable is set, +then the shell informs the user of +any modifications to the specified files +that have occurred within the last +.B +.SM MAILCHECK +seconds. +Each file name can be followed by a +.B ? +and a message that will be printed. +The message will undergo parameter expansion, command substitution, +and arithmetic substitution +with the variable +.B $_ +defined as the name of the file that has changed. +The default message is +.I you have mail in $_\^. +.TP +.B +.SM PATH +The search path for commands (see +.I Execution\^ +below). +The user may not change +.B \s-1PATH\s+1 +if executing under +.if \nZ=0 .B rsh +.if \nZ=1 .B rksh +.if \nZ=2 .B rksh93 +(except in +.BR .profile\^). +.TP +.SM +.B PS1 +The value of this variable is expanded for parameter +expansion, command substitution, and arithmetic substitution to define the +primary prompt string which by default is +.RB `` "$\|\|\|" ''. +The character +.B ! +in the primary prompt string is replaced by the +.I command\^ +number (see +.I "Command Re-entry\^" +below). +Two successive occurrences of +.B ! +will produce a single +.B ! +when the prompt string is printed. +.TP +.SM +.B PS2 +Secondary prompt string, by default +.RB `` "> \|" ''. +.TP +.SM +.B PS3 +Selection prompt string +used within a +.B select +loop, by default +.RB `` "#? \|" ''. +.TP +.SM +.B PS4 +The value of this variable is expanded for parameter evaluation, +command substitution, and arithmetic substitution +and precedes each line of an execution trace. +By default, +.SM +.B PS4 +is +.RB `` "+ \|" ''. +In addition +when +.SM +.B PS4 +is unset, +the execution trace prompt is also +.RB `` "+ \|" ''. +.TP +.SM +.B SHELL +The pathname of the +.I shell\^ +is kept in the environment. +At invocation, if the basename of this variable is +.BR rsh , +.BR rksh , +or +.BR krsh , +then the shell becomes restricted. +If it is +.BR pfsh +or +.BR pfksh , +then the shell becomes a profile shell (see +.IR pfexec (1)). +.TP +.SM +.B TIMEFORMAT +The value of this parameter is used as a format string specifying +how the timing information for pipelines prefixed with the +.B time +reserved word should be displayed. +The \fB%\fP character introduces a format sequence that is +expanded to a time value or other information. +The format sequences and their meanings are as follows. +.sp .5 +.RS +.PD 0 +.TP 10 +.B %% +A literal \fB%\fP. +.TP +.B %[\fIp\fP][l]R +The elapsed time in seconds. +.TP +.B %[\fIp\fP][l]U +The number of CPU seconds spent in user mode. +.TP +.B %[\fIp\fP][l]S +The number of CPU seconds spent in system mode. +.TP +.B %P +The CPU percentage, computed as (U + S) / R. +.PD +.RE +.IP +The braces denote optional portions. +The optional \fIp\fP is a digit specifying the \fIprecision\fP, +the number of fractional digits after a decimal point. +A value of 0 causes no decimal point or fraction to be output. +At most three places after the decimal point can be displayed; +values of \fIp\fP greater than 3 are treated as 3. +If \fIp\fP is not specified, the value 3 is used. +.IP +The optional \fBl\fP specifies a longer format, including +hours if greater than zero, +minutes, and seconds of the form \fIHH\fPh\fIMM\fPm\fISS\fP.\fIFF\fPs. +The value of \fIp\fP determines whether or not the fraction is +included. +.IP +All other characters are output without change and a trailing +newline is added. +If unset, the default value, \fB$'\enreal\et%2lR\enuser\et%2lU\ensys\t%2lS'\fP, +is used. If the value is null, no timing information is displayed. +.TP +.B +.SM TMOUT +If set to a value greater than zero, +.B +.SM TMOUT +will be the default timeout value for the +.B read +built-in command. +The +.B select +compound command terminates after +.B +.SM TMOUT +seconds when input is from a terminal. +Otherwise, +the shell will terminate if a line is not entered within +the prescribed number of seconds while reading from a terminal. +(Note that the shell can be compiled with a maximum bound +for this value which cannot be exceeded.) +.TP +.B +.SM VISUAL +If the value of this variable matches the pattern +.IR *[Vv][Ii]* , +then the +.B vi +option +(see Special Command +.B set +below) +is turned on. +If the value matches the pattern +.I *gmacs* , +the +.B gmacs +option is turned on. +If the value matches the pattern +.IR *macs* , +then the +.B emacs +option +will be turned on. +The value of +.B +.SM VISUAL +overrides the value of +.B +.SM EDITOR. +.PD +.RE +.PP +The shell gives default values to +\f3\s-1PATH\s+1\fP, \f3\s-1PS1\s+1\fP, \f3\s-1PS2\s+1\fP, +\f3\s-1PS3\s+1\fP, \f3\s-1PS4\s+1\fP, \f3\s-1MAILCHECK\s+1\fP, \f3\s-1FCEDIT\s+1\fP, +\f3\s-1TMOUT\s+1\fP and \f3\s-1IFS\s+1\fP, +while +.SM +.BR HOME , +.SM +.BR SHELL , +.SM +.BR ENV , +and +.SM +.B MAIL +are +not set at all by the shell (although +.SM +.B HOME +.I is\^ +set by +.IR login (1)). +On some systems +.SM +.B MAIL +and +.SM +.B SHELL +are also +set by +.IR login (1). +.SS Field Splitting. +After parameter expansion and command substitution, +the results of substitutions are scanned for the field separator +characters (those found in +.SM +.B IFS\^\c +) +and split into distinct fields where such characters are found. +Explicit null fields (\^\f3"\^"\fP or \f3\(fm\^\(fm\fP\^) are retained. +Implicit null fields +(those resulting from +.I parameters\^ +that have no values or command substitutions with no output) are removed. +.PP +If the +.B braceexpand +.RB ( \-B ) +option is set then each of the fields resulting from +.SM +.B IFS +are checked to see if they contain one or more of the brace patterns +.BR {*,*} , +.BI { l1 .. l2 } +, +.BI { n1 .. n2 } +, +.BI { n1 .. n2 % +.IB fmt } +, +.BI { n1 .. n2 +.BI .. n3 } +, or +.BI { n1 .. n2 +.BI .. n3 % fmt } +, where +.B * +represents any character, +.IR l1\^ , l2\^ +are letters and +.IR n1\^ , n2\^ , n3\^ +are signed numbers and +.I fmt\^ +is a format specified as used by +.BR printf . +In each case, fields are created +by prepending the characters before the +.B { +and appending the characters after the +.B } +to each of the strings generated by the characters between +the +.B { +and +.BR } . +The resulting fields are checked to see if they have any +brace patterns. +.PP +In the first form, a field is created for each string between +.BR { +and +.BR , , +between +.BR , +and +.BR , , +and between +.BR , +and +.BR } . +The string represented by +.B * +can contain embedded matching +.B { +and +.B } +without quoting. +Otherwise, each +.B { +and +.B } +with +.B * +must be quoted. +.PP +In the seconds form, +.I l1\^ +and +.I l2\^ +must both be either upper case or both be lower case characters +in the C locale. In this case a field is created for each character +from +.I l1\^ +thru +.IR l2\^ . +.PP +In the remaining forms, a field is created for each number starting at +.I n1\^ +and continuing until it reaches +.I n2\^ +incrementing +.I n1\^ +by +.IR n3\^ . +The cases where +.I n3\^ +is not specified behave as if +.I n3\^ +where +.B 1 +if +.IB n1 <= n2 +and +.B \-1 +otherwise. +If forms which specify +.BI % fmt\^ +any format flags, widths and precisions can be specified +and +.I fmt\^ +can end in any of the specifiers +.BR cdiouxX . +For example, +.B {a,z}{1..5..3%02d}{b..c}x +expands to the 8 fields, +.BR a01bx , +.BR a01cx , +.BR a04bx , +.BR a04cx , +.BR z01bx , +.BR z01cx , +.B z04bx +and +.BR z4cx . +.SS File Name Generation. +Following splitting, each field is scanned for the characters +.BR \(** , +.BR ? , +.BR ( , +and +.B \*(OK\^ +unless the +.B \-f +option has been set. +If one of these characters appears, +then the word is regarded as a +.IR pattern . +Each file name component that contains any pattern character +is replaced with a lexicographically sorted set of names +that matches the pattern +from +that directory. +If no file name is found that matches the pattern, then +that component of the filename is left unchanged unless +the pattern is prefixed with +.B \(ap(N)\fP +in which case it is removed as described below. +If +.SM +.B FIGNORE +is set, +then each file name component +that matches the pattern defined by the value of +.SM +.B FIGNORE +is ignored when generating the matching filenames. +The names +.B . +and +.B .. +are also ignored. +If +.SM +.B FIGNORE +is not set, +the character +.B . +at the start of each file name component +will be ignored unless the first character of the pattern +corresponding to this component is the character +.BR . +itself. +Note, that for other +uses of pattern matching the +.B / +and +.B . +are not treated specially. +.PP +.PD 0 +.RS +.TP +.B \(** +Matches any string, including the null string. +When used for filename expansion, +if the +.B globstar +option is on, two adjacent +.BR \(** 's +by itself +will match all files and zero or more directories +and subdirectories. +If followed by a +.B / +than only directories and subdirectories will match. +.TP +.B ? +Matches any single character. +.TP +.BR \*(OK \^.\|.\|.\^ \*(CK +Matches any one of the enclosed characters. +A pair of characters separated by +.B \- +matches any +character lexically between the pair, inclusive. +If the first character following the opening +.B \*(OK\^ +is a +.B ! +then any character not enclosed is matched. +A +.B \- +can be included in the character set by putting it as the +first or last character. +.br +Within +.B \*(OK\^ +and +.BR \*(CK\^ , +character classes can be specified with the syntax +\f3[:\fP\f2class\fP\f3:]\fP +where class is one of the following classes defined in the ANSI-C standard: +(Note that \f3word\fP is equivalent to \f3alnum\fP plus the character \f3_\fP). +.PP +.if t .RS +.B +.if n alnum alpha blank cntrl digit graph lower print punct space upper word xdigit +.if t alnum alpha blank cntrl digit graph lower print punct space upper word xdigit +.br +Within +.B \*(OK\^ +and +.BR \*(CK\^ , +an equivalence class can be specified with the syntax +\f3[=\fP\f2c\fP\f3=]\fP +which matches all characters with the same primary +collation weight (as defined by the current locale) as +the character \f2c\fP. +.br +Within +.B \*(OK\^ +and +.BR \*(CK\^ , +\f3[.\fP\f2symbol\fP\f3.]\fP +matches the collating symbol \f2symbol\fP. +.if t .RE +.PD +.RE +A +.I pattern-list +is a list of one or more patterns separated from each other +with a +.B & +or +.BR \(bv . +A +.B & +signifies that all patterns must be matched whereas +.BR \(bv +requires that only one pattern be matched. +Composite patterns can be formed with one or more of the following sub-patterns: +.PD 0 +.RS +.TP +\f3?(\fP\f2pattern-list\^\fP\f3)\fP +Optionally matches any one of the given patterns. +.TP +\f3*(\fP\f2pattern-list\^\fP\f3)\fP +Matches zero or more occurrences of the given patterns. +.TP +\f3+(\fP\f2pattern-list\^\fP\f3)\fP +Matches one or more occurrences of the given patterns. +.TP +\f3{\fP\f2n\^\fP\f3}\fP(\fP\f2pattern-list\^\fP\f3)\fP +Matches \f2n\^\fP occurrences of the given patterns. +.TP +\f3{\fP\f2m\^\fP\f3,\fP\f2n\^\fP\f3}\fP(\fP\f2pattern-list\^\fP\f3)\fP +Matches from \f2m\^\fP to \f2n\^\fP occurrences of the given patterns. +If \f2m\^\fP is omitted, \f30\fP will be used. If \f2n\^\fP +is omitted at least \f2m\^\fP occurrences will be matched. +.TP +\f3\&@\&(\fP\f2pattern-list\^\fP\f3)\fP +Matches exactly one of the given patterns. +.br +.TP +\f3!(\fP\f2pattern-list\^\fP\f3)\fP +Matches anything except one of the given patterns. +.PD +.RE +By default, each pattern, or sub-pattern will match the +longest string possible consistent with generating +the longest overall match. If more than one match is +possible, the one starting closest to the beginning +of the string will be chosen. However, for each of the above +compound patterns a \f3\-\fP can be inserted in front of the \f3(\fP +to cause the shortest match to the specified \f2pattern-list\^\fP +to be used. +.PP +When \f2pattern-list\^\fP is contained within parenthesis, +the backslash character \f3\e\fP is treated specially even +when inside a character class. All ANSI-C character escapes are +recognized and match the specified character. In addition +the following escape sequences are recognized: +.PD 0 +.RS +.TP +.B \ed +Matches any character in the \f3digit\fP class. +.TP +.B \eD +Matches any character not in the \f3digit\fP class. +.TP +.B \es +Matches any character in the \f3space\fP class. +.TP +.B \eS +Matches any character not in the \f3space\fP class. +.TP +.B \ew +Matches any character in the \f3word\fP class. +.TP +.B \eW +Matches any character not in the \f3word\fP class. +.PD +.RE +.PP +A pattern of the form +\f3%(\fP\f2pattern-pair\^\fP(s)\f3)\fP +is a sub-pattern that +can be used to match nested character expressions. +Each +.I pattern-pair\^ +is a two character sequence which cannot contain +.B & +or +.BR \(bv . +The first +.I pattern-pair\^ +specifies the starting and ending characters for the match. +Each subsequent +.I pattern-pair\^ +represents the beginning and ending characters of a nested group that +will be skipped over when counting starting and ending character matches. +The behavior is unspecified when the first character of a +.I pattern-pair\^ +is alpha-numeric +except for the following: +.PD 0 +.RS +.TP +.B D +Causes the ending character to terminate the search for this pattern without +finding a match. +.TP +.B E +Causes the ending character to be interpreted as an escape character. +.TP +.B L +Causes the ending character to be interpreted as a quote character +causing all characters to be ignored when looking for a match. +.TP +.B Q +Causes the ending character to be interpreted as a quote character +causing all characters other than any escape character to be ignored +when looking for a match. +.PD +.RE +Thus, +\f3%(\^{\^}Q"E\e\^)\fP, +matches characters starting at +.B { +until the matching +.B } +is found not counting any +.B { +or +.B } +that is inside a double quoted string or preceded by the escape character +.BR \e . +Without the +.B {\^} +this pattern matches any C language string. +.PP +Each sub-pattern in a composite pattern is numbered, +starting at 1, by the location of the \f3(\fP within +the pattern. +The sequence \f3\e\fP\f2n\^\fP, where \f2n\^\fP +is a single digit and \f3\e\fP\f2n\^\fP comes after +the \f2n\fP-th. sub-pattern, +matches the same string as the sub-pattern itself. +.PP +Finally a pattern can contain sub-patterns of the form +\f3\(ap(\fP\f2options\^\fP\f3:\fP\f2pattern-list\^\fP\f3)\fP. +where either \f2options\^\fP or \f3:\fP\f2pattern-list\^\fP +can be omitted. Unlike, the other compound patterns, +these sub-patterns are not counted in the numbered sub-patterns. +If \f2options\^\fP is present, it can consist of one or more +of the following: +.PD 0 +.RS +.TP +.B + +Enable the following options. This is the default. +.TP +.B \- +Disable the following options. +.TP +.B E +The remainder of the pattern uses extended regular expression syntax +like the +.IR egrep (1) +command. +.TP +.B F +The remainder of the pattern uses +.IR fgrep (1) +expression syntax. +.TP +.B G +The remainder of the pattern uses basic regular expression syntax +like the +.IR grep (1) +command. +.TP +.B K +The remainder of the pattern uses shell pattern syntax. +This is the default. +.TP +.B N +This is ignored. However, when it is the first letter and is +used with file name generation, and no matches occur, +the file pattern expands to the empty string. +.TP +.B i +Treat the match as case insensitive. +.TP +.B g +File the longest match (greedy). This is the default. +.TP +.B l +Left anchor the pattern. This is the default for +.B K +style patterns. +.TP +.B r +Right anchor the pattern. This is the default for +.B K +style patterns. +.PD +.RE +If both \f2options\^\fP and \f3:\fP\f2pattern-list\^\fP +are specified, then the options apply only to \f2pattern-list\^\fP. +Otherwise, these options remain in effect until they are disabled +by a subsequent \f3\(ap(\fP\f2...\^\fP\f3)\fP or at the end of +the sub-pattern containing \f3\(ap(\fP\f2...\^\fP\f3)\fP. +.SS Quoting. +Each of the +.I metacharacters\^ +listed earlier (see +.I Definitions\^ +above) +has a special meaning to the shell +.TP +.B i +Treat the match as case insensitive. +.TP +.B g +File the longest match (greedy). This is the default. +.PD +.RE +If both \f2options\^\fP and \f3:\fP\f2pattern-list\^\fP +are specified, then the options apply only to \f2pattern-list\^\fP. +Otherwise, these options remain in effect until they are disabled +by a subsequent \f3\(ap(\fP\f2...\^\fP\f3)\fP or at the end of +the sub-pattern containing \f3\(ap(\fP\f2...\^\fP\f3)\fP. +.SS Quoting. +Each of the +.I metacharacters\^ +listed earlier (see +.I Definitions\^ +above) +has a special meaning to the shell +and causes termination of a word unless quoted. +A character may be +.I quoted\^ +(i.e., made to stand for itself) +by preceding +it with a +.BR \e . +The pair +.B \enew-line +is removed. +All characters enclosed between a pair of single quote marks +(\^\f3\(fm\^\(fm\fP\^) +that is not preceded by a +.B $ +are quoted. +A single quote cannot appear within the single quotes. +A single quoted string preceded by an unquoted +.B $ +is processed as an ANSI-C string +except for the following: +.PD 0 +.TP +.B \e0 +Causes the remainder of the string to be ignored. +.TP +.B \eE +Equivalent to the escape character +(ascii +.BR 033 ), +.TP +.B \ee +Equivalent to the escape character +(ascii +.BR 033 ), +.TP +.BI \ec x +Expands to the character control-\f2x\fP. +.TP +.BI \eC[. name .] +Expands to the collating element \f2name\fP. +.PD +.PP +Inside double quote marks +(\f3"\^"\fP), +parameter and command substitution occur and +.B \e +quotes the characters +.BR \e , +.BR \*` , +\f3"\fP, +and +.BR $ . +A +.B $ +in front of a double quoted string will be ignored +in the "C" or "POSIX" locale, and may cause +the string to be replaced by a locale specific string otherwise. +The meaning of +.B "$\(**" +and +.B "$@" +is identical when not quoted or when used as a variable assignment value +or as a file name. +However, when used as a command argument, +.B +"$\(**" +is equivalent to +\f3"$1\fP\f2d\fP\f3\|$2\fP\f2d\fP\|.\|.\|.\f3"\fP, +where +.I d +is the first character of the +.SM +.B IFS +variable, whereas +.B +"$@" +is equivalent to +.B +"$1"\| +.B +"$2"\| +\&.\|.\|.\^. +Inside grave quote marks +(\f3\*`\^\*`\fP), +.B \e +quotes the characters +.BR \e , +.BR \*` , +and +.BR $ . +If the grave quotes occur within double quotes, then +.B \e +also quotes the character +\f3"\fP. +.PP +The special meaning of reserved words or aliases can be removed by quoting any +character of the reserved word. +The recognition of function names or built-in command names listed below +cannot be altered by quoting them. +.SS Arithmetic Evaluation. +The shell performs arithmetic evaluation for +arithmetic substitution, to evaluate an arithmetic command, +to evaluate an indexed array subscript, +and to evaluate arguments to +the built-in commands +.B shift\^ +and +.BR let . +Evaluations are performed using +double precision floating point +arithmetic or long double precision floating point for +systems that provide this data type. +Floating point constants follow the ANSI-C programming language +floating point conventions. +Integer constants follow the ANSI-C programming language +integer constant conventions although only single byte +character constants are recognized and character casts +are not recognized. +In addition constants can be of the form +\*(OK\f2base\f3#\^\f1\*(CK\f2n\^\fP +where +.I base\^ +is a decimal number between two and sixty-four +representing the arithmetic base +and +.I n\^ +is a number in that base. +The digits above 9 are represented +by the lower case letters, the upper case letters, +.BR @ , +and +.B _ +respectively. +For bases less than or equal to 36, upper and lower case +characters can be used interchangeably. +.PP +An arithmetic expression uses the same syntax, precedence, and +associativity of +expression as the C language. +All the C language operators +that apply to floating point quantities can be used. +In addition, the operator +.B ** +can be used for exponentiation. +It has higher precedence than multiplication as is left associative. +In addition, when the value of an arithmetic variable +or sub-expression can be represented as a long integer, +all C language integer arithmetic operations can be performed. +Variables can be referenced by name within an arithmetic expression +without using the parameter expansion syntax. +When a variable is referenced, its value is evaluated as +an arithmetic expression. +.PP +Any of the following math library functions that are in the C math library +can be used within an arithmetic expression: +.PP +.if t .RS +.B +.if n abs acos acosh asin asinh atan atan2 atanh cbrt copysign cos cosh erf erfc exp exp2 expm1 fabs fdim finite floor fma fmax fmod hypot ilogb int isinf isnan lgamma log log2 logb nearbyint nextafter nexttoward pow remainder rint round sin sinh sqrt tan tanh tgamma trunc +.if t abs acos acosh asin asinh atan atan2 atanh cbrt copysign cos cosh erf erfc exp exp2 expm1 fabs fdim finite floor fma fmax fmod hypot ilogb int isinf isnan lgamma log log2 logb nearbyint nextafter nextroward pow rint round sin sinh sqrt tan tanh tgamma trunc +.if t .RE +.PP +An internal representation of a +.I variable\^ +as a double precision floating point can be specified with the +\f3\-E\fP \*(OK\f2n\^\fP\*(CK +or +\f3\-F\fP \*(OK\f2n\^\fP\*(CK +option of the +.B typeset +special built-in command. +The +.B \-E +option causes the expansion of the value to be represented using +scientific notation when it is expanded. +The optional option argument +.I n +defines the number of significant figures. +The +.B \-F +option causes the expansion to be represented as a floating decimal number +when it is expanded. +The optional option argument +.I n +defines the number of places after the decimal point in this case. +.PP +An internal integer representation of a +.I variable\^ +can be specified with the +\f3\-i\fP \*(OK\f2n\^\fP\*(CK +option of the +.B typeset +special built-in command. +The optional option argument +.I n +specifies an arithmetic base to be used when expanding the variable. +If you do not specify an arithmetic base, +base 10 will be used. +.PP +Arithmetic evaluation is performed on the value of each +assignment to a variable with the +.BR \-E , +.BR \-F , +or +.B \-i +attribute. +Assigning a floating point number to a +variable whose type is an integer causes the fractional +part to be truncated. +.PP +.SS Prompting. +When used interactively, +the shell prompts with the value of +.SM +.B PS1 +after expanding it for parameter expansion, command substitution, and +arithmetic substitution, +before reading a command. +In addition, each single +.B ! +in the prompt is replaced by the command number. +A +.B !! +is required to place +.B ! +in the prompt. +If at any time a new-line is typed and further input is needed +to complete a command, then the secondary prompt +(i.e., the value of +.BR \s-1PS2\s+1 ) +is issued. +.SS Conditional Expressions. +A +.I "conditional expression" +is used with the +.B [[ +compound command to test attributes of files and to compare +strings. +Field splitting and file name generation are +not performed on the words between +.B [[ +and +.BR ]] . +Each expression can be constructed from one or more +of the following unary or binary expressions: +.PD 0 +.TP +\f2string\fP +True, if +.I string +is not null. +.TP +\f3\-a\fP \f2file\fP +Same as \f3\-e\fP below. +This is obsolete. +.TP +\f3\-b\fP \f2file\fP +True, if +.I file\^ +exists and is a block special file. +.TP +\f3\-c\fP \f2file\fP +True, if +.I file\^ +exists and is a character special file. +.TP +\f3\-d\fP \f2file\fP +True, if +.I file\^ +exists and is a directory. +.TP +\f3\-e\fP \f2file\fP +True, if +.I file\^ +exists. +.TP +\f3\-f\fP \f2file\fP +True, if +.I file\^ +exists and is an ordinary file. +.TP +\f3\-g\fP \f2file\fP +True, if +.I file\^ +exists and it has its setgid bit set. +.TP +\f3\-k\fP \f2file\fP +True, if +.I file\^ +exists and it has its sticky bit set. +.TP +\f3\-n\fP \f2string\fP +True, if length of +.I string\^ +is non-zero. +.TP +\f3\-o\fP \f3?\fP\f2option\fP +True, if option named +.I option\^ +is a valid option name. +.TP +\f3\-o\fP \f2option\fP +True, if option named +.I option\^ +is on. +.TP +\f3\-p\fP \f2file\fP +True, if +.I file\^ +exists and is a fifo special file or a pipe. +.TP +\f3\-r\fP \f2file\fP +True, if +.I file\^ +exists and is readable by current process. +.TP +\f3\-s\fP \f2file\fP +True, if +.I file\^ +exists and has size greater than zero. +.TP +\f3\-t\fP \f2fildes\fP +True, if file descriptor number +.I fildes\^ +is open and associated with a terminal device. +.TP +\f3\-u\fP \f2file\fP +True, if +.I file\^ +exists and it has its setuid bit set. +.TP +\f3\-w\fP \f2file\fP +True, if +.I file\^ +exists and is writable by current process. +.TP +\f3\-x\fP \f2file\fP +True, if +.I file\^ +exists and is executable by current process. +If +.I file\^ +exists and is a directory, then true if the current process +has permission to search in the directory. +.TP +\f3\-z\fP \f2string\fP +True, if length of +.I string\^ +is zero. +.TP +\f3\-L\fP \f2file\fP +True, if +.I file\^ +exists and is a symbolic link. +.TP +\f3\-h\fP \f2file\fP +True, if +.I file\^ +exists and is a symbolic link. +.TP +\f3\-N\fP \f2file\fP +True, if +.I file\^ +exists and the modification time is greater than the last access time. +.TP +\f3\-O\fP \f2file\fP +True, if +.I file\^ +exists and is owned by the effective user id of this process. +.TP +\f3\-G\fP \f2file\fP +True, if +.I file\^ +exists and its group matches the effective group id of this process. +.TP +\f3\-S\fP \f2file\fP +True, if +.I file\^ +exists and is a socket. +.TP +\f2file1\fP \f3\-nt\fP \f2file2\fP +True, if +.I file1\^ +exists and +.I file2\^ +does not, or +.I file1\^ +is newer than +.IR file2 . +.TP +\f2file1\fP \f3\-ot\fP \f2file2\fP +True, if +.I file2\^ +exists and +.I file1\^ +does not, or +.I file1\^ +is older than +.IR file2 . +.TP +\f2file1\fP \f3\-ef\fP \f2file2\fP +True, if +.I file1\^ +and +.I file2\^ +exist and refer to the same file. +.TP +\f2string\fP \f3==\fP \f2pattern\fP +True, if +.I string\^ +matches +.IR pattern . +Any part of +.I pattern\^ +can be quoted to cause it to be matched as a string. +With a successful match to a pattern, the +.B .sh.match +array variable will contain the match and sub-pattern matches. +.TP +\f2string\fP \f3=\fP \f2pattern\fP +Same as \f3==\fP above, but is obsolete. +.TP +\f2string\fP \f3!=\fP \f2pattern\fP +True, if +.I string\^ +does not match +.IR pattern . +With the +.I string\^ +matches the +.I pattern\^ +the +.B .sh.match +array variable will contain the match and sub-pattern matches. +.TP +\f2string\fP \f3=\(ap\fP \f2ere\fP +True if +.I string\^ +matches the pattern +.BI \(ap(E) ere\^ +where +.I ere\^ +is an extended regular expression. +.TP +\f2string1\fP \f3<\fP \f2string2\fP +True, if +.I string1\^ +comes before +.I string2\^ +based on ASCII value of their characters. +.TP +\f2string1\fP \f3>\fP \f2string2\fP +True, if +.I string1\^ +comes after +.I string2\^ +based on ASCII value of their characters. +.PP +The following obsolete arithmetic comparisons are also permitted: +.PD 0 +.TP +\f2exp1\fP \f3\-eq\fP \f2exp2\fP +True, if +.I exp1\^ +is equal to +.IR exp2 . +.TP +\f2exp1\fP \f3\-ne\fP \f2exp2\fP +True, if +.I exp1\^ +is not equal to +.IR exp2 . +.TP +\f2exp1\fP \f3\-lt\fP \f2exp2\fP +True, if +.I exp1\^ +is less than +.IR exp2 . +.TP +\f2exp1\fP \f3\-gt\fP \f2exp2\fP +True, if +.I exp1\^ +is greater than +.IR exp2 . +.TP +\f2exp1\fP \f3\-le\fP \f2exp2\fP +True, if +.I exp1\^ +is less than or equal to +.IR exp2 . +.TP +\f2exp1\fP \f3\-ge\fP \f2exp2\fP +True, if +.I exp1\^ +is greater than or equal to +.IR exp2 . +.PD +.PP +In each of the above expressions, if +.I file\^ +is of the form +\f3/dev/fd/\fP\f2n\fP, +where +.I n\^ +is an integer, +then the test is applied to the open file whose +descriptor number is +.IR n . +.PP +A compound expression can be constructed from these primitives by +using any of the following, listed in decreasing order of precedence. +.PD 0 +.TP +\f3(\fP\f2expression\fP\f3)\fP +True, if +.I expression\^ +is true. +Used to group expressions. +.TP +\f3!\fP \f2expression\fP +True if +.I expression\^ +is false. +.TP +\f2expression1\fP \f3&&\fP \f2expression2\fP +True, if +.I expression1\^ +and +.I expression2\^ +are both true. +.TP +\f2expression1\fP \f3\(bv\(bv\fP \f2expression2\fP +True, if either +.I expression1\^ +or +.I expression2\^ +is true. +.PD +.SS Input/Output. +Before a command is executed, its input and output +may be redirected using a special notation interpreted by the shell. +The following may appear anywhere in a simple-command +or may precede or follow a +.I command\^ +and are +.I not\^ +passed on to the invoked command. +Command substitution, parameter expansion, +and arithmetic substitution occur before +.I word\^ +or +.I digit\^ +is used except as noted below. +File name generation +occurs only if the shell is interactive and +the pattern matches a single file. +Field splitting is not performed. +.PP +In each of the following redirections, if +.I file\^ +is of the form +\f3/dev/sctp/\fP\f2host\fP\f3/\fP\f2port\fP, +\f3/dev/tcp/\fP\f2host\fP\f3/\fP\f2port\fP, +or +\f3/dev/udp/\fP\f2host\fP\f3/\fP\f2port\fP, +where +.I host\^ +is a hostname or host address, +and +.I port\^ +is a service given by name or an integer port number, +then the redirection attempts to make a +\f3tcp\fP, \f3sctp\fP or \f3udp\fP connection to the corresponding +socket. +.PP +No intervening space is allowed between the characters of redirection operators. +.TP 14 +.BI < word +Use file +.I word\^ +as standard input (file descriptor 0). +.TP +.BI > word +Use file +.I word\^ +as standard output (file descriptor 1). +If the file does not exist then it is created. +If the file exists, and the +.B noclobber +option is on, +this causes an error; +otherwise, it is truncated to zero length. +.TP +.BI >| word +Sames as +.BR > , +except that it overrides the +.B noclobber +option. +.TP +.BI >> word +Use file +.I word\^ +as standard output. +If the file exists, then output is appended to it (by first seeking to the end-of-file); +otherwise, the file is created. +.TP +.BI <> word +Open file +.I word\^ +for reading and writing +as standard input. +.TP +\f3<<\fP\*(OK\f3\-\fP\*(CK\f2word\fP +The shell input is read up to a line that is the same as +.IR word +after any quoting has been removed, +or to an end-of-file. +No parameter substitution, command substitution, arithmetic substitution or +file name generation is performed on +.IR word . +The resulting document, +called a +.IR here-document , +becomes +the standard input. +If any character of +.I word\^ +is quoted, then no interpretation +is placed upon the characters of the document; +otherwise, parameter expansion, command substitution, and arithmetic +substitution occur, +.B \enew-line +is ignored, +and +.B \e +must be used to quote the characters +.BR \e , +.BR $ , +.BR \*` . +If +.B \- +is appended to +.BR << , +then all leading tabs are stripped from +.I word\^ +and from the document. +If +.B # +is appended to +.BR << , +then leading spaces and tabs will be stripped off the first +line of the document and up to an equivalent indentation will +be stripped from the remaining lines and from +.IR word . +A tab stop is assumend to occur at every 8 columns for the +purposes of determining the indentation. +.TP +\f3<<<\fP\f2word\fP +A short form of here document in which \f2word\fP becomes the +contents of the here-document after any +parameter expansion, command substitution, and arithmetic +substitution occur. +.TP +.BI <& digit +The standard input is duplicated from file descriptor +.I digit +(see +.IR dup (2)). +Similarly for the standard output using +\f3>&\^\f2digit\fR. +.TP +.BI <& digit \- +The file descriptor given by +.I digit +is moved to standard input. +Similarly for the standard output using +\f3>&\^\f2digit\f3\-\fR. +.TP +.B <&\- +The standard input is closed. +Similarly for the standard output using +.BR >&\- . +.TP +.B <&p +The input from the co-process is moved to standard input. +.TP +.B >&p +The output to the co-process is moved to standard output. +.TP +.BI <# \^\^\^ (( expr )) +Evaluate arithmetic expression +.I expr\^ +and position file descriptor 0 +to the resulting value +bytes from the start of the file. +The variables +.B CUR +and +.B EOF +evaluate to the current offset and end-of-file offset +respectively when evaluating +.IR expr. +.TP +.BI ># \^\^\^ (( offset )) +The same as +.B <# +except applies to file descriptor 1. +.TP +.BI <# pattern +Seeks forward to the beginning of the next line containing +.IR pattern . +.TP +.BI <## pattern +The same as +.B <# +except that the portion of the file that is skipped is copied to +standard output. +.PP +If one of the above is preceded by a digit, +with no intervening space, then the +file descriptor number referred to is that specified +by the digit +(instead of the default 0 or 1). +If one of the above, other than +.BR >&\- +and the +.B ># +and +.B ># +forms, +is preceded by +.BI { varname } +with no intervening space, +then a file descriptor number > 10 +will be selected by +the shell and stored in the variable +.IR varname . +If +.B >&\- +or the any of the +.B ># +and +.B ># +forms +is preceded by +.BI { varname } +the value of +.I varname\^ +defines the file descriptor to close or position. +For example: +.PP +.RS +\fB\&.\|.\|. \|2>&1\fR +.RE +.PP +means file descriptor 2 is to be opened +for writing as a duplicate +of file descriptor 1 and +.PP +.RS +\fBexec {n}<file\fR +.RE +.PP +means open file named +.B file +for reading and store +the file descriptor number in variable +.BR n . +.PP +The order in which redirections are specified is significant. +The shell evaluates each redirection in terms of the +.RI ( "file descriptor" ", " file ) +association at the time of evaluation. +For example: +.PP +.RS +\fB\&.\|.\|. \|1>\f2fname\^\fP 2>&1\fR +.RE +.PP +first associates file descriptor 1 with file +.IR fname\^ . +It then associates file descriptor 2 with the file associated with file +descriptor 1 (i.e. +.IR fname\^ ). +If the order of redirections were reversed, file descriptor 2 would be associated +with the terminal (assuming file descriptor 1 had been) and then file descriptor +1 would be associated with file +.IR fname\^ . +.PP +If a command is followed by +.B & +and job control is not active, +then the default standard input +for the command +is the empty file +.BR /dev/null . +Otherwise, the environment for the execution of a command contains the +file descriptors of the invoking shell as modified by +input/output specifications. +.SS Environment. +The +.I environment\^ +(see +.IR environ (7)) +is a list of name-value pairs that is passed to +an executed program in the same way as a normal argument list. +The names must be +.I identifiers\^ +and the values are character strings. +The shell interacts with the environment in several ways. +On invocation, the shell scans the environment +and creates a +variable +for each name found, +giving it the corresponding value and attributes and marking it +.IR export . +Executed commands inherit the environment. +If the user modifies the values of these +variables +or creates new ones, +using the +.B export +or +.B typeset \-x +commands, they become part of the +environment. +The environment seen by any executed command is thus composed +of any name-value pairs originally inherited by the shell, +whose values may be modified by the current shell, +plus any additions +which must be noted in +.B export +or +.B typeset \-x +commands. +.PP +The environment for any +.I simple-command\^ +or function +may be augmented by prefixing it with one or more variable assignments. +A variable assignment argument is a word of the form +.IR identifier=value . +Thus: +.PP +.RS +\fB\s-1TERM\s+1=450 \|cmd \|args\fR and +.br +\fB(export \|\s-1TERM\s+1; \|\s-1TERM\s+1=450; \|cmd \|args)\fR +.RE +.PP +are equivalent (as far as the above execution of +.I cmd\^ +is concerned except for special built-in commands listed below \- +those that are +preceded with a dagger). +.PP +If the obsolete +.B \-k +option is set, +.I all\^ +variable assignment arguments are placed in the environment, +even if they occur after the command name. +The following +first prints +.B "a=b c" +and then +.BR c : +.PP +.RS +.nf +.ft B +echo \|a=b \|c +set \|\-k +echo \|a=b \|c +.ft R +.fi +.RE +This feature is intended for use with scripts written +for early versions of the shell and its use in new scripts +is strongly discouraged. +It is likely to disappear someday. +.SS Functions. +.PP +For historical reasons, there are two +ways to define functions, +the +.IB name (\^) +syntax and +the +.B function +.I name\^ +syntax, described in the +.I Commands +section above. +Shell functions are read in and stored internally. +Alias names are resolved when the function is read. +Functions are executed like commands with the arguments +passed as positional parameters. +(See +.I Execution +below.) +.PP +Functions defined by the +.B function +.I name +syntax and called by name execute in the same process as the caller and +share all files +and present working directory with the +caller. +Traps caught by the caller are reset to their default action +inside the function. +A trap condition that is not caught or ignored by the +function causes the function to terminate and the condition +to be passed on to the caller. +A trap on +.SM +.B EXIT +set inside a function +is executed +in the environment +of the caller +after the function completes. +Ordinarily, +variables are shared between the calling program +and the function. +However, +the +.B typeset +special built-in command used within a function +defines local variables whose scope includes +the current function. +They can be passed to functions that they call in the +variable assignment list the precedes the call or as arguments +passed as name references. +Errors within functions return control to the caller. +.PP +Functions defined with the +.IB name (\^) +syntax and functions defined with the +.B function +.I name +syntax that are invoked with the \f3\s+2.\s-2\fP +special built-in +are executed in the caller's +environment and share all variables +and traps with the caller. +Errors within these function executions cause the script that contains +them to abort. +.PP +The special built-in command +.B return +is used to return +from function calls. +.PP +Function names +can be listed with the +.B \-f +or +.B +f +option of the +.B typeset +special built-in command. +The text of functions, when available, will also +be listed with +.BR \-f . +Functions can be undefined with the +.B \-f +option of the +.B unset +special built-in command. +.PP +Ordinarily, functions are unset when the shell executes a shell script. +Functions that need to be defined across separate +invocations of the shell should +be placed in a directory and the +.B +.SM +FPATH +variable should contain the name of this directory. +They may also +be specified in the +.B +.SM +ENV +file. +.SS Discipline Functions. +Each variable can have zero or more discipline functions +associated with it. +The shell initially understands the discipline names \f3get\fP, +\f3set\fP, \f3append\fP, and \f3unset\fP but on most systems +others can be added at run time via the +C programming interface extension provided by the +.B builtin +built-in utility. +If the \f3get\fP discipline is defined for a variable, it is invoked +whenever the given variable is referenced. +If the variable \f3.sh.value\fP is assigned a value inside +the discipline function, the referenced variable will evaluate +to this value instead. +If the \f3set\fP discipline is defined for a variable, it is invoked +whenever the given variable is assigned a value. +If the \f3append\fP discipline is defined for a variable, it is invoked +whenever a value is appended to the given variable. +The variable \f3.sh.value\fP is given the value +of the variable before invoking the discipline, and +the variable will be assigned the value of \f3.sh.value\fP +after the discipline completes. +If \f3.sh.value\fP is unset inside the discipline, then +that value is unchanged. +If the \f3unset\fP discipline is defined for a variable, it is invoked +whenever the given variable is unset. +The variable will not be unset unless it is unset explicitly +from within this discipline function. +.PP +The variable +.B .sh.name +contains the name of the variable for which the discipline function is called, +.B .sh.subscript +is the subscript of the variable, and +.B .sh.value +will contain the value being assigned inside the +.B .set +discipline function. +For the \f3set\fP discipline, +changing +.B .sh.value +will change the value that gets assigned. +.SS Jobs. +.PP +If the +.B monitor +option of the +.B set +command is turned on, +an interactive shell associates a \fIjob\fR with each pipeline. +It keeps +a table of current jobs, printed by the +.B jobs +command, and assigns them small integer numbers. +When a job is started asynchronously with +.BR & , +the shell prints a line which looks +like: +.PP +.DT + [1] 1234 +.PP +indicating that the job which was started asynchronously was job number +1 and had one (top-level) process, whose process id was 1234. +.PP +This paragraph and the next require features that are +not in all versions of UNIX and may not apply. +If you are running a job and wish to do something else you may hit the key +\fB^Z\fR (control-Z) which sends a STOP signal to the current job. +The shell will then normally indicate that the job has been `Stopped', +and print another prompt. +You can then manipulate the state of this job, +putting it in the background with the +.B bg +command, or run some other +commands and then eventually bring the job back into the foreground with +the foreground command +.BR fg . +A \fB^Z\fR takes effect immediately and +is like an interrupt in that pending output and unread input are discarded +when it is typed. +.PP +A job being run in the background will stop if it tries to read +from the terminal. +Background jobs are normally allowed to produce output, +but this can be disabled by giving the command +.BR "stty tostop" . +If you set this +tty option, then background jobs will stop when they try to produce +output like they do when they try to read input. +.PP +There are several ways to refer to jobs in the shell. +A job can be referred to by the process id of any process of the job +or by one of the following: +.PD 0 +.TP +.BI % number +The job with the given number. +.TP +.BI % string +Any job whose command line begins with +.IR string . +.TP +.BI %? string +Any job whose command line contains +.IR string . +.TP +.BI %% +Current job. +.TP +.BI %+ +Equivalent to +.BR %% . +.TP +.BI %\- +Previous job. +.PD +.PP +The shell learns immediately whenever a process changes state. +It normally informs you whenever a job becomes blocked so that +no further progress is possible, but only just before it prints +a prompt. +This is done so that it does not otherwise disturb your work. +The +.B notify +option of the +.B set +command causes +the shell to print these job change messages +as soon as they occur. +.PP +When the +.B monitor +option is on, each background job that completes +triggers any trap set for +.BR CHLD . +.PP +When you try to leave the shell while jobs are running or stopped, you will +be warned that `You have stopped(running) jobs.' +You may use the +.B jobs +command to see what they are. +If you immediately try to +exit again, the shell will not warn you a second time, and the stopped +jobs will be terminated. +When a login shell receives a HUP signal, it sends +a HUP signal to each job that has not been disowned with the +.B disown +built-in command described below. +.SS Signals. +The \s-1INT\s+1 and \s-1QUIT\s+1 signals for an invoked +command are ignored if the command is followed by +.B & +and the +.B monitor +option is not active. +Otherwise, signals have the values +inherited by the shell from its parent +(but see also +the +.B trap +built-in command below). +.SS Execution. +Each time a command is read, the above substitutions +are carried out. +If the command name matches one +of the +.I "Special Built-in Commands\^" +listed below, +it is executed within the +current shell process. +Next, the command name is checked to see if +it matches a user defined function. +If it does, +the positional parameters are saved +and then reset to the arguments of the +.I function\^ +call. +A function is also executed in the +current shell process. +When the +.I function\^ +completes or issues a +.BR return , +the positional parameter list is restored. +For functions defined with the +.B function +.I name\^ +syntax, +any trap set on +.SM +.B EXIT +within the function is executed. +The exit value of a +.I function\^ +is the value of the last command executed. +If a command name is not a +.I "special built-in command\^" +or a user defined +.IR function , +but it is one of the built-in commands listed below, +it is executed in the current shell process. +.PP +The shell variable +.B +.SM PATH +defines the search path for +the directory containing the command. +Alternative directory names are separated by +a colon +.RB ( : ). +The default path is +.B /bin:/usr/bin: +(specifying +.BR /bin , +.BR /usr/bin , +and the current directory +in that order). +The current directory can be specified by +two or more adjacent colons, or by a colon +at the beginning or end of the path list. +If the command name contains a \f3/\fP, then the search path +is not used. +Otherwise, each directory in the path is +searched for an executable file +of the given name +that is not a directory. +If found, and if the shell +determines that there is a built-in version +of a command corresponding to a given pathname, +this built-in is invoked in the current process. +If found, and this directory is also contained in the value of the +.B +.SM FPATH +variable, +then this file is loaded into the current shell environment +as if it were the argument to the \fB.\fP command +except that only preset aliases are expanded, +and a function of the given name is executed +as described above. +If not found, and the file +.B .paths +is found, and the this file contains a line of the form +.BI FPATH= path +where +.I path\^ +names an +existing directory, and this directory contains +a file of the given name, +then this file is loaded into the current shell environment +as if it were the argument to the \fB.\fP special built-in command +and a function of the given name is executed. +Otherwise, if found, +a process is created and +an attempt is made to execute the command via +.IR exec (2). +.P +When an executable is found, the directory where it is found +in is searched for a file named +.BR .paths . +If this file is found and it contains a line of the form +.BI BUILTIN_LIB= value\^ +, then the library named by +.I value\^ +will be searched for as if it were an option argument to +.BR "builtin -f" , +and if it contains a built-in of the specified name +this will be executed instead of a command by this name. +Otherwise, if this file is found and it contains a line of the form +.IB name\^ = value\^ +in the first or second line, then the environment variable +.I name\^ +is modified by prepending the directory specified by +.I value\^ +to the directory list. +If +.I value\^ +is not an absolute directory, then it +specifies a directory relative to the directory that the +executable was found. +If the environment variable +.I name\^ +does not already exist it will be added to the environment +list for the specified command. +.P +If the file has execute permission but is not an +.B a.out +file, +it is assumed to be a file containing shell commands. +A separate shell is spawned to read it. +All non-exported variables are removed in this case. +If the shell command +file doesn't have read permission, +or if the +.I setuid +and/or +.I setgid +bits are set on the file, +then the shell executes an agent whose job it is to +set up the permissions and execute the shell with the +shell command file passed down as an open file. +A parenthesized command is executed in +a sub-shell without removing non-exported variables. +.SS Command Re-entry. +The text of the last +.B +.SM +HISTSIZE +(default 512) +commands entered from a terminal device +is saved in a +.I history +file. +The file +.B \s-1$HOME\s+1/.sh_history +is used if the +.B +.SM +HISTFILE +variable is not set +or if the file it names is not writable. +A shell can access the commands of +all +.I interactive +shells which use the same named +.SM +.BR HISTFILE . +The built-in command +.B hist\^ +is used to list or +edit a portion of this file. +The portion of the file to be edited or listed can be selected by +number or by giving the first character or +characters of the command. +A single command or range of commands can be specified. +If you do not specify an editor program as +an argument to +.B hist\^ +then the value of the variable +.SM +.B HISTEDIT +is used. +If +.SM +.B HISTEDIT +is unset, the obsolete variable +.SM +.B FCEDIT +is used. +If +.SM +.B FCEDIT +is not defined, then +.B /bin/ed +is used. +The edited command(s) is printed and re-executed upon +leaving the editor unless you quit without writing. +The +.B \-s +option +(and in obsolete versions, the editor name +.BR \-\^ ) +is used to skip the editing phase and +to re-execute the command. +In this case a substitution parameter of the form +\f2old\fP\f3=\fP\f2new\fP +can be used to modify the command before execution. +For example, with the preset alias +.BR r , +which is aliased to +.BR "\(fmhist \-s\(fm" , +typing +`\f3r bad=good c\fP' +will re-execute the most recent command which starts with the letter +.BR c , +replacing the first occurrence of the string +.B bad +with the string +.BR good . +.SS In-line Editing Options. +Normally, each command line entered from a terminal device is simply +typed followed by a \f3new-line\fP (`RETURN' or `LINE\ FEED'). +If either the +.BR emacs , +.BR gmacs , +or +.B vi +option is active, the user can edit the command line. +To be in either of these edit modes +.B set +the corresponding +option. +An editing option is automatically selected each time the +.SM +.B VISUAL +or +.SM +.B EDITOR +variable is assigned a value ending in either of these +option names. +.PP +The editing features require that the user's terminal +accept `RETURN' as carriage return without line feed +and that a space (`\ ') must overwrite the current character on +the screen. +.PP +Unless the +.B multiline +option is on, +the editing modes implement a concept where the user is looking through a +window at the current line. +The window width is the value of +.SM +.B COLUMNS +if it is defined, otherwise 80. +If the window width is too small to display the prompt and leave +at least 8 columns to enter input, the prompt is truncated from the +left. +If the line is longer than the window width minus two, a mark is +displayed at the end of the window to notify the user. +As the cursor moves and reaches the window boundaries the window will be +centered about the cursor. +The mark is a +.BR > " (<" , +.BR * ) +if the line extends on the +right (left, both) side(s) of the window. +.PP +The search commands in each edit mode provide access to the history file. +Only strings are matched, not patterns, although a leading +.B ^ +in the string restricts the match +to begin at the first character in the line. +.PP +Each of the edit modes has an operation to list the files +or commands that match a partially entered word. +When applied to the first word on the line, +or the first word after a +.BR ; , +.BR \(bv , +.BR & , +or +.BR ( , +and the word does not begin with +.B \(ap +or contain a +.BR / , +the list of aliases, functions, and executable commands +defined by the +.B +.SM PATH +variable that could match the partial word is displayed. +Otherwise, the list of files that match the given +word is displayed. +If the partially entered word does not contain any +file expansion characters, a +.B * +is appended before generating these lists. +After displaying the generated list, the input line +is redrawn. +These operations are called command name listing and file name listing, +respectively. +There are additional operations, referred to as command name +completion and file name completion, which compute the list +of matching commands or files, but instead of printing the list, +replace +the current word with a complete or partial match. +For file name completion, +if the match is unique, a +.B / +is appended if the file is a directory and a space is +appended if the file is not a directory. +Otherwise, the longest common prefix for all the matching +files replaces the word. +For command name completion, only the portion of the file names +after the last +.B / +are used to find the longest command prefix. +If only a single name matches this prefix, then the +word is replaced with the command name followed by a space. +When using a +.I tab\^ +for completion that does not yield a unique match, +a subsequent +.I tab\^ +will provide a numbered list of matching alternatives. +A specific selection can be made by entering the +selection number followed by a +.IR tab . +.SS Key Bindings. +The +.B +.SM KEYBD +trap can be used to intercept keys as they are typed +and change the characters that are actually seen by +the shell. +This trap is executed after each character +(or sequence of characters when the first character is ESC) +is entered while reading from a terminal. +The variable +.B .sh.edchar +contains the character or character sequence which +generated the trap. +Changing the value of +.B .sh.edchar +in the trap action causes the shell to behave as if the +new value were entered from the keyboard rather than +the original value. +.PP +The variable +.B .sh.edcol +is set to the input column number of the cursor at the time +of the input. +The variable +.B .sh.edmode +is set to +ESC +when in +.B vi +insert mode (see below) and is null otherwise. +By prepending +.B ${.sh.editmode} +to a value assigned to +.B .sh.edchar +it will cause the shell +to change to control mode if it is not already in this mode. +.PP +This trap is not invoked for characters entered as arguments to +editing directives, or while reading input for a character search. +.SS Emacs Editing Mode. +This mode is entered by enabling either the +.B emacs +or +.B gmacs +option. +The only difference between these two modes is the way +they handle +.BR ^T . +To edit, the user +moves the cursor to the point needing correction and +then inserts or deletes characters or words as needed. +All the editing commands are control characters or escape +sequences. +The notation for control characters is caret +.RB ( ^ ) +followed +by the character. +For example, +.B ^F +is the notation for control +.BR F . +This is entered by depressing `f' while holding down the +`CTRL' (control) key. +The `SHIFT' key is +.I not +depressed. +(The notation +.B ^? +indicates the DEL (delete) key.) +.PP +The notation for escape sequences is +.B M- +followed by a +character. +For example, +.B M-f +(pronounced Meta f) +is entered by depressing ESC +(ascii +.BR 033 ) +followed by `f'. +.RB ( M-F +would be the notation for ESC followed by `SHIFT' (capital) `F'.) +.PP +All edit commands +operate from any place on the line +(not just at the beginning). +Neither the `RETURN' nor the `LINE FEED' key is +entered after edit commands except when noted. +.PP +.PD 0 +.TP 10 +.BI ^F +Move cursor forward (right) one character. +.PP +.TP 10 +.BI M-[C +Move cursor forward (right) one character. +.PP +.TP 10 +.BI M-f +Move cursor forward one word. +(The +.B emacs +editor's idea of a word is a string of characters +consisting of only letters, digits and underscores.) +.PP +.TP 10 +.BI ^B +Move cursor backward (left) one character. +.PP +.TP 10 +.BI M-[D +Move cursor backward (left) one character. +.PP +.TP 10 +.BI M-b +Move cursor backward one word. +.PP +.TP 10 +.BI ^A +Move cursor to start of line. +.PP +.TP 10 +.BI M-[H +Move cursor to start of line. +.PP +.TP 10 +.BI ^E +Move cursor to end of line. +.PP +.TP 10 +.BI M-[Y +Move cursor to end of line. +.PP +.TP 10 +.BI ^] char +Move cursor forward to character +.I char +on current line. +.PP +.TP 10 +.BI M-^] char +Move cursor backward to character +.I char +on current line. +.PP +.TP 10 +.BI ^X^X +Interchange the cursor and mark. +.PP +.TP 10 +.I erase +(User defined erase character as defined +by the +.IR stty (1) +command, usually +.B ^H +or +.BR # .) +Delete previous character. +.PP +.TP 10 +.I lnext +(User defined literal next character as defined +by the +.IR stty (1) +command. +or +.B ^V +if not defined.) +Removes the next character's +editing features (if any). +.PP +.TP 10 +.BI ^D +Delete current character. +.PP +.TP 10 +.BI M-d +Delete current word. +.PP +.TP 10 +.BI M-^H +(Meta-backspace) Delete previous word. +.PP +.TP 10 +.BI M-h +Delete previous word. +.PP +.TP 10 +.BI M-^? +(Meta-DEL) Delete previous word (if your interrupt character is +.B ^? +(DEL, the default) then this command will not work). +.PP +.TP 10 +.BI ^T +Transpose current character with previous character +and advance the cursor +in +.I emacs +mode. +Transpose two previous characters in +.I gmacs +mode. +.PP +.TP 10 +.BI ^C +Capitalize current character. +.PP +.TP 10 +.BI M-c +Capitalize current word. +.PP +.TP 10 +.BI M-l +Change the current word to lower case. +.PP +.TP 10 +.BI ^K +Delete from the cursor to the end of the line. +If preceded by a numerical parameter whose value is less than the +current cursor position, then delete from given position +up to the cursor. +If preceded by a numerical parameter whose value is greater than the +current cursor position, then delete from cursor up to +given cursor position. +.PP +.TP 10 +.BI ^W +Kill from the cursor to the mark. +.PP +.TP 10 +.BI M-p +Push the region from the cursor to the mark on the stack. +.PP +.TP 10 +.I kill +(User defined kill character as defined +by the stty command, usually +.B ^G +or +.BR @ .) +Kill the entire current line. +If two +.I kill +characters are entered in succession, all +kill characters from then on cause a line feed +(useful when using paper terminals). +.PP +.TP 10 +.BI ^Y +Restore last item removed from line. (Yank item back to the line.) +.PP +.TP 10 +.BI ^L +Line feed and print current line. +.PP +.TP 10 +.BI M-^L +Clear the screen. +.PP +.TP 10 +.BI ^@ +(Null character) Set mark. +.PP +.TP 10 +.BI M- space +(Meta space) Set mark. +.PP +.TP 10 +.BI ^J +(New\ line) Execute the current line. +.PP +.TP 10 +.BI ^M +(Return) Execute the current line. +.PP +.TP 10 +.I eof +End-of-file character, +normally +.BR ^D , +is processed as an End-of-file only +if the current line is null. +.PP +.TP 10 +.BI ^P +Fetch previous command. +Each time +.B ^P +is entered +the previous command back in time is accessed. +Moves back one line when not on the first line of a multi-line command. +.PP +.TP 10 +.BI M-[A +Equivalent to +.BR ^P. +.PP +.TP 10 +.BI M-< +Fetch the least recent (oldest) history line. +.PP +.TP 10 +.BI M-> +Fetch the most recent (youngest) history line. +.PP +.TP 10 +.BI ^N +Fetch next command line. +Each time +.B ^N +is entered +the next command line forward in time is accessed. +.PP +.TP 10 +.BI M-[B +Equivalent to +.BR ^N. +.PP +.TP 10 +.BI ^R string +Reverse search history for a previous command line containing +.IR string . +If a parameter of zero is given, the search is forward. +.I String +is terminated by a `RETURN' or `NEW\ LINE'. +If string is preceded by a +.BR ^ , +the matched line must begin with +.IR string . +If +.I string +is omitted, +then the next command line containing the most recent +.I string +is accessed. +In this case a parameter of zero +reverses the direction of the search. +.PP +.TP 10 +.B ^O +Operate \- Execute the current line and fetch +the next line relative to current line from the +history file. +.PP +.TP 10 +.BI M- digits +(Escape) Define numeric parameter, the digits +are taken as a parameter to the next command. +The commands that accept a parameter are +.BR ^F , +.BR ^B , +.IR erase , +.BR ^C , +.BR ^D , +.BR ^K , +.BR ^R , +.BR ^P , +.BR ^N , +.BR ^] , +.BR M-. , +.BR M-^] , +.BR M-_ , +.BR M-= , +.BR M-b , +.BR M-c , +.BR M-d , +.BR M-f , +.BR M-h , +.B M-l +and +.BR M-^H . +.PP +.TP 10 +.BI M- letter +Soft-key \- Your alias list is searched for an +alias by the name +.BI _ letter +and if an alias of this name is defined, its +value will be inserted on the input queue. +The +.I letter +must not be one of the above meta-functions. +.PP +.TP 10 +.BI M-[ letter +Soft-key \- Your alias list is searched for an +alias by the name +.BI _\&_ letter +and if an alias of this name is defined, its +value will be inserted on the input queue. +The can be used to program functions keys on many terminals. +.PP +.TP 10 +.B M-. +The last word of the previous command is inserted +on the line. +If preceded by a numeric parameter, the value +of this parameter determines which word to insert rather than +the last word. +.PP +.TP 10 +.B M-_ +Same as +.BR M-. . +.PP +.TP 10 +.B M-* +Attempt file name generation on the current word. +An asterisk is appended if the word doesn't match any file +or contain any special +pattern characters. +.PP +.TP 10 +.B M-ESC +Command or file name completion as described above. +.PP +.TP 10 +.BI ^I " tab" +Attempts command or file name completion as described above. +If a partial completion occurs, repeating this will +behave as if +.B M-= +were entered. +If no match is found or entered after +.IR space\^ , +a +.I tab\^ +is inserted. +.PP +.TP 10 +.B M-= +If not preceded by a numeric parameter, +it generates the list of matching commands or +file names as described above. +Otherwise, the word under the cursor is replaced by +the item corresponding to the value of the numeric parameter +from the most recently generated command or file list. +If the cursor is not on a word, it is inserted instead. +.PP +.TP 10 +.BI ^U +Multiply parameter of next command by 4. +.PP +.TP 10 +.BI \e +Escape next character. +Editing characters, the user's erase, kill and +interrupt (normally +.BR ^? ) +characters +may be entered +in a command line or in a search string if preceded by a +.BR \e . +The +.B \e +removes the next character's +editing features (if any). +.PP +.TP 10 +.B M-^V +Display version of the shell. +.PP +.TP 10 +.B M-# +If the line does not begin with a +.BR # , +a +.B # +is inserted +at the beginning of the line +and after each new-line, +and the line is entered. +This causes a comment to be inserted in the history file. +If the line begins with a +.BR # , +the +.B # +is deleted and one +.B # +after each new-line is also deleted. +.PD +.SS Vi Editing Mode. +There are two typing modes. +Initially, when you enter a command you are in the +.I input\^ +mode. +To edit, the user enters +.I control\^ +mode by typing ESC +.RB ( 033 ) +and moves the cursor to the point needing correction and +then inserts or deletes characters or words as needed. +Most control commands accept an optional repeat +.I count +prior to the command. +.PP +When in +.B vi +mode on most systems, +canonical processing is initially enabled and the +command will be echoed again if the speed is 1200 baud or greater and it +contains any control characters or less than one second has elapsed +since the prompt was printed. +The ESC character terminates canonical processing for the remainder of the command +and the user can then modify the command line. +This scheme has the advantages of canonical processing with the type-ahead +echoing of raw mode. +.PP +If the option +.B viraw +is also set, the terminal will always have canonical processing +disabled. +This mode is implicit for systems that do not support two +alternate end of line delimiters, +and may be helpful for certain terminals. +.SS "\ \ \ \ \ Input Edit Commands" +.PP +.RS +By default the editor is in input mode. +.PD 0 +.TP 10 +.I erase +(User defined erase character as defined +by the stty command, usually +.B ^H +or +.BR # .) +Delete previous character. +.TP 10 +.BI ^W +Delete the previous blank separated word. +On some systems the \f3viraw\fP option +may be required for this to work. +.TP 10 +.I eof +As the first character of the line causes +the shell to terminate unless the \f3ignoreeof\fP +option is set. +Otherwise this character is ignored. +.TP 10 +.I lnext +(User defined literal next character as defined +by the +.IR stty (1) +or +.B ^V +if not defined.) +Removes the next character's +editing features (if any). +On some systems the \f3viraw\fP option +may be required for this to work. +.TP 10 +.BI \e +Escape the next +.I erase +or +.I kill +character. +.TP 10 +.BI ^I " tab" +Attempts command or file name completion as described above +and returns to input mode. +If a partial completion occurs, repeating this will +behave as if +.B = +were entered from control mode. +If no match is found or entered after +.IR space\^ , +a +.I tab\^ +is inserted. +.RE +.SS "\ \ \ \ \ Motion Edit Commands" +.RS +These commands will move the cursor. +.TP 10 +[\f2count\fP]\f3l\fP +Cursor forward (right) one character. +.TP 10 +[\f2count\fP]\f3[C\fP +Cursor forward (right) one character. +.TP 10 +[\f2count\fP]\f3w\fP +Cursor forward one alpha-numeric word. +.TP 10 +[\f2count\fP]\f3W\fP +Cursor to the beginning of the next word that follows a blank. +.TP 10 +[\f2count\fP]\f3e\fP +Cursor to end of word. +.TP 10 +[\f2count\fP]\f3E\fP +Cursor to end of the current blank delimited word. +.TP 10 +[\f2count\fP]\f3h\fP +Cursor backward (left) one character. +.TP 10 +[\f2count\fP]\f3[D\fP +Cursor backward (left) one character. +.TP 10 +[\f2count\fP]\f3b\fP +Cursor backward one word. +.TP 10 +[\f2count\fP]\f3B\fP +Cursor to preceding blank separated word. +.TP 10 +[\f2count\fP]\f3\(bv\fP +Cursor to column +.IR count . +.TP 10 +[\f2count\fP]\f3f\fP\f2c\fP +Find the next character \fIc\fP in the current line. +.TP 10 +[\f2count\fP]\f3F\fP\f2c\fP +Find the previous character \fIc\fP in the current line. +.TP 10 +[\f2count\fP]\f3t\fP\f2c\fP +Equivalent to +.B f +followed by +.BR h . +.TP 10 +[\f2count\fP]\f3T\fP\f2c\fP +Equivalent to +.B F +followed by +.BR l . +.TP 10 +[\f2count\fP]\f3;\fP +Repeats +.I count +times, +the last single character find command, +.BR f , +.BR F , +.BR t , +or +.BR T . +.TP 10 +[\f2count\fP]\f3,\fP +Reverses the last single character find command +.I count +times. +.TP 10 +.B 0 +Cursor to start of line. +.TP 10 +.B ^ +Cursor to start of line. +.TP 10 +.B [H +Cursor to first non-blank character in line. +.TP 10 +.B $ +Cursor to end of line. +.TP 10 +.B [Y +Cursor to end of line. +.TP 10 +.B % +Moves to balancing +.BR ( , +.BR ) , +.BR { , +.BR } , +.BR [ , +or +.BR ] . +If cursor is not on one of the above characters, +the remainder of the line is searched for the first +occurrence of one of the above characters first. +.RE +.SS "\ \ \ \ \ Search Edit Commands" +.RS +These commands access your command history. +.TP 10 +[\f2count\fP]\f3k\fP +Fetch previous command. +Each time +.B k +is entered +the previous command back in time is accessed. +.TP 10 +[\f2count\fP]\f3\-\fP +Equivalent to +.BR k . +.TP 10 +[\f2count\fP]\f3[A\fP +Equivalent to +.BR k . +.TP 10 +[\f2count\fP]\f3j\fP +Fetch next command. +Each time +.B j +is entered +the next command forward in time is accessed. +.TP 10 +[\f2count\fP]\f3+\fP +Equivalent to +.BR j . +.TP 10 +[\f2count\fP]\f3[B\fP +Equivalent to +.BR j . +.TP 10 +[\f2count\fP]\f3G\fP +The command number +.I count +is fetched. +The default is the least recent history command. +.TP 10 +.BI / string +Search backward through history for a previous command containing +.IR string . +.I String +is terminated by a `RETURN' or `NEW\ LINE'. +If string is preceded by a +.BR ^ , +the matched line must begin with +.IR string . +If \fIstring\fP is null, the previous string will be used. +.TP 10 +.BI ? string +Same as +.B / +except that search will be in the forward direction. +.TP 10 +.B n +Search for next match of the last pattern to +.B / +or +.B ? +commands. +.TP 10 +.B N +Search for next match of the last pattern to +.B / +or +.BR ? , +but in reverse direction. +.RE +.SS "\ \ \ \ \ Text Modification Edit Commands" +.RS +These commands will modify the line. +.TP 10 +.B a +Enter input mode and enter text after the current character. +.TP 10 +.B A +Append text to the end of the line. +Equivalent to +.BR $a . +.TP 10 +[\f2count\fP]\f3c\fP\f2motion\fP +.TP 10 +\f3c\fP[\f2count\fP]\f2motion\fP +Delete current character through the character that +.I motion +would move the cursor to and enter input mode. +If \fImotion\fP is +.BR c , +the entire line will be deleted and +input mode entered. +.TP 10 +.B C +Delete the current character through the end of line and enter input mode. +Equivalent to +.BR c$ . +.TP 10 +.B S +Equivalent to +.BR cc . +.TP 10 +[\f2count\fP]\f3s\fP +Replace characters under the cursor in input mode. +.TP 10 +.B D +Delete the current character through the end of line. +Equivalent to +.BR d$ . +.TP 10 +[\f2count\fP]\f3d\fP\f2motion\fP +.TP 10 +\f3d\fP[\f2count\fP]\f2motion\fP +Delete current character through the character that +.I motion +would move to. +If \fImotion\fP is +.B d , +the entire line will be deleted. +.TP 10 +.B i +Enter input mode and insert text before the current character. +.TP 10 +.B I +Insert text before the beginning of the line. +Equivalent to +.BR 0i . +.TP 10 +[\f2count\fP]\f3P\fP +Place the previous text modification before the cursor. +.TP 10 +[\f2count\fP]\f3p\fP +Place the previous text modification after the cursor. +.TP 10 +.B R +Enter input mode and +replace characters on the screen with characters you type overlay fashion. +.TP 10 +[\f2count\fP]\f3r\fP\f2c\fP +Replace the +.I count +character(s) starting at the current cursor position with +.IR c , +and advance the cursor. +.TP 10 +[\f2count\fP]\f3x\fP +Delete current character. +.TP 10 +[\f2count\fP]\f3X\fP +Delete preceding character. +.TP 10 +[\f2count\fP]\f3.\fP +Repeat the previous text modification command. +.TP 10 +[\f2count\fP]\f3\(ap\fP +Invert the case of the +.I count +character(s) starting at the current cursor position and advance the cursor. +.TP 10 +[\f2count\fP]\f3_\fP +Causes the +.I count\^ +word of the previous command to be appended and +input mode entered. +The last word is used +if +.I count\^ +is omitted. +.TP 10 +.B * +Causes an +.B * +to be appended to the current word and file name generation attempted. +If no match is found, +it rings the bell. +Otherwise, the word is replaced +by the matching pattern and input mode is entered. +.TP 10 +.B \e +Command or file name completion as described above. +.RE +.SS "\ \ \ \ \ Other Edit Commands" +.RS +Miscellaneous commands. +.TP 10 +[\f2count\fP]\f3y\fP\f2motion\fP +.TP 10 +\f3y\fP[\f2count\fP]\f2motion\fP +Yank current character through character that +.I motion +would move the cursor to and puts them into the delete buffer. +The text and cursor are unchanged. +.TP 10 +.B yy +Yanks the entire line. +.TP 10 +.B Y +Yanks from current position to end of line. +Equivalent to +.BR y$ . +.TP 10 +.B u +Undo the last text modifying command. +.TP 10 +.B U +Undo all the text modifying commands performed on the line. +.TP 10 +[\f2count\fP]\f3v\fP +Returns the command +.BI "hist \-e ${\s-1VISUAL\s+1:\-${\s-1EDITOR\s+1:\-vi}}" " count" +in the input buffer. +If +.I count\^ +is omitted, then the current line is used. +.TP 10 +.BI ^L +Line feed and print current line. +Has effect only in control mode. +.TP 10 +.BI ^J +(New\ line) Execute the current line, regardless of mode. +.TP 10 +.BI ^M +(Return) Execute the current line, regardless of mode. +.TP 10 +.B # +If the first character of the command is a +.BR # , +then this command deletes this +.B # +and each +.B # +that follows a newline. +Otherwise, +sends the line after +inserting a +.B # +in front of each line in the command. +Useful for causing the current line to be +inserted in the history as a comment and +uncommenting previously commented commands +in the history file. +.TP 10 +[\f2count\fP]\f3=\fP +If \f2count\fP is not specified, +it generates the list of matching commands or +file names as described above. +Otherwise, the word under the the cursor is replaced by the +\f2count\fP item from the most recently generated command or file list. +If the cursor is not on a word, it is inserted instead. +.TP 10 +.BI @ letter +Your alias list is searched for an +alias by the name +.BI _ letter +and if an alias of this name is defined, its +value will be inserted on the input queue for processing. +.TP 10 +.BI ^V +Display version of the shell. +.RE +.PD +.SS Built-in Commands. +The following simple-commands are executed in the shell process. +Input/Output redirection is permitted. +Unless otherwise indicated, the output is written on file descriptor 1 +and the exit status, when there is no syntax error, is zero. +Except for +.BR : , +.BR true , +.BR false , +.BR echo , +.BR newgrp , +and +.BR login , +all built-in commands accept +.B \-\- +to indicate end of options. +They also interpret the option +.B \-\-man +as a request to display the man page onto +standard error and +.B \-? +as a help request which prints a +.I usage\^ +message +on standard error. +Commands that are preceded by one or two \(dg symbols +are special built-in commands and +are treated specially in the following ways: +.PD 0 +.TP +1. +Variable assignment lists preceding the command +remain in effect when the command completes. +.TP +2. +I/O redirections are processed after variable assignments. +.TP +3. +Errors +cause a script +that contains them to abort. +.TP +4. +They are not valid function names. +.TP +5. +Words +following a command preceded by \(dg\(dg +that are in the format of a variable assignment +are expanded with the same rules as a variable assignment. +This means that +tilde substitution is performed after the +.B = +sign and field splitting and file name generation are not +performed. +.PD +.TP +\(dg \f3:\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +The command only expands parameters. +.br +.ne 2 +.TP +\(dg \f3\|. \f2name\^\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +If +.I name\^ +is a function defined with the +.B function +.I name\^ +reserved word syntax, +the function is executed in the current environment +(as if it had been defined with the +.IB name () +syntax.) +Otherwise if +.I name\^ +refers to a file, the +file is read in its entirety and the commands are +executed in the current shell environment. +The search path +specified by +.B +.SM PATH +is used to find the directory containing the file. +If any arguments +.I arg\^ +are given, +they become the positional parameters while processing +the +.B . +command and the original positional parameters are restored upon completion. +Otherwise the positional parameters are unchanged. +The exit status is the exit status of the last command executed. +.TP +\(dg\(dg \f3alias\fP \*(OK \f3\-ptx\fP \*(CK \*(OK \f2name\fP\*(OK \f3=\fP\f2value\^\fP \*(CK \*(CK .\|.\|. +.B alias\^ +with no arguments prints the list of aliases +in the form +.I name=value\^ +on standard output. +The +.B \-p +option +causes the word +.B alias +to be inserted before each one. +When one or more arguments are given, +an +.I alias\^ +is defined +for each +.I name\^ +whose +.I value\^ +is given. +A trailing space in +.I value\^ +causes the next word to be checked for +alias substitution. +The obsolete +.B \-t +option is used to set and list tracked aliases. +The value of a tracked alias is the full pathname +corresponding to the given +.IR name . +The value becomes undefined when the value of +.SM +.B PATH +is reset but the alias remains tracked. +Without the +.B \-t +option, +for each +.I name\^ +in the argument list +for which no +.I value\^ +is given, the name +and value of the alias is printed. +The obsolete +.B \-x +option has no effect. +The exit status is non-zero if a +.I name\^ +is given, but no value, and no alias has been defined for the +.IR name\^ . +.TP +\f3bg\fP \*(OK \f2job\^\fP.\|.\|. \*(CK +This command is only on systems that support job control. +Puts each specified +.I job\^ +into the background. +The current job is put in the background +if +.I job\^ +is not specified. +See +.I Jobs +for a description of the format of +.IR job . +.TP +\(dg \f3break\fP \*(OK \f2n\^\fP \*(CK +Exit from the enclosing +.BR for\^ , +.BR while\^ , +.BR until\^ , +or +.B select\^ +loop, if any. +If +.I n\^ +is specified, then break +.I n\^ +levels. +.TP +\f3builtin\fP \*(OK \f3\-ds\fP \*(CK \*(OK \f3\-f\fP \f2file\^\fP \*(CK \*(OK \f2name\^\fP .\|.\|. \*(CK +If +.I name\^ +is not specified, +and no +.B \-f +option is specified, +the built-ins are printed on standard output. +The +.B \-s +option prints only the special built-ins. +Otherwise, each +.I name\^ +represents the pathname whose basename is the name of the built-in. +The entry point function name is determined by prepending +.B b_ +to the built-in name. +The ISO C/C++ prototype is +\f3b_\fP\f2mycommand\fP\f3(int\fP \f2argc\fP, \f3char *\fP\f2argv\fP\f3[]\fP, \f3void *\fP\f2context\fP\f3)\fP +for the builtin command +.I mycommand\^ +where +.I argv\^ +is array an of +.I argc\^ +elements and context is an optional pointer to a +.B Shell_t +structure as described in +.BR <ast/shell.h> . +.sp .5 +Special built-ins cannot be bound to a pathname or deleted. +The +.B \-d +option deletes each of the given built-ins. +On systems that support dynamic loading, the +.B \-f +option names a shared library containing the code for built-ins. +The shared library prefix and/or suffix, which depend on the system, +can be omitted. +Once a library is loaded, its symbols become available +for subsequent invocations of +.BR builtin . +Multiple libraries can be specified with separate invocations +of the +.B builtin +command. +Libraries are searched in the reverse order in which they are specified. +When a library is loaded, it looks for a function in the library +whose name is +.B lib_init() +and invokes this function with an argument of +.BR 0 . +.TP +.PD 0 +\f3cd\fP \*(OK \f3\-LP\fP \*(CK \*(OK \f2arg\^\fP \*(CK +.TP +\f3cd\fP \*(OK \f3\-LP\fP \*(CK \f2old\^\fP \f2new\^\fP +.PD +This command can be in either of two forms. +In the first form it +changes the current directory to +.IR arg . +If +.I arg\^ +is +.B \- +the directory is changed to the previous +directory. +The shell +variable +.B +.SM HOME +is the default +.IR arg . +The variable +.SM +.B PWD +is set to the current directory. +The shell variable +.B +.SM CDPATH +defines the search path for +the directory containing +.IR arg . +Alternative directory names are separated by +a colon +.RB ( : ). +The default path is +.B <null> +(specifying the current directory). +Note that the current directory is specified by a null path name, +which can appear immediately after the equal sign +or between the colon delimiters anywhere else in the path list. +If +.I arg +begins with a \f3/\fP then the search path +is not used. +Otherwise, each directory in the path is +searched for +.IR arg . +.sp .5 +The second form of +.B cd +substitutes the string +.I new +for the string +.I old +in the current directory name, +.SM +.BR PWD , +and tries to change to this new directory. +.sp .5 +By default, symbolic link names are treated literally when +finding the directory name. +This is equivalent to the +.B \-L +option. +The +.B \-P +option causes +symbolic links to be resolved when determining the directory. +The last instance of +.B \-L +or +.B \-P +on the command line +determines which method is used. +.sp .5 +The +.B cd\^ +command may not be executed by +.if \nZ=0 .B rsh\^. +.if \nZ=1 .B rksh\^. +.if \nZ=1 .B rksh93\^. +.TP +\f3command\fP \*(OK \f3\-pvxV\fP \*(CK \f2name\^\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +Without the +.B \-v +or +.B \-V +options, +.B command +executes +.I name\^ +with the arguments given by +.IR arg . +The +.B \-p +option causes +a default path to be searched +rather than the one defined by the value of +.SM +.BR PATH . +Functions will not be searched for when finding +.IR name . +In addition, if +.I name\^ +refers to a special built-in, +none of the special properties associated with the leading +daggers will be honored. +(For example, the predefined alias +.B "redirect=\(fmcommand exec\(fm" +prevents a script from terminating when an invalid +redirection is given.) +With the +.B \-x +option, +if command execution would result in a failure because +there are too many arguments, errno +.SM +.BR E2BIG , +the shell will invoke command +.I name\^ +multiple times with a subset of the arguments on each invocation. +Arguments that occur prior to the first word that +expands to multiple arguments and after the last word +that expands to multiple arguments will be passed on each invocation. +The exit status will be the maximum invocation exit status. +With the +.B \-v +option, +.B command +is equivalent to the built-in +.B whence +command described below. +The +.B \-V +option causes +.B command +to act like +.BR "whence \-v" . +.TP +\(dg \f3continue\fP \*(OK \f2n\^\fP \*(CK +Resume the next iteration of the enclosing +.BR for\^ , +.BR while\^ , +.BR until\^ , +or +.B select\^ +loop. +If +.I n\^ +is specified, then resume at the +.IR n -th +enclosing loop. +.TP +\f3disown\fP \*(OK \f2job\^\fP.\|.\|. \*(CK +Causes the shell not to send a HUP signal to +each given +.IR job , +or all active jobs if +.I job +is omitted, +when a login shell terminates. +.TP +\f3echo\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +When the first +.I arg\^ +does not begin with a \-, and +none of the arguments contain a \e, +then +.B echo +prints each of its arguments separated by a space +and terminated by a new-line. +Otherwise, the behavior of +.B echo +is system dependent +and +.B print +or +.B printf +described below should be used. +See +.IR echo (1) +for usage and description. +.TP +\(dg \f3eval\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +The arguments are read as input +to the shell +and the resulting command(s) executed. +.TP +\(dg \f3exec\fP \*(OK \f3\-c\fP \*(CK \*(OK \f3\-a\fP \f2name\^\fP \*(CK \*(OK \f2arg\^\fP .\|.\|. \*(CK +If +.I arg\^ +is given, +the command specified by +the arguments is executed in place of this shell +without creating a new process. +The +.B \-c +option causes the environment to be cleared before applying +variable assignments associated with the +.B exec +invocation. +The +.B \-a +option +causes +.I name\^ +rather than the first +.IR arg , +to become +.B argv[0] +for the new process. +Input/output arguments may appear and +affect the current process. +If +.I arg\^ +is not given, +the effect of this command is to +modify file descriptors +as prescribed by the input/output redirection list. +In this case, +any file descriptor numbers greater than 2 that are +opened with this mechanism are closed when invoking +another program. +.TP +\(dg \f3exit\fP \*(OK \f2n\^\fP \*(CK +Causes the shell to exit +with the exit status specified by +.IR n . +The value will be the least significant 8 bits of the specified status. +If +.I n\^ +is omitted, then the exit status is that of the last command executed. +An end-of-file will also cause the shell to exit +except for a +shell which has the +.B ignoreeof +option (see +.B set +below) turned on. +.TP +\(dg\(dg \f3export\fP \*(OK \f3\-p\fP \*(CK \*(OK \f2name\^\fP\*(OK\f3=\fP\f2value\^\fP\*(CK \*(CK .\|.\|. +If +.I name\^ +is not given, +the names and values of each variable with +the export attribute are printed with the values +quoted in a manner that allows them to be re-input. +The +.B \-p +option +causes the word +.B export +to be inserted before each one. +Otherwise, the given +.IR name s +are marked for automatic +export to the +.I environment\^ +of subsequently-executed commands. +.TP +\f3false\fP +Does nothing, and exits 1. Used with +.B until +for infinite loops. +.TP +\f3fg\fP \*(OK \f2job\^\fP.\|.\|. \*(CK +This command is only on systems that support job control. +Each +.I job\^ +specified is brought to the foreground and waited for in +the specified order. +Otherwise, the current job is +brought into the foreground. +See +.I Jobs +for a description of the format of +.IR job . +.TP +\f3getconf\fP \*(OK \f2name\^\fP \*(OK \f2pathname\^\fP \*(CK \*(CK +Prints the current value of the configuration parameter given by +.IR name . +The configuration parameters are defined by the IEEE POSIX 1003.1 +and IEEE POSIX 1003.2 standards. +(See +.IR pathconf (2) +and +.IR sysconf (2).) +The +.I pathname +argument is required for parameters whose value depends on +the location in the file system. +If no arguments are given, +.B getconf +prints the names and values of the current configuration +parameters. +The pathname +.B / +is used for each of the parameters that requires +.IR pathname . +.TP +\f3getopts\fP \*(OK \f3\ -a\fP \f2name\^\fP \*(CK \f2optstring vname\^\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +Checks +.I arg +for legal options. +If +.I arg +is omitted, +the positional parameters are used. +An option argument begins with a +.B + +or a +.BR \- . +An option not beginning with +.B + +or +.B \- +or the argument +.B \-\|\- +ends the options. +Options beginning with +.B + +are only recognized when +.I optstring\^ +begins with a +.BR + . +.I optstring\^ +contains the letters that +.B getopts +recognizes. +If a letter is followed by a +.BR : , +that option is expected to have an argument. +The options can be separated from the argument by blanks. +The option +.B \-? +causes +.B getopts +to generate a usage message on standard error. +The +.B \-a +argument can be used to specify the name to use for the +usage message, which defaults to +.BR $0 . +.sp .5 +.B +getopts +places the next option letter it finds inside variable +.I vname\^ +each time it is invoked. +The option letter will be prepended with a +.B + +when +.I arg +begins with a +.BR + . +The index of the next +.I arg +is stored in +.SM +.BR OPTIND . +The option argument, +if any, +gets stored in +.SM +.BR OPTARG . +.sp .5 +A leading +.B : +in +.I optstring +causes +.B getopts +to store the letter of an invalid +option in +.SM +.BR OPTARG , +and to set +.I vname +to +.B ? +for an unknown option and to +.B : +when a required option argument is missing. +Otherwise, +.B getopts +prints an error message. +The exit status is non-zero when there are no more options. +.sp .5 +There is no way to specify any of the options +.BR : , +.BR + , +.BR \- , +.BR ? , +.BR [ , +and +.BR ] . +The option +.B # +can only be specified as the first option. +.TP +.PD 0 +\f3hist\fP \*(OK \f3\-e\fP \f2ename\^\fP \ \*(CK \*(OK \f3\-nlr\^\fP \*(CK \*(OK \f2first\^\fP \*(OK \f2last\^\fP \*(CK \*(CK +.TP +\f3hist \-s \fP \*(OK \f2old\fP\f3\=\fP\f2new\^\fP \*(CK \*(OK \f2command\^\fP \*(CK +.PD +In the first form, +a range of commands from +.I first\^ +to +.I last\^ +is selected from the last +.SM +.B HISTSIZE +commands that were typed at the terminal. +The arguments +.I first\^ +and +.I last\^ +may be specified as a number or as a string. +A string is used to locate the most recent command starting with +the given string. +A negative number is used as an offset to the current command number. +If the +.B \-l +option +is selected, +the commands are listed on standard output. +Otherwise, the editor program +.I ename\^ +is invoked on a file containing these +keyboard commands. +If +.I ename\^ +is not supplied, then the value of the variable +.SM +.B HISTEDIT +is used. +If +.SM +.B HISTEDIT +is not set, then +.SM +.B FCEDIT +(default +.BR /bin/ed\^ ) +is used as the editor. +When editing is complete, the edited command(s) +is executed if the changes have been saved. +If +.I last\^ +is not specified, +then it will be set to +.IR first . +If +.I first\^ +is not specified, +the default is the previous command +for editing and \-16 for listing. +The option +.B \-r +reverses the order of the commands and +the option +.B \-n +suppresses command numbers when listing. +In the second form, +.I command\^ +is interpreted as +.I first\^ +described above +and defaults to the last command executed. +The resulting command is executed +after the optional substitution +\f2old\^\fP\f3=\fP\f2new\^\fP +is performed. +.TP +\f3jobs\fP \*(OK \f3\-lnp\^\fP \*(CK \*(OK \f2job\^\fP \.\|.\|. \*(CK +Lists information about each given job; or all active jobs if +.I job +is omitted. +The +.B \-l +option lists process ids in addition to the normal information. +The +.B \-n +option only displays jobs that have stopped or exited since last +notified. +The +.B \-p +option causes only the process group to be listed. +See +.I Jobs +for a description of the format of +.IR job . +.TP +.PD 0 +\f3kill\fP \*(OK \f3\-s\fP \f2signame\^\fP \*(CK \f2job\^\fP .\|.\|. +.TP +.PD 0 +\f3kill\fP \*(OK \f3\-n\fP \f2signum\^\fP \*(CK \f2job\^\fP .\|.\|. +.TP +\f3kill\fP \f3\-l\fP \*(OK \f2sig\^\fP .\|.\|. \*(CK +.PD +Sends either the TERM (terminate) signal or the +specified signal to the specified jobs or processes. +Signals are either given by number with the +.B \-n +option or by name with the +.B \-s +option +(as given in +.BR <signal.h> , +stripped of the prefix ``SIG'' with +the exception that SIGCLD is named CHLD). +For backward compatibility, the +.B n +and +.B s +can be omitted and the number or name placed immediately +after the +.BR \- . +If the signal being sent is TERM (terminate) or HUP (hangup), +then the job or process will be sent a CONT (continue) signal +if it is stopped. +The argument +.I job\^ +can be the process id of a process that is not a member of one of the +active jobs. +See +.I Jobs +for a description of the format of +.IR job . +In the third form, +.BR "kill \-l" , +if +.I sig\^ +is not specified, +the signal names are listed. +Otherwise, for each +.I sig\^ +that is a name, the corresponding signal number is listed. +For each +.I sig\^ +that is a number, the signal name corresponding to the +least significant 8 bits of +.I sig\^ +is listed. +.TP +\f3let\fP \f2arg\^\fP .\|.\|. +Each +.I arg +is a separate +.I "arithmetic expression" +to be evaluated. +See +.I "Arithmetic Evaluation" +above, for a description of arithmetic expression evaluation. +.sp .5 +The exit status is +0 if the value of the last expression +is non-zero, and 1 otherwise. +.TP +\(dg \f3newgrp\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +Equivalent to +.BI "exec /bin/newgrp" " arg\^" +\&.\|.\|.\^. +.TP +\f3print\fP \*(OK \f3\-Renprs\^\fP \*(CK \*(OK \f3\-u\fP \f2unit\^\fP\*(CK \*(OK \f3\-f\fP \f2format\^\fP \*(CK \*(OK \f2arg\^\fP .\|.\|. \*(CK +With no options or with option +.B \- +or +.BR \-\|\- , +each +.I arg +is printed +on standard output. +The +.B \-f +option causes the arguments to be printed as +described by +.BR printf . +In this case, any +.BR e , +.BR n , +.BR r , +.B R +options are ignored. +Otherwise, +unless the +.B \-R +or +.BR \-r , +are specified, the following +escape conventions will be applied: +.RS +.PD 0 +.TP +.B \ea +The alert character (ascii +.BR 07 ). +.TP +.B \eb +The backspace character (ascii +.BR 010 ). +.TP +.B \ec +Causes +.B print +to end without processing more arguments and +not adding a new-line. +.TP +.B \ef +The formfeed character (ascii +.BR 014 ). +.TP +.B \en +The new-line character (ascii +.BR 012 ). +.TP +.B \er +The carriage return character (ascii +.BR 015 ). +.TP +.B \et +The tab character (ascii +.BR 011 ). +.TP +.B \ev +The vertical tab character (ascii +.BR 013 ). +.TP +.B \eE +The escape character (ascii +.BR 033 ). +.TP +.B \e\e +The backslash character \e. +.TP +.BI \e0 x +The character defined by the 1, 2, or 3-digit octal +string given by \fIx\fP. +.PD +.PP +The +.B \-R +option will print all subsequent arguments and options +other than +.BR \-n . +The +.B \-e +causes the above escape conventions to be applied +This is the default behavior. +It reverses the effect of an earlier +.BR \-r . +The +.B \-p +option causes the +arguments to be written onto the pipe +of the process spawned with +.B \(bv& +instead of standard output. +The +.B \-s +option causes the +arguments to be written onto the history file +instead of standard output. +The +.B \-u +option can be used to specify a one digit +file descriptor unit number +.I unit\^ +on which the +output will be placed. +The default is 1. +If the option +.B \-n +is used, no +.B new-line\^ +is added to the output. +.RE +.TP +\f3printf\fP \f2format\^\fP \*(OK \f2arg\^\fP .\|.\|. \*(CK +The arguments +.I arg\^ +are printed on standard output +in accordance with the ANSI-C +formatting rules associated with the format string +.IR format . +If the number of arguments exceeds the number of +format specifications, the +.B format\^ +string is reused to format remaining arguments. +The following extensions can also be used: +.BL +.LI +A +.B %b +format can be used instead of +.B %s +to cause escape sequences in the corresponding +.I arg\^ +to be expanded as described in +.BR print. +.LI +A +.B %B +option causes each of the arguments to be treated +as variable names and the binary value of variable +will be printed. +This is most useful for variables whose attribute +is +.BR \-b . +.LI +A +.B %H +format can be used instead of +.B %s +to cause characters in +.I arg\^ +that are special in HTML and XML +to be output to be output as their entity name. +.LI +A +.B %P +format can be used instead of +.B %s +to cause +.I arg\^ +to be interpreted as an extended regular +expression and be printed as a shell pattern. +.LI +A +.B %R +format can be used instead of +.B %s +to cause +.I arg\^ +interpreted as a shell pattern +and to be printed as an extended regular expression. +.LI +A +.B %q +format can be used instead of +.B %s +to cause the resulting string to be quoted in a manner than can +be reinput to the shell. +.LI +A +.BI %( date-format )T +format can be use to treat an argument as a date/time string +and to format the date/time according to the +.I date-format\^ +as defined for the +.BR date (1) +command. +.LI +A +.B %Z +format will output a byte whose value is 0. +.LI +The precision field of the +.B %d +format can be followed by a +.B . +and the output base. +In this case, the +.B # +flag character caues +.IB base # +to be prepended. +.LI +The +.B # +flag when used with the +.B d +specifier without an output base, +causes the output to be displayed in thousands units with one of the suffixes +.B "k M G T P E" +to indicate the unit. +.LI +The +.B # +flag when used with the +.B i +specifier causes the output to be displayed in 1024 with one of the suffixes +.B "Ki Mi Gi Ti Pi Ei" +to indicate the unit. +.LI +The +.B = +flag has been added to center the output within the specified field width. +.LE +.TP +\f3pwd\fP \*(OK \f3\-LP\fP \*(CK +Outputs the value of the current working +directory. +The +.B \-L +option is the default; it prints the logical name of the current directory. +If the +.B \-P +option is given, +all symbolic links are resolved from the name. +The last instance of +.B \-L +or +.B \-P +on the command line +determines which method is used. +.TP +\f3read\fP \*(OK \f3\-Aprs\^\fP \*(CK \*(OK \f3\-d\fP \f2delim\^\fP\*(CK \*(OK \f3\-n\fP \f2n\^\fP\*(CK \*(OK \*(OK \f3\-N\fP \f2n\^\fP\*(CK \*(OK \*(OK \f3\-t\fP \f2timeout\^\fP\*(CK \*(OK \f3\-u\fP \f2unit\^\fP\*(CK \*(OK \f2vname\f3?\f2prompt\^\f1 \*(CK \*(OK \f2vname\^\fP .\|.\|. \*(CK +The shell input mechanism. +One line is read and +is broken up into fields using the characters in +.B +.SM IFS +as separators. +The escape character, +.BR \e , +is used to remove any special meaning for the next +character and for line continuation. +The +.B \-d +option +causes the read to continue to the first character of +.I delim\^ +rather than new-line. +The +.B \-n +option causes at most +.I n\^ +bytes to read rather a full line +but will return when reading from a slow device +as soon as any characters have been read. +The +.B \-N +option causes exactly +.I n\^ +to be read unless an end-of-file has been encountered or +the read times out because of the +.B \-t +option. +In raw mode, +.B \-r, +the +.B \e +character is not treated specially. +The first +field is assigned to the first +.IR vname , +the second field +to the second +.IR vname , +etc., with leftover fields assigned to the last +.IR vname . +When +.IR vname +has the binary attribute and +.B \-n +or +.B \-N +is specified, the bytes that are read are stored directly +into the variable. +If the +.B \-v +is specified, then the value of the first +.I vname\^ +will be used as a default value when reading from a terminal device. +The +.B \-A +option causes the variable +.I vname\^ +to be unset and each field that is read to be stored in +successive elements of the indexed array +.IR vname. +The +.B \-p +option causes the input line +to be taken from the input pipe +of a process spawned by the shell +using +.BR \(bv& . +If the +.B \-s +option is present, +the input will be saved as a command in the history file. +The option +.B \-u +can be used to specify a one digit file +descriptor unit +.I unit\^ +to read from. +The file descriptor can be opened with the +.B exec\^ +special built-in command. +The default value of unit +.I n\^ +is 0. +The option +.B \-t +is used to specify a timeout in +seconds when reading from a terminal or pipe. +If +.I vname\^ +is omitted, then +.SM +.B REPLY +is used as the default +.IR vname . +An end-of-file with the +.B \-p +option causes cleanup for this process +so that another can be spawned. +If the first argument contains a +.BR ? , +the remainder of this word is used as a +.I prompt\^ +on standard error +when the shell is interactive. +The exit status is 0 unless an end-of-file is encountered +or +.B read +has timed out. +.TP +\(dg\(dg \f3readonly\fP \*(OK \f3\-p\fP \*(CK \*(OK \f2vname\fP\*(OK\f3=\fP\f2value\^\fP\*(CK \*(CK .\|.\|. +If +.I vname\^ +is not given, +the names and values of each variable with +the readonly attribute is printed with the values +quoted in a manner that allows them to be re-inputted. +The +.B \-p +option +causes the word +.B readonly +to be inserted before each one. +Otherwise, the given +.IR vname s +are marked +readonly and these +names cannot be changed +by subsequent assignment. +.TP +\(dg \f3return\fP \*(OK \f2n\^\fP \*(CK +Causes a shell +.I function +or +\f3\|.\fP +script to return +to the invoking script +with the exit status specified by +.IR n . +The value will be the least significant 8 bits of the specified status. +If +.I n\^ +is omitted, then the return status is that of the last command executed. +If +.B return +is invoked while not in a +.I function +or a +\f3\|.\fP +script, +then it behaves the same as +.BR exit . +.TP +\(dg \f3set\fP \*(OK \f3\(+-CGabefhkmnoprstuvx\fP \*(CK \*(OK \f3\(+-o\fP \*(OK \f2option\^\fP \*(CK \*(CK .\|.\|. \*(OK \f3\(+-A\fP \f2vname\^\fP \*(CK \*(OK \f2arg\^\fP .\|.\|. \*(CK +The options for this command have meaning as follows: +.RS +.PD 0 +.TP 8 +.B \-A +Array assignment. +Unset the variable +.I vname +and assign values sequentially from the +.I arg\^ +list. +If +.B +A +is used, the variable +.I vname +is not unset first. +.TP 8 +.B \-B +Enable brace pattern field generation. +This is the default behavior. +.TP 8 +.B \-C +Prevents redirection +.B > +from truncating existing files. +Files that are created are opened with the O_EXCL mode. +Requires +.B >\(bv +to truncate a file when turned on. +.TP 8 +.B \-G +Causes the pattern +.B \(**\(** +by itself to match files and zero or more directories and sub-directories +when used for file name generation. +If followed by a +.B / +only directories and sub-directories are matched. +.TP 8 +.B \-a +All subsequent variables that are defined are automatically exported. +.TP 8 +.B \-b +Prints job completion messages as soon as a background job changes +state rather than waiting for the next prompt. +.TP 8 +.B \-e +If a command has a non-zero exit status, +execute the +.SM +.B ERR +trap, if set, +and exit. +This mode is disabled while reading profiles. +.TP 8 +.B \-f +Disables file name generation. +.TP 8 +.B \-h +Each command +becomes a tracked alias when first encountered. +.TP 8 +.B \-k +(Obsolete). All variable assignment arguments are placed in the environment for a command, +not just those that precede the command name. +.TP 8 +.B \-m +Background jobs will run in a separate process group +and a line will print upon completion. +The exit status of background jobs is reported in a completion message. +On systems with job control, +this option is turned on automatically for +interactive shells. +.TP 8 +.B \-n +Read commands and check them for syntax errors, but do not execute them. +Ignored for interactive shells. +.TP 8 +.B \-o +The following argument can be one of the following option names: +.RS +.TP 8 +.B allexport +Same as +.BR \-a . +.TP 8 +.B errexit +Same as +.BR \-e . +.TP 8 +.B bgnice +All background jobs are run at a lower priority. +This is the default mode. +.TP 8 +.B bracexpand +Sans as +.BR \-B . +.TP 8 +.B emacs +Puts you in an +.I emacs +style in-line editor for command entry. +.TP 8 +.B globstar +Same as +.BR \-G . +.TP 8 +.B gmacs +Puts you in a +.I gmacs +style in-line editor for command entry. +.TP 8 +.B ignoreeof +The shell will not exit on end-of-file. +The command +.B exit +must be used. +.TP 8 +.B keyword +Same as +.BR \-k . +.TP 8 +.B markdirs +All directory names resulting from file name generation have a trailing +.B / +appended. +.TP 8 +.B monitor +Same as +.BR \-m . +.TP 8 +.B multiline +The built-in editors will use multiple lines on the screen for lines +that are longer than the width of the screen. This may not work +for all terminals. +.TP 8 +.B noclobber +Same as +.BR \-C . +.TP 8 +.B noexec +Same as +.BR \-n . +.TP 8 +.B noglob +Same as +.BR \-f . +.TP 8 +.B nolog +Do not save function definitions in the history file. +.TP 8 +.B notify +Same as +.BR \-b . +.TP 8 +.B nounset +Same as +.BR \-u . +.TP 8 +.B pipefail +A pipeline will not complete until all components +of the pipeline have completed, and the return value +will be the value of the last non-zero command +to fail or zero of no command has failed. +.TP 8 +.B showme +When enabled, simple commands or pipelines preceded by a a semicolon +.RB ( ; ) +will be displayed as if the +.B xtrace +option were enabled but will not be executed. +Otherwise, the leading +.B ; +will be ignored. +.TP 8 +.B privileged +Same as +.BR \-p . +.TP 8 +.B verbose +Same as +.BR \-v . +.TP 8 +.B trackall +Same as +.BR \-h . +.TP 8 +.B vi +Puts you in insert mode of a +.I vi\^ +style in-line editor +until you hit the escape character +.BR 033 . +This puts you in control mode. +A return sends the line. +.TP 8 +.B viraw +Each character is processed as it is typed +in +.I vi\^ +mode. +.TP 8 +.B xtrace +Same as +.BR \-x . +.PP +If no option name is supplied, then the current option settings are printed. +.RE +.TP 8 +.B \-p +Disables processing of the +.B \s-1$HOME\s+1/.profile +file and uses the file +.B /etc/suid_profile +instead of the +.SM +.B ENV +file. +This mode is on whenever the effective uid (gid) +is not equal to the real uid (gid). +Turning this off causes the effective uid and gid to be +set to the real uid and gid. +.TP 8 +.B \-r +Enables the restricted shell. This option cannot be unset +once set. +.TP 8 +.B \-s +Sort the positional parameters lexicographically. +.TP 8 +.B \-t +(Obsolete). Exit after reading and executing one command. +.TP 8 +.B \-u +Treat unset parameters as an error when substituting. +.TP 8 +.B \-v +Print shell input lines as they are read. +.TP 8 +.B \-x +Print commands and their arguments as they are executed. +.TP 8 +.B \-\|\- +Do not change any of the options; useful in setting +.B $1 +to a value beginning with +.BR \- . +If no arguments follow this option then the positional parameters are unset. +.PD +.PP +As an obsolete feature, +if the first +.I arg\^ +is +.B \- +then the +.B \-x +and +.B \-v +options are turned off and the next +.I arg +is treated as the first argument. +Using +.B \+ +rather than +.B \- +causes these options to be turned off. +These options can also be used upon invocation of the shell. +The current set of options may be found in +.BR $\- . +Unless +.B \-A +is specified, +the remaining arguments are positional +parameters and are assigned, in order, to +.B $1 +.B $2 +\&.\|.\|.\^. +If no arguments are given, then the names and values +of all variables are printed on the standard output. +.RE +.TP +\(dg \f3shift\fP \*(OK \f2n\^\fP \*(CK +.br +The positional parameters from +\f3$\fP\f2n\fP\f3+1\fP +\&.\|.\|. +are renamed +.B $1 +\&.\|.\|.\^ +, default +.I n\^ +is 1. +The parameter +.I n\^ +can be any arithmetic expression that evaluates to a non-negative +number less than or equal to +.BR $# . +.TP +\f3sleep\fP \f2seconds\^\fP +Suspends execution for the number of decimal seconds or fractions of a +second given by +.IR seconds . +.TP +\(dg \f3trap\fP \*(OK \f3\-p\fP \*(CK \*(OK \f2action\^\fP \*(CK \*(OK \f2sig\^\fP \*(CK .\|.\|. +The +.B \-p +option causes the trap +action associated with each trap as specified by the arguments +to be printed with appropriate quoting. +Otherwise, +.I action\^ +will be processed as if it were an argument to +.B eval +when the shell +receives signal(s) +.IR sig . +Each +.I sig\^ +can be given as a number or as the name of the signal. +Trap commands are executed in order of signal number. +Any attempt to set a trap on a signal that +was ignored on entry to the current shell +is ineffective. +If +.I action\^ +is omitted and the first +.I sig\^ +is a number, or if +.I action\^ +is +.BR \- , +then the trap(s) for each +.I sig\^ +are reset +to their original values. +If +.I action\^ +is the null +string then this signal is ignored by the shell and by the commands +it invokes. +If +.I sig\^ +is +.SM +.B ERR +then +.I action\^ +will be executed whenever a command has a non-zero exit status. +If +.I sig\^ +is +.SM +.B DEBUG +then +.I action\^ +will be executed before each command. +The variable +.B .sh.command +will contain the contents of the current command line +when +.I action\^ +is running. +If +.I sig\^ +is +.B 0 +or +.SM +.B EXIT +and the +.B trap +statement is executed inside the body of a function defined with the +.B function +.I name\^ +syntax, +then the command +.I action\^ +is executed +after the function completes. +If +.I sig\^ +is +.B 0 +or +.SM +.B EXIT +for a +.B trap +set outside any function +then the command +.I action\^ +is executed +on exit from the shell. +If +.I sig\^ +is +.SM +.BR KEYBD , +then +.I action\^ +will be executed whenever a key is read +while in +.BR emacs , +.BR gmacs , +or +.B vi\^ +mode. +The +.B trap +command +with no arguments prints a list +of commands associated with each signal number. +.TP +\f3true\fP +Does nothing, and exits 0. Used with +.B while +for infinite loops. +.TP +\(dg\(dg \f3typeset\fP \*(OK \f3\(+-AHflabnprtux\^\fP \*(CK \*(OK \f3\(+-EFLRZi\*(OK\f2n\^\fP\*(CK \*(CK \*(OK \f2vname\^\fP\*(OK\f3=\fP\f2value\^\fP \*(CK \^ \*(CK .\|.\|. +Sets attributes and values for shell variables and functions. +When invoked inside a function defined with the +.B function +.I name\^ +syntax, +a new instance of the variable +.I vname\^ +is created, +and the variable's value and type are restored +when the function completes. +The following list of attributes may be specified: +.RS +.PD 0 +.TP +.B \-A +Declares +.I vname\^ +to be an associative array. +Subscripts are strings rather than arithmetic +expressions. +.TP +.B \-a +Declares +.I vname\^ +to be an indexed array. +This is optional unless except for compound variable assignments. +.TP +.B \-E +Declares +.I vname\^ +to be a double precision floating point number. +If +.I n\^ +is non-zero, it defines the number of significant figures +that are used when expanding +.IR vname . +Otherwise, ten significant figures will be used. +.TP +.B \-F +Declares +.I vname\^ +to be a double precision floating point number. +If +.I n\^ +is non-zero, it defines the number of places after the +decimal point that are used when expanding +.IR vname . +Otherwise ten places after the decimal point will be used. +.TP +.B \-H +This option provides UNIX to host-name file mapping on non-UNIX +machines. +.TP +.B \-L +Left justify and remove leading blanks from +.IR value . +If +.I n\^ +is non-zero, it defines the width +of the field, +otherwise it is determined by the width of the value of +first assignment. +When the variable is assigned to, it is +filled on the right with blanks or truncated, if necessary, to +fit into the field. +The +.B \-R +option is turned off. +.TP +.B \-R +Right justify and fill with leading blanks. +If +.I n\^ +is non-zero, it defines the width +of the field, +otherwise it is determined by the width of the value of +first assignment. +The field is left filled with blanks or +truncated from the end if the +variable is reassigned. +The +.B \-L +option is turned off. +.TP +.B \-Z +Right justify and fill with leading zeros if +the first non-blank character is a digit and the +.B \-L +option has not been set. +Remove leading zeros if the +.B \-L +option is also set. +If +.I n\^ +is non-zero, it defines the width +of the field, +otherwise it is determined by the width of the value of +first assignment. +.TP +.B \-f +The names refer to function names rather than +variable names. +No assignments can be made and the only other +valid options are +.BR \-t , +.B \-u +and +.BR \-x . +The +.B \-t +option +turns on execution tracing for this function. +The +.B \-u +option +causes this function to be marked undefined. +The +.SM +.B FPATH +variable will be searched to find the function definition +when the function is referenced. +If no options other than +.B \-f +is specified, then the function definition will be displayed +on standard output. If +.B +f +is specified, then a line containing the function name followed +by a shell comment containing the line number and path name of the +file where this function was defined, if any, is displayed. +.TP +.B \-b +The variable can hold any number of bytes of data. +The data can be text or binary. +The value is represented by the base64 encoding of the data. +If +.B \-Z +is also specified, the size in bytes of the +data in the buffer will be determined by the size associated with the +.BR \-Z . +If the base64 string assigned results in more data, it will be +truncated. Otherwise, it will be filled with bytes +whose value is zero. +The +.B printf +format +.B %B +can be used to output the actual data in this buffer instead +of the base64 encoding of the data. +.TP +.B \-i +Declares +.I vname\^ +to be represented internally as integer. +The right hand side of an assignment is evaluated as an +arithmetic expression when assigning to an integer. +If +.I n\^ +is non-zero, it defines the output arithmetic base, +otherwise the output base will be ten. +.TP +.B \-l +All upper-case characters are +converted to lower-case. +The upper-case option, +.BR \-u , +is turned off. +.TP +.B \-n +Declares +.I vname\^ +to be a reference to the variable whose name is +defined by the value of variable +.IR vname . +This is usually used to reference a variable inside +a function whose name has been passed as an argument. +.TP +.B \-r +The given +.IR vname s +are marked +readonly and these +names cannot be changed +by subsequent assignment. +.TP +.B \-t +Tags the variables. +Tags are user definable and have no special +meaning to the shell. +.TP +.B \-u +All lower-case characters are converted +to upper-case. +The lower-case option, +.BR \-l , +is turned off. +.TP +.B \-x +The given +.IR vname s +are marked for automatic +export to the +.I environment\^ +of subsequently-executed commands. +Variables whose names contain a \fB\s+2.\s-2\fP +cannot be exported. +.PD +.PP +The +.B \-i +attribute cannot be specified along with +.BR \-R , +.BR \-L , +.BR \-Z , +or +.BR \-f . +.PP +Using +.B \+ +rather than +.B \- +causes these options to be turned off. +If no +.I vname\^ +arguments are given, +a list of +.I vnames\^ +(and optionally the +.IR values\^ ) +of the +.I variables\^ +is printed. +(Using +.B \+ +rather than +.B \- +keeps the +values from being printed.) +The +.B \-p +option causes +.B typeset +followed by the option letters +to be printed before each name +rather than the names of the options. +If any option other than +.B \-p +is given, +only those variables +which have all of the given +options are printed. +Otherwise, the +.IR vname s +and +.I attributes\^ +of all +.I variables\^ +that have attributes +are printed. +.RE +.TP +\f3ulimit\fP \*(OK \f3\-HSacdfmnpstv\fP \*(CK \*(OK \f2limit\^\fP \*(CK +Set or display a resource limit. +The available resource limits are listed below. +Many systems do not support one or more of these limits. +The limit for a specified resource is set when +.I limit\^ +is specified. +The value of +.I limit\^ +can be a number in the unit specified below with each resource, +or the value +.BR unlimited . +The +.B \-H +and +.B \-S +options specify whether the hard limit or the +soft limit for the given resource is set. +A hard limit cannot be increased once it is set. A soft +limit can be increased up to the value of the hard limit. +If neither the +.B H +nor +.B S +options is specified, the limit applies to both. +The current resource limit is printed when +.I limit\^ +is omitted. +In this case, the soft limit is printed unless +.B H +is specified. +When more than one resource is specified, then the limit +name and unit is printed before the value. +.RS +.PD 0 +.TP +.B \-a +Lists all of the current resource limits. +.TP +.B \-c +The number of 512-byte blocks on the size of core dumps. +.TP +.B \-d +The number of K-bytes on the size of the data area. +.TP +.B \-f +The number of 512-byte blocks on files that can be written by the +current process or by child processes (files of any size may be read). +.TP +.B \-m +The number of K-bytes on the size of physical memory. +.TP +.B \-n +The number of file descriptors plus 1. +.TP +.B \-p +The number of 512-byte blocks for pipe buffering. +.TP +.B \-s +The number of K-bytes on the size of the stack area. +.TP +.B \-t +The number of CPU seconds to be used by each process. +.TP +.B \-v +The number of K-bytes for virtual memory. +.PD +.PP +If no option is given, +.B \-f +is assumed. +.RE +.TP +\f3umask\fP \*(OK \f3\-S\fP \*(CK \*(OK \f2mask\^\fP \*(CK +The user file-creation mask is set to +.I mask\^ +(see +.IR umask (2)). +.I mask +can either be an octal number or +a symbolic value as described in +.IR chmod (1). +If a symbolic value is given, +the new +umask value is the complement of the result of +applying +.I mask\^ +to the complement of the previous umask value. +If +.I mask\^ +is omitted, the current value of the mask is printed. +The +.B \-S +option causes the mode to be printed as a symbolic +value. Otherwise, the +mask is printed in octal. +.TP +\(dg \f3unalias\fP \*(OK \f3\-a\fP \*(CK \f2name\^\fP .\|.\|. +The aliases +given by the list of +.IR name s +are removed from the alias list. +The +.B \-a +option causes all the +aliases to be unset. +.TP +\(dg\f3unset\fP \*(OK \f3\-fnv\fP \*(CK \f2vname\^\fP .\|.\|. +The variables given by the list of +.IR vname s +are unassigned, +i.e., +their values and attributes are erased. +Readonly variables cannot be unset. +If the +.B \-f +option +is set, then the names refer to +.I function\^ +names. +If the +.B \-v +option is set, then the names refer to +.I variable\^ +names. +The +.B \-f +option overrides +.BR \-v . +If +.B \-n +is set and +.I name\^ +is a name reference, then +.I name\^ +will be unset rather than the variable +that it references. +The default is equivalent to +.BR \-v . +Unsetting +.SM +.BR LINENO , +.SM +.BR MAILCHECK , +.SM +.BR OPTARG , +.SM +.BR OPTIND , +.SM +.BR RANDOM , +.SM +.BR SECONDS , +.SM +.BR TMOUT , +and +.SM +.B _ +removes their special meaning even if they are +subsequently assigned to. +.TP +\f3wait\fP \*(OK \f2job\^\fP .\|.\|. \*(CK +Wait for the specified +.I job +and +report its termination status. +If +.I job\^ +is not given, then all currently active child processes are waited for. +The exit status from this command is that of +the last process waited for if +.I job\^ +is specified; otherwise it is zero. +See +.I Jobs +for a description of the format of +.IR job . +.TP +\f3whence\fP \*(OK \f3\-afpv\fP \*(CK \f2name\^\fP .\|.\|. +For each +.IR name , +indicate how it +would be interpreted if used as a command name. +.sp .5 +The +.B \-v +option +produces a more verbose report. +The +.B \-f +options skips the search for functions. +The +.B \-p +option +does a path search for +.I name\^ +even if name is an alias, a function, or a reserved word. +The +.B \-a +option +is similar to the +.B \-v +option but causes +all interpretations of the given name to be reported. +.SS Invocation. +If the shell is invoked by +.IR exec (2), +and the first character of argument zero +.RB ( $0 ) +is +.BR \- , +then the shell is assumed to be a +.I login +shell and +commands are read from +.B /etc/profile +and then from either +.B .profile +in the current directory or +.BR \s-1$HOME\s+1/.profile , +if either file exists. +Next, for interactive shells, commands are read from +the file named by +performing parameter expansion, command substitution, +and arithmetic substitution on +the value of the environment variable +.SM +.B ENV +if the file exists. +If the +.B \-s +option is not present and +.I arg\^ +and a file by the name of +.I arg\^ +exits, then it reads and executes this script. +Otherwise, if the first +.I arg\^ +does not contain a +.BR / , +a path search is performed on the first +.I arg\^ +to determine the name of the script to execute. +The script +.I arg\^ +must have execute permission and any +.I setuid +and +.I setgid +settings will be ignored. +If the script is not found on the path, +.I arg\^ +is processed as if it named a built-in command or function. +Commands are then read as described below; +the following options are interpreted by the shell +when it is invoked: +.PP +.PD 0 +.TP 10 +.BI \-c +If the +.B \-c +option is present, then +commands are read from the first +.IR arg . +Any remaining arguments become +positional parameters starting at +.BR 0 . +.TP +.B \-s +If the +.B \-s +option is present or if no +arguments remain, +then commands are read from the standard input. +Shell output, +except for the output of the +.I Special Commands\^ +listed above, +is written to +file descriptor 2. +.TP +.B \-i +If the +.B \-i +option is present or +if the shell input and output are attached to a terminal (as told by +.IR tcgetattr (2)), +then this shell is +.IR interactive . +In this case \s-1TERM\s+1 is ignored (so that \f3kill 0\fP +does not kill an interactive shell) and \s-1INTR\s+1 is caught and ignored +(so that +.B wait +is interruptible). +In all cases, \s-1QUIT\s+1 is ignored by the shell. +.TP +.B \-r +If the +.B \-r +option is present, the shell is a restricted shell. +.TP +.B \-D +A list of all double quoted strings that are preceded by a +.B $ +will be printed on standard output and the shell will exit. +This set of strings will be subject to language translation +when the locale is not C or POSIX. +No commands will be executed. +.PD +.TP +.B \-P +If +.B \-P +or +.B \-o profile +is present, the shell is a profile shell (see +.IR pfexec (1)). +.TP +.BI \-R " filename\^" +The +.B \-R +.I filename\^ +option is used +to generate a cross reference database +that can be used by a separate utility +to find definitions and references for variables and commands. +.PP +The remaining options and arguments are described under the +.B set +command above. +An optional +.B \- +as the first argument is ignored. +.if \nZ=0 \{.SS Rsh Only. +.I Rsh\} +.if \nZ=1 \{.SS Rksh Only. +.I Rksh\} +.if \nZ=2 \{.SS Rksh93 Only. +.I Rksh93\} +is used to set up login names and execution environments whose +capabilities are more controlled than those of the standard shell. +The actions of +.if \nZ=0 .B rsh\^ +.if \nZ=1 .B rksh\^ +.if \nZ=2 .B rksh93\^ +are identical to those of +.if \nZ=0 .BR sh\^ , +.if \nZ=1 .BR ksh\^ , +.if \nZ=2 .BR ksh93\^ , +except that the following are disallowed: +.RS +.PD 0 +.PP +Unsetting the restricted option. +.br +changing directory (see +.IR cd (1)), +.br +setting or unsetting the value or attributes of +.SM +.BR SHELL , +.SM +.BR ENV , +.SM +.BR FPATH , +or +.SM +.BR PATH\*S, +.br +specifying path or +command names containing +.BR / , +.br +redirecting output +.RB ( > , +.BR >| , +.BR <> , +and +.BR >> ). +.br +adding or deleting built-in commands. +.br +using +.B "command -p" +to invoke a command. +.PD +.RE +.PP +The restrictions above are enforced +after \f3.profile\fP and the +.SM +.B ENV +files are interpreted. +.PP +When a command to be executed is found to be a shell procedure, +.if \nZ=0 \{.B rsh\^ +invokes +.I sh\^\} +.if \nZ=1 \{.B rksh\^ +invokes +.I ksh\^\} +.if \nZ=2 \{.B rksh93\^ +invokes +.I ksh93\^\} +to execute it. +Thus, it is possible to provide to the end-user shell procedures +that have access to the full power of +the standard shell, +while imposing a limited menu of commands; +this scheme assumes that the end-user does not have write and +execute permissions in the same directory. +.PP +The net effect of these rules is that the writer of the +.B .profile +has complete control over user actions, +by performing guaranteed setup actions +and leaving the user in an appropriate directory +(probably +.I not\^ +the login directory). +.PP +The system administrator often sets up a directory +of commands +(e.g., +.BR /usr/rbin ) +that can be safely invoked by +.if \nZ=0 .BR rsh . +.if \nZ=1 .BR rksh . +.if \nZ=2 .BR rksh93 . +.SH EXIT STATUS +Errors detected by the shell, such as syntax errors, +cause the shell +to return a non-zero exit status. +If the shell is being used non-interactively, +then execution of the shell file is abandoned +unless the error occurs inside a subshell in which case +the subshell is abandoned. +Otherwise, the shell returns the exit status of +the last command executed (see also the +.B exit +command above). +Run time errors detected by the shell are reported by +printing the command or function name and the error condition. +If the line number that the error occurred on is greater than one, +then the line number is also printed in square brackets +.RB ( "[]" ) +after the command or function name. +.SH FILES +/etc/profile +The system wide initialization file, executed for login shells. +.RE +.if \nZ=2 \{.br +/etc/ksh.kshrc +.RS +The system wide startup file, executed for interactive shells. +.RE\} +.br +\s-1$HOME\s+1/\f3.\fPprofile +.RS +The personal initialization file, executed for login shells after /etc/profile. +.RE +.br +\s-1$HOME\s+1/\f3.\fP.kshrc +.RS +Default personal initialization file, executed for interactive shells when +.SM +.B ENV +is not set. +.RE +.br +/etc/suid_profile +.RS +Alternative initialization file, executed when instead of personal initialization file when the real and effective user or group id do not match. +.RE +.br +/dev/null +.RS +NULL device +.RE +.SH SEE ALSO +cat(1), +cd(1), +chmod(1), +cut(1), +egrep(1), +echo(1), +emacs(1), +env(1), +fgrep(1), +gmacs(1), +grep(1), +newgrp(1), +pfexec(1), +stty(1), +test(1), +umask(1), +vi(1), +dup(2), +exec(2), +fork(2), +getpwnam(3), +ioctl(2), +lseek(2), +paste(1), +pathconf(2), +pipe(2), +sysconf(2), +umask(2), +ulimit(2), +wait(2), +rand(3), +a.out(5), +profile(5), +environ(7). +.PP +Morris I. Bolsky and David G. Korn, +.IR "The New KornShell Command and Programming Language" , +Prentice Hall, 1995. +.PP +.I "POSIX \- Part 2: Shell and Utilities," +IEEE Std 1003.2-1992, ISO/IEC 9945-2, IEEE, 1993. +.SH CAVEATS +.PP +If a command +is executed, and then a command with the same name is +installed in a directory in the search path before the directory where the +original command was found, the shell will continue to +.I exec\^ +the original command. +Use the +.B \-t +option of the +.B alias\^ +command to correct this situation. +.PP +Some very old shell scripts contain a +.B ^ +as a synonym for the pipe character +.BR \(bv . +.PP +Using the +.B hist\^ +built-in command within a compound command will cause the whole +command to disappear from the history file. +.PP +The built-in command \f3\|.\fP \f2file\^\fP +reads the whole file before any commands are executed. +Therefore, +.B alias +and +.B unalias +commands in the file +will not apply to any commands defined in the file. +.PP +Traps are not processed while a job is waiting for a foreground process. +Thus, a trap on +.B CHLD +won't be executed until the foreground job terminates. +.PP +It is a good idea to leave a space after the comma operator in +arithmetic expressions to prevent the comma from being interpreted +as the decimal point character in certain locales. diff --git a/usr/src/lib/libshell/common/sh.memo b/usr/src/lib/libshell/common/sh.memo new file mode 100644 index 0000000000..9f321e6eeb --- /dev/null +++ b/usr/src/lib/libshell/common/sh.memo @@ -0,0 +1,3248 @@ +. \" use troff -mm +.nr C 3 +.nr N 2 +.SA 1 \" right justified +.ND "December 21, 1993" +.TL "311466-6713" "61175" \" charging case filing case +Introduction to \f5ksh-93\fP +.AU "David G. Korn" DGK MH 11267 7975 3C-526B "(research!dgk)" +.TM 11267-931221-26 \" technical memo + TM numbers +.MT 1 \" memo type +.OK Shell "Command interpreter" Language UNIX \" keyword +.AS 2 \" abstract start for TM +\f5ksh-93\fP is a +major rewrite of \f5ksh\fP, +a program that serves as a command language +(shell) for the UNIX* +.FS * +UNIX is a registered trademark of Novell. +.FE +operating system. +As with \f5ksh\fP, \f5ksh-93\fP +is essentially compatible with the System V version of the Bourne shell\*(Rf, +.RS +S. R. Bourne, +.I "An Introduction to the UNIX +Shell," +BSTJ - Vol. 57, No. 6 part 2, pages 1947-1972, 1978. +.RF +and compatible with previous versions of \f5ksh\fP. +\f5ksh-93\fP is intended to comply with the IEEE POSIX 1003.2 +and ISO 9945-2\*(Rf +.RS +.I "POSIX \- Part 2: Shell and Utilities," +IEEE Std 1003.2-1992, ISO/IEC 9945-2, IEEE, 1993. +.RF +shell standard. +In addition to changes in the language required +by these standards, the primary focus of \f5ksh-93\fP +is related to shell programming. +\f5ksh-93\fP provides the programming power of several +other interpretive languages such as \f5awk\fP\*(Rf, +.RS +Al Aho, +Brian Kernighan, +and +Peter Weinberger, +.I "The AWK Programming Language," +Addison Wesley, 1988. +.RF +\f5FIT\fP\*(Rf, +.RS +Lloyd H. Nakatani and Laurence W. Ruedisueli, +.I "The FIT Programming Language Primer", +TM 1126-920301-03, 1992. +.RF +\f5PERL\fP\*(Rf, +.RS +Larry Wall and Randal Schwartz, +.I "Programming perl," +O'Reilly & Assoc, 1990. +.RF +and +\f5tcl\fP\*(Rf. +.RS +John K. Ousterhout, +.I "Tcl: An Embeddable Command Language", +Proceedings of the Washington USENIX meeting, pp. 133-146, 1990. +.RF +.P +This memo +assumes that the reader is already familiar with the Bourne shell. +It introduces most of the features of \f5ksh-93\fP +relative to the Bourne shell; both +as a command language and as a programming language. +The Appendix contains +a sample script written in \f5ksh-93\fP. +.AE \" abstract end +.H 1 "INTRODUCTION" +.P +The term "shell" is used to describe a program that provides +a command language +interface. +Because the UNIX*\ +.FS * +UNIX is a registered trademark of USL +.FE +system shell is a user level program, and not part of +the operating system itself, +anyone can write a new shell or modify an existing one. +This has caused an evolutionary progress +in the design and implementation of shells, +with the better ones surviving. +The most widely available UNIX system shells are the Bourne shell\*(Rf, +.RS +S. R. Bourne, +.IR "An Introduction to the UNIX Shell" , +Bell System Technical Journal, +Vol. 57, No. 6, Part 2, pp. 1947-1972, July 1978. +.RF +written by Steve Bourne +at AT&T Bell Laboratories, +the C shell\*(Rf, +.RS +W. Joy, +.IR "An Introduction to the C Shell" , +Unix Programmer's Manual, Berkeley Software Distribution, +University of California, Berkeley, 1980. +.RF +written by Bill Joy at the University of California, Berkeley, +and the KornShell language \*(Rf, +.RS +Morris Bolsky and David Korn, +.IR "The KornShell Command and Programming Language" , +Prentice Hall, 1989. +.RF +written by David Korn +at AT&T Bell Laboratories. +The Bourne shell is available on almost all versions of the UNIX +system. +The C Shell is available with all Berkeley Software Distribution (BSD) UNIX systems and on many other systems. +The KornShell +is available on System V Release 4 systems. +In addition, it is available on many other systems. +The source for the KornShell language is available from the AT&T Toolchest, +an electronic software distribution system. +It runs on all known versions of the UNIX system and +on many UNIX system look-alikes. +.P +There have been several articles comparing the UNIX system shells. +Jason Levitt\*(Rf +.RS +Jason Levitt, +.IR "The Korn Shell: An Emerging Standard" , +UNIX/World, pp. 74-81, September 1986. +.RF +highlights some of the new features +introduced by the KornShell language. +Rich Bilancia\*(Rf +.RS +Rich Bilancia, +.IR "Proficiency and Power are Yours With the Korn Shell" , +UNIX/World, pp. 103-107, September 1987. +.RF +explains some of the advantages of using the KornShell language. +John Sebes\*(Rf +.RS +John Sebes, +.I "Comparing UNIX Shells," +UNIX Papers, +Edited by the Waite Group, Howard W. Sams & Co., 1987. +.RF +provides a more detailed comparison of the three shells, +both as a command language and as a programming language. +.P +The KornShell language is a superset of the +Bourne shell. The KornShell language has many of the popular C shell features, +plus additional features of its own. +Its initial popularity stems primarily from its improvements as +a command language. +The primary interactive benefit of the KornShell command language +is a visual command line editor that allows you to +make corrections to your current command line +or to earlier command lines, +without having to retype them. +.P +However, +in the long run, +the power of the KornShell language as a high-level programming language, +as described by Dolotta and Mashey\*(Rf, +.RS +T. A. Dolotta and J. R. Mashey, +.I "Using the shell as a Primary Programming Tool," +Proc. 2nd. Int. Conf. on Software Engineering, 1976, +pages 169-176. +.RF +may prove to be of greater significance. +\f5ksh-93\fP provides the programming power of several +other interpretive languages such as \f5awk\fP, +\f5FIT\fP, +\f5PERL\fP, +and +\f5tcl\fP. +An application that was originally written in the C programming language +was rewritten in the KornShell language. +More than 20,000 lines of C code were replaced with KornShell scripts +totaling fewer than 700 lines. +In most instances there was no perceptible difference in performance +between the two versions of the code. +.P +The KornShell language has been embedded into windowing systems +allowing graphical user interfaces to be developed in shell +rather than having to build applications that need to be +compiled. +The \f5wksh\fP program\*(Rf +.RS +J. S. Pendergrast, +.IR "WKSH - Korn Shell with X-Windows Support", +USL. 1991. +.RF +provides a method of developing OpenLook or Motif +applications as \f5ksh\fP scripts. +.P +This memo is an introduction to \f5ksh-93\fP, +the program that implements an enhanced version +of the KornShell language. +It is referred to as \f5ksh\fP in the rest of this memo. +The memo describes the KornShell language based on the +features of the 12/28/93 release of \f5ksh\fP. +This memo is not a tutorial, only an introduction. +The second edition of reference [9] gives +a more complete treatment of the KornShell language. +.P +A concerted effort has been made to achieve both System V Bourne shell +compatibility and IEEE POSIX compatibility +so that scripts written for either of these shells +can run without modification with \f5ksh\fP. +In addition, \f5ksh-93\fP attempts to +be compatible with older versions of \f5ksh\fP. +When there are conflicts between versions of the shell, +\f5ksh-93\fP selects the behavior dictated by the IEEE POSIX +standard. +The description of features in this memo assumes +that the reader is already familiar with the Bourne shell. +.H 1 "COMMAND LANGUAGE" +There is no separate command language. +All features of the language, except job control, +can be +used both within a script and interactively from a terminal. +However, features that are more likely to be used +while running commands interactively from a terminal +are presented here. +.H 2 "Setting Options" +By convention, UNIX commands +consist of a command name followed by options and other arguments. +Options are either of the form \f5-\fP\fIletter\fP, +or \f5-\fP\fIletter value\fP. +In the former case, several options may be grouped after a single \f5-\fP. +The argument \f5--\fP signifies an end to the option list and is +only required when the first non-option argument begins with +a \f5-\fP. +Most commands print an error message which +shows which options are permitted +when given incorrect arguments. +In addition, the option sequence \f5-?\fP causes most commands +to print a usage message which lists the valid options. +.P +Ordinarily, \f5ksh\fP executes a command by +using the command name to locate a program to run +and by running the program as a separate process. +Some commands, referred to as +.IR built-ins , +are carried out by \f5ksh\fP itself, +without creating a separate process. +The reasons that some commands are built-in are presented later. +In nearly all cases the distinction +between a command that is built-in and one that +is not is invisible to the user. +However, nearly +all commands that are built-in follow command line conventions. +.P +\f5ksh\fP has several options that can be set by the user +as command line arguments at invocation and as option arguments to the +\f5set\fP command. +Most other options can be set with a single letter option or as a name +that follows the \f5-o\fP option. +Use +\f5set\ -o\fP +to display the current option settings. +Some of these options, such as +.B interactive +and +.B monitor +(see +.I "Job Control" +below), +are enabled automatically by \f5ksh\fP +when the shell is connected to a terminal device. +Other options, such as +.B noclobber +and +.BR ignoreeof , +are normally placed in a startup file. +The +.B noclobber +option causes +\f5ksh\fP +to print an error message when you use +.B > +to redirect output to a file that already exists. +If you want to redirect to an existing file, then +you have to use +.B >| +to override +the +.B noclobber +option. +The +.B ignoreeof +option +is used to prevent the +.I end-of-file +character, normally +.B ^D +(Control- d), +from exiting the shell and possibly logging you out. +You must type \f5exit\fP +to log out. +Most of the options are described in this memo as appropriate. +.H 2 "Command Aliases" +.P +Command aliases provide a mechanism of associating a command name and +arguments with a shorter name. +Aliases are defined with the \f5alias\fP +built-in. +The form of an \f5alias\fP +command definition is: +.ce +\f5alias\fP \fIname\fP\f5=\fP\fIvalue\fP +As with most other shell assignments, no space is allowed before or after +the \f5=\fP. +The characters of an alias name cannot be characters that are +special to the shell. +The replacement string, +.I value, +can contain any valid shell script, +including meta-characters such as pipe symbols and i/o-redirection +provided that they are quoted. +Unlike +\f5csh\fP, +aliases in +\f5ksh\fP +cannot take arguments. +The equivalent functionality of aliases with arguments can +be achieved with shell functions, described later. +.P +As a command is being read, +the command name is checked against a list of +.I alias +names. +If it is found, +the name is replaced by the alias value associated with the +.I alias +and then rescanned. +When rescanning the value for an alias, alias substitutions +are performed except for an alias that is currently being processed. +This prevents infinite loops in alias substitutions. +For example with the aliases, \f5alias\ l=ls\ 'ls=ls\ -C'\fP, +the command name \f5l\fP becomes \f5ls\fP, which becomes \f5ls\ -C\fP. +Ordinarily, only the command name word is processed for alias substitution. +However, if the value of an alias ends in a space, +then the word following the alias is also checked for alias substitution. +This makes it possible +to define an alias whose first argument is the name of a command +and have alias substitution performed on this argument, +for example +\f5nohup='nohup\ '\fP. +.P +Aliases can be used to redefine built-in commands so that +the alias, +.ce +\f5alias test=./test\fP +can be used to look for \f5test\fP +in your current working directory rather than +using the built-in \f5test\fP command. +Reserved words such as +\f5for\fP and \f5while\fP +cannot be changed by aliasing. +The command \f5alias\fP, +without arguments, generates +a list of aliases and corresponding alias values. +The \f5unalias\fP command removes the name and text of an alias. +.P +Aliases are used to save typing and to improve readability of scripts. +Several aliases are predefined by \f5ksh\fP. +For example, the predefined alias +.ce +\f5alias integer='typeset -i'\fP +allows the integer variables \f5i\fP and \f5j\fP +to be declared and initialized with the command +.ce +\f5integer i=0 j=1\fP +.P +While aliases can be defined in scripts, +it is not recommended. +The location of an alias command can be important +since aliases are only processed when a command is read. +A \fB\s+2.\s-2\fP +procedure (the shell equivalent of an include file) +is read all at once (unlike +start up files +which are read a command at +a time) so that any aliases defined there will not effect any commands +within this script. +Predefined aliases do not have this problem. +.H 2 "Command Re-entry" +.P +When run interactively, +\f5ksh\fP saves the +commands you type at a terminal in a file. +If the variable +\fB\s-1HISTFILE\s+1\fP +is set to the name of a file to which the user +has write access, +then the commands are stored in this +.I history +file. +Otherwise the file +\fB$\s-1HOME\s+1/.sh_history\fP +is checked for write access and if this fails +an unnamed file is used to hold the history lines. +Commands are always appended to this file. +Instances of \f5ksh\fP +that run concurrently and use the same history file +name, share access to the history file so that a command +entered in one shell will be available for editing in another +shell. +The file may be truncated when \f5ksh\fP +determines that no other shell is using the history file. +The number of commands accessible to the user is determined by the value of the +\fB\s-1HISTSIZE\s+1\fP +variable at the time the shell is invoked. +The default value is 256. +Each command may consist of one or more lines since a compound +command is considered one command. +If the character +.B ! +is placed within the +.I "primary prompt" +string, +\fB\s-1PS1\s+1\fP, +then it is replaced by the command number each time the prompt is given. +.P +A built-in command named \f5hist\fP +is used to list and/or edit +any of these saved commands. +The option +.B \-l +is used to specify listing of previous commands. +The command can always be specified with +a range of one or more commands. +The range can be specified by giving the command +number, relative or absolute, or by giving +the first character or characters of the command. +When given without specifying the range, +the last 16 +commands are listed, each +preceded by the command number. +.P +If the listing option is not selected, +then the range of commands specified, +or the last command if no range is given, +is passed to an editor program before +being re-executed by \f5ksh\fP. +The editor to be used may be specified +with the option +.B \-e +and following it with the editor name. +If this option is not specified, the +value of the shell variable +\fB\s-1HISTEDIT\s+1\fP +is used as the name of the editor, +providing that this variable has a non-null value. +If this variable is not set, or is null, +and the +.B \-e +option has not been selected, +then +\f5/bin/ed\fP +is used. +When editing has been complete, +the edited text automatically becomes +the input for \f5ksh\fP. +As this text is read by \f5ksh\fP, it is echoed onto the terminal. +.P +The +.B \-s +option causes the editing to be bypassed +and just re-executes the command. +In this case only a single command can be specified as the range +and an optional argument of the form +\fIold\fP\fB=\fP\fInew\fP +may be added which requests a simple string substitution +prior to evaluation. +A convenient alias, +.ce +\f5alias r='hist -s'\fP +has been pre-defined so that +the single key-stroke +\f5r\fP +can be used to re-execute the previous command +and the key-stroke sequence, +\f5r\ abc=def\ c\fP +can be used to re-execute the last command that starts with +the letter \f5c\fP +with the first occurrence of the string \f5abc\fP +replaced with the string \f5def\fP. +Typing +\f5r\ c\ >\ file\fP +re-executes the most recent command starting with the letter \f5c\fP, +with standard output redirected to +.IR file . +.H 2 "In-line editing" +.P +Lines typed from a terminal frequently need changes made +before entering them. +With the Bourne shell the only method to fix up commands +is by backspacing or killing the whole line. +\f5ksh\fP offers options that allow the user to edit parts of the +current command line before submitting the command. +The in-line edit options make the command line into a single +line screen edit window. +When the command is longer than the width of the terminal, +only a portion of the command is visible. +Moving within the line automatically makes that portion visible. +Editing can be performed on this window until the +.I return +key is pressed. +The editing modes have editing directives that access the history file +in which previous commands are saved. +A user can copy any of the most recent +\fB\s-1HISTSIZE\s+1\fP +commands from this file into the input edit window. +You can locate commands by searching or by position. +.P +The in-line editing options do not use the +.I termcap +or +.I terminfo +databases. +They work on most standard terminals. +They only require that the backspace character moves the cursor left +and the space character overwrites the current character on the screen +and moves the cursor to the right. +Very few terminals or terminal emulators do not have +this behavior. +.P +There is a choice of editor options. +The +.BR emacs , +.BR gmacs , +or +.B vi +option is selected by turning on the +corresponding +option of the \f5set\fP +command. +If the value of the +\fB\s-1EDITOR\s+1\fP +or +\fB\s-1VISUAL\s+1\fP +variables ends with any of these suffixes +the corresponding option is turned on. +A large subset of each of these editors' +features is available within the shell. Additional +functions, such as file name completion, have also been added. +.P +In the +.B emacs +or +.B gmacs +mode the user positions the cursor to the point +needing correction and inserts, deletes, or replaces +characters as needed. +The only difference between these two modes is the +meaning of the directive +.BR ^T . +Control keys and escape sequences are used for cursor +positioning and control functions. +The available editing functions are listed in the manual page. +.P +The +.B vi +editing mode +starts in insert mode and enters control mode when the +user types ESC ( 033 ). +The +.I return +key, which submits the current command for processing, +can be entered from either mode. +The cursor can be anywhere on the line. +A subset of commonly used +.I vi +editing directives are available. +The +.B k +and +.B j +directives that normally move up and down by one +.IR line , +move up and down one +.I command +in the history file, +copying the command into the input edit window. +For reasons of efficiency, +the terminal is kept in canonical mode until an +ESC +is typed. +On some terminals, +and on earlier versions of the UNIX operating system, +this doesn't work correctly. +The +.B viraw +option, +which always uses +.I raw +or +.I cbreak +mode, +must be used in this case. +.P +Most of the code for the editing options does not rely on the +\f5ksh\fP code and can be used in a stand-alone mode with most any command +to add in-line edit capability. +However, +all versions of the in-line editors have some features that +use some shell specific code. For example, +with all edit modes, the +ESC-= +directive applied to command words +(the first word on the line, +or the first word after a +.BR ; , +.BR | , +.BR ( , +or +.BR & ) +lists all aliases, functions, or commands +that match the portion of the given current word. +When applied to other words, this directive +prints the names of files that match the current +word. +The ESC\fB-*\fP directive +adds the expanded list of matching files to the command line. +A trailing +.B * +is added to the word if it doesn't contain any file pattern matching +characters before the expansion. +In +.B emacs +and +.B gmacs +mode, +ESC-ESC +indicates command completion when applied to +command names, otherwise it indicates pathname completion. +With command or pathname completion, +the list generated by the +ESC-= directive is examined to find +the longest common prefix. +With command completion, only the last component of +the pathname is used to compute the longest command prefix. +If the longest common prefix is a complete match, +then the word is replaced by the pathname, and a +.B / +is appended if +pathname is a directory, otherwise a space is added. +In +.B vi +mode, +.B \e +from control mode gives the same behavior. +.H 2 "Key Binding" +.P +It is possible to intercept keys as they are entered and +apply new meanings or bindings. +A trap named +\fB\s-1KEYBD\s+1\fP +is evaluated each time +\f5ksh\fP processes characters entered +from the keyboard, +other than those typed +while entering a search string or an argument to an +edit directive such as +.B r +in vi-mode. +The action associated with this trap can change the value of +the entered key to cause the key to perform a different +operation. +.P +When the +\fB\s-1KEYBD\s+1\fP +trap is entered, +the \fB.sh.edtext\fP +variable contains the contents of the current input line +and the \fB.sh.edcol\fP +variable gives the current cursor position within this line. +The \fB.sh.edmode\fP +variable contains the +.B ESC +character when the trap is entered from +.B vi +insert mode. +Otherwise, this value is null. +The \fB.sh.edchar\fP +variable contains the character or +escape sequence that caused the trap. +A key sequence is either a single character, +.B ESC +followed by a single character, +or +.B ESC[ +followed by a single character. +In the \fBvi\fP edit mode, +the characters after the +.B ESC +must be entered within half a second after the +.BR ESC . +The value of \fB.sh.edchar\fP +at the end of the trap will be used as +the input sequence. +.P +Using the associative array facility of \f5ksh\fP described later, +and the function facility of \f5ksh\fP, it is easy to write +a single trap so that keys can be bound dynamically. For example, +.sp +.nf +.in .5i +.ta 4i +\f5typeset -A Keytable +trap 'eval "${Keytable[${.sh.edchar}]}"' KEYBD +function keybind # key action +{ + typeset key=$(print -f "%q" "$2") + case $# in + 2) Keytable[$1]='.sh.edchar=${.sh.edmode}'"$key" + ;; + 1) unset Keytable[$1] + ;; + *) print -u2 "Usage: $0 key [action]" + ;; + esac +}\fP +.ta +.in +.fi +.sp +.H 2 "Job Control" +.P +The job control mechanism +is almost identical to the version introduced in \f5csh\fP +of the Berkeley UNIX operating system, +version 4.1 and later. +The job control feature allows the user to stop and +restart programs, and to move programs to and from the +foreground and the background. +It will only work on systems that provide support for +these features. +However, +even systems without job control have a +.B monitor +option which, when enabled, will report the progress +of background jobs and enable the user to \f5kill\fP +jobs by job number or job name. +.P +An interactive shell associates a +.I job +with each pipeline typed in from the terminal +and assigns it a small integer number +called the job number. +If the job is run asynchronously, +the job number is printed at the terminal. +At any given time, only one job owns the terminal, +i.e., keyboard signals are only sent to the processes in one job. +When \f5ksh\fP creates a foreground job, +it gives it ownership of the terminal. +If you are running a job and wish to stop +it you hit the key +.B ^Z +(control-\fBZ\fP) +which sends a +\fB\s-1STOP\s+1\fP +signal to all processes in the current job. +The shell receives notification that the processes +have stopped and takes back control of the terminal. +.P +There are commands to continue programs in the foreground +and background. +There are several ways to refer to jobs. +The character +.B % +introduces a job name. +You can refer to jobs by name or number as described in the manual page. +The built-in command \f5bg\fP +allows you to continue a job in the background, +while the built-in command \f5fg\fP +allows you to continue a job in the foreground even +though you may have started it in the background. +.P +A job being run in the background will stop if it tries +to read from the terminal. +It is also possible to stop background jobs that try to write on +the terminal by setting the terminal options +appropriately. +.P +There is a built-in command \f5jobs\fP +that lists the status of all running and stopped jobs. +In addition, +you are informed of the change of state (running or stopped) +of any background +jobs just before each prompt. +If you want to be notified about background job completions +as soon as they occur without waiting for a prompt, then use the +.B notify +option. +When you try to exit the shell while jobs are stopped or running, +you will receive a message from \f5ksh\fP. +If you ignore this message and try to exit again, +all stopped processes will be terminated. +In addition, for login shells, the +\fB\s-1HUP\s+1\fP +signal will be sent to +all background jobs +unless the job has been disowned with the +.B disown +command. +.P +A built-in version of \f5kill\fP +makes it possible to use +.I job +numbers as targets for signals. +Signals can be selected by number or name. +The name of the signal is the name found in the +.I include +file +.B /usr/include/sys/signal.h +with the prefix +.B \s-1SIG\s+1 +removed. +The +.B \-l +option of \f5kill\fP +provides a means to map individual signal names to and from +signal number. +In addition, if no signal name or number is given, +\f5kill\ -l\fP +generates a list of valid signal names. +.H 2 "Changing Directories" +By default, +\f5ksh\fP +maintains a logical view of the file system hierarchy +which makes symbolic links transparent. +For systems that have symbolic links, +this means that if \f5/bin\fP is a symbolic link to \f5/usr/bin\fP +and you change directory to \f5/bin\fP, \f5pwd\fP will indicate +that you are in \f5/bin\fP, not \f5/usr/bin\fP. +\f5pwd\ -P\fP +generates the physical pathname of the present working +directory by resolving all the symbolic links. +By default, +the \f5cd\fP +command will take you where you expect to go even if you cross +symbolic links. +A subsequent \f5cd\ ..\fP in the example above +will place you in \f5/\fP, not \f5/usr\fP. +On systems with symbolic links, +\f5cd\ -P\fP +causes +.B .. +to be treated physically. +.P +\f5ksh\fP remembers your last directory +in the variable +\fB\s-1OLDPWD\s+1\fP. +The \f5cd\fP +built-in can be given with argument +.B \- +to return to the previous directory +and print the name of the directory. +Note that \f5cd\ -\fP +done twice returns you to the starting directory, +not the second previous directory. +A directory +.I stack +manager has been written as shell +.I functions +to +.I push +and +.I pop +directories from the stack. +.H 2 "Prompts" +.P +When \f5ksh\fP +reads commands from a terminal, +it issues a prompt whenever it is ready +to accept more input and then +waits for the user to respond. +The +\fB\s-1TMOUT\s+1\fP +variable +can be set to be the number of seconds that the shell will wait for +input before terminating. +A 60 second warning message is printed +before terminating. +.P +The shell uses two prompts. +The primary prompt, +defined by the value of the +\fB\s-1PS1\s+1\fP +variable, +is issued at the start of each command. +The secondary prompt, +defined by the value of the +\fB\s-1PS2\s+1\fP +variable, +is issued when more input is needed to complete a command. +.P +\f5ksh\fP allows the user to specify a list of files or directories +to check before issuing the +\fB\s-1PS1\s+1\fP +prompt. +The variable +\fB\s-1MAILPATH\s+1\fP +is a colon ( +.B : +) separated list of file names to be checked for changes +periodically. The user is notified +before the next prompt. +Each of the names in this list can be followed by a +.B ? +and a message to be given when a change has been detected in the file. +The prompt will be evaluated for parameter expansion, command +substitution and arithmetic expansion which are described later. +The parameter +.B $_ +within a mail message will evaluate to the name of the file that +has changed. +The parameter +\fB\s-1MAILCHECK\s+1\fP +is used to specify the minimal interval in seconds before +new mail is checked for. +.P +In addition to replacing each +.B ! +in the prompt with the command number, +\f5ksh\fP expands +the value of the +.B \s-1PS1\s+1 +variable +for parameter expansions, arithmetic expansions, +and command substitutions as described below +to generate the prompt. +The expansion characters that are to be applied when +the prompt is issued must be quoted to prevent the +expansions from occurring when assigning the value to +.B \s-1PS1\s+1. +For example, +\f3\s-1PS1\s+1="$\s-1PWD\s+1"\fP +causes +.B \s-1PS1\s+1 +to be set to the value of +.B \s-1PWD\s+1 +at the time of the assignment whereas +.B \s-1PS1\s+1='$\s-1PWD\s+1' +causes +.B \s-1PWD\s+1 +to be expanded at the time the prompt is issued. +.P +Command substitution may require a separate process +to execute and cause the prompt display to be somewhat +slow, especially +when the return key is pressed several times in a row. +Therefore, its use +within +.B \s-1PS1\s+1 +is discouraged. +Some variables are maintained by \f5ksh\fP +so that their values can be used with +.B \s-1PS1\s+1. +The +.B \s-1PWD\s+1 +variable stores the pathname of the current working directory. +The value of +.B \s-1SECONDS\s+1 +variable +is the value of the most +recent assignment plus the elapsed time. +By default, the time is measured in milli-seconds, +but since +.B \s-1SECONDS\s+1 +is a floating point variable, the +number of places after the decimal point in the expanded +value can be +specified with +\f5typeset\ -F\fP\fIplaces\fP\f5\ SECONDS\fP. +In a roundabout way, this variable +can be used to generate a time stamp into the +.B \s-1PS1\s+1 +prompt without creating a process at each prompt. +The following code explains how you can do this on +System V. On BSD, you need a different command to initialize +the +.B \s-1SECONDS\s+1 +variable. +\f5 +.sp +.nf +.in .5i +# . this script and use $TIME as part of your PS1 string to +# get the time of day in your prompt +typeset -RZ2 _x1 _x2 _x3 +(( SECONDS=$(date '+3600*%H+60*%M+%S') )) +_s='_x1=(SECONDS/3600)%24,_x2=(SECONDS/60)%60,_x3=SECONDS%60,0' +TIME='"${_d[_s]}$_x1:$_x2:$_x3"' +# PS1=${TIME}whatever +.fi +.ta +.in +.sp +\fP +.H 2 "Tilde substitution" +.P +The character +.B \(ap +at the beginning of a word has special meaning to \f5ksh\fP. +If the characters after the +.B \(ap +up to a +.B / +match a user login name in the password database, then the +.B \(ap +and the name are replaced by +that user's login directory. +If no match is found, the original word +is unchanged. +A +.B \(ap +by itself, or in front of a +.BR / , +is replaced by the value of the +\fB\s-1HOME\s+1\fP +parameter. +A +.B \(ap +followed by a +.B + +or +.B \- +is replaced by the value of +.B $\s-1PWD\s+1 +or +.B $\s-1OLDPWD\s+1 +respectively. +.H 2 "Output formats" +The output of built-in commands and traces have values quoted so that they +can be re-input to the shell. +This makes it easy to cut and paste shell output on systems +which use a pointing device such as a mouse. +In addition, output can be saved in a file for reuse. +.P +.H 2 "The \fB\s-1ENV\s+1\fP file" +When an interactive \f5ksh\fP starts, it evaluates the +.B $\s-1ENV\s+1 +variable to arrive at a file name. +If this value is not null, +\f5ksh\fP attempts to read and process +commands in a file by this name. +Earlier versions of \f5ksh\fP read the \fB\s-1ENV\s+1\fP file +for all invocations of the shell primarily to allow +function definitions to be available for all shell +invocations. +The function search path, \fB\s-1FPATH\s+1\fP, described later, +eliminated the primary need for this capability and it was +removed because the high performance cost was no longer +deemed acceptable. +.H 1 "PROGRAMMING LANGUAGE" +The KornShell vastly extends the set of applications that +can be implemented efficiently at the shell level. +It does this by providing simple yet powerful mechanisms +to perform arithmetic, pattern matching, +substring generation, +and arrays. +Users can write applications as separate functions that can +be defined in the same file or in a library of functions +stored in a directory and loaded on demand. +.H 2 "String Processing" +The shell is primarily a string processing language. +By default, variables hold variable length strings. +There are no limits to the length of strings. Storage +management is handled by the shell automatically. +Declarations are not required. +With most programming languages, string constants are designated +by enclosing characters in single quotes or double quotes. +Since most of the words in the language are strings, the shell +requires quotes only when a string contains characters that +are normally processed specially by the shell, but their +literal meaning is intended. +However, since the shell is a string processing language, +and some characters can occur as literals and as language metacharacters, +quoting is an important part of the language. +.P +There are four quoting mechanisms in \f5ksh\fP. +The simplest is to enclose a sequence of characters inside single quotes. +All characters between a pair of single quotes have their literal meaning; +the single quote itself cannot appear. +A +.B $ +immediately preceding +a single quoted string +causes all the characters until the matching single quote +to be interpreted as an ANSI-C language string. +Thus, \f5'\en'\fP represents characters \f5\e\fP and +\f5n\fP, whereas, \f5$'\en'\fP +represents the new-line character. +Double quoted strings remove the special meaning of all characters +except +.BR $ , +.BR \(ga , +and +.BR \e , +so that parameter expansion and command substitution (defined below) +are performed. +The final mechanism for quoting a character is by preceding it with the +escape character +.BR \e\^ . +This mechanism works outside of quoted strings and for the characters +.BR $ , +.BR \(ga , +\fB"\fP, +and +.B \e +in double quoted strings. +.P +Variables are designated by +one or more +strings of alphanumeric +characters beginning with an alphabetic character +separated by a \fB\s+2.\s-2\fP. +Upper and lower case characters are distinct, so that the variable +.B A +and +.B a +are names of different variables. +There is no +limit to the length of the name of a variable. +You do not have to declare variables. +You can assign a value to a variable by writing the name of the +variable, followed by an equal sign, followed by a character string +that represents its value. +To create a variable whose name +contains a \fB\s+2.\s-2\fP, +the variable whose name consists of +the characters before the last \fB\s+2.\s-2\fP +must already exist. +You reference a variable by +putting the name inside curly braces and +preceding the braces with a dollar sign. +The braces may be omitted when the name +is alphanumeric. +If \f5x\fP and \f5y\fP +are two shell variables, then +to define a new variable, +\f5z\fP, +whose value is +the concatenation of the values of +\f5x\fP and \f5y\fP, +you just say +\f5z=$x$y\fP. +It is that easy. +.P +The +.B $ +can be thought of as meaning +"value of." +You can also capture the output of any command with the notation +.BI $( command ) . +This is referred to as command substitution. +For example, +\f5x=$(date)\fP +assigns the output from the \f5date\fP +command to the variable \f5x\fP. +Command substitution in the +Bourne shell is denoted by enclosing the command between +backquotes, +(\fB\(ga\^\(ga\fP). +This notation +suffers from some +complicated quoting rules. +Thus, it is hard to write \f5sed\fP +patterns which contains back slashes within command substitution. +Putting the pattern in single quotes +is of little help. +\f5ksh\fP accepts the Bourne shell command substitution syntax +for backward compatibility. +The +.BI $( command ) +notation allows +the \fIcommand\fP itself to contain quoted strings even if the substitution +occurs within double quotes. Nesting is legal. +.P +The special command substitution of the form +\f5$(cat\ file)\fP +can be replaced by +\f5$(<\ file)\fP, +which is faster because +the \f5cat\fP +command doesn't have to run. +.H 2 "Shell Parameters and Variables" +.P +There are three types of parameters used by \f5ksh\fP, +special parameters, positional parameters, and named +parameters which are called variables. +\f5ksh\fP defines the same special parameters, +.BR 0 , +.BR * , +.BR @ , +.BR # , +.BR ? , +.BR $ , +.BR ! , +and +.BR \- , +as in the Bourne shell. +.P +Positional parameters are set when the shell is invoked, +as arguments to the \f5set\fP built-in, +and by calls to functions (see below) and \fB\s+2.\s-2\fP +procedures. +They are named by numbers starting at 1. +.P +The third type of parameter is a variable. +As mentioned earlier, +\f5ksh\fP uses variables whose names +consist of one or more +alpha-numeric strings separated by a \fB\s+2.\s-2\fP. +There is no need to specify the +.I type +of a variable in the shell because, by default, +variables store strings of arbitrary length +and values will automatically be converted to numbers +when used in an arithmetic context. +However, \f5ksh\fP variables +can have one or more +.I attributes +that control the internal representation of the variable, +the way the variable is printed, and its access or +scope. +In addition, +\f5ksh\fP +allows variables to represent arrays of values +and references to other variables. +The \f5typeset\fP +built-in command of \f5ksh\fP +assigns attributes to variables. +Two of the attributes, +.I readonly +and +.IR export , +are available in the Bourne shell. +Most of the remaining attributes are discussed here. +The complete list of attributes appears in the manual. +The \f5unset\fP +built-in of \f5ksh\fP removes +values and attributes of variables. +When a variable is exported, certain of its attributes are also exported. +.P +Whenever a value is assigned to a variable, +the value is transformed according to the attributes of the variable. +Changing the attribute of a variable can change its value. +The attributes +.B \-L +and +.B \-R +are for left and right field justification respectively. +They are useful for aligning columns in a report. +For each of these attributes, a width can be defined explicitly or else +it is defined the first time an assignment is made to the variable. +Each assignment causes justification of the field, truncating +if necessary. +Assignment to fixed sized variables +provides one way to generate a substring consisting of +a fixed number of characters from +the beginning or end of a string. +Other methods are discussed later. +.P +The attributes +.B \-u +and +.B \-l +are used for upper case and lower case +formatting, respectively. +Since it makes no sense to have both attributes on simultaneously, +turning on either of these attributes turns the other off. +The following script, +using \f5read\fP and \f5print\fP which are described later, +provides an example of the use of shell variables +with attributes. +This script reads a file of lines each consisting of five fields separated by +.B : +and prints fields 4 and 2 in upper case in columns 1-15, left justified, +and columns 20-25 right-justified respectively. +.sp +.nf +.in .5i +.ta 3.4i +\f5typeset -uL15 f4 # 15 character left justified +typeset -uR6 f2 # 6 character right justified +IFS=: # set field separator to : +while read -r f1 f2 f3 f4 f5 # read line, split into fields +do print -r -- "$f4 $f2" # print fields 4 and 2 +done\fP +.fi +.ta +.in +.sp +.P +The +.BR \-i , +.BR \-E , +and +.BR \-F , +attributes are used to represent numbers. +Each can be followed by a decimal number. +The +.B \-i +attribute causes the value to be represented as an integer and it +can be followed by a number representing the numeric base when expanding +its value. +Whenever a value is assigned to an integer variable, it is evaluated +as an arithmetic expression +and then truncated to an integer. +.P +The +.B \-E +attribute causes the value to be represented in scientific +notation whenever its value is expanded. The number following the +.B \-E +determines the number of significant figures, and defaults to 6. +The +.B \-F +attribute causes the value to be represented with a fixed number +of places after the decimal point. +Assignments to variables with the +.B \-E +or +.B \-F +attributes cause the evaluation of the right hand side of the assignment. +.P +\f5ksh\fP allows one-dimensional +.I arrays +in addition to simple variables. +There are two types of arrays; associative arrays +and indexed arrays. +The subscript for an associative array is an arbitrary +string, whereas the subscript for an indexed array is +an arithmetic expression that is evaluated to yield an integer +index. +Any variable can become an indexed array +by referring to it with +an integer +.IR subscript . +All elements of an array need not exist. +Subscripts for arrays +must evaluate to an +integer between 0 and some maximum value, otherwise +an error results. +The maximum value may vary from one machine to another but +is at least 4095. +Evaluation of subscripts is described in +the next section. +Attributes apply to the whole array. +.P +Assignments to array variables can be made to individual elements +via parameter +assignment commands or the +.B typeset +built-in. +Additionally, values can be assigned sequentially with +compound assignment as described below, or by the +.B \-A +.I name +option of the \f5set\fP command. +Referencing of subscripted variables requires the character +.BR $ , +but also requires braces around the array element name. +The braces are needed to avoid conflicts with the +file name generation mechanism. +The form of any array element reference is: +.ce +.BI ${ name [ subscript ]} +Subscript values of +.B * +and +.B @ +can be used to generate all elements of an array, +as they are used for expansion of positional parameters. +The list of currently defined subscripts for a given +variable can be generated with +.BI ${! name [@]} , +or +.BI ${! name [*]} . +.P +The +.B \-n +or +.I nameref +attribute causes the variable to be treated +as a reference to the variable defined by its value. +Once this attribute is set, all references to this variable +become references to the variable named by the value +of this variable. +For example, if \f5foo=bar\fP, then setting the reference +attribute on \f5foo\fP will cause all subsequent references +to \f5foo\fP to behave as the variable whose name is \f5$foo\fP +was referenced, which in this case is the variable \f5bar\fP. +Unsetting this attribute breaks the association. +Reference variables are usually used inside functions whose +arguments are the names of shell variables. +The names for reference variables cannot contain a \fB\s+2.\s-2\fP. +Whenever a shell variable is referenced, the portion of the +variable up to the first \fB\s+2.\s-2\fP +is checked to see whether it matches the name of a reference +variable. +If it does, then the name of the variable actually used +consists of the concatenation of the name of the variable +defined by the reference plus the remaining portion of the +original variable name. +For example, using the predefined alias, \f5alias\ nameref='typeset\ -n'\fP, +.sp +.nf +.in .5i +.ta 3.4i +\f5\^.bar.home.bam="hello world" +nameref foo=.bar.home +print ${foo.bam} +\fBhello world\fP\fP +.fi +.ta +.in +.sp +.H 2 "Compound Assignment" +Compound assignments are used to assign values to arrays +and compound data structures. +The syntax for a compound assignment is +.IB name =( assignment-list ) +where +\fIname\fP +is the name of the variable to which you want to assign values. +No space is permitted between the variable name and the \fB=\fP +but can appear between the \fB=\fP and the open parenthesis. +New-lines can appear between the parentheses. +.P +The \fIassignment-list\fP can be in several different forms +yielding different results. +If \fIassignment-list\fP is simply a list of words, then +the words are processed as they are with the \f5for\fP command +and assigned sequentially as an indexed array. +For example, +.ce +\f5foo=( * )\fP +creates an indexed array \f5foo\fP and assigns the +file names in the current directory to each index starting +at zero. +.P +The second form for \fIassignment-list\fP is a list of assignments +of the special form \fB[\fP\fIword\fP\fB]=\fP\fIword\fP. +No space is permitted before or after the \fB=\fP. +In this case, the variable given by \fIname\fP becomes +an associative array with the given arguments as subscripts. +For example, +.ce +\f5bar=( [color]=red [shape]=box )\fP +creates an associate array named \f5bar\fP whose +subscripts are \f5color\fP and \f5shape\fP. +.P +The third form for \fIassignment-list\fP is a list of +normal assignments, including compound assignments. +These assignments cause sub-variables to be assigned +corresponding to the given assignments. +In addition to assignments, the \fIassignment-list\fP +can contain \f5typeset\fP commands. +In addition to creating sub-variables, +the effect of a compound assignment is to make +the value of the original variable be a parenthesized +assignment list of its components. +For example, the assignment +.sp +.nf +.in .5i +.ta 3.4i +\f5foo=( + left=bar + typeset -i count=3 + point=( + x=50 + y=60 + ) + colors=( red green yellow ) + right=bam +) \fP +.ta +.in +.fi +.sp +is equivalent to the assignments +.sp +.nf +.in .5i +.ta 3.4i +\f5foo.left=bar +foo.count=3 +foo.point.x=50 +foo.point.y=60 +foo.colors=( red green yellow ) +foo.right=bam\fP +.ta +.in +.fi +.sp +In addition, the value of \f5"$foo"\fP is +.sp +.nf +.in .5i +.ta 3.4i +\f5( + colors=( red green yellow ) + left=bar + typeset -i count=3 + point=( + y=60 + x=50 + ) + right=bam +)\fP +.ta +.in +.fi +.sp +.H 2 "Substring Generation" +The expansion of a variable or parameter can be modified so that +only a portion of the value results. +It is often necessary to extract a portion of a shell variable or +a portion of an array. +There are several parameter expansion operators that can do this. +One method to generate a substring is with an expansion of +the form \fB${\fP\fIname\fP\fB:\fP\fIoffset\fP\fB:\fP\fIlength\fP\fB}\fP +where \fIoffset\^\fP is an arithmetic expression that defines the +offset of the first character starting from 0, and +\fIlength\^\fP is an arithmetic expression that defines the +length of the substring. +If +.BI : length\^ +is omitted, +the length of the value of +.I name\^ +starting at +.I offset\^ +is used. +The +.BI : offset : length +operators can also be applied to array expansions and to parameters +.B * +and +.B @ +to generate portions of an array. +For example, the expansion, \fB${\fP\fIname\fP\fB[@]:\fP\fIoffset\fP\fB:\fP\fIlength\fP\fB}\fP, yields up to \fIlength\fP elements of the array \fIname\fP +starting at the element \fIoffset\fP. +.P +The other parameter expansion modifiers use shell patterns +to describe portions of the string to modify and delete. +A description of shell patterns is contained below. +When these +modifiers are applied to special parameters +.B @ +and +.B * +or to array parameters given as +\fIname\fP\fB[@]\fP or \fIname\fP\fB[*]\fP, +the operation is performed on each element. +There are four parameter expansion modifiers that +strip off leading and trailing substrings +during parameter expansion +by removing the characters matching a given pattern. +An expansion of +the form \fB${\fP\fIname\fP\fB#\fP\fIpattern\fP\fB}\fP +causes the smallest matching prefix of the value of +.I name\^ +to be removed. +The largest prefix matching +.I pattern\^ +is removed by using +.B ## +instead of +.BR # . +Similarly, +an expansion of +the form \fB${\fP\fIname\fP\fB%\fP\fIpattern\fP\fB}\fP +causes the smallest matching substring at the end of +.I name\^ +to be removed. +Again, using +.B %% +instead of +.BR % , +causes the largest matching trailing substring to be deleted. +For example, if the shell variable +.B file +has value +.BR foo.c , +then the expression +.B ${file%.c}.o +has value +.BR foo.o . +.P +The value of an expansion can be changed by +specifying a pattern that matches the part that needs to be changed +after the +the parameter expansion modifier +.BR / . +An expansion of the form +\fB${\fP\fIname\fP\fB/\fP\fIpattern\fP\fB/\fP\fIstring\fP\fB}\fP +replaces the first match of \fIpattern\fP with +the value of variable \fIname\fP to \fIstring\fP. +The second +.B / +is not necessary when \fIstring\fP is null. +The expansion +\fB${\fP\fIname\fP\fB//\fP\fIpattern\fP\fB/\fP\fIstring\fP\fB}\fP +changes all occurrences of the \fIpattern\fP into \fIstring\fP. +The parameter expansion modifiers +.B /# +and +.B /% +cause the matching pattern to be anchored to the beginning and +end respectively. +.P +Finally, there are parameter expansion modifiers that yield +the name of the variable, the string length of the value, or the number +of elements of an array. +\fB${!\fP\fIname\fP\fB}\fP +yields the name of the variable which will be \fIname\fP itself +except when \fIname\fP is a reference variable. In this case +it will yield the name of the variable it refers to. +When applied to an array variable, +\fB${!\fP\fIname\fP\fB[@]}\fP and +\fB${!\fP\fIname\fP\fB[*]}\fP +generate the names of all subscripts. +\fB${#\fP\fIname\fP\fB}\fP +will be the length in bytes of +\fB$\fP\fIname\fP. +For an array variable +\fB${#\fP\fIname\fP\fB[*]}\fP +gives the number of elements in the array. +.H 2 "Arithmetic Evaluation" +.P +For the most part, the shell is a string processing +language. However, the need for arithmetic has +long been obvious. +Many of the characters that are special to the +Bourne shell are needed as arithmetic operators. +To make arithmetic easy to use, and to maintain +compatibility with the Bourne shell, \f5ksh\fP uses matching +.B (( +and +.B )) +to delineate arithmetic expressions. +While single parentheses might have been +more desirable, these already mean +.I subshell\^ +so that another notation was required. +The arithmetic expression +inside the double parentheses +follows the same syntax, associativity and precedence +as the ANSI-C\*(Rf +.RS +American National Standard for Information Systems \- Programming +Language \- C, ANSI X3.159-1989. +.RF +programming language. +The characters between the matching double parentheses +are processed with the same rules used for double +quotes so that spaces can be used to aid readability +without additional quoting. +.P +All arithmetic evaluations are performed using +double precision floating point arithmetic. +Floating point constants follow the same rules as +the ANSI-C programming language. +Integer arithmetic constants are written as +.ce +.IB base # number, +where +.I base\^ +is a decimal integer between +two and sixty-four and +.I number\^ +is any non-negative number. +Base ten is used +when no base is specified. +The digits are represented by the characters +.BR 0-9a-zA-Z_@ . +For bases less than or equal to 36, +upper and lower case characters can +be used interchangeably to represent the digits +from 10 thru 35. +.P +Arithmetic expressions are made from constants, +variables, and operators. +Parentheses may be used for grouping. +The contents inside the double parentheses +are processed with the same expansions as occurs in a double quoted string, +so that all +.B $ +expansions are performed before the expression is evaluated. +However, there is usually no need to use the +.B $ +to get the value of a variable +because the arithmetic evaluator replaces the name of the variable +by its value within an arithmetic expression. +The +.B $ +cannot be used when the variable is the subject of assignment +or an increment operation. +As a rule it is better not to use +.B $ +in front of variables in an arithmetic expression. +.P +An arithmetic command of the form +.B +(( ... )) +.R +is a command that evaluates the enclosed arithmetic expression. +For example, the command +.ce +\f5(( x++ ))\fP +can be used to +increment the variable \f5x\fP, +assuming that \f5x\fP contains some numerical value. +The arithmetic command is true (return value 0), when the resulting +expression is non-zero, and false (return value 1) when the +expression evaluates to zero. +This makes the command easy to use with the \f5if\fP and \f5while\fP +compound commands. +.P +The \f5for\fP compound command +has been extended for use in arithmetic contexts. +The syntax, +.ce +\f5for\fP \fB((\fP \fIexpr1\fP\fB;\fP \fIexpr2\fP \fB;\fP \fIexpr3 \fP\fB))\fP +can be used as the first line of a \f5for\fP loop with the same semantics +as the \f5for\fP statement in the ANSI-C programming language. +.P +Arithmetic evaluations can also be performed as part of the evaluation +of a command line. +The syntax +.B +$((\ ...\ )) +.R +expands to the value of the enclosed arithmetic expression. +This expansion can occur wherever parameter expansion is performed. +For example using the \f5ksh\fP command \f5print\fP (described +later) +.ce +\f5print $((2+2))\fP +prints the number 4. +.P +The following script prints the first +.I n +lines of its standard input onto its standard output, +where +.I n +can be supplied as an optional argument whose default value is 20. +.sp +.nf +.in .5i +.ta 4i +\f5integer n=${1-20} # set n +while (( n-- >=0 )) && read -r line # at most n lines +do print -r -- "$line" +done\fP +.fi +.ta +.in +.sp +.H 2 "Shell Expansions" +.P +The commands you enter from the terminal or from a script +are divided into words and each word undergoes several +expansions to generate the command name and its arguments. +This is done in two phases. +The first phase recognizes reserved words, spaces and operators +to decide where command boundaries lie. +Alias substitutions take place during this phase. +The second phase performs expansions in the following order: +.BL +.LI +Tilde substitution, +parameter expansion, +arithmetic expansion, +and command substitution +are performed from left to right. +The option +.B \-u +or +.BR nounset , +will cause an error to occur when any variable +that is not set is expanded. +.LI +The characters that result from parameter expansion and +command substitution above are checked with the characters +in the +\fB\s-1IFS\s+1\fP variable +for possible +field splitting. +(See a description of \f5read\fP below to see how +\fB\s-1IFS\s+1\fP is used.) +Setting +\fB\s-1IFS\s+1\fP to a null +value causes field splitting to be skipped. +.LI +Pathname generation (as described below) +is performed on each of the fields. +Any field that doesn't match a pathname is left alone. +The option, +.B \-f +or +.BR noglob , +is used to disable pathname generation. +.LE +.H 2 "Pattern Matching" +The shell is primarily a string processing language and uses +patterns for matching file names as well as for matching strings. +The characters +.BR ? , +.BR * , +and +.B [ +are processed specially +by the shell when not quoted. +These characters are used to form patterns that +match strings. +Patterns are used by the shell to match pathnames, +to specify substrings, +and for +.B case +commands. +The character +.B ? +matches any one character. +The character +.B * +matches zero or more characters. +The character sequence +.BR [ ... ] +defines a character class +that matches any character contained within +.BR [\^] . +A range of characters can be specified by putting a +.B \- +between the first and last character of the range. +An exclamation mark, +.BR ! , +immediately after the +.BR [ , +means match all characters except the characters specified. +For example, the pattern +\f5a?c*.[!a-z]\fP +matches any string beginning with an +.BR a , +whose third character is a +.BR c , +and that ends in +.B . +(dot) followed by any character except the lower case letters, +.BR a\-z . +The sequence \f5[:alpha:]\fP +inside a character class, matches any set of characters in +the ANSI-C +.B alpha +class. +Similarly, \f5[:\fP\fIclass\fP\f5:]\fP matches +each of the characters in the given \fIclass\fP +for all the ANSI-C character classes. +For example, \f5[[:alnum:]_]\fP +matches any alpha-numeric character or the character +.BR _ . +.P +\f5ksh\fP +treats +strings of the form +.BI ( pattern-list +.BR ) , +where +.I pattern-list +is a list of one or more patterns separated by a +.BR \(bv , +specially when preceded by +.BR * , +.BR ? , +.BR + , +.BR @ , +or +.BR ! . +A +.B ? +preceding +.BI ( pattern-list ) +means that the pattern list enclosed in +.B (\^) +is optional. +An +.BI @( pattern-list ) +matches any pattern in the list of patterns enclosed in +.BR () . +A +.BI *( pattern-list ) +matches any string that contains zero or more of each of the enclosed +patterns, +whereas +.BI +( pattern-list ) +requires a match of one or more of any of the given patterns. +For instance, the pattern +.B +([0\-9])?(.) +matches one or more digits optionally followed by a +.BR . (dot). +A +.BI !( pattern-list ) +matches anything except any of the given patterns. +For example, +\f5print\ !(*.o)\fP +displays all file names in the current directory that do not end in +.BR .o . +.P +When patterns are used to generate pathnames when expanding +commands several other rules apply. +A separate match is made +for each file name component of the pathname. +Read permission is required for +any portion of the pathname that contains any special +pattern character. +Search permission is required for every component except +possibly the last. +.P +By default, +file names in each directory that begin with \fB\s+2.\s-2\fP +are skipped when performing a match. +If the pattern to be matched starts with a leading \fB\s+2.\s-2\fP, +then only files beginning with a \fB\s+2.\s-2\fP, +are examined when reading each directory to find matching files. +If the +\fB\s-1FIGNORE\s+1\fP variable +is set, +then only files that do not match this pattern +are considered. +This overrides the special meaning of \fB\s+2.\s-2\fP +in a pattern and in a file name. +.P +If the +.B markdirs +option is set, +each matching pathname that is the name +of a directory has a trailing +.B / +appended to the name. +.P +.H 2 "Conditional Expressions" +The Bourne shell uses the \f5test\fP +command, or the equivalent \f5[\fP +command, to test files for attributes +and to compare strings or numbers. +The problem with \f5test\fP +is that the shell has expanded the words of the \f5test\fP +command and +split them into arguments before \f5test\fP begins execution. +\f5test\fP +cannot distinguish between operators and operands. +In most cases +\f5test\ "$1"\fP +will test whether argument 1 is non-null. +However, +if argument 1 is +.BR \-f , +then \f5test\fP +will treat +.B \-f +as an operator and +yield a syntax error. +One of the most frequent errors with +\f5test\fP +occurs when its operands are not within double quotes. +In this case, the argument may expand to more than a single +argument or to no argument at all. In either case this +will likely cause a syntax error. +What makes this most insidious is that these errors are frequently +data dependent. A script that appears to run correctly may abort +if given unexpected data. +.P +To get around these problems, +\f5ksh\fP +has a compound command for conditional expression testing +as part of the language. +The reserved words +.B [[ +and +.B ]] +delimit the range of the command. +Because they are reserved words, not operator characters, +they require spaces to separate them +from arguments. +The words between +.B [[ +and +.B ]] +are not processed for field splitting or for pathname generation. +In addition, since \f5ksh\fP +determines the operators before parameter expansion, +expansions that yield no argument cause no problem. +The operators within +.BR [[ ... ]] +are almost the same as those for the \f5test\fP +command. +All unary operators are of the form +.BI \- letter +and are followed by a single operand. +Instead of +.B \-a +and +.BR \-o , +.BR [[ ... ]] +uses +.B && +and +.B \(bv\(bv +to indicate "and" and "or". +Parentheses are used without quoting for grouping. +.P +The right hand side of the string comparison operators +.B == +and +.B != +takes a pattern and tests whether the left hand operand +matches this pattern. Quoting the pattern results +is a string comparison rather than the pattern match. +The operators +.B < +and +.B > +within +.BR [[ ... ]] +designate lexicographical comparison. +.P +In addition there are several other new comparison primitives. +The binary operators +.B \-ot +and +.B \-nt +compare the modification times +of two files to see which file is +.I "older than" +or +.I "newer than" +the other. +The binary operator +.B \-ef +tests whether two files +have the same device and i-node number, +i.\ e., a link to the same file. +.P +The unary operator +.B \-L +returns true if its operand is a symbolic link. +The unary operator +.B \-O +(\fB\-G\fP) +returns true if the owner (or group) of the file operand matches +that of the caller. +The unary operator +.B \-o +returns true when its operand is the name of an option that is +currently on. +.P +The following script illustrates some of the uses of +.BR [[ ... ]] . +The reference manual contains the complete list of operators. +.sp +.nf +.in .5i +.ta 4i +\f5for i +do # execute foo for numeric directory + if [[ \-d $i && $i == +([0\-9]) ]] + then foo + # otherwise if writable or executable file and not mine + elif [[ (\-w $i\(bv\(bv\-x $i) && ! \-O $i ]] + then bar + fi +done\fP +.fi +.ta +.in +.sp +.H 2 "Input and Output" +\f5ksh\fP has +extended I/O capabilities to enhance the +use of the shell as a programming language. +As with the Bourne shell, +you use the I/O redirection operator, +.BR < , +to control where input comes from, +and the I/O redirection operator, +.BR > , +to control where output goes to. +Each of these operators can be preceded with a single digit that +specifies a file unit number to associate with the file stream. +Ordinarily you specify these I/O redirection operators with a specific +command to which it applies. +However, if you specify I/O redirections with the \f5exec\fP +command, +and don't specify arguments to \f5exec\fP, +then the I/O redirection applies to the current program. +For example, the command +\f5exec\ <\ foobar\fP +opens file \f5foobar\fP +for reading. +The \f5exec\fP +command is also used to close files. +A file descriptor unit can be opened as a copy of an existing +file descriptor unit by using either of the +.B <& +or +.B >& +operators and putting the file descriptor unit of the original file +after the +.BR & . +Thus, \f52>&1\fP means open standard error (file descriptor 2) +as a copy of standard output (file descriptor 1). +A file descriptor value of +.B \- +after the +.B & +indicates that the file should be closed. +To close file unit 5, specify +\f5exec\ 5<&-\fP. +There are two additional redirection operators with \f5ksh\fP +and the POSIX shell that are not part of the Bourne shell. +The +.B >| +operator overrides the effect of the +.B noclobber +option described earlier. +The +.B <\^> +operator causes a file to be opened for both reading and writing. +.P +\f5ksh\fP recognizes certain pathnames and treats them +specially. +Pathnames of the form +.BI /dev/fd/ n\^ +are treated as equivalent to the file defined by file descriptor +.IR n . +These name can be used as the script argument to \f5ksh\fP +and in conditional testing as described above. +On underlying systems that support +.B /dev/fd +in the file system, these names can be passed to other commands. +Pathnames of the form +.BI /dev/tcp/ hostid / port +and +.BI /dev/udp/ hostid / port +can be used to create +.B tcp +and +.B udp +connections to services given by the +.I hostid\^ +number and +.I port\^ +number. +The +.I hostid\^ +cannot use symbolic values. In practice these +numbers are typically generated by command substitution. +For example, +\f5exec\ 5>\ /dev/tcp/$(service\ name)\fP +would open file descriptor 5 for sending messages +to hostid and port number defined by the output of \f5service\ name\fP. +.P +The Bourne shell has a built-in command \f5read\fP +for reading lines from standard input (file descriptor 0) +and splitting it into fields based on the value of the +.B \s-1IFS\s+1 +variable, and a command \f5echo\fP +to write strings to standard output. +(On some systems, \f5echo\fP +is not a built-in command and incurs considerable overhead to use.) +Unfortunately, neither of these commands +is able to perform some very basic tasks. +For example. +with the Bourne shell, +the \f5read\fP +built-in cannot read a single line that ends in +.BR \e . +With \f5ksh\fP +the \f5read\fP +built-in has a +.B \-r +option to remove the special meaning for +.B \e +which allows it to be +treated as a regular +character rather than the line continuation character. +With the Bourne shell, +there is no simple way to have more than one file open +at any time for reading. +\f5ksh\fP has options on the \f5read\fP +command to specify the file +descriptor for the input. +The fields that are read from a line can be stored into an indexed +array with the +.B \-A +option to read. +This allows a line to be split into an arbitrary number of fields. +.P +The way the Bourne shell uses the +\fB\s-1IFS\s+1\fP variable to +split lines into fields greatly limits its utility. +Often data files consist of lines that use a character such +as +.B : +to delimit fields with two adjacent delimiters that denote +a null field. +The Bourne shell treats adjacent delimiters as a single +field delimiter. +With \f5ksh\fP, +delimiters that are considered white space characters +have the behavior of the Bourne shell, but other +adjacent delimiters separate +null fields. +.P +The \f5read\fP command is often used in scripts that interact +with the user by prompting the user and then requesting some +input. +With the Bourne shell two commands are needed; one to +prompt the user, the other to read the reply. +\f5ksh\fP allows these two commands to be combined. +The first argument of the \f5read\fP +command can be followed by a +.B ? +and a prompt string which is used whenever the input +device is a terminal. +Because the prompt is associated with the \f5read\fP built-in, +the built-in command line editors will be able to re-output +the prompt whenever the line needs to be refreshed when +reading from a terminal device. +.P +With the Bourne shell, +there is no way to set a time limit for waiting for the user +response to read. +The +.B \-t +option to \f5read\fP takes a floating +point argument that gives the time in seconds, +or fractions of seconds that the shell should wait for a reply. +.P +The version of the \f5echo\fP command in System V +treats certain sequences beginning with +.B \e +as control sequences. +This makes it hard to output strings without interpretation. +Most BSD derived systems do not interpret +.B \e +control sequences. +Unfortunately, the BSD versions of \f5echo\fP accepts a +.B \-n +option to prevent a trailing new-line, but has no way to +cause the string +.B \-n +to be printed. +Neither of these versions is adequate. Also, because they +are incompatible, it is very hard to write portable shell scripts +using \f5echo\fP. +The \f5ksh\fP built-in, \f5print\fP, +outputs characters to the terminal or to a file and +subsumes the functions of all versions of \f5echo\fP. +Ordinarily, escape sequences in arguments beginning with +.B \e +are processed the same as for the System V \f5echo\fP command. +However \f5print\fP follows the standard conventions for +options and has options that make \f5print\fP very versatile. +The +.B \-r +option can be used to output the arguments without any special meaning. +The +.B \-n +option can be used here to suppress the trailing new-line +that is ordinarily appended. +As with \f5read\fP, it is possible to specify the file descriptor number +as an option to the command to avoid having to use +redirection operators with each occurrence of the command. +.P +The IEEE POSIX shell and utilities standard committee was unable +to reconcile the differences between the System V and BSD +versions of \f5echo\fP. +They introduced a new command named \f5printf\fP +which takes an ANSI-C format string and a list of options +and outputs the strings using the ANSI-C formatting rules. +Since \f5ksh\fP is POSIX conforming, it accepts \f5printf\fP. +However, there is a +.B \-f +options to \f5print\fP that can be used to specify +a format string which processes the arguments the same way that +\f5printf\fP does. +.P +The format processing for \f5print\fP and \f5printf\fP has +been extended slightly. +There are three additional formatting directives. +The +.B %b +format causes the +.B \e +escape sequences to be expanded as they are with the System V \f5echo\fP +command. +The +.B %q +format causes quotes to +be placed on the output as required +so that it can be used as shell input. +Special characters in the output of most \f5ksh\fP built-in commands +and in the output from an execution trace +are quoted in an equivalent fashion. +The +.B %P +format causes an extended regular expression string to +be converted into a shell pattern. +This is useful for writing shell applications that have +to accept regular expressions as input. +Finally, the escape sequence +.B \e\^E +which expands to the terminal escape character (octal 033) +has been added. +.P +The shell is frequently used as a programming language for +interactive dialogues. +The +\f5select\fP +statement has been added to the language +to make it easier to +present menu selection alternatives to the +user and evaluate the reply. +The list of alternatives is numbered and put in columns. +A user settable prompt, +\fB\s-1PS3\s+1\fP, +is issued and if the answer is +a number corresponding to one of the alternatives, +the select loop variable is set to this value. +In any case, the +.B \s-1REPLY\s+1 +variable is used to store the user entered reply. +The shell variables +.B \s-1LINES\s+1 +and +.B \s-1COLUMNS\s+1 +are used to control the layout of select lists. +.H 2 "Option Parsing" +The \f5getopts\fP built-in command can be used +to process command arguments in a manner consistent +with the way \f5ksh\fP does for its own built-in commands. +.P +The \f5getopts\fP built-in allows users to specify options +as separate arguments or to group options that do not +take arguments together. Options that require arguments +do not require space to separate them from the option argument. +The +.B \s-1OPTARG\s+1 +variable stores the value of the option argument +after finding a variable that takes an argument. +The +.B \s-1OPTIND\s+1 +variable holds the index of the current options argument. +After processing options, the arguments should be +shifted by +.B \s-1OPTIND\s+1\-1 +to make the +remaining arguments be \f5"$@"\fP. +.P +The \f5getopts\fP argument description allows additional +information to be specified along with the options +that is used to generate \fIusage\fP messages for +incorrect arguments and for the option argument \fB\-?\fP. +The example in the APPENDIX uses \f5getopts\fP to process +its arguments. +.H 2 "Co-process" +\f5ksh\fP can spawn a +.I co-process +by adding a +.B "|&" +after a command. +This process will be run with its standard input and its +standard output connected to the shell. The built-in command \f5print\fP +with the +.B \-p +option will write into the standard input of this +process and +the built-in command \f5read\fP +with the +.B \-p +option will read from the output of this process. +.P +In addition, the I/O redirection operators \fB<&\fP and \fB>&\fP can +be used to move the input or output pipe of the co-process +to a numbered file descriptor. +Use \f5exec\ 3>&\ p\fP to move the input of the co-process +to file descriptor \fB3\fP. +After you have connected to file descriptor \fB3\fP, you +can direct the output of any command to the co-process +by running \fIcommand\fP\f5\ >&3\fP. +Also, by moving the input of the co-process to a numbered descriptor, +it is possible to run a second co-process. +The output of both co-processes will be the file descriptor +associated with \f5read\ -p\fP. +You can use \f5exec\ 4<&\ p\fP to cause the output of these +co-processes to go to file descriptor \fB4\fP of the shell. +Once you have moved the pipe to descriptor \fB4\fP, it is possible +to connect a server to the co-process by running \fIcommand\fP\f5\ 4<&\ p\fP +or to close the co-process pipe with \f5exec\ 4<&\ -\fP. +.H 2 "Functions" +.P +Function definitions are of the form +.sp +.in +.5i +.nf +\f5function\fP \fIname\fP +.br +.B { +.br + any shell script +.br +.B } +.fi +.sp +.in +A function whose name contains a \fB\s+2.\s-2\fP +is called a \fIdiscipline\fP function. +The portion of the name after the last \fB\s+2.\s-2\fP +is the name of the discipline. +Discipline functions named \f5get\fP, \f5set\fP, and \f5unset\fP +can be assigned to any variable to intercept lookups, +assignments and unsetting of the variable +defined by the portion of the name before the last \fB\s+2.\s-2\fP. +Applications can create additional disciplines for variables +that are created as part of user defined built-ins. +The portion of the name before the last \fB\s+2.\s-2\fP +must refer to the name of an existing variable. +Thus, if \f5p\fP is a reference to \f5PATH\fP, then +the function name \f5p.get\fP and \f5PATH.get\fP +refer to the same function. +.P +The function is invoked either +by specifying +.I name +as the command name +and optionally following it with arguments +or by using it as an option to the \fB\s+2.\s-2\fP +built-in command. +Positional parameters are saved before each +function call and restored when completed. +The arguments that follow the function name on the calling +line become positional parameters inside the function. +The \f5return\fP +built-in can be used to cause the function to return to +the statement following +the point of invocation. +.P +Functions can also be defined with the System V notation, +.sp +.in +.5i +.nf +\fIname\fP \f5()\fP +.br +.B { +.br + any shell script +.br +.B } +.fi +.sp +.in +Functions defined with this syntax cannot be used as the first +argument to a \fB\s+2.\s-2\fP procedure. +\f5ksh\fP accepts this notation for compatibility only. +There is no need to use this notation when writing +\f5ksh\fP scripts. +.P +Functions defined with the \f5function\fP\ \fIname\fP syntax +and invoked by name +are executed in the current shell environment +and can share named variables with the calling program. +Options, other than execution trace +.BR \-x , +set by the calling program are +passed down to a function. +The options are +not shared with +the function so that any options set within a function are +restored when the function exits. +Traps ignored by the caller are ignored within the function +and cannot be enabled. +Traps caught by the calling program are reset to their +default action within the function. +In most instances, the default action is +to cause the function to terminate. +A trap on +\fB\s-1EXIT\s+1\fP +defined within a function executes after the function +completes but +before the caller resumes. +Therefore, +any variable assignments and +any options set as part of a trap action will be effective +after the caller resumes. +.P +By default, variables are inherited by the function and shared +by the calling program. +However, +for functions defined with the \f5function\fP\ \fIname\fP syntax +that are invoked by name, +environment substitutions preceding the function call +apply only to the scope of the function call. +Also, variables whose names do not contain a \fB\s+2.\s-2\fP +that are defined with the \f5typeset\fP +built-in command are local to the function that they are declared in. +Thus, for the function defined +.sp +.nf +.in .5i +\f5function name +{ + typeset -i x=10 + let z=x+y + print $z +}\fP +.fi +.ta +.in +.sp +invoked as +\f5y=13\ name\fP, +\f5x\fP and \f5y\fP +are local variables with respect to the function +\f5name\fP +while +\f5z\fP +is global. +.P +Functions defined with the \fIname\fP\f5()\fP syntax, +and functions invoked as an argument to the \fB\s+2.\s-2\fP +command, +share everything other than positional parameters with the caller. +Assignments that precede the call remain in effect after the +function completes. +.P +Alias and function names are not passed down to shell scripts +or carried across separate +invocations of \f5ksh\fP. +The +.B $\s-1FPATH\s+1 +variable gives a colon separated list of directories that +is searched for function definitions when trying to resolve +the command name. +Whenever a file name contained in +.B $\s-1FPATH\s+1 +is found, the complete file is read and all functions +contained within become defined. +.P +Calls that reference functions can be recursive. +Except for special built-ins, +function names take precedence over built-in names and names +of programs when used as command names. +To write a replacement function that invokes the command that +you wish to replace, +you can use the \f5command\fP built-in command. +The arguments to \f5command\fP are the name and arguments +of the program you want to execute. +For example to write a +.B cd +function which changes the directory and prints out the directory name, +you can write +.sp +.nf +.in .5i +\f5function cd +{ + if command cd "$@" + then print -r -- $PWD + fi +}\fP +.fi +.ta +.in +.sp +.P +The +\fB\s-1FPATH\s+1\fP +variable is a colon separated list that \f5ksh\fP +uses to search for function definitions. +When +\f5ksh\fP +encounters an autoload function, +it runs the +.B . +command on the script containing the function, +and then executes the function. +.P +For interactive shells, +function definitions may also be placed in the +\fB\s-1ENV\s+1\fP +file. +However, this +causes the shell to take longer to begin executing. +.H 2 "Process Substitution" +.P +This feature is only available +on versions of the UNIX operating system which support the +.B /dev/fd +directory for naming open files. +Each command argument of the form +\fB<(\fP\fIlist\^\fP\fB)\fP +or +\fB>(\fP\fIlist\^\fP\fB)\fP +will run process +.I list +asynchronously connected to some file in the +.B /dev/fd +directory. +The name of this file will become the argument to the command. +If the form with +.B > +is selected then writing on this file will provide input for +.IR list . +If +.B < +is used, +then the file passed as an argument will contain the output of the +.I list +process. +For example, +.sp +.nf +.in .5i +\f5paste <(cut \-f1 \fP\fIfile1\fP\f5) <(cut \-f2 \fP\fIfile2\fP\f5) | tee >(\fP\fIprocess1\fP\f5) >(\fP\fIprocess2\fP\f5)\fP +.fi +.ta +.in +.sp +extracts +fields 1 and 3 from +the files +.I file1 +and +.I file2 +respectively, +places the +results side by side, and +sends it +to the processes +.I process1 +and +.IR process2 , +as well as putting it onto the standard output. +Note that the file which is passed as an argument to the command is +a UNIX system +.IR pipe (2) +so that the programs that expect to +.IR lseek (2) +on the file will not work. +.H 2 "Finding Commands" +.P +The addition of aliases, functions, +and more built-ins +has made it substantially more difficult to know what +a given command name really means. +.P +Commands that begin with reserved words +are an integral part of the shell language itself +and typically define the control flow of the language. +Some control flow commands are not reserved words in +the language but are \fIspecial\fP built-ins. +Special built-ins are built-ins that are considered a +part of the language rather than user definable commands. +The best examples of commands that fit this description +are \f5break\fP and \f5continue\fP. +Because they are not reserved words, they can be the +result of shell expansions and are not effected by quoting. +These commands have the following special properties: +.BL +.LI +Assignments that precede them apply to the current shell process, +not just to the given command. +.LI +An error in the format of these commands cause a shell script +or function that contains them to abort. +.LI +They cannot be overridden by shell functions. +.LE +.P +Other commands are built-in because they perform side effects +on the current environment that would be nearly impossible +to implement otherwise. +Built-ins such as \f5cd\fP and \f5read\fP +are examples of such built-ins. +These built-ins are semantically equivalent to commands that +are not built-in except that they don't take a path search +to locate. +.P +A third reason to have a command built-in is so that +it will be unaffected by the setting of the +.B \s-1PATH\s+1 +variable. +The \f5print\fP command fits this category. +Scripts that use \f5print\fP will be portable +to all sites that run \f5ksh\fP. +.P +The final reason for having a command be a built-in is +for performance. +On most systems it is more than an order of magnitude +faster to initiate a command that is built-in than +to create a separate process to run the command. +Examples that fit this category are \f5test\fP +and \f5pwd\fP. +.P +Given a command name \f5ksh\fP decides what it means using +the following order: +.BL +.LI +Reserved words define commands that form part of the shell +grammar. +They cannot be quoted. +.LI +Alias substitutions occur first as part of the reading of commands. +Using quotes in the command name will prevent alias substitutions. +.LI +Special built-ins. +.LI +Functions. +.LI +Commands that are built-in that are not associated with a pathname +such as \f5cd\fP and \f5print\fP. +.LI +If the command name contains a +.BR / , +the program or script corresponding to the given name is executed. +.LI +A path search locates the pathname corresponding to the command. +If the pathname where it is found matches the pathname associated +with a built-in command, the built-in command is executed. +If the directory where the command is found is listed in the +.B \s-1FPATH\s+1 +variable, the file is read into the shell +like a dot script, and a function by that name is invoked. +Once a pathname is found, \f5ksh\fP remembers its location +and only checks relative directories in \fB\s-1PATH\s+1\fP +the next time the command name is used. +Assigning a value to \fB\s-1PATH\s+1\fP +causes \f5ksh\fP to forget the location of all command names. +.LI +The +.B \s-1FPATH\s+1 +variable is searched and files found are treated as described above. +.LE +.P +The first argument of the \f5command\fP built-in, described earlier, +skips the checks for reserved words and for function definitions. +In all other ways, \f5command\fP behaves like a built-in +that is not associated with a pathname. +As a result, if the first argument of \f5command\fP is +a special built-in, the special properties of this built-in +do not apply. +For example, whereas, \f5exec\ 3<\ foo\fP will cause a script containing +it to abort if the open fails, \f5command\ exec\ 3<\ foo\fP +results in a non-zero exit status but does not abort the script. +.P +You can get a complete list of the special built-in commands +with \f5builtin\ -s\fP. +In addition \f5builtin\fP without arguments gives a list of +the current built-ins and the pathname that they are associated with. +A built-in can be bound to another pathname by giving +the pathname for the built-in. The basename of this path must +be the name of an existing built-in for this to succeed. +Specifying the name of the built-in without a pathname causes +this built-in to be found before a path search. +A built-in can be deleted with the \fB\-d\fP option. +.P +On systems with run time loading of libraries, built-in commands +can be added with the \f5builtin\fP command. +Each command that is to be built-in must be written as a +C function whose name is of the form \f5b_\fP\fIname\fP, where +\fIname\fP is the name of the built-in that is to be added. +The function has the same argument calling convention as +\f5main\fP. The lower eight bits of the return value become +the exit status for this built-in. +Builtins are added by specifying the pathname of the library +as an argument to the \fB\-f\fP option of \f5builtin\fP. +.P +The built-in command, +\f5whence\fP, +when used with the +.B \-v +option, tells how a given command is bound. +A line is printed for each argument to \f5whence\fP +telling what would happen if this argument were used as a command name. +It reports on reserved words, aliases, built-ins, and +functions. +If the command is none of the above, +it follows the path search rules and prints the full path-name, +if any, otherwise it prints an error message. +.H 2 "Symbolic Names" +To avoid implementation dependencies, \f5ksh\fP +accepts and generates symbolic names +for built-ins that use numerical values in the Bourne shell. +The +.B \-S +option of the +\f5umask\fP built-in command +accepts and displays +default file creation permissions +symbolically. +It uses the same symbolic notation as the \f5chmod\fP command. +.P +The \f5trap\fP and \f5kill\fP built-in commands +allows the signal names to be given symbolically. +The names of signals and traps +corresponding to signals are the same as the signal name with +the +.B \s-1SIG\s+1 +prefix removed. +The trap +.B 0 +is named +\fB\s-1EXIT\s+1\fP. +.H 2 "Additional Variables" +In addition to the variables discussed earlier, \f5ksh\fP +has other variables that it handles specially. +The variable \fB\s-1RANDOM\s+1\fP +produces a random number in the range 0 to 32767 each time it is referenced. +Assignment to this variable sets the seed for the +random number generator. +.P +The parameter \fB\s-1PPID\s+1\fP +is used to generate the process id of the process which invoked this shell. +.H 2 "Added Traps" +A new trap named +\fB\s-1ERR\s+1\fP +has been added. +This trap is invoked whenever the shell would exit if the +.B \-e +option were set. +This trap is used by +Fourth Generation Make\*(Rf +.RS +G. S. Fowler, +.I "The Fourth Generation Make," +Proceedings of the Portland USENIX meeting, pp. 159-174, 1985. +.RF +which runs \f5ksh\fP +as a co-process. +.P +A trap named +\fB\s-1DEBUG\s+1\fP +gets executed after each command. +This trap can be used for debugging and other purposes. +.P +The +\fB\s-1KEYBD\s+1\fP +trap was described earlier. +.H 2 Debugging +The primary method for debugging Bourne shell scripts is to +use the +.B \-x +option to enable the execution trace. +After all +the expansions have been performed, +but before each command is executed, +the trace writes to standard error the name and arguments +of each command preceded by a +.BR + . +While the trace is very useful, there is no way +to find out what line of source a given trace line +corresponds to. +With +\f5ksh\fP +the +\fB\s-1PS4\s+1\fP +variable +is evaluated for parameter expansion and +is displayed before each command, +instead of the +.BR + . +.P +The +\fB\s-1LINENO\s+1\fP +variable is set to the current line number relative to the +beginning of the current script or function. +It is most useful as part of the +\fB\s-1PS4\s+1\fP +prompt. +.P +The +\fB\s-1DEBUG\s+1\fP +trap can be used to write a break point shell +debugger in \f5ksh\fP. +An example of such a debugger is \f5kshdb\fP.\*(Rf +.RS +Bill Rosenblatt, +.IR "Debugging Shell Scripts with \f5kshdb\fP" , +Unix World, Volume X, No. 5, 1993. +.RF +.H 2 "Timing Commands" +.P +Finding the time it takes to execute commands +has been a serious problem with the Bourne shell. +Since the \f5time\fP command is not part of the +language, it is necessary to write a script +in order to time a \f5for\fP or \f5while\fP loop. +The extra time in invoking the shell and processing +the script is accumulated along with the time +to execute the script. +.P +More seriously, the Bourne shell does not give correct +times for pipelines. +The reason for this is that the times for some members +of a pipeline are not counted when computing the time. +As an extreme example, +running \f5time\fP on the script +.ce +\f5cat < /dev/null | sort -u bigfile | wc\fP +with the Bourne shell will show very little +user and system time no matter how +large \f5bigfile\fP is. +.P +To correct these problems, +a reserved word \f5time\fP +has been added to replace +the \f5time\fP +command. +Any function, command or pipeline can be preceded by this reserved word +to obtain information about the elapsed, user, and system times. +Since I/O redirections bind to the command, not to +\f5time\fP, +parentheses should be used to redirect the timing information which +is normally printed on file descriptor 2. +.H 1 SECURITY +There are several documented problems associated with the security of +shell procedures\*(Rf. +.RS +F. T. Grampp and R. H. Morris, +.I "UNIX Operating System Security," +AT&T Bell Labs Tech. Journal, Vol. 63, No. 8, Part 2, pp. 1649-1671, 1984. +.RF +These security holes occur primarily because a user can manipulate the +.I environment +to subvert the intent of a +.I setuid +shell procedure. +Sometimes, shell procedures are initiated from +binary programs, without the author's +awareness, by library routines which invoke shells to carry out +their tasks. +When the binary program is run +.I setuid +then the shell procedure runs with the permissions afforded to the +owner of the binary file. +.P +In the Bourne shell, +the +.B \s-1IFS\s+1 +parameter is used to split each word into separate command arguments. +If a user knows that some +.I setuid +program will run +\f5sh\ -c\ /bin/pwd\fP +(or any other command in +.BR /bin ) +then the user sets and exports +.BR \s-1IFS\s+1=\^/ . +Instead of running +.B /bin/pwd +the shell will run +.B bin +with +.B pwd +as an argument. +The user puts his or her own \f5bin\fP +program into the current directory. +This program can +create a copy of the shell, +make this shell +.IR setuid , +and then run the \f5/bin/pwd\fP +program so that the original program continues to run successfully. +This kind of penetration is not possible with +\f5ksh\fP +since the +.B \s-1IFS\s+1 +parameter only splits arguments that result from command or parameter +substitution. +.P +Some +.I setuid +programs run programs using +.I system() +without giving the full pathname. +If the +user sets the +.B \s-1PATH\s+1 +variable so that the desired command will be found +in his or her local bin, then the same technique described above can +be employed to compromise the security of the system. +To close up this and other security holes, +\f5ksh\fP +resets the effective user id to the real user id and the effective +group id to the real group id unless the +.I privileged +option +.RB ( \-p\^ ) +is specified at invocation. +In +this mode, the +.B privileged +mode, the +.B .profile +and +.B \s-1ENV\s+1 +files are not processed. +Instead, the file +.B /etc/suid_profile +is read and executed. +This gives an administrator control over the +environment to set the +.B \s-1PATH\s+1 +variable or to log setuid shell invocations. +Clearly security of the system is compromised if +.B /etc +or this file is publicly writable. +.P +Some versions of the UNIX operating system look for the characters +\f5#!\fP +as the first two characters of an executable file. +If these characters are found, then the next word on this line is taken +as the interpreter to +invoke +for this command and the interpreter is +.IR exec ed +with the name of the script as argument zero and argument one. +If the +.I setuid +or +.I setgid +bits are on for this file, then the interpreter +is run with the effective uid and/or gid set accordingly. +This scheme has three major drawbacks. +First of all, +putting the pathname of the interpreter into the script +makes the script less portable since the interpreter +may be installed in a different directory on another system. +Secondly, using the +\f5#!\fP +notation forces an +.B exec +of the interpreter even when the call is invoked from the interpreter +which it must exec. This is inefficient since +\f5ksh\fP can handle a failed exec much faster than starting up +again. +More importantly, +.I setuid +and +.I setgid +procedures provide an easy target for intrusion. +By linking a +.I setuid +or +.I setgid +procedure to a name beginning with a +.B \- +the interpreter is fooled into thinking that it is being invoked with +a command line option rather than the name of a file. +When the interpreter is the shell, the user gets a privileged +interactive shell. +There is code in +\f5ksh\fP +to guard against this simple form of intrusion. +.P +A more reliable way to handle +.I setuid +and +.I setgid +procedures is provided with +\f5ksh\fP. +The technique does not require any changes to the operating system +and provides better security. +Another advantage to this method is that it also allows scripts which +have execute permission but no read permission to run. Taking away read +permission makes scripts more secure. +.P +The method relies on a setuid +.B root +program to authenticate the +request and exec the shell with the correct mode bits to carry out +the task. This shell is invoked with the requested file already open +for reading. A script which cannot be opened for reading or which +has its setuid and/or setgid bits turned on causes this setuid +.B root +program to get \fBexec\fPed. +For security reasons, this program is given the full +pathname +\f5/etc/suid_exec\fP. +A description of the implementation of the +\f5/etc/suid_exec\fP +program can be found in +a separate paper\*(Rf. +.RS +D. G Korn +.I "Parlez-vous Kanji?" +TM-59554-860602-03, 1986. +.RF +.H 1 "CODE CHANGES" +\f5ksh\fP is written in ANSI-C as a reusable library. +The code can be compiled with C++ and older K&R C as well. +The code uses the IEEE POSIX 1003.1 and ISO 9945-1 standard\*(Rf +.RS +.I "POSIX \- Part 1: System Application Program Interface," +IEEE Std 1003.1-1990, ISO/IEC 9945-1:1990. +.RF +wherever possible so that \f5ksh\fP should be able to run +on any POSIX compliant system. In addition, it is possible +to compile \f5ksh\fP for older systems. +.P +Unlike earlier version of the Bourne shell, +\f5ksh\fP treats eight bit characters transparently +without stripping off the +leading bit. +There is also a compile time switch to enable handling multi-byte +and multi-width characters sets. +.P +On systems with dynamic libraries, it is possible to add built-in +commands at run time with the built-in command \f5builtin\fP +described earlier. +It is also possible to embed \f5ksh\fP in applications in +a manner analogous to \f5tcl\fP. +.H 1 "EXAMPLE" +.P +An example of a \f5ksh\fP script is included +in the Appendix. +This one page program is a variant of the UNIX system +\f5grep\fP(1) program. +Pattern matching for this version of \f5grep\fP +means shell patterns. +.P +The first half uses the \f5getopts\fP command to +find the option flags. +Nearly all options have been implemented. +The second half goes through each line of each file +to look for a pattern match. +.P +This program is not intended to serve as a +replacement for \f5grep\fP +which has been highly tuned for performance. +It does +illustrate the programming power of \f5ksh\fP. +Note that no auxiliary processes are spawned by this script. +It was written and debugged in under two hours. +While performance is acceptable for small files, +this program runs at only one tenth +the speed of \f5grep\fP +for large files. +.H 1 "PERFORMANCE" +.P +\f5ksh\fP executes many scripts faster than the System V Bourne shell; +in some cases more than 10 times as fast. +The primary reason for this is that \f5ksh\fP creates fewer +processes. +The time to execute a built-in command or a function is one or two +orders of magnitude faster than performing a \f5fork\fP() and +\f5exec\fP() to create a separate process. +Command substitution and commands inside parentheses +are performed without creating another process, unless necessary +to preserve correct behavior. +.P +Another reason for improved performance is the use of the \fBsfio\fP\*(Rf, +.RS +David Korn and Kiem-Phong Vo, +.IR "SFIO - A Safe/Fast String/File I/O," +Proceedings of the Summer Usenix, +pp. 235-255, 1991. +.RF +library for I/O. The \fBsfio\fP library buffers all I/O +and buffers are flushed only when required. +The algorithms used in \fBsfio\fP perform better than +traditional versions of standard I/O so that programs that +spend most of their time +formatting output may actually perform better +than versions written in C. +.P +Several of the internal algorithms have been changed +so that the number of subroutine calls has been +substantially reduced. +\f5ksh\fP uses variable sized hash tables for variables. +Scripts that rely heavily on referencing variables execute faster. +More processing is performed while reading the script +so that execution time is saved while running loops. +These changes are not noticeable for scripts that \f5fork()\fP +and run processes, +but they reduce the time that it takes to interpret commands by +more than a factor of two. +.P +Most importantly, \f5ksh\fP provide mechanisms to write applications +that do not require as many processes. +The arithmetic provided by the shell eliminates the need for the +\f5expr\fP command. +The pattern matching and substring capabilities eliminate the +need to use \f5sed\fP or \f5awk\fP to process strings. +.P +The architecture of \f5ksh\fP makes it easy to make commands +built-ins without changing the semantics at all. +Systems that have run-time binding of libraries allow +applications to be sped up by supplying the critical +programs as shell built-in commands. +Implementations on other systems can add built-in commands +at compile time. +The procedure for writing built-in commands that can be loaded +at run time is in a separate document.\*(Rf, +.RS +David Korn, +.IR "Guidelines for writing \f5ksh-93\fP built-in commands," +to be published, 1994. +.RF +.H 1 "CONCLUSION" +.P +The 1988 version of \f5ksh\fP has tens of thousands of regular users +and is a suitable replacement for the Bourne shell. +The 1993 version of \f5ksh\fP is essentially upward compatible with +both the 1988 version of \f5ksh\fP and with the recent IEEE POSIX +and ISO shell standard. +The 1993 version offers many advantages for programming applications, +and it has been rewritten so that it can be used in embedded applications. +It also offers improved performance. +.SG dgk \" signature typist initials +\" .CS 14 24 38 0 0 16 \" cover sheet for TM +.bp +.ce +\fIAPPENDIX\fP +.nf +\f5 +.ta .66i 1.33i 2i 2.66i 3.33i 4i 4.66i 5.33i 6i 6.66i 7.33i 8i +.so grep.mm +.fi +\fP + + diff --git a/usr/src/lib/libshell/common/sh/args.c b/usr/src/lib/libshell/common/sh/args.c new file mode 100644 index 0000000000..ad1bf46a40 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/args.c @@ -0,0 +1,846 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * UNIX shell + * + * S. R. Bourne + * Rewritten by David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include "path.h" +#include "builtins.h" +#include "terminal.h" +#include "edit.h" +#include "FEATURE/poll" +#if SHOPT_KIA +# include "shlex.h" +# include "io.h" +#endif /* SHOPT_KIA */ +#if SHOPT_PFSH +# define PFSHOPT "P" +#else +# define PFSHOPT +#endif +#if SHOPT_BASH +# define BASHOPT "\375\374\373" +#else +# define BASHOPT +#endif +#if SHOPT_HISTEXPAND +# define HFLAG "H" +#else +# define HFLAG "" +#endif + +#define SORT 1 +#define PRINT 2 + +void sh_applyopts(Shopt_t); + +static int arg_expand(struct argnod*,struct argnod**,int); + +static char *null; + +/* The following order is determined by sh_optset */ +static const char optksh[] = PFSHOPT BASHOPT "DircabefhkmnpstuvxBCGEl" HFLAG; +static const int flagval[] = +{ +#if SHOPT_PFSH + SH_PFSH, +#endif +#if SHOPT_BASH + SH_NOPROFILE, SH_RC, SH_POSIX, +#endif + SH_DICTIONARY, SH_INTERACTIVE, SH_RESTRICTED, SH_CFLAG, + SH_ALLEXPORT, SH_NOTIFY, SH_ERREXIT, SH_NOGLOB, SH_TRACKALL, + SH_KEYWORD, SH_MONITOR, SH_NOEXEC, SH_PRIVILEGED, SH_SFLAG, SH_TFLAG, + SH_NOUNSET, SH_VERBOSE, SH_XTRACE, SH_BRACEEXPAND, SH_NOCLOBBER, + SH_GLOBSTARS, SH_RC, SH_LOGIN_SHELL, +#if SHOPT_HISTEXPAND + SH_HISTEXPAND, +#endif + 0 +}; + +#define NUM_OPTS (sizeof(flagval)/sizeof(*flagval)) + +typedef struct _arg_ +{ + Shell_t *shp; + struct dolnod *argfor; /* linked list of blocks to be cleaned up */ + struct dolnod *dolh; + char flagadr[NUM_OPTS+1]; +#if SHOPT_KIA + char *kiafile; +#endif /* SHOPT_KIA */ +} Arg_t; + + +/* ======== option handling ======== */ + +void *sh_argopen(Shell_t *shp) +{ + void *addr = newof(0,Arg_t,1,0); + Arg_t *ap = (Arg_t*)addr; + ap->shp = shp; + return(addr); +} + +static int infof(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp) +{ +#if SHOPT_BASH + extern const char sh_bash1[], sh_bash2[]; + if(strcmp(s,"bash1")==0) + { + if(sh_isoption(SH_BASH)) + sfputr(sp,sh_bash1,-1); + } + else if(strcmp(s,"bash2")==0) + { + if(sh_isoption(SH_BASH)) + sfputr(sp,sh_bash2,-1); + } + else if(*s==':' && sh_isoption(SH_BASH)) + sfputr(sp,s,-1); + else +#endif + if(*s!=':') + sfputr(sp,sh_set,-1); + return(1); +} + +/* + * This routine turns options on and off + * The options "PDicr" are illegal from set command. + * The -o option is used to set option by name + * This routine returns the number of non-option arguments + */ +int sh_argopts(int argc,register char *argv[]) +{ + register int n,o; + register Arg_t *ap = (Arg_t*)sh.arg_context; + Shopt_t newflags; + int setflag=0, action=0, trace=(int)sh_isoption(SH_XTRACE); + Namval_t *np = NIL(Namval_t*); + const char *cp; + int verbose,f; + Optdisc_t disc; + newflags=sh.options; + memset(&disc, 0, sizeof(disc)); + disc.version = OPT_VERSION; + disc.infof = infof; + opt_info.disc = &disc; + + if(argc>0) + setflag = 4; + else + argc = -argc; + while((n = optget(argv,setflag?sh_optset:sh_optksh))) + { + o=0; + f=*opt_info.option=='-'; + switch(n) + { + case 'A': + np = nv_open(opt_info.arg,sh.var_tree,NV_NOASSIGN|NV_ARRAY|NV_VARNAME); + if(f) + nv_unset(np); + continue; +#if SHOPT_BASH + case 'O': /* shopt options, only in bash mode */ + if(!sh_isoption(SH_BASH)) + errormsg(SH_DICT,ERROR_exit(1), e_option, opt_info.name); +#endif + case 'o': /* set options */ + byname: + if(!opt_info.arg||!*opt_info.arg||*opt_info.arg=='-') + { + action = PRINT; + /* print style: -O => shopt options + * bash => print unset options also, no heading + */ + verbose = (f?PRINT_VERBOSE:PRINT_NO_HEADER)| + (n=='O'?PRINT_SHOPT:0)| + (sh_isoption(SH_BASH)?PRINT_ALL|PRINT_NO_HEADER:0)| + ((opt_info.arg&&(!*opt_info.arg||*opt_info.arg=='-'))?(PRINT_TABLE|PRINT_NO_HEADER):0); + continue; + } + o = sh_lookopt(opt_info.arg,&f); + if(o<=0 + || (!sh_isoption(SH_BASH) && (o&SH_BASHEXTRA)) + || ((!sh_isoption(SH_BASH) || n=='o') && (o&SH_BASHOPT)) + + || (setflag && (o&SH_COMMANDLINE))) + { + errormsg(SH_DICT,2, e_option, opt_info.arg); + error_info.errors++; + } + o &= 0xff; + if(sh_isoption(SH_RESTRICTED) && !f && o==SH_RESTRICTED) + errormsg(SH_DICT,ERROR_exit(1), e_restricted, opt_info.arg); + break; +#if SHOPT_BASH + case -1: /* --rcfile */ + sh.rcfile = opt_info.arg; + continue; + case -6: /* --version */ + sfputr(sfstdout, "ksh bash emulation, version ",-1); + np = nv_open("BASH_VERSION",sh.var_tree,0); + sfputr(sfstdout, nv_getval(np),-1); + np = nv_open("MACHTYPE",sh.var_tree,0); + sfprintf(sfstdout, " (%s)\n", nv_getval(np)); + sh_exit(0); + + case -2: /* --noediting */ + off_option(&newflags,SH_VI); + off_option(&newflags,SH_EMACS); + off_option(&newflags,SH_GMACS); + continue; + + case -3: /* --profile */ + f = !f; + /*FALLTHROUGH*/ + case -4: /* --rc */ + case -5: /* --posix */ + /* mask lower 8 bits to find char in optksh string */ + n&=0xff; + goto skip; +#endif + case 'D': + on_option(&newflags,SH_NOEXEC); + goto skip; + case 's': + if(setflag) + { + action = SORT; + continue; + } +#if SHOPT_KIA + goto skip; + case 'R': + if(setflag) + n = ':'; + else + { + ap->kiafile = opt_info.arg; + n = 'n'; + } + /* FALL THRU */ +#endif /* SHOPT_KIA */ + skip: + default: + if(cp=strchr(optksh,n)) + o = flagval[cp-optksh]; + break; + case ':': + if(opt_info.name[0]=='-'&&opt_info.name[1]=='-') + { + opt_info.arg = argv[opt_info.index-1] + 2; + f = 1; + goto byname; + } + errormsg(SH_DICT,2, "%s", opt_info.arg); + continue; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(-1); + } + if(f) + { + if(o==SH_VI || o==SH_EMACS || o==SH_GMACS) + { + off_option(&newflags,SH_VI); + off_option(&newflags,SH_EMACS); + off_option(&newflags,SH_GMACS); + } + on_option(&newflags,o); + off_option(&sh.offoptions,o); + } + else + { + if(o==SH_XTRACE) + trace = 0; + off_option(&newflags,o); + if(setflag==0) + on_option(&sh.offoptions,o); + } + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage(NIL(char*))); + /* check for '-' or '+' argument */ + if((cp=argv[opt_info.index]) && cp[1]==0 && (*cp=='+' || *cp=='-') && + strcmp(argv[opt_info.index-1],"--")) + { + opt_info.index++; + off_option(&newflags,SH_XTRACE); + off_option(&newflags,SH_VERBOSE); + trace = 0; + } + if(trace) + sh_trace(argv,1); + argc -= opt_info.index; + argv += opt_info.index; + if(action==PRINT) + sh_printopts(newflags,verbose,0); + if(setflag) + { + if(action==SORT) + { + if(argc>0) + strsort(argv,argc,strcoll); + else + strsort(sh.st.dolv+1,sh.st.dolc,strcoll); + } + if(np) + { + nv_setvec(np,0,argc,argv); + nv_close(np); + } + else if(argc>0 || ((cp=argv[-1]) && strcmp(cp,"--")==0)) + sh_argset(argv-1); + } + else if(is_option(&newflags,SH_CFLAG)) + { + if(!(sh.comdiv = *argv++)) + { + errormsg(SH_DICT,2,e_cneedsarg); + errormsg(SH_DICT,ERROR_usage(2),optusage(NIL(char*))); + } + argc--; + } + /* handling SH_INTERACTIVE and SH_PRIVILEGED has been moved to + * sh_applyopts(), so that the code can be reused from b_shopt(), too + */ + sh_applyopts(newflags); +#if SHOPT_KIA + if(ap->kiafile) + { + if(!(shlex.kiafile=sfopen(NIL(Sfio_t*),ap->kiafile,"w+"))) + errormsg(SH_DICT,ERROR_system(3),e_create,ap->kiafile); + if(!(shlex.kiatmp=sftmp(2*SF_BUFSIZE))) + errormsg(SH_DICT,ERROR_system(3),e_tmpcreate); + sfputr(shlex.kiafile,";vdb;CIAO/ksh",'\n'); + shlex.kiabegin = sftell(shlex.kiafile); + shlex.entity_tree = dtopen(&_Nvdisc,Dtbag); + shlex.scriptname = strdup(sh_fmtq(argv[0])); + shlex.script=kiaentity(shlex.scriptname,-1,'p',-1,0,0,'s',0,""); + shlex.fscript=kiaentity(shlex.scriptname,-1,'f',-1,0,0,'s',0,""); + shlex.unknown=kiaentity("<unknown>",-1,'p',-1,0,0,'0',0,""); + kiaentity("<unknown>",-1,'p',0,0,shlex.unknown,'0',0,""); + shlex.current = shlex.script; + ap->kiafile = 0; + } +#endif /* SHOPT_KIA */ + return(argc); +} + +/* apply new options */ + +void sh_applyopts(Shopt_t newflags) +{ + /* cannot set -n for interactive shells since there is no way out */ + if(sh_isoption(SH_INTERACTIVE)) + off_option(&newflags,SH_NOEXEC); + if(is_option(&newflags,SH_PRIVILEGED)) + on_option(&newflags,SH_NOUSRPROFILE); + if(is_option(&newflags,SH_PRIVILEGED) != sh_isoption(SH_PRIVILEGED)) + { + if(sh_isoption(SH_PRIVILEGED)) + { + setuid(sh.userid); + setgid(sh.groupid); + if(sh.euserid==0) + { + sh.euserid = sh.userid; + sh.egroupid = sh.groupid; + } + } + else if((sh.userid!=sh.euserid && setuid(sh.euserid)<0) || + (sh.groupid!=sh.egroupid && setgid(sh.egroupid)<0) || + (sh.userid==sh.euserid && sh.groupid==sh.egroupid)) + off_option(&newflags,SH_PRIVILEGED); + } +#if SHOPT_BASH + on_option(&newflags,SH_CMDHIST); + on_option(&newflags,SH_CHECKHASH); + on_option(&newflags,SH_EXECFAIL); + on_option(&newflags,SH_EXPAND_ALIASES); + on_option(&newflags,SH_HISTAPPEND); + on_option(&newflags,SH_INTERACTIVE_COMM); + on_option(&newflags,SH_LITHIST); + on_option(&newflags,SH_NOEMPTYCMDCOMPL); + + if(!is_option(&newflags,SH_XPG_ECHO) && sh_isoption(SH_XPG_ECHO)) + astconf("UNIVERSE", 0, "ucb"); + if(is_option(&newflags,SH_XPG_ECHO) && !sh_isoption(SH_XPG_ECHO)) + astconf("UNIVERSE", 0, "att"); + if(!is_option(&newflags,SH_PHYSICAL) && sh_isoption(SH_PHYSICAL)) + astconf("PATH_RESOLVE", 0, "metaphysical"); + if(is_option(&newflags,SH_PHYSICAL) && !sh_isoption(SH_PHYSICAL)) + astconf("PATH_RESOLVE", 0, "physical"); + if(is_option(&newflags,SH_HISTORY2) && !sh_isoption(SH_HISTORY2)) + { + sh_onstate(SH_HISTORY); + sh_onoption(SH_HISTORY); + } + if(!is_option(&newflags,SH_HISTORY2) && sh_isoption(SH_HISTORY2)) + { + sh_offstate(SH_HISTORY); + sh_offoption(SH_HISTORY); + } +#endif + sh.options = newflags; +} +/* + * returns the value of $- + */ +char *sh_argdolminus(void) +{ + register const char *cp=optksh; + register Arg_t *ap = (Arg_t*)sh.arg_context; + register char *flagp=ap->flagadr; + while(cp< &optksh[NUM_OPTS]) + { + int n = flagval[cp-optksh]; + if(sh_isoption(n)) + *flagp++ = *cp; + cp++; + } + *flagp = 0; + return(ap->flagadr); +} + +/* + * set up positional parameters + */ +void sh_argset(char *argv[]) +{ + register Arg_t *ap = (Arg_t*)sh.arg_context; + sh_argfree(ap->dolh,0); + ap->dolh = sh_argcreate(argv); + /* link into chain */ + ap->dolh->dolnxt = ap->argfor; + ap->argfor = ap->dolh; + sh.st.dolc = ap->dolh->dolnum-1; + sh.st.dolv = ap->dolh->dolval; +} + +/* + * free the argument list if the use count is 1 + * If count is greater than 1 decrement count and return same blk + * Free the argument list if the use count is 1 and return next blk + * Delete the blk from the argfor chain + * If flag is set, then the block dolh is not freed + */ +struct dolnod *sh_argfree(struct dolnod *blk,int flag) +{ + register struct dolnod* argr=blk; + register struct dolnod* argblk; + register Arg_t *ap = (Arg_t*)sh.arg_context; + if(argblk=argr) + { + if((--argblk->dolrefcnt)==0) + { + argr = argblk->dolnxt; + if(flag && argblk==ap->dolh) + ap->dolh->dolrefcnt = 1; + else + { + /* delete from chain */ + if(ap->argfor == argblk) + ap->argfor = argblk->dolnxt; + else + { + for(argr=ap->argfor;argr;argr=argr->dolnxt) + if(argr->dolnxt==argblk) + break; + if(!argr) + return(NIL(struct dolnod*)); + argr->dolnxt = argblk->dolnxt; + argr = argblk->dolnxt; + } + free((void*)argblk); + } + } + } + return(argr); +} + +/* + * grab space for arglist and copy args + * The strings are copied after the argment vector + */ +struct dolnod *sh_argcreate(register char *argv[]) +{ + register struct dolnod *dp; + register char **pp=argv, *sp; + register int size=0,n; + /* count args and number of bytes of arglist */ + while(sp= *pp++) + size += strlen(sp); + n = (pp - argv)-1; + dp=new_of(struct dolnod,n*sizeof(char*)+size+n); + dp->dolrefcnt=1; /* use count */ + dp->dolnum = n; + dp->dolnxt = 0; + pp = dp->dolval; + sp = (char*)dp + sizeof(struct dolnod) + n*sizeof(char*); + while(n--) + { + *pp++ = sp; + sp = strcopy(sp, *argv++) + 1; + } + *pp = NIL(char*); + return(dp); +} + +/* + * used to set new arguments for functions + */ +struct dolnod *sh_argnew(char *argi[], struct dolnod **savargfor) +{ + register Arg_t *ap = (Arg_t*)sh.arg_context; + register struct dolnod *olddolh = ap->dolh; + *savargfor = ap->argfor; + ap->dolh = 0; + ap->argfor = 0; + sh_argset(argi); + return(olddolh); +} + +/* + * reset arguments as they were before function + */ +void sh_argreset(struct dolnod *blk, struct dolnod *afor) +{ + register Arg_t *ap = (Arg_t*)sh.arg_context; + while(ap->argfor=sh_argfree(ap->argfor,0)); + ap->argfor = afor; + if(ap->dolh = blk) + { + sh.st.dolc = ap->dolh->dolnum-1; + sh.st.dolv = ap->dolh->dolval; + } +} + +/* + * increase the use count so that an sh_argset will not make it go away + */ +struct dolnod *sh_arguse(void) +{ + register struct dolnod *dh; + register Arg_t *ap = (Arg_t*)sh.arg_context; + if(dh=ap->dolh) + dh->dolrefcnt++; + return(dh); +} + +/* + * Print option settings on standard output + * if mode is inclusive or of PRINT_* + * if <mask> is set, only options with this mask value are displayed + */ +void sh_printopts(Shopt_t oflags,register int mode, Shopt_t *mask) +{ + register const Shtable_t *tp; + const char *name; + int on; + int value; + if(!(mode&PRINT_NO_HEADER)) + sfputr(sfstdout,sh_translate(e_heading),'\n'); + if(mode&PRINT_TABLE) + { + int w; + int c; + int r; + int i; + + c = 0; + for(tp=shtab_options; value=tp->sh_number; tp++) + { + if(mask && !is_option(mask,value&0xff)) + continue; + name = tp->sh_name; + if(name[0] == 'n' && name[1] == 'o' && name[2] != 't') + name += 2; + if(c<(w=strlen(name))) + c = w; + } + c += 4; + if((w = ed_window()) < (2*c)) + w = 2*c; + r = w / c; + i = 0; + for(tp=shtab_options; value=tp->sh_number; tp++) + { + if(mask && !is_option(mask,value&0xff)) + continue; + on = !!is_option(&oflags,value); + value &= 0xff; + name = tp->sh_name; + if(name[0] == 'n' && name[1] == 'o' && name[2] != 't') + { + name += 2; + on = !on; + } + if(++i>=r) + { + i = 0; + sfprintf(sfstdout, "%s%s\n", on ? "" : "no", name); + } + else + sfprintf(sfstdout, "%s%-*s", on ? "" : "no", on ? c : (c-2), name); + } + if(i) + sfputc(sfstdout,'\n'); + return; + } +#if SHOPT_RAWONLY + on_option(&oflags,SH_VIRAW); +#endif + if(!(mode&(PRINT_ALL|PRINT_VERBOSE))) /* only print set options */ + { + if(mode&PRINT_SHOPT) + sfwrite(sfstdout,"shopt -s",3); + else + sfwrite(sfstdout,"set",3); + } + for(tp=shtab_options; value=tp->sh_number; tp++) + { + if(mask && !is_option(mask,value&0xff)) + continue; + if(sh_isoption(SH_BASH)) + { + if (!(mode&PRINT_SHOPT) != !(value&SH_BASHOPT)) + continue; + } + else if (value&(SH_BASHEXTRA|SH_BASHOPT)) + continue; + on = !!is_option(&oflags,value); + name = tp->sh_name; + if(name[0] == 'n' && name[1] == 'o' && name[2] != 't') + { + name += 2; + on = !on; + } + if(mode&PRINT_VERBOSE) + { + sfputr(sfstdout,name,' '); + sfnputc(sfstdout,' ',24-strlen(name)); + sfputr(sfstdout,on ? sh_translate(e_on) : sh_translate(e_off),'\n'); + } + else if(mode&PRINT_ALL) /* print unset options also */ + { + if(mode&PRINT_SHOPT) + sfprintf(sfstdout, "shopt -%c %s\n", + on?'s':'u', + name); + else + sfprintf(sfstdout, "set %co %s\n", + on?'-':'+', + name); + } + else if(!(value&SH_COMMANDLINE) && is_option(&oflags,value&0xff)) + sfprintf(sfstdout," %s%s%s",(mode&PRINT_SHOPT)?"":"--",on?"":"no",name); + } + if(!(mode&(PRINT_VERBOSE|PRINT_ALL))) + sfputc(sfstdout,'\n'); +} + +/* + * build an argument list + */ +char **sh_argbuild(int *nargs, const struct comnod *comptr,int flag) +{ + register struct argnod *argp; + struct argnod *arghead=0; + sh.xargmin = 0; + { + register const struct comnod *ac = comptr; + register int n; + /* see if the arguments have already been expanded */ + if(!ac->comarg) + { + *nargs = 0; + return(&null); + } + else if(!(ac->comtyp&COMSCAN)) + { + register struct dolnod *ap = (struct dolnod*)ac->comarg; + *nargs = ap->dolnum; + ((struct comnod*)ac)->comtyp |= COMFIXED; + return(ap->dolval+ap->dolbot); + } + sh.lastpath = 0; + *nargs = 0; + if(ac) + { + if(ac->comnamp == SYSLET) + flag |= ARG_LET; + argp = ac->comarg; + while(argp) + { + n = arg_expand(argp,&arghead,flag); + if(n>1) + { + if(sh.xargmin==0) + sh.xargmin = *nargs; + sh.xargmax = *nargs+n; + } + *nargs += n; + argp = argp->argnxt.ap; + } + argp = arghead; + } + } + { + register char **comargn; + register int argn; + register char **comargm; + int argfixed = COMFIXED; + argn = *nargs; + /* allow room to prepend args */ + argn += 1; + + comargn=(char**)stakalloc((unsigned)(argn+1)*sizeof(char*)); + comargm = comargn += argn; + *comargn = NIL(char*); + if(!argp) + { + /* reserve an extra null pointer */ + *--comargn = 0; + return(comargn); + } + while(argp) + { + struct argnod *nextarg = argp->argchn.ap; + argp->argchn.ap = 0; + *--comargn = argp->argval; + if(!(argp->argflag&ARG_RAW) || (argp->argflag&ARG_EXP)) + argfixed = 0; + if(!(argp->argflag&ARG_RAW)) + sh_trim(*comargn); + if(!(argp=nextarg) || (argp->argflag&ARG_MAKE)) + { + if((argn=comargm-comargn)>1) + strsort(comargn,argn,strcoll); + comargm = comargn; + } + } + ((struct comnod*)comptr)->comtyp |= argfixed; + return(comargn); + } +} + +#if _pipe_socketpair && !_socketpair_devfd +# define sh_pipe arg_pipe +/* + * create a real pipe (not a socket) and print message on failure + */ +static int arg_pipe(register int pv[]) +{ + int fd[2]; + if(pipe(fd)<0 || (pv[0]=fd[0])<0 || (pv[1]=fd[1])<0) + errormsg(SH_DICT,ERROR_system(1),e_pipe); + pv[0] = sh_iomovefd(pv[0]); + pv[1] = sh_iomovefd(pv[1]); + sh.fdstatus[pv[0]] = IONOSEEK|IOREAD; + sh.fdstatus[pv[1]] = IONOSEEK|IOWRITE; + sh_subsavefd(pv[0]); + sh_subsavefd(pv[1]); + return(0); +} +#endif + +/* Argument expansion */ +static int arg_expand(register struct argnod *argp, struct argnod **argchain,int flag) +{ + register int count = 0; + argp->argflag &= ~ARG_MAKE; +#if SHOPT_DEVFD + if(*argp->argval==0 && (argp->argflag&ARG_EXP)) + { + /* argument of the form (cmd) */ + register struct argnod *ap; + int monitor, fd, pv[2]; + ap = (struct argnod*)stakseek(ARGVAL); + ap->argflag |= ARG_MAKE; + ap->argflag &= ~ARG_RAW; + ap->argchn.ap = *argchain; + *argchain = ap; + count++; + stakwrite(e_devfdNN,8); + sh_pipe(pv); + fd = argp->argflag&ARG_RAW; + stakputs(fmtbase((long)pv[fd],10,0)); + ap = (struct argnod*)stakfreeze(1); + sh.inpipe = sh.outpipe = 0; + if(monitor = (sh_isstate(SH_MONITOR)!=0)) + sh_offstate(SH_MONITOR); + if(fd) + { + sh.inpipe = pv; + sh_exec((Shnode_t*)argp->argchn.ap,(int)sh_isstate(SH_ERREXIT)); + } + else + { + sh.outpipe = pv; + sh_exec((Shnode_t*)argp->argchn.ap,(int)sh_isstate(SH_ERREXIT)); + } + if(monitor) + sh_onstate(SH_MONITOR); + close(pv[1-fd]); + sh_iosave(-pv[fd], sh.topfd); + } + else +#endif /* SHOPT_DEVFD */ + if(!(argp->argflag&ARG_RAW)) + { +#if SHOPT_OPTIMIZE + struct argnod *ap; + if(flag&ARG_OPTIMIZE) + argp->argchn.ap=0; + if(ap=argp->argchn.ap) + { + sh.optcount++; + count = 1; + ap->argchn.ap = *argchain; + ap->argflag |= ARG_RAW; + ap->argflag &= ~ARG_EXP; + *argchain = ap; + } + else +#endif /* SHOPT_OPTIMIZE */ + count = sh_macexpand(argp,argchain,flag); + } + else + { + argp->argchn.ap = *argchain; + *argchain = argp; + argp->argflag |= ARG_MAKE; + count++; + } + return(count); +} + diff --git a/usr/src/lib/libshell/common/sh/arith.c b/usr/src/lib/libshell/common/sh/arith.c new file mode 100644 index 0000000000..b81e798fae --- /dev/null +++ b/usr/src/lib/libshell/common/sh/arith.c @@ -0,0 +1,363 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Shell arithmetic - uses streval library + * David Korn + * AT&T Labs + */ + +#include "defs.h" +#include <ctype.h> +#include "lexstates.h" +#include "name.h" +#include "streval.h" +#include "variables.h" + +#ifndef LLONG_MAX +#define LLONG_MAX LONG_MAX +#endif + +static Sfdouble_t Zero, NaN, Inf; +static Namval_t Infnod = +{ + { 0 }, + "Inf", + NV_NOFREE|NV_LDOUBLE,NV_RDONLY +}; + +static Namval_t NaNnod = +{ + { 0 }, + "NaN", + NV_NOFREE|NV_LDOUBLE,NV_RDONLY +}; + +static Namval_t *scope(register Namval_t *np,register struct lval *lvalue,int assign) +{ + register Namarr_t *ap; + register int flag = lvalue->flag; + register char *sub=0; + if(lvalue->emode&ARITH_COMP) + { + char *cp = (char*)np; + register Namval_t *mp; + if(cp>=lvalue->expr && cp < lvalue->expr+lvalue->elen) + { + /* do bindiing to node now */ + int c = cp[flag]; + cp[flag] = 0; + np = nv_open(cp,sh.var_tree,NV_NOASSIGN|NV_VARNAME); + cp[flag] = c; + if(cp[flag+1]=='[') + flag++; + else + flag = 0; + } + else if(dtvnext(sh.var_tree) && (mp=nv_search((char*)np,sh.var_tree,HASH_NOSCOPE|HASH_SCOPE|HASH_BUCKET))) + { + while(nv_isref(mp)) + { + sub = nv_refsub(mp); + mp = nv_refnode(mp); + } + np = mp; + } + } + if(flag || sub) + { + if(!sub) + sub = (char*)&lvalue->expr[flag]; + if(((ap=nv_arrayptr(np)) && array_assoc(ap)) || (lvalue->emode&ARITH_COMP)) + nv_endsubscript(np,sub,NV_ADD|NV_SUBQUOTE); + else + nv_putsub(np, NIL(char*),flag); + } + return(np); +} + +static Sfdouble_t arith(const char **ptr, struct lval *lvalue, int type, Sfdouble_t n) +{ + register Sfdouble_t r= 0; + char *str = (char*)*ptr; + switch(type) + { + case ASSIGN: + { + register Namval_t *np = (Namval_t*)(lvalue->value); + np = scope(np,lvalue,1); + nv_putval(np, (char*)&n, NV_LDOUBLE); + r=nv_getnum(np); + break; + } + case LOOKUP: + { + register int c = *str; + register char *xp=str; + lvalue->value = (char*)0; + if(c=='.') + str++; + c = mbchar(str); + if(isaletter(c)) + { + register Namval_t *np; + int dot=0; + char *cp; + while(1) + { + while(xp=str, c=mbchar(str), isaname(c)); + str = xp; + if(c!='.') + break; + dot=1; + if((c = *++str) !='[') + continue; + str = nv_endsubscript((Namval_t*)0,cp=str,NV_SUBQUOTE)-1; + if(sh_checkid(cp+1,(char*)0)) + str -=2; + } + if(c=='(') + { + int fsize = str- (char*)(*ptr); + const struct mathtab *tp; + c = **ptr; + lvalue->fun = 0; + if(fsize<=(sizeof(tp->fname)-2)) for(tp=shtab_math; *tp->fname; tp++) + { + if(*tp->fname > c) + break; + if(tp->fname[1]==c && tp->fname[fsize+1]==0 && strncmp(&tp->fname[1],*ptr,fsize)==0) + { + lvalue->fun = tp->fnptr; + lvalue->nargs = *tp->fname; + break; + } + } + if(lvalue->fun) + break; + lvalue->value = (char*)ERROR_dictionary(e_function); + return(r); + } + if((lvalue->emode&ARITH_COMP) && dot) + { + lvalue->value = (char*)*ptr; + lvalue->flag = str-lvalue->value; + break; + } + *str = 0; + if(sh_isoption(SH_NOEXEC)) + np = L_ARGNOD; + else + { + int offset = staktell(); + char *saveptr = stakfreeze(0); + Dt_t *root = (lvalue->emode&ARITH_COMP)?sh.var_base:sh.var_tree; + *str = c; + while(c=='[' || c=='.') + { + if(c=='[') + { + str = nv_endsubscript(np,cp=str,0); + if((c= *str)!='[' && c!='.') + { + str = cp; + c = '['; + break; + } + } + else + { + str++; + while(xp=str, c=mbchar(str), isaname(c)); + str = xp; + } + } + *str = 0; + if(strcasecmp(*ptr,"Inf")==0) + { + Inf = 1.0/Zero; + Infnod.nvalue.ldp = &Inf; + np = &Infnod; + } + else if(strcasecmp(*ptr,"NaN")==0) + { + NaN = 0.0/Zero; + NaNnod.nvalue.ldp = &NaN; + np = &NaNnod; + } + else + np = nv_open(*ptr,root,NV_NOASSIGN|NV_VARNAME); + if(saveptr != stakptr(0)) + stakset(saveptr,offset); + else + stakseek(offset); + } + *str = c; + lvalue->value = (char*)np; + if((lvalue->emode&ARITH_COMP) || (nv_isarray(np) && nv_aindex(np)<0)) + { + /* bind subscript later */ + lvalue->flag = 0; + if(c=='[') + { + lvalue->flag = (str-lvalue->expr); + do + str = nv_endsubscript(np,str,0); + while((c= *str)=='['); + } + break; + } + if(c=='[') + { + do + str = nv_endsubscript(np,str,NV_ADD|NV_SUBQUOTE); + while((c=*str)=='['); + } + else if(nv_isarray(np)) + nv_putsub(np,NIL(char*),ARRAY_UNDEF); + if(nv_isattr(np,NV_INTEGER|NV_DOUBLE)==(NV_INTEGER|NV_DOUBLE)) + lvalue->isfloat=1; + lvalue->flag = nv_aindex(np); + } + else + { + char lastbase=0, *val = xp, oerrno = errno; + errno = 0; + r = strtonll(val,&str, &lastbase,-1); + if(*str=='8' || *str=='9') + { + lastbase=10; + errno = 0; + r = strtonll(val,&str, &lastbase,-1); + } + if(lastbase<=1) + lastbase=10; + if(*val=='0') + { + while(*val=='0') + val++; + if(*val==0 || *val=='.' || *val=='x' || *val=='X') + val--; + } + if(r==LLONG_MAX && errno) + c='e'; + else + c = *str; + if(c==GETDECIMAL(0) || c=='e' || c == 'E') + { + lvalue->isfloat=1; + r = strtold(val,&str); + } + else if(lastbase==10 && val[1]) + { + if(val[2]=='#') + val += 3; + if((str-val)>2*sizeof(Sflong_t)) + { + Sfdouble_t rr; + rr = strtold(val,&str); + if(rr!=r) + { + r = rr; + lvalue->isfloat=1; + } + } + } + errno = oerrno; + } + break; + } + case VALUE: + { + register Namval_t *np = (Namval_t*)(lvalue->value); + if(sh_isoption(SH_NOEXEC)) + return(0); + np = scope(np,lvalue,0); + if(((lvalue->emode&2) || lvalue->level>1 || sh_isoption(SH_NOUNSET)) && nv_isnull(np) && !nv_isattr(np,NV_INTEGER)) + { + *ptr = nv_name(np); + lvalue->value = (char*)ERROR_dictionary(e_notset); + lvalue->emode |= 010; + return(0); + } + r = nv_getnum(np); + if(nv_isattr(np,NV_INTEGER|NV_BINARY)==(NV_INTEGER|NV_BINARY)) + lvalue->isfloat= (r!=(Sflong_t)r); + else if(nv_isattr(np,NV_INTEGER|NV_DOUBLE)==(NV_INTEGER|NV_DOUBLE)) + lvalue->isfloat=1; + return(r); + } + + case MESSAGE: + sfsync(NIL(Sfio_t*)); +#if 0 + if(warn) + errormsg(SH_DICT,ERROR_warn(0),lvalue->value,*ptr); + else +#endif + errormsg(SH_DICT,ERROR_exit((lvalue->emode&3)!=0),lvalue->value,*ptr); + } + *ptr = str; + return(r); +} + +/* + * convert number defined by string to a Sfdouble_t + * ptr is set to the last character processed + * if mode>0, an error will be fatal with value <mode> + */ + +Sfdouble_t sh_strnum(register const char *str, char** ptr, int mode) +{ + register Sfdouble_t d; + char base=0, *last; + if(*str==0) + { + if(ptr) + *ptr = (char*)str; + return(0); + } + errno = 0; + d = strtonll(str,&last,&base,-1); + if(*last || errno) + { + d = strval(str,&last,arith,mode); + if(!ptr && *last && mode>0) + errormsg(SH_DICT,ERROR_exit(1),e_lexbadchar,*last,str); + } + if(ptr) + *ptr = last; + return(d); +} + +Sfdouble_t sh_arith(register const char *str) +{ + return(sh_strnum(str, (char**)0, 1)); +} + +void *sh_arithcomp(register char *str) +{ + const char *ptr = str; + Arith_t *ep; + ep = arith_compile(str,(char**)&ptr,arith,ARITH_COMP|1); + if(*ptr) + errormsg(SH_DICT,ERROR_exit(1),e_lexbadchar,*ptr,str); + return((void*)ep); +} diff --git a/usr/src/lib/libshell/common/sh/array.c b/usr/src/lib/libshell/common/sh/array.c new file mode 100644 index 0000000000..5b33f20e5a --- /dev/null +++ b/usr/src/lib/libshell/common/sh/array.c @@ -0,0 +1,865 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Array processing routines + * + * David Korn + * AT&T Labs + * dgk@research.att.com + * + */ + +#include "defs.h" +#include <stak.h> +#include "name.h" + +#define NUMSIZE (4+(ARRAY_MAX>999)+(ARRAY_MAX>9999)+(ARRAY_MAX>99999)) +#define is_associative(ap) array_assoc((Namarr_t*)(ap)) +#define array_setbit(cp, n) (cp[(n)/CHAR_BIT] |= 1<<(((n)&(CHAR_BIT-1)))) +#define array_clrbit(cp, n) (cp[(n)/CHAR_BIT] &= ~(1<<(((n)&(CHAR_BIT-1))))) +#define array_isbit(cp, n) (cp[(n)/CHAR_BIT] & 1<<(((n)&(CHAR_BIT-1)))) +#define NV_CHILD NV_EXPORT + +static char Empty[] = ""; + +struct index_array +{ + Namarr_t header; + int cur; /* index of current element */ + int maxi; /* maximum index for array */ + unsigned char *bits; /* bit array for child subscripts */ + union Value val[1]; /* array of value holders */ +}; + +struct assoc_array +{ + Namarr_t header; + Dt_t *table; + Namval_t *pos; + Namval_t *nextpos; + Namval_t *cur; +}; + +/* + * replace discipline with new one + */ +static void array_setptr(register Namval_t *np, struct index_array *old, struct index_array *new) +{ + register Namfun_t **fp = &np->nvfun; + while(*fp && *fp!= &old->header.hdr) + fp = &((*fp)->next); + if(*fp) + { + new->header.hdr.next = (*fp)->next; + *fp = &new->header.hdr; + } + else sfprintf(sfstderr,"discipline not replaced\n"); +} + +/* + * Calculate the amount of space to be allocated to hold an + * indexed array into which <maxi> is a legal index. The number of + * elements that will actually fit into the array (> <maxi> + * but <= ARRAY_MAX) is returned. + * + */ +static int arsize(register int maxi) +{ + register int i = roundof(maxi,ARRAY_INCR); + return (i>ARRAY_MAX?ARRAY_MAX:i); +} + +static struct index_array *array_grow(Namval_t*, struct index_array*,int); + +/* return index of highest element of an array */ +int array_maxindex(Namval_t *np) +{ + register struct index_array *ap = (struct index_array*)nv_arrayptr(np); + register int i = ap->maxi; + if(is_associative(ap)) + return(-1); + while(i>0 && ap->val[--i].cp==0); + return(i+1); +} + +static union Value *array_getup(Namval_t *np, Namarr_t *arp) +{ + register struct index_array *ap = (struct index_array*)arp; + register union Value *up; + if(!nv_isarray(np)) + return(&np->nvalue); + if(is_associative(ap)) + up = (union Value*)((*arp->fun)(np,NIL(char*),0)); + else + { + if(ap->cur >= ap->maxi) + errormsg(SH_DICT,ERROR_exit(1),e_subscript,nv_name(np)); + up = &(ap->val[ap->cur]); + } + return(up); +} + +/* + * Get the Value pointer for an array. + * Delete space as necessary if flag is ARRAY_DELETE + * After the lookup is done the last @ or * subscript is incremented + */ +static Namval_t *array_find(Namval_t *np,Namarr_t *arp, int flag) +{ + register struct index_array *ap = (struct index_array*)arp; + register union Value *up; + Namval_t *mp; + int wasundef; + if(wasundef = ap->header.nelem&ARRAY_UNDEF) + { + ap->header.nelem &= ~ARRAY_UNDEF; + /* delete array is the same as delete array[@] */ + if(flag&ARRAY_DELETE) + { + nv_putsub(np, NIL(char*), ARRAY_SCAN); + ap->header.nelem |= ARRAY_SCAN; + } + else /* same as array[0] */ + { + if(is_associative(ap)) + (*ap->header.fun)(np,"0",flag==ARRAY_ASSIGN?NV_AADD:0); + else + ap->cur = 0; + } + } + if(is_associative(ap)) + { + mp = (Namval_t*)((*arp->fun)(np,NIL(char*),NV_ACURRENT)); + if(!mp) + up = (union Value*)∓ + else if(nv_isattr(mp,NV_CHILD)) + { + if(wasundef && nv_isarray(mp->nvalue.np)) + nv_putsub(mp->nvalue.np,NIL(char*),ARRAY_UNDEF); + return(mp->nvalue.np); + } + else + up = &mp->nvalue; + } + else + { + if(!(ap->header.nelem&ARRAY_SCAN) && ap->cur >= ap->maxi) + ap = array_grow(np, ap, (int)ap->cur); + if(ap->cur>=ap->maxi) + errormsg(SH_DICT,ERROR_exit(1),e_subscript,nv_name(np)); + up = &(ap->val[ap->cur]); + if(up->np && array_isbit(ap->bits,ap->cur)) + { + if(wasundef && nv_isarray(up->np)) + nv_putsub(up->np,NIL(char*),ARRAY_UNDEF); + return(up->np); + } + } + np->nvalue.cp = up->cp; + if(!up->cp) + { + if(flag!=ARRAY_ASSIGN) + return(0); + ap->header.nelem++; + } + return(np); +} + +static Namfun_t *array_clone(Namval_t *np, Namval_t *mp, int flags, Namfun_t *fp) +{ + Namarr_t *ap = (Namarr_t*)fp; + Namval_t *nq, *mq; + char *name, *sub=0; + int nelem = ap->nelem,offset=staktell(); + struct index_array *aq, *ar; + if(nelem&ARRAY_NOCLONE) + return(0); + if(array_assoc(ap)) + nv_setarray(mp,ap->fun); + else + { + nv_putsub(mp,NIL(char*),ap->nelem); + if(aq=(struct index_array*)nv_arrayptr(mp)) + aq->bits = (unsigned char*)&aq->val[aq->maxi]; + } + if(!(nelem&(ARRAY_SCAN|ARRAY_UNDEF)) && (sub=nv_getsub(np))) + sub = strdup(sub); + ar = (struct index_array*)ap; + nv_onattr(mp,nv_isattr(np,NV_INTEGER|NV_UTOL|NV_LTOU|NV_LJUST|NV_RJUST|NV_ZFILL|NV_BINARY)); + nv_putsub(np,NIL(char*),ARRAY_SCAN); + do + { + if(array_assoc(ap)) + name = (char*)((*ap->fun)(np,NIL(char*),NV_ANAME)); + else + name = nv_getsub(np); + nv_putsub(mp,name,ARRAY_ADD); + if((!array_assoc(ap) && array_isbit(ar->bits,ar->cur) && (nq=np)) || + (array_assoc(ap) && (nq = (Namval_t*)((*ap->fun)(np,NIL(char*),NV_ACURRENT))) && nv_isattr(nq, NV_CHILD))) + { + sfprintf(stkstd,"%s[%s]",nv_name(mp),name); + stakputc(0); + mq = nv_search(stakptr(offset), sh.var_tree, NV_ADD); + stakseek(offset); + if(mq) + { + nv_clone(nq->nvalue.np,mq,0); + if(array_assoc(ap)) + { + nq = (Namval_t*)((*ap->fun)(mp,NIL(char*),NV_ACURRENT)); + nq->nvalue.np = mp; + nv_onattr(nq,NV_CHILD); + } + else if(aq) + { + array_setbit(aq->bits,aq->cur); + aq->val[aq->cur].np = mq; + } + } + } + else if(nv_isattr(np,NV_INTEGER)) + { + Sfdouble_t d= nv_getnum(np); + nv_putval(mp,(char*)&d,NV_LDOUBLE); + } + else + nv_putval(mp,nv_getval(np),NV_RDONLY); + } + while(nv_nextsub(np)); + if(sub) + { + nv_putsub(np,sub,0L); + free((void*)sub); + } + ap->nelem = nelem; + ((Namarr_t*)mp->nvfun)->nelem = nelem; + return(nv_stack(mp,(Namfun_t*)0)); +} + +static char *array_getval(Namval_t *np, Namfun_t *disc) +{ + register Namarr_t *ap = (Namarr_t*)disc; + register Namval_t *mp; + if((mp=array_find(np,ap,ARRAY_LOOKUP))!=np) + return(mp?nv_getval(mp):0); + return(nv_getv(np,&ap->hdr)); +} + +static Sfdouble_t array_getnum(Namval_t *np, Namfun_t *disc) +{ + register Namarr_t *ap = (Namarr_t*)disc; + register Namval_t *mp; + if((mp=array_find(np,ap,ARRAY_LOOKUP))!=np) + return(mp?nv_getnum(mp):0); + return(nv_getn(np,&ap->hdr)); +} + +static void array_putval(Namval_t *np, const char *string, int flags, Namfun_t *dp) +{ + register Namarr_t *ap = (Namarr_t*)dp; + register union Value *up; + register Namval_t *mp; + register struct index_array *aq = (struct index_array*)ap; + do + { + mp = array_find(np,ap,string?ARRAY_ASSIGN:ARRAY_DELETE); + if(mp && mp!=np) + nv_putval(mp, string, flags); + if(!string) + { + if(mp) + { + if(mp!=np) + { + dtdelete(sh.var_tree,(void*)mp); + free((void*)mp); + } + if(is_associative(ap)) + (*ap->fun)(np,NIL(char*),NV_ADELETE); + else if(mp!=np) + { + array_clrbit(aq->bits,aq->cur); + aq->val[aq->cur].cp = 0; + } + ap->nelem--; + } + if(array_elem(ap)==0 && ((ap->nelem&ARRAY_SCAN) || !is_associative(ap))) + { + if(is_associative(ap)) + (*ap->fun)(np, NIL(char*), NV_AFREE); + nv_offattr(np,NV_ARRAY); + } + if(!mp || mp!=np) + continue; + } + /* prevent empty string from being deleted */ + if(np->nvalue.cp == Empty) + np->nvalue.cp = 0; + nv_putv(np,string,flags,&ap->hdr); + up = array_getup(np,ap); + up->cp = np->nvalue.cp; + } + while(!string && nv_nextsub(np)); + if(!string && !nv_isattr(np,NV_ARRAY)) + { + Namfun_t *nfp; + if(nfp = nv_disc(np,(Namfun_t*)ap,NV_POP)) + free((void*)nfp); + } +} + +static const Namdisc_t array_disc = +{ + sizeof(Namarr_t), + array_putval, + array_getval, + array_getnum, + 0, + 0, + array_clone +}; + +/* + * Increase the size of the indexed array of elements in <arp> + * so that <maxi> is a legal index. If <arp> is 0, an array + * of the required size is allocated. A pointer to the + * allocated Namarr_t structure is returned. + * <maxi> becomes the current index of the array. + */ +static struct index_array *array_grow(Namval_t *np, register struct index_array *arp,int maxi) +{ + register struct index_array *ap; + register int i=0; + register int newsize = arsize(maxi+1); + if (maxi >= ARRAY_MAX) + errormsg(SH_DICT,ERROR_exit(1),e_subscript, fmtbase((long)maxi,10,0)); + ap = new_of(struct index_array,(newsize-1)*sizeof(union Value*)+newsize/CHAR_BIT); + memset((void*)ap,0,sizeof(*ap)); + ap->maxi = newsize; + ap->cur = maxi; + ap->bits = (unsigned char*)&ap->val[newsize]; + memset(ap->bits, 0, newsize/CHAR_BIT); + if(arp) + { + ap->header = arp->header; + for(;i < arp->maxi;i++) + ap->val[i].cp = arp->val[i].cp; + memcpy(ap->bits, arp->bits, (arp->maxi/CHAR_BIT)); + array_setptr(np,arp,ap); + free((void*)arp); + } + else + { + ap->header.fun = 0; + if((ap->val[0].cp=np->nvalue.cp)) + i++; + else if(nv_hasdisc(np,&array_disc)) + { + Namval_t *mp; + int offset = staktell(); + sfprintf(stkstd,"%s[0]",nv_name(np)); + stakputc(0); + mp = nv_search(stakptr(offset), sh.var_tree, NV_ADD); + stakseek(offset); + if(mp && nv_isnull(mp)) + { + nv_clone(np,mp,0); + ap->val[0].np = mp; + array_setbit(ap->bits,0); + } + i++; + } + else if(nv_isattr(np,NV_INTEGER)) + { + Sfdouble_t d= nv_getnum(np); + i++; + } + ap->header.nelem = i; + ap->header.hdr.nofree = 1; + ap->header.hdr.disc = &array_disc; + nv_disc(np,(Namfun_t*)ap, NV_LAST); + } + for(;i < newsize;i++) + ap->val[i].cp = 0; + return(ap); +} + +Namarr_t *nv_arrayptr(register Namval_t *np) +{ + if(nv_isattr(np,NV_ARRAY)) + return((Namarr_t*)nv_hasdisc(np, &array_disc)); + return(0); +} + +/* + * Verify that argument is an indexed array and convert to associative, + * freeing relevant storage + */ +static Namarr_t *nv_changearray(Namval_t *np, void *(*fun)(Namval_t*,const char*,int)) +{ + register Namarr_t *ap; + char numbuff[NUMSIZE+1]; + unsigned dot, digit, n; + union Value *up; + struct index_array *save_ap; + register char *string_index=&numbuff[NUMSIZE]; + numbuff[NUMSIZE]='\0'; + + if(!fun || !(ap = nv_arrayptr(np)) || is_associative(ap)) + return(NIL(Namarr_t*)); + + nv_stack(np,&ap->hdr); + save_ap = (struct index_array*)nv_stack(np,0); + ap = (Namarr_t*)((*fun)(np, NIL(char*), NV_AINIT)); + ap->nelem = 0; + ap->fun = fun; + nv_onattr(np,NV_ARRAY); + + for(dot = 0; dot < (unsigned)save_ap->maxi; dot++) + { + if(save_ap->val[dot].cp) + { + if ((digit = dot)== 0) + *--string_index = '0'; + else while( n = digit ) + { + digit /= 10; + *--string_index = '0' + (n-10*digit); + } + nv_putsub(np, string_index, ARRAY_ADD); + up = (union Value*)((*ap->fun)(np,NIL(char*),0)); + ap->nelem++; + up->cp = save_ap->val[dot].cp; + save_ap->val[dot].cp = 0; + } + string_index = &numbuff[NUMSIZE]; + } + free((void*)save_ap); + return(ap); +} + +/* + * set the associative array processing method for node <np> to <fun> + * The array pointer is returned if sucessful. + */ +Namarr_t *nv_setarray(Namval_t *np, void *(*fun)(Namval_t*,const char*,int)) +{ + register Namarr_t *ap; + char *value; + if(fun && (ap = nv_arrayptr(np))) + { + /* + * if it's already an indexed array, convert to + * associative structure + */ + if(!is_associative(ap)) + ap = nv_changearray(np, fun); + return(ap); + } + value = nv_getval(np); + if(fun && !ap && (ap = (Namarr_t*)((*fun)(np, NIL(char*), NV_AINIT)))) + { + /* check for preexisting initialization and save */ + ap->nelem = 0; + ap->fun = fun; + nv_onattr(np,NV_ARRAY); + if(value) + { + nv_putsub(np, "0", ARRAY_ADD); + nv_putval(np, value, 0); + } + return(ap); + } + return(NIL(Namarr_t*)); +} + +/* + * move parent subscript into child + */ +Namval_t *nv_arraychild(Namval_t *np, Namval_t *nq, int c) +{ + register Namarr_t *ap = nv_arrayptr(np); + union Value *up; + if(!(up = array_getup(np,ap))) + return((Namval_t*)0); + if(!nq) + return(array_find(np,ap, ARRAY_LOOKUP)); + np->nvalue.cp = up->cp; + ap->nelem |= ARRAY_NOCLONE; + nv_clone(np, nq, NV_NODISC); + nv_offattr(nq,NV_ARRAY); + ap->nelem &= ~ARRAY_NOCLONE; + if(ap->fun) + { + up->np = (Namval_t*)((*ap->fun)(np,NIL(char*),NV_ACURRENT)); + nv_onattr(up->np, NV_CHILD); + (up->np)->nvalue.np = nq; + } + else + { + struct index_array *aq = (struct index_array*)ap; + array_setbit(aq->bits,aq->cur); + up->np = nq; + } + if(c=='.') + nv_setvtree(nq); + return(nq); +} + +/* + * This routine sets subscript of <np> to the next element, if any. + * The return value is zero, if there are no more elements + * Otherwise, 1 is returned. + */ +int nv_nextsub(Namval_t *np) +{ + register struct index_array *ap = (struct index_array*)nv_arrayptr(np); + register unsigned dot; + if(!ap || !(ap->header.nelem&ARRAY_SCAN)) + return(0); + if(is_associative(ap)) + { + struct assoc_array *aq; + if(aq=(*ap->header.fun)(np,NIL(char*),NV_ANEXT)) + { + if(nv_isattr(aq->cur,NV_CHILD)) + nv_putsub(aq->cur->nvalue.np,NIL(char*),ARRAY_UNDEF); + return(1); + } + ap->header.nelem &= ~(ARRAY_SCAN|ARRAY_NOCHILD); + return(0); + } + for(dot=ap->cur+1; dot < (unsigned)ap->maxi; dot++) + { + if(ap->val[dot].cp) + { + ap->cur = dot; + if(array_isbit(ap->bits, dot)) + { + + if(ap->header.nelem&ARRAY_NOCHILD) + continue; + nv_putsub(ap->val[dot].np,NIL(char*),ARRAY_UNDEF); + } + return(1); + } + } + ap->header.nelem &= ~(ARRAY_SCAN|ARRAY_NOCHILD); + ap->cur = 0; + return(0); +} + +/* + * Set an array subscript for node <np> given the subscript <sp> + * An array is created if necessary. + * <mode> can be a number, plus or more of symbolic constants + * ARRAY_SCAN, ARRAY_UNDEF, ARRAY_ADD + * The node pointer is returned which can be NULL if <np> is + * not already array and the ARRAY_ADD bit of <mode> is not set. + * ARRAY_FILL sets the specified subscript to the empty string when + * ARRAY_ADD is specified and there is no value or sets all + * the elements up to the number specified if ARRAY_ADD is not specified + */ +Namval_t *nv_putsub(Namval_t *np,register char *sp,register long mode) +{ + register struct index_array *ap = (struct index_array*)nv_arrayptr(np); + register int size = (mode&ARRAY_MASK); + if(!ap || !ap->header.fun) + { + if(sp) + size = (int)sh_arith((char*)sp); + if(size >= ARRAY_MAX || (size < 0)) + { + errormsg(SH_DICT,ERROR_exit(1),e_subscript, nv_name(np)); + return(NIL(Namval_t*)); + } + if(!ap || size>=ap->maxi) + { + if(size==0 && !(mode&ARRAY_FILL)) + return(NIL(Namval_t*)); + if(sh.subshell) + np = sh_assignok(np,1); + ap = array_grow(np, ap,size); + nv_onattr(np,NV_ARRAY); + } + ap->header.nelem &= ~ARRAY_UNDEF; + ap->header.nelem |= (mode&(ARRAY_SCAN|ARRAY_NOCHILD|ARRAY_UNDEF)); + ap->cur = size; + if((mode&ARRAY_SCAN) && !ap->val[size].cp && !nv_nextsub(np)) + np = 0; + if(mode&ARRAY_FILL) + { + if(!(mode&ARRAY_ADD)) + { + int n; + for(n=0; n < size; n++) + { + if(!ap->val[n].cp) + ap->val[n].cp = Empty; + } + ap->header.nelem = n|(ap->header.nelem&(ARRAY_SCAN|ARRAY_UNDEF)); + if(n=ap->maxi-ap->maxi) + memset(&ap->val[size],0,n*sizeof(union Value)); + } + else if(!ap->val[size].cp) + { + if(sh.subshell) + np = sh_assignok(np,1); + ap->val[size].cp = Empty; + ap->header.nelem++; + } + } + else if(!(mode&ARRAY_SCAN)) + { + ap->header.nelem &= ~ARRAY_SCAN; + if(array_isbit(ap->bits,size)) + nv_putsub(ap->val[size].np,NIL(char*),ARRAY_UNDEF); + } + return((Namval_t*)np); + } + ap->header.nelem &= ~ARRAY_UNDEF; + if(!(mode&ARRAY_FILL)) + ap->header.nelem &= ~ARRAY_SCAN; + ap->header.nelem |= (mode&(ARRAY_SCAN|ARRAY_NOCHILD|ARRAY_UNDEF)); + if(sp) + { + union Value *up; + if(mode&ARRAY_SETSUB) + { + (*ap->header.fun)(np, sp, NV_ASETSUB); + return(np); + } + up = (union Value*)(*ap->header.fun)(np, sp, (mode&ARRAY_ADD)?NV_AADD:0); + if(up && !up->cp && (mode&ARRAY_ADD) && (mode&ARRAY_FILL)) + { + if(sh.subshell) + np = sh_assignok(np,1); + up->cp = Empty; + ap->header.nelem++; + } + } + else if(mode&ARRAY_SCAN) + (*ap->header.fun)(np,(char*)np,0); + else if(mode&ARRAY_UNDEF) + (*ap->header.fun)(np, "",0); + if((mode&ARRAY_SCAN) && !nv_nextsub(np)) + np = 0; + return(np); +} + +/* + * process an array subscript for node <np> given the subscript <cp> + * returns pointer to character after the subscript + */ +char *nv_endsubscript(Namval_t *np, register char *cp, int mode) +{ + register int count=1, quoted=0, c; + register char *sp = cp+1; + /* first find matching ']' */ + while(count>0 && (c= *++cp)) + { + if(c=='\\' && (!(mode&NV_SUBQUOTE) || (c=cp[1])=='[' || c==']' || c=='\\' || c=='*' || c=='@')) + { + quoted=1; + cp++; + } + else if(c=='[') + count++; + else if(c==']') + count--; + } + *cp = 0; + if(quoted) + { + /* strip escape characters */ + count = staktell(); + stakwrite(sp,1+cp-sp); + sh_trim(sp=stakptr(count)); + } + if(mode && np) + nv_putsub(np, sp, ARRAY_ADD|(cp[1]?ARRAY_FILL:mode&ARRAY_FILL)); + if(quoted) + stakseek(count); + *cp++ = c; + return(cp); +} + + +Namval_t *nv_opensub(Namval_t* np) +{ + register struct index_array *ap = (struct index_array*)nv_arrayptr(np); + if(ap && is_associative(ap)) + return((Namval_t*)((*ap->header.fun)(np,NIL(char*),NV_ACURRENT))); + return(NIL(Namval_t*)); +} + +char *nv_getsub(Namval_t* np) +{ + static char numbuff[NUMSIZE]; + register struct index_array *ap; + register unsigned dot, n; + register char *cp = &numbuff[NUMSIZE]; + if(!np || !(ap = (struct index_array*)nv_arrayptr(np))) + return(NIL(char*)); + if(is_associative(ap)) + return((char*)((*ap->header.fun)(np,NIL(char*),NV_ANAME))); + if((dot = ap->cur)==0) + *--cp = '0'; + else while(n=dot) + { + dot /= 10; + *--cp = '0' + (n-10*dot); + } + return(cp); +} + +/* + * If <np> is an indexed array node, the current subscript index + * returned, otherwise returns -1 + */ +int nv_aindex(register Namval_t* np) +{ + Namarr_t *ap = nv_arrayptr(np); + if(!ap || is_associative(ap)) + return(-1); + return(((struct index_array*)(ap))->cur&ARRAY_MASK); +} + + +/* + * This is the default implementation for associate arrays + */ +void *nv_associative(register Namval_t *np,const char *sp,int mode) +{ + register struct assoc_array *ap = (struct assoc_array*)nv_arrayptr(np); + register int type; + switch(mode) + { + case NV_AINIT: + if(ap = (struct assoc_array*)calloc(1,sizeof(struct assoc_array))) + { + ap->table = dtopen(&_Nvdisc,Dtbag); + ap->cur = 0; + ap->pos = 0; + ap->header.hdr.disc = &array_disc; + ap->header.hdr.nofree = 1; + nv_disc(np,(Namfun_t*)ap, NV_LAST); + } + return((void*)ap); + case NV_ADELETE: + if(ap->cur) + { + if(nv_isattr(ap->cur,NV_NOFREE)) + nv_offattr(ap->cur,NV_NOFREE); + else + { + dtdelete(ap->table,(void*)ap->cur); + free((void*)ap->cur); + ap->cur = 0; + } + } + return((void*)ap); + case NV_AFREE: + ap->pos = 0; + dtclose(ap->table); + return((void*)ap); + case NV_ANEXT: + if(!ap->pos) + { + if(!(ap->pos=ap->cur)) + ap->pos = (Namval_t*)dtfirst(ap->table); + } + else + ap->pos = ap->nextpos; + for(;ap->cur=ap->pos; ap->pos=ap->nextpos) + { + ap->nextpos = (Namval_t*)dtnext(ap->table,ap->pos); + if(ap->cur->nvalue.cp) + { + if((ap->header.nelem&ARRAY_NOCHILD) && nv_isattr(ap->cur,NV_CHILD)) + continue; + return((void*)ap); + } + } + return(NIL(void*)); + case NV_ASETSUB: + ap->cur = (Namval_t*)sp; + /* FALL THROUGH*/ + case NV_ACURRENT: + return((void*)ap->cur); + case NV_ANAME: + if(ap->cur) + return((void*)nv_name(ap->cur)); + return(NIL(void*)); + default: + if(sp) + { + if(sp==(char*)np) + { + ap->cur = 0; + return(0); + } + else if(!(ap->header.nelem&ARRAY_SCAN)) + ap->pos = 0; + type = nv_isattr(np,NV_PUBLIC&~(NV_ARRAY|NV_CHILD)); + if((np=nv_search(sp,ap->table,mode?NV_ADD:0)) && nv_isnull(np)) + nv_onattr(np,type); + ap->cur = np; + } + if(ap->cur) + return((void*)(&ap->cur->nvalue)); + else + return((void*)(&ap->cur)); + } +} + +/* + * Assign values to an array + */ +void nv_setvec(register Namval_t *np,int append,register int argc,register char *argv[]) +{ + int arg0=0; + struct index_array *ap=0; + if(nv_isarray(np)) + { + ap = (struct index_array*)nv_arrayptr(np); + if(ap && is_associative(ap)) + errormsg(SH_DICT,ERROR_exit(1),"cannot append index array to associate array %s",nv_name(np)); + } + if(append) + { + if(ap) + { + arg0 = ap->maxi; + while(--arg0>0 && ap->val[arg0].cp==0); + arg0++; + } + else if(!nv_isnull(np)) + arg0=1; + } + while(--argc >= 0) + { + if((argc+arg0)>0 || nv_isattr(np,NV_ARRAY)) + nv_putsub(np,NIL(char*),(long)argc+arg0); + nv_putval(np,argv[argc],0); + } +} + diff --git a/usr/src/lib/libshell/common/sh/bash.c b/usr/src/lib/libshell/common/sh/bash.c new file mode 100644 index 0000000000..ecb10472b8 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/bash.c @@ -0,0 +1,430 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +/* + * bash specific extensions + * originally provided by Karsten Fleischer + */ + +#include "defs.h" +#include "path.h" +#include "io.h" +#include "builtins.h" +#include "name.h" + +#ifndef BASH_MAJOR +# define BASH_MAJOR "1" +# define BASH_MINOR "0" +# define BASH_PATCH "0" +# define BASH_BUILD "0" +# define BASH_RELEASE "experimental" +#endif +#define BASH_VERSION BASH_MAJOR "." BASH_MINOR "." BASH_PATCH "(" BASH_BUILD ")-" BASH_RELEASE + + +void sh_applyopts(Shopt_t); + +extern const char bash_pre_rc[]; + +static char *login_files[4]; + +const char sh_bash1[] = + "[B?Enable brace group expansion. This option is only availabe in bash " + "compatibility mode. In ksh mode, brace group expansion is always on.]" + "[P?Do not follow symbolic links, use physical directory structure " + "instead. Only available in bash compatibility mode.]"; +const char sh_bash2[] = +"[l:login?Make the shell act as if it had been invoked as a login shell. " +"Only available if invoked as \bbash\b.]" +"[O]:?[shopt_option?\ashopt_option\a is one of the shell options accepted by " + "the \bshopt\b builtin. If \ashopt_option\a is present, \b-O\b sets " + "the value of that option; \b+O\b unsets it. If \ashopt_option\a is " + "not supplied, the names and values of the shell options accepted by " + "\bshopt\b are printed on the standard output. If the invocation " + "option is \b+O\b, the output is displayed in a format that may be " + "reused as input. Only available if invoked as \bbash\b.]" +"[01:init-file|rcfile]:[file?Execute commands from \afile\a instead of the " + "standard personal initialization file ~/.bashrc if the shell is " + "interactive. Only available if invoked as \bbash\b.]" +"[02:editing?For option compatibility with \bbash\b only. Ignored.]" +"[03:profile?Read either the system-wide startup file or any of the " + "personal initialization files. On by default for interactive " + "shells. Only available if invoked as \bbash\b.]" +"[04:rc?Read and execute the personal initialization file " + "\b$HOME/.bashrc\b. On by default for interactive shells. Only " + "available if invoked as \bbash\b.]" +"[05:posix?If invoked as \bbash\b, turn on POSIX compatibility. \bBash\b in " + "POSIX mode is not the same as \bksh\b.]" +"[06:version?Print version number and exit.]"; + +const char sh_optshopt[] = +"+[-1c?\n@(#)$Id: shopt (AT&T Research) 2003-02-13 $\n]" +"[-author?Karsten Fleischer <K.Fleischer@omnium.de>]" +USAGE_LICENSE +"[+NAME?shopt - set/unset variables controlling optional shell behavior]" +"[+DESCRIPTION?\bshopt\b sets or unsets variables controlling optional shell " + "behavior. With no options, or with the \b-p\b option, a list of all " + "settable options is displayed, with an indication of whether or not " + "each is set.]" +"[p?Causes output to be displayed in a form that may be reused as input.]" +"[s?Set each \aoptname\a.]" +"[u?Unset each \aoptname\a.]" +"[q?Suppress output (quiet mode). The return status indicates whether the " + "\aoptname\a is set or unset. If multiple \aoptname\a arguments are " + "given with \b-q\b, the return status is zero if all \aoptname\as are " + "enabled; non-zero otherwise.]" +"[o?Restricts the values of \aoptname\a to be those defined for the \b-o\b " + "option to the set builtin.]" +"[+?If either \b-s\b or \b-u\b is used with no \aoptname\a arguments, the " + "display is limited to those options which are set or unset.]" +"[+?\bshopt\b supports all bash options. Some settings do not have any effect " + "or are are always on and cannot be changed.]" +"[+?The value of \aoptname\a must be one of the following:]{" + "[+cdable_vars?If set, arguments to the \bcd\b command are " + "assumed to be names of variables whose values are to " + "be used if the usual \bcd\b proceeding fails.]" + "[+cdspell?Currently ignored.]" + "[+checkhash?Always on.]" + "[+checkwinsize?Currently ignored.]" + "[+cmdhist?Always on.]" + "[+dotglob?If set, include filenames beginning with a \b.\b " + "in the results of pathname expansion.]" + "[+execfail?Always on.]" + "[+expand_aliases?Always on.]" + "[+extglob?Enable extended pattern matching features.]" + "[+histappend?Always on.]" + "[+histreedit?If set and an edit mode is selected, the user " + "is given the opportunity to re-edit a failed history " + "substitution.]" + "[+histverify?If set and an edit mode is selected, the result " + "of a history substitution will not be executed " + "immediately but be placed in the edit buffer for " + "further modifications.]" + "[+hostcomplete?Currently ignored.]" + "[+huponexit?Currently ignored.]" + "[+interactive_comments?Always on.]" + "[+lithist?Always on.]" + "[+login_shell?This option is set if the shell is started as " + "a login shell. The value cannot be changed.]" + "[+mailwarn?Currently ignored.]" + "[+no_empty_cmd_completion?Always on.]" + "[+nocaseglob?Match filenames in a case-insensitive fashion " + "when performing filename expansion.]" + "[+nullglob?Allows filename patterns which match no files to " + "expand to a null string, rather than themselves.]" + "[+progcomp?Currently ignored.]" + "[+promptvars?Currently ignored.]" + "[+restricted_shell?This option is set if the shell is started " + "as a restricted shell. The value cannot be changed. " + "It is not reset during execution of startup files, " + "allowing the startup files to determine whether the " + "shell is restricted.]" + "[+shift_verbose?Currently ignored.]" + "[+sourcepath?If set, the \b.\b builtin uses the value of PATH " + "to find the directory containing the file supplied " + "as an argument.]" + "[+xpg_echo?If set, the \becho\b and \bprint\b builtins " + "expand backslash-escape sequences.]" +"}" +"\n" +"\n[optname ...]\n" +"\n" +"[+EXIT STATUS?]{" + "[+?The return status when listing options is zero if all \aoptnames\a " + "are enabled, non-zero otherwise. When setting or unsetting options, " + "the return status is zero unless an \aoptname\a is not a valid shell " + "option.]" +"}" + +"[+SEE ALSO?\bset\b(1)]" +; + +/* GLOBIGNORE discipline. Turn on SH_DOTGLOB on set, turn off on unset. */ + +static void put_globignore(register Namval_t* np, const char *val, int flags, Namfun_t *fp) +{ + if(val) + sh_onoption(SH_DOTGLOB); + else + sh_offoption(SH_DOTGLOB); + + nv_putv(np,val,flags,fp); +} + +const Namdisc_t SH_GLOBIGNORE_disc = { sizeof(Namfun_t), put_globignore }; + +/* FUNCNAME discipline */ + +struct funcname +{ + Namfun_t hdr; +}; + +static void put_funcname(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + /* bash silently returns with an error when FUNCNAME is set, + unsetting FUNCNAME is allowed */ + if(val && !(flags&NV_RDONLY)) + error_info.exit(1); + + nv_putv(np,val,flags,fp); +} + +const Namdisc_t SH_FUNCNAME_disc = { sizeof(struct funcname), put_funcname }; + +#define SET_SET 1 +#define SET_UNSET 2 +#define SET_NOARGS 4 + +/* shopt builtin */ + +int b_shopt(int argc,register char *argv[],void *extra) +{ + Shell_t *shp = (Shell_t*)extra; + int n, f, ret=0; + Shopt_t newflags=shp->options, opt; + int verbose=PRINT_SHOPT|PRINT_ALL|PRINT_NO_HEADER|PRINT_VERBOSE; + int setflag=0, quietflag=0, oflag=0; + memset(&opt,0,sizeof(opt)); +#if SHOPT_RAWONLY + on_option(&newflags,SH_VIRAW); +#endif + while((n = optget(argv,sh_optshopt))) + { + switch(n) + { + case 'p': + verbose&=~PRINT_VERBOSE; + break; + case 's': + case 'u': + setflag|=n=='s'?SET_SET:SET_UNSET; + if(setflag==(SET_SET|SET_UNSET)) + { + errormsg(SH_DICT,ERROR_ERROR,"cannot set and unset options simultaneously"); + error_info.errors++; + } + break; + case 'q': + quietflag=1; + break; + case 'o': + oflag=1; + verbose&=~PRINT_SHOPT; + break; + case ':': + errormsg(SH_DICT,2, "%s", opt_info.arg); + continue; + case '?': + errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); + return(-1); + } + } + if(error_info.errors) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage(NIL(char*))); + argc -= opt_info.index; + if(argc==0) + { + /* no args, -s => mask=current options, -u mask=~(current options) + else mask=all bits */ + if(setflag&SET_SET) + opt=newflags; + else if(setflag&SET_UNSET) + for(n=0;n<4;n++) + opt.v[n]=~newflags.v[n]; + else + memset(&opt,0xff,sizeof(opt)); + setflag=SET_NOARGS; + } + while(argc>0) + { + f=1; + n=sh_lookopt(argv[opt_info.index],&f); + if(n<=0||(setflag + && (is_option(&opt,SH_INTERACTIVE) + || is_option(&opt,SH_RESTRICTED) + || is_option(&opt,SH_RESTRICTED2) + || is_option(&opt,SH_BASH) + || is_option(&opt,SH_LOGIN_SHELL))) + ||(oflag&&(n&SH_BASHOPT))) + { + errormsg(SH_DICT,ERROR_ERROR, e_option, argv[opt_info.index]); + error_info.errors++; + ret=1; + } + else if(f) + on_option(&opt,n&0xff); + else + off_option(&opt,n&0xff); + opt_info.index++; + argc--; + } + if(setflag&(SET_SET|SET_UNSET)) + { + if(setflag&SET_SET) + { + if(sh_isoption(SH_INTERACTIVE)) + off_option(&opt,SH_NOEXEC); + if(is_option(&opt,SH_VI)||is_option(&opt,SH_EMACS)||is_option(&opt,SH_GMACS)) + { + off_option(&newflags,SH_VI); + off_option(&newflags,SH_EMACS); + off_option(&newflags,SH_GMACS); + } + for(n=0;n<4;n++) + newflags.v[n] |= opt.v[n]; + } + else if(setflag&SET_UNSET) + for(n=0;n<4;n++) + newflags.v[n] &= ~opt.v[n]; + sh_applyopts(newflags); + shp->options = newflags; + if(is_option(&newflags,SH_XTRACE)) + sh_trace(argv,1); + } + else if(!(setflag&SET_NOARGS)) /* no -s,-u but args, ret=0 if opt&mask==mask */ + { + for(n=0;n<4;n++) + ret+=((newflags.v[n]&opt.v[n])!=opt.v[n]); + } + if(!quietflag&&!(setflag&(SET_SET|SET_UNSET))) + sh_printopts(newflags,verbose,&opt); + return(ret); +} + +/* mode = 0: init, called two times + before parsing shell args with SH_PREINIT state turned on + second time after sh_init() is through and with SH_PREINIT state turned off + mode > 1: re-init + mode < 0: shutdown +*/ + +void bash_init(int mode) +{ + Sfio_t *iop; + Namval_t *np; + int n=0,xtrace,verbose; + if(mode>0) + goto reinit; + if(mode < 0) + { + /* termination code */ + if(sh_isoption(SH_LOGIN_SHELL) && !sh_isoption(SH_POSIX)) + sh_source(&sh, NiL, sh_mactry((char*)e_bash_logout)); + return; + } + + if(sh_isstate(SH_PREINIT)) + { /* pre-init stage */ + if(sh_isoption(SH_RESTRICTED)) + sh_onoption(SH_RESTRICTED2); + sh_onoption(SH_HISTORY2); + sh_onoption(SH_INTERACTIVE_COMM); + sh_onoption(SH_SOURCEPATH); + sh_onoption(SH_HISTAPPEND); + sh_onoption(SH_CMDHIST); + sh_onoption(SH_LITHIST); + sh_onoption(SH_NOEMPTYCMDCOMPL); + if(sh.login_sh==2) + sh_onoption(SH_LOGIN_SHELL); + if(strcmp(astconf("CONFORMANCE",0,0),"standard")==0) + sh_onoption(SH_POSIX); + if(strcmp(astconf("UNIVERSE",0,0),"att")==0) + sh_onoption(SH_XPG_ECHO); + else + sh_offoption(SH_XPG_ECHO); + if(strcmp(astconf("PATH_RESOLVE",0,0),"physical")==0) + sh_onoption(SH_PHYSICAL); + else + sh_offoption(SH_PHYSICAL); + + /* add builtins */ + sh_addbuiltin("shopt", b_shopt, &sh); + + /* set up some variables needed for --version + * needs to go here because --version option is parsed before the init script. + */ + if(np=nv_open("HOSTTYPE",sh.var_tree,0)) + nv_putval(np, BASH_HOSTTYPE, NV_NOFREE); + if(np=nv_open("MACHTYPE",sh.var_tree,0)) + nv_putval(np, BASH_MACHTYPE, NV_NOFREE); + if(np=nv_open("BASH_VERSION",sh.var_tree,0)) + nv_putval(np, BASH_VERSION, NV_NOFREE); + if(np=nv_open("BASH_VERSINFO",sh.var_tree,0)) + { + char *argv[7]; + argv[0] = BASH_MAJOR; + argv[1] = BASH_MINOR; + argv[2] = BASH_PATCH; + argv[3] = BASH_BUILD; + argv[4] = BASH_RELEASE; + argv[5] = BASH_MACHTYPE; + argv[6] = 0; + nv_setvec(np, 0, 6, argv); + nv_onattr(np,NV_RDONLY); + } + return; + } + + /* rest of init stage */ + + /* restrict BASH_ENV */ + if(np=nv_open("BASH_ENV",sh.var_tree,0)) + { + const Namdisc_t *dp = nv_discfun(NV_DCRESTRICT); + Namfun_t *fp = calloc(dp->dsize,1); + fp->disc = dp; + nv_disc(np, fp, 0); + } + + /* open GLOBIGNORE node */ + if(np=nv_open("GLOBIGNORE",sh.var_tree,0)) + { + const Namdisc_t *dp = &SH_GLOBIGNORE_disc; + Namfun_t *fp = calloc(dp->dsize,1); + fp->disc = dp; + nv_disc(np, fp, 0); + } + + /* set startup files */ + n=0; + if(!sh_isoption(SH_NOPROFILE)) + { + if(!sh_isoption(SH_POSIX)) + { + login_files[n++] = (char*)e_bash_profile; + login_files[n++] = (char*)e_bash_login; + } + login_files[n++] = (char*)e_profile; + } + sh.login_files = login_files; +reinit: + xtrace = sh_isoption(SH_XTRACE); + sh_offoption(SH_XTRACE); + verbose = sh_isoption(SH_VERBOSE); + sh_offoption(SH_VERBOSE); + if(np = nv_open("SHELLOPTS", sh.var_tree, NV_NOADD)) + nv_offattr(np,NV_RDONLY); + iop = sfopen(NULL, bash_pre_rc, "s"); + sh_eval(iop,0); + if(xtrace) + sh_offoption(SH_XTRACE); + if(verbose) + sh_offoption(SH_VERBOSE); +} diff --git a/usr/src/lib/libshell/common/sh/defs.c b/usr/src/lib/libshell/common/sh/defs.c new file mode 100644 index 0000000000..8b11839eb4 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/defs.c @@ -0,0 +1,47 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +/* + * Ksh - AT&T Labs + * Written by David Korn + * This file defines all the read/write shell global variables + */ + +#include "defs.h" +#include "jobs.h" +#include "shlex.h" +#include "edit.h" +#include "timeout.h" + +struct sh_static sh = {0}; +#ifdef __IMPORT__ + struct sh_static *_imp__sh = &sh; +#endif + +Dtdisc_t _Nvdisc = +{ + offsetof(Namval_t,nvname), -1 , 0, 0, 0, nv_compare +}; + +/* reserve room for writable state table */ +char *sh_lexstates[ST_NONE] = {0}; + +struct jobs job = {0}; +int32_t sh_mailchk = 600; + diff --git a/usr/src/lib/libshell/common/sh/deparse.c b/usr/src/lib/libshell/common/sh/deparse.c new file mode 100644 index 0000000000..74457d1092 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/deparse.c @@ -0,0 +1,584 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * David Korn + * AT&T Labs + * + * shell deparser + * + */ + +#include "defs.h" +#include "shnodes.h" +#include "test.h" + + +#define HUGE_INT (((unsigned)-1)>>1) +#define BEGIN 0 +#define MIDDLE 1 +#define END 2 +#define PRE 1 +#define POST 2 + + +/* flags that can be specified with p_tree() */ +#define NO_NEWLINE 1 +#define NEED_BRACE 2 +#define NO_BRACKET 4 + +static void p_comlist(const struct dolnod*,int); +static void p_arg(const struct argnod*, int endchar, int opts); +static void p_comarg(const struct comnod*); +static void p_keyword(const char*,int); +static void p_redirect(const struct ionod*); +static void p_switch(const struct regnod*); +static void here_body(const struct ionod*); +static void p_tree(const Shnode_t*,int); + +static int level; +static int begin_line; +static int end_line; +static char io_op[7]; +static char un_op[3] = "-?"; +static const struct ionod *here_doc; +static Sfio_t *outfile; +static const char *forinit = ""; + +extern void sh_deparse(Sfio_t*, const Shnode_t*,int); + +void sh_deparse(Sfio_t *out, const Shnode_t *t,int tflags) +{ + outfile = out; + p_tree(t,tflags); +} +/* + * print script corresponding to shell tree <t> + */ +static void p_tree(register const Shnode_t *t,register int tflags) +{ + register char *cp; + int save = end_line; + int needbrace = (tflags&NEED_BRACE); + tflags &= ~NEED_BRACE; + if(tflags&NO_NEWLINE) + end_line = ' '; + else + end_line = '\n'; + switch(t->tre.tretyp&COMMSK) + { + case TTIME: + if(t->tre.tretyp&COMSCAN) + p_keyword("!",BEGIN); + else + p_keyword("time",BEGIN); + if(t->par.partre) + p_tree(t->par.partre,tflags); + level--; + break; + + case TCOM: + if(begin_line && level>0) + sfnputc(outfile,'\t',level); + begin_line = 0; + p_comarg((struct comnod*)t); + break; + + case TSETIO: + if(t->tre.tretyp&FPCL) + tflags |= NEED_BRACE; + else + tflags = NO_NEWLINE|NEED_BRACE; + p_tree(t->fork.forktre,tflags); + p_redirect(t->fork.forkio); + break; + + case TFORK: + if(needbrace) + tflags |= NEED_BRACE; + if(t->tre.tretyp&(FAMP|FCOOP)) + { + tflags = NEED_BRACE|NO_NEWLINE; + end_line = ' '; + } + else if(t->fork.forkio) + tflags = NO_NEWLINE; + p_tree(t->fork.forktre,tflags); + if(t->fork.forkio) + p_redirect(t->fork.forkio); + if(t->tre.tretyp&FCOOP) + { + sfputr(outfile,"|&",'\n'); + begin_line = 1; + } + else if(t->tre.tretyp&FAMP) + { + sfputr(outfile,"&",'\n'); + begin_line = 1; + } + break; + + case TIF: + p_keyword("if",BEGIN); + p_tree(t->if_.iftre,0); + p_keyword("then",MIDDLE); + p_tree(t->if_.thtre,0); + if(t->if_.eltre) + { + p_keyword("else",MIDDLE); + p_tree(t->if_.eltre,0); + } + p_keyword("fi",END); + break; + + case TWH: + if(t->wh.whinc) + cp = "for"; + else if(t->tre.tretyp&COMSCAN) + cp = "until"; + else + cp = "while"; + p_keyword(cp,BEGIN); + if(t->wh.whinc) + { + struct argnod *arg = (t->wh.whtre)->ar.arexpr; + sfprintf(outfile,"(( %s; ",forinit); + forinit = ""; + sfputr(outfile,arg->argval,';'); + arg = (t->wh.whinc)->arexpr; + sfprintf(outfile," %s))\n",arg->argval); + } + else + p_tree(t->wh.whtre,0); + t = t->wh.dotre; + goto dolist; + + case TLST: + { + Shnode_t *tr = t->lst.lstrit; + if(tr->tre.tretyp==TWH && tr->wh.whinc && t->lst.lstlef->tre.tretyp==TARITH) + { + /* arithmetic for statement */ + struct argnod *init = (t->lst.lstlef)->ar.arexpr; + forinit= init->argval; + p_tree(t->lst.lstrit,tflags); + break; + } + if(needbrace) + p_keyword("{",BEGIN); + p_tree(t->lst.lstlef,0); + if(needbrace) + tflags = 0; + p_tree(t->lst.lstrit,tflags); + if(needbrace) + p_keyword("}",END); + break; + } + + case TAND: + cp = "&&"; + goto andor; + case TORF: + cp = "||"; + goto andor; + case TFIL: + cp = "|"; + andor: + { + int bracket = 0; + if(t->tre.tretyp&TTEST) + { + tflags |= NO_NEWLINE; + if(!(tflags&NO_BRACKET)) + { + p_keyword("[[",BEGIN); + tflags |= NO_BRACKET; + bracket=1; + } + } + p_tree(t->lst.lstlef,NEED_BRACE|NO_NEWLINE|(tflags&NO_BRACKET)); + sfputr(outfile,cp,here_doc?'\n':' '); + if(here_doc) + { + here_body(here_doc); + here_doc = 0; + } + level++; + p_tree(t->lst.lstrit,tflags|NEED_BRACE); + if(bracket) + p_keyword("]]",END); + level--; + break; + } + + case TPAR: + p_keyword("(",BEGIN); + p_tree(t->par.partre,0); + p_keyword(")",END); + break; + + case TARITH: + { + register struct argnod *ap = t->ar.arexpr; + if(begin_line && level) + sfnputc(outfile,'\t',level); + sfprintf(outfile,"(( %s ))%c",ap->argval,end_line); + if(!(tflags&NO_NEWLINE)) + begin_line=1; + break; + } + + case TFOR: + cp = ((t->tre.tretyp&COMSCAN)?"select":"for"); + p_keyword(cp,BEGIN); + sfputr(outfile,t->for_.fornam,' '); + if(t->for_.forlst) + { + sfputr(outfile,"in",' '); + tflags = end_line; + end_line = '\n'; + p_comarg(t->for_.forlst); + end_line = tflags; + } + else + sfputc(outfile,'\n'); + begin_line = 1; + t = t->for_.fortre; + dolist: + p_keyword("do",MIDDLE); + p_tree(t,0); + p_keyword("done",END); + break; + + case TSW: + p_keyword("case",BEGIN); + p_arg(t->sw.swarg,' ',0); + if(t->sw.swlst) + { + begin_line = 1; + sfputr(outfile,"in",'\n'); + tflags = end_line; + end_line = '\n'; + p_switch(t->sw.swlst); + end_line = tflags; + } + p_keyword("esac",END); + break; + + case TFUN: + if(t->tre.tretyp&FPOSIX) + { + sfprintf(outfile,"%s",t->funct.functnam); + p_keyword("()\n",BEGIN); + } + else + { + p_keyword("function",BEGIN); + tflags = (t->funct.functargs?' ':'\n'); + sfputr(outfile,t->funct.functnam,tflags); + if(t->funct.functargs) + { + tflags = end_line; + end_line = '\n'; + p_comarg(t->funct.functargs); + end_line = tflags; + } + } + begin_line = 1; + p_keyword("{\n",MIDDLE); + begin_line = 1; + p_tree(t->funct.functtre,0); + p_keyword("}",END); + break; + /* new test compound command */ + case TTST: + if(!(tflags&NO_BRACKET)) + p_keyword("[[",BEGIN); + if((t->tre.tretyp&TPAREN)==TPAREN) + { + p_keyword("(",BEGIN); + p_tree(t->lst.lstlef,NO_BRACKET|NO_NEWLINE); + p_keyword(")",END); + } + else + { + int flags = (t->tre.tretyp)>>TSHIFT; + if(t->tre.tretyp&TNEGATE) + sfputr(outfile,"!",' '); + if(t->tre.tretyp&TUNARY) + { + un_op[1] = flags; + sfputr(outfile,un_op,' '); + } + else + cp = ((char*)(shtab_testops+(flags&037)-1)->sh_name); + p_arg(&(t->lst.lstlef->arg),' ',0); + if(t->tre.tretyp&TBINARY) + { + sfputr(outfile,cp,' '); + p_arg(&(t->lst.lstrit->arg),' ',0); + } + } + if(!(tflags&NO_BRACKET)) + p_keyword("]]",END); + } + while(begin_line && here_doc) + { + here_body(here_doc); + here_doc = 0; + } + end_line = save; + return; +} + +/* + * print a keyword + * increment indent level for flag==BEGIN + * decrement indent level for flag==END + */ +static void p_keyword(const char *word,int flag) +{ + register int sep; + if(flag==END) + sep = end_line; + else if(*word=='[' || *word=='(') + sep = ' '; + else + sep = '\t'; + if(flag!=BEGIN) + level--; + if(begin_line && level) + sfnputc(outfile,'\t',level); + sfputr(outfile,word,sep); + if(sep=='\n') + begin_line=1; + else + begin_line=0; + if(flag!=END) + level++; +} + +static void p_arg(register const struct argnod *arg,register int endchar,int opts) +{ + register const char *cp; + register int flag; + do + { + if(!arg->argnxt.ap) + flag = endchar; + else if(opts&PRE) + { + /* case alternation lists in reverse order */ + p_arg(arg->argnxt.ap,'|',opts); + flag = endchar; + } + else if(opts) + flag = ' '; + cp = arg->argval; + if(*cp==0 && opts==POST && arg->argchn.ap) + { + /* compound assignment */ + struct fornod *fp=(struct fornod*)arg->argchn.ap; + sfprintf(outfile,"%s=(\n",fp->fornam); + sfnputc(outfile,'\t',++level); + p_tree(fp->fortre,0); + if(--level) + sfnputc(outfile,'\t',level); + sfputc(outfile,')'); + } + else if((arg->argflag&ARG_RAW) && (cp[1] || (*cp!='[' && *cp!=']'))) + cp = sh_fmtq(cp); + sfputr(outfile,cp,flag); + if(flag=='\n') + begin_line = 1; + arg = arg->argnxt.ap; + } + while((opts&POST) && arg); + return; +} + +static void p_redirect(register const struct ionod *iop) +{ + register char *cp; + register int iof,iof2; + for(;iop;iop=iop->ionxt) + { + iof=iop->iofile; + cp = io_op; + if(iop->iovname) + { + sfwrite(outfile,"(;",2); + sfputr(outfile,iop->iovname,')'); + cp++; + } + else + *cp = '0'+(iof&IOUFD); + if(iof&IOPUT) + { + if(*cp == '1' && !iop->iovname) + cp++; + io_op[1] = '>'; + } + else + { + if(*cp == '0' && !iop->iovname) + cp++; + io_op[1] = '<'; + } + io_op[2] = 0; + io_op[3] = 0; + if(iof&IOLSEEK) + { + io_op[1] = '#'; + if(iof&IOARITH) + strcpy(&io_op[3]," (("); + } + else if(iof&IOMOV) + io_op[2] = '&'; + else if(iof&(IORDW|IOAPP)) + io_op[2] = '>'; + else if(iof&IOCLOB) + io_op[2] = '|'; + if(iop->iodelim) + { + /* here document */ +#ifdef xxx + iop->iolink = (char*)here_doc; +#endif + here_doc = iop; + io_op[2] = '<'; +#ifdef future + if(iof&IOSTRIP) + io_op[3] = '-'; +#endif + } + sfputr(outfile,cp,' '); + if(iop->ionxt) + iof = ' '; + else + { + if((iof=end_line)=='\n') + begin_line = 1; + } + if((iof&IOLSEEK) && (iof&IOARITH)) + iof2 = iof, iof = ' '; + if(iop->iodelim) + { + if(!(iop->iofile&IODOC)) + sfwrite(outfile,"''",2); + sfputr(outfile,sh_fmtq(iop->iodelim),iof); + } + else if(iop->iofile&IORAW) + sfputr(outfile,sh_fmtq(iop->ioname),iof); + else + sfputr(outfile,iop->ioname,iof); + if((iof&IOLSEEK) && (iof&IOARITH)) + sfputr(outfile, "))", iof2); + } + return; +} + +static void p_comarg(register const struct comnod *com) +{ + register int flag = end_line; + if(com->comarg || com->comio) + flag = ' '; + if(com->comset) + p_arg(com->comset,flag,POST); + if(com->comarg) + { + if(!com->comio) + flag = end_line; + if(com->comtyp&COMSCAN) + p_arg(com->comarg,flag,POST); + else + p_comlist((struct dolnod*)com->comarg,flag); + } + if(com->comio) + p_redirect(com->comio); + return; +} + +static void p_comlist(const struct dolnod *dol,int endchar) +{ + register char *cp, *const*argv; + register int flag = ' ', special; + argv = dol->dolval+ARG_SPARE; + cp = *argv; + special = (*cp=='[' && cp[1]==0); + do + { + if(cp) + argv++; + else + cp = ""; + if(*argv==0) + { + if((flag=endchar)=='\n') + begin_line = 1; + special = (*cp==']' && cp[1]==0); + } + sfputr(outfile,special?cp:sh_fmtq(cp),flag); + special = 0; + } + while(cp = *argv); + return; +} + +static void p_switch(register const struct regnod *reg) +{ + if(level>1) + sfnputc(outfile,'\t',level-1); + p_arg(reg->regptr,')',PRE); + begin_line = 0; + sfputc(outfile,'\t'); + if(reg->regcom) + p_tree(reg->regcom,0); + level++; + if(reg->regflag) + p_keyword(";&",END); + else + p_keyword(";;",END); + if(reg->regnxt) + p_switch(reg->regnxt); + return; +} + +/* + * output here documents + */ +static void here_body(register const struct ionod *iop) +{ + Sfio_t *infile; +#ifdef xxx + if(iop->iolink) + here_body((struct inode*)iop->iolink); + iop->iolink = 0; +#endif + if(iop->iofile&IOSTRG) + infile = sfnew((Sfio_t*)0,iop->ioname,iop->iosize,-1,SF_STRING|SF_READ); + else + sfseek(infile=sh.heredocs,iop->iooffset,SEEK_SET); + sfmove(infile,outfile,iop->iosize,-1); + if(iop->iofile&IOSTRG) + sfclose(infile); + sfputr(outfile,iop->iodelim,'\n'); +} + diff --git a/usr/src/lib/libshell/common/sh/env.c b/usr/src/lib/libshell/common/sh/env.c new file mode 100644 index 0000000000..d7f7b0b6d7 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/env.c @@ -0,0 +1,255 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <ast.h> +#include <cdt.h> + +#define env_change() (++ast.env_serial) + +typedef struct _venv_ Evar_t; +struct _venv_ +{ + union + { + Evar_t *next; + char *ptr; + } un; + Dtlink_t link; + int index; +}; + +typedef struct _env_ +{ + Dt_t *dt; + Evar_t *freelist; + char **env; + int count; + int extra; + int max; + int flags; +} Env_t; + +#define _BLD_env 1 +#include <env.h> + +#define ENV_VALID 2 /* set if env is valid */ +#define ENV_PMALLOC 1 /* set if Evar_t->un.ptr *s malloced */ +#define ENV_VMALLOC 2 /* set of Evar_t was malloced */ +#define ENV_BITS 3 + +/* + * Compares the name portion of name=... only. + */ +static int compare(Dt_t *dt, Void_t* key1, Void_t* key2, Dtdisc_t* disc) +{ + register int c,d; + const unsigned char *s1=(unsigned const char*)key1; + const unsigned char *s2=(unsigned const char*)key2; + while((c= *s1++) && c!='=' && c==*s2) + s2++; + if(c=='=') + c = 0; + if((d=*s2)=='=') + d = 0; + return(c-d); +} + +static Dtdisc_t env_disc = +{ + 0, -1, + sizeof(char*), + 0, + 0, + compare +}; + +/* + * return a pointer to the environment in sorted order + * NULL is returned if there if there is nospace + */ +char **env_get(Env_t* ep) +{ + register Evar_t *vp; + register int n=ep->extra; + if(ep->flags&ENV_VALID) + return(ep->env+n); + if(ep->count > ep->max) + { + if(ep->flags&ENV_MALLOCED) + free((void*)ep->env); + if(!(ep->env = (char**)malloc(sizeof(char*)*(ep->count+1)))) + return(0); + ep->flags |= ENV_MALLOCED; + ep->max = ep->count; + } + for(vp=(Evar_t*)dtfirst(ep->dt);vp; vp=(Evar_t*)dtnext(ep->dt,vp)) + { + vp->index = (n<<ENV_BITS) | (vp->index&((1<<ENV_BITS)-1)); + ep->env[n++] = vp->un.ptr; + } + ep->env[n] = 0; + ep->flags |= ENV_VALID; + environ = ep->env+ep->extra; + return(ep->env+ep->extra); +} + +/* + * add name=value pair given by <str> to <ep> + * if malloced is set, the variable will be freed when reassigned + * The environment list may become invalidated + * Returns 1 for success, 0 for failure + */ +int env_add(Env_t *ep, const char *str, int flags) +{ + Evar_t *vp = (Evar_t*)dtmatch(ep->dt,(void*)str); + if(vp && strcmp(str,vp->un.ptr)==0) + return(1); + if(flags&ENV_STRDUP) + str = strdup(str); + if(vp) + { + if(vp->index&ENV_PMALLOC) + free((void*)vp->un.ptr); + vp->un.ptr = (char*)str; + if(ep->env && (ep->flags&ENV_VALID)) + ep->env[vp->index>>ENV_BITS] = vp->un.ptr; + } + else + { + ep->flags &= ~ENV_VALID; + if(vp = ep->freelist) + ep->freelist = vp->un.next; + else if(vp = newof((Evar_t*)0,Evar_t,2,0)) + { + vp->index = ENV_VMALLOC; + ep->freelist = (vp+1); + ep->freelist->un.next = 0; + } + else + return(0); + vp->un.ptr = (void*)str; + if(!(vp=dtinsert(ep->dt,vp))) + return(0); + ep->count++; + } + if(flags) + vp->index |= ENV_PMALLOC; + else + vp->index &= ~ENV_PMALLOC; + env_change(); + return(1); +} + +/* + * delete name from <ep> + * The environment list may become invalidated + * Returns 1 for success, 0 for if name is not present + */ +int env_delete(Env_t *ep, const char *str) +{ + Evar_t *vp = (Evar_t*)dtmatch(ep->dt,(void*)str); + if(!vp) + return(0); + ep->flags &= ~ENV_VALID; + if(vp->index&ENV_PMALLOC) + free((void*)vp->un.ptr); + dtdelete(ep->dt,vp); + vp->un.next = ep->freelist; + ep->freelist = vp; + env_change(); + return(1); +} + +/* + * open up a structure to support environment variables + * initialize with environment give by <envp> + * If <extra> > 0, <extra> slots will be left at beginning of + * environment list when env_get() is involed. + * If <extra>==ENV_USABLE, then the original environ can be + * used and returned. Otherwise, a new one will be returned + */ +Env_t *env_open(char **envp, int extra) +{ + char **env; + Env_t *ep; + Evar_t *vp; + int n=2; + if(!(ep = newof((Env_t*)0,Env_t,1,0))) + return(0); + if(!(ep->dt = dtopen(&env_disc,Dtoset))) + return(0); + if(env=envp) + { + while(*env++); + n = (env+2)-envp; + } + if(extra==ENV_STABLE) + { + ep->env = envp; + ep->max = n-1; + } + else + ep->count = ep->extra = extra; + ep->freelist = vp = newof((Evar_t*)0,Evar_t,n,0); + vp->index = ENV_VMALLOC; + while(--n>0) + { + vp->un.next = (vp+1); + vp++; + } + vp->un.next = 0; + if(env) + { + for(env=envp; *env; env++) + env_add(ep,*env,0); + } + return(ep); +} + +/* + * close <ep> and free up all space used by it + */ +void env_close(Env_t *ep) +{ + Evar_t *vp, *vpnext,*top; + if(ep->env && (ep->flags&ENV_MALLOCED)) + free((void*)ep->env); + for(vp=(Evar_t*)dtfirst(ep->dt);vp; vp=vpnext) + { + vpnext = (Evar_t*)dtnext(ep->dt,vp); + env_delete(ep,vp->un.ptr); + } + for(top=0,vp = ep->freelist; vp; vp = vpnext) + { + vpnext = vp->un.next; + if(vp->index&ENV_VMALLOC) + { + vp->un.next = top; + top = vp; + } + } + for(vp=top; vp; vp = vpnext) + { + vpnext = vp->un.next; + free((void*)vp); + } + dtclose(ep->dt); +} diff --git a/usr/src/lib/libshell/common/sh/expand.c b/usr/src/lib/libshell/common/sh/expand.c new file mode 100644 index 0000000000..097ed0da66 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/expand.c @@ -0,0 +1,465 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * File name expansion + * + * David Korn + * AT&T Labs + * + */ + +#if KSHELL +# include "defs.h" +# include "variables.h" +# include "test.h" +#else +# include <ast.h> +# include <setjmp.h> +#endif /* KSHELL */ +#include <glob.h> +#include <ls.h> +#include <stak.h> +#include <ast_dir.h> +#include "io.h" +#include "path.h" + +#if !SHOPT_BRACEPAT +# define SHOPT_BRACEPAT 0 +#endif + +#if KSHELL +# define argbegin argnxt.cp + static const char *sufstr; + static int suflen; + static int scantree(Dt_t*,const char*, struct argnod**); +#else +# define sh_sigcheck() (0) +# define sh_access access +# define suflen 0 +#endif /* KSHELL */ + + +/* + * This routine builds a list of files that match a given pathname + * Uses external routine strgrpmatch() to match each component + * A leading . must match explicitly + * + */ + +#ifndef GLOB_AUGMENTED +# define GLOB_AUGMENTED 0 +#endif + +#define GLOB_RESCAN 1 +#define globptr() ((struct glob*)membase) + +static struct glob *membase; + +#if GLOB_VERSION >= 20010916L +static char *nextdir(glob_t *gp, char *dir) +{ + Pathcomp_t *pp = (Pathcomp_t*)gp->gl_handle; + if(!dir) + pp = path_get(""); + else + pp = pp->next; + gp->gl_handle = (void*)pp; + if(pp) + return(pp->name); + return(0); +} +#endif + +int path_expand(const char *pattern, struct argnod **arghead) +{ + glob_t gdata; + register struct argnod *ap; + register glob_t *gp= &gdata; + register int flags,extra=0; +#if SHOPT_BASH + register int off; + register char *sp, *cp, *cp2; +#endif + memset(gp,0,sizeof(gdata)); + flags = GLOB_AUGMENTED|GLOB_NOCHECK|GLOB_NOSORT|GLOB_STACK|GLOB_LIST|GLOB_DISC; + if(sh_isoption(SH_MARKDIRS)) + flags |= GLOB_MARK; + if(sh_isoption(SH_GLOBSTARS)) + flags |= GLOB_STARSTAR; +#if SHOPT_BASH +#if 0 + if(sh_isoption(SH_BASH) && !sh_isoption(SH_EXTGLOB)) + flags &= ~GLOB_AUGMENTED; +#endif + if(sh_isoption(SH_NULLGLOB)) + flags &= ~GLOB_NOCHECK; + if(sh_isoption(SH_NOCASEGLOB)) + flags |= GLOB_ICASE; +#endif + if(sh_isstate(SH_COMPLETE)) + { +#if KSHELL + extra += scantree(sh.alias_tree,pattern,arghead); + extra += scantree(sh.fun_tree,pattern,arghead); +# if GLOB_VERSION >= 20010916L + gp->gl_nextdir = nextdir; +# endif +#endif /* KSHELL */ + flags |= GLOB_COMPLETE; + flags &= ~GLOB_NOCHECK; + } +#if SHOPT_BASH + if(off = staktell()) + sp = stakfreeze(0); + if(sh_isoption(SH_BASH)) + { + /* + * For bash, FIGNORE is a colon separated list of suffixes to + * ignore when doing filename/command completion. + * GLOBIGNORE is similar to ksh FIGNORE, but colon separated + * instead of being an augmented shell pattern. + * Generate shell patterns out of those here. + */ + if(sh_isstate(SH_FCOMPLETE)) + cp=nv_getval(nv_scoped(FIGNORENOD)); + else + { + static Namval_t *GLOBIGNORENOD; + if(!GLOBIGNORENOD) + GLOBIGNORENOD = nv_open("GLOBIGNORE",sh.var_tree,0); + cp=nv_getval(nv_scoped(GLOBIGNORENOD)); + } + if(cp) + { + flags |= GLOB_AUGMENTED; + stakputs("@("); + if(!sh_isstate(SH_FCOMPLETE)) + { + stakputs(cp); + for(cp=stakptr(off); *cp; cp++) + if(*cp == ':') + *cp='|'; + } + else + { + cp2 = strtok(cp, ":"); + if(!cp2) + cp2=cp; + do + { + stakputc('*'); + stakputs(cp2); + if(cp2 = strtok(NULL, ":")) + { + *(cp2-1)=':'; + stakputc('|'); + } + } while(cp2); + } + stakputc(')'); + gp->gl_fignore = stakfreeze(1); + } + else if(!sh_isstate(SH_FCOMPLETE) && sh_isoption(SH_DOTGLOB)) + gp->gl_fignore = ""; + } + else +#endif + gp->gl_fignore = nv_getval(nv_scoped(FIGNORENOD)); + if(suflen) + gp->gl_suffix = sufstr; + gp->gl_intr = &sh.trapnote; + suflen = 0; + if(memcmp(pattern,"~(N",3)==0) + flags &= ~GLOB_NOCHECK; + glob(pattern, flags, 0, gp); +#if SHOPT_BASH + if(off) + stakset(sp,off); + else + stakseek(0); +#endif + sh_sigcheck(); + for(ap= (struct argnod*)gp->gl_list; ap; ap = ap->argnxt.ap) + { + ap->argchn.ap = ap->argnxt.ap; + if(!ap->argnxt.ap) + ap->argchn.ap = *arghead; + } + if(gp->gl_list) + *arghead = (struct argnod*)gp->gl_list; + return(gp->gl_pathc+extra); +} + +#if KSHELL + +/* + * scan tree and add each name that matches the given pattern + */ +static int scantree(Dt_t *tree, const char *pattern, struct argnod **arghead) +{ + register Namval_t *np; + register struct argnod *ap; + register int nmatch=0; + register char *cp; + np = (Namval_t*)dtfirst(tree); + for(;np && !nv_isnull(np);(np = (Namval_t*)dtnext(tree,np))) + { + if(strmatch(cp=nv_name(np),pattern)) + { + ap = (struct argnod*)stakseek(ARGVAL); + stakputs(cp); + ap = (struct argnod*)stakfreeze(1); + ap->argbegin = NIL(char*); + ap->argchn.ap = *arghead; + ap->argflag = ARG_RAW|ARG_MAKE; + *arghead = ap; + nmatch++; + } + } + return(nmatch); +} + +/* + * file name completion + * generate the list of files found by adding an suffix to end of name + * The number of matches is returned + */ + +int path_complete(const char *name,register const char *suffix, struct argnod **arghead) +{ + sufstr = suffix; + suflen = strlen(suffix); + return(path_expand(name,arghead)); +} + +#endif + +#if SHOPT_BRACEPAT + +static int checkfmt(Sfio_t* sp, void* vp, Sffmt_t* fp) +{ + return -1; +} + +int path_generate(struct argnod *todo, struct argnod **arghead) +/*@ + assume todo!=0; + return count satisfying count>=1; +@*/ +{ + register char *cp; + register int brace; + register struct argnod *ap; + struct argnod *top = 0; + struct argnod *apin; + char *pat, *rescan; + char *format; + char comma, range=0; + int first, last, incr, count = 0; + char tmp[32], end[1]; + todo->argchn.ap = 0; +again: + apin = ap = todo; + todo = ap->argchn.ap; + cp = ap->argval; + range = comma = brace = 0; + /* first search for {...,...} */ + while(1) switch(*cp++) + { + case '{': + if(brace++==0) + pat = cp; + break; + case '}': + if(--brace>0) + break; + if(brace==0 && comma && *cp!='(') + goto endloop1; + comma = brace = 0; + break; + case '.': + if(brace==1 && *cp=='.') + { + char *endc; + incr = 1; + if(isdigit(*pat) || *pat=='+' || *pat=='-') + { + first = strtol(pat,&endc,0); + if(endc==(cp-1)) + { + last = strtol(cp+1,&endc,0); + if(*endc=='.' && endc[1]=='.') + incr = strtol(endc+2,&endc,0); + else if(last<first) + incr = -1; + if(incr) + { + if(*endc=='%') + { + Sffmt_t fmt; + memset(&fmt, 0, sizeof(fmt)); + fmt.version = SFIO_VERSION; + fmt.form = endc; + fmt.extf = checkfmt; + sfprintf(sfstdout, "%!", &fmt); + if(!(fmt.flags&(SFFMT_LLONG|SFFMT_LDOUBLE))) + switch (fmt.fmt) + { + case 'c': + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + format = endc; + endc = fmt.form; + break; + } + } + else + format = "%d"; + if(*endc=='}') + { + cp = endc+1; + range = 2; + goto endloop1; + } + } + } + } + else if((cp[2]=='}' || cp[2]=='.' && cp[3]=='.') && ((*pat>='a' && *pat<='z' && cp[1]>='a' && cp[1]<='z') || (*pat>='A' && *pat<='Z' && cp[1]>='A' && cp[1]<='Z'))) + { + first = *pat; + last = cp[1]; + cp += 2; + if(*cp=='.') + { + incr = strtol(cp+2,&endc,0); + cp = endc; + } + else if(first>last) + incr = -1; + if(incr && *cp=='}') + { + cp++; + range = 1; + goto endloop1; + } + } + cp++; + } + break; + case ',': + if(brace==1) + comma = 1; + break; + case '\\': + cp++; + break; + case 0: + /* insert on stack */ + ap->argchn.ap = top; + top = ap; + if(todo) + goto again; + for(; ap; ap=apin) + { + apin = ap->argchn.ap; + if(!sh_isoption(SH_NOGLOB)) + brace=path_expand(ap->argval,arghead); + else + { + ap->argchn.ap = *arghead; + *arghead = ap; + brace=1; + } + if(brace) + { + count += brace; + (*arghead)->argflag |= ARG_MAKE; + } + } + return(count); + } +endloop1: + rescan = cp; + cp = pat-1; + *cp = 0; + while(1) + { + brace = 0; + if(range) + { + if(range==1) + { + pat[0] = first; + cp = &pat[1]; + } + else + { + *(rescan - 1) = 0; + sfsprintf(pat=tmp,sizeof(tmp),format,first); + *(rescan - 1) = '}'; + *(cp = end) = 0; + } + if(incr*(first+incr) > last*incr) + *cp = '}'; + else + first += incr; + } + /* generate each pattern and put on the todo list */ + else while(1) switch(*++cp) + { + case '\\': + cp++; + break; + case '{': + brace++; + break; + case ',': + if(brace==0) + goto endloop2; + break; + case '}': + if(--brace<0) + goto endloop2; + } + endloop2: + brace = *cp; + *cp = 0; + sh_sigcheck(); + ap = (struct argnod*)stakseek(ARGVAL); + ap->argflag = ARG_RAW; + ap->argchn.ap = todo; + stakputs(apin->argval); + stakputs(pat); + stakputs(rescan); + todo = ap = (struct argnod*)stakfreeze(1); + if(brace == '}') + break; + if(!range) + pat = cp+1; + } + goto again; +} + +#endif /* SHOPT_BRACEPAT */ diff --git a/usr/src/lib/libshell/common/sh/fault.c b/usr/src/lib/libshell/common/sh/fault.c new file mode 100644 index 0000000000..2d71736187 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/fault.c @@ -0,0 +1,573 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Fault handling routines + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <fcin.h> +#include "io.h" +#include "history.h" +#include "shnodes.h" +#include "variables.h" +#include "jobs.h" +#include "path.h" + +#define abortsig(sig) (sig==SIGABRT || sig==SIGBUS || sig==SIGILL || sig==SIGSEGV) + +static char indone; + +#if !_std_malloc +# include <vmalloc.h> +#endif +#if defined(VMFL) && (VMALLOC_VERSION>=20031205L) + /* + * This exception handler is called after vmalloc() unlocks the region + */ + static int malloc_done(Vmalloc_t* vm, int type, Void_t* val, Vmdisc_t* dp) + { + dp->exceptf = 0; + sh_exit(SH_EXITSIG); + return(0); + } +#endif + +/* + * Most signals caught or ignored by the shell come here +*/ +void sh_fault(register int sig) +{ + register int flag=0; + register char *trap; + register struct checkpt *pp = (struct checkpt*)sh.jmplist; + int action=0; + /* reset handler */ + if(!(sig&SH_TRAP)) + signal(sig, sh_fault); + sig &= ~SH_TRAP; +#ifdef SIGWINCH + if(sig==SIGWINCH) + { + int rows=0, cols=0; + int32_t v; + astwinsize(2,&rows,&cols); + if(v = cols) + nv_putval(COLUMNS, (char*)&v, NV_INT32); + if(v = rows) + nv_putval(LINES, (char*)&v, NV_INT32); + } +#endif /* SIGWINCH */ + if(sh.savesig) + { + /* critical region, save and process later */ + sh.savesig = sig; + return; + } + + /* handle ignored signals */ + if((trap=sh.st.trapcom[sig]) && *trap==0) + return; + flag = sh.sigflag[sig]&~SH_SIGOFF; + if(!trap) + { + if(flag&SH_SIGIGNORE) + return; + if(flag&SH_SIGDONE) + { + void *ptr=0; + if((flag&SH_SIGINTERACTIVE) && sh_isstate(SH_INTERACTIVE) && !sh_isstate(SH_FORKED) && ! sh.subshell) + { + /* check for TERM signal between fork/exec */ + if(sig==SIGTERM && job.in_critical) + sh.trapnote |= SH_SIGTERM; + return; + } + sh.lastsig = sig; + sigrelease(sig); + if(pp->mode < SH_JMPFUN) + pp->mode = SH_JMPFUN; + else + pp->mode = SH_JMPEXIT; + if(sig==SIGABRT || (abortsig(sig) && (ptr = malloc(1)))) + { + if(ptr) + free(ptr); + if(!sh.subshell) + sh_done(sig); + sh_exit(SH_EXITSIG); + } + /* mark signal and continue */ + sh.trapnote |= SH_SIGSET; + if(sig < sh.sigmax) + sh.sigflag[sig] |= SH_SIGSET; +#if defined(VMFL) && (VMALLOC_VERSION>=20031205L) + if(abortsig(sig)) + { + /* abort inside malloc, process when malloc returns */ + /* VMFL defined when using vmalloc() */ + Vmdisc_t* dp = vmdisc(Vmregion,0); + if(dp) + dp->exceptf = malloc_done; + } +#endif + return; + } + } + errno = 0; + if(pp->mode==SH_JMPCMD) + sh.lastsig = sig; + if(trap) + { + /* + * propogate signal to foreground group + */ + if(sig==SIGHUP && job.curpgid) + killpg(job.curpgid,SIGHUP); + flag = SH_SIGTRAP; + } + else + { + sh.lastsig = sig; + flag = SH_SIGSET; +#ifdef SIGTSTP + if(sig==SIGTSTP) + { + sh.trapnote |= SH_SIGTSTP; + if(pp->mode==SH_JMPCMD && sh_isstate(SH_STOPOK)) + { + sigrelease(sig); + sh_exit(SH_EXITSIG); + flag = 0; + } + } +#endif /* SIGTSTP */ + } +#ifdef ERROR_NOTIFY + if((error_info.flags&ERROR_NOTIFY) && sh.bltinfun) + action = (*sh.bltinfun)(-sig,(char**)0,(void*)0); +#endif + if(action>0) + return; + sh.trapnote |= flag; + if(sig < sh.sigmax) + sh.sigflag[sig] |= flag; + if(pp->mode==SH_JMPCMD && sh_isstate(SH_STOPOK)) + { + if(action<0) + return; + sigrelease(sig); + sh_exit(SH_EXITSIG); + } +} + +/* + * initialize signal handling + */ +void sh_siginit(void) +{ + register int sig, n=SIGTERM+1; + register const struct shtable2 *tp = shtab_signals; + sig_begin(); + /* find the largest signal number in the table */ + while(*tp->sh_name) + { + if((sig=tp->sh_number&((1<<SH_SIGBITS)-1))>n && sig<SH_TRAP) + n = sig; + tp++; + } +#if defined(_SC_SIGRT_MAX) && defined(_SIGRTMAX) + if((sig=SIGRTMAX+1)>n && sig<SH_TRAP) + n = sig; +#endif + sh.sigmax = n; + sh.st.trapcom = (char**)calloc(n,sizeof(char*)); + sh.sigflag = (unsigned char*)calloc(n,1); + sh.sigmsg = (char**)calloc(n,sizeof(char*)); + for(tp=shtab_signals; sig=tp->sh_number; tp++) + { + n = (sig>>SH_SIGBITS); + if((sig &= ((1<<SH_SIGBITS)-1)) > sh.sigmax) + continue; + sig--; +#if defined(_SC_SIGRT_MIN) && defined(_SIGRTMIN) + if(sig==_SIGRTMIN) + sig = SIGRTMIN; +#endif +#if defined(_SC_SIGRT_MAX) && defined(_SIGRTMAX) + if(sig==_SIGRTMAX) + sig = SIGRTMAX; +#endif + if(sig>=0) + { + sh.sigflag[sig] = n; + if(*tp->sh_name) + sh.sigmsg[sig] = (char*)tp->sh_value; + } + } +} + +/* + * Turn on trap handler for signal <sig> + */ +void sh_sigtrap(register int sig) +{ + register int flag; + void (*fun)(int); + sh.st.otrapcom = 0; + if(sig==0) + sh_sigdone(); + else if(!((flag=sh.sigflag[sig])&(SH_SIGFAULT|SH_SIGOFF))) + { + /* don't set signal if already set or off by parent */ + if((fun=signal(sig,sh_fault))==SIG_IGN) + { + signal(sig,SIG_IGN); + flag |= SH_SIGOFF; + } + else + { + flag |= SH_SIGFAULT; + if(sig==SIGALRM && fun!=SIG_DFL && fun!=sh_fault) + signal(sig,fun); + } + flag &= ~(SH_SIGSET|SH_SIGTRAP); + sh.sigflag[sig] = flag; + } +} + +/* + * set signal handler so sh_done is called for all caught signals + */ +void sh_sigdone(void) +{ + register int flag, sig = sh.sigmax; + sh.sigflag[0] |= SH_SIGFAULT; + while(--sig>0) + { + flag = sh.sigflag[sig]; + if((flag&(SH_SIGDONE|SH_SIGIGNORE|SH_SIGINTERACTIVE)) && !(flag&(SH_SIGFAULT|SH_SIGOFF))) + sh_sigtrap(sig); + } +} + +/* + * Restore to default signals + * Free the trap strings if mode is non-zero + * If mode>1 then ignored traps cause signal to be ignored + */ +void sh_sigreset(register int mode) +{ + register char *trap; + register int flag, sig=sh.st.trapmax; + while(sig-- > 0) + { + if(trap=sh.st.trapcom[sig]) + { + flag = sh.sigflag[sig]&~(SH_SIGTRAP|SH_SIGSET); + if(*trap) + { + if(mode) + free(trap); + sh.st.trapcom[sig] = 0; + } + else if(sig && mode>1) + { + signal(sig,SIG_IGN); + flag &= ~SH_SIGFAULT; + flag |= SH_SIGOFF; + } + sh.sigflag[sig] = flag; + } + } + for(sig=SH_DEBUGTRAP;sig>=0;sig--) + { + if(trap=sh.st.trap[sig]) + { + if(mode) + free(trap); + sh.st.trap[sig] = 0; + } + + } + sh.st.trapcom[0] = 0; + if(mode) + sh.st.trapmax = 0; + sh.trapnote=0; +} + +/* + * free up trap if set and restore signal handler if modified + */ +void sh_sigclear(register int sig) +{ + register int flag = sh.sigflag[sig]; + register char *trap; + sh.st.otrapcom=0; + if(!(flag&SH_SIGFAULT)) + return; + flag &= ~(SH_SIGTRAP|SH_SIGSET); + if(trap=sh.st.trapcom[sig]) + { + free(trap); + sh.st.trapcom[sig]=0; + } + sh.sigflag[sig] = flag; +} + +/* + * check for traps + */ + +void sh_chktrap(void) +{ + register int sig=sh.st.trapmax; + register char *trap; + if(!sh.trapnote) + sig=0; + sh.trapnote &= ~SH_SIGTRAP; + /* execute errexit trap first */ + if(sh_isstate(SH_ERREXIT) && sh.exitval) + { + int sav_trapnote = sh.trapnote; + sh.trapnote &= ~SH_SIGSET; + if(sh.st.trap[SH_ERRTRAP]) + sh_trap(sh.st.trap[SH_ERRTRAP],0); + sh.trapnote = sav_trapnote; + if(sh_isoption(SH_ERREXIT)) + { + struct checkpt *pp = (struct checkpt*)sh.jmplist; + pp->mode = SH_JMPEXIT; + sh_exit(sh.exitval); + } + } + if(sh.sigflag[SIGALRM]&SH_SIGALRM) + sh_timetraps(); + while(--sig>=0) + { + if(sh.sigflag[sig]&SH_SIGTRAP) + { + sh.sigflag[sig] &= ~SH_SIGTRAP; + if(trap=sh.st.trapcom[sig]) + sh_trap(trap,0); + } + } +} + + +/* + * parse and execute the given trap string, stream or tree depending on mode + * mode==0 for string, mode==1 for stream, mode==2 for parse tree + */ +int sh_trap(const char *trap, int mode) +{ + int jmpval, savxit = sh.exitval; + int was_history = sh_isstate(SH_HISTORY); + int was_verbose = sh_isstate(SH_VERBOSE); + int staktop = staktell(); + char *savptr = stakfreeze(0); + struct checkpt buff; + Fcin_t savefc; + fcsave(&savefc); + sh_offstate(SH_HISTORY); + sh_offstate(SH_VERBOSE); + sh.intrap++; + sh_pushcontext(&buff,SH_JMPTRAP); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval == 0) + { + if(mode==2) + sh_exec((Shnode_t*)trap,sh_isstate(SH_ERREXIT)); + else + { + Sfio_t *sp; + if(mode) + sp = (Sfio_t*)trap; + else + sp = sfopen(NIL(Sfio_t*),trap,"s"); + sh_eval(sp,0); + } + } + else if(indone) + { + if(jmpval==SH_JMPSCRIPT) + indone=0; + else + { + if(jmpval==SH_JMPEXIT) + savxit = sh.exitval; + jmpval=SH_JMPTRAP; + } + } + sh_popcontext(&buff); + sh.intrap--; + sfsync(sh.outpool); + if(jmpval!=SH_JMPEXIT && jmpval!=SH_JMPFUN) + sh.exitval=savxit; + stakset(savptr,staktop); + fcrestore(&savefc); + if(was_history) + sh_onstate(SH_HISTORY); + if(was_verbose) + sh_onstate(SH_VERBOSE); + exitset(); + if(jmpval>SH_JMPTRAP) + siglongjmp(*sh.jmplist,jmpval); + return(sh.exitval); +} + +/* + * exit the current scope and jump to an earlier one based on pp->mode + */ +void sh_exit(register int xno) +{ + register struct checkpt *pp = (struct checkpt*)sh.jmplist; + register int sig=0; + register Sfio_t* pool; + sh.exitval=xno; + if(xno==SH_EXITSIG) + sh.exitval |= (sig=sh.lastsig); +#ifdef SIGTSTP + if(sh.trapnote&SH_SIGTSTP) + { + /* ^Z detected by the shell */ + sh.trapnote = 0; + sh.sigflag[SIGTSTP] = 0; + if(!sh.subshell && sh_isstate(SH_MONITOR) && !sh_isstate(SH_STOPOK)) + return; + if(sh_isstate(SH_TIMING)) + return; + /* Handles ^Z for shell builtins, subshells, and functs */ + sh.lastsig = 0; + sh_onstate(SH_MONITOR); + sh_offstate(SH_STOPOK); + sh.trapnote = 0; + if(!sh.subshell && (sig=sh_fork(0,NIL(int*)))) + { + job.curpgid = 0; + job.parent = (pid_t)-1; + job_wait(sig); + job.parent = 0; + sh.sigflag[SIGTSTP] = 0; + /* wait for child to stop */ + sh.exitval = (SH_EXITSIG|SIGTSTP); + /* return to prompt mode */ + pp->mode = SH_JMPERREXIT; + } + else + { + if(sh.subshell) + sh_subfork(); + /* child process, put to sleep */ + sh_offstate(SH_STOPOK); + sh_offstate(SH_MONITOR); + sh.sigflag[SIGTSTP] = 0; + /* stop child job */ + killpg(job.curpgid,SIGTSTP); + /* child resumes */ + job_clear(); + sh.forked = 1; + sh.exitval = (xno&SH_EXITMASK); + return; + } + } +#endif /* SIGTSTP */ + /* unlock output pool */ + sh_offstate(SH_NOTRACK); + if(!(pool=sfpool(NIL(Sfio_t*),sh.outpool,SF_WRITE))) + pool = sh.outpool; /* can't happen? */ + sfclrlock(pool); +#ifdef SIGPIPE + if(sh.lastsig==SIGPIPE) + sfpurge(pool); +#endif /* SIGPIPE */ + sfclrlock(sfstdin); + if(!pp) + sh_done(sig); + sh.prefix = 0; + if(pp->mode == SH_JMPSCRIPT && !pp->prev) + sh_done(sig); + siglongjmp(pp->buff,pp->mode); +} + +/* + * This is the exit routine for the shell + */ + +void sh_done(register int sig) +{ + register char *t; + register int savxit = sh.exitval; + sh.trapnote = 0; + indone=1; + if(sig==0) + sig = sh.lastsig; + if(sh.userinit) + (*sh.userinit)(-1); + if(t=sh.st.trapcom[0]) + { + sh.st.trapcom[0]=0; /*should free but not long */ + sh.oldexit = savxit; + sh_trap(t,0); + savxit = sh.exitval; + } + else + { + /* avoid recursive call for set -e */ + sh_offstate(SH_ERREXIT); + sh_chktrap(); + } + sh_freeup(); +#if SHOPT_ACCT + sh_accend(); +#endif /* SHOPT_ACCT */ +#if SHOPT_VSH || SHOPT_ESH + if(sh_isoption(SH_EMACS)||sh_isoption(SH_VI)||sh_isoption(SH_GMACS)) + tty_cooked(-1); +#endif +#ifdef JOBS + if((sh_isoption(SH_INTERACTIVE) && sh.login_sh) || (!sh_isoption(SH_INTERACTIVE) && (sig==SIGHUP))) + job_walk(sfstderr,job_terminate,SIGHUP,NIL(char**)); +#endif /* JOBS */ + job_close(); + if(nv_search("VMTRACE", sh.var_tree,0)) + strmatch((char*)0,(char*)0); + sfsync((Sfio_t*)sfstdin); + sfsync((Sfio_t*)sh.outpool); + sfsync((Sfio_t*)sfstdout); + if(sig) + { + /* generate fault termination code */ + signal(sig,SIG_DFL); + sigrelease(sig); + kill(getpid(),sig); + pause(); + } +#if SHOPT_KIA + if(sh_isoption(SH_NOEXEC)) + kiaclose(); +#endif /* SHOPT_KIA */ + exit(savxit&SH_EXITMASK); +} + diff --git a/usr/src/lib/libshell/common/sh/fcin.c b/usr/src/lib/libshell/common/sh/fcin.c new file mode 100644 index 0000000000..aea98364f5 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/fcin.c @@ -0,0 +1,211 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Routines to implement fast character input + * + * David Korn + * AT&T Labs + * + */ + +#include <ast.h> +#include <sfio.h> +#include <error.h> +#include <fcin.h> + +Fcin_t _Fcin = {0}; + +/* + * open stream <f> for fast character input + */ +int fcfopen(register Sfio_t* f) +{ + register int n; + char *buff; + Fcin_t save; + errno = 0; + _Fcin.fcbuff = _Fcin.fcptr; + _Fcin._fcfile = f; + fcsave(&save); + if(!(buff=(char*)sfreserve(f,SF_UNBOUND,SF_LOCKR))) + { + fcrestore(&save); + _Fcin.fcchar = 0; + _Fcin.fcptr = _Fcin.fcbuff = &_Fcin.fcchar; + _Fcin.fclast = 0; + _Fcin._fcfile = (Sfio_t*)0; + return(EOF); + } + n = sfvalue(f); + fcrestore(&save); + sfread(f,buff,0); + _Fcin.fcoff = sftell(f);; + buff = (char*)sfreserve(f,SF_UNBOUND,SF_LOCKR); + _Fcin.fclast = (_Fcin.fcptr=_Fcin.fcbuff=(unsigned char*)buff)+n; + if(sffileno(f) >= 0) + *_Fcin.fclast = 0; + return(n); +} + + +/* + * With _Fcin.fcptr>_Fcin.fcbuff, the stream pointer is advanced and + * If _Fcin.fclast!=0, performs an sfreserve() for the next buffer. + * If a notify function has been set, it is called + * If last is non-zero, and the stream is a file, 0 is returned when + * the previous character is a 0 byte. + */ +int fcfill(void) +{ + register int n; + register Sfio_t *f; + register unsigned char *last=_Fcin.fclast, *ptr=_Fcin.fcptr; + if(!(f=fcfile())) + { + /* see whether pointer has passed null byte */ + if(ptr>_Fcin.fcbuff && *--ptr==0) + _Fcin.fcptr=ptr; + else + _Fcin.fcoff = 0; + return(0); + } + if(last) + { + if( ptr<last && ptr>_Fcin.fcbuff && *(ptr-1)==0) + return(0); + if(_Fcin.fcchar) + *last = _Fcin.fcchar; + if(ptr > last) + _Fcin.fcptr = ptr = last; + } + if((n = ptr-_Fcin.fcbuff) && _Fcin.fcfun) + (*_Fcin.fcfun)(f,(const char*)_Fcin.fcbuff,n); + sfread(f, (char*)_Fcin.fcbuff, n); + _Fcin.fcoff +=n; + _Fcin._fcfile = 0; + if(!last) + return(0); + else if(fcfopen(f) < 0) + return(EOF); + return(*_Fcin.fcptr++); +} + +/* + * Synchronize and close the current stream + */ +int fcclose(void) +{ + register unsigned char *ptr; + if(_Fcin.fclast==0) + return(0); + if((ptr=_Fcin.fcptr)>_Fcin.fcbuff && *(ptr-1)==0) + _Fcin.fcptr--; + if(_Fcin.fcchar) + *_Fcin.fclast = _Fcin.fcchar; + _Fcin.fclast = 0; + _Fcin.fcleft = 0; + return(fcfill()); +} + +/* + * Set the notify function that is called for each fcfill() + */ +void fcnotify(void (*fun)(Sfio_t*,const char*,int)) +{ + _Fcin.fcfun = fun; +} + +#ifdef __EXPORT__ +# define extern __EXPORT__ +#endif + +#undef fcsave +extern void fcsave(Fcin_t *fp) +{ + *fp = _Fcin; +} + +#undef fcrestore +extern void fcrestore(Fcin_t *fp) +{ + _Fcin = *fp; +} + +struct Extra +{ + unsigned char buff[2*MB_LEN_MAX]; + unsigned char *next; +}; + +int fcmbstate(const char *state, int *s, int *len) +{ + static struct Extra extra; + register int i, c, n; + if(_Fcin.fcleft) + { + if((c = mbsize(extra.next)) < 0) + c = 1; + if((_Fcin.fcleft -= c) <=0) + { + _Fcin.fcptr = (unsigned char*)fcfirst() - _Fcin.fcleft; + _Fcin.fcleft = 0; + } + *len = c; + if(c==1) + *s = state[*extra.next++]; + else if(c==0) + _Fcin.fcleft = 0; + else + { + c = mbchar(extra.next); + *s = state['a']; + } + return(c); + } + switch(*len = mbsize(_Fcin.fcptr)) + { + case -1: + if(_Fcin._fcfile && (n=(_Fcin.fclast-_Fcin.fcptr)) < MB_LEN_MAX) + { + memcmp(extra.buff, _Fcin.fcptr, n); + _Fcin.fcptr = _Fcin.fclast; + for(i=n; i < MB_LEN_MAX+n; i++) + { + if((extra.buff[i] = fcgetc(c))==0) + break; + } + _Fcin.fcleft = n; + extra.next = extra.buff; + return(fcmbstate(state,s,len)); + } + *len = 1; + /* fall through */ + case 0: + case 1: + *s = state[c=fcget()]; + break; + default: + c = mbchar(_Fcin.fcptr); + *s = state['a']; + } + return(c); +} + diff --git a/usr/src/lib/libshell/common/sh/init.c b/usr/src/lib/libshell/common/sh/init.c new file mode 100644 index 0000000000..fbb6e5c6bc --- /dev/null +++ b/usr/src/lib/libshell/common/sh/init.c @@ -0,0 +1,1465 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * + * Shell initialization + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <stak.h> +#include <ctype.h> +#include <ccode.h> +#include <pwd.h> +#include "variables.h" +#include "path.h" +#include "fault.h" +#include "name.h" +#include "edit.h" +#include "jobs.h" +#include "io.h" +#include "shlex.h" +#include "builtins.h" +#include "FEATURE/time" +#include "FEATURE/dynamic" +#include "lexstates.h" +#include "version.h" + +#if SHOPT_MULTIBYTE + char e_version[] = "\n@(#)$Id: Version M "SH_RELEASE" $\0\n"; +#else + char e_version[] = "\n@(#)$Id: Version "SH_RELEASE" $\0\n"; +#endif /* SHOPT_MULTIBYTE */ + +#if SHOPT_BASH + extern void bash_init(int); +#endif + +#define RANDMASK 0x7fff +#ifndef CLK_TCK +# define CLK_TCK 60 +#endif /* CLK_TCK */ + +#ifndef environ + extern char **environ; +#endif + +#undef getconf +#define getconf(x) strtol(astconf(x,NiL,NiL),NiL,0) + +struct seconds +{ + Namfun_t hdr; + Shell_t *sh; +}; + +struct rand +{ + Namfun_t hdr; + Shell_t *sh; + int32_t rand_last; +}; + +struct ifs +{ + Namfun_t hdr; + Shell_t *sh; + Namval_t *ifsnp; +}; + +struct shell +{ + Namfun_t hdr; + Shell_t *sh; +}; + +struct match +{ + Namfun_t hdr; + char *val; + char *rval; + int vsize; + int nmatch; + int lastsub; + int match[2*(MATCH_MAX+1)]; +}; + +typedef struct _init_ +{ + Shell_t *sh; +#if SHOPT_FS_3D + Namfun_t VPATH_init; +#endif /* SHOPT_FS_3D */ + struct ifs IFS_init; + struct shell PATH_init; +#ifdef PATH_BFPATH + struct shell FPATH_init; + struct shell CDPATH_init; +#endif + struct shell SHELL_init; + struct shell ENV_init; + struct shell VISUAL_init; + struct shell EDITOR_init; + struct shell OPTINDEX_init; + struct seconds SECONDS_init; + struct rand RAND_init; + struct shell LINENO_init; + struct shell L_ARG_init; + struct match SH_MATCH_init; +#ifdef _hdr_locale + struct shell LC_TYPE_init; + struct shell LC_NUM_init; + struct shell LC_COLL_init; + struct shell LC_MSG_init; + struct shell LC_ALL_init; + struct shell LANG_init; +#endif /* _hdr_locale */ +} Init_t; + +static void env_init(Shell_t*); +static Init_t *nv_init(Shell_t*); +static Dt_t *inittree(Shell_t*,const struct shtable2*); + +#ifdef _WINIX +# define EXE "?(.exe)" +#else +# define EXE +#endif + +static int rand_shift; + + +/* + * Invalidate all path name bindings + */ +static void rehash(register Namval_t *np,void *data) +{ + NOT_USED(data); + nv_onattr(np,NV_NOALIAS); +} + +/* + * out of memory routine for stak routines + */ +static char *nospace(int unused) +{ + NOT_USED(unused); + errormsg(SH_DICT,ERROR_exit(3),e_nospace); + return(NIL(char*)); +} + +/* Trap for VISUAL and EDITOR variables */ +static void put_ed(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + register const char *cp, *name=nv_name(np); + if(*name=='E' && nv_getval(nv_scoped(VISINOD))) + goto done; + sh_offoption(SH_VI); + sh_offoption(SH_EMACS); + sh_offoption(SH_GMACS); + if(!(cp=val) && (*name=='E' || !(cp=nv_getval(nv_scoped(EDITNOD))))) + goto done; + /* turn on vi or emacs option if editor name is either*/ + cp = path_basename(cp); + if(strmatch(cp,"*[Vv][Ii]*")) + sh_onoption(SH_VI); + else if(strmatch(cp,"*gmacs*")) + sh_onoption(SH_GMACS); + else if(strmatch(cp,"*macs*")) + sh_onoption(SH_EMACS); +done: + nv_putv(np, val, flags, fp); +} + +/* Trap for OPTINDEX */ +static void put_optindex(Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + Shell_t *shp = ((struct shell*)fp)->sh; + shp->st.opterror = shp->st.optchar = 0; + nv_putv(np, val, flags, fp); +} + +static Sfdouble_t nget_optindex(register Namval_t* np, Namfun_t *fp) +{ + return((Sfdouble_t)*np->nvalue.lp); +} + +/* Trap for restricted variables FPATH, PATH, SHELL, ENV */ +static void put_restricted(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + Shell_t *shp = ((struct shell*)fp)->sh; + int path_scoped = 0; +#ifdef PATH_BFPATH + Pathcomp_t *pp; + char *name = nv_name(np); +#endif + if(!(flags&NV_RDONLY) && sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,nv_name(np)); + if(np==PATHNOD || (path_scoped=(strcmp(name,PATHNOD->nvname)==0))) + { +#ifndef PATH_BFPATH + shp->lastpath = 0; +#endif + nv_scan(shp->track_tree,rehash,(void*)0,NV_TAGGED,NV_TAGGED); + if(path_scoped && !val) + val = PATHNOD->nvalue.cp; + } + if(val && !(flags&NV_RDONLY) && np->nvalue.cp && strcmp(val,np->nvalue.cp)==0) + return; +#ifdef PATH_BFPATH + if(shp->pathlist && np==FPATHNOD) + shp->pathlist = (void*)path_unsetfpath((Pathcomp_t*)shp->pathlist); +#endif + nv_putv(np, val, flags, fp); +#ifdef PATH_BFPATH + if(shp->pathlist) + { + val = np->nvalue.cp; + if(np==PATHNOD || path_scoped) + pp = (void*)path_addpath((Pathcomp_t*)shp->pathlist,val,PATH_PATH); + else if(val && np==FPATHNOD) + pp = (void*)path_addpath((Pathcomp_t*)shp->pathlist,val,PATH_FPATH); + else + return; + if(shp->pathlist = (void*)pp) + pp->shp = shp; + if(!val && (flags&NV_NOSCOPE)) + { + Namval_t *mp = dtsearch(shp->var_tree,np); + if(mp && (val=nv_getval(mp))) + nv_putval(mp,val,NV_RDONLY); + } +#if 0 +sfprintf(sfstderr,"%d: name=%s val=%s\n",getpid(),name,val); +path_dump((Pathcomp_t*)shp->pathlist); +#endif + } +#endif +} + +#ifdef PATH_BFPATH +static void put_cdpath(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + Pathcomp_t *pp; + Shell_t *shp = ((struct shell*)fp)->sh; + nv_putv(np, val, flags, fp); + if(!shp->cdpathlist) + return; + val = np->nvalue.cp; + pp = (void*)path_addpath((Pathcomp_t*)shp->cdpathlist,val,PATH_CDPATH); + if(shp->cdpathlist = (void*)pp) + pp->shp = shp; +} +#endif + +#ifdef _hdr_locale + /* + * This function needs to be modified to handle international + * error message translations + */ +#if ERROR_VERSION >= 20000101L + static char* msg_translate(const char* catalog, const char* message) + { + NOT_USED(catalog); + return((char*)message); + } +#else + static char* msg_translate(const char* message, int type) + { + NOT_USED(type); + return((char*)message); + } +#endif + + /* Trap for LC_ALL, LC_TYPE, LC_MESSAGES, LC_COLLATE and LANG */ + static void put_lang(Namval_t* np,const char *val,int flags,Namfun_t *fp) + { + int type; + char *lc_all = nv_getval(LCALLNOD); + char *name = nv_name(np); + if(name==(LCALLNOD)->nvname) + type = LC_ALL; + else if(name==(LCTYPENOD)->nvname) + type = LC_CTYPE; + else if(name==(LCMSGNOD)->nvname) + type = LC_MESSAGES; + else if(name==(LCCOLLNOD)->nvname) + type = LC_COLLATE; + else if(name==(LCNUMNOD)->nvname) + type = LC_NUMERIC; + else if(name==(LANGNOD)->nvname && (!lc_all || *lc_all==0)) + type = LC_ALL; + else + type= -1; + if(sh_isstate(SH_INIT) && type>=0 && type!=LC_ALL && lc_all && *lc_all) + type= -1; + if(type>=0) + { + if(!setlocale(type,val?val:"")) + { + if(!sh_isstate(SH_INIT) || sh.login_sh==0) + errormsg(SH_DICT,0,e_badlocale,val); + return; + } + } + if(CC_NATIVE==CC_ASCII && (type==LC_ALL || type==LC_CTYPE)) + { + if(sh_lexstates[ST_BEGIN]!=sh_lexrstates[ST_BEGIN]) + free((void*)sh_lexstates[ST_BEGIN]); + if(ast.locale.set&(1<<AST_LC_CTYPE)) + { + register int c; + char *state[4]; + sh_lexstates[ST_BEGIN] = state[0] = (char*)malloc(4*(1<<CHAR_BIT)); + memcpy(state[0],sh_lexrstates[ST_BEGIN],(1<<CHAR_BIT)); + sh_lexstates[ST_NAME] = state[1] = state[0] + (1<<CHAR_BIT); + memcpy(state[1],sh_lexrstates[ST_NAME],(1<<CHAR_BIT)); + sh_lexstates[ST_DOL] = state[2] = state[1] + (1<<CHAR_BIT); + memcpy(state[2],sh_lexrstates[ST_DOL],(1<<CHAR_BIT)); + sh_lexstates[ST_BRACE] = state[3] = state[2] + (1<<CHAR_BIT); + memcpy(state[3],sh_lexrstates[ST_BRACE],(1<<CHAR_BIT)); + for(c=0; c<(1<<CHAR_BIT); c++) + { + if(state[0][c]!=S_REG) + continue; + if(state[2][c]!=S_ERR) + continue; + if(isblank(c)) + { + state[0][c]=0; + state[1][c]=S_BREAK; + state[2][c]=S_BREAK; + continue; + } + if(!isalpha(c)) + continue; + state[0][c]=S_NAME; + if(state[1][c]==S_REG) + state[1][c]=0; + state[2][c]=S_ALP; + if(state[3][c]==S_ERR) + state[3][c]=0; + } + } + else + { + sh_lexstates[ST_BEGIN]=(char*)sh_lexrstates[ST_BEGIN]; + sh_lexstates[ST_NAME]=(char*)sh_lexrstates[ST_NAME]; + sh_lexstates[ST_DOL]=(char*)sh_lexrstates[ST_DOL]; + sh_lexstates[ST_BRACE]=(char*)sh_lexrstates[ST_BRACE]; + } + } +#if ERROR_VERSION < 20000101L + if(type==LC_ALL || type==LC_MESSAGES) + error_info.translate = msg_translate; +#endif + nv_putv(np, val, flags, fp); + } +#endif /* _hdr_locale */ + +/* Trap for IFS assignment and invalidates state table */ +static void put_ifs(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + register struct ifs *ip = (struct ifs*)fp; + ip->ifsnp = 0; + if(val != np->nvalue.cp) + nv_putv(np, val, flags, fp); + +} + +/* + * This is the lookup function for IFS + * It keeps the sh.ifstable up to date + */ +static char* get_ifs(register Namval_t* np, Namfun_t *fp) +{ + register struct ifs *ip = (struct ifs*)fp; + register char *cp, *value; + register int c,n; + register Shell_t *shp = ip->sh; + value = nv_getv(np,fp); + if(np!=ip->ifsnp) + { + ip->ifsnp = np; + memset(shp->ifstable,0,(1<<CHAR_BIT)); + if(cp=value) + { +#if SHOPT_MULTIBYTE + while(n=mbsize(cp),c= *(unsigned char*)cp) +#else + while(c= *(unsigned char*)cp++) +#endif /* SHOPT_MULTIBYTE */ + { +#if SHOPT_MULTIBYTE + cp++; + if(n>1) + { + cp += (n-1); + shp->ifstable[c] = S_MBYTE; + continue; + } +#endif /* SHOPT_MULTIBYTE */ + n = S_DELIM; + if(c== *cp) + cp++; + else if(c=='\n') + n = S_NL; + else if(isspace(c)) + n = S_SPACE; + shp->ifstable[c] = n; + } + } + else + { + shp->ifstable[' '] = shp->ifstable['\t'] = S_SPACE; + shp->ifstable['\n'] = S_NL; + } + } + return(value); +} + +/* + * these functions are used to get and set the SECONDS variable + */ +#ifdef timeofday +# define dtime(tp) ((double)((tp)->tv_sec)+1e-6*((double)((tp)->tv_usec))) +# define tms timeval +#else +# define dtime(tp) (((double)times(tp))/sh.lim.clk_tck) +# define timeofday(a) +#endif + +static void put_seconds(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + double d; + struct tms tp; + if(!val) + { + nv_stack(np, NIL(Namfun_t*)); + nv_unset(np); + return; + } + if(!np->nvalue.dp) + { + nv_setsize(np,3); + np->nvalue.dp = new_of(double,0); + } + nv_putv(np, val, flags, fp); + d = *np->nvalue.dp; + timeofday(&tp); + *np->nvalue.dp = dtime(&tp)-d; +} + +static char* get_seconds(register Namval_t* np, Namfun_t *fp) +{ + register int places = nv_size(np); + struct tms tp; + double d, offset = (np->nvalue.dp?*np->nvalue.dp:0); + NOT_USED(fp); + timeofday(&tp); + d = dtime(&tp)- offset; + sfprintf(sh.strbuf,"%.*f",places,d); + return(sfstruse(sh.strbuf)); +} + +static Sfdouble_t nget_seconds(register Namval_t* np, Namfun_t *fp) +{ + struct tms tp; + double offset = (np->nvalue.dp?*np->nvalue.dp:0); + NOT_USED(fp); + timeofday(&tp); + return(dtime(&tp)- offset); +} + +/* + * These three functions are used to get and set the RANDOM variable + */ +static void put_rand(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + struct rand *rp = (struct rand*)fp; + register long n; + if(!val) + { + nv_stack(np, NIL(Namfun_t*)); + nv_unset(np); + return; + } + if(flags&NV_INTEGER) + n = *(double*)val; + else + n = sh_arith(val); + srand((int)(n&RANDMASK)); + rp->rand_last = -1; + if(!np->nvalue.lp) + np->nvalue.lp = &rp->rand_last; +} + +/* + * get random number in range of 0 - 2**15 + * never pick same number twice in a row + */ +static Sfdouble_t nget_rand(register Namval_t* np, Namfun_t *fp) +{ + register long cur, last= *np->nvalue.lp; + NOT_USED(fp); + do + cur = (rand()>>rand_shift)&RANDMASK; + while(cur==last); + *np->nvalue.lp = cur; + return((Sfdouble_t)cur); +} + +static char* get_rand(register Namval_t* np, Namfun_t *fp) +{ + register long n = nget_rand(np,fp); + return(fmtbase(n, 10, 0)); +} + +/* + * These three routines are for LINENO + */ +static Sfdouble_t nget_lineno(Namval_t* np, Namfun_t *fp) +{ + double d=1; + if(error_info.line >0) + d = error_info.line; + else if(error_info.context && error_info.context->line>0) + d = error_info.context->line; + NOT_USED(np); + NOT_USED(fp); + return(d); +} + +static void put_lineno(Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + register long n; + Shell_t *shp = ((struct shell*)fp)->sh; + if(!val) + { + nv_stack(np, NIL(Namfun_t*)); + nv_unset(np); + return; + } + if(flags&NV_INTEGER) + n = *(double*)val; + else + n = sh_arith(val); + shp->st.firstline += nget_lineno(np,fp)+1-n; +} + +static char* get_lineno(register Namval_t* np, Namfun_t *fp) +{ + register long n = nget_lineno(np,fp); + return(fmtbase(n, 10, 0)); +} + +static char* get_lastarg(Namval_t* np, Namfun_t *fp) +{ + NOT_USED(np); + return(sh.lastarg); +} + +static void put_lastarg(Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + if(flags&NV_INTEGER) + { + sfprintf(sh.strbuf,"%.*g",12,*((double*)val)); + val = sfstruse(sh.strbuf); + } + if(sh.lastarg && !nv_isattr(np,NV_NOFREE)) + free((void*)sh.lastarg); + else + nv_offattr(np,NV_NOFREE); + if(val) + sh.lastarg = strdup(val); + else + sh.lastarg = 0; +} + +static int hasgetdisc(register Namfun_t *fp) +{ + while(fp && !fp->disc->getnum && !fp->disc->getval) + fp = fp->next; + return(fp!=0); +} + +/* + * store the most recent value for use in .sh.match + */ +void sh_setmatch(const char *v, int vsize, int nmatch, int match[]) +{ + struct match *mp = (struct match*)(SH_MATCHNOD->nvfun); + register int i,n; + if(mp->nmatch = nmatch) + { + memcpy(mp->match,match,nmatch*2*sizeof(match[0])); + for(n=match[0],i=1; i < 2*nmatch; i++) + { + if(mp->match[i] < n) + n = mp->match[i]; + } + for(vsize=0,i=0; i < 2*nmatch; i++) + { + if((mp->match[i] -= n) > vsize) + vsize = mp->match[i]; + } + v += n; + if(vsize >= mp->vsize) + { + if(mp->vsize) + mp->val = (char*)realloc(mp->val,vsize+1); + else + mp->val = (char*)malloc(vsize+1); + mp->vsize = vsize; + } + memcpy(mp->val,v,vsize); + mp->val[vsize] = 0; + nv_putsub(SH_MATCHNOD, NIL(char*), nmatch|ARRAY_FILL); + mp->lastsub = -1; + } +} + +#define array_scan(np) ((nv_arrayptr(np)->nelem&ARRAY_SCAN)) + +static char* get_match(register Namval_t* np, Namfun_t *fp) +{ + struct match *mp = (struct match*)fp; + int sub,n; + char *val; + sub = nv_aindex(np); + if(sub>=mp->nmatch) + return(0); + if(sub==mp->lastsub) + return(mp->rval); + if(mp->rval) + { + free((void*)mp->rval); + mp->rval = 0; + } + n = mp->match[2*sub+1]-mp->match[2*sub]; + if(n<=0) + return(""); + val = mp->val+mp->match[2*sub]; + if(mp->val[mp->match[2*sub+1]]==0) + return(val); + mp->rval = (char*)malloc(n+1); + mp->lastsub = sub; + memcpy(mp->rval,val,n); + mp->rval[n] = 0; + return(mp->rval); +} + +static const Namdisc_t SH_MATCH_disc = { sizeof(struct match), 0, get_match }; + +#if SHOPT_FS_3D + /* + * set or unset the mappings given a colon separated list of directories + */ + static void vpath_set(char *str, int mode) + { + register char *lastp, *oldp=str, *newp=strchr(oldp,':'); + if(!sh.lim.fs3d) + return; + while(newp) + { + *newp++ = 0; + if(lastp=strchr(newp,':')) + *lastp = 0; + mount((mode?newp:""),oldp,FS3D_VIEW,0); + newp[-1] = ':'; + oldp = newp; + newp=lastp; + } + } + + /* catch vpath assignments */ + static void put_vpath(register Namval_t* np,const char *val,int flags,Namfun_t *fp) + { + register char *cp; + if(cp = nv_getval(np)) + vpath_set(cp,0); + if(val) + vpath_set((char*)val,1); + nv_putv(np,val,flags,fp); + } + static const Namdisc_t VPATH_disc = { 0, put_vpath }; + static Namfun_t VPATH_init = { &VPATH_disc, 1 }; +#endif /* SHOPT_FS_3D */ + + +static const Namdisc_t IFS_disc = { sizeof(struct ifs), put_ifs, get_ifs }; +const Namdisc_t RESTRICTED_disc = { sizeof(struct shell), put_restricted }; +#ifdef PATH_BFPATH +static const Namdisc_t CDPATH_disc = { sizeof(struct shell), put_cdpath }; +#endif +static const Namdisc_t EDITOR_disc = { sizeof(struct shell), put_ed }; +static const Namdisc_t OPTINDEX_disc = { sizeof(struct shell), put_optindex, 0, nget_optindex }; +static const Namdisc_t SECONDS_disc = { sizeof(struct seconds), put_seconds, get_seconds, nget_seconds }; +static const Namdisc_t RAND_disc = { sizeof(struct rand), put_rand, get_rand, nget_rand }; +static const Namdisc_t LINENO_disc = { sizeof(struct shell), put_lineno, get_lineno, nget_lineno }; +static const Namdisc_t L_ARG_disc = { sizeof(struct shell), put_lastarg, get_lastarg }; + +#if SHOPT_NAMESPACE + static char* get_nspace(Namval_t* np, Namfun_t *fp) + { + if(sh.namespace) + return(nv_name(sh.namespace)); + return((char*)np->nvalue.cp); + } + static const Namdisc_t NSPACE_disc = { 0, 0, get_nspace }; + static Namfun_t NSPACE_init = { &NSPACE_disc, 1}; +#endif /* SHOPT_NAMESPACE */ + +#ifdef _hdr_locale + static const Namdisc_t LC_disc = { sizeof(struct shell), put_lang }; +#endif /* _hdr_locale */ + +/* + * This function will get called whenever a configuration parameter changes + */ +static int newconf(const char *name, const char *path, const char *value) +{ + register char *arg; + if(!name) + setenviron(value); + else if(strcmp(name,"UNIVERSE")==0 && strcmp(astconf(name,0,0),value)) + { + sh.universe = 0; + /* set directory in new universe */ + if(*(arg = path_pwd(0))=='/') + chdir(arg); + /* clear out old tracked alias */ + stakseek(0); + stakputs(nv_getval(PATHNOD)); + stakputc(0); + nv_putval(PATHNOD,stakseek(0),NV_RDONLY); + } + return(1); +} + +#if (CC_NATIVE != CC_ASCII) + static void a2e(char *d, const char *s) + { + register const unsigned char *t; + register int i; + t = CCMAP(CC_ASCII, CC_NATIVE); + for(i=0; i<(1<<CHAR_BIT); i++) + d[t[i]] = s[i]; + } + + static void init_ebcdic(void) + { + int i; + char *cp = (char*)malloc(ST_NONE*(1<<CHAR_BIT)); + for(i=0; i < ST_NONE; i++) + { + a2e(cp,sh_lexrstates[i]); + sh_lexstates[i] = cp; + cp += (1<<CHAR_BIT); + } + } +#endif + +/* + * return SH_TYPE_* bitmask for path + * 0 for "not a shell" + */ +int sh_type(register const char *path) +{ + register const char* s; + register int t = 0; + + if (s = (const char*)strrchr(path, '/')) + { + if (*path == '-') + t |= SH_TYPE_LOGIN; + s++; + } + else + s = path; + if (*s == '-') + { + s++; + t |= SH_TYPE_LOGIN; + } + for (;;) + { + if (!(t & (SH_TYPE_KSH|SH_TYPE_BASH))) + { + if (*s == 'k') + { + s++; + t |= SH_TYPE_KSH; + continue; + } +#if SHOPT_BASH + if (*s == 'b' && *(s+1) == 'a') + { + s += 2; + t |= SH_TYPE_BASH; + continue; + } +#endif + } + if (!(t & (SH_TYPE_PROFILE|SH_TYPE_RESTRICTED))) + { +#if SHOPT_PFSH + if (*s == 'p' && *(s+1) == 'f') + { + s += 2; + t |= SH_TYPE_PROFILE; + continue; + } +#endif + if (*s == 'r') + { + s++; + t |= SH_TYPE_RESTRICTED; + continue; + } + } + break; + } + if (*s++ != 's' || *s++ != 'h') + return 0; + t |= SH_TYPE_SH; + if ((t & SH_TYPE_KSH) && *s == '9' && *(s+1) == '3') + s += 2; +#if _WINIX + if (*s == '.' && *(s+1) == 'e' && *(s+2) == 'x' && *(s+3) == 'e') + s += 4; +#endif + if (*s) + t &= ~(SH_TYPE_PROFILE|SH_TYPE_RESTRICTED); + return t; +} + +/* + * initialize the shell + */ +Shell_t *sh_init(register int argc,register char *argv[], void(*userinit)(int)) +{ + register int n; + int type; + static char *login_files[3]; + n = strlen(e_version); + if(e_version[n-1]=='$' && e_version[n-2]==' ') + e_version[n-2]=0; +#if (CC_NATIVE == CC_ASCII) + memcpy(sh_lexstates,sh_lexrstates,ST_NONE*sizeof(char*)); +#else + init_ebcdic(); +#endif + umask(umask(0)); + sh.mac_context = sh_macopen(&sh); + sh.arg_context = sh_argopen(&sh); + sh.lex_context = (void*)sh_lexopen(0,&sh,1); + sh.ed_context = (void*)ed_open(&sh); + sh.strbuf = sfstropen(); + sfsetbuf(sh.strbuf,(char*)0,64); + sh_onstate(SH_INIT); + error_info.exit = sh_exit; + error_info.id = path_basename(argv[0]); +#if ERROR_VERSION >= 20000102L + error_info.catalog = e_dict; +#endif + sh.cpipe[0] = -1; + sh.coutpipe = -1; + sh.userid=getuid(); + sh.euserid=geteuid(); + sh.groupid=getgid(); + sh.egroupid=getegid(); + for(n=0;n < 10; n++) + { + /* don't use lower bits when rand() generates large numbers */ + if(rand() > RANDMASK) + { + rand_shift = 3; + break; + } + } + sh.lim.clk_tck = getconf("CLK_TCK"); + sh.lim.arg_max = getconf("ARG_MAX"); + sh.lim.open_max = getconf("OPEN_MAX"); + sh.lim.child_max = getconf("CHILD_MAX"); + sh.lim.ngroups_max = getconf("NGROUPS_MAX"); + sh.lim.posix_version = getconf("VERSION"); + sh.lim.posix_jobcontrol = getconf("JOB_CONTROL"); + if(sh.lim.arg_max <=0) + sh.lim.arg_max = ARG_MAX; + if(sh.lim.child_max <=0) + sh.lim.child_max = CHILD_MAX; + if(sh.lim.open_max <0) + sh.lim.open_max = OPEN_MAX; + if(sh.lim.open_max > (SHRT_MAX-2)) + sh.lim.open_max = SHRT_MAX-2; + if(sh.lim.clk_tck <=0) + sh.lim.clk_tck = CLK_TCK; +#if SHOPT_FS_3D + if(fs3d(FS3D_TEST)) + sh.lim.fs3d = 1; +#endif /* SHOPT_FS_3D */ + sh_ioinit(); + /* initialize signal handling */ + sh_siginit(); + stakinstall(NIL(Stak_t*),nospace); + /* set up memory for name-value pairs */ + sh.init_context = nv_init(&sh); + /* read the environment */ + if(argc>0) + { + type = sh_type(*argv); + if(type&SH_TYPE_LOGIN) + sh.login_sh = 2; + } + env_init(&sh); +#if SHOPT_SPAWN + { + /* + * try to find the pathname for this interpreter + * try using environment variable _ or argv[0] + */ + char *last, *cp=nv_getval(L_ARGNOD); + char buff[PATH_MAX+1]; + sh.shpath = 0; + sfprintf(sh.strbuf,"/proc/%d/exe",getpid()); + if((n=readlink(sfstruse(sh.strbuf),buff,sizeof(buff)-1))>0) + { + buff[n] = 0; + sh.shpath = strdup(buff); + } + else if((cp && (sh_type(cp)&SH_TYPE_SH)) || (argc>0 && strchr(cp= *argv,'/'))) + { + if(*cp=='/') + sh.shpath = strdup(cp); + else if(cp = nv_getval(PWDNOD)) + { + int offset = staktell(); + stakputs(cp); + stakputc('/'); + stakputs(argv[0]); + pathcanon(stakptr(offset),PATH_DOTDOT); + sh.shpath = strdup(stakptr(offset)); + stakseek(offset); + } + } + } +#endif + nv_putval(IFSNOD,(char*)e_sptbnl,NV_RDONLY); +#if SHOPT_FS_3D + nv_stack(VPATHNOD, &VPATH_init); +#endif /* SHOPT_FS_3D */ + astconfdisc(newconf); +#if SHOPT_TIMEOUT + sh.st.tmout = SHOPT_TIMEOUT; +#endif /* SHOPT_TIMEOUT */ + /* initialize jobs table */ + job_clear(); + if(argc>0) + { + /* check for restricted shell */ + if(type&SH_TYPE_RESTRICTED) + sh_onoption(SH_RESTRICTED); +#if SHOPT_PFSH + /* check for profile shell */ + else if(type&SH_TYPE_PROFILE) + sh_onoption(SH_PFSH); +#endif +#if SHOPT_BASH + /* check for invocation as bash */ + if(type&SH_TYPE_BASH) + { + sh.userinit = userinit = bash_init; + sh_onoption(SH_BASH); + sh_onstate(SH_PREINIT); + (*userinit)(0); + sh_offstate(SH_PREINIT); + } +#endif + /* look for options */ + /* sh.st.dolc is $# */ + if((sh.st.dolc = sh_argopts(-argc,argv)) < 0) + { + sh.exitval = 2; + sh_done(0); + } + opt_info.disc = 0; + sh.st.dolv=argv+(argc-1)-sh.st.dolc; + sh.st.dolv[0] = argv[0]; + if(sh.st.dolc < 1) + sh_onoption(SH_SFLAG); + if(!sh_isoption(SH_SFLAG)) + { + sh.st.dolc--; + sh.st.dolv++; +#if _WINIX + { + char* name; + name = sh.st.dolv[0]; + if(name[1]==':' && (name[2]=='/' || name[2]=='\\')) + { +#if _lib_pathposix + char* p; + + if((n = pathposix(name, NIL(char*), 0)) > 0 && (p = (char*)malloc(++n))) + { + pathposix(name, p, n); + name = p; + } + else +#endif + { + name[1] = name[0]; + name[0] = name[2] = '/'; + } + } + } +#endif /* _WINIX */ + } + } +#if SHOPT_PFSH + if (sh_isoption(SH_PFSH)) + { + struct passwd *pw = getpwuid(sh.userid); + if(pw) + sh.user = strdup(pw->pw_name); + + } +#endif + /* set[ug]id scripts require the -p flag */ + if(sh.userid!=sh.euserid || sh.groupid!=sh.egroupid) + { +#if SHOPT_P_SUID + /* require sh -p to run setuid and/or setgid */ + if(!sh_isoption(SH_PRIVILEGED) && sh.euserid < SHOPT_P_SUID) + { + setuid(sh.euserid=sh.userid); + setgid(sh.egroupid=sh.groupid); + } + else +#else + sh_onoption(SH_PRIVILEGED); +#endif /* SHOPT_P_SUID */ +#ifdef SHELLMAGIC + /* careful of #! setuid scripts with name beginning with - */ + if(sh.login_sh && argv[1] && strcmp(argv[0],argv[1])==0) + errormsg(SH_DICT,ERROR_exit(1),e_prohibited); +#endif /*SHELLMAGIC*/ + } + else + sh_offoption(SH_PRIVILEGED); + /* shname for $0 in profiles and . scripts */ + if(strmatch(argv[1],e_devfdNN)) + sh.shname = strdup(argv[0]); + else + sh.shname = strdup(sh.st.dolv[0]); + /* + * return here for shell script execution + * but not for parenthesis subshells + */ + error_info.id = strdup(sh.st.dolv[0]); /* error_info.id is $0 */ + sh.jmpbuffer = (void*)&sh.checkbase; + sh_pushcontext(&sh.checkbase,SH_JMPSCRIPT); + sh.st.self = &sh.global; + sh.topscope = (Shscope_t*)sh.st.self; + sh_offstate(SH_INIT); + login_files[0] = (char*)e_profile; + login_files[1] = ".profile"; + sh.login_files = login_files; + if(sh.userinit=userinit) + (*userinit)(0); + return(&sh); +} + +Shell_t *sh_getinterp(void) +{ + return(&sh); +} + +/* + * reinitialize before executing a script + */ +int sh_reinit(char *argv[]) +{ + Shopt_t opt; + dtclear(sh.fun_tree); + dtclose(sh.alias_tree); + sh.alias_tree = inittree(&sh,shtab_aliases); + sh.namespace = 0; + sh.inuse_bits = 0; + if(sh.userinit) + (*sh.userinit)(1); + if(sh.heredocs) + { + sfclose(sh.heredocs); + sh.heredocs = 0; + } + /* remove locals */ + sh_onstate(SH_INIT); + nv_scan(sh.var_tree,sh_envnolocal,(void*)0,NV_EXPORT,0); + nv_scan(sh.var_tree,sh_envnolocal,(void*)0,NV_ARRAY,NV_ARRAY); + sh_offstate(SH_INIT); + memset(sh.st.trapcom,0,(sh.st.trapmax+1)*sizeof(char*)); + memset((void*)&opt,0,sizeof(opt)); + if(sh_isoption(SH_TRACKALL)) + on_option(&opt,SH_TRACKALL); + if(sh_isoption(SH_EMACS)) + on_option(&opt,SH_EMACS); + if(sh_isoption(SH_GMACS)) + on_option(&opt,SH_GMACS); + if(sh_isoption(SH_VI)) + on_option(&opt,SH_VI); + if(sh_isoption(SH_VIRAW)) + on_option(&opt,SH_VIRAW); + sh.options = opt; + /* set up new args */ + if(argv) + sh.arglist = sh_argcreate(argv); + if(sh.arglist) + sh_argreset(sh.arglist,NIL(struct dolnod*)); + sh.envlist=0; + sh.curenv = 0; + sh.shname = error_info.id = strdup(sh.st.dolv[0]); + sh_offstate(SH_FORKED); + sh.fn_depth = sh.dot_depth = 0; + sh_sigreset(0); + return(1); +} + +/* + * set when creating a local variable of this name + */ +Namfun_t *nv_cover(register Namval_t *np) +{ +#ifdef PATH_BFPATH + if(np==IFSNOD || np==PATHNOD || np==SHELLNOD || np==FPATHNOD || np==CDPNOD || np==SECONDS) +#else + if(np==IFSNOD || np==PATHNOD || np==SHELLNOD || np==SECONDS) +#endif + return(np->nvfun); +#ifdef _hdr_locale + if(np==LCALLNOD || np==LCTYPENOD || np==LCMSGNOD || np==LCCOLLNOD || np==LCNUMNOD || np==LANGNOD) + return(np->nvfun); +#endif + return(0); +} + +static Namtype_t typeset; +static const char *shdiscnames[] = { "tilde", 0}; + +/* + * Initialize the shell name and alias table + */ +static Init_t *nv_init(Shell_t *shp) +{ + Namval_t *np; + register Init_t *ip; + double d=0; + ip = newof(0,Init_t,1,0); + if(!ip) + return(0); + ip->sh = shp; + shp->var_base = shp->var_tree = inittree(shp,shtab_variables); + ip->IFS_init.hdr.disc = &IFS_disc; + ip->IFS_init.hdr.nofree = 1; + ip->IFS_init.sh = shp; + ip->PATH_init.hdr.disc = &RESTRICTED_disc; + ip->PATH_init.hdr.nofree = 1; + ip->PATH_init.sh = shp; +#ifdef PATH_BFPATH + ip->FPATH_init.hdr.disc = &RESTRICTED_disc; + ip->FPATH_init.hdr.nofree = 1; + ip->FPATH_init.sh = shp; + ip->CDPATH_init.hdr.disc = &CDPATH_disc; + ip->CDPATH_init.hdr.nofree = 1; + ip->CDPATH_init.sh = shp; +#endif + ip->SHELL_init.hdr.disc = &RESTRICTED_disc; + ip->SHELL_init.sh = shp; + ip->SHELL_init.hdr.nofree = 1; + ip->ENV_init.hdr.disc = &RESTRICTED_disc; + ip->ENV_init.hdr.nofree = 1; + ip->ENV_init.sh = shp; + ip->VISUAL_init.hdr.disc = &EDITOR_disc; + ip->VISUAL_init.hdr.nofree = 1; + ip->VISUAL_init.sh = shp; + ip->EDITOR_init.hdr.disc = &EDITOR_disc; + ip->EDITOR_init.hdr.nofree = 1; + ip->EDITOR_init.sh = shp; + ip->OPTINDEX_init.hdr.disc = &OPTINDEX_disc; + ip->OPTINDEX_init.hdr.nofree = 1; + ip->OPTINDEX_init.sh = shp; + ip->SECONDS_init.hdr.disc = &SECONDS_disc; + ip->SECONDS_init.hdr.nofree = 1; + ip->SECONDS_init.sh = shp; + ip->RAND_init.hdr.disc = &RAND_disc; + ip->RAND_init.hdr.nofree = 1; + ip->SH_MATCH_init.hdr.disc = &SH_MATCH_disc; + ip->SH_MATCH_init.hdr.nofree = 1; + ip->LINENO_init.hdr.disc = &LINENO_disc; + ip->LINENO_init.hdr.nofree = 1; + ip->LINENO_init.sh = shp; + ip->L_ARG_init.hdr.disc = &L_ARG_disc; + ip->L_ARG_init.hdr.nofree = 1; +#ifdef _hdr_locale + ip->LC_TYPE_init.hdr.disc = &LC_disc; + ip->LC_TYPE_init.hdr.nofree = 1; + ip->LC_NUM_init.hdr.disc = &LC_disc; + ip->LC_NUM_init.hdr.nofree = 1; + ip->LC_COLL_init.hdr.disc = &LC_disc; + ip->LC_COLL_init.hdr.nofree = 1; + ip->LC_MSG_init.hdr.disc = &LC_disc; + ip->LC_MSG_init.hdr.nofree = 1; + ip->LC_ALL_init.hdr.disc = &LC_disc; + ip->LC_ALL_init.hdr.nofree = 1; + ip->LANG_init.hdr.disc = &LC_disc; + ip->LANG_init.hdr.nofree = 1; + ip->LC_TYPE_init.sh = shp; + ip->LC_NUM_init.sh = shp; + ip->LC_COLL_init.sh = shp; + ip->LC_MSG_init.sh = shp; + ip->LANG_init.sh = shp; +#endif /* _hdr_locale */ + nv_stack(IFSNOD, &ip->IFS_init.hdr); + nv_stack(PATHNOD, &ip->PATH_init.hdr); +#ifdef PATH_BFPATH + nv_stack(FPATHNOD, &ip->FPATH_init.hdr); + nv_stack(CDPNOD, &ip->CDPATH_init.hdr); +#endif + nv_stack(SHELLNOD, &ip->SHELL_init.hdr); + nv_stack(ENVNOD, &ip->ENV_init.hdr); + nv_stack(VISINOD, &ip->VISUAL_init.hdr); + nv_stack(EDITNOD, &ip->EDITOR_init.hdr); + nv_stack(OPTINDNOD, &ip->OPTINDEX_init.hdr); + nv_stack(SECONDS, &ip->SECONDS_init.hdr); + nv_stack(L_ARGNOD, &ip->L_ARG_init.hdr); + nv_putval(SECONDS, (char*)&d, NV_INTEGER|NV_DOUBLE); + nv_stack(RANDNOD, &ip->RAND_init.hdr); + d = (shp->pid&RANDMASK); + nv_putval(RANDNOD, (char*)&d, NV_INTEGER|NV_DOUBLE); + nv_stack(LINENO, &ip->LINENO_init.hdr); + nv_putsub(SH_MATCHNOD,(char*)0,10); + nv_onattr(SH_MATCHNOD,NV_RDONLY); + nv_stack(SH_MATCHNOD, &ip->SH_MATCH_init.hdr); +#ifdef _hdr_locale + nv_stack(LCTYPENOD, &ip->LC_TYPE_init.hdr); + nv_stack(LCALLNOD, &ip->LC_ALL_init.hdr); + nv_stack(LCMSGNOD, &ip->LC_MSG_init.hdr); + nv_stack(LCCOLLNOD, &ip->LC_COLL_init.hdr); + nv_stack(LCNUMNOD, &ip->LC_NUM_init.hdr); + nv_stack(LANGNOD, &ip->LANG_init.hdr); +#endif /* _hdr_locale */ + (PPIDNOD)->nvalue.lp = (&shp->ppid); + (TMOUTNOD)->nvalue.lp = (&shp->st.tmout); + (MCHKNOD)->nvalue.lp = (&sh_mailchk); + (OPTINDNOD)->nvalue.lp = (&shp->st.optindex); + /* set up the seconds clock */ + shp->alias_tree = inittree(shp,shtab_aliases); + shp->track_tree = dtopen(&_Nvdisc,Dtset); + shp->bltin_tree = inittree(shp,(const struct shtable2*)shtab_builtins); + typeset.shp = shp; + typeset.optstring = sh_opttypeset; + nv_search("typeset",shp->bltin_tree,0)->nvfun = (void*)&typeset; +#if SHOPT_BASH + nv_search("local",shp->bltin_tree,0)->nvfun = (void*)&typeset; +#endif + shp->fun_tree = dtopen(&_Nvdisc,Dtoset); + dtview(shp->fun_tree,shp->bltin_tree); +#if SHOPT_NAMESPACE + if(np = nv_mount(DOTSHNOD, "global", shp->var_tree)) + nv_onattr(np,NV_RDONLY); + np = nv_search("namespace",nv_dict(DOTSHNOD),NV_ADD); + nv_putval(np,".sh.global",NV_RDONLY|NV_NOFREE); + nv_stack(np, &NSPACE_init); +#endif /* SHOPT_NAMESPACE */ + np = nv_mount(DOTSHNOD, "type", dtopen(&_Nvdisc,Dtoset)); + nv_adddisc(DOTSHNOD, shdiscnames, (Namval_t**)0); + return(ip); +} + +/* + * initialize name-value pairs + */ + +static Dt_t *inittree(Shell_t *shp,const struct shtable2 *name_vals) +{ + register Namval_t *np; + register const struct shtable2 *tp; + register unsigned n = 0; + register Dt_t *treep; + Dt_t *base_treep, *dict; + for(tp=name_vals;*tp->sh_name;tp++) + n++; + np = (Namval_t*)calloc(n,sizeof(Namval_t)); + if(!shp->bltin_nodes) + shp->bltin_nodes = np; + else if(name_vals==(const struct shtable2*)shtab_builtins) + shp->bltin_cmds = np; + base_treep = treep = dtopen(&_Nvdisc,Dtoset); + for(tp=name_vals;*tp->sh_name;tp++,np++) + { + if((np->nvname = strrchr(tp->sh_name,'.')) && np->nvname!=((char*)tp->sh_name)) + np->nvname++; + else + { + np->nvname = (char*)tp->sh_name; + treep = base_treep; + } + np->nvenv = 0; + if(name_vals==(const struct shtable2*)shtab_builtins) + np->nvalue.bfp = ((struct shtable3*)tp)->sh_value; + else + np->nvalue.cp = (char*)tp->sh_value; + nv_setattr(np,tp->sh_number); + if(nv_istable(np)) + nv_mount(np,(const char*)0,dict=dtopen(&_Nvdisc,Dtoset)); + if(nv_isattr(np,NV_INTEGER)) + nv_setsize(np,10); + else + nv_setsize(np,0); + dtinsert(treep,np); + if(nv_istable(np)) + treep = dict; + } + return(treep); +} + +/* + * read in the process environment and set up name-value pairs + * skip over items that are not name-value pairs + */ + +static void env_init(Shell_t *shp) +{ + register char *cp; + register Namval_t *np; + register char **ep=environ; + register char *next=0; +#ifdef _ENV_H + shp->env = env_open(environ,3); + env_delete(shp->env,"_"); +#endif + if(ep) + { + while(cp= *ep++) + { + if(*cp=='A' && cp[1]=='_' && cp[2]=='_' && cp[3]=='z' && cp[4]=='=') + next = cp+4; + else if(np=nv_open(cp,shp->var_tree,(NV_EXPORT|NV_IDENT|NV_ASSIGN|NV_NOFAIL))) + { + nv_onattr(np,NV_IMPORT); + np->nvenv = cp; + nv_close(np); + } + } + while(cp=next) + { + if(next = strchr(++cp,'=')) + *next = 0; + np = nv_search(cp+2,shp->var_tree,NV_ADD); + if(nv_isattr(np,NV_IMPORT|NV_EXPORT)) + { + int flag = *(unsigned char*)cp-' '; + int size = *(unsigned char*)(cp+1)-' '; + if((flag&NV_INTEGER) && size==0) + { + /* check for floating*/ + char *ep,*val = nv_getval(np); + strtol(val,&ep,10); + if(*ep=='.' || *ep=='e' || *ep=='E') + { + char *lp; + flag |= NV_DOUBLE; + if(*ep=='.') + { + strtol(ep+1,&lp,10); + if(*lp) + ep = lp; + } + if(*ep && *ep!='.') + { + flag |= NV_EXPNOTE; + size = ep-val; + } + else + size = strlen(ep); + size--; + } + } + nv_newattr(np,flag|NV_IMPORT|NV_EXPORT,size); + } + } + } +#ifdef _ENV_H + env_delete(sh.env,e_envmarker); +#endif + if(nv_isnull(PWDNOD) || nv_isattr(PWDNOD,NV_TAGGED)) + { + nv_offattr(PWDNOD,NV_TAGGED); + path_pwd(0); + } + if((cp = nv_getval(SHELLNOD)) && (sh_type(cp)&SH_TYPE_RESTRICTED)) + sh_onoption(SH_RESTRICTED); /* restricted shell */ + return; +} + +/* + * terminate shell and free up the space + */ +int sh_term(void) +{ + sfdisc(sfstdin,SF_POPDISC); + free((char*)sh.outbuff); + stakset(NIL(char*),0); + return(0); +} + +/* function versions of these */ + +#define DISABLE /* proto workaround */ + +unsigned long sh_isoption DISABLE (int opt) +{ + return(sh_isoption(opt)); +} + +unsigned long sh_onoption DISABLE (int opt) +{ + return(sh_onoption(opt)); +} + +unsigned long sh_offoption DISABLE (int opt) +{ + return(sh_offoption(opt)); +} + +void sh_sigcheck DISABLE (void) +{ + sh_sigcheck(); +} + +Dt_t* sh_bltin_tree DISABLE (void) +{ + return(sh.bltin_tree); +} diff --git a/usr/src/lib/libshell/common/sh/io.c b/usr/src/lib/libshell/common/sh/io.c new file mode 100644 index 0000000000..a14a941942 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/io.c @@ -0,0 +1,2215 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +/* + * Input/output file processing + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <fcin.h> +#include <ls.h> +#include <stdarg.h> +#include <ctype.h> +#include <regex.h> +#include "variables.h" +#include "path.h" +#include "io.h" +#include "jobs.h" +#include "shnodes.h" +#include "history.h" +#include "edit.h" +#include "timeout.h" +#include "FEATURE/externs" +#include "FEATURE/dynamic" +#include "FEATURE/poll" + +#ifdef FNDELAY +# ifdef EAGAIN +# if EAGAIN!=EWOULDBLOCK +# undef EAGAIN +# define EAGAIN EWOULDBLOCK +# endif +# else +# define EAGAIN EWOULDBLOCK +# endif /* EAGAIN */ +# ifndef O_NONBLOCK +# define O_NONBLOCK FNDELAY +# endif /* !O_NONBLOCK */ +#endif /* FNDELAY */ + +#ifndef O_SERVICE +# define O_SERVICE O_NOCTTY +#endif + +#define RW_ALL (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH) + +static void *timeout; +static int (*fdnotify)(int,int); + +#if defined(_lib_socket) && defined(_sys_socket) && defined(_hdr_netinet_in) +# include <sys/socket.h> +# include <netdb.h> +# include <netinet/in.h> +# if !defined(htons) && !_lib_htons +# define htons(x) (x) +# endif +# if !defined(htonl) && !_lib_htonl +# define htonl(x) (x) +# endif +# if _pipe_socketpair +# if _socketpair_shutdown_mode +# define pipe(v) ((socketpair(AF_UNIX,SOCK_STREAM,0,v)<0||shutdown((v)[0],1)<0||fchmod((v)[0],S_IRUSR)<0||shutdown((v)[1],0)<0||fchmod((v)[1],S_IWUSR)<0)?(-1):0) +# else +# define pipe(v) ((socketpair(AF_UNIX,SOCK_STREAM,0,v)<0||shutdown((v)[0],1)<0||shutdown((v)[1],0)<0)?(-1):0) +# endif +# endif + +#if !_lib_getaddrinfo + +#undef EAI_SYSTEM + +#define EAI_SYSTEM 1 + +#undef addrinfo +#undef getaddrinfo +#undef freeaddrinfo + +#define addrinfo local_addrinfo +#define getaddrinfo local_getaddrinfo +#define freeaddrinfo local_freeaddrinfo + +struct addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr* ai_addr; + struct addrinfo* ai_next; +}; + +static int +getaddrinfo(const char* node, const char* service, const struct addrinfo* hint, struct addrinfo **addr) +{ + unsigned long ip_addr = 0; + unsigned short ip_port = 0; + struct addrinfo* ap; + struct hostent* hp; + struct sockaddr_in* ip; + char* prot; + long n; + + if (!(hp = gethostbyname(node)) || hp->h_addrtype!=AF_INET || hp->h_length>sizeof(struct in_addr)) + { + errno = EADDRNOTAVAIL; + return EAI_SYSTEM; + } + ip_addr = (unsigned long)((struct in_addr*)hp->h_addr)->s_addr; + if ((n = strtol(service, &prot, 10)) > 0 && n <= USHRT_MAX && !*prot) + ip_port = htons((unsigned short)n); + else + { + struct servent* sp; + const char* protocol = 0; + + if (hint) + switch (hint->ai_socktype) + { + case SOCK_STREAM: + switch (hint->ai_protocol) + { + case 0: + protocol = "tcp"; + break; +#ifdef IPPROTO_SCTP + case IPPROTO_SCTP: + protocol = "sctp"; + break; +#endif + } + break; + case SOCK_DGRAM: + protocol = "udp"; + break; + } + if (!protocol) + { + errno = EPROTONOSUPPORT; + return 1; + } + if (sp = getservbyname(service, protocol)) + ip_port = sp->s_port; + } + if (!ip_port) + { + errno = EADDRNOTAVAIL; + return EAI_SYSTEM; + } + if (!(ap = newof(0, struct addrinfo, 1, sizeof(struct sockaddr_in)))) + return EAI_SYSTEM; + if (hint) + *ap = *hint; + ap->ai_family = hp->h_addrtype; + ap->ai_addrlen = sizeof(struct sockaddr_in); + ap->ai_addr = (struct sockaddr *)(ap+1); + ip = (struct sockaddr_in *)ap->ai_addr; + ip->sin_family = AF_INET; + ip->sin_port = ip_port; + ip->sin_addr.s_addr = ip_addr; + *addr = ap; + return 0; +} + +static void +freeaddrinfo(struct addrinfo* ap) +{ + if (ap) + free(ap); +} + +#endif + +/* + * return <protocol>/<host>/<service> fd + */ + +typedef int (*Inetintr_f)(struct addrinfo*, void*); + +static int +inetopen(const char* path, int server, Inetintr_f onintr, void* handle) +{ + register char* s; + register char* t; + int fd; + int oerrno; + struct addrinfo hint; + struct addrinfo* addr; + struct addrinfo* p; + + memset(&hint, 0, sizeof(hint)); + hint.ai_family = PF_UNSPEC; + switch (path[0]) + { +#ifdef IPPROTO_SCTP + case 's': + if (path[1]!='c' || path[2]!='t' || path[3]!='p' || path[4]!='/') + { + errno = ENOTDIR; + return -1; + } + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_SCTP; + path += 5; + break; +#endif + case 't': + if (path[1]!='c' || path[2]!='p' || path[3]!='/') + { + errno = ENOTDIR; + return -1; + } + hint.ai_socktype = SOCK_STREAM; + path += 4; + break; + case 'u': + if (path[1]!='d' || path[2]!='p' || path[3]!='/') + { + errno = ENOTDIR; + return -1; + } + hint.ai_socktype = SOCK_DGRAM; + path += 4; + break; + default: + errno = ENOTDIR; + return -1; + } + if (!(s = strdup(path))) + return -1; + if (t = strchr(s, '/')) + { + *t++ = 0; + if (streq(s, "local")) + s = "localhost"; + fd = getaddrinfo(s, t, &hint, &addr); + } + else + fd = -1; + free(s); + if (fd) + { + if (fd != EAI_SYSTEM) + errno = ENOTDIR; + return -1; + } + oerrno = errno; + errno = 0; + fd = -1; + for (p = addr; p; p = p->ai_next) + { + /* + * some api's don't take the hint + */ + + if (!p->ai_protocol) + p->ai_protocol = hint.ai_protocol; + if (!p->ai_socktype) + p->ai_socktype = hint.ai_socktype; + while ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) >= 0) + { + if (server && !bind(fd, p->ai_addr, p->ai_addrlen) && !listen(fd, 5) || !server && !connect(fd, p->ai_addr, p->ai_addrlen)) + goto done; + close(fd); + fd = -1; + if (errno != EINTR || !onintr) + break; + if ((*onintr)(addr, handle)) + return -1; + } + } + done: + freeaddrinfo(addr); + if (fd >= 0) + errno = oerrno; + return fd; +} + +#else + +#undef O_SERVICE + +#endif + +struct fdsave +{ + int orig_fd; /* original file descriptor */ + int save_fd; /* saved file descriptor */ + int subshell; /* saved for subshell */ +}; + +static int subexcept(Sfio_t*, int, void*, Sfdisc_t*); +static int eval_exceptf(Sfio_t*, int, void*, Sfdisc_t*); +static int slowexcept(Sfio_t*, int, void*, Sfdisc_t*); +static int pipeexcept(Sfio_t*, int, void*, Sfdisc_t*); +static ssize_t piperead(Sfio_t*, void*, size_t, Sfdisc_t*); +static ssize_t slowread(Sfio_t*, void*, size_t, Sfdisc_t*); +static ssize_t subread(Sfio_t*, void*, size_t, Sfdisc_t*); +static ssize_t tee_write(Sfio_t*,const void*,size_t,Sfdisc_t*); +static int io_prompt(Sfio_t*,int); +static int io_heredoc(register struct ionod*, const char*, int); +static void sftrack(Sfio_t*,int,int); +static const Sfdisc_t eval_disc = { NULL, NULL, NULL, eval_exceptf, NULL}; +static Sfdisc_t tee_disc = {NULL,tee_write,NULL,NULL,NULL}; +static Sfio_t *subopen(Sfio_t*, off_t, long); +static const Sfdisc_t sub_disc = { subread, 0, 0, subexcept, 0 }; + +struct subfile +{ + Sfdisc_t disc; + Sfio_t *oldsp; + off_t offset; + long size; + long left; +}; + +struct Eof +{ + Namfun_t hdr; + int fd; +}; + +static Sfdouble_t nget_cur_eof(register Namval_t* np, Namfun_t *fp) +{ + struct Eof *ep = (struct Eof*)fp; + Sfoff_t end, cur =lseek(ep->fd, (Sfoff_t)0, SEEK_CUR); + if(*np->nvname=='C') + return((Sfdouble_t)cur); + if(cur<0) + return((Sfdouble_t)-1); + end =lseek(ep->fd, (Sfoff_t)0, SEEK_END); + lseek(ep->fd, (Sfoff_t)0, SEEK_CUR); + return((Sfdouble_t)end); +} + +static const Namdisc_t EOF_disc = { sizeof(struct Eof), 0, 0, nget_cur_eof}; + +#define MATCH_BUFF (64*1024) +struct Match +{ + Sfoff_t offset; + char *base; +}; + +static int matchf(void *handle, char *ptr, size_t size) +{ + struct Match *mp = (struct Match*)handle; + mp->offset += (ptr-mp->base); + return(1); +} + + +static struct fdsave *filemap; +static short filemapsize; + +/* ======== input output and file copying ======== */ + +void sh_ioinit(void) +{ + register int n; + filemapsize = 8; + filemap = (struct fdsave*)malloc(8*sizeof(struct fdsave)); +#if SHOPT_FASTPIPE + n = sh.lim.open_max+2; +#else + n = sh.lim.open_max; +#endif /* SHOPT_FASTPIPE */ + sh.fdstatus = (unsigned char*)malloc((unsigned)n); + memset((char*)sh.fdstatus,0,n); + sh.fdptrs = (int**)malloc(n*sizeof(int*)); + memset((char*)sh.fdptrs,0,n*sizeof(int*)); + sh.sftable = (Sfio_t**)malloc(n*sizeof(Sfio_t*)); + memset((char*)sh.sftable,0,n*sizeof(Sfio_t*)); + sh.sftable[0] = sfstdin; + sh.sftable[1] = sfstdout; + sh.sftable[2] = sfstderr; + sfnotify(sftrack); + sh_iostream(0); + /* all write steams are in the same pool and share outbuff */ + sh.outpool = sfopen(NIL(Sfio_t*),NIL(char*),"sw"); /* pool identifier */ + sh.outbuff = (char*)malloc(IOBSIZE); + sh.errbuff = (char*)malloc(IOBSIZE/4); + sfsetbuf(sfstderr,sh.errbuff,IOBSIZE/4); + sfsetbuf(sfstdout,sh.outbuff,IOBSIZE); + sfpool(sfstdout,sh.outpool,SF_WRITE); + sfpool(sfstderr,sh.outpool,SF_WRITE); + sfset(sfstdout,SF_LINE,0); +} + +/* + * create or initialize a stream corresponding to descriptor <fd> + * a buffer with room for a sentinal is allocated for a read stream. + * A discipline is inserted when read stream is a tty or a pipe + * For output streams, the buffer is set to sh.output and put into + * the sh.outpool synchronization pool + */ +Sfio_t *sh_iostream(register int fd) +{ + register Sfio_t *iop; + register int status = sh_iocheckfd(fd); + register int flags = SF_WRITE; + char *bp; +#if SHOPT_FASTPIPE + if(fd>=sh.lim.open_max) + return(sh.sftable[fd]); +#endif /* SHOPT_FASTPIPE */ + if(status==IOCLOSE) + { + switch(fd) + { + case 0: + return(sfstdin); + case 1: + return(sfstdout); + case 2: + return(sfstderr); + } + return(NIL(Sfio_t*)); + } + if(status&IOREAD) + { + if(!(bp = (char *)malloc(IOBSIZE+1))) + return(NIL(Sfio_t*)); + flags |= SF_READ; + if(!(status&IOWRITE)) + flags &= ~SF_WRITE; + } + else + bp = sh.outbuff; + if(status&IODUP) + flags |= SF_SHARE|SF_PUBLIC; + if((iop = sh.sftable[fd]) && sffileno(iop)>=0) + sfsetbuf(iop, bp, IOBSIZE); + else if(!(iop=sfnew((fd<=2?iop:0),bp,IOBSIZE,fd,flags))) + return(NIL(Sfio_t*)); + if(status&IOREAD) + { + Sfdisc_t *dp; + sfset(iop,SF_MALLOC,1); + { + dp = newof(0,Sfdisc_t,1,0); + dp->exceptf = slowexcept; + if(status&IOTTY) + dp->readf = slowread; + else if(status&IONOSEEK) + { + dp->readf = piperead; + sfset(iop, SF_IOINTR,1); + } + else + dp->readf = 0; + dp->seekf = 0; + dp->writef = 0; + sfdisc(iop,dp); + } + } + else + sfpool(iop,sh.outpool,SF_WRITE); + sh.sftable[fd] = iop; + return(iop); +} + +/* + * preserve the file descriptor or stream by moving it + */ +static void io_preserve(register Sfio_t *sp, register int f2) +{ + register int fd; + if(sp) + fd = sfsetfd(sp,10); + else + fd = sh_fcntl(f2,F_DUPFD,10); + if(f2==sh.infd) + sh.infd = fd; + if(fd<0) + errormsg(SH_DICT,ERROR_system(1),e_toomany); + if(sh.fdptrs[fd]=sh.fdptrs[f2]) + { + if(f2==job.fd) + job.fd=fd; + *sh.fdptrs[fd] = fd; + sh.fdptrs[f2] = 0; + } + sh.sftable[fd] = sp; + sh.fdstatus[fd] = sh.fdstatus[f2]; + if(fcntl(f2,F_GETFD,0)&1) + { + fcntl(fd,F_SETFD,FD_CLOEXEC); + sh.fdstatus[fd] |= IOCLEX; + } + sh.sftable[f2] = 0; +} + +/* + * Given a file descriptor <f1>, move it to a file descriptor number <f2> + * If <f2> is needed move it, otherwise it is closed first. + * The original stream <f1> is closed. + * The new file descriptor <f2> is returned; + */ +int sh_iorenumber(register int f1,register int f2) +{ + register Sfio_t *sp = sh.sftable[f2]; + if(f1!=f2) + { + /* see whether file descriptor is in use */ + if(sh_inuse(f2) || (f2>2 && sp)) + { + if(!(sh.inuse_bits&(1<<f2))) + io_preserve(sp,f2); + sp = 0; + } + else if(f2==0) + sh.st.ioset = 1; + sh_close(f2); + if(f2<=2 && sp) + { + register Sfio_t *spnew = sh_iostream(f1); + sh.fdstatus[f2] = (sh.fdstatus[f1]&~IOCLEX); + sfsetfd(spnew,f2); + sfswap(spnew,sp); + sfset(sp,SF_SHARE|SF_PUBLIC,1); + } + else + { + sh.fdstatus[f2] = (sh.fdstatus[f1]&~IOCLEX); + if((f2 = sh_fcntl(f1,F_DUPFD, f2)) < 0) + errormsg(SH_DICT,ERROR_system(1),e_file+4); + else if(f2 <= 2) + sh_iostream(f2); + } + if(sp) + sh.sftable[f1] = 0; + sh_close(f1); + } + return(f2); +} + +/* + * close a file descriptor and update stream table and attributes + */ +int sh_close(register int fd) +{ + register Sfio_t *sp; + register int r = 0; + if(fd<0) + return(-1); + if(!(sp=sh.sftable[fd]) || sfclose(sp) < 0) + { + if(fdnotify) + (*fdnotify)(fd,SH_FDCLOSE); + r=close(fd); + } + if(fd>2) + sh.sftable[fd] = 0; + sh.fdstatus[fd] = IOCLOSE; + if(sh.fdptrs[fd]) + *sh.fdptrs[fd] = -1; + sh.fdptrs[fd] = 0; + if(fd < 10) + sh.inuse_bits &= ~(1<<fd); + return(r); +} + +static int +onintr(struct addrinfo* addr, void* handle) +{ + Shell_t* sh = (Shell_t*)handle; + + if (sh->trapnote&SH_SIGSET) + { + freeaddrinfo(addr); + sh_exit(SH_EXITSIG); + return -1; + } + if (sh->trapnote) + sh_chktrap(); + return 0; +} + +/* + * Mimic open(2) with checks for pseudo /dev/ files. + */ +int sh_open(register const char *path, int flags, ...) +{ + register int fd = -1; + mode_t mode; + char *e; + va_list ap; + va_start(ap, flags); + mode = (flags & O_CREAT) ? va_arg(ap, int) : 0; + va_end(ap); + errno = 0; + if(*path==0) + { + errno = ENOENT; + return(-1); + } + if (path[0]=='/' && path[1]=='d' && path[2]=='e' && path[3]=='v' && path[4]=='/') + { + switch (path[5]) + { + case 'f': + if (path[6]=='d' && path[7]=='/') + { + fd = (int)strtol(path+8, &e, 10); + if (*e) + fd = -1; + } + break; + case 's': + if (path[6]=='t' && path[7]=='d') + switch (path[8]) + { + case 'e': + if (path[9]=='r' && path[10]=='r' && !path[11]) + fd = 2; + break; + case 'i': + if (path[9]=='n' && !path[10]) + fd = 0; + break; + case 'o': + if (path[9]=='u' && path[10]=='t' && !path[11]) + fd = 1; + break; + } + } +#ifdef O_SERVICE + if (fd < 0) + { + if ((fd = inetopen(path+5, !!(flags & O_SERVICE), onintr, &sh)) < 0 && errno != ENOTDIR) + return -1; + if (fd >= 0) + goto ok; + } +#endif + } + if (fd >= 0) + { + if((mode=sh_iocheckfd(fd))==IOCLOSE) + return(-1); + flags &= O_ACCMODE; + if(!(mode&IOWRITE) && ((flags==O_WRONLY) || (flags==O_RDWR))) + return(-1); + if(!(mode&IOREAD) && ((flags==O_RDONLY) || (flags==O_RDWR))) + return(-1); + if((fd=dup(fd))<0) + return(-1); + } + else while((fd = open(path, flags, mode)) < 0) + if(errno!=EINTR || sh.trapnote) + return(-1); +#ifdef O_SERVICE + ok: +#endif + flags &= O_ACCMODE; + if(flags==O_WRONLY) + mode = IOWRITE; + else if(flags==O_RDWR) + mode = (IOREAD|IOWRITE); + else + mode = IOREAD; + sh.fdstatus[fd] = mode; + return(fd); +} + +/* + * Open a file for reading + * On failure, print message. + */ +int sh_chkopen(register const char *name) +{ + register int fd = sh_open(name,O_RDONLY,0); + if(fd < 0) + errormsg(SH_DICT,ERROR_system(1),e_open,name); + return(fd); +} + +/* + * move open file descriptor to a number > 2 + */ +int sh_iomovefd(register int fdold) +{ + register int fdnew; + if(fdold<0 || fdold>2) + return(fdold); + fdnew = sh_iomovefd(dup(fdold)); + sh.fdstatus[fdnew] = (sh.fdstatus[fdold]&~IOCLEX); + close(fdold); + sh.fdstatus[fdold] = IOCLOSE; + return(fdnew); +} + +/* + * create a pipe and print message on failure + */ +int sh_pipe(register int pv[]) +{ + int fd[2]; + if(pipe(fd)<0 || (pv[0]=fd[0])<0 || (pv[1]=fd[1])<0) + errormsg(SH_DICT,ERROR_system(1),e_pipe); + pv[0] = sh_iomovefd(pv[0]); + pv[1] = sh_iomovefd(pv[1]); + sh.fdstatus[pv[0]] = IONOSEEK|IOREAD; + sh.fdstatus[pv[1]] = IONOSEEK|IOWRITE; + sh_subsavefd(pv[0]); + sh_subsavefd(pv[1]); + return(0); +} + +static int pat_seek(void *handle, const char *str, size_t sz) +{ + char **bp = (char**)handle; + *bp = (char*)str; + return(-1); +} + +static int pat_line(const regex_t* rp, const char *buff, register size_t n) +{ + register const char *cp=buff, *sp; + while(n>0) + { + for(sp=cp; n-->0 && *cp++ != '\n';); + if(regnexec(rp,sp,cp-sp, 0, (regmatch_t*)0, 0)==0) + return(sp-buff); + } + return(cp-buff); +} + +static int io_patseek(regex_t *rp, Sfio_t* sp, int flags) +{ + char *cp, *match; + int r, fd=sffileno(sp), close_exec = sh.fdstatus[fd]&IOCLEX; + int was_share,s=(PIPE_BUF>SF_BUFSIZE?SF_BUFSIZE:PIPE_BUF); + size_t n,m; + sh.fdstatus[sffileno(sp)] |= IOCLEX; + if(fd==0) + was_share = sfset(sp,SF_SHARE,1); + while((cp=sfreserve(sp, -s, SF_LOCKR)) || (cp=sfreserve(sp,SF_UNBOUND, SF_LOCKR))) + { + m = n = sfvalue(sp); + while(n>0 && cp[n-1]!='\n') + n--; + if(n) + m = n; + r = regrexec(rp,cp,m,0,(regmatch_t*)0, 0, '\n', (void*)&match, pat_seek); + if(r<0) + m = match-cp; + else if(r==2) + { + if((m = pat_line(rp,cp,m)) < n) + r = -1; + } + if(m && (flags&IOCOPY)) + sfwrite(sfstdout,cp,m); + sfread(sp,cp,m); + if(r<0) + break; + } + if(!close_exec) + sh.fdstatus[sffileno(sp)] &= ~IOCLEX; + if(fd==0 && !(was_share&SF_SHARE)) + sfset(sp, SF_SHARE,0); + return(0); +} + +static Sfoff_t file_offset(int fn, char *fname) +{ + Sfio_t *sp = sh.sftable[fn]; + char *cp; + Sfoff_t off; + struct Eof endf; + Namval_t *mp = nv_open("EOF",sh.var_tree,0); + Namval_t *pp = nv_open("CUR",sh.var_tree,0); + memset(&endf,0,sizeof(struct Eof)); + endf.fd = fn; + endf.hdr.disc = &EOF_disc; + endf.hdr.nofree = 1; + if(mp) + nv_stack(mp, &endf.hdr); + if(pp) + nv_stack(pp, &endf.hdr); + if(sp) + sfsync(sp); + off = sh_strnum(fname, &cp, 0); + if(mp) + nv_stack(mp, NiL); + if(pp) + nv_stack(pp, NiL); + return(*cp?(Sfoff_t)-1:off); +} + +/* + * close a pipe + */ +void sh_pclose(register int pv[]) +{ + if(pv[0]>=2) + sh_close(pv[0]); + if(pv[1]>=2) + sh_close(pv[1]); + pv[0] = pv[1] = -1; +} + +/* + * I/O redirection + * flag = 0 if files are to be restored + * flag = 2 if files are to be closed on exec + * flag = 3 when called from $( < ...), just open file and return + * flag = SH_SHOWME for trace only + */ +int sh_redirect(struct ionod *iop, int flag) +{ + Sfoff_t off; + register char *fname; + register int fd, iof; + const char *message = e_open; + int o_mode; /* mode flag for open */ + static char io_op[7]; /* used for -x trace info */ + int clexec=0, fn, traceon; + int r, indx = sh.topfd; + char *after="", *trace = sh.st.trap[SH_DEBUGTRAP]; + Namval_t *np=0; + if(flag==2) + clexec = 1; + if(iop) + traceon = sh_trace(NIL(char**),0); + for(;iop;iop=iop->ionxt) + { + iof=iop->iofile; + fn = (iof&IOUFD); + io_op[0] = '0'+(iof&IOUFD); + if(iof&IOPUT) + { + io_op[1] = '>'; + o_mode = O_WRONLY|O_CREAT; + } + else + { + io_op[1] = '<'; + o_mode = O_RDONLY|O_NONBLOCK; + } + io_op[2] = 0; + io_op[3] = 0; + io_op[4] = 0; + fname = iop->ioname; + if(!(iof&IORAW)) + { + if(iof&IOLSEEK) + { + struct argnod *ap = (struct argnod*)stakalloc(ARGVAL+strlen(iop->ioname)); + memset(ap, 0, ARGVAL); + ap->argflag = ARG_MAC; + strcpy(ap->argval,iop->ioname); + fname=sh_macpat(ap,(iof&IOARITH)?ARG_ARITH:ARG_EXP); + } + else + fname=sh_mactrim(fname,(!sh_isoption(SH_NOGLOB)&&sh_isoption(SH_INTERACTIVE))?2:0); + } + errno=0; + if(iop->iovname) + { + np = nv_open(iop->iovname,sh.var_tree,NV_NOASSIGN|NV_VARNAME); + if(nv_isattr(np,NV_RDONLY)) + errormsg(SH_DICT,ERROR_exit(1),e_readonly, nv_name(np)); + io_op[0] = '}'; + if((iof&IOMOV) && *fname=='-') + fn = nv_getnum(np); + } + if(iof&IOLSEEK) + { + io_op[2] = '#'; + if(iof&IOARITH) + { + strcpy(&io_op[3]," (("); + after = "))"; + } + else if(iof&IOCOPY) + io_op[3] = '#'; + goto traceit; + } + if(*fname) + { + if(iof&IODOC) + { + if(traceon) + sfputr(sfstderr,io_op,'<'); + fd = io_heredoc(iop,fname,traceon); + if(traceon && (flag==SH_SHOWME)) + sh_close(fd); + fname = 0; + } + else if(iof&IOMOV) + { + int dupfd,toclose= -1; + io_op[2] = '&'; + if((fd=fname[0])>='0' && fd<='9') + { + char *number = fname; + dupfd = strtol(fname,&number,10); + if(*number=='-') + { + toclose = dupfd; + number++; + } + if(*number || dupfd > IOUFD) + { + message = e_file; + goto fail; + } + if(sh.subshell && dupfd==1) + { + sh_subtmpfile(); + dupfd = sffileno(sfstdout); + } + else if(sh.sftable[dupfd]) + sfsync(sh.sftable[dupfd]); + } + else if(fd=='-' && fname[1]==0) + { + fd= -1; + goto traceit; + } + else if(fd=='p' && fname[1]==0) + { + if(iof&IOPUT) + dupfd = sh.coutpipe; + else + dupfd = sh.cpipe[0]; + if(flag) + toclose = dupfd; + } + else + { + message = e_file; + goto fail; + } + if(flag==SH_SHOWME) + goto traceit; + if((fd=sh_fcntl(dupfd,F_DUPFD,3))<0) + goto fail; + sh_iocheckfd(dupfd); + sh.fdstatus[fd] = (sh.fdstatus[dupfd]&~IOCLEX); + if(toclose<0 && sh.fdstatus[fd]&IOREAD) + sh.fdstatus[fd] |= IODUP; + else if(dupfd==sh.cpipe[0]) + sh_pclose(sh.cpipe); + else if(toclose>=0) + { + if(flag==0) + sh_iosave(toclose,indx); /* save file descriptor */ + sh_close(toclose); + } + } + else if(iof&IORDW) + { + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,fname); + io_op[2] = '>'; + o_mode = O_RDWR|O_CREAT; + goto openit; + } + else if(!(iof&IOPUT)) + { + if(flag==SH_SHOWME) + goto traceit; + fd=sh_chkopen(fname); + } + else if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,fname); + else + { + if(iof&IOAPP) + { + io_op[2] = '>'; + o_mode |= O_APPEND; + } + else + { + o_mode |= O_TRUNC; + if(iof&IOCLOB) + io_op[2] = '|'; + else if(sh_isoption(SH_NOCLOBBER)) + { + struct stat sb; + if(stat(fname,&sb)>=0) + { +#if SHOPT_FS_3D + if(S_ISREG(sb.st_mode)&& + (!sh.lim.fs3d || iview(&sb)==0)) +#else + if(S_ISREG(sb.st_mode)) +#endif /* SHOPT_FS_3D */ + { + errno = EEXIST; + errormsg(SH_DICT,ERROR_system(1),e_exists,fname); + } + } + else + o_mode |= O_EXCL; + } + } + openit: + if(flag!=SH_SHOWME) + { + if((fd=sh_open(fname,o_mode,RW_ALL)) <0) + errormsg(SH_DICT,ERROR_system(1),((o_mode&O_CREAT)?e_create:e_open),fname); + } + } + traceit: + if(traceon && fname) + { + if(np) + sfprintf(sfstderr,"{%s",nv_name(np)); + sfprintf(sfstderr,"%s %s%s%c",io_op,fname,after,iop->ionxt?' ':'\n'); + } + if(flag==SH_SHOWME) + return(indx); + if(trace && fname) + { + char *argv[7], **av=argv; + av[3] = io_op; + av[4] = fname; + av[5] = 0; + av[6] = 0; + if(iof&IOARITH) + av[5] = after; + if(np) + { + av[0] = "{"; + av[1] = nv_name(np); + av[2] = "}"; + } + else + av +=3; + sh_debug(trace,(char*)0,(char*)0,av,ARG_NOGLOB); + } + if(iof&IOLSEEK) + { + Sfio_t *sp = sh.sftable[fn]; + r = sh.fdstatus[fn]; + if(!(r&(IOSEEK|IONOSEEK))) + r = sh_iocheckfd(fn); + sfsprintf(io_op,sizeof(io_op),"%d\0",fn); + if(r==IOCLOSE) + { + fname = io_op; + message = e_file; + goto fail; + } + if(iof&IOARITH) + { + if(r&IONOSEEK) + { + fname = io_op; + message = e_notseek; + goto fail; + } + message = e_badseek; + if((off = file_offset(fn,fname))<0) + goto fail; + if(sp) + r=sfseek(sp, off, SEEK_SET); + else + r=lseek(fn, off, SEEK_SET); + } + else + { + regex_t *rp; + extern const char e_notimp[]; + if(!(r&IOREAD)) + { + message = e_noread; + goto fail; + } + if(!(rp = regcache(fname, REG_SHELL|REG_NOSUB|REG_NEWLINE|REG_AUGMENTED|REG_FIRST|REG_LEFT|REG_RIGHT, &r))) + { + message = e_badpattern; + goto fail; + } + if(!sp) + sp = sh_iostream(fn); + r=io_patseek(rp,sp,iof); + if(sp && flag==3) + { + /* close stream but not fn */ + sfsetfd(sp,-1); + sfclose(sp); + } + } + if(r<0) + goto fail; + if(flag==3) + return(fn); + continue; + } + if(!np) + { + if(flag==0) + { + if(fd==fn) + { + if((r=sh_fcntl(fd,F_DUPFD,10)) > 0) + { + fd = r; + sh_close(fn); + } + } + sh_iosave(fn,indx); + } + else if(sh_subsavefd(fn)) + sh_iosave(fn,indx|IOSUBSHELL); + } + if(fd<0) + { + if(sh_inuse(fn) || fn==sh.infd) + { + if(fn>9 || !(sh.inuse_bits&(1<<fn))) + io_preserve(sh.sftable[fn],fn); + } + sh_close(fn); + } + if(flag==3) + return(fd); + if(fd>=0) + { + if(np) + { + int32_t v; + fn = fd; + if(fd<10) + { + if((fn=fcntl(fd,F_DUPFD,10)) < 0) + goto fail; + sh.fdstatus[fn] = sh.fdstatus[fd]; + sh_close(fd); + fd = fn; + } + nv_unset(np); + nv_onattr(np,NV_INT32); + v = fn; + nv_putval(np,(char*)&v, NV_INT32); + sh_iocheckfd(fd); + } + else + { + fd = sh_iorenumber(sh_iomovefd(fd),fn); + if(fn>2 && fn<10) + sh.inuse_bits |= (1<<fn); + } + } + if(fd >2 && clexec) + { + fcntl(fd,F_SETFD,FD_CLOEXEC); + sh.fdstatus[fd] |= IOCLEX; + } + } + else + goto fail; + } + return(indx); +fail: + errormsg(SH_DICT,ERROR_system(1),message,fname); + /* NOTREACHED */ + return(0); +} +/* + * Create a tmp file for the here-document + */ +static int io_heredoc(register struct ionod *iop, const char *name, int traceon) +{ + register Sfio_t *infile = 0, *outfile; + register int fd; + if(!(iop->iofile&IOSTRG) && (!sh.heredocs || iop->iosize==0)) + return(sh_open(e_devnull,O_RDONLY)); + /* create an unnamed temporary file */ + if(!(outfile=sftmp(0))) + errormsg(SH_DICT,ERROR_system(1),e_tmpcreate); + if(iop->iofile&IOSTRG) + { + if(traceon) + sfprintf(sfstderr,"< %s\n",name); + sfputr(outfile,name,'\n'); + } + else + { + infile = subopen(sh.heredocs,iop->iooffset,iop->iosize); + if(traceon) + { + char *cp = sh_fmtq(iop->iodelim); + fd = (*cp=='$' || *cp=='\'')?' ':'\\'; + sfprintf(sfstderr," %c%s\n",fd,cp); + sfdisc(outfile,&tee_disc); + } + if(iop->iofile&IOQUOTE) + { + /* This is a quoted here-document, not expansion */ + sfmove(infile,outfile,SF_UNBOUND,-1); + sfclose(infile); + } + else + { + char *lastpath = sh.lastpath; + sh_machere(infile,outfile,iop->ioname); + sh.lastpath = lastpath; + if(infile) + sfclose(infile); + } + } + /* close stream outfile, but save file descriptor */ + fd = sffileno(outfile); + sfsetfd(outfile,-1); + sfclose(outfile); + if(traceon && !(iop->iofile&IOSTRG)) + sfputr(sfstderr,iop->ioname,'\n'); + lseek(fd,(off_t)0,SEEK_SET); + sh.fdstatus[fd] = IOREAD; + return(fd); +} + +/* + * This write discipline also writes the output on standard error + * This is used when tracing here-documents + */ +static ssize_t tee_write(Sfio_t *iop,const void *buff,size_t n,Sfdisc_t *unused) +{ + NOT_USED(unused); + sfwrite(sfstderr,buff,n); + return(write(sffileno(iop),buff,n)); +} + +/* + * copy file <origfd> into a save place + * The saved file is set close-on-exec + * if <origfd> < 0, then -origfd is saved, but not duped so that it + * will be closed with sh_iorestore. + */ +void sh_iosave(register int origfd, int oldtop) +{ +/*@ + assume oldtop>=0 && oldtop<sh.lim.open_max; +@*/ + + register int savefd; + int flag = (oldtop&IOSUBSHELL); + oldtop &= ~IOSUBSHELL; + /* see if already saved, only save once */ + for(savefd=sh.topfd; --savefd>=oldtop; ) + { + if(filemap[savefd].orig_fd == origfd) + return; + } + /* make sure table is large enough */ + if(sh.topfd >= filemapsize) + { + filemapsize += 8; + if(!(filemap = (struct fdsave*)realloc(filemap,filemapsize*sizeof(struct fdsave)))) + errormsg(SH_DICT,ERROR_exit(4),e_nospace); + + } +#if SHOPT_DEVFD + if(origfd <0) + { + savefd = origfd; + origfd = -origfd; + } + else +#endif /* SHOPT_DEVFD */ + { + if((savefd = sh_fcntl(origfd, F_DUPFD, 10)) < 0 && errno!=EBADF) + errormsg(SH_DICT,ERROR_system(1),e_toomany); + } + filemap[sh.topfd].subshell = flag; + filemap[sh.topfd].orig_fd = origfd; + filemap[sh.topfd++].save_fd = savefd; + if(savefd >=0) + { + register Sfio_t* sp = sh.sftable[origfd]; + /* make saved file close-on-exec */ + sh_fcntl(savefd,F_SETFD,FD_CLOEXEC); + if(origfd==job.fd) + job.fd = savefd; + sh.fdstatus[savefd] = sh.fdstatus[origfd]; + sh.fdptrs[savefd] = &filemap[sh.topfd-1].save_fd; + if(!(sh.sftable[savefd]=sp)) + return; + sfsync(sp); + if(origfd <=2) + { + /* copy standard stream to new stream */ + sp = sfswap(sp,NIL(Sfio_t*)); + sh.sftable[savefd] = sp; + } + else + sh.sftable[origfd] = 0; + } +} + +/* + * close all saved file descriptors + */ +void sh_iounsave(void) +{ + register int fd, savefd, newfd; + for(newfd=fd=0; fd < sh.topfd; fd++) + { + if((savefd = filemap[fd].save_fd)< 0) + filemap[newfd++] = filemap[fd]; + else + { + sh.sftable[savefd] = 0; + sh_close(savefd); + } + } + sh.topfd = newfd; +} + +/* + * restore saved file descriptors from <last> on + */ +void sh_iorestore(int last, int jmpval) +{ + register int origfd, savefd, fd; + int flag = (last&IOSUBSHELL); + last &= ~IOSUBSHELL; + for (fd = sh.topfd - 1; fd >= last; fd--) + { + if(!flag && filemap[fd].subshell) + continue; + if(jmpval==SH_JMPSCRIPT) + { + if ((savefd = filemap[fd].save_fd) >= 0) + { + sh.sftable[savefd] = 0; + sh_close(savefd); + } + continue; + } + origfd = filemap[fd].orig_fd; + sh_close(origfd); + if ((savefd = filemap[fd].save_fd) >= 0) + { + sh_fcntl(savefd, F_DUPFD, origfd); + if(savefd==job.fd) + job.fd=origfd; + sh.fdstatus[origfd] = sh.fdstatus[savefd]; + /* turn off close-on-exec if flag if necessary */ + if(sh.fdstatus[origfd]&IOCLEX) + fcntl(origfd,F_SETFD,FD_CLOEXEC); + if(origfd<=2) + { + sfswap(sh.sftable[savefd],sh.sftable[origfd]); + if(origfd==0) + sh.st.ioset = 0; + } + else + sh.sftable[origfd] = sh.sftable[savefd]; + sh.sftable[savefd] = 0; + sh_close(savefd); + } + else + sh.fdstatus[origfd] = IOCLOSE; + } + if(!flag) + { + /* keep file descriptors for subshell restore */ + for (fd = last ; fd < sh.topfd; fd++) + { + if(filemap[fd].subshell) + filemap[last++] = filemap[fd]; + } + } + if(last < sh.topfd) + sh.topfd = last; +} + +/* + * returns access information on open file <fd> + * returns -1 for failure, 0 for success + * <mode> is the same as for access() + */ +int sh_ioaccess(int fd,register int mode) +{ + register int flags; + if(mode==X_OK) + return(-1); + if((flags=sh_iocheckfd(fd))!=IOCLOSE) + { + if(mode==F_OK) + return(0); + if(mode==R_OK && (flags&IOREAD)) + return(0); + if(mode==W_OK && (flags&IOWRITE)) + return(0); + } + return(-1); +} + +/* + * Handle interrupts for slow streams + */ +static int slowexcept(register Sfio_t *iop,int type,void *data,Sfdisc_t *handle) +{ + register int n,fno; + NOT_USED(handle); + if(type==SF_DPOP || type==SF_FINAL) + free((void*)handle); + if(type!=SF_READ) + return(0); + if((sh.trapnote&(SH_SIGSET|SH_SIGTRAP)) && errno!=EIO && errno!=ENXIO) + errno = EINTR; + fno = sffileno(iop); + if((n=sfvalue(iop))<=0) + { +#ifndef FNDELAY +# ifdef O_NDELAY + if(errno==0 && (n=fcntl(fno,F_GETFL,0))&O_NDELAY) + { + n &= ~O_NDELAY; + fcntl(fno, F_SETFL, n); + return(1); + } +# endif /* O_NDELAY */ +#endif /* !FNDELAY */ +#ifdef O_NONBLOCK + if(errno==EAGAIN) + { + n = fcntl(fno,F_GETFL,0); + n &= ~O_NONBLOCK; + fcntl(fno, F_SETFL, n); + return(1); + } +#endif /* O_NONBLOCK */ + if(errno!=EINTR) + return(0); + n=1; + } + errno = 0; + if(sh.trapnote&SH_SIGSET) + { + if(isatty(fno)) + sfputc(sfstderr,'\n'); + sh_exit(SH_EXITSIG); + } + if(sh.trapnote&SH_SIGTRAP) + sh_chktrap(); + return(n); +} + +/* + * called when slowread times out + */ +static void time_grace(void *handle) +{ + NOT_USED(handle); + timeout = 0; + if(sh_isstate(SH_GRACE)) + { + sh_offstate(SH_GRACE); + if(!sh_isstate(SH_INTERACTIVE)) + return; + ((struct checkpt*)sh.jmplist)->mode = SH_JMPEXIT; + errormsg(SH_DICT,2,e_timeout); + sh.trapnote |= SH_SIGSET; + return; + } + errormsg(SH_DICT,0,e_timewarn); + sh_onstate(SH_GRACE); + sigrelease(SIGALRM); + sh.trapnote |= SH_SIGTRAP; +} + +static ssize_t piperead(Sfio_t *iop,void *buff,register size_t size,Sfdisc_t *handle) +{ + int fd = sffileno(iop); + NOT_USED(handle); + if(sh.trapnote) + { + errno = EINTR; + return(-1); + } + if(sh_isstate(SH_INTERACTIVE) && io_prompt(iop,sh.nextprompt)<0 && errno==EIO) + return(0); + if(!(sh.fdstatus[sffileno(iop)]&IOCLEX) && (sfset(iop,0,0)&SF_SHARE)) + size = ed_read(sh.ed_context, fd, (char*)buff, size,0); + else + size = sfrd(iop,buff,size,handle); + return(size); +} +/* + * This is the read discipline that is applied to slow devices + * This routine takes care of prompting for input + */ +static ssize_t slowread(Sfio_t *iop,void *buff,register size_t size,Sfdisc_t *handle) +{ + int (*readf)(void*, int, char*, int, int); + int reedit=0, rsize; +#if SHOPT_HISTEXPAND + char *xp=0; +#endif + NOT_USED(handle); +# if SHOPT_ESH + if(sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS)) + readf = ed_emacsread; + else +# endif /* SHOPT_ESH */ +# if SHOPT_VSH +# if SHOPT_RAWONLY + if(sh_isoption(SH_VI) || ((SHOPT_RAWONLY-0) && mbwide())) +# else + if(sh_isoption(SH_VI)) +# endif + readf = ed_viread; + else +# endif /* SHOPT_VSH */ + readf = ed_read; + if(sh.trapnote) + { + errno = EINTR; + return(-1); + } + while(1) + { + if(io_prompt(iop,sh.nextprompt)<0 && errno==EIO) + return(0); + if(sh.timeout) + timeout = (void*)sh_timeradd(sh_isstate(SH_GRACE)?1000L*TGRACE:1000L*sh.timeout,0,time_grace,NIL(void*)); + rsize = (*readf)(sh.ed_context, sffileno(iop), (char*)buff, size, reedit); + if(timeout) + timerdel(timeout); + timeout=0; +#if SHOPT_HISTEXPAND + if(rsize && *(char*)buff != '\n' && sh.nextprompt==1 && sh_isoption(SH_HISTEXPAND)) + { + int r; + ((char*)buff)[rsize] = '\0'; + if(xp) + { + free(xp); + xp = 0; + } + r = hist_expand(buff, &xp); + if((r & (HIST_EVENT|HIST_PRINT)) && !(r & HIST_ERROR) && xp) + { + strlcpy(buff, xp, size); + rsize = strlen(buff); + if(!sh_isoption(SH_HISTVERIFY) || readf==ed_read) + { + sfputr(sfstderr, xp, -1); + break; + } + reedit = rsize - 1; + continue; + } + if((r & HIST_ERROR) && sh_isoption(SH_HISTREEDIT)) + { + reedit = rsize - 1; + continue; + } + if(r & (HIST_ERROR|HIST_PRINT)) + { + *(char*)buff = '\n'; + rsize = 1; + } + } +#endif + break; + } + return(rsize); +} + +/* + * check and return the attributes for a file descriptor + */ + +int sh_iocheckfd(register int fd) +{ + register int flags, n; + if((n=sh.fdstatus[fd])&IOCLOSE) + return(n); + if(!(n&(IOREAD|IOWRITE))) + { +#ifdef F_GETFL + if((flags=fcntl(fd,F_GETFL,0)) < 0) + return(sh.fdstatus[fd]=IOCLOSE); + if((flags&O_ACCMODE)!=O_WRONLY) + n |= IOREAD; + if((flags&O_ACCMODE)!=O_RDONLY) + n |= IOWRITE; +#else + struct stat statb; + if((flags = fstat(fd,&statb))< 0) + return(sh.fdstatus[fd]=IOCLOSE); + n |= (IOREAD|IOWRITE); + if(read(fd,"",0) < 0) + n &= ~IOREAD; +#endif /* F_GETFL */ + } + if(!(n&(IOSEEK|IONOSEEK))) + { + struct stat statb; + /* /dev/null check is a workaround for select bug */ + static ino_t null_ino; + static dev_t null_dev; + if(null_ino==0 && stat(e_devnull,&statb) >=0) + { + null_ino = statb.st_ino; + null_dev = statb.st_dev; + } + if(tty_check(fd)) + n |= IOTTY; + if(lseek(fd,NIL(off_t),SEEK_CUR)<0) + { + n |= IONOSEEK; +#ifdef S_ISSOCK + if((fstat(fd,&statb)>=0) && S_ISSOCK(statb.st_mode)) + n |= IOREAD|IOWRITE; +#endif /* S_ISSOCK */ + } + else if((fstat(fd,&statb)>=0) && ( + S_ISFIFO(statb.st_mode) || +#ifdef S_ISSOCK + S_ISSOCK(statb.st_mode) || +#endif /* S_ISSOCK */ + /* The following is for sockets on the sgi */ + (statb.st_ino==0 && (statb.st_mode & ~(S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH|S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID))==0) || + (S_ISCHR(statb.st_mode) && (statb.st_ino!=null_ino || statb.st_dev!=null_dev)) + )) + n |= IONOSEEK; + else + n |= IOSEEK; + } + sh.fdstatus[fd] = n; + return(n); +} + +/* + * Display prompt PS<flag> on standard error + */ + +static int io_prompt(Sfio_t *iop,register int flag) +{ + register char *cp; + char buff[1]; + char *endprompt; + static short cmdno; + int sfflags; + if(flag<3 && !sh_isstate(SH_INTERACTIVE)) + flag = 0; + if(flag==2 && sfpkrd(sffileno(iop),buff,1,'\n',0,1) >= 0) + flag = 0; + if(flag==0) + return(sfsync(sfstderr)); + sfflags = sfset(sfstderr,SF_SHARE|SF_PUBLIC|SF_READ,0); + if(!(sh.prompt=(char*)sfreserve(sfstderr,0,0))) + sh.prompt = ""; + switch(flag) + { + case 1: + { + register int c; +#if defined(TIOCLBIC) && defined(LFLUSHO) + if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS)) + { + /* + * re-enable output in case the user has + * disabled it. Not needed with edit mode + */ + int mode = LFLUSHO; + ioctl(sffileno(sfstderr),TIOCLBIC,&mode); + } +#endif /* TIOCLBIC */ + cp = sh_mactry(nv_getval(nv_scoped(PS1NOD))); + for(;c= *cp;cp++) + { + if(c==HIST_CHAR) + { + /* look at next character */ + c = *++cp; + /* print out line number if not !! */ + if(c!= HIST_CHAR) + { + sfprintf(sfstderr,"%d", sh.hist_ptr?(int)sh.hist_ptr->histind:++cmdno); + } + if(c==0) + goto done; + } + sfputc(sfstderr,c); + } + goto done; + } + case 2: + cp = nv_getval(nv_scoped(PS2NOD)); + break; + case 3: + cp = nv_getval(nv_scoped(PS3NOD)); + break; + default: + goto done; + } + if(cp) + sfputr(sfstderr,cp,-1); +done: + if(*sh.prompt && (endprompt=(char*)sfreserve(sfstderr,0,0))) + *endprompt = 0; + sfset(sfstderr,sfflags&SF_READ|SF_SHARE|SF_PUBLIC,1); + return(sfsync(sfstderr)); +} + +/* + * This discipline is inserted on write pipes to prevent SIGPIPE + * from causing an infinite loop + */ +static int pipeexcept(Sfio_t* iop, int mode, void *data, Sfdisc_t* handle) +{ + NOT_USED(iop); + if(mode==SF_DPOP || mode==SF_FINAL) + free((void*)handle); + else if(mode==SF_WRITE && errno==EINTR && sh.lastsig==SIGPIPE) + return(-1); + return(0); +} + +/* + * keep track of each stream that is opened and closed + */ +static void sftrack(Sfio_t* sp,int flag, int newfd) +{ + register int fd = sffileno(sp); + register struct checkpt *pp; + register int mode; + if(flag==SF_SETFD || flag==SF_CLOSING) + { + if(newfd<0) + flag = SF_CLOSING; + if(fdnotify) + (*fdnotify)(sffileno(sp),flag==SF_CLOSING?-1:newfd); + } +#ifdef DEBUG + if(flag==SF_READ || flag==SF_WRITE) + { + char *z = fmtbase((long)getpid(),0,0); + write(ERRIO,z,strlen(z)); + write(ERRIO,": ",2); + write(ERRIO,"attempt to ",11); + if(flag==SF_READ) + write(ERRIO,"read from",9); + else + write(ERRIO,"write to",8); + write(ERRIO," locked stream\n",15); + return; + } +#endif + if((unsigned)fd >= sh.lim.open_max) + return; + if(sh_isstate(SH_NOTRACK)) + return; + mode = sfset(sp,0,0); + if(sp==sh.heredocs && fd < 10 && flag==SF_NEW) + { + fd = sfsetfd(sp,10); + fcntl(fd,F_SETFD,FD_CLOEXEC); + } + if(fd < 3) + return; + if(flag==SF_NEW) + { + if(!sh.sftable[fd] && sh.fdstatus[fd]==IOCLOSE) + { + sh.sftable[fd] = sp; + flag = (mode&SF_WRITE)?IOWRITE:0; + if(mode&SF_READ) + flag |= IOREAD; + sh.fdstatus[fd] = flag; +#if 0 + if(flag==IOWRITE) + sfpool(sp,sh.outpool,SF_WRITE); + else +#else + if(flag!=IOWRITE) +#endif + sh_iostream(fd); + } + if((pp=(struct checkpt*)sh.jmplist) && pp->mode==SH_JMPCMD) + { + struct openlist *item; + /* + * record open file descriptors so they can + * be closed in case a longjmp prevents + * built-ins from cleanup + */ + item = new_of(struct openlist, 0); + item->strm = sp; + item->next = pp->olist; + pp->olist = item; + } + if(fdnotify) + (*fdnotify)(-1,sffileno(sp)); + } + else if(flag==SF_CLOSING || (flag==SF_SETFD && newfd<=2)) + { + sh.sftable[fd] = 0; + sh.fdstatus[fd]=IOCLOSE; + if(pp=(struct checkpt*)sh.jmplist) + { + struct openlist *item; + for(item=pp->olist; item; item=item->next) + { + if(item->strm == sp) + { + item->strm = 0; + break; + } + } + } + } +} + +struct eval +{ + Sfdisc_t disc; + char **argv; + short slen; + char addspace; +}; + +/* + * Create a stream consisting of a space separated argv[] list + */ + +Sfio_t *sh_sfeval(register char *argv[]) +{ + register Sfio_t *iop; + register char *cp; + if(argv[1]) + cp = ""; + else + cp = argv[0]; + iop = sfopen(NIL(Sfio_t*),(char*)cp,"s"); + if(argv[1]) + { + register struct eval *ep; + if(!(ep = new_of(struct eval,0))) + return(NIL(Sfio_t*)); + ep->disc = eval_disc; + ep->argv = argv; + ep->slen = -1; + ep->addspace = 0; + sfdisc(iop,&ep->disc); + } + return(iop); +} + +/* + * This code gets called whenever an end of string is found with eval + */ + +static int eval_exceptf(Sfio_t *iop,int type, void *data, Sfdisc_t *handle) +{ + register struct eval *ep = (struct eval*)handle; + register char *cp; + register int len; + + /* no more to do */ + if(type!=SF_READ || !(cp = ep->argv[0])) + { + if(type==SF_CLOSING) + sfdisc(iop,SF_POPDISC); + else if(ep && (type==SF_DPOP || type==SF_FINAL)) + free((void*)ep); + return(0); + } + + if(!ep->addspace) + { + /* get the length of this string */ + ep->slen = len = strlen(cp); + /* move to next string */ + ep->argv++; + } + else /* insert space between arguments */ + { + len = 1; + cp = " "; + } + /* insert the new string */ + sfsetbuf(iop,cp,len); + ep->addspace = !ep->addspace; + return(1); +} + +/* + * This routine returns a stream pointer to a segment of length <size> from + * the stream <sp> starting at offset <offset> + * The stream can be read with the normal stream operations + */ + +static Sfio_t *subopen(Sfio_t* sp, off_t offset, long size) +{ + register struct subfile *disp; + if(sfseek(sp,offset,SEEK_SET) <0) + return(NIL(Sfio_t*)); + if(!(disp = (struct subfile*)malloc(sizeof(struct subfile)+IOBSIZE+1))) + return(NIL(Sfio_t*)); + disp->disc = sub_disc; + disp->oldsp = sp; + disp->offset = offset; + disp->size = disp->left = size; + sp = sfnew(NIL(Sfio_t*),(char*)(disp+1),IOBSIZE,sh.lim.open_max,SF_READ); + sfdisc(sp,&disp->disc); + return(sp); +} + +/* + * read function for subfile discipline + */ +static ssize_t subread(Sfio_t* sp,void* buff,register size_t size,Sfdisc_t* handle) +{ + register struct subfile *disp = (struct subfile*)handle; + NOT_USED(sp); + if(disp->left == 0) + return(0); + if(size > disp->left) + size = disp->left; + disp->left -= size; + return(sfread(disp->oldsp,buff,size)); +} + +/* + * exception handler for subfile discipline + */ +static int subexcept(Sfio_t* sp,register int mode, void *data, Sfdisc_t* handle) +{ + register struct subfile *disp = (struct subfile*)handle; + if(mode==SF_CLOSING) + { + sfdisc(sp,SF_POPDISC); + return(0); + } + else if(disp && (mode==SF_DPOP || mode==SF_FINAL)) + { + free((void*)disp); + return(0); + } +#ifdef SF_ATEXIT + else if (mode==SF_ATEXIT) + { + sfdisc(sp, SF_POPDISC); + return(0); + } +#endif + else if(mode==SF_READ) + return(0); + return(-1); +} + +#define NROW 15 /* number of rows before going to multi-columns */ +#define LBLSIZ 3 /* size of label field and interfield spacing */ +/* + * print a list of arguments in columns + */ +void sh_menu(Sfio_t *outfile,int argn,char *argv[]) +{ + register int i,j; + register char **arg; + int nrow, ncol=1, ndigits=1; + int fldsize, wsize = ed_window(); + char *cp = nv_getval(nv_scoped(LINES)); + nrow = (cp?1+2*((int)strtol(cp, (char**)0, 10)/3):NROW); + for(i=argn;i >= 10;i /= 10) + ndigits++; + if(argn < nrow) + { + nrow = argn; + goto skip; + } + i = 0; + for(arg=argv; *arg;arg++) + { + if((j=strlen(*arg)) > i) + i = j; + } + i += (ndigits+LBLSIZ); + if(i < wsize) + ncol = wsize/i; + if(argn > nrow*ncol) + { + nrow = 1 + (argn-1)/ncol; + } + else + { + ncol = 1 + (argn-1)/nrow; + nrow = 1 + (argn-1)/ncol; + } +skip: + fldsize = (wsize/ncol)-(ndigits+LBLSIZ); + for(i=0;i<nrow;i++) + { + if(sh.trapnote&SH_SIGSET) + return; + j = i; + while(1) + { + arg = argv+j; + sfprintf(outfile,"%*d) %s",ndigits,j+1,*arg); + j += nrow; + if(j >= argn) + break; + sfnputc(outfile,' ',fldsize-strlen(*arg)); + } + sfputc(outfile,'\n'); + } +} + +#undef read +/* + * shell version of read() for user added builtins + */ +ssize_t sh_read(register int fd, void* buff, size_t n) +{ + register Sfio_t *sp; + if(sp=sh.sftable[fd]) + return(sfread(sp,buff,n)); + else + return(read(fd,buff,n)); +} + +#undef write +/* + * shell version of write() for user added builtins + */ +ssize_t sh_write(register int fd, const void* buff, size_t n) +{ + register Sfio_t *sp; + if(sp=sh.sftable[fd]) + return(sfwrite(sp,buff,n)); + else + return(write(fd,buff,n)); +} + +#undef lseek +/* + * shell version of lseek() for user added builtins + */ +off_t sh_seek(register int fd, off_t offset, int whence) +{ + register Sfio_t *sp; + if((sp=sh.sftable[fd]) && (sfset(sp,0,0)&(SF_READ|SF_WRITE))) + return(sfseek(sp,offset,whence)); + else + return(lseek(fd,offset,whence)); +} + +#undef dup +int sh_dup(register int old) +{ + register int fd = dup(old); + if(fd>=0) + { + if(sh.fdstatus[old] == IOCLOSE) + sh.fdstatus[old] = 0; + sh.fdstatus[fd] = (sh.fdstatus[old]&~IOCLEX); + if(fdnotify) + (*fdnotify)(old,fd); + } + return(fd); +} + +#undef fcntl +int sh_fcntl(register int fd, int op, ...) +{ + int newfd, arg; + va_list ap; + va_start(ap, op); + arg = va_arg(ap, int) ; + va_end(ap); + newfd = fcntl(fd,op,arg); + if(newfd>=0) switch(op) + { + case F_DUPFD: + if(sh.fdstatus[fd] == IOCLOSE) + sh.fdstatus[fd] = 0; + sh.fdstatus[newfd] = (sh.fdstatus[fd]&~IOCLEX); + if(fdnotify) + (*fdnotify)(fd,newfd); + break; + case F_SETFD: + if(sh.fdstatus[fd] == IOCLOSE) + sh.fdstatus[fd] = 0; + if(arg&FD_CLOEXEC) + sh.fdstatus[fd] |= IOCLEX; + else + sh.fdstatus[fd] &= ~IOCLEX; + } + return(newfd); +} + +#undef umask +mode_t sh_umask(mode_t m) +{ + sh.mask = m; + return(umask(m)); +} + +/* + * give file descriptor <fd> and <mode>, return an iostream pointer + * <mode> must be SF_READ or SF_WRITE + * <fd> must be a non-negative number ofr SH_IOCOPROCESS or SH_IOHISTFILE. + * returns NULL on failure and may set errno. + */ + +Sfio_t *sh_iogetiop(int fd, int mode) +{ + int n; + Sfio_t *iop=0; + if(mode!=SF_READ && mode!=SF_WRITE) + { + errno = EINVAL; + return(iop); + } + switch(fd) + { + case SH_IOHISTFILE: + if(!sh_histinit()) + return(iop); + fd = sffileno(sh.hist_ptr->histfp); + break; + case SH_IOCOPROCESS: + if(mode==SF_WRITE) + fd = sh.coutpipe; + else + fd = sh.cpipe[0]; + break; + default: + if(fd<0 || fd >= sh.lim.open_max) + fd = -1; + } + if(fd<0) + { + errno = EBADF; + return(iop); + } + if(!(n=sh.fdstatus[fd])) + n = sh_iocheckfd(fd); + if(mode==SF_WRITE && !(n&IOWRITE)) + return(iop); + if(mode==SF_READ && !(n&IOREAD)) + return(iop); + if(!(iop = sh.sftable[fd])) + iop=sh_iostream(fd); + return(iop); +} + +typedef int (*Notify_f)(int,int); + +Notify_f sh_fdnotify(Notify_f notify) +{ + Notify_f old; + old = fdnotify; + fdnotify = notify; + return(old); +} + +Sfio_t *sh_fd2sfio(int fd) +{ + register int status; + Sfio_t *sp = sh.sftable[fd]; + if(!sp && (status = sh_iocheckfd(fd))!=IOCLOSE) + { + register int flags=0; + if(status&IOREAD) + flags |= SF_READ; + if(status&IOWRITE) + flags |= SF_WRITE; + sp = sfnew(NULL, NULL, -1, fd,flags); + sh.sftable[fd] = sp; + } + return(sp); +} + +Sfio_t *sh_pathopen(const char *cp) +{ + int n; +#ifdef PATH_BFPATH + if((n=path_open(cp,path_get(cp))) < 0) + n = path_open(cp,(Pathcomp_t*)0); +#else + if((n=path_open(cp,path_get(cp))) < 0) + n = path_open(cp,""); +#endif + if(n < 0) + errormsg(SH_DICT,ERROR_system(1),e_open,cp); + return(sh_iostream(n)); +} diff --git a/usr/src/lib/libshell/common/sh/jobs.c b/usr/src/lib/libshell/common/sh/jobs.c new file mode 100644 index 0000000000..d397810f3d --- /dev/null +++ b/usr/src/lib/libshell/common/sh/jobs.c @@ -0,0 +1,1706 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Job control for UNIX Shell + * + * David Korn + * AT&T Labs + * + * Written October, 1982 + * Rewritten April, 1988 + * Revised January, 1992 + */ + +#include "defs.h" +#include <ctype.h> +#include <wait.h> +#include "io.h" +#include "jobs.h" +#include "history.h" + +#if !defined(WCONTINUED) || !defined(WIFCONTINUED) +# undef WCONTINUED +# define WCONTINUED 0 +# undef WIFCONTINUED +# define WIFCONTINUED(wstat) (0) +#endif + +#define NJOB_SAVELIST 4 + +/* + * temporary hack to get W* macros to work + */ +#undef wait +#define wait ______wait +/* + * This struct saves a link list of processes that have non-zero exit + * status, have had $! saved, but haven't been waited for + */ +struct jobsave +{ + struct jobsave *next; + pid_t pid; + unsigned short exitval; +}; + +static struct jobsave *job_savelist; +static int njob_savelist; + +static void init_savelist(void) +{ + register struct jobsave *jp; + while(njob_savelist < NJOB_SAVELIST) + { + jp = newof(0,struct jobsave,1,0); + jp->next = job_savelist; + job_savelist = jp; + njob_savelist++; + } +} + +/* + * return next on link list of jobsave free list + */ +static struct jobsave *jobsave_create(pid_t pid) +{ + register struct jobsave *jp = job_savelist; + if(jp) + { + njob_savelist--; + job_savelist = jp->next; + } + else + jp = newof(0,struct jobsave,1,0); + if(jp) + jp->pid = pid; + return(jp); +} + +struct back_save +{ + int count; + struct jobsave *list; +}; + +#define BYTE(n) (((n)+CHAR_BIT-1)/CHAR_BIT) +#define MAXMSG 25 +#define SH_STOPSIG (SH_EXITSIG<<1) + +#ifdef VSUSP +# ifndef CNSUSP +# ifdef _POSIX_VDISABLE +# define CNSUSP _POSIX_VDISABLE +# else +# define CNSUSP 0 +# endif /* _POSIX_VDISABLE */ +# endif /* CNSUSP */ +# ifndef CSWTCH +# ifdef CSUSP +# define CSWTCH CSUSP +# else +# define CSWTCH ('z'&037) +# endif /* CSUSP */ +# endif /* CSWTCH */ +#endif /* VSUSP */ + +/* Process states */ +#define P_EXITSAVE 01 +#define P_STOPPED 02 +#define P_NOTIFY 04 +#define P_SIGNALLED 010 +#define P_STTY 020 +#define P_DONE 040 +#define P_COREDUMP 0100 +#define P_DISOWN 0200 +#define P_FG 0400 + +static int job_chksave(pid_t); +static struct process *job_bypid(pid_t); +static struct process *job_byjid(int); +static char *job_sigmsg(int); +static int job_alloc(void); +static void job_free(int); +static struct process *job_unpost(struct process*,int); +static void job_unlink(struct process*); +static void job_prmsg(struct process*); +static struct process *freelist; +static char beenhere; +static char possible; +static struct process dummy; +static char by_number; +static Sfio_t *outfile; +static pid_t lastpid; +static struct back_save bck; + + +#ifdef JOBS + static void job_set(struct process*); + static void job_reset(struct process*); + static void job_waitsafe(int); + static struct process *job_byname(char*); + static struct process *job_bystring(char*); + static struct termios my_stty; /* terminal state for shell */ + static char *job_string; +#else + extern const char e_coredump[]; +#endif /* JOBS */ + +#ifdef SIGTSTP + static void job_unstop(struct process*); + static void job_fgrp(struct process*, int); +# ifndef _lib_tcgetpgrp +# ifdef TIOCGPGRP + static int _i_; +# define tcgetpgrp(a) (ioctl(a, TIOCGPGRP, &_i_)>=0?_i_:-1) +# endif /* TIOCGPGRP */ + int tcsetpgrp(int fd,pid_t pgrp) + { + int pgid = pgrp; +# ifdef TIOCGPGRP + return(ioctl(fd, TIOCSPGRP, &pgid)); +# else + return(-1); +# endif /* TIOCGPGRP */ + } +# endif /* _lib_tcgetpgrp */ +#else +# define job_unstop(pw) +# undef CNSUSP +#endif /* SIGTSTP */ + +#ifndef OTTYDISC +# undef NTTYDISC +#endif /* OTTYDISC */ + +#ifdef JOBS + +typedef int (*Waitevent_f)(int,long,int); + +/* + * Reap one job + * When called with sig==0, it does a blocking wait + */ +int job_reap(register int sig) +{ + register pid_t pid; + register struct process *pw; + struct process *px; + register int flags; + struct process dummy; + struct jobsave *jp; + int nochild=0, oerrno, wstat; + Waitevent_f waitevent = sh.waitevent; + static int wcontinued = WCONTINUED; +#ifdef DEBUG + if(sfprintf(sfstderr,"ksh: job line %4d: reap pid=%d critical=%d signal=%d\n",__LINE__,getpid(),job.in_critical,sig) <=0) + write(2,"waitsafe\n",9); + sfsync(sfstderr); +#endif /* DEBUG */ + job.savesig = 0; + if(sig) + flags = WNOHANG|WUNTRACED|wcontinued; + else + flags = WUNTRACED|wcontinued; + sh.waitevent = 0; + oerrno = errno; + while(1) + { + if(!(flags&WNOHANG) && !sh.intrap && waitevent && job.pwlist) + { + if((*waitevent)(-1,-1L,0)) + flags |= WNOHANG; + } + pid = waitpid((pid_t)-1,&wstat,flags); + + /* + * some systems (linux 2.6) may return EINVAL + * when there are no continued children + */ + + if (pid<0 && errno==EINVAL && (flags&WCONTINUED)) + pid = waitpid((pid_t)-1,&wstat,flags&=~WCONTINUED); + sh_sigcheck(); + if(sig && pid<0 && errno==EINTR) + continue; + if(pid<=0) + break; + flags |= WNOHANG; + job.waitsafe++; + jp = 0; + if(!(pw=job_bypid(pid))) + { +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: reap pid=%d critical=%d unknown job pid=%d pw=%x\n",__LINE__,getpid(),job.in_critical,pid,pw); +#endif /* DEBUG */ + pw = &dummy; + pw->p_exit = 0; + pw->p_pgrp = 0; + if(job.toclear) + job_clear(); + if(bck.count++ > sh.lim.child_max) + job_chksave(0); + if(jp = jobsave_create(pid)) + { + jp->next = bck.list; + bck.list = jp; + jp->exitval = 0; + } + pw->p_flag = 0; + lastpid = pw->p_pid = pid; + px = 0; + if(jp && WIFSTOPPED(wstat)) + { + jp->exitval = SH_STOPSIG; + continue; + } + } +#ifdef SIGTSTP + else + px=job_byjid(pw->p_job); + if(WIFSTOPPED(wstat)) + { + if(px) + { + /* move to top of job list */ + job_unlink(px); + px->p_nxtjob = job.pwlist; + job.pwlist = px; + } + pw->p_exit = WSTOPSIG(wstat); + pw->p_flag |= (P_NOTIFY|P_SIGNALLED|P_STOPPED); + if(pw->p_pgrp && pw->p_pgrp==job.curpgid && sh_isstate(SH_STOPOK)) + sh_fault(pw->p_exit); + continue; + } + else if (WIFCONTINUED(wstat) && wcontinued) + { + pw->p_flag &= ~(P_NOTIFY|P_SIGNALLED|P_STOPPED); + pw->p_exit = 0; + } + else +#endif /* SIGTSTP */ + { + /* check for coprocess completion */ + if(pid==sh.cpid) + { + sh_close(sh.coutpipe); + sh_close(sh.cpipe[1]); + sh.cpipe[1] = -1; + sh.coutpipe = -1; + } + if (WIFSIGNALED(wstat)) + { + pw->p_flag &= ~P_STOPPED; + pw->p_flag |= (P_DONE|P_NOTIFY|P_SIGNALLED); + if (WTERMCORE(wstat)) + pw->p_flag |= P_COREDUMP; + pw->p_exit = WTERMSIG(wstat); + /* if process in current jobs terminates from + * an interrupt, propogate to parent shell + */ + if(pw->p_pgrp && pw->p_pgrp==job.curpgid && pw->p_exit==SIGINT && sh_isstate(SH_STOPOK)) + { + pw->p_flag &= ~P_NOTIFY; + sh_offstate(SH_STOPOK); + sh_fault(SIGINT); + sh_onstate(SH_STOPOK); + } + } + else + { + pw->p_flag |= (P_DONE|P_NOTIFY); + if(WEXITSTATUS(wstat) > pw->p_exit) + pw->p_exit = WEXITSTATUS(wstat); + } + if(pw->p_pgrp==0) + pw->p_flag &= ~P_NOTIFY; + } + if(jp && pw== &dummy) + { + jp->exitval = pw->p_exit; + if(pw->p_flag&P_SIGNALLED) + jp->exitval |= SH_EXITSIG; + } +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: reap pid=%d critical=%d job %d with pid %d flags=%o complete with status=%x exit=%d\n",__LINE__,getpid(),job.in_critical,pw->p_job,pid,pw->p_flag,wstat,pw->p_exit); + sfsync(sfstderr); +#endif /* DEBUG*/ + /* only top-level process in job should have notify set */ + if(px && pw != px) + pw->p_flag &= ~P_NOTIFY; + } + if(errno==ECHILD) + { + errno = oerrno; + nochild = 1; + } + sh.waitevent = waitevent; + if(!sh.intrap && sh.st.trapcom[SIGCHLD]) + { + sh.sigflag[SIGCHLD] |= SH_SIGTRAP; + sh.trapnote |= SH_SIGTRAP; + } + if(sh_isoption(SH_NOTIFY) && sh_isstate(SH_TTYWAIT)) + { + outfile = sfstderr; + job_list(pw,JOB_NFLAG|JOB_NLFLAG); + job_unpost(pw,1); + sfsync(sfstderr); + } + if(sig) + signal(sig, job_waitsafe); + return(nochild); +} + +/* + * This is the SIGCLD interrupt routine + */ +static void job_waitsafe(int sig) +{ + if(job.in_critical) + { + job.savesig = sig; + job.waitsafe++; + } + else + job_reap(sig); +} + +/* + * initialize job control if possible + * if lflag is set the switching driver message will not print + */ +void job_init(int lflag) +{ + register int i,ntry=0; + job.fd = JOBTTY; + signal(SIGCHLD,job_waitsafe); +# if defined(SIGCLD) && (SIGCLD!=SIGCHLD) + signal(SIGCLD,job_waitsafe); +# endif + if(njob_savelist < NJOB_SAVELIST) + init_savelist(); + if(!sh_isoption(SH_INTERACTIVE)) + return; + /* use new line discipline when available */ +#ifdef NTTYDISC +# ifdef FIOLOOKLD + if((job.linedisc = ioctl(JOBTTY, FIOLOOKLD, 0)) <0) +# else + if(ioctl(JOBTTY,TIOCGETD,&job.linedisc) !=0) +# endif /* FIOLOOKLD */ + return; + if(job.linedisc!=NTTYDISC && job.linedisc!=OTTYDISC) + { + /* no job control when running with MPX */ +# if SHOPT_VSH + sh_onoption(SH_VIRAW); +# endif /* SHOPT_VSH */ + return; + } + if(job.linedisc==NTTYDISC) + job.linedisc = -1; +#endif /* NTTYDISC */ + + job.mypgid = getpgrp(); + /* some systems have job control, but not initialized */ + if(job.mypgid<=0) + { + /* Get a controlling terminal and set process group */ + /* This should have already been done by rlogin */ + register int fd; + register char *ttynam; +#ifndef SIGTSTP + setpgid(0,sh.pid); +#endif /*SIGTSTP */ + if(job.mypgid<0 || !(ttynam=ttyname(JOBTTY))) + return; + close(JOBTTY); + if((fd = open(ttynam,O_RDWR)) <0) + return; + if(fd!=JOBTTY) + sh_iorenumber(fd,JOBTTY); + job.mypgid = sh.pid; +#ifdef SIGTSTP + tcsetpgrp(JOBTTY,sh.pid); + setpgid(0,sh.pid); +#endif /* SIGTSTP */ + } +#ifdef SIGTSTP + if(possible = (setpgid(0,job.mypgid)>=0) || errno==EPERM) + { + /* wait until we are in the foreground */ + while((job.mytgid=tcgetpgrp(JOBTTY)) != job.mypgid) + { + if(job.mytgid == -1) + return; + /* Stop this shell until continued */ + signal(SIGTTIN,SIG_DFL); + kill(sh.pid,SIGTTIN); + /* resumes here after continue tries again */ + if(ntry++ > IOMAXTRY) + { + errormsg(SH_DICT,0,e_no_start); + return; + } + } + } +#endif /* SIGTTIN */ + +#ifdef NTTYDISC + /* set the line discipline */ + if(job.linedisc>=0) + { + int linedisc = NTTYDISC; +# ifdef FIOPUSHLD + tty_get(JOBTTY,&my_stty); + if (ioctl(JOBTTY, FIOPOPLD, 0) < 0) + return; + if (ioctl(JOBTTY, FIOPUSHLD, &linedisc) < 0) + { + ioctl(JOBTTY, FIOPUSHLD, &job.linedisc); + return; + } + tty_set(JOBTTY,TCSANOW,&my_stty); +# else + if(ioctl(JOBTTY,TIOCSETD,&linedisc) !=0) + return; +# endif /* FIOPUSHLD */ + if(lflag==0) + errormsg(SH_DICT,0,e_newtty); + else + job.linedisc = -1; + } +#endif /* NTTYDISC */ + if(!possible) + return; + +#ifdef SIGTSTP + /* make sure that we are a process group leader */ + setpgid(0,sh.pid); +# if defined(SA_NOCLDWAIT) && defined(_lib_sigflag) + sigflag(SIGCHLD, SA_NOCLDSTOP|SA_NOCLDWAIT, 0); +# endif /* SA_NOCLDWAIT */ + signal(SIGTTIN,SIG_IGN); + signal(SIGTTOU,SIG_IGN); + /* The shell now handles ^Z */ + signal(SIGTSTP,sh_fault); + tcsetpgrp(JOBTTY,sh.pid); +# ifdef CNSUSP + /* set the switch character */ + tty_get(JOBTTY,&my_stty); + job.suspend = (unsigned)my_stty.c_cc[VSUSP]; + if(job.suspend == (unsigned char)CNSUSP) + { + my_stty.c_cc[VSUSP] = CSWTCH; + tty_set(JOBTTY,TCSAFLUSH,&my_stty); + } +# endif /* CNSUSP */ + sh_onoption(SH_MONITOR); + job.jobcontrol++; + job.mypid = sh.pid; +#endif /* SIGTSTP */ + return; +} + + +/* + * see if there are any stopped jobs + * restore tty driver and pgrp + */ +int job_close(void) +{ + register struct process *pw; + register int count = 0, running = 0; + if(possible && !job.jobcontrol) + return(0); + else if(!possible && (!sh_isstate(SH_MONITOR) || sh_isstate(SH_FORKED))) + return(0); + else if(getpid() != job.mypid) + return(0); + job_lock(); + if(!tty_check(0)) + beenhere++; + for(pw=job.pwlist;pw;pw=pw->p_nxtjob) + { + if(!(pw->p_flag&P_STOPPED)) + { + if(!(pw->p_flag&P_DONE)) + running++; + continue; + } + if(beenhere) + killpg(pw->p_pgrp,SIGTERM); + count++; + } + if(beenhere++ == 0 && job.pwlist) + { + if(count) + { + errormsg(SH_DICT,0,e_terminate); + return(-1); + } + else if(running && sh.login_sh) + { + errormsg(SH_DICT,0,e_jobsrunning); + return(-1); + } + } + job_unlock(); +# ifdef SIGTSTP + if(possible && setpgid(0,job.mypgid)>=0) + tcsetpgrp(job.fd,job.mypgid); +# endif /* SIGTSTP */ +# ifdef NTTYDISC + if(job.linedisc>=0) + { + /* restore old line discipline */ +# ifdef FIOPUSHLD + tty_get(job.fd,&my_stty); + if (ioctl(job.fd, FIOPOPLD, 0) < 0) + return(0); + if (ioctl(job.fd, FIOPUSHLD, &job.linedisc) < 0) + { + job.linedisc = NTTYDISC; + ioctl(job.fd, FIOPUSHLD, &job.linedisc); + return(0); + } + tty_set(job.fd,TCSAFLUSH,&my_stty); +# else + if(ioctl(job.fd,TIOCSETD,&job.linedisc) !=0) + return(0); +# endif /* FIOPUSHLD */ + errormsg(SH_DICT,0,e_oldtty); + } +# endif /* NTTYDISC */ +# ifdef CNSUSP + if(possible && job.suspend==CNSUSP) + { + tty_get(job.fd,&my_stty); + my_stty.c_cc[VSUSP] = CNSUSP; + tty_set(job.fd,TCSAFLUSH,&my_stty); + } +# endif /* CNSUSP */ + job.jobcontrol = 0; + return(0); +} + +static void job_set(register struct process *pw) +{ + /* save current terminal state */ + tty_get(job.fd,&my_stty); + if(pw->p_flag&P_STTY) + { + /* restore terminal state for job */ + tty_set(job.fd,TCSAFLUSH,&pw->p_stty); + } +#ifdef SIGTSTP + if((pw->p_flag&P_STOPPED) || tcgetpgrp(job.fd) == sh.pid) + tcsetpgrp(job.fd,pw->p_fgrp); + /* if job is stopped, resume it in the background */ + job_unstop(pw); +#endif /* SIGTSTP */ +} + +static void job_reset(register struct process *pw) +{ + /* save the terminal state for current job */ +#ifdef SIGTSTP + job_fgrp(pw,tcgetpgrp(job.fd)); + if(tcsetpgrp(job.fd,sh.pid) !=0) + return; +#endif /* SIGTSTP */ + /* force the following tty_get() to do a tcgetattr() unless fg */ + if(!(pw->p_flag&P_FG)) + tty_set(-1, 0, NIL(struct termios*)); + if(pw && (pw->p_flag&P_SIGNALLED) && pw->p_exit!=SIGHUP) + { + if(tty_get(job.fd,&pw->p_stty) == 0) + pw->p_flag |= P_STTY; + /* restore terminal state for job */ + tty_set(job.fd,TCSAFLUSH,&my_stty); + } + beenhere = 0; +} +#endif /* JOBS */ + +/* + * wait built-in command + */ + +void job_bwait(char **jobs) +{ + register char *jp; + register struct process *pw; + register pid_t pid; + if(*jobs==0) + job_wait((pid_t)-1); + else while(jp = *jobs++) + { +#ifdef JOBS + if(*jp == '%') + { + job_lock(); + pw = job_bystring(jp); + job_unlock(); + if(pw) + pid = pw->p_pid; + else + return; + } + else +#endif /* JOBS */ + pid = (int)strtol(jp, (char**)0, 10); + job_wait(-pid); + } +} + +#ifdef JOBS +/* + * execute function <fun> for each job + */ + +int job_walk(Sfio_t *file,int (*fun)(struct process*,int),int arg,char *joblist[]) +{ + register struct process *pw; + register int r = 0; + register char *jobid, **jobs=joblist; + register struct process *px; + job_string = 0; + outfile = file; + by_number = 0; + job_lock(); + pw = job.pwlist; + if(jobs==0) + { + /* do all jobs */ + for(;pw;pw=px) + { + px = pw->p_nxtjob; + if(pw->p_env != sh.jobenv) + continue; + if((*fun)(pw,arg)) + r = 2; + } + } + else if(*jobs==0) /* current job */ + { + /* skip over non-stop jobs */ + while(pw && (pw->p_env!=sh.jobenv || pw->p_pgrp==0)) + pw = pw->p_nxtjob; + if((*fun)(pw,arg)) + r = 2; + } + else while(jobid = *jobs++) + { + job_string = jobid; + if(*jobid==0) + errormsg(SH_DICT,ERROR_exit(1),e_jobusage,job_string); + if(*jobid == '%') + pw = job_bystring(jobid); + else + { + int pid = (int)strtol(jobid, (char**)0, 10); + if(pid<0) + jobid++; + while(isdigit(*jobid)) + jobid++; + if(*jobid) + errormsg(SH_DICT,ERROR_exit(1),e_jobusage,job_string); + if(!(pw = job_bypid(pid))) + { + pw = &dummy; + pw->p_pid = pid; + pw->p_pgrp = pid; + } + by_number = 1; + } + if((*fun)(pw,arg)) + r = 2; + by_number = 0; + } + job_unlock(); + return(r); +} + +/* + * send signal <sig> to background process group if not disowned + */ +int job_terminate(register struct process *pw,register int sig) +{ + if(pw->p_pgrp && !(pw->p_flag&P_DISOWN)) + job_kill(pw,sig); + return(0); +} + +/* + * list the given job + * flag JOB_LFLAG for long listing + * flag JOB_NFLAG for list only jobs marked for notification + * flag JOB_PFLAG for process id(s) only + */ + +int job_list(struct process *pw,register int flag) +{ + register struct process *px = pw; + register int n; + register const char *msg; + register int msize; + if(!pw || pw->p_job<=0) + return(1); + if(pw->p_env != sh.jobenv) + return(0); + if((flag&JOB_NFLAG) && (!(px->p_flag&P_NOTIFY)||px->p_pgrp==0)) + return(0); + if((flag&JOB_PFLAG)) + { + sfprintf(outfile,"%d\n",px->p_pgrp?px->p_pgrp:px->p_pid); + return(0); + } + if((px->p_flag&P_DONE) && job.waitall && !(flag&JOB_LFLAG)) + return(0); + job_lock(); + n = px->p_job; + if(px==job.pwlist) + msize = '+'; + else if(px==job.pwlist->p_nxtjob) + msize = '-'; + else + msize = ' '; + if(flag&JOB_NLFLAG) + sfputc(outfile,'\n'); + sfprintf(outfile,"[%d] %c ",n, msize); + do + { + n = 0; + if(flag&JOB_LFLAG) + sfprintf(outfile,"%d\t",px->p_pid); + if(px->p_flag&P_SIGNALLED) + msg = job_sigmsg((int)(px->p_exit)); + else if(px->p_flag&P_NOTIFY) + { + msg = sh_translate(e_done); + n = px->p_exit; + } + else + msg = sh_translate(e_running); + px->p_flag &= ~P_NOTIFY; + sfputr(outfile,msg,-1); + msize = strlen(msg); + if(n) + { + sfprintf(outfile,"(%d)",(int)n); + msize += (3+(n>10)+(n>100)); + } + if(px->p_flag&P_COREDUMP) + { + msg = sh_translate(e_coredump); + sfputr(outfile, msg, -1); + msize += strlen(msg); + } + sfnputc(outfile,' ',MAXMSG>msize?MAXMSG-msize:1); + if(flag&JOB_LFLAG) + px = px->p_nxtproc; + else + { + while(px=px->p_nxtproc) + px->p_flag &= ~P_NOTIFY; + px = 0; + } + if(!px) + hist_list(sh.hist_ptr,outfile,pw->p_name,0,";"); + else + sfputr(outfile, e_nlspace, -1); + } + while(px); + job_unlock(); + return(0); +} + +/* + * get the process group given the job number + * This routine returns the process group number or -1 + */ +static struct process *job_bystring(register char *ajob) +{ + register struct process *pw=job.pwlist; + register int c; + if(*ajob++ != '%' || !pw) + return(NIL(struct process*)); + c = *ajob; + if(isdigit(c)) + pw = job_byjid((int)strtol(ajob, (char**)0, 10)); + else if(c=='+' || c=='%') + ; + else if(c=='-') + { + if(pw) + pw = job.pwlist->p_nxtjob; + } + else + pw = job_byname(ajob); + if(pw && pw->p_flag) + return(pw); + return(NIL(struct process*)); +} + +/* + * Kill a job or process + */ + +int job_kill(register struct process *pw,register int sig) +{ + register pid_t pid; + register int r; + const char *msg; +#ifdef SIGTSTP + int stopsig = (sig==SIGSTOP||sig==SIGTSTP||sig==SIGTTIN||sig==SIGTTOU); +#else +# define stopsig 1 +#endif /* SIGTSTP */ + job_lock(); + errno = ECHILD; + if(pw==0) + goto error; + pid = pw->p_pid; + if(by_number) + { + if(pid==0 && job.jobcontrol) + r = job_walk(outfile, job_kill,sig, (char**)0); +#ifdef SIGTSTP + if(sig==SIGSTOP && pid==sh.pid && sh.ppid==1) + { + /* can't stop login shell */ + errno = EPERM; + r = -1; + } + else + { + if(pid>=0) + { + if((r = kill(pid,sig))>=0 && !stopsig) + { + if(pw->p_flag&P_STOPPED) + pw->p_flag &= ~(P_STOPPED|P_SIGNALLED); + if(sig) + kill(pid,SIGCONT); + } + } + else + { + if((r = killpg(-pid,sig))>=0 && !stopsig) + { + job_unstop(job_bypid(pw->p_pid)); + if(sig) + killpg(-pid,SIGCONT); + } + } + } +#else + if(pid>=0) + r = kill(pid,sig); + else + r = killpg(-pid,sig); +#endif /* SIGTSTP */ + } + else + { + if(pid = pw->p_pgrp) + { + r = killpg(pid,sig); +#ifdef SIGTSTP + if(r>=0 && (sig==SIGHUP||sig==SIGTERM || sig==SIGCONT)) + job_unstop(pw); +#endif /* SIGTSTP */ + if(r>=0) + sh_delay(.05); + } + while(pw && pw->p_pgrp==0 && (r=kill(pw->p_pid,sig))>=0) + { +#ifdef SIGTSTP + if(sig==SIGHUP || sig==SIGTERM) + kill(pw->p_pid,SIGCONT); +#endif /* SIGTSTP */ + pw = pw->p_nxtproc; + } + } + if(r<0 && job_string) + { + error: + if(pw && by_number) + msg = sh_translate(e_no_proc); + else + msg = sh_translate(e_no_job); + if(errno == EPERM) + msg = sh_translate(e_access); + sfprintf(sfstderr,"kill: %s: %s\n",job_string, msg); + r = 2; + } + sh_delay(.001); + job_unlock(); + return(r); +} + +/* + * Get process structure from first letters of jobname + * + */ + +static struct process *job_byname(char *name) +{ + register struct process *pw = job.pwlist; + register struct process *pz = 0; + register int *flag = 0; + register char *cp = name; + int offset; + if(!sh.hist_ptr) + return(NIL(struct process*)); + if(*cp=='?') + cp++,flag= &offset; + for(;pw;pw=pw->p_nxtjob) + { + if(hist_match(sh.hist_ptr,pw->p_name,cp,flag)>=0) + { + if(pz) + errormsg(SH_DICT,ERROR_exit(1),e_jobusage,name-1); + pz = pw; + } + } + return(pz); +} + +#else +# define job_set(x) +# define job_reset(x) +#endif /* JOBS */ + + + +/* + * Initialize the process posting array + */ + +void job_clear(void) +{ + register struct process *pw, *px; + register struct process *pwnext; + register int j = BYTE(sh.lim.child_max); + register struct jobsave *jp,*jpnext; + job_lock(); + for(pw=job.pwlist; pw; pw=pwnext) + { + pwnext = pw->p_nxtjob; + while(px=pw) + { + pw = pw->p_nxtproc; + free((void*)px); + } + } + for(jp=bck.list; jp;jp=jpnext) + { + jpnext = jp->next; + free((void*)jp); + } + bck.list = 0; + if(njob_savelist < NJOB_SAVELIST) + init_savelist(); + job.pwlist = NIL(struct process*); + job.numpost=0; + job.waitall = 0; + job.curpgid = 0; + job.toclear = 0; + if(!job.freejobs) + job.freejobs = (unsigned char*)malloc((unsigned)(j+1)); + while(j >=0) + job.freejobs[j--] = 0; + job_unlock(); +} + +/* + * put the process <pid> on the process list and return the job number + * if non-zero, <join> is the process id of the job to join + */ + +int job_post(pid_t pid, pid_t join) +{ + register struct process *pw; + register History_t *hp = sh.hist_ptr; + sh.jobenv = sh.curenv; + if(njob_savelist < NJOB_SAVELIST) + init_savelist(); + if(job.toclear) + { + job_clear(); + return(0); + } + job_lock(); + if(pw = job_bypid(pid)) + job_unpost(pw,0); + if(join && (pw=job_bypid(join))) + { + /* if job to join is not first move it to front */ + if((pw=job_byjid(pw->p_job)) != job.pwlist) + { + job_unlink(pw); + pw->p_nxtjob = job.pwlist; + job.pwlist = pw; + } + } + if(pw=freelist) + freelist = pw->p_nxtjob; + else + pw = new_of(struct process,0); + job.numpost++; + if(join && job.pwlist) + { + /* join existing current job */ + pw->p_nxtjob = job.pwlist->p_nxtjob; + pw->p_nxtproc = job.pwlist; + pw->p_job = job.pwlist->p_job; + } + else + { + /* create a new job */ + while((pw->p_job = job_alloc()) < 0) + job_wait((pid_t)1); + pw->p_nxtjob = job.pwlist; + pw->p_nxtproc = 0; + } + job.pwlist = pw; + pw->p_env = sh.curenv; + pw->p_pid = pid; + pw->p_flag = P_EXITSAVE; + pw->p_exit = sh.xargexit; + sh.xargexit = 0; + if(sh_isstate(SH_MONITOR)) + { + if(killpg(job.curpgid,0)<0 && errno==ESRCH) + job.curpgid = pid; + pw->p_fgrp = job.curpgid; + } + else + pw->p_fgrp = 0; + pw->p_pgrp = pw->p_fgrp; +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: post pid=%d critical=%d job=%d pid=%d pgid=%d savesig=%d join=%d\n",__LINE__,getpid(),job.in_critical,pw->p_job, + pw->p_pid,pw->p_pgrp,job.savesig,join); + sfsync(sfstderr); +#endif /* DEBUG */ +#ifdef JOBS + if(hp && !sh_isstate(SH_PROFILE)) + pw->p_name=hist_tell(sh.hist_ptr,(int)hp->histind-1); + else + pw->p_name = -1; +#endif /* JOBS */ + if(pid==lastpid) + { + int val = job_chksave(pid); + pw->p_exit = val>0?val:0; + if(pw->p_exit==SH_STOPSIG) + { + pw->p_flag |= (P_SIGNALLED|P_STOPPED); + pw->p_exit = 0; + } + else + pw->p_flag |= (P_DONE|P_NOTIFY); + } + lastpid = 0; + job_unlock(); + return(pw->p_job); +} + +/* + * Returns a process structure give a process id + */ + +static struct process *job_bypid(pid_t pid) +{ + register struct process *pw, *px; + for(pw=job.pwlist; pw; pw=pw->p_nxtjob) + for(px=pw; px; px=px->p_nxtproc) + { + if(px->p_pid==pid) + return(px); + } + return(NIL(struct process*)); +} + +/* + * return a pointer to a job given the job id + */ + +static struct process *job_byjid(int jobid) +{ + register struct process *pw; + for(pw=job.pwlist;pw; pw = pw->p_nxtjob) + { + if(pw->p_job==jobid) + break; + } + return(pw); +} + +/* + * print a signal message + */ +static void job_prmsg(register struct process *pw) +{ + if(pw->p_exit!=SIGINT && pw->p_exit!=SIGPIPE) + { + register const char *msg, *dump; + msg = job_sigmsg((int)(pw->p_exit)); + msg = sh_translate(msg); + if(pw->p_flag&P_COREDUMP) + dump = sh_translate(e_coredump); + else + dump = ""; + if(sh_isstate(SH_INTERACTIVE)) + sfprintf(sfstderr,"%s%s\n",msg,dump); + else + errormsg(SH_DICT,2,"%d: %s%s",pw->p_pid,msg,dump); + } +} + +/* + * Wait for process pid to complete + * If pid < -1, then wait can be interrupted, -pid is waited for (wait builtin) + * pid=0 to unpost all done processes + * pid=1 to wait for at least one process to complete + * pid=-1 to wait for all runing processes + */ + +void job_wait(register pid_t pid) +{ + register struct process *pw=0,*px; + register int jobid = 0; + int nochild; + char intr = 0; + if(pid <= 0) + { + if(pid==0) + goto done; + pid = -pid; + intr = 1; + } + job_lock(); + if(pid > 1) + { + if(!(pw=job_bypid(pid))) + { + /* check to see whether job status has been saved */ + if((sh.exitval = job_chksave(pid)) < 0) + sh.exitval = ERROR_NOENT; + exitset(); + job_unlock(); + return; + } + else if(intr && pw->p_env!=sh.curenv) + { + sh.exitval = ERROR_NOENT; + job_unlock(); + return; + } + jobid = pw->p_job; + if(!intr) + pw->p_flag &= ~P_EXITSAVE; + if(pw->p_pgrp && job.parent!= (pid_t)-1) + job_set(job_byjid(jobid)); + } +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: wait pid=%d critical=%d job=%d pid=%d\n",__LINE__,getpid(),job.in_critical,jobid,pid); + if(pw) + sfprintf(sfstderr,"ksh: job line %4d: wait pid=%d critical=%d flags=%o\n",__LINE__,getpid(),job.in_critical,pw->p_flag); +#endif /* DEBUG*/ + errno = 0; + while(1) + { + if(job.waitsafe) + { + for(px=job.pwlist;px; px = px->p_nxtjob) + { + if(px!=pw && (px->p_flag&P_NOTIFY)) + { + if(sh_isoption(SH_NOTIFY)) + { + outfile = sfstderr; + job_list(px,JOB_NFLAG|JOB_NLFLAG); + sfsync(sfstderr); + } + else if(!sh_isoption(SH_INTERACTIVE) && (px->p_flag&P_SIGNALLED)) + { + job_prmsg(px); + px->p_flag &= ~P_NOTIFY; + } + } + } + } + if(pw && (pw->p_flag&(P_DONE|P_STOPPED))) + { +#ifdef SIGTSTP + if(pw->p_flag&P_STOPPED) + { + pw->p_flag |= P_EXITSAVE; + if(sh_isoption(SH_INTERACTIVE) && !sh_isstate(SH_FORKED)) + { + if( pw->p_exit!=SIGTTIN && pw->p_exit!=SIGTTOU) + break; + + killpg(pw->p_pgrp,SIGCONT); + } + else /* ignore stop when non-interactive */ + pw->p_flag &= ~(P_NOTIFY|P_SIGNALLED|P_STOPPED|P_EXITSAVE); + } + else +#endif /* SIGTSTP */ + { + if(pw->p_flag&P_SIGNALLED) + { + pw->p_flag &= ~P_NOTIFY; + job_prmsg(pw); + } + else if(pw->p_flag&P_DONE) + pw->p_flag &= ~P_NOTIFY; + if(pw->p_job==jobid) + { + px = job_byjid(jobid); + /* last process in job */ + if(sh_isoption(SH_PIPEFAIL)) + { + /* last non-zero exit */ + for(;px;px=px->p_nxtproc) + { + if(px->p_exit) + break; + } + if(!px) + px = pw; + } + else if(px!=pw) + px = 0; + if(px) + { + sh.exitval=px->p_exit; + if(px->p_flag&P_SIGNALLED) + sh.exitval |= SH_EXITSIG; + if(intr) + px->p_flag &= ~P_EXITSAVE; + } + } + if(!job.waitall) + { + if(!sh_isoption(SH_PIPEFAIL)) + job_unpost(pw,1); + break; + } + else if(!(px=job_unpost(pw,1))) + break; + pw = px; + continue; + } + } + sfsync(sfstderr); + job.waitsafe = 0; + nochild = job_reap(job.savesig); + if(job.waitsafe) + continue; + if(nochild) + break; + if(sh.sigflag[SIGALRM]&SH_SIGTRAP) + sh_timetraps(); + if((intr && sh.trapnote) || (pid==1 && !intr)) + break; + } + job_unlock(); + if(pid==1) + return; + exitset(); + if(pw->p_pgrp) + { + job_reset(pw); + /* propogate keyboard interrupts to parent */ + if((pw->p_flag&P_SIGNALLED) && pw->p_exit==SIGINT && !(sh.sigflag[SIGINT]&SH_SIGOFF)) + sh_fault(SIGINT); +#ifdef SIGTSTP + else if((pw->p_flag&P_STOPPED) && pw->p_exit==SIGTSTP) + { + job.parent = 0; + sh_fault(SIGTSTP); + } +#endif /* SIGTSTP */ + } + else + tty_set(-1, 0, NIL(struct termios*)); +done: + if(!job.waitall && sh_isoption(SH_PIPEFAIL)) + return; + if(!sh.intrap) + { + job_lock(); + for(pw=job.pwlist; pw; pw=px) + { + px = pw->p_nxtjob; + job_unpost(pw,0); + } + job_unlock(); + } +} + +/* + * move job to foreground if bgflag == 'f' + * move job to background if bgflag == 'b' + * disown job if bgflag == 'd' + */ + +int job_switch(register struct process *pw,int bgflag) +{ + register const char *msg; + job_lock(); + if(!pw || !(pw=job_byjid((int)pw->p_job))) + { + job_unlock(); + return(1); + } + if(bgflag=='d') + { + for(; pw; pw=pw->p_nxtproc) + pw->p_flag |= P_DISOWN; + job_unlock(); + return(0); + } +#ifdef SIGTSTP + if(bgflag=='b') + { + sfprintf(outfile,"[%d]\t",(int)pw->p_job); + sh.bckpid = pw->p_pid; + msg = "&"; + } + else + { + job_unlink(pw); + pw->p_nxtjob = job.pwlist; + job.pwlist = pw; + msg = ""; + } + hist_list(sh.hist_ptr,outfile,pw->p_name,'&',";"); + sfputr(outfile,msg,'\n'); + sfsync(outfile); + if(bgflag=='f') + { + if(!(pw=job_unpost(pw,1))) + { + job_unlock(); + return(1); + } + job.waitall = 1; + pw->p_flag |= P_FG; + job_wait(pw->p_pid); + job.waitall = 0; + } + else if(pw->p_flag&P_STOPPED) + job_unstop(pw); +#endif /* SIGTSTP */ + job_unlock(); + return(0); +} + + +#ifdef SIGTSTP +/* + * Set the foreground group associated with a job + */ + +static void job_fgrp(register struct process *pw, int newgrp) +{ + for(; pw; pw=pw->p_nxtproc) + pw->p_fgrp = newgrp; +} + +/* + * turn off STOP state of a process group and send CONT signals + */ + +static void job_unstop(register struct process *px) +{ + register struct process *pw; + register int num = 0; + for(pw=px ;pw ;pw=pw->p_nxtproc) + { + if(pw->p_flag&P_STOPPED) + { + num++; + pw->p_flag &= ~(P_STOPPED|P_SIGNALLED|P_NOTIFY); + } + } + if(num!=0) + { + if(px->p_fgrp != px->p_pgrp) + killpg(px->p_fgrp,SIGCONT); + killpg(px->p_pgrp,SIGCONT); + } +} +#endif /* SIGTSTP */ + +/* + * remove a job from table + * If all the processes have not completed, unpost first non-completed process + * Otherwise the job is removed and job_unpost returns NULL. + * pwlist is reset if the first job is removed + * if <notify> is non-zero, then jobs with pending notifications are unposted + */ + +static struct process *job_unpost(register struct process *pwtop,int notify) +{ + register struct process *pw; + /* make sure all processes are done */ +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: drop pid=%d critical=%d pid=%d env=%d\n",__LINE__,getpid(),job.in_critical,pwtop->p_pid,pwtop->p_env); + sfsync(sfstderr); +#endif /* DEBUG */ + pwtop = pw = job_byjid((int)pwtop->p_job); + for(; pw && (pw->p_flag&P_DONE)&&(notify||!(pw->p_flag&P_NOTIFY)||pw->p_env); pw=pw->p_nxtproc); + if(pw) + return(pw); + /* all processes complete, unpost job */ + job_unlink(pwtop); + for(pw=pwtop; pw; pw=pw->p_nxtproc) + { + /* save the exit status for background jobs */ + if(pw->p_flag&P_EXITSAVE) + { + struct jobsave *jp; + /* save status for future wait */ + if(bck.count++ > sh.lim.child_max) + job_chksave(0); + if(jp = jobsave_create(pw->p_pid)) + { + jp->next = bck.list; + bck.list = jp; + jp->exitval = pw->p_exit; + if(pw->p_flag&P_SIGNALLED) + jp->exitval |= SH_EXITSIG; + } + pw->p_flag &= ~P_EXITSAVE; + } + pw->p_flag &= ~P_DONE; + job.numpost--; + pw->p_nxtjob = freelist; + freelist = pw; + } +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: free pid=%d critical=%d job=%d\n",__LINE__,getpid(),job.in_critical,pwtop->p_job); + sfsync(sfstderr); +#endif /* DEBUG */ + job_free((int)pwtop->p_job); + return((struct process*)0); +} + +/* + * unlink a job form the job list + */ +static void job_unlink(register struct process *pw) +{ + register struct process *px; + if(pw==job.pwlist) + { + job.pwlist = pw->p_nxtjob; + job.curpgid = 0; + return; + } + for(px=job.pwlist;px;px=px->p_nxtjob) + if(px->p_nxtjob == pw) + { + px->p_nxtjob = pw->p_nxtjob; + return; + } +} + +/* + * get an unused job number + * freejobs is a bit vector, 0 is unused + */ + +static int job_alloc(void) +{ + register int j=0; + register unsigned mask = 1; + register unsigned char *freeword; + register int jmax = BYTE(sh.lim.child_max); + /* skip to first word with a free slot */ + for(j=0;job.freejobs[j] == UCHAR_MAX; j++); + if(j >= jmax) + { + register struct process *pw; + for(j=1; j < sh.lim.child_max; j++) + { + if((pw=job_byjid(j))&& !job_unpost(pw,0)) + break; + } + j /= CHAR_BIT; + if(j >= jmax) + return(-1); + } + freeword = &job.freejobs[j]; + j *= CHAR_BIT; + for(j++;mask&(*freeword);j++,mask <<=1); + *freeword |= mask; + return(j); +} + +/* + * return a job number + */ + +static void job_free(register int n) +{ + register int j = (--n)/CHAR_BIT; + register unsigned mask; + n -= j*CHAR_BIT; + mask = 1 << n; + job.freejobs[j] &= ~mask; +} + +static char *job_sigmsg(int sig) +{ + static char signo[40]; +#ifdef apollo + /* + * This code handles the formatting for the apollo specific signal + * SIGAPOLLO. + */ + extern char *apollo_error(void); + + if ( sig == SIGAPOLLO ) + return( apollo_error() ); +#endif /* apollo */ + if(sig<sh.sigmax && sh.sigmsg[sig]) + return(sh.sigmsg[sig]); +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if(sig>=SIGRTMIN && sig<=SIGRTMAX) + { + static char sigrt[20]; + sfsprintf(sigrt,sizeof(sigrt),"SIGRTMIN+%d",sig-SIGRTMIN); + return(sigrt); + } +#endif + sfsprintf(signo,sizeof(signo),sh_translate(e_signo),sig); + return(signo); +} + +/* + * see whether exit status has been saved and delete it + * if pid==0, then oldest saved process is deleted + * If pid is not found a -1 is returned. + */ +static int job_chksave(register pid_t pid) +{ + register struct jobsave *jp = bck.list, *jpold=0; + register int r= -1; + while(jp) + { + if(jp->pid==pid) + break; + if(pid==0 && !jp->next) + break; + jpold = jp; + jp = jp->next; + } + if(jp) + { + r = 0; + if(pid) + r = jp->exitval; + if(jpold) + jpold->next = jp->next; + else + bck.list = jp->next; + bck.count--; + if(njob_savelist < NJOB_SAVELIST) + { + njob_savelist++; + jp->next = job_savelist; + job_savelist = jp; + } + else + free((void*)jp); + } + return(r); +} + +void *job_subsave(void) +{ + struct back_save *bp = new_of(struct back_save,0); + job_lock(); + *bp = bck; + bck.count = 0; + bck.list = 0; + job_unlock(); + return((void*)bp); +} + +void job_subrestore(void* ptr) +{ + register struct jobsave *jp,*jpnext; + register struct back_save *bp = (struct back_save*)ptr; + register struct process *pw, *px, *pwnext; + job_lock(); + for(pw=job.pwlist; pw; pw=pwnext) + { + pwnext = pw->p_nxtjob; + if(pw->p_env != sh.curenv) + continue; + for(px=pw; px; px=px->p_nxtproc) + px->p_flag |= P_DONE; + job_unpost(pw,0); + } + for(jp=bck.list,bck= *bp; jp; jp=jpnext) + { + jpnext = jp->next; + free((void*)jp); + } + free(ptr); + job_unlock(); +} + +int sh_waitsafe(void) +{ + return(job.waitsafe); +} + +void job_fork(pid_t parent) +{ +#ifdef DEBUG + sfprintf(sfstderr,"ksh: job line %4d: fork pid=%d critical=%d parent=%d\n",__LINE__,getpid(),job.in_critical,parent); +#endif /* DEBUG */ + switch (parent) + { + case -1: + job_lock(); + break; + case 0: + job_unlock(); + job.waitsafe = 0; + job.in_critical = 0; + break; + default: + job_unlock(); + break; + } +} diff --git a/usr/src/lib/libshell/common/sh/lex.c b/usr/src/lib/libshell/common/sh/lex.c new file mode 100644 index 0000000000..c8502e3b7b --- /dev/null +++ b/usr/src/lib/libshell/common/sh/lex.c @@ -0,0 +1,2276 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * KornShell lexical analyzer + * + * Written by David Korn + * AT&T Labs + * + */ + +#include <ast.h> +#include <stak.h> +#include <fcin.h> +#include <nval.h> +#include "FEATURE/options" + +#if KSHELL +# include "defs.h" +#else +# include <shell.h> +# define nv_getval(np) ((np)->nvalue) + Shell_t sh = {1}; +#endif /* KSHELL */ + +#include "argnod.h" +#include "test.h" +#include "lexstates.h" +#include "io.h" + +#define TEST_RE 3 +#define SYNBAD 3 /* exit value for syntax errors */ +#define STACK_ARRAY 3 /* size of depth match stack growth */ + +#if _lib_iswblank < 0 /* set in lexstates.h to enable this code */ + +int +local_iswblank(wchar_t wc) +{ + static int initialized; + static wctype_t wt; + + if (!initialized) + { + initialized = 1; + wt = wctype("blank"); + } + return(iswctype(wc, wt)); +} + +#endif + +/* + * This structure allows for arbitrary depth nesting of (...), {...}, [...] + */ +struct lexstate +{ + char incase; /* 1 for case pattern, 2 after case */ + char intest; /* 1 inside [[...]] */ + char testop1; /* 1 when unary test op legal */ + char testop2; /* 1 when binary test op legal */ + char reservok; /* >0 for reserved word legal */ + char skipword; /* next word can't be reserved */ + char last_quote; /* last multi-line quote character */ + char comp_assign; /* inside compound assignment */ +}; + +struct lexdata +{ + char nocopy; + char paren; + char dolparen; + char nest; + char docword; + char *docend; + char noarg; + char balance; + char warn; + char message; + char arith; + char *first; + int level; + int lastc; + int lex_max; + int *lex_match; + int lex_state; +#if SHOPT_KIA + off_t kiaoff; +#endif +}; + +#define _SHLEX_PRIVATE \ + struct lexdata _lexd; \ + struct lexstate _lex; + +#include "shlex.h" + +#define lexd lp->_lexd +#define lex lp->_lex +#undef shlex +#define shlex lp->_shlex + + +#define pushlevel(c,s) ((lexd.level>=lexd.lex_max?stack_grow(lp):1) &&\ + ((lexd.lex_match[lexd.level++]=lexd.lastc),\ + lexd.lastc=(((s)<<CHAR_BIT)|(c)))) +#define oldmode() (lexd.lastc>>CHAR_BIT) +#define endchar() (lexd.lastc&0xff) +#define setchar(c) (lexd.lastc = ((lexd.lastc&~0xff)|(c))) +#define poplevel() (lexd.lastc=lexd.lex_match[--lexd.level]) + +static char *fmttoken(Lex_t*, int, char*); +#ifdef SF_BUFCONST + static int alias_exceptf(Sfio_t*, int, void*, Sfdisc_t*); +#else + static int alias_exceptf(Sfio_t*, int, Sfdisc_t*); +#endif +static void setupalias(Lex_t*,const char*, Namval_t*); +static int comsub(Lex_t*); +static void nested_here(Lex_t*); +static int here_copy(Lex_t*, struct ionod*); +static int stack_grow(Lex_t*); +static const Sfdisc_t alias_disc = { NULL, NULL, NULL, alias_exceptf, NULL }; + +#if SHOPT_KIA + +static void refvar(int type) +{ + register Shell_t *shp = sh_getinterp(); + register Lex_t *lp = (Lex_t*)shp->lex_context; + off_t off = (fcseek(0)-(type+1))-(lexd.first?lexd.first:fcfirst()); + unsigned long r; + if(lexd.first) + { + off = (fcseek(0)-(type+1)) - lexd.first; + r=kiaentity(lexd.first+lexd.kiaoff+type,off-lexd.kiaoff,'v',-1,-1,shlex.current,'v',0,""); + } + else + { + int n,offset = staktell(); + char *savptr,*begin; + off = offset + (fcseek(0)-(type+1)) - fcfirst(); + if(lexd.kiaoff < offset) + { + /* variable starts on stak, copy remainder */ + if(off>offset) + stakwrite(fcfirst()+type,off-offset); + n = staktell()-lexd.kiaoff; + begin = stakptr(lexd.kiaoff); + } + else + { + /* variable in data buffer */ + begin = fcfirst()+(type+lexd.kiaoff-offset); + n = off-lexd.kiaoff; + } + savptr = stakfreeze(0); + r=kiaentity(begin,n,'v',-1,-1,shlex.current,'v',0,""); + stakset(savptr,offset); + } + sfprintf(shlex.kiatmp,"p;%..64d;v;%..64d;%d;%d;r;\n",shlex.current,r,shp->inlineno,shp->inlineno); +} +#endif /* SHOPT_KIA */ + +/* + * This routine gets called when reading across a buffer boundary + * If lexd.nocopy is off, then current token is saved on the stack + */ +static void lex_advance(Sfio_t *iop, const char *buff, register int size) +{ + register Shell_t *shp = sh_getinterp(); + register Lex_t *lp = (Lex_t*)shp->lex_context; + register Sfio_t *log= shp->funlog; +#if KSHELL + /* write to history file and to stderr if necessary */ + if(iop && !sfstacked(iop)) + { + if(sh_isstate(SH_HISTORY) && shp->hist_ptr) + log = shp->hist_ptr->histfp; + sfwrite(log, (void*)buff, size); + if(sh_isstate(SH_VERBOSE)) + sfwrite(sfstderr, buff, size); + } +#endif + if(lexd.nocopy) + return; + if(lexd.first) + { + size -= (lexd.first-(char*)buff); + buff = lexd.first; + if(!lexd.noarg) + shlex.arg = (struct argnod*)stakseek(ARGVAL); +#if SHOPT_KIA + lexd.kiaoff += ARGVAL; +#endif /* SHOPT_KIA */ + } + if(size>0 && (shlex.arg||lexd.noarg)) + { + stakwrite(buff,size); + lexd.first = 0; + } +} + +/* + * fill up another input buffer + * preserves lexical state + */ +static int lexfill(void) +{ + Shell_t *shp = sh_getinterp(); + register int c; + register Lex_t *lp = (Lex_t*)shp->lex_context; + struct shlex_t savelex; + struct lexdata savedata; + struct lexstate savestate; + struct argnod *ap; + int aok; + savelex = shlex; + savedata = lexd; + savestate = lex; + ap = shlex.arg; + c = fcfill(); + if(ap) + shlex.arg = ap; + lex = savestate; + lexd = savedata; + lexd.first = 0; + aok= shlex.aliasok; + ap = shlex.arg; + shlex = savelex; + shlex.arg = ap; + shlex.aliasok = aok; + return(c); +} + +/* + * mode=1 for reinitialization + */ +Lex_t *sh_lexopen(Lex_t *lp, Shell_t *sp, int mode) +{ + fcnotify(lex_advance); + if(!lp) + { + lp = (Lex_t*)newof(0,Lex_t,1,0); + lp->_shlex.sh = sp; + } + lex.intest = lex.incase = lex.skipword = lexd.warn = 0; + lex.comp_assign = 0; + lex.reservok = 1; + if(!sh_isoption(SH_DICTIONARY) && sh_isoption(SH_NOEXEC)) + lexd.warn=1; + if(!mode) + { + lexd.noarg = lexd.level= lexd.dolparen = 0; + lexd.nocopy = lexd.docword = lexd.nest = lexd.paren = 0; + } + shlex.comsub = 0; + return(lp); +} + +#ifdef DBUG +extern int lextoken(void); +int sh_lex(void) +{ + Shell_t *shp = sh_getinterp(); + register Lex_t *lp = (Lex_t*)shp->lex_context; + register int flag; + char *quoted, *macro, *split, *expand; + char tokstr[3]; + register int tok = lextoken(); + quoted = macro = split = expand = ""; + if(tok==0 && (flag=shlex.arg->argflag)) + { + if(flag&ARG_MAC) + macro = "macro:"; + if(flag&ARG_EXP) + expand = "expand:"; + if(flag&ARG_QUOTED) + quoted = "quoted:"; + } + sfprintf(sfstderr,"line %d: %o:%s%s%s%s %s\n",shp->inlineno,tok,quoted, + macro, split, expand, fmttoken(lp,tok,tokstr)); + return(tok); +} +#define sh_lex lextoken +#endif + +/* + * Get the next word and put it on the top of the stak + * A pointer to the current word is stored in shlex.arg + * Returns the token type + */ +int sh_lex(void) +{ + register Shell_t *shp = sh_getinterp(); + register const char *state; + register int n, c, mode=ST_BEGIN, wordflags=0; + register Lex_t *lp = (Lex_t*)shp->lex_context; + int inlevel=lexd.level, assignment=0, ingrave=0; + Sfio_t *sp; +#if SHOPT_MULTIBYTE + LEN=1; +#endif /* SHOPT_MULTIBYTE */ + if(lexd.paren) + { + lexd.paren = 0; + return(shlex.token=LPAREN); + } + if(lex.incase) + shlex.assignok = 0; + else + shlex.assignok |= lex.reservok; + if(lex.comp_assign==2) + lex.comp_assign = lex.reservok = 0; + lexd.arith = (lexd.nest==1); + if(lexd.nest) + { + pushlevel(lexd.nest,ST_NONE); + lexd.nest = 0; + mode = lexd.lex_state; + } + else if(lexd.docword) + { + if(fcgetc(c)=='-' || c=='#') + { + lexd.docword++; + shlex.digits=(c=='#'?3:1); + } + else if(c=='<') + { + shlex.digits=2; + lexd.docword=0; + } + else if(c>0) + fcseek(-1); + } + if(!lexd.dolparen) + { + shlex.arg = 0; + if(mode!=ST_BEGIN) + lexd.first = fcseek(0); + else + lexd.first = 0; + } + shlex.lastline = sh.inlineno; + while(1) + { + /* skip over characters in the current state */ + state = sh_lexstates[mode]; + while((n=STATE(state,c))==0); + switch(n) + { + case S_BREAK: + fcseek(-1); + goto breakloop; + case S_EOF: + sp = fcfile(); + if((n=lexfill()) > 0) + { + fcseek(-1); + continue; + } + /* check for zero byte in file */ + if(n==0 && fcfile()) + { + if(shp->readscript) + { + char *cp = error_info.id; + errno = ENOEXEC; + error_info.id = shp->readscript; + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,cp); + } + else + { + shlex.token = -1; + sh_syntax(); + } + } + /* end-of-file */ + if(mode==ST_BEGIN) + return(shlex.token=EOFSYM); + if(mode >ST_NORM && lexd.level>0) + { + switch(c=endchar()) + { + case '$': + if(mode==ST_LIT) + { + c = '\''; + break; + } + mode = oldmode(); + poplevel(); + continue; + case RBRACT: + c = LBRACT; + break; + case 1: /* for ((...)) */ + case RPAREN: + c = LPAREN; + break; + default: + c = LBRACE; + break; + case '"': case '`': case '\'': + lexd.balance = c; + break; + } + if(sp && !(sfset(sp,0,0)&SF_STRING)) + { + shlex.lasttok = c; + shlex.token = EOFSYM; + sh_syntax(); + } + lexd.balance = c; + } + goto breakloop; + case S_COM: + /* skip one or more comment line(s) */ + lex.reservok = !lex.intest; + if((n=lexd.nocopy) && lexd.dolparen) + lexd.nocopy--; + do + { + while(fcgetc(c)>0 && c!='\n'); + if(c<=0 || shlex.heredoc) + break; + while(shp->inlineno++,fcpeek(0)=='\n') + fcseek(1); + while(state[c=fcpeek(0)]==0) + fcseek(1); + } + while(c=='#'); + lexd.nocopy = n; + if(c<0) + return(shlex.token=EOFSYM); + n = S_NLTOK; + shp->inlineno--; + /* FALL THRU */ + case S_NLTOK: + /* check for here-document */ + if(shlex.heredoc) + { + if(!lexd.dolparen) + lexd.nocopy++; + c = shp->inlineno; + if(here_copy(lp,shlex.heredoc)<=0 && shlex.lasttok) + { + shlex.lasttok = IODOCSYM; + shlex.token = EOFSYM; + shlex.lastline = c; + sh_syntax(); + } + if(!lexd.dolparen) + lexd.nocopy--; + shlex.heredoc = 0; + } + lex.reservok = !lex.intest; + lex.skipword = 0; + /* FALL THRU */ + case S_NL: + /* skip over new-lines */ + lex.last_quote = 0; + while(shp->inlineno++,fcget()=='\n'); + fcseek(-1); + if(n==S_NLTOK) + { + lex.comp_assign = 0; + return(shlex.token='\n'); + } + case S_BLNK: + if(lex.incase<=TEST_RE) + continue; + /* implicit RPAREN for =~ test operator */ + if(inlevel+1==lexd.level) + { + fcseek(-1); + c = RPAREN; + goto do_pop; + } + continue; + case S_OP: + /* return operator token */ + if(c=='<' || c=='>') + { + if(lex.testop2) + lex.testop2 = 0; + else + { + shlex.digits = (c=='>'); + lex.skipword = 1; + shlex.aliasok = lex.reservok; + lex.reservok = 0; + } + } + else + { + lex.reservok = !lex.intest; + if(c==RPAREN) + { + if(!lexd.dolparen) + lex.incase = 0; + return(shlex.token=c); + } + lex.testop1 = lex.intest; + } + if(fcgetc(n)>0) + fcseek(-1); + if(state[n]==S_OP || n=='#') + { + if(n==c) + { + if(c=='<') + lexd.docword=1; + else if(n==LPAREN) + { + lexd.nest=1; + shlex.lastline = shp->inlineno; + lexd.lex_state = ST_NESTED; + fcseek(1); + return(sh_lex()); + } + c |= SYMREP; + } + else if(c=='(' || c==')') + return(shlex.token=c); + else if(c=='&') + { +#if SHOPT_BASH + if(!sh_isoption(SH_POSIX) && n=='>') + { + shlex.digits = -1; + c = '>'; + } + else +#endif + n = 0; + } + else if(n=='&') + c |= SYMAMP; + else if(c!='<' && c!='>') + n = 0; + else if(n==LPAREN) + { + c |= SYMLPAR; + lex.reservok = 1; + lex.skipword = 0; + } + else if(n=='|') + c |= SYMPIPE; + else if(c=='<' && n=='>') + c = IORDWRSYM; + else if(n=='#' && (c=='<'||c=='>')) + c |= SYMSHARP; + else + n = 0; + if(n) + { + fcseek(1); + lex.incase = (c==BREAKCASESYM || c==FALLTHRUSYM); + } + else + { + if((n=fcpeek(0))!=RPAREN && n!=LPAREN && lexd.warn) + errormsg(SH_DICT,ERROR_warn(0),e_lexspace,shp->inlineno,c,n); + } + } + if(c==LPAREN && lex.comp_assign && !lex.intest && !lex.incase) + lex.comp_assign = 2; + else + lex.comp_assign = 0; + return(shlex.token=c); + case S_ESC: + /* check for \<new-line> */ + fcgetc(n); + c=2; +#if SHOPT_CRNL + if(n=='\r') + { + if(fcgetc(n)=='\n') + c=3; + else + { + n='\r'; + fcseek(-1); + } + } +#endif /* SHOPT_CRNL */ + if(n=='\n') + { + Sfio_t *sp; + struct argnod *ap; + shp->inlineno++; + /* synchronize */ + if(!(sp=fcfile())) + state=fcseek(0); + fcclose(); + ap = shlex.arg; + if(sp) + fcfopen(sp); + else + fcsopen((char*)state); + /* remove \new-line */ + n = staktell()-c; + stakseek(n); + shlex.arg = ap; + if(n<=ARGVAL) + { + mode = 0; + lexd.first = 0; + } + continue; + } + wordflags |= ARG_QUOTED; + if(mode==ST_DOL) + goto err; +#ifndef STR_MAXIMAL + else if(mode==ST_NESTED && lexd.warn && + endchar()==RBRACE && + sh_lexstates[ST_DOL][n]==S_DIG + ) + errormsg(SH_DICT,ERROR_warn(0),e_lexfuture,shp->inlineno,n); +#endif /* STR_MAXIMAL */ + break; + case S_NAME: + if(!lex.skipword) + lex.reservok *= 2; + /* FALL THRU */ + case S_TILDE: + case S_RES: + if(!lexd.dolparen) + lexd.first = fcseek(0)-LEN; + else if(lexd.docword) + lexd.docend = fcseek(0)-LEN; + mode = ST_NAME; + if(c=='.') + fcseek(-1); + if(n!=S_TILDE) + continue; + fcgetc(n); + if(n>0) + fcseek(-1); + if(n==LPAREN) + goto epat; + wordflags = ARG_MAC; + mode = ST_NORM; + continue; + case S_REG: + if(mode==ST_BEGIN) + { + /* skip new-line joining */ + if(c=='\\' && fcpeek(0)=='\n') + { + shp->inlineno++; + fcseek(1); + continue; + } + fcseek(-1); + if(!lexd.dolparen) + lexd.first = fcseek(0); + else if(lexd.docword) + lexd.docend = fcseek(0); + if(c=='[' && shlex.assignok>=SH_ASSIGN) + { + mode = ST_NAME; + continue; + } + } + mode = ST_NORM; + continue; + case S_LIT: + if(oldmode()==ST_NONE) /* in ((...)) */ + { + if((c=fcpeek(0))==LPAREN || c==RPAREN || c=='$' || c==LBRACE || c==RBRACE || c=='[' || c==']') + { + if(fcpeek(1)=='\'') + fcseek(2); + } + continue; + } + wordflags |= ARG_QUOTED; + if(mode==ST_DOL) + { + if(endchar()!='$') + goto err; + if(oldmode()==ST_QUOTE) /* $' within "" or `` */ + { + if(lexd.warn) + errormsg(SH_DICT,ERROR_warn(0),e_lexslash,shp->inlineno); + mode = ST_LIT; + } + } + if(mode!=ST_LIT) + { + if(lexd.warn && lex.last_quote && shp->inlineno > shlex.lastline) + errormsg(SH_DICT,ERROR_warn(0),e_lexlongquote,shlex.lastline,lex.last_quote); + lex.last_quote = 0; + shlex.lastline = shp->inlineno; + if(mode!=ST_DOL) + pushlevel('\'',mode); + mode = ST_LIT; + continue; + } + /* check for multi-line single-quoted string */ + else if(shp->inlineno > shlex.lastline) + lex.last_quote = '\''; + mode = oldmode(); + poplevel(); + break; + case S_ESC2: + /* \ inside '' */ + if(endchar()=='$') + { + fcgetc(n); + if(n=='\n') + shp->inlineno++; + } + continue; + case S_GRAVE: + if(lexd.warn && (mode!=ST_QUOTE || endchar()!='`')) + errormsg(SH_DICT,ERROR_warn(0),e_lexobsolete1,shp->inlineno); + wordflags |=(ARG_MAC|ARG_EXP); + if(mode==ST_QUOTE) + ingrave = !ingrave; + /* FALL THRU */ + case S_QUOTE: + if(oldmode()==ST_NONE && lexd.arith) /* in ((...)) */ + continue; + if(n==S_QUOTE) + wordflags |=ARG_QUOTED; + if(mode!=ST_QUOTE) + { + if(c!='"' || mode!=ST_QNEST) + { + if(lexd.warn && lex.last_quote && shp->inlineno > shlex.lastline) + errormsg(SH_DICT,ERROR_warn(0),e_lexlongquote,shlex.lastline,lex.last_quote); + lex.last_quote=0; + shlex.lastline = shp->inlineno; + pushlevel(c,mode); + } + ingrave = (c=='`'); + mode = ST_QUOTE; + continue; + } + else if((n=endchar())==c) + { + if(shp->inlineno > shlex.lastline) + lex.last_quote = c; + mode = oldmode(); + poplevel(); + } + else if(c=='"' && n==RBRACE) + mode = ST_QNEST; + break; + case S_DOL: + /* don't check syntax inside `` */ + if(mode==ST_QUOTE && ingrave) + continue; +#if SHOPT_KIA + if(lexd.first) + lexd.kiaoff = fcseek(0)-lexd.first; + else + lexd.kiaoff = staktell()+fcseek(0)-fcfirst(); +#endif /* SHOPT_KIA */ + pushlevel('$',mode); + mode = ST_DOL; + continue; + case S_PAR: + wordflags |= ARG_MAC; + mode = oldmode(); + poplevel(); + fcseek(-1); + wordflags |= comsub(lp); + continue; + case S_RBRA: + if((n=endchar()) == '$') + goto err; + if(mode!=ST_QUOTE || n==RBRACE) + { + mode = oldmode(); + poplevel(); + } + break; + case S_EDOL: + /* end $identifier */ +#if SHOPT_KIA + if(shlex.kiafile) + refvar(0); +#endif /* SHOPT_KIA */ + if(lexd.warn && c==LBRACT) + errormsg(SH_DICT,ERROR_warn(0),e_lexusebrace,shp->inlineno); + fcseek(-1); + mode = oldmode(); + poplevel(); + break; + case S_DOT: + /* make sure next character is alpha */ + if(fcgetc(n)>0) + fcseek(-1); + if(isaletter(n) || n==LBRACT) + continue; + if(mode==ST_NAME) + { + if(n=='=') + continue; + break; + } + else if(n==RBRACE) + continue; + if(isastchar(n)) + continue; + goto err; + case S_SPC1: + wordflags |= ARG_MAC; + if(endchar()==RBRACE) + { + setchar(c); + continue; + } + /* FALL THRU */ + case S_ALP: + if(c=='.' && endchar()=='$') + goto err; + case S_SPC2: + case S_DIG: + wordflags |= ARG_MAC; + switch(endchar()) + { + case '$': + if(n==S_ALP) /* $identifier */ + mode = ST_DOLNAME; + else + { + mode = oldmode(); + poplevel(); + } + break; +#if SHOPT_TYPEDEF + case '@': +#endif /* SHOPT_TYPEDEF */ + case '!': + if(n!=S_ALP) + goto dolerr; + case '#': + case RBRACE: + if(n==S_ALP) + { + setchar(RBRACE); + if(c=='.') + fcseek(-1); + mode = ST_BRACE; + } + else + { + if(fcgetc(c)>0) + fcseek(-1); + if(state[c]==S_ALP) + goto err; + if(n==S_DIG) + setchar('0'); + else + setchar('!'); + } + break; + case '0': + if(n==S_DIG) + break; + default: + goto dolerr; + } + break; + dolerr: + case S_ERR: + if((n=endchar()) == '$') + goto err; + if(c=='*' || (n=sh_lexstates[ST_BRACE][c])!=S_MOD1 && n!=S_MOD2) + { + /* see whether inside `...` */ + mode = oldmode(); + poplevel(); + if((n = endchar()) != '`') + goto err; + pushlevel(RBRACE,mode); + } + else + setchar(RBRACE); + mode = ST_NESTED; + continue; + case S_MOD1: + if(oldmode()==ST_QUOTE || oldmode()==ST_NONE) + { + /* allow ' inside "${...}" */ + if(c==':' && fcgetc(n)>0) + { + n = state[n]; + fcseek(-1); + } + if(n==S_MOD1) + { + mode = ST_QUOTE; + continue; + } + } + /* FALL THRU */ + case S_MOD2: +#if SHOPT_KIA + if(shlex.kiafile) + refvar(1); +#endif /* SHOPT_KIA */ + if(c!=':' && fcgetc(n)>0) + { + if(n!=c) + c = 0; + if(!c || (fcgetc(n)>0)) + { + fcseek(-1); + if(n==LPAREN) + { + if(c!='%') + { + shlex.token = n; + sh_syntax(); + } + else if(lexd.warn) + errormsg(SH_DICT,ERROR_warn(0),e_lexquote,shp->inlineno,'%'); + } + } + } + mode = ST_NESTED; + continue; + case S_LBRA: + if((c=endchar()) == '$') + { + setchar(RBRACE); + if(fcgetc(c)>0) + fcseek(-1); + if(state[c]!=S_ERR && c!=RBRACE) + continue; + } + err: + n = endchar(); + mode = oldmode(); + poplevel(); + if(n!='$') + { + shlex.token = c; + sh_syntax(); + } + else + { + if(lexd.warn && c!='/' && sh_lexstates[ST_NORM][c]!=S_BREAK && (c!='"' || mode==ST_QUOTE)) + errormsg(SH_DICT,ERROR_warn(0),e_lexslash,shp->inlineno); + else if(c=='"' && mode!=ST_QUOTE) + wordflags |= ARG_MESSAGE; + fcseek(-1); + } + continue; + case S_META: + if(lexd.warn && endchar()==RBRACE) + errormsg(SH_DICT,ERROR_warn(0),e_lexusequote,shp->inlineno,c); + continue; + case S_PUSH: + pushlevel(RPAREN,mode); + mode = ST_NESTED; + continue; + case S_POP: + do_pop: + if(lexd.level <= inlevel) + break; + n = endchar(); + if(c==RBRACT && !(n==RBRACT || n==RPAREN)) + continue; + if((c==RBRACE||c==RPAREN) && n==RPAREN) + { + if(fcgetc(n)==LPAREN) + { + if(c!=RPAREN) + fcseek(-1); + continue; + } + if(n>0) + fcseek(-1); + n = RPAREN; + } + if(c==';' && n!=';') + { + if(lexd.warn && n==RBRACE) + errormsg(SH_DICT,ERROR_warn(0),e_lexusequote,shp->inlineno,c); + continue; + } + if(mode==ST_QNEST) + { + if(lexd.warn) + errormsg(SH_DICT,ERROR_warn(0),e_lexescape,shp->inlineno,c); + continue; + } + mode = oldmode(); + poplevel(); + /* quotes in subscript need expansion */ + if(mode==ST_NAME && (wordflags&ARG_QUOTED)) + wordflags |= ARG_MAC; + /* check for ((...)) */ + if(n==1 && c==RPAREN) + { + if(fcgetc(n)==RPAREN) + { + if(mode==ST_NONE && !lexd.dolparen) + goto breakloop; + lex.reservok = 1; + lex.skipword = 0; + return(shlex.token=EXPRSYM); + } + /* backward compatibility */ + if(lexd.dolparen) + fcseek(-1); + else + { + if(lexd.warn) + errormsg(SH_DICT,ERROR_warn(0),e_lexnested,shp->inlineno); + if(!(state=lexd.first)) + state = fcfirst(); + fcseek(state-fcseek(0)); + if(shlex.arg) + { + shlex.arg = (struct argnod*)stakfreeze(1); + setupalias(lp,shlex.arg->argval,NIL(Namval_t*)); + } + lexd.paren = 1; + } + return(shlex.token=LPAREN); + } + if(mode==ST_NONE) + return(0); + if(c!=n) + { + shlex.token = c; + sh_syntax(); + } + if(c==RBRACE && (mode==ST_NAME||mode==ST_NORM)) + goto epat; + continue; + case S_EQ: + assignment = shlex.assignok; + /* FALL THRU */ + case S_COLON: + if(assignment) + { + if((c=fcget())=='~') + wordflags |= ARG_MAC; + else if(c!=LPAREN && assignment==SH_COMPASSIGN) + assignment = 0; + fcseek(-1); + } + break; + case S_LABEL: + if(lex.reservok && !lex.incase) + { + c = fcget(); + fcseek(-1); + if(state[c]==S_BREAK) + { + assignment = -1; + goto breakloop; + } + } + break; + case S_BRACT: + /* check for possible subscript */ + if((n=endchar())==RBRACT || n==RPAREN || + (mode==ST_BRACE) || + (oldmode()==ST_NONE) || + (mode==ST_NAME && (shlex.assignok||lexd.level))) + { + pushlevel(RBRACT,mode); + wordflags |= ARG_QUOTED; + mode = ST_NESTED; + continue; + } + wordflags |= ARG_EXP; + break; + case S_BRACE: + { + int isfirst; + if(lexd.dolparen) + break; + isfirst = (lexd.first&&fcseek(0)==lexd.first+1); + fcgetc(n); + /* check for {} */ + if(c==LBRACE && n==RBRACE) + break; + if(n>0) + fcseek(-1); + else if(lex.reservok) + break; + /* check for reserved word { or } */ + if(lex.reservok && state[n]==S_BREAK && isfirst) + break; + if(sh_isoption(SH_BRACEEXPAND) && c==LBRACE && !assignment && state[n]!=S_BREAK + && !lex.incase && !lex.intest + && !lex.skipword) + { + wordflags |= ARG_EXP; + } + if(c==RBRACE && n==LPAREN) + goto epat; + break; + } + case S_PAT: + wordflags |= ARG_EXP; + /* FALL THRU */ + case S_EPAT: + epat: + if(fcgetc(n)==LPAREN) + { + if(lex.incase==TEST_RE) + { + lex.incase++; + pushlevel(RPAREN,ST_NORM); + mode = ST_NESTED; + } + wordflags |= ARG_EXP; + pushlevel(RPAREN,mode); + mode = ST_NESTED; + continue; + } + if(n>0) + fcseek(-1); + if(n=='=' && c=='+' && mode==ST_NAME) + continue; + break; + } + lex.comp_assign = 0; + if(mode==ST_NAME) + mode = ST_NORM; + else if(mode==ST_NONE) + return(0); + } +breakloop: + if(lexd.dolparen) + { + lexd.balance = 0; + if(lexd.docword) + nested_here(lp); + lexd.message = (wordflags&ARG_MESSAGE); + return(shlex.token=0); + } + if(!(state=lexd.first)) + state = fcfirst(); + n = fcseek(0)-(char*)state; + if(!shlex.arg) + shlex.arg = (struct argnod*)stakseek(ARGVAL); + if(n>0) + stakwrite(state,n); + /* add balancing character if necessary */ + if(lexd.balance) + { + stakputc(lexd.balance); + lexd.balance = 0; + } + stakputc(0); + stakseek(staktell()-1); + state = stakptr(ARGVAL); + n = staktell()-ARGVAL; + lexd.first=0; + if(n==1) + { + /* check for numbered redirection */ + n = state[0]; + if((c=='<' || c=='>') && isadigit(n)) + { + c = sh_lex(); + shlex.digits = (n-'0'); + return(c); + } + if(n==LBRACT) + c = 0; + else if(n=='~') + c = ARG_MAC; + else + c = (wordflags&ARG_EXP); + n = 1; + } + else if(n>2 && state[0]=='{' && state[n-1]=='}' && !lex.intest && !lex.incase && (c=='<' || c== '>') && sh_isoption(SH_BRACEEXPAND)) + { + if(!strchr(state,',')) + { + stakseek(staktell()-1); + shlex.arg = (struct argnod*)stakfreeze(1); + return(shlex.token=IOVNAME); + } + c = wordflags; + } + else + c = wordflags; + if(assignment<0) + { + stakseek(staktell()-1); + shlex.arg = (struct argnod*)stakfreeze(1); + lex.reservok = 1; + return(shlex.token=LABLSYM); + } + if(assignment || (lex.intest&&!lex.incase) || mode==ST_NONE) + c &= ~ARG_EXP; + if((c&ARG_EXP) && (c&ARG_QUOTED)) + c |= ARG_MAC; + if(mode==ST_NONE) + { + /* eliminate trailing )) */ + stakseek(staktell()-2); + } + if(c&ARG_MESSAGE) + { + if(sh_isoption(SH_DICTIONARY)) + shlex.arg = sh_endword(2); + if(!sh_isoption(SH_NOEXEC)) + { + shlex.arg = sh_endword(1); + c &= ~ARG_MESSAGE; + } + } + if(c==0 || (c&(ARG_MAC|ARG_EXP)) || (lexd.warn && !lexd.docword)) + { + shlex.arg = (struct argnod*)stakfreeze(1); + shlex.arg->argflag = (c?c:ARG_RAW); + } + else if(mode==ST_NONE) + shlex.arg = sh_endword(-1); + else + shlex.arg = sh_endword(0); + state = shlex.arg->argval; + lex.comp_assign = assignment; + if(assignment) + shlex.arg->argflag |= ARG_ASSIGN; + else if(!lex.skipword) + shlex.assignok = 0; + shlex.arg->argchn.cp = 0; + shlex.arg->argnxt.ap = 0; + if(mode==ST_NONE) + return(shlex.token=EXPRSYM); + if(lex.intest) + { + if(lex.testop1) + { + lex.testop1 = 0; + if(n==2 && state[0]=='-' && state[2]==0 && + strchr(test_opchars,state[1])) + { + if(lexd.warn && state[1]=='a') + errormsg(SH_DICT,ERROR_warn(0),e_lexobsolete2,shp->inlineno); + shlex.digits = state[1]; + shlex.token = TESTUNOP; + } + else if(n==1 && state[0]=='!' && state[1]==0) + { + lex.testop1 = 1; + shlex.token = '!'; + } + else + { + lex.testop2 = 1; + shlex.token = 0; + } + return(shlex.token); + } + lex.incase = 0; + c = sh_lookup(state,shtab_testops); + switch(c) + { + case TEST_END: + lex.testop2 = lex.intest = 0; + lex.reservok = 1; + shlex.token = ETESTSYM; + return(shlex.token); + + case TEST_SEQ: + if(lexd.warn && state[1]==0) + errormsg(SH_DICT,ERROR_warn(0),e_lexobsolete3,shp->inlineno); + /* FALL THRU */ + default: + if(lex.testop2) + { + if(lexd.warn && (c&TEST_ARITH)) + errormsg(SH_DICT,ERROR_warn(0),e_lexobsolete4,shp->inlineno,state); + if(c&TEST_PATTERN) + lex.incase = 1; + else if(c==TEST_REP) + lex.incase = TEST_RE; + lex.testop2 = 0; + shlex.digits = c; + shlex.token = TESTBINOP; + return(shlex.token); + } + + case TEST_OR: case TEST_AND: + case 0: + return(shlex.token=0); + } + } + if(lex.reservok /* && !lex.incase*/ && n<=2) + { + /* check for {, }, ! */ + c = state[0]; + if(n==1 && (c=='{' || c=='}' || c=='!')) + { + if(lexd.warn && c=='{' && lex.incase==2) + errormsg(SH_DICT,ERROR_warn(0),e_lexobsolete6,shp->inlineno); + if(lex.incase==1 && c==RBRACE) + lex.incase = 0; + return(shlex.token=c); + } + else if(!lex.incase && c==LBRACT && state[1]==LBRACT) + { + lex.intest = lex.testop1 = 1; + lex.testop2 = lex.reservok = 0; + return(shlex.token=BTESTSYM); + } + } + c = 0; + if(!lex.skipword) + { + if(n>1 && lex.reservok==1 && mode==ST_NAME && + (c=sh_lookup(state,shtab_reserved))) + { + if(lex.incase) + { + if(lex.incase >1) + lex.incase = 1; + else if(c==ESACSYM) + lex.incase = 0; + else + c = 0; + } + else if(c==FORSYM || c==CASESYM || c==SELECTSYM || c==FUNCTSYM || c==NSPACESYM) + { + lex.skipword = 1; + lex.incase = 2*(c==CASESYM); + } + else + lex.skipword = 0; + if(c==INSYM) + lex.reservok = 0; + else if(c==TIMESYM) + { + /* yech - POSIX requires time -p */ + while(fcgetc(n)==' ' || n=='\t'); + if(n>0) + fcseek(-1); + if(n=='-') + c=0; + } + return(shlex.token=c); + } + if(!(wordflags&ARG_QUOTED) && (lex.reservok||shlex.aliasok)) + { + /* check for aliases */ + Namval_t* np; + if(!lex.incase && !assignment && fcpeek(0)!=LPAREN && + (np=nv_search(state,shp->alias_tree,HASH_SCOPE)) + && !nv_isattr(np,NV_NOEXPAND) +#if KSHELL + && (!sh_isstate(SH_NOALIAS) || nv_isattr(np,NV_NOFREE)) +#endif /* KSHELL */ + && (state=nv_getval(np))) + { + setupalias(lp,state,np); + nv_onattr(np,NV_NOEXPAND); + lex.reservok = 1; + shlex.assignok |= lex.reservok; + return(sh_lex()); + } + } + lex.reservok = 0; + } + lex.skipword = lexd.docword = 0; + return(shlex.token=c); +} + +/* + * read to end of command substitution + */ +static int comsub(register Lex_t *lp) +{ + register int n,c,count=1; + register int line=shlex.sh->inlineno; + char word[5]; + int messages=0, assignok=shlex.assignok; + struct lexstate save; + save = lex; + sh_lexopen(lp,shlex.sh,1); + lexd.dolparen++; + lex.incase=0; + pushlevel(0,0); + if(sh_lex()==LPAREN) + { + while(1) + { + /* look for case and esac */ + n=0; + while(1) + { + fcgetc(c); + /* skip leading white space */ + if(n==0 && !sh_lexstates[ST_BEGIN][c]) + continue; + if(n==4) + break; + if(sh_lexstates[ST_NAME][c]) + goto skip; + word[n++] = c; + } + if(sh_lexstates[ST_NAME][c]==S_BREAK) + { + if(memcmp(word,"case",4)==0) + lex.incase=1; + else if(memcmp(word,"esac",4)==0) + lex.incase=0; + } + skip: + if(c && (c!='#' || n==0)) + fcseek(-1); + if(c==RBRACE && lex.incase) + lex.incase=0; + switch(sh_lex()) + { + case LPAREN: case IPROCSYM: case OPROCSYM: + if(!lex.incase) + count++; + break; + case RPAREN: + if(lex.incase) + lex.incase=0; + else if(--count<=0) + goto done; + break; + case EOFSYM: + shlex.lastline = line; + shlex.lasttok = LPAREN; + sh_syntax(); + case IOSEEKSYM: + if(fcgetc(c)!='#' && c>0) + fcseek(-1); + break; + case IODOCSYM: + sh_lex(); + break; + case 0: + messages |= lexd.message; + } + } + } +done: + poplevel(); + shlex.lastline = line; + lexd.dolparen--; + lex = save; + shlex.assignok = (endchar()==RBRACT?assignok:0); + return(messages); +} + +/* + * here-doc nested in $(...) + * allocate ionode with delimiter filled in without disturbing stak + */ +static void nested_here(register Lex_t *lp) +{ + register struct ionod *iop; + register int n,offset; + struct argnod *arg = shlex.arg; + char *base; + if(offset=staktell()) + base = stakfreeze(0); + n = fcseek(0)-lexd.docend; + iop = newof(0,struct ionod,1,n+ARGVAL); + iop->iolst = shlex.heredoc; + stakseek(ARGVAL); + stakwrite(lexd.docend,n); + shlex.arg = sh_endword(0); + iop->ioname = (char*)(iop+1); + strcpy(iop->ioname,shlex.arg->argval); + iop->iofile = (IODOC|IORAW); + if(lexd.docword>1) + iop->iofile |= IOSTRIP; + shlex.heredoc = iop; + shlex.arg = arg; + lexd.docword = 0; + if(offset) + stakset(base,offset); + else + stakseek(0); +} + +/* + * skip to <close> character + * if <copy> is non,zero, then the characters are copied to the stack + * <state> is the initial lexical state + */ +void sh_lexskip(int close, register int copy, int state) +{ + register Lex_t *lp = (Lex_t*)sh.lex_context; + register char *cp; + lexd.nest = close; + lexd.lex_state = state; + lexd.noarg = 1; + if(copy) + fcnotify(lex_advance); + else + lexd.nocopy++; + sh_lex(); + lexd.noarg = 0; + if(copy) + { + fcnotify(0); + if(!(cp=lexd.first)) + cp = fcfirst(); + if((copy = fcseek(0)-cp) > 0) + stakwrite(cp,copy); + } + else + lexd.nocopy--; +} + +#if SHOPT_CRNL + ssize_t _sfwrite(Sfio_t *sp, const Void_t *buff, size_t n) + { + const char *cp = (const char*)buff, *next=cp, *ep = cp + n; + int m=0,k; + while(next = (const char*)memchr(next,'\r',ep-next)) + if(*++next=='\n') + { + if(k=next-cp-1) + { + if((k=sfwrite(sp,cp,k)) < 0) + return(m>0?m:-1); + m += k; + } + cp = next; + } + if((k=sfwrite(sp,cp,ep-cp)) < 0) + return(m>0?m:-1); + return(m+k); + } +# define sfwrite _sfwrite +#endif /* SHOPT_CRNL */ + +/* + * read in here-document from script + * quoted here documents, and here-documents without special chars are + * noted with the IOQUOTE flag + * returns 1 for complete here-doc, 0 for EOF + */ + +static int here_copy(Lex_t *lp,register struct ionod *iop) +{ + register const char *state; + register int c,n; + register char *bufp,*cp; + register Sfio_t *sp=shlex.sh->heredocs, *funlog; + int stripcol=0,stripflg, nsave, special=0; + if(funlog=shlex.sh->funlog) + { + if(fcfill()>0) + fcseek(-1); + shlex.sh->funlog = 0; + } + if(iop->iolst) + here_copy(lp,iop->iolst); + iop->iooffset = sfseek(sp,(off_t)0,SEEK_END); + iop->iosize = 0; + iop->iodelim=iop->ioname; + /* check for and strip quoted characters in delimiter string */ + if(stripflg=iop->iofile&IOSTRIP) + { + while(*iop->iodelim=='\t') + iop->iodelim++; + /* skip over leading tabs in document */ + if(iop->iofile&IOLSEEK) + { + iop->iofile &= ~IOLSEEK; + while(fcgetc(c)=='\t' || c==' ') + { + if(c==' ') + stripcol++; + else + stripcol += 8 - stripcol%8; + } + } + else + while(fcgetc(c)=='\t'); + if(c>0) + fcseek(-1); + } + if(iop->iofile&IOQUOTE) + state = sh_lexstates[ST_LIT]; + else + state = sh_lexstates[ST_QUOTE]; + bufp = fcseek(0); + n = S_NL; + while(1) + { + if(n!=S_NL) + { + /* skip over regular characters */ + while((n=STATE(state,c))==0); + } + if(n==S_EOF || !(c=fcget())) + { + if(!lexd.dolparen && (c=(fcseek(0)-1)-bufp)) + { + if(n==S_ESC) + c--; + if((c=sfwrite(sp,bufp,c))>0) + iop->iosize += c; + } + if((c=lexfill())<=0) + break; + if(n==S_ESC) + { +#if SHOPT_CRNL + if(c=='\r' && (c=fcget())!=NL) + fcseek(-1); +#endif /* SHOPT_CRNL */ + if(c==NL) + fcseek(1); + else + sfputc(sp,'\\'); + } + bufp = fcseek(-1); + } + else + fcseek(-1); + switch(n) + { + case S_NL: + shlex.sh->inlineno++; + if((stripcol && c==' ') || (stripflg && c=='\t')) + { + if(!lexd.dolparen) + { + /* write out line */ + n = fcseek(0)-bufp; + if((n=sfwrite(sp,bufp,n))>0) + iop->iosize += n; + } + /* skip over tabs */ + if(stripcol) + { + int col=0; + do + { + fcgetc(c); + if(c==' ') + col++; + else + col += 8 - col%8; + if(col>stripcol) + break; + } + while (c==' ' || c=='\t'); + } + else while(c=='\t') + fcgetc(c); + if(c<=0) + goto done; + bufp = fcseek(-1); + } + if(c!=iop->iodelim[0]) + break; + cp = fcseek(0); + nsave = n = 0; + while(1) + { + if(!(c=fcget())) + { + if(!lexd.dolparen && (c=cp-bufp)) + { + if((c=sfwrite(sp,cp=bufp,c))>0) + iop->iosize+=c; + } + nsave = n; + if((c=lexfill())<=0) + { + c = iop->iodelim[n]==0; + goto done; + } + } +#if SHOPT_CRNL + if(c=='\r' && (c=fcget())!=NL) + { + if(c) + fcseek(-1); + c='\r'; + } +#endif /* SHOPT_CRNL */ + if(c==NL) + shlex.sh->inlineno++; + if(iop->iodelim[n]==0 && (c==NL||c==RPAREN)) + { + if(!lexd.dolparen && (n=cp-bufp)) + { + if((n=sfwrite(sp,bufp,n))>0) + iop->iosize += n; + } + shlex.sh->inlineno--; + if(c==RPAREN) + fcseek(-1); + goto done; + } + if(iop->iodelim[n++]!=c) + { + /* + * The match for delimiter failed. + * nsave>0 only when a buffer boundary + * was crossed while checking the + * delimiter + */ + if(!lexd.dolparen && nsave>0) + { + if((n=sfwrite(sp,bufp,nsave))>0) + iop->iosize += n; + bufp = fcfirst(); + } + if(c==NL) + fcseek(-1); + break; + } + } + break; + case S_ESC: + n=1; +#if SHOPT_CRNL + if(c=='\r') + { + fcseek(1); + if(c=fcget()) + fcseek(-1); + if(c==NL) + n=2; + else + { + special++; + break; + } + } +#endif /* SHOPT_CRNL */ + if(c==NL) + { + /* new-line joining */ + shlex.sh->inlineno++; + if(!lexd.dolparen && (n=(fcseek(0)-bufp)-n)>0) + { + if((n=sfwrite(sp,bufp,n))>0) + iop->iosize += n; + bufp = fcseek(0)+1; + } + } + else + special++; + fcget(); + break; + + case S_GRAVE: + case S_DOL: + special++; + break; + } + n=0; + } +done: + shlex.sh->funlog = funlog; + if(lexd.dolparen) + free((void*)iop); + else if(!special) + iop->iofile |= IOQUOTE; + return(c); +} + +/* + * generates string for given token + */ +static char *fmttoken(Lex_t *lp, register int sym, char *tok) +{ + if(sym < 0) + return((char*)sh_translate(e_lexzerobyte)); + if(sym==0) + return(shlex.arg?shlex.arg->argval:"?"); + if(lex.intest && shlex.arg && *shlex.arg->argval) + return(shlex.arg->argval); + if(sym&SYMRES) + { + register const Shtable_t *tp=shtab_reserved; + while(tp->sh_number && tp->sh_number!=sym) + tp++; + return((char*)tp->sh_name); + } + if(sym==EOFSYM) + return((char*)sh_translate(e_endoffile)); + if(sym==NL) + return((char*)sh_translate(e_newline)); + tok[0] = sym; + if(sym&SYMREP) + tok[1] = sym; + else + { + switch(sym&SYMMASK) + { + case SYMAMP: + sym = '&'; + break; + case SYMPIPE: + sym = '|'; + break; + case SYMGT: + sym = '>'; + break; + case SYMLPAR: + sym = LPAREN; + break; + case SYMSHARP: + sym = '#'; + break; + default: + sym = 0; + } + tok[1] = sym; + } + tok[2] = 0; + return(tok); +} + +/* + * print a bad syntax message + */ + +void sh_syntax(void) +{ + register Shell_t *shp = sh_getinterp(); + register const char *cp = sh_translate(e_unexpected); + register char *tokstr; + register Lex_t *lp = (Lex_t*)shp->lex_context; + register int tok = shlex.token; + char tokbuf[3]; + Sfio_t *sp; + if((tok==EOFSYM) && shlex.lasttok) + { + tok = shlex.lasttok; + cp = sh_translate(e_unmatched); + } + else + shlex.lastline = shp->inlineno; + tokstr = fmttoken(lp,tok,tokbuf); + if((sp=fcfile()) || (shp->infd>=0 && (sp=shp->sftable[shp->infd]))) + { + /* clear out any pending input */ + register Sfio_t *top; + while(fcget()>0); + fcclose(); + while(top=sfstack(sp,SF_POPSTACK)) + sfclose(top); + } + else + fcclose(); + shp->inlineno = shlex.inlineno; + shp->st.firstline = shlex.firstline; +#if KSHELL + if(!sh_isstate(SH_INTERACTIVE) && !sh_isstate(SH_PROFILE)) +#else + if(shp->inlineno!=1) +#endif + errormsg(SH_DICT,ERROR_exit(SYNBAD),e_lexsyntax1,shlex.lastline,tokstr,cp); + else + errormsg(SH_DICT,ERROR_exit(SYNBAD),e_lexsyntax2,tokstr,cp); +} + +static char *stack_shift(register char *sp,char *dp) +{ + register char *ep; + register int offset = staktell(); + register int left = offset-(sp-stakptr(0)); + register int shift = (dp+1-sp); + offset += shift; + stakseek(offset); + sp = stakptr(offset); + ep = sp - shift; + while(left--) + *--sp = *--ep; + return(sp); +} + +/* + * Assumes that current word is unfrozen on top of the stak + * If <mode> is zero, gets rid of quoting and consider argument as string + * and returns pointer to frozen arg + * If mode==1, just replace $"..." strings with international strings + * The result is left on the stak + * If mode==2, the each $"" string is printed on standard output + */ +struct argnod *sh_endword(int mode) +{ + register const char *state = sh_lexstates[ST_NESTED]; + register int n; + register char *sp,*dp; + register int inquote=0, inlit=0; /* set within quoted strings */ + struct argnod* argp=0; + char *ep=0, *xp=0; + int bracket=0; + stakputc(0); + sp = stakptr(ARGVAL); +#if SHOPT_MULTIBYTE + if(mbwide()) + { + do + { + int len; + switch(len = mbsize(sp)) + { + case -1: /* illegal multi-byte char */ + case 0: + case 1: + n=state[*sp++]; + break; + default: + /* + * None of the state tables contain + * entries for multibyte characters, + * however, they should be treated + * the same as any other alph + * character. Therefore, we'll use + * the state of the 'a' character. + */ + n=state['a']; + sp += len; + } + } + while(n == 0); + } + else +#endif /* SHOPT_MULTIBYTE */ + while((n=state[*sp++])==0); + dp = sp; + if(mode<0) + inquote = 1; + while(1) + { + switch(n) + { + case S_EOF: + stakseek(dp-stakptr(0)); + if(mode<=0) + { + argp = (struct argnod*)stakfreeze(0); + argp->argflag = ARG_RAW|ARG_QUOTED; + } + return(argp); + case S_LIT: + if(!(inquote&1)) + { + inlit = !inlit; + if(mode==0 || (mode<0 && bracket)) + { + dp--; + if(ep) + { + *dp = 0; + dp = ep+stresc(ep); + } + ep = 0; + } + } + break; + case S_QUOTE: + if(mode<0 && !bracket) + break; + if(!inlit) + { + if(mode<=0) + dp--; + inquote = inquote^1; + if(ep) + { + char *msg; + if(mode==2) + { + sfprintf(sfstdout,"%.*s\n",dp-ep,ep); + ep = 0; + break; + } + *--dp = 0; +#if ERROR_VERSION >= 20000317L + msg = ERROR_translate(0,error_info.id,0,ep); +#else +# if ERROR_VERSION >= 20000101L + msg = ERROR_translate(error_info.id,ep); +# else + msg = ERROR_translate(ep,2); +# endif +#endif + n = strlen(msg); + dp = ep+n; + if(sp-dp <= 1) + { + sp = stack_shift(sp,dp); + dp = sp-1; + ep = dp-n; + } + memmove(ep,msg,n); + *dp++ = '"'; + } + ep = 0; + } + break; + case S_DOL: /* check for $'...' and $"..." */ + if(inlit) + break; + if(*sp==LPAREN || *sp==LBRACE) + { + inquote <<= 1; + break; + } + if(inquote&1) + break; + if(*sp=='\'' || *sp=='"') + { + if(*sp=='"') + inquote |= 1; + else + inlit = 1; + sp++; + if((mode==0||(mode<0&&bracket)) || (inquote&1)) + { + if(mode==2) + ep = dp++; + else if(mode==1) + (ep=dp)[-1] = '"'; + else + ep = --dp; + } + } + break; + case S_ESC: +#if SHOPT_CRNL + if(*sp=='\r' && sp[1]=='\n') + sp++; +#endif /* SHOPT_CRNL */ + if(inlit || mode>0) + { + if(mode<0) + { + if(dp>=sp) + { + sp = stack_shift(sp,dp+1); + dp = sp-2; + } + *dp++ = '\\'; + } + if(ep) + *dp++ = *sp++; + break; + } + n = *sp; +#if SHOPT_DOS + if(!(inquote&1) && sh_lexstates[ST_NORM][n]==0) + break; +#endif /* SHOPT_DOS */ + if(!(inquote&1) || (sh_lexstates[ST_QUOTE][n] && n!=RBRACE)) + { + if(n=='\n') + dp--; + else + dp[-1] = n; + sp++; + } + break; + case S_POP: + if(sp[-1]!=RBRACT) + break; + if(!inlit && !(inquote&1)) + { + inquote >>= 1; + if(xp) + dp = sh_checkid(xp,dp); + xp = 0; + if(--bracket<=0 && mode<0) + inquote = 1; + } + else if((inlit||inquote) && mode<0) + { + dp[-1] = '\\'; + if(dp>=sp) + { + sp = stack_shift(sp,dp); + dp = sp-1; + } + *dp++ = ']'; + } + break; + case S_BRACT: + if(dp[-2]=='.') + xp = dp; + if(mode<0) + { + if(inlit || (bracket&&inquote)) + { + dp[-1] = '\\'; + if(dp>=sp) + { + sp = stack_shift(sp,dp); + dp = sp-1; + } + *dp++ = '['; + } + else if(bracket++==0) + inquote = 0; + } + break; + } +#if SHOPT_MULTIBYTE + if(mbwide()) + { + do + { + int len; + switch(len = mbsize(sp)) + { + case -1: /* illegal multi-byte char */ + case 0: + case 1: + n=state[*dp++ = *sp++]; + break; + default: + /* + * None of the state tables contain + * entries for multibyte characters, + * however, they should be treated + * the same as any other alph + * character. Therefore, we'll use + * the state of the 'a' character. + */ + while(len--) + *dp++ = *sp++; + n=state['a']; + } + } + while(n == 0); + } + else +#endif /* SHOPT_MULTIBYTE */ + while((n=state[*dp++ = *sp++])==0); + } +} + +struct alias +{ + Sfdisc_t disc; + Namval_t *np; + int nextc; + int line; + char buf[2]; + Lex_t *lp; +}; + +/* + * This code gets called whenever an end of string is found with alias + */ + +#ifndef SF_ATEXIT +# define SF_ATEXIT 0 +#endif +/* + * This code gets called whenever an end of string is found with alias + */ +#ifdef SF_BUFCONST +static int alias_exceptf(Sfio_t *iop,int type,void *data, Sfdisc_t *handle) +#else +static int alias_exceptf(Sfio_t *iop,int type,Sfdisc_t *handle) +#endif +{ + register struct alias *ap = (struct alias*)handle; + register Namval_t *np; + register Lex_t *lp; + if(type==0 || type==SF_ATEXIT || !ap) + return(0); + lp = ap->lp; + np = ap->np; + if(type!=SF_READ) + { + if(type==SF_CLOSING) + { + register Sfdisc_t *dp = sfdisc(iop,SF_POPDISC); + if(dp!=handle) + sfdisc(iop,dp); + } + else if(type==SF_FINAL) + free((void*)ap); + goto done; + } + if(ap->nextc) + { + /* if last character is a blank, then next work can be alias */ + register int c = fcpeek(-1); + if(isblank(c)) + shlex.aliasok = 1; + *ap->buf = ap->nextc; + ap->nextc = 0; + sfsetbuf(iop,ap->buf,1); + return(1); + } +done: + if(np) + nv_offattr(np,NV_NOEXPAND); + return(0); +} + + +static void setupalias(Lex_t *lp, const char *string,Namval_t *np) +{ + register Sfio_t *iop, *base; + struct alias *ap = (struct alias*)malloc(sizeof(struct alias)); + ap->disc = alias_disc; + ap->lp = lp; + ap->buf[1] = 0; + if(ap->np = np) + { +#if SHOPT_KIA + if(shlex.kiafile) + { + unsigned long r; + r=kiaentity(nv_name(np),-1,'p',0,0,shlex.current,'a',0,""); + sfprintf(shlex.kiatmp,"p;%..64d;p;%..64d;%d;%d;e;\n",shlex.current,r,shlex.sh->inlineno,shlex.sh->inlineno); + } +#endif /* SHOPT_KIA */ + if((ap->nextc=fcget())==0) + ap->nextc = ' '; + } + else + ap->nextc = 0; + iop = sfopen(NIL(Sfio_t*),(char*)string,"s"); + sfdisc(iop, &ap->disc); + lexd.nocopy++; + if(!(base=fcfile())) + base = sfopen(NIL(Sfio_t*),fcseek(0),"s"); + fcclose(); + sfstack(base,iop); + fcfopen(base); + lexd.nocopy--; +} + +/* + * grow storage stack for nested constructs by STACK_ARRAY + */ +static int stack_grow(Lex_t *lp) +{ + lexd.lex_max += STACK_ARRAY; + if(lexd.lex_match) + lexd.lex_match = (int*)realloc((char*)lexd.lex_match,sizeof(int)*lexd.lex_max); + else + lexd.lex_match = (int*)malloc(sizeof(int)*STACK_ARRAY); + return(lexd.lex_match!=0); +} + diff --git a/usr/src/lib/libshell/common/sh/macro.c b/usr/src/lib/libshell/common/sh/macro.c new file mode 100644 index 0000000000..8108050b14 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/macro.c @@ -0,0 +1,2301 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Shell macro expander + * expands ~ + * expands ${...} + * expands $(...) + * expands $((...)) + * expands `...` + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <fcin.h> +#include <pwd.h> +#include "name.h" +#include "variables.h" +#include "shlex.h" +#include "io.h" +#include "shnodes.h" +#include "path.h" +#include "national.h" +#include "streval.h" + +#undef STR_GROUP +#ifndef STR_GROUP +# define STR_GROUP 0 +#endif + +#if !SHOPT_MULTIBYTE +#define mbchar(p) (*(unsigned char*)p++) +#endif + +static int _c_; +typedef struct _mac_ +{ + Shell_t *shp; /* pointer to shell interpreter */ + Sfio_t *sp; /* stream pointer for here-document */ + struct argnod **arghead; /* address of head of argument list */ + char *ifsp; /* pointer to IFS value */ + int fields; /* number of fields */ + short quoted; /* set when word has quotes */ + unsigned char ifs; /* first char of IFS */ + char quote; /* set within double quoted contexts */ + char lit; /* set within single quotes */ + char split; /* set when word splittin is possible */ + char pattern; /* set when file expansion follows */ + char patfound; /* set if pattern character found */ + char assign; /* set for assignments */ + char arith; /* set for ((...)) */ + char let; /* set when expanding let arguments */ + char zeros; /* strip leading zeros when set */ + void *nvwalk; /* for name space walking*/ +} Mac_t; + +#define mac (*((Mac_t*)(sh.mac_context))) + +#undef ESCAPE +#define ESCAPE '\\' +#define isescchar(s) ((s)>S_QUOTE) +#define isqescchar(s) ((s)>=S_QUOTE) +#define isbracechar(c) ((c)==RBRACE || (_c_=sh_lexstates[ST_BRACE][c])==S_MOD1 ||_c_==S_MOD2) +#define ltos(x) fmtbase((long)(x),0,0) + +/* type of macro expansions */ +#define M_BRACE 1 /* ${var} */ +#define M_TREE 2 /* ${var.} */ +#define M_SIZE 3 /* ${#var} */ +#define M_VNAME 4 /* ${!var} */ +#define M_SUBNAME 5 /* ${!var[sub]} */ +#define M_NAMESCAN 6 /* ${!var*} */ +#define M_NAMECOUNT 7 /* ${#var*} */ +#define M_TYPE 8 /* ${@var} */ + +static int substring(const char*, const char*, int[], int); +static void copyto(Mac_t*, int, int); +static void comsubst(Mac_t*,int); +static int varsub(Mac_t*); +static void mac_copy(Mac_t*,const char*, int); +static void tilde_expand2(int); +static char *sh_tilde(const char*); +static char *special(int); +static void endfield(Mac_t*,int); +static void mac_error(Namval_t*); +static char *mac_getstring(char*); +static int charlen(const char*,int); +#if SHOPT_MULTIBYTE + static char *lastchar(const char*,const char*); +#endif /* SHOPT_MULTIBYTE */ + +void *sh_macopen(Shell_t *shp) +{ + void *addr = newof(0,Mac_t,1,0); + Mac_t *mp = (Mac_t*)addr; + mp->shp = shp; + return(addr); +} + +/* + * perform only parameter substitution and catch failures + */ +char *sh_mactry(register char *string) +{ + if(string) + { + int jmp_val; + int savexit = sh.savexit; + struct checkpt buff; + sh_pushcontext(&buff,SH_JMPSUB); + jmp_val = sigsetjmp(buff.buff,0); + if(jmp_val == 0) + string = sh_mactrim(string,0); + sh_popcontext(&buff); + sh.savexit = savexit; + return(string); + } + return(""); +} + +/* + * Perform parameter expansion, command substitution, and arithmetic + * expansion on <str>. + * If <mode> greater than 1 file expansion is performed if the result + * yields a single pathname. + * If <mode> negative, than expansion rules for assignment are applied. + */ +char *sh_mactrim(char *str, register int mode) +{ + register Mac_t *mp = (Mac_t*)sh.mac_context; + Mac_t savemac; + savemac = *mp; + stakseek(0); + mp->arith = (mode==3); + mp->let = 0; + sh.argaddr = 0; + mp->pattern = (mode==1||mode==2); + mp->patfound = 0; + mp->assign = (mode<0); + mp->quoted = mp->lit = mp->split = mp->quote = 0; + mp->sp = 0; + if(mp->ifsp=nv_getval(nv_scoped(IFSNOD))) + mp->ifs = *mp->ifsp; + else + mp->ifs = ' '; + stakseek(0); + fcsopen(str); + copyto(mp,0,mp->arith); + str = stakfreeze(1); + if(mode==2) + { + /* expand only if unique */ + struct argnod *arglist=0; + if((mode=path_expand(str,&arglist))==1) + str = arglist->argval; + else if(mode>1) + errormsg(SH_DICT,ERROR_exit(1),e_ambiguous,str); + sh_trim(str); + } + *mp = savemac; + return(str); +} + +/* + * Perform all the expansions on the argument <argp> + */ +int sh_macexpand(register struct argnod *argp, struct argnod **arghead,int flag) +{ + register int flags = argp->argflag; + register char *str = argp->argval; + register Mac_t *mp = (Mac_t*)sh.mac_context; + char **saveargaddr = sh.argaddr; + Mac_t savemac; + savemac = *mp; + mp->sp = 0; + if(mp->ifsp=nv_getval(nv_scoped(IFSNOD))) + mp->ifs = *mp->ifsp; + else + mp->ifs = ' '; + if(flag&ARG_OPTIMIZE) + sh.argaddr = (char**)&argp->argchn.ap; + else + sh.argaddr = 0; + mp->arghead = arghead; + mp->quoted = mp->lit = mp->quote = 0; + mp->arith = ((flag&ARG_ARITH)!=0); + mp->let = ((flag&ARG_LET)!=0); + mp->split = !(flag&ARG_ASSIGN); + mp->assign = !mp->split; + mp->pattern = mp->split && !(flag&ARG_NOGLOB) && !sh_isoption(SH_NOGLOB); + str = argp->argval; + fcsopen(str); + mp->fields = 0; + if(!arghead) + { + mp->split = 0; + mp->pattern = ((flag&ARG_EXP)!=0); + stakseek(0); + } + else + { + stakseek(ARGVAL); + *stakptr(ARGVAL-1) = 0; + } + mp->patfound = 0; + copyto(mp,0,mp->arith); + if(!arghead) + { + argp->argchn.cp = stakfreeze(1); + if(sh.argaddr) + argp->argflag |= ARG_MAKE; + } + else + { + endfield(mp,mp->quoted); + flags = mp->fields; + if(flags==1 && sh.argaddr) + argp->argchn.ap = *arghead; + } + sh.argaddr = saveargaddr; + *mp = savemac; + return(flags); +} + +/* + * Expand here document which is stored in <infile> or <string> + * The result is written to <outfile> + */ +void sh_machere(Sfio_t *infile, Sfio_t *outfile, char *string) +{ + register int c,n; + register const char *state = sh_lexstates[ST_QUOTE]; + register char *cp; + register Mac_t *mp = (Mac_t*)sh.mac_context; + Fcin_t save; + Mac_t savemac; + savemac = *mp; + stakseek(0); + sh.argaddr = 0; + mp->sp = outfile; + mp->split = mp->assign = mp->pattern = mp->patfound = mp->lit = mp->arith = mp->let = 0; + mp->quote = 1; + mp->ifsp = nv_getval(nv_scoped(IFSNOD)); + mp->ifs = ' '; + fcsave(&save); + if(infile) + fcfopen(infile); + else + fcsopen(string); + fcnotify(0); + cp = fcseek(0); + while(1) + { +#if SHOPT_MULTIBYTE + if(mbwide()) + { + do + { + ssize_t len; + switch(len = mbsize(cp)) + { + case -1: /* illegal multi-byte char */ + case 0: + case 1: + n=state[*(unsigned char*)cp++]; + break; + default: + /* use state of alpah character */ + n=state['a']; + cp += len; + } + } + while(n == 0); + } + else +#endif /* SHOPT_MULTIBYTE */ + while((n=state[*(unsigned char*)cp++])==0); + if(n==S_NL || n==S_QUOTE || n==S_RBRA) + continue; + if(c=(cp-1)-fcseek(0)) + sfwrite(outfile,fcseek(0),c); + cp = fcseek(c+1); + switch(n) + { + case S_EOF: + if((n=fcfill()) <=0) + { + /* ignore 0 byte when reading from file */ + if(n==0 && fcfile()) + continue; + fcrestore(&save); + *mp = savemac; + return; + } + cp = fcseek(-1); + continue; + case S_ESC: + fcgetc(c); + cp=fcseek(-1); + if(c>0) + cp++; + if(!isescchar(state[c])) + sfputc(outfile,ESCAPE); + continue; + case S_GRAVE: + comsubst(mp,0); + break; + case S_DOL: + c = fcget(); + if(c=='.') + goto regular; + again: + switch(n=sh_lexstates[ST_DOL][c]) + { + case S_ALP: case S_SPC1: case S_SPC2: + case S_DIG: case S_LBRA: + { + Fcin_t save2; + int offset = staktell(); + int offset2; + stakputc(c); + if(n==S_LBRA) + sh_lexskip(RBRACE,1,ST_BRACE); + else if(n==S_ALP) + { + while(fcgetc(c),isaname(c)) + stakputc(c); + fcseek(-1); + } + stakputc(0); + offset2 = staktell(); + fcsave(&save2); + fcsopen(stakptr(offset)); + varsub(mp); + if(c=staktell()-offset2) + sfwrite(outfile,(char*)stakptr(offset2),c); + fcrestore(&save2); + stakseek(offset); + break; + } + case S_PAR: + comsubst(mp,1); + break; + case S_EOF: + if((c=fcfill()) > 0) + goto again; + /* FALL THRU */ + default: + regular: + sfputc(outfile,'$'); + fcseek(-1); + break; + } + } + cp = fcseek(0); + } +} + +/* + * expand argument but do not trim pattern characters + */ +char *sh_macpat(register struct argnod *arg, int flags) +{ + register char *sp = arg->argval; + if((arg->argflag&ARG_RAW)) + return(sp); + if(flags&ARG_OPTIMIZE) + arg->argchn.ap=0; + if(!(sp=arg->argchn.cp)) + { + sh_macexpand(arg,NIL(struct argnod**),flags); + sp = arg->argchn.cp; + if(!(flags&ARG_OPTIMIZE) || !(arg->argflag&ARG_MAKE)) + arg->argchn.cp = 0; + arg->argflag &= ~ARG_MAKE; + } + else + sh.optcount++; + return(sp); +} + +/* + * Process the characters up to <endch> or end of input string + */ +static void copyto(register Mac_t *mp,int endch, int newquote) +{ + register int c,n; + register const char *state = sh_lexstates[ST_MACRO]; + register char *cp,*first; + int tilde = -1; + int oldquote = mp->quote; + int ansi_c = 0; + int paren = 0; + int ere = 0; + int brace = 0; + Sfio_t *sp = mp->sp; + mp->sp = NIL(Sfio_t*); + mp->quote = newquote; + first = cp = fcseek(0); + if(!mp->quote && *cp=='~') + tilde = staktell(); + /* handle // operator specially */ + if(mp->pattern==2 && *cp=='/') + cp++; + while(1) + { +#if SHOPT_MULTIBYTE + if(mbwide()) + { + ssize_t len; + do + { + switch(len = mbsize(cp)) + { + case -1: /* illegal multi-byte char */ + case 0: + len = 1; + case 1: + n = state[*(unsigned char*)cp++]; + break; + default: + /* treat as if alpha */ + cp += len; + n=state['a']; + } + } + while(n == 0); + c = (cp-len) - first; + } + else +#endif /* SHOPT_MULTIBYTE */ + { + while((n=state[*(unsigned char*)cp++])==0); + c = (cp-1) - first; + } + switch(n) + { + case S_ESC: + if(ansi_c) + { + /* process ANSI-C escape character */ + char *addr= --cp; + if(c) + stakwrite(first,c); + c = chresc(cp,&addr); + cp = addr; + first = fcseek(cp-first); +#if SHOPT_MULTIBYTE + if(c > UCHAR_MAX && mbwide()) + { + int i; + unsigned char mb[8]; + + n = wctomb((char*)mb, c); + for(i=0;i<n;i++) + stakputc(mb[i]); + } + else +#endif /* SHOPT_MULTIBYTE */ + stakputc(c); + if(c==ESCAPE && mp->pattern) + stakputc(ESCAPE); + break; + } + else if(sh_isoption(SH_BRACEEXPAND) && mp->pattern==4 && (*cp==',' || *cp==LBRACE || *cp==RBRACE || *cp=='.')) + break; + else if(mp->split && endch && !mp->quote && !mp->lit) + { + if(c) + mac_copy(mp,first,c); + cp = fcseek(c+2); + if(c= cp[-1]) + { + stakputc(c); + if(c==ESCAPE) + stakputc(ESCAPE); + } + else + cp--; + first = cp; + break; + } + n = state[*(unsigned char*)cp]; + if(n==S_ENDCH && *cp!=endch) + n = S_PAT; + if(mp->pattern) + { + /* preserve \digit for pattern matching */ + /* also \alpha for extended patterns */ + if(!mp->lit && !mp->quote && (n==S_DIG || ((paren+ere) && sh_lexstates[ST_DOL][*(unsigned char*)cp]==S_ALP))) + break; + /* followed by file expansion */ + if(!mp->lit && (n==S_ESC || (!mp->quote && + (n==S_PAT||n==S_ENDCH||n==S_SLASH||n==S_BRACT||*cp=='-')))) + { + cp += (n!=S_EOF); + break; + } + if(mp->lit || (mp->quote && !isqescchar(n) && n!=S_ENDCH)) + { + /* add \ for file expansion */ + stakwrite(first,c+1); + first = fcseek(c); + break; + } + } + if(mp->lit) + break; + if(!mp->quote || isqescchar(n) || n==S_ENDCH) + { + /* eliminate \ */ + if(c) + stakwrite(first,c); + /* check new-line joining */ + first = fcseek(c+1); + } + cp += (n!=S_EOF); + break; + case S_GRAVE: case S_DOL: + if(mp->lit) + break; + if(c) + { + if(mp->split && !mp->quote && endch) + mac_copy(mp,first,c); + else + stakwrite(first,c); + } + first = fcseek(c+1); + c = mp->pattern; + if(n==S_GRAVE) + comsubst(mp,0); + else if((n= *cp)==0 || !varsub(mp)) + { + if(n=='\'' && !mp->quote) + ansi_c = 1; + else if(mp->quote || n!='"') + stakputc('$'); + } + cp = first = fcseek(0); + if(*cp) + mp->pattern = c; + break; + case S_ENDCH: + if((mp->lit || cp[-1]!=endch || mp->quote!=newquote)) + goto pattern; + if(endch==RBRACE && *cp==LPAREN && mp->pattern && brace) + goto pattern; + case S_EOF: + if(c) + { + if(mp->split && !mp->quote && !mp->lit && endch) + mac_copy(mp,first,c); + else + stakwrite(first,c); + } + c += (n!=S_EOF); + first = fcseek(c); + if(tilde>=0) + tilde_expand2(tilde); + goto done; + case S_QUOTE: + if(mp->lit || mp->arith) + break; + case S_LIT: + if(mp->arith) + { + if((*cp=='`' || *cp=='[') && cp[1]=='\'') + cp +=2; + break; + } + if(n==S_LIT && mp->quote) + break; + if(c) + { + if(mp->split && endch && !mp->quote && !mp->lit) + mac_copy(mp,first,c); + else + stakwrite(first,c); + } + first = fcseek(c+1); + if(n==S_LIT) + { + if(mp->quote) + continue; + if(mp->lit) + mp->lit = ansi_c = 0; + else + mp->lit = 1; + } + else + mp->quote = !mp->quote; + mp->quoted++; + break; + case S_BRACT: + if(mp->arith || ((mp->assign==1 || endch==RBRACT) && + !(mp->quote || mp->lit))) + { + int offset=0,oldpat = mp->pattern; + int oldarith = mp->arith; + stakwrite(first,++c); + if(mp->assign==1 && first[c-2]=='.') + offset = staktell(); + first = fcseek(c); + mp->pattern = 4; + mp->arith = 0; + copyto(mp,RBRACT,0); + mp->arith = oldarith; + mp->pattern = oldpat; + stakputc(RBRACT); + if(offset) + { + cp = stakptr(staktell()); + if(sh_checkid(stakptr(offset),cp)!=cp) + stakseek(staktell()-2); + } + cp = first = fcseek(0); + break; + } + case S_PAT: + if(mp->pattern && !(mp->quote || mp->lit)) + { + mp->patfound = mp->pattern; + if((n=cp[-1])==LPAREN) + { + paren++; + if((cp-first)>1 && cp[-2]=='~') + { + char *p = cp; + while((c=mbchar(p)) && c!=RPAREN && c!='E'); + ere = c=='E'; + } + } + else if(n==RPAREN) + --paren; + } + goto pattern; + case S_BRACE: + if(!(mp->quote || mp->lit)) + { + mp->patfound = mp->split && sh_isoption(SH_BRACEEXPAND); + brace = 1; + } + pattern: + if(!mp->pattern || !(mp->quote || mp->lit)) + { + /* mark beginning of {a,b} */ + if(n==S_BRACE && endch==0 && mp->pattern) + mp->pattern=4; + if(n==S_SLASH && mp->pattern==2) + mp->pattern=3; + break; + } + if(mp->pattern==3) + break; + if(c) + stakwrite(first,c); + first = fcseek(c); + stakputc(ESCAPE); + break; + case S_EQ: + if(mp->assign==1) + { + if(*cp=='~' && !endch && !mp->quote && !mp->lit) + tilde = staktell()+(c+1); + mp->assign = 2; + } + break; + case S_SLASH: + case S_COLON: + if(tilde >=0) + { + if(c) + stakwrite(first,c); + first = fcseek(c); + tilde_expand2(tilde); + tilde = -1; + c=0; + } + if(n==S_COLON && mp->assign==2 && *cp=='~' && endch==0 && !mp->quote &&!mp->lit) + tilde = staktell()+(c+1); + else if(n==S_SLASH && mp->pattern==2) +#if 0 + goto pattern; +#else + { + if(mp->quote || mp->lit) + goto pattern; + stakwrite(first,c+1); + first = fcseek(c+1); + c = staktell(); + sh_lexskip(RBRACE,0,ST_NESTED); + stakseek(c); + cp = fcseek(-1); + stakwrite(first,cp-first); + first=cp; + } +#endif + break; + } + } +done: + mp->sp = sp; + mp->quote = oldquote; +} + +/* + * copy <str> to stack performing sub-expression substitutions + */ +static void mac_substitute(Mac_t *mp, register char *cp,char *str,register int subexp[],int subsize) +{ + register int c,n; +#if 0 + register char *first=cp; +#else + register char *first=fcseek(0); + char *ptr; + Mac_t savemac; + n = staktell(); + savemac = *mp; + mp->pattern = 3; + mp->split = 0; + fcsopen(cp); + copyto(mp,0,0); + stakputc(0); + ptr = cp = strdup(stakptr(n)); + stakseek(n); + *mp = savemac; + fcsopen(first); + first = cp; +#endif + while(1) + { + while((c= *cp++) && c!=ESCAPE); + if(c==0) + break; + if((n= *cp++)=='\\' || n==RBRACE || (n>='0' && n<='9' && (n-='0')<subsize)) + { + c = cp-first-2; + if(c) + mac_copy(mp,first,c); + first=cp; + if(n=='\\' || n==RBRACE) + { + first--; + continue; + } + if((c=subexp[2*n])>=0) + { + if((n=subexp[2*n+1]-c)>0) + mac_copy(mp,str+c,n); + } + } + else if(n==0) + break; + } + if(n=cp-first-1) + mac_copy(mp,first,n); +#if 1 + free(ptr); +#endif +} + +#if SHOPT_FILESCAN +#define MAX_OFFSETS (sizeof(shp->offsets)/sizeof(shp->offsets[0])) +#define MAX_ARGN (32*1024) + +/* + * compute the arguments $1 ... $n and $# from the current line as needed + * save line offsets in the offsets array. + */ +static char *getdolarg(Shell_t *shp, int n, int *size) +{ + register int c=S_DELIM, d=shp->ifstable['\\']; + register unsigned char *first,*last,*cp = (unsigned char*)shp->cur_line; + register int m=shp->offsets[0],delim=0; + if(m==0) + return(0); + if(m<0) + m = 0; + else if(n<=m) + m = n-1; + else + m--; + if(m >= MAX_OFFSETS-1) + m = MAX_OFFSETS-2; + cp += shp->offsets[m+1]; + n -= m; + shp->ifstable['\\'] = 0; + shp->ifstable[0] = S_EOF; + while(1) + { + if(c==S_DELIM) + while(shp->ifstable[*cp++]==S_SPACE); + first = --cp; + if(++m < MAX_OFFSETS) + shp->offsets[m] = (first-(unsigned char*)shp->cur_line); + while((c=shp->ifstable[*cp++])==0); + last = cp-1; + if(c==S_SPACE) + while((c=shp->ifstable[*cp++])==S_SPACE); + if(--n==0 || c==S_EOF) + { + if(last==first && c==S_EOF && (!delim || (m>1))) + { + n++; + m--; + } + break; + } + delim = (c==S_DELIM); + } + shp->ifstable['\\'] = d; + if(m > shp->offsets[0]) + shp->offsets[0] = m; + if(n) + first = last = 0; + if(size) + *size = last-first; + return((char*)first); +} +#endif /* SHOPT_FILESCAN */ + +/* + * get the prefix after name reference resolution + */ +static char *prefix(char *id) +{ + Namval_t *np; + register char *cp = strchr(id,'.'); + if(cp) + { + *cp = 0; + np = nv_search(id, sh.var_tree,0); + *cp = '.'; + if(isastchar(cp[1])) + cp[1] = 0; + if(np && nv_isref(np)) + { + int n; + char *sp; + sh.argaddr = 0; + while(nv_isref(np)) + np = nv_refnode(np); + id = (char*)malloc(strlen(cp)+1+(n=strlen(sp=nv_name(np)))+1); + strcpy(&id[n],cp); + memcpy(id,sp,n); + return(id); + } + } + return(strdup(id)); +} + +/* + * copy to ']' onto the stack and return offset to it + */ +static int subcopy(Mac_t *mp, int flag) +{ + int split = mp->split; + int xpattern = mp->pattern; + int loc = staktell(); + int xarith = mp->arith; + mp->split = 0; + mp->arith = 0; + mp->pattern = flag?4:0; + copyto(mp,RBRACT,0); + mp->pattern = xpattern; + mp->split = split; + mp->arith = xarith; + return(loc); +} + +static int namecount(Mac_t *mp,const char *prefix) +{ + int count = 0; + mp->nvwalk = nv_diropen(prefix); + while(nv_dirnext(mp->nvwalk)) + count++; + nv_dirclose(mp->nvwalk); + return(count); +} + +static char *nextname(Mac_t *mp,const char *prefix, int len) +{ + char *cp; + if(len==0) + { + mp->nvwalk = nv_diropen(prefix); + return((char*)mp->nvwalk); + } + if(!(cp=nv_dirnext(mp->nvwalk))) + nv_dirclose(mp->nvwalk); + return(cp); +} + +/* + * This routine handles $param, ${parm}, and ${param op word} + * The input stream is assumed to be a string + */ +static int varsub(Mac_t *mp) +{ + register int c; + register int type=0; /* M_xxx */ + register char *v,*argp=0; + register Namval_t *np = NIL(Namval_t*); + register int dolg=0, mode=0; + Namarr_t *ap=0; + int dolmax=0, vsize= -1, offset= -1, nulflg, replen=0, bysub=0; + char idbuff[3], *id = idbuff, *pattern=0, *repstr; + int oldpat=mp->pattern,idnum=0,flag=0,d; +retry1: + mp->zeros = 0; + idbuff[0] = 0; + idbuff[1] = 0; + c = fcget(); + switch(c>0x7f?S_ALP:sh_lexstates[ST_DOL][c]) + { + case S_RBRA: + if(type<M_SIZE) + goto nosub; + /* This code handles ${#} */ + c = mode; + mode = type = 0; + /* FALL THRU */ + case S_SPC1: + if(type==M_BRACE) + { + if(isaletter(mode=fcpeek(0)) || mode=='.') + { + if(c=='#') + type = M_SIZE; +#ifdef SHOPT_TYPEDEF + else if(c=='@') + { + type = M_TYPE; + goto retry1; + } +#endif /* SHOPT_TYPEDEF */ + else + type = M_VNAME; + mode = c; + goto retry1; + } + else if(c=='#' && (isadigit(mode)||fcpeek(1)==RBRACE)) + { + type = M_SIZE; + mode = c; + goto retry1; + } + } + /* FALL THRU */ + case S_SPC2: + *id = c; + v = special(c); + if(isastchar(c)) + { + mode = c; +#if SHOPT_FILESCAN + if(sh.cur_line) + { + v = getdolarg(&sh,1,(int*)0); + dolmax = MAX_ARGN; + } + else +#endif /* SHOPT_FILESCAN */ + dolmax = sh.st.dolc+1; + dolg = (v!=0); + } + break; + case S_LBRA: + if(type) + goto nosub; + type = M_BRACE; + goto retry1; + case S_PAR: + if(type) + goto nosub; + comsubst(mp,1); + return(1); + case S_DIG: + c -= '0'; + sh.argaddr = 0; + if(type) + { + register int d; + while((d=fcget()),isadigit(d)) + c = 10*c + (d-'0'); + fcseek(-1); + } + idnum = c; + if(c==0) + v = special(c); +#if SHOPT_FILESCAN + else if(sh.cur_line) + { + sh.used_pos = 1; + v = getdolarg(&sh,c,&vsize); + } +#endif /* SHOPT_FILESCAN */ + else if(c <= sh.st.dolc) + { + sh.used_pos = 1; + v = sh.st.dolv[c]; + } + else + v = 0; + break; + case S_ALP: + if(c=='.' && type==0) + goto nosub; + offset = staktell(); + do + { + np = 0; + do + stakputc(c); + while(((c=fcget()),(c>0x7f||isaname(c)))||type && c=='.'); + while(c==LBRACT && type) + { + sh.argaddr=0; + if((c=fcget(),isastchar(c)) && fcpeek(0)==RBRACT) + { + if(type==M_VNAME) + type = M_SUBNAME; + idbuff[0] = mode = c; + fcget(); + c = fcget(); + if(c=='.' || c==LBRACT) + { + stakputc(LBRACT); + stakputc(mode); + stakputc(RBRACT); + } + else + flag = NV_ARRAY; + break; + } + else + { + fcseek(-1); + if(type==M_VNAME) + type = M_SUBNAME; + stakputc(LBRACT); + v = stakptr(subcopy(mp,1)); + stakputc(RBRACT); + c = fcget(); + } + } + } + while(type && c=='.'); + if(c==RBRACE && type && fcpeek(-2)=='.') + { + stakseek(staktell()-1); + type = M_TREE; + } + stakputc(0); + id=stakptr(offset); + if(isastchar(c) && type) + { + if(type==M_VNAME || type==M_SIZE) + { + idbuff[0] = mode = c; + if((d=fcpeek(0))==c) + idbuff[1] = fcget(); + if(type==M_VNAME) + type = M_NAMESCAN; + else + type = M_NAMECOUNT; + break; + } + goto nosub; + } + flag |= NV_NOASSIGN|NV_VARNAME|NV_NOADD; + if(c=='=' || c=='?' || (c==':' && ((d=fcpeek(0))=='=' || d=='?'))) + flag &= ~NV_NOADD; +#if SHOPT_FILESCAN + if(sh.cur_line && *id=='R' && strcmp(id,"REPLY")==0) + { + sh.argaddr=0; + np = REPLYNOD; + } + else +#endif /* SHOPT_FILESCAN */ + if(sh.argaddr) + flag &= ~NV_NOADD; + np = nv_open(id,sh.var_tree,flag|NV_NOFAIL); + ap = np?nv_arrayptr(np):0; + if(type) + { + if(ap && isastchar(mode) && !(ap->nelem&ARRAY_SCAN)) + nv_putsub(np,NIL(char*),ARRAY_SCAN); + if(!isbracechar(c)) + goto nosub; + else + fcseek(-1); + } + else + fcseek(-1); + if((type==M_VNAME||type==M_SUBNAME) && sh.argaddr && strcmp(nv_name(np),id)) + sh.argaddr = 0; + c = (type>M_BRACE && isastchar(mode)); + if(np && (!c || !ap)) + { + if(type==M_VNAME) + { + type = M_BRACE; + v = nv_name(np); + } +#ifdef SHOPT_TYPEDEF + else if(type==M_TYPE) + { +#if 0 + Namval_t *nq = nv_type(np); +#else + Namval_t *nq = 0; +#endif + type = M_BRACE; + if(nq) + v = nv_name(nq); + else + { + nv_attribute(np,sh.strbuf,"typeset",1); + v = sfstruse(sh.strbuf); + } + } +#endif /* SHOPT_TYPEDEF */ +#if SHOPT_FILESCAN + else if(sh.cur_line && np==REPLYNOD) + v = sh.cur_line; +#endif /* SHOPT_FILESCAN */ + else if(type==M_TREE) + v = nv_getvtree(np,(Namfun_t*)0); + else + { + v = nv_getval(np); + /* special case --- ignore leading zeros */ + if( (mp->arith||mp->let) && (np->nvfun || nv_isattr(np,(NV_LJUST|NV_RJUST|NV_ZFILL))) && (offset==0 || !isalnum(*((unsigned char*)stakptr(offset-1))))) + mp->zeros = 1; + } + } + else + v = 0; + stakseek(offset); + if(ap) + { +#if SHOPT_OPTIMIZE + if(sh.argaddr) + nv_optimize(np); +#endif + if(isastchar(mode) && array_elem(ap)> !c) + dolg = -1; + else + dolg = 0; + } + break; + case S_EOF: + fcseek(-1); + default: + goto nosub; + } + c = fcget(); + if(type>M_TREE) + { + if(c!=RBRACE) + mac_error(np); + if(type==M_NAMESCAN || type==M_NAMECOUNT) + { + id = prefix(id); + stakseek(offset); + if(type==M_NAMECOUNT) + { + c = namecount(mp,id); + v = ltos(c); + } + else + { + dolmax = strlen(id); + dolg = -1; + nextname(mp,id,0); + v = nextname(mp,id,dolmax); + } + } + else if(type==M_SUBNAME) + { + if(dolg<0) + { + v = nv_getsub(np); + bysub=1; + } + else if(v) + { + if(!ap || isastchar(mode)) + v = "0"; + else + v = nv_getsub(np); + } + } + else + { + if(!isastchar(mode)) + c = charlen(v,vsize); + else if(dolg>0) + { +#if SHOPT_FILESCAN + if(sh.cur_line) + { + getdolarg(&sh,MAX_ARGN,(int*)0); + c = sh.offsets[0]; + } + else +#endif /* SHOPT_FILESCAN */ + c = sh.st.dolc; + } + else if(dolg<0) + c = array_elem(ap); + else + c = (v!=0); + dolg = dolmax = 0; + v = ltos(c); + } + c = RBRACE; + } + nulflg = 0; + if(type && c==':') + { + c = fcget(); + if(sh_lexstates[ST_BRACE][c]==S_MOD1 && c!='*' && c!= ':') + nulflg=1; + else if(c!='%' && c!='#') + { + fcseek(-1); + c = ':'; + } + } + if(type) + { + if(!isbracechar(c)) + { + if(!nulflg) + mac_error(np); + fcseek(-1); + c = ':'; + } + if(c!=RBRACE) + { + int newops = (c=='#' || c == '%' || c=='/'); + offset = staktell(); + if(c=='/' ||c==':' || ((!v || (nulflg && *v==0)) ^ (c=='+'||c=='#'||c=='%'))) + { + int newquote = mp->quote; + int split = mp->split; + int quoted = mp->quoted; + int arith = mp->arith; + int zeros = mp->zeros; + if(newops) + { + type = fcget(); + if(type=='%' || type=='#') + { + int d = fcget(); + fcseek(-1); + if(d=='(') + type = 0; + } + fcseek(-1); + mp->pattern = 1+(c=='/'); + mp->split = 0; + mp->quoted = 0; + mp->arith = mp->zeros = 0; + newquote = 0; + } + else if(c=='?' || c=='=') + mp->split = mp->pattern = 0; + copyto(mp,RBRACE,newquote); + if(!oldpat) + mp->patfound = 0; + mp->pattern = oldpat; + mp->split = split; + mp->quoted = quoted; + mp->arith = arith; + mp->zeros = zeros; + /* add null byte */ + stakputc(0); + stakseek(staktell()-1); + } + else + { + sh_lexskip(RBRACE,0,(!newops&&mp->quote)?ST_QUOTE:ST_NESTED); + stakseek(offset); + } + argp=stakptr(offset); + } + } + else + { + fcseek(-1); + c=0; + } + if(c==':') /* ${name:expr1[:expr2]} */ + { + char *ptr; + type = (int)sh_strnum(argp,&ptr,1); + if(isastchar(mode)) + { + if(id==idbuff) /* ${@} or ${*} */ + { + if(type<0 && (type+= dolmax)<0) + type = 0; + if(type==0) + v = special(dolg=0); +#if SHOPT_FILESCAN + else if(sh.cur_line) + { + v = getdolarg(&sh,dolg=type,&vsize); + if(!v) + dolmax = type; + } +#endif /* SHOPT_FILESCAN */ + else if(type < dolmax) + v = sh.st.dolv[dolg=type]; + else + v = 0; + } + else if(ap) + { + if(type<0) + { + if(array_assoc(ap)) + type = -type; + else + type += array_maxindex(np); + } + if(array_assoc(ap)) + { + while(type-- >0 && (v=0,nv_nextsub(np))) + v = nv_getval(np); + } + else if(type > 0) + { + if(nv_putsub(np,NIL(char*),type|ARRAY_SCAN)) + v = nv_getval(np); + else + v = 0; + } + } + else if(type>0) + v = 0; + } + else if(v) + { + vsize = charlen(v,vsize); + if(type<0 && (type += vsize)<0) + type = 0; + if(vsize < type) + v = 0; +#if SHOPT_MULTIBYTE + else if(mbwide()) + { + mbinit(); + while(type-->0) + { + if((c=mbsize(v))<1) + c = 1; + v += c; + } + c = ':'; + } +#endif /* SHOPT_MULTIBYTE */ + else + v += type; + vsize -= type; + } + if(*ptr==':') + { + if((type = (int)sh_strnum(ptr+1,&ptr,1)) <=0) + v = 0; + else if(isastchar(mode)) + { + if(dolg>=0) + { + if(dolg+type < dolmax) + dolmax = dolg+type; + } + else + dolmax = type; + } + else if(type < vsize) + { +#if SHOPT_MULTIBYTE + if(mbwide()) + { + char *vp = v; + mbinit(); + while(type-->0) + { + if((c=mbsize(vp))<1) + c = 1; + vp += c; + } + type = vp-v; + c = ':'; + } +#endif /* SHOPT_MULTIBYTE */ + vsize = type; + } + } + if(*ptr) + mac_error(np); + stakseek(offset); + argp = 0; + } + /* check for substring operations */ + else if(c == '#' || c == '%' || c=='/') + { + if(c=='/') + { + if(type=='/' || type=='#' || type=='%') + { + c = type; + type = '/'; + argp++; + } + else + type = 0; + } + else + { + if(type==c) /* ## or %% */ + argp++; + else + type = 0; + } + pattern = strdup(argp); + if((type=='/' || c=='/') && (repstr = mac_getstring(pattern))) + replen = strlen(repstr); + if(v || c=='/' && offset>=0) + stakseek(offset); + } + /* check for quoted @ */ + if(mode=='@' && mp->quote && !v && c!='-') + mp->quoted-=2; +retry2: + if(v && (!nulflg || *v ) && c!='+') + { + register int d = (mode=='@'?' ':mp->ifs); + int match[2*(MATCH_MAX+1)], nmatch, vsize_last; + char *vlast; + while(1) + { + if(!v) + v= ""; + if(c=='/' || c=='#' || c== '%') + { + flag = (type || c=='/')?STR_GROUP|STR_MAXIMAL:STR_GROUP; + if(c!='/') + flag |= STR_LEFT; + while(1) + { + vsize = strlen(v); + if(c=='%') + nmatch=substring(v,pattern,match,flag&STR_MAXIMAL); + else + nmatch=strgrpmatch(v,pattern,match,elementsof(match)/2,flag); + if(replen>0) + sh_setmatch(v,vsize,nmatch,match); + if(nmatch) + { + vlast = v; + vsize_last = vsize; + vsize = match[0]; + } + else if(c=='#') + vsize = 0; + if(vsize) + mac_copy(mp,v,vsize); + if(nmatch && replen>0) + mac_substitute(mp,repstr,v,match,nmatch); + if(nmatch==0) + v += vsize; + else + v += match[1]; + if(*v && c=='/' && type) + { + /* avoid infinite loop */ + if(nmatch && match[1]==0) + v++; + continue; + } + vsize = -1; + break; + } + if(replen==0) + sh_setmatch(vlast,vsize_last,nmatch,match); + } + if(vsize) + mac_copy(mp,v,vsize>0?vsize:strlen(v)); + if(dolg==0 && dolmax==0) + break; + if(dolg>=0) + { + if(++dolg >= dolmax) + break; +#if SHOPT_FILESCAN + if(sh.cur_line) + { + if(dolmax==MAX_ARGN && isastchar(mode)) + break; + if(!(v=getdolarg(&sh,dolg,&vsize))) + { + dolmax = dolg; + break; + } + } + else +#endif /* SHOPT_FILESCAN */ + v = sh.st.dolv[dolg]; + } + else if(!np) + { + if(!(v = nextname(mp,id,dolmax))) + break; + } + else + { + if(dolmax && --dolmax <=0) + { + nv_putsub(np,NIL(char*),ARRAY_UNDEF); + break; + } + if(nv_nextsub(np) == 0) + break; + if(bysub) + v = nv_getsub(np); + else + v = nv_getval(np); + } + if(mp->split && (!mp->quote || mode=='@')) + { + if(!np) + mp->pattern = 0; + endfield(mp,mp->quoted); + mp->pattern = oldpat; + } + else if(d) + { + if(mp->sp) + sfputc(mp->sp,d); + else + stakputc(d); + } + } + if(pattern) + free((void*)pattern); + } + else if(argp) + { + if(c=='/' && replen>0 && pattern && strmatch("",pattern)) + mac_substitute(mp,repstr,v,0,0); + if(c=='?') + { + if(np) + id = nv_name(np); + else if(idnum) + id = ltos(idnum); + if(*argp) + { + stakputc(0); + errormsg(SH_DICT,ERROR_exit(1),"%s: %s",id,argp); + } + else if(v) + errormsg(SH_DICT,ERROR_exit(1),e_nullset,id); + else + errormsg(SH_DICT,ERROR_exit(1),e_notset,id); + } + else if(c=='=') + { + if(np) + { + if(sh.subshell) + np = sh_assignok(np,1); + nv_putval(np,argp,0); + v = nv_getval(np); + nulflg = 0; + stakseek(offset); + goto retry2; + } + else + mac_error(np); + } + } + else if(sh_isoption(SH_NOUNSET) && (!np || nv_isnull(np) || (nv_isarray(np) && !np->nvalue.cp))) + { + if(np) + { + if(nv_isarray(np)) + { + sfprintf(sh.strbuf,"%s[%s]\0",nv_name(np),nv_getsub(np)); + id = nv_getsub(np); + id = sfstruse(sh.strbuf); + } + else + id = nv_name(np); + nv_close(np); + } + errormsg(SH_DICT,ERROR_exit(1),e_notset,id); + } + if(np) + nv_close(np); + return(1); +nosub: + if(type) + mac_error(np); + fcseek(-1); + nv_close(np); + return(0); +} + +/* + * This routine handles command substitution + * <type> is 0 for older `...` version + */ +static void comsubst(Mac_t *mp,int type) +{ + Sfdouble_t num; + register int c; + register char *str; + Sfio_t *sp; + Fcin_t save; + struct slnod *saveslp = sh.st.staklist; + struct _mac_ savemac; + int savtop = staktell(); + char lastc, *savptr = stakfreeze(0); + int was_history = sh_isstate(SH_HISTORY); + int was_verbose = sh_isstate(SH_VERBOSE); + int newlines,bufsize; + register Shnode_t *t; + Namval_t *np; + sh.argaddr = 0; + savemac = *mp; + sh.st.staklist=0; + if(type) + { + sp = 0; + fcseek(-1); + t = sh_dolparen(); + if(t && t->tre.tretyp==TARITH) + { + str = t->ar.arexpr->argval; + fcsave(&save); + if(!(t->ar.arexpr->argflag&ARG_RAW)) + str = sh_mactrim(str,3); + num = sh_arith(str); + out_offset: + stakset(savptr,savtop); + *mp = savemac; + if((Sflong_t)num==num) + sfprintf(sh.strbuf,"%lld",(Sflong_t)num); + else + sfprintf(sh.strbuf,"%.*Lg",LDBL_DIG,num); + str = sfstruse(sh.strbuf); + mac_copy(mp,str,strlen(str)); + sh.st.staklist = saveslp; + fcrestore(&save); + return; + } + } + else + { + while(fcgetc(c)!='`' && c) + { + if(c==ESCAPE) + { + fcgetc(c); + if(!(isescchar(sh_lexstates[ST_QUOTE][c]) || + (c=='"' && mp->quote)) || (c=='$' && fcpeek(0)=='\'')) + stakputc(ESCAPE); + } + stakputc(c); + } + c = staktell(); + str=stakfreeze(1); + /* disable verbose and don't save in history file */ + sh_offstate(SH_HISTORY); + sh_offstate(SH_VERBOSE); + if(mp->sp) + sfsync(mp->sp); /* flush before executing command */ + sp = sfnew(NIL(Sfio_t*),str,c,-1,SF_STRING|SF_READ); + c = sh.inlineno; + sh.inlineno = error_info.line+sh.st.firstline; + t = (Shnode_t*)sh_parse(mp->shp, sp,SH_EOF|SH_NL); + sh.inlineno = c; + } +#if KSHELL + if(t) + { + fcsave(&save); + sfclose(sp); + if(t->tre.tretyp==0 && !t->com.comarg) + { + /* special case $(<file) and $(<#file) */ + register int fd; + int r; + struct checkpt buff; + struct ionod *ip=0; + sh_pushcontext(&buff,SH_JMPIO); + if((ip=t->tre.treio) && + ((ip->iofile&IOLSEEK) || !(ip->iofile&IOUFD)) && + (r=sigsetjmp(buff.buff,0))==0) + fd = sh_redirect(ip,3); + else + fd = sh_chkopen(e_devnull); + sh_popcontext(&buff); + if(r==0 && ip && (ip->iofile&IOLSEEK)) + { + if(sp=sh.sftable[fd]) + num = sftell(sp); + else + num = lseek(fd, (off_t)0, SEEK_CUR); + goto out_offset; + } + sp = sfnew(NIL(Sfio_t*),(char*)malloc(IOBSIZE+1),IOBSIZE,fd,SF_READ|SF_MALLOC); + } + else + sp = sh_subshell(t,sh_isstate(SH_ERREXIT),1); + fcrestore(&save); + } + else + sp = sfopen(NIL(Sfio_t*),"","sr"); + sh_freeup(); + sh.st.staklist = saveslp; + if(was_history) + sh_onstate(SH_HISTORY); + if(was_verbose) + sh_onstate(SH_VERBOSE); +#else + sp = sfpopen(NIL(Sfio_t*),str,"r"); +#endif + *mp = savemac; + np = nv_scoped(IFSNOD); + nv_putval(np,mp->ifsp,0); + mp->ifsp = nv_getval(np); + stakset(savptr,savtop); + newlines = 0; + lastc = 0; + sfsetbuf(sp,(void*)sp,0); + bufsize = sfvalue(sp); + /* read command substitution output and put on stack or here-doc */ + sfpool(sp, NIL(Sfio_t*), SF_WRITE); + while((str=(char*)sfreserve(sp,SF_UNBOUND,0)) && (c = sfvalue(sp))>0) + { +#if SHOPT_CRNL + /* eliminate <cr> */ + register char *dp; + char *buff = str; + while(c>1 && (*str !='\r'|| str[1]!='\n')) + { + c--; + str++; + } + dp = str; + while(c>1) + { + str++; + c--; + while(c>1 && (*str!='\r' || str[1]!='\n')) + { + c--; + *dp++ = *str++; + } + } + if(c) + *dp++ = *str++; + *dp = 0; + str = buff; + c = dp-str; +#endif /* SHOPT_CRNL */ + if(newlines >0) + { + if(mp->sp) + sfnputc(mp->sp,'\n',newlines); + else if(!mp->quote && mp->split && sh.ifstable['\n']) + endfield(mp,0); + else while(newlines--) + stakputc('\n'); + newlines = 0; + } + else if(lastc) + { + mac_copy(mp,&lastc,1); + lastc = 0; + } + /* delay appending trailing new-lines */ + while(str[--c]=='\n') + newlines++; + if(++c < bufsize) + str[c] = 0; + else + { + /* can't write past buffer so save last character */ + lastc = str[--c]; + str[c] = 0; + } + mac_copy(mp,str,c); + } + if(--newlines>0 && sh.ifstable['\n']==S_DELIM) + { + if(mp->sp) + sfnputc(mp->sp,'\n',newlines); + else if(!mp->quote && mp->split && sh.ifstable['\n']) + endfield(mp,0); + else while(newlines--) + stakputc('\n'); + } + if(lastc) + mac_copy(mp,&lastc,1); + sfclose(sp); + return; +} + +/* + * copy <str> onto the stack + */ +static void mac_copy(register Mac_t *mp,register const char *str, register int size) +{ + register char *state; + register const char *cp=str; + register int c,n,nopat; + nopat = (mp->quote||mp->assign==1||mp->arith); + if(mp->zeros) + { + /* prevent leading 0's from becomming octal constants */ + while(size>1 && *str=='0') + str++,size--; + mp->zeros = 0; + cp = str; + } + if(mp->sp) + sfwrite(mp->sp,str,size); + else if(mp->pattern>=2 || (mp->pattern && nopat)) + { + state = sh_lexstates[ST_MACRO]; + /* insert \ before file expansion characters */ + while(size-->0) + { + c = state[n= *(unsigned char*)cp++]; + if(nopat&&(c==S_PAT||c==S_ESC||c==S_BRACT||c==S_ENDCH) && mp->pattern!=3) + c=1; + else if(mp->pattern==4 && (c==S_ESC||c==S_BRACT||c==S_ENDCH || isastchar(n))) + c=1; + else if(mp->pattern==2 && c==S_SLASH) + c=1; + else if(mp->pattern==3 && c==S_ESC && (state[*(unsigned char*)cp]==S_DIG||(*cp==ESCAPE))) + { + if(!(c=mp->quote)) + cp++; + } + else + c=0; + if(c) + { + if(c = (cp-1) - str) + stakwrite(str,c); + stakputc(ESCAPE); + str = cp-1; + } + } + if(c = cp-str) + stakwrite(str,c); + } + else if(!mp->quote && mp->split && (mp->ifs||mp->pattern)) + { + /* split words at ifs characters */ + state = sh.ifstable; + if(mp->pattern) + { + char *sp = "&|()"; + while(c = *sp++) + { + if(state[c]==0) + state[c] = S_EPAT; + } + sp = "*?[{"; + while(c = *sp++) + { + if(state[c]==0) + state[c] = S_PAT; + } + if(state[ESCAPE]==0) + state[ESCAPE] = S_ESC; + } + while(size-->0) + { + if((n=state[c= *(unsigned char*)cp++])==S_ESC || n==S_EPAT) + { + /* don't allow extended patterns in this case */ + mp->patfound = mp->pattern; + stakputc(ESCAPE); + } + else if(n==S_PAT) + mp->patfound = mp->pattern; + else if(n && mp->ifs) + { +#if SHOPT_MULTIBYTE + if(n==S_MBYTE) + { + if(sh_strchr(mp->ifsp,cp-1)<0) + continue; + n = mbsize(cp-1) - 1; + if(n==-2) + n = 0; + cp += n; + size -= n; + n= S_DELIM; + } +#endif /* SHOPT_MULTIBYTE */ + if(n==S_SPACE || n==S_NL) + { + while(size>0 && ((n=state[c= *(unsigned char*)cp++])==S_SPACE||n==S_NL)) + size--; +#if SHOPT_MULTIBYTE + if(n==S_MBYTE && sh_strchr(mp->ifsp,cp-1)>=0) + { + n = mbsize(cp-1) - 1; + if(n==-2) + n = 0; + cp += n; + size -= n; + n=S_DELIM; + } + else +#endif /* SHOPT_MULTIBYTE */ + if(n==S_DELIM) + size--; + } + endfield(mp,n==S_DELIM||mp->quoted); + mp->patfound = 0; + if(n==S_DELIM) + while(size>0 && ((n=state[c= *(unsigned char*)cp++])==S_SPACE||n==S_NL)) + size--; + if(size<=0) + break; + cp--; + continue; + + } + stakputc(c); + } + if(mp->pattern) + { + cp = "&|()"; + while(c = *cp++) + { + if(state[c]==S_EPAT) + state[c] = 0; + } + cp = "*?[{"; + while(c = *cp++) + { + if(state[c]==S_PAT) + state[c] = 0; + } + if(sh.ifstable[ESCAPE]==S_ESC) + sh.ifstable[ESCAPE] = 0; + } + } + else + stakwrite(str,size); +} + +/* + * Terminate field. + * If field is null count field if <split> is non-zero + * Do filename expansion of required + */ +static void endfield(register Mac_t *mp,int split) +{ + register struct argnod *argp; + register int count=0; + if(staktell() > ARGVAL || split) + { + argp = (struct argnod*)stakfreeze(1); + argp->argnxt.cp = 0; + argp->argflag = 0; + if(mp->patfound) + { + sh.argaddr = 0; +#if SHOPT_BRACEPAT + count = path_generate(argp,mp->arghead); +#else + count = path_expand(argp->argval,mp->arghead); +#endif /* SHOPT_BRACEPAT */ + if(count) + mp->fields += count; + else if(split) /* pattern is null string */ + *argp->argval = 0; + else /* pattern expands to nothing */ + count = -1; + } + if(count==0) + { + argp->argchn.ap = *mp->arghead; + *mp->arghead = argp; + mp->fields++; + } + if(count>=0) + { + (*mp->arghead)->argflag |= ARG_MAKE; + if(mp->assign || sh_isoption(SH_NOGLOB)) + argp->argflag |= ARG_RAW|ARG_EXP; + } + stakseek(ARGVAL); + } + mp->quoted = mp->quote; +} + +/* + * Finds the right substring of STRING using the expression PAT + * the longest substring is found when FLAG is set. + */ +static int substring(register const char *string,const char *pat,int match[], int flag) +{ + register const char *sp=string; + register int size,len,nmatch,n; + int smatch[2*(MATCH_MAX+1)]; + if(flag) + { + if(n=strgrpmatch(sp,pat,smatch,elementsof(smatch)/2,STR_RIGHT|STR_MAXIMAL)) + { + memcpy(match,smatch,n*2*sizeof(smatch[0])); + return(n); + } + return(0); + } + size = len = strlen(sp); + sp += size; + while(sp>=string) + { +#if SHOPT_MULTIBYTE + if(mbwide()) + sp = lastchar(string,sp); +#endif /* SHOPT_MULTIBYTE */ + if(n=strgrpmatch(sp,pat,smatch,elementsof(smatch)/2,STR_RIGHT|STR_LEFT|STR_MAXIMAL)) + { + nmatch = n; + memcpy(match,smatch,n*2*sizeof(smatch[0])); + size = sp-string; + break; + } + sp--; + } + if(size==len) + return(0); + if(nmatch) + { + nmatch *=2; + while(--nmatch>=0) + match[nmatch] += size; + } + return(n); +} + +#if SHOPT_MULTIBYTE + static char *lastchar(const char *string, const char *endstring) + { + register char *str = (char*)string; + register int c; + mbinit(); + while(*str) + { + if((c=mbsize(str))<0) + c = 1; + if(str+c > endstring) + break; + str += c; + } + return(str); + } +#endif /* SHOPT_MULTIBYTE */ +static int charlen(const char *string,int len) +{ + if(!string) + return(0); +#if SHOPT_MULTIBYTE + if(mbwide()) + { + register const char *str = string, *strmax=string+len; + register int n=0; + mbinit(); + if(len>0) + { + while(str<strmax && mbchar(str)) + n++; + } + else while(mbchar(str)) + n++; + return(n); + } + else +#endif /* SHOPT_MULTIBYTE */ + { + if(len<0) + return(strlen(string)); + return(len); + } +} + +/* + * This is the default tilde discipline function + */ +static int sh_btilde(int argc, char *argv[], void *context) +{ + char *cp = sh_tilde(argv[1]); + NOT_USED(argc); + NOT_USED(context); + if(!cp) + cp = argv[1]; + sfputr(sfstdout, cp, '\n'); + return(0); +} + +/* + * <offset> is byte offset for beginning of tilde string + */ +static void tilde_expand2(register int offset) +{ + char shtilde[10], *av[3], *ptr=stakfreeze(1); + Sfio_t *iop, *save=sfstdout; + Namval_t *np; + static int beenhere=0; + strcpy(shtilde,".sh.tilde"); + np = nv_open(shtilde,sh.fun_tree, NV_VARNAME|NV_NOARRAY|NV_NOASSIGN|NV_NOFAIL); + if(np && !beenhere) + { + beenhere = 1; + sh_addbuiltin(shtilde,sh_btilde,0); + } + av[0] = ".sh.tilde"; + av[1] = &ptr[offset]; + av[2] = 0; + iop = sftmp(IOBSIZE+1);; + sfset(iop,SF_READ,0); + sfstdout = iop; + if(np) + sh_fun(np, (Namval_t*)0, av); + else + sh_btilde(2, av, &sh); + sfstdout = save; + stakset(ptr, offset); + sfseek(iop,(Sfoff_t)0,SEEK_SET); + sfset(iop,SF_READ,1); + if(ptr = sfreserve(iop, SF_UNBOUND, -1)) + { + Sfoff_t n = sfvalue(iop); + while(ptr[n-1]=='\n') + n--; + if(n==1 && fcpeek(0)=='/' && ptr[n-1]) + n--; + if(n) + stakwrite(ptr,n); + } + else + stakputs(av[1]); + sfclose(iop); +} + +/* + * This routine is used to resolve ~ expansion. + * A ~ by itself is replaced with the users login directory. + * A ~- is replaced by the previous working directory in shell. + * A ~+ is replaced by the present working directory in shell. + * If ~name is replaced with login directory of name. + * If string doesn't start with ~ or ~... not found then 0 returned. + */ + +static char *sh_tilde(register const char *string) +{ + register char *cp; + register int c; + register struct passwd *pw; + register Namval_t *np=0; + static Dt_t *logins_tree; + if(*string++!='~') + return(NIL(char*)); + if((c = *string)==0) + { + if(!(cp=nv_getval(nv_scoped(HOME)))) + cp = getlogin(); + return(cp); + } + if((c=='-' || c=='+') && string[1]==0) + { + if(c=='+') + cp = nv_getval(nv_scoped(PWDNOD)); + else + cp = nv_getval(nv_scoped(OLDPWDNOD)); + return(cp); + } + if(logins_tree && (np=nv_search(string,logins_tree,0))) + return(nv_getval(np)); + if(!(pw = getpwnam(string))) + return(NIL(char*)); + if(!logins_tree) + logins_tree = dtopen(&_Nvdisc,Dtbag); + if(np=nv_search(string,logins_tree,NV_ADD)) + nv_putval(np, pw->pw_dir,0); + return(pw->pw_dir); +} + +/* + * return values for special macros + */ +static char *special(register int c) +{ + register Namval_t *np; + if(c!='$') + sh.argaddr = 0; + switch(c) + { + case '@': + case '*': + return(sh.st.dolc>0?sh.st.dolv[1]:NIL(char*)); + case '#': +#if SHOPT_FILESCAN + if(sh.cur_line) + { + getdolarg(&sh,MAX_ARGN,(int*)0); + return(ltos(sh.offsets[0])); + } +#endif /* SHOPT_FILESCAN */ + return(ltos(sh.st.dolc)); + case '!': + if(sh.bckpid) + return(ltos(sh.bckpid)); + break; + case '$': + if(nv_isnull(SH_DOLLARNOD)) + return(ltos(sh.pid)); + return(nv_getval(SH_DOLLARNOD)); + case '-': + return(sh_argdolminus()); + case '?': + return(ltos(sh.savexit)); + case 0: + if(sh_isstate(SH_PROFILE) || !error_info.id || ((np=nv_search(error_info.id,sh.bltin_tree,0)) && nv_isattr(np,BLT_SPC))) + return(sh.shname); + else + return(error_info.id); + } + return(NIL(char*)); +} + +/* + * Handle macro expansion errors + */ +static void mac_error(Namval_t *np) +{ + if(np) + nv_close(np); + errormsg(SH_DICT,ERROR_exit(1),e_subst,fcfirst()); +} + +/* + * Given pattern/string, replace / with 0 and return pointer to string + * \ characters are stripped from string. + */ +static char *mac_getstring(char *pattern) +{ + register char *cp = pattern; + register int c; + while(c = *cp++) + { + if(c==ESCAPE) + cp++; + else if(c=='/') + { + cp[-1] = 0; + return(cp); + } + } + return(NIL(char*)); +} diff --git a/usr/src/lib/libshell/common/sh/main.c b/usr/src/lib/libshell/common/sh/main.c new file mode 100644 index 0000000000..0b2f4ac875 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/main.c @@ -0,0 +1,769 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * UNIX shell + * + * S. R. Bourne + * Rewritten By David Korn + * AT&T Labs + * + */ + +#include <ast.h> +#include <sfio.h> +#include <stak.h> +#include <ls.h> +#include <fcin.h> +#include "defs.h" +#include "variables.h" +#include "path.h" +#include "io.h" +#include "jobs.h" +#include "shnodes.h" +#include "history.h" +#include "timeout.h" +#include "FEATURE/time" +#include "FEATURE/pstat" +#include "FEATURE/execargs" +#include "FEATURE/externs" +#ifdef _hdr_nc +# include <nc.h> +#endif /* _hdr_nc */ + +#define CMD_LENGTH 64 + +/* These routines are referenced by this module */ +static void exfile(Shell_t*, Sfio_t*,int); +static void chkmail(Shell_t *shp, char*); +#if defined(_lib_fork) && !defined(_NEXT_SOURCE) + static void fixargs(char**,int); +#else +# define fixargs(a,b) +#endif + +#ifndef environ + extern char **environ; +#endif + +static struct stat lastmail; +static time_t mailtime; +static char beenhere = 0; + +#ifdef _lib_sigvec + void clearsigmask(register int sig) + { + struct sigvec vec; + if(sigvec(sig,NIL(struct sigvec*),&vec)>=0 && vec.sv_mask) + { + vec.sv_mask = 0; + sigvec(sig,&vec,NIL(struct sigvec*)); + } + } +#endif /* _lib_sigvec */ + +#ifdef _lib_fts_notify +# include <fts.h> + /* check for interrupts during tree walks */ + static int fts_sigcheck(FTS* fp, FTSENT* ep, void* context) + { + Shell_t *shp = (Shell_t*)context; + NOT_USED(fp); + NOT_USED(ep); + if(shp->trapnote&SH_SIGSET) + { + errno = EINTR; + return(-1); + } + return(0); + } +#endif /* _lib_fts_notify */ + +#ifdef PATH_BFPATH +#define PATHCOMP NIL(Pathcomp_t*) +#else +#define PATHCOMP "" +#endif + +/* + * search for file and exfile() it if it exists + * 1 returned if file found, 0 otherwise + */ + +int sh_source(Shell_t *shp, Sfio_t *iop, const char *file) +{ + char* oid; + char* nid; + int fd; + + if (!file || !*file || (fd = path_open(file, PATHCOMP)) < 0) + return 0; + oid = error_info.id; + nid = error_info.id = strdup(file); + shp->st.filename = path_fullname(stakptr(PATH_OFFSET)); + exfile(shp, iop, fd); + error_info.id = oid; + free(nid); + return 1; +} + +#ifdef S_ISSOCK +#define REMOTE(m) (S_ISSOCK(m)||!(m)) +#else +#define REMOTE(m) !(m) +#endif + +int sh_main(int ac, char *av[], void (*userinit)(int)) +{ + register char *name; + register int fdin; + register Sfio_t *iop; + register Shell_t *shp; + struct stat statb; + int i, rshflag; /* set for restricted shell */ + char *command; +#ifdef _lib_sigvec + /* This is to clear mask that my be left on by rlogin */ + clearsigmask(SIGALRM); + clearsigmask(SIGHUP); + clearsigmask(SIGCHLD); +#endif /* _lib_sigvec */ +#ifdef _hdr_nc + _NutConf(_NC_SET_SUFFIXED_SEARCHING, 1); +#endif /* _hdr_nc */ + fixargs(av,0); + shp = sh_init(ac,av,userinit); + time(&mailtime); + if(rshflag=sh_isoption(SH_RESTRICTED)) + sh_offoption(SH_RESTRICTED); +#ifdef _lib_fts_notify + fts_notify(fts_sigcheck,(void*)shp); +#endif /* _lib_fts_notify */ + if(sigsetjmp(*((sigjmp_buf*)shp->jmpbuffer),0)) + { + /* begin script execution here */ + sh_reinit((char**)0); + if(rshflag) + sh_onoption(SH_RESTRICTED); + } + shp->fn_depth = shp->dot_depth = 0; + command = error_info.id; + /* set pidname '$$' */ + shp->pid = getpid(); + srand(shp->pid&0x7fff); + shp->ppid = getppid(); + if(nv_isnull(PS4NOD)) + nv_putval(PS4NOD,e_traceprompt,NV_RDONLY); + path_pwd(1); + iop = (Sfio_t*)0; +#if SHOPT_BRACEPAT + sh_onoption(SH_BRACEEXPAND); +#endif + if((beenhere++)==0) + { + sh_onstate(SH_PROFILE); + if(shp->ppid==1) + shp->login_sh++; + if(shp->login_sh >= 2) + sh_onoption(SH_LOGIN_SHELL); + /* decide whether shell is interactive */ + if(!sh_isoption(SH_TFLAG) && !sh_isoption(SH_CFLAG) && sh_isoption(SH_SFLAG) && + tty_check(0) && tty_check(ERRIO)) + { + sh_onoption(SH_INTERACTIVE); + sh_onoption(SH_BGNICE); + sh_onoption(SH_RC); + } + if(!sh_isoption(SH_RC) && (sh_isoption(SH_BASH) && !sh_isoption(SH_POSIX) +#if SHOPT_REMOTE + || !fstat(0, &statb) && REMOTE(statb.st_mode) +#endif + )) + sh_onoption(SH_RC); + for(i=0; i<elementsof(sh.offoptions.v); i++) + sh.options.v[i] &= ~sh.offoptions.v[i]; + if(sh_isoption(SH_INTERACTIVE)) + { +#ifdef SIGXCPU + signal(SIGXCPU,SIG_DFL); +#endif /* SIGXCPU */ +#ifdef SIGXFSZ + signal(SIGXFSZ,SIG_DFL); +#endif /* SIGXFSZ */ + sh_onoption(SH_MONITOR); + } + job_init(sh_isoption(SH_LOGIN_SHELL)); + if(sh_isoption(SH_LOGIN_SHELL) && !sh_isoption(SH_NOPROFILE)) + { + /* system profile */ + sh_source(shp, iop, e_sysprofile); + if(!sh_isoption(SH_NOUSRPROFILE) && !sh_isoption(SH_PRIVILEGED)) + { + char **files = shp->login_files; + while ((name = *files++) && !sh_source(shp, iop, sh_mactry(name))); + } + } + /* make sure PWD is set up correctly */ + path_pwd(1); + if(!sh_isoption(SH_NOEXEC)) + { + if(!sh_isoption(SH_NOUSRPROFILE) && !sh_isoption(SH_PRIVILEGED) && sh_isoption(SH_RC)) + { +#if SHOPT_BASH + if(sh_isoption(SH_BASH) && !sh_isoption(SH_POSIX)) + { +#if SHOPT_SYSRC + sh_source(shp, iop, e_bash_sysrc); +#endif + sh_source(shp, iop, shp->rcfile ? shp->rcfile : sh_mactry((char*)e_bash_rc)); + } + else +#endif + { +#if SHOPT_SYSRC + sh_source(shp, iop, e_sysrc); +#endif + sh_source(shp, iop, sh_mactry(nv_getval(ENVNOD))); + } + } + else if(sh_isoption(SH_INTERACTIVE) && sh_isoption(SH_PRIVILEGED)) + sh_source(shp, iop, e_suidprofile); + } + shp->st.cmdname = error_info.id = command; + sh_offstate(SH_PROFILE); + if(rshflag) + sh_onoption(SH_RESTRICTED); + /* open input file if specified */ + if(shp->comdiv) + { + shell_c: + iop = sfnew(NIL(Sfio_t*),shp->comdiv,strlen(shp->comdiv),0,SF_STRING|SF_READ); + } + else + { + name = error_info.id; + error_info.id = shp->shname; + if(sh_isoption(SH_SFLAG)) + fdin = 0; + else + { + char *sp; + /* open stream should have been passed into shell */ + if(strmatch(name,e_devfdNN)) + { + char *cp; + int type; + fdin = (int)strtol(name+8, (char**)0, 10); + if(fstat(fdin,&statb)<0) + errormsg(SH_DICT,ERROR_system(1),e_open,error_info.id); +#if !_WINIX + /* + * try to undo effect of solaris 2.5+ + * change for argv for setuid scripts + */ + if(((type = sh_type(cp = av[0])) & SH_TYPE_SH) && (!(name = nv_getval(L_ARGNOD)) || !((type = sh_type(cp = name)) & SH_TYPE_SH))) + { + av[0] = (type & SH_TYPE_LOGIN) ? cp : path_basename(cp); + /* exec to change $0 for ps */ + execv(pathshell(),av); + /* exec fails */ + shp->st.dolv[0] = av[0]; + fixargs(shp->st.dolv,1); + } +#endif + name = av[0]; + sh_offoption(SH_VERBOSE); + sh_offoption(SH_XTRACE); + } + else + { + int isdir = 0; + if((fdin=sh_open(name,O_RDONLY,0))>=0 &&(fstat(fdin,&statb)<0 || S_ISDIR(statb.st_mode))) + { + close(fdin); + isdir = 1; + fdin = -1; + } + else + shp->st.filename = path_fullname(name); + sp = 0; + if(fdin < 0 && !strchr(name,'/')) + { +#ifdef PATH_BFPATH + if(path_absolute(name,NIL(Pathcomp_t*))) + sp = stakptr(PATH_OFFSET); +#else + sp = path_absolute(name,NIL(char*)); +#endif + if(sp) + { + if((fdin=sh_open(sp,O_RDONLY,0))>=0) + shp->st.filename = path_fullname(sp); + } + } + if(fdin<0) + { + if(isdir) + errno = EISDIR; + error_info.id = av[0]; + if(sp || errno!=ENOENT) + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_open,name); + /* try sh -c 'name "$@"' */ + sh_onoption(SH_CFLAG); + shp->comdiv = (char*)malloc(strlen(name)+7); + name = strcopy(shp->comdiv,name); + if(shp->st.dolc) + strcopy(name," \"$@\""); + goto shell_c; + } + if(fdin==0) + fdin = sh_iomovefd(fdin); + } + shp->readscript = shp->shname; + } + error_info.id = name; + shp->comdiv--; +#if SHOPT_ACCT + sh_accinit(); + if(fdin != 0) + sh_accbegin(error_info.id); +#endif /* SHOPT_ACCT */ + } + } + else + { + fdin = shp->infd; + fixargs(shp->st.dolv,1); + } + if(sh_isoption(SH_INTERACTIVE)) + sh_onstate(SH_INTERACTIVE); + nv_putval(IFSNOD,(char*)e_sptbnl,NV_RDONLY); + exfile(shp,iop,fdin); + sh_done(0); + /* NOTREACHED */ + return(0); +} + +/* + * iop is not null when the input is a string + * fdin is the input file descriptor + */ + +static void exfile(register Shell_t *shp, register Sfio_t *iop,register int fno) +{ + time_t curtime; + Shnode_t *t; + int maxtry=IOMAXTRY, tdone=0, execflags; + int states,jmpval; + struct checkpt buff; + sh_pushcontext(&buff,SH_JMPERREXIT); + /* open input stream */ + nv_putval(SH_PATHNAMENOD, shp->st.filename ,NV_NOFREE); + if(!iop) + { + if(fno > 0) + { + int r; + if(fno < 10 && ((r=sh_fcntl(fno,F_DUPFD,10))>=10)) + { + shp->fdstatus[r] = shp->fdstatus[fno]; + sh_close(fno); + fno = r; + } + fcntl(fno,F_SETFD,FD_CLOEXEC); + shp->fdstatus[fno] |= IOCLEX; + iop = sh_iostream(fno); + } + else + iop = sfstdin; + } + else + fno = -1; + shp->infd = fno; + if(sh_isstate(SH_INTERACTIVE)) + { + if(nv_isnull(PS1NOD)) + nv_putval(PS1NOD,(shp->euserid?e_stdprompt:e_supprompt),NV_RDONLY); + sh_sigdone(); + if(sh_histinit()) + sh_onoption(SH_HISTORY); + } + else + { + if(!sh_isstate(SH_PROFILE)) + { + buff.mode = SH_JMPEXIT; + sh_onoption(SH_TRACKALL); + sh_offoption(SH_MONITOR); + } + sh_offstate(SH_INTERACTIVE); + sh_offstate(SH_MONITOR); + sh_offstate(SH_HISTORY); + sh_offoption(SH_HISTORY); + } + states = sh_getstate(); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval) + { + Sfio_t *top; + sh_iorestore(0,jmpval); + hist_flush(shp->hist_ptr); + sfsync(shp->outpool); + shp->st.execbrk = shp->st.breakcnt = 0; + /* check for return from profile or env file */ + if(sh_isstate(SH_PROFILE) && (jmpval==SH_JMPFUN || jmpval==SH_JMPEXIT)) + goto done; + if(!sh_isoption(SH_INTERACTIVE) || sh_isstate(SH_FORKED) || (jmpval > SH_JMPERREXIT && job_close() >=0)) + { + sh_offstate(SH_INTERACTIVE); + sh_offstate(SH_MONITOR); + goto done; + } + /* skip over remaining input */ + if(top = fcfile()) + { + while(fcget()>0); + fcclose(); + while(top=sfstack(iop,SF_POPSTACK)) + sfclose(top); + } + /* make sure that we own the terminal */ +#ifdef SIGTSTP + tcsetpgrp(job.fd,shp->pid); +#endif /* SIGTSTP */ + } + /* error return here */ + sfclrerr(iop); + sh_setstate(states); + shp->st.optindex = 1; + opt_info.offset = 0; + shp->st.loopcnt = 0; + shp->trapnote = 0; + shp->intrap = 0; + error_info.line = 1; + shp->inlineno = 1; + shp->binscript = 0; + if(sfeof(iop)) + goto eof_or_error; + /* command loop */ + while(1) + { + shp->nextprompt = 1; + sh_freeup(); + stakset(NIL(char*),0); + exitset(); + sh_offstate(SH_STOPOK); + sh_offstate(SH_ERREXIT); + sh_offstate(SH_VERBOSE); + sh_offstate(SH_TIMING); + sh_offstate(SH_GRACE); + sh_offstate(SH_TTYWAIT); + if(sh_isoption(SH_VERBOSE)) + sh_onstate(SH_VERBOSE); + sh_onstate(SH_ERREXIT); + /* -eim flags don't apply to profiles */ + if(sh_isstate(SH_PROFILE)) + { + sh_offstate(SH_INTERACTIVE); + sh_offstate(SH_ERREXIT); + sh_offstate(SH_MONITOR); + } + if(sh_isstate(SH_INTERACTIVE) && !tdone) + { + register char *mail; +#ifdef JOBS + sh_offstate(SH_MONITOR); + if(sh_isoption(SH_MONITOR)) + sh_onstate(SH_MONITOR); + if(job.pwlist) + { + job_walk(sfstderr,job_list,JOB_NFLAG,(char**)0); + job_wait((pid_t)0); + } +#endif /* JOBS */ + if((mail=nv_getval(MAILPNOD)) || (mail=nv_getval(MAILNOD))) + { + time(&curtime); + if ((curtime - mailtime) >= sh_mailchk) + { + chkmail(shp,mail); + mailtime = curtime; + } + } + if(shp->hist_ptr) + hist_eof(shp->hist_ptr); + /* sets timeout for command entry */ + shp->timeout = shp->st.tmout; +#if SHOPT_TIMEOUT + if(shp->timeout <= 0 || shp->timeout > SHOPT_TIMEOUT) + shp->timeout = SHOPT_TIMEOUT; +#endif /* SHOPT_TIMEOUT */ + shp->inlineno = 1; + error_info.line = 1; + shp->exitval = 0; + shp->trapnote = 0; + if(buff.mode == SH_JMPEXIT) + { + buff.mode = SH_JMPERREXIT; +#ifdef DEBUG + errormsg(SH_DICT,ERROR_warn(0),"%d: mode changed to JMP_EXIT",getpid()); +#endif + } + } + errno = 0; + if(tdone || !sfreserve(iop,0,0)) + { + eof_or_error: + if(sh_isstate(SH_INTERACTIVE) && !sferror(iop)) + { + if(--maxtry>0 && sh_isoption(SH_IGNOREEOF) && + !sferror(sfstderr) && (shp->fdstatus[fno]&IOTTY)) + { + sfclrerr(iop); + errormsg(SH_DICT,0,e_logout); + continue; + } + else if(job_close()<0) + continue; + } + if(errno==0 && sferror(iop) && --maxtry>0) + { + sfclrlock(iop); + sfclrerr(iop); + continue; + } + goto done; + } + maxtry = IOMAXTRY; + if(sh_isstate(SH_INTERACTIVE) && shp->hist_ptr) + { + job_wait((pid_t)0); + hist_eof(shp->hist_ptr); + sfsync(sfstderr); + } + if(sh_isoption(SH_HISTORY)) + sh_onstate(SH_HISTORY); + job.waitall = job.curpgid = 0; + error_info.flags |= ERROR_INTERACTIVE; + t = (Shnode_t*)sh_parse(shp,iop,0); + if(!sh_isstate(SH_INTERACTIVE) && !sh_isstate(SH_CFLAG)) + error_info.flags &= ~ERROR_INTERACTIVE; + shp->readscript = 0; + if(sh_isstate(SH_INTERACTIVE) && shp->hist_ptr) + hist_flush(shp->hist_ptr); + sh_offstate(SH_HISTORY); + if(t) + { + execflags = sh_state(SH_ERREXIT)|sh_state(SH_INTERACTIVE); + /* The last command may not have to fork */ + if(!sh_isstate(SH_PROFILE) && !sh_isstate(SH_INTERACTIVE) && + (fno<0 || !(shp->fdstatus[fno]&(IOTTY|IONOSEEK))) + && !sfreserve(iop,0,0)) + { + execflags |= sh_state(SH_NOFORK); + } + shp->st.execbrk = 0; + sh_exec(t,execflags); + if(shp->forked) + { + sh_offstate(SH_INTERACTIVE); + goto done; + } + /* This is for sh -t */ + if(sh_isoption(SH_TFLAG) && !sh_isstate(SH_PROFILE)) + tdone++; + } + } +done: + sh_popcontext(&buff); + if(sh_isstate(SH_INTERACTIVE)) + { + sfputc(sfstderr,'\n'); + job_close(); + } + if(jmpval == SH_JMPSCRIPT) + siglongjmp(*shp->jmplist,jmpval); + else if(jmpval == SH_JMPEXIT) + sh_done(0); + if(fno>0) + sh_close(fno); + if(shp->st.filename) + free((void*)shp->st.filename); + shp->st.filename = 0; +} + + +/* prints out messages if files in list have been modified since last call */ +static void chkmail(Shell_t *shp, char *files) +{ + register char *cp,*sp,*qp; + register char save; + struct argnod *arglist=0; + int offset = staktell(); + char *savstak=stakptr(0); + struct stat statb; + if(*(cp=files) == 0) + return; + sp = cp; + do + { + /* skip to : or end of string saving first '?' */ + for(qp=0;*sp && *sp != ':';sp++) + if((*sp == '?' || *sp=='%') && qp == 0) + qp = sp; + save = *sp; + *sp = 0; + /* change '?' to end-of-string */ + if(qp) + *qp = 0; + do + { + /* see if time has been modified since last checked + * and the access time <= the modification time + */ + if(stat(cp,&statb) >= 0 && statb.st_mtime >= mailtime + && statb.st_atime <= statb.st_mtime) + { + /* check for directory */ + if(!arglist && S_ISDIR(statb.st_mode)) + { + /* generate list of directory entries */ + path_complete(cp,"/*",&arglist); + } + else + { + /* + * If the file has shrunk, + * or if the size is zero + * then don't print anything + */ + if(statb.st_size && + ( statb.st_ino != lastmail.st_ino + || statb.st_dev != lastmail.st_dev + || statb.st_size > lastmail.st_size)) + { + /* save and restore $_ */ + char *save = shp->lastarg; + shp->lastarg = cp; + errormsg(SH_DICT,0,sh_mactry(qp?qp+1:(char*)e_mailmsg)); + shp->lastarg = save; + } + lastmail = statb; + break; + } + } + if(arglist) + { + cp = arglist->argval; + arglist = arglist->argchn.ap; + } + else + cp = 0; + } + while(cp); + if(qp) + *qp = '?'; + *sp++ = save; + cp = sp; + } + while(save); + stakset(savstak,offset); +} + +#undef EXECARGS +#undef PSTAT +#if defined(_hdr_execargs) && defined(pdp11) +# include <execargs.h> +# define EXECARGS 1 +#endif + +#if defined(_lib_pstat) && defined(_sys_pstat) +# include <sys/pstat.h> +# define PSTAT 1 +#endif + +#if defined(_lib_fork) && !defined(_NEXT_SOURCE) +/* + * fix up command line for ps command + * mode is 0 for initialization + */ +static void fixargs(char **argv, int mode) +{ +#if EXECARGS + *execargs=(char *)argv; +#else + static char *buff; + static int command_len; + register char *cp; + int offset=0,size; +# ifdef PSTAT + union pstun un; + if(mode==0) + { + struct pst_static st; + un.pst_static = &st; + if(pstat(PSTAT_STATIC, un, sizeof(struct pst_static), 1, 0)<0) + return; + command_len = st.command_length; + return; + } + stakseek(command_len+2); + buff = stakseek(0); +# else + if(mode==0) + { + buff = argv[0]; + while(cp = *argv++) + command_len += strlen(cp)+1; + if(environ && *environ==buff+command_len) + { + for(argv=environ; cp = *argv; cp++) + { + if(command_len > CMD_LENGTH) + { + command_len = CMD_LENGTH; + break; + } + *argv++ = strdup(cp); + command_len += strlen(cp)+1; + } + } + command_len -= 1; + return; + } +# endif /* PSTAT */ + if(command_len==0) + return; + while((cp = *argv++) && offset < command_len) + { + if(offset + (size=strlen(cp)) >= command_len) + size = command_len - offset; + memcpy(buff+offset,cp,size); + offset += size; + buff[offset++] = ' '; + } + buff[offset-1] = 0; +# ifdef PSTAT + un.pst_command = stakptr(0); + pstat(PSTAT_SETCMD,un,0,0,0); +# endif /* PSTAT */ +#endif /* EXECARGS */ +} +#endif /* _lib_fork */ diff --git a/usr/src/lib/libshell/common/sh/name.c b/usr/src/lib/libshell/common/sh/name.c new file mode 100644 index 0000000000..b9d5cbe9eb --- /dev/null +++ b/usr/src/lib/libshell/common/sh/name.c @@ -0,0 +1,2382 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * AT&T Labs + * + */ + +#define putenv ___putenv + +#include "defs.h" +#include <ctype.h> +#include "variables.h" +#include "path.h" +#include "lexstates.h" +#include "timeout.h" +#include "FEATURE/externs" +#include "streval.h" + +static char *savesub = 0; + +#if !_lib_pathnative && _lib_uwin_path + +#define _lib_pathnative 1 + +extern int uwin_path(const char*, char*, int); + +size_t +pathnative(const char* path, char* buf, size_t siz) +{ + return uwin_path(path, buf, siz); +} + +#endif /* _lib_pathnative */ + +static void attstore(Namval_t*,void*); +#ifndef _ENV_H +static void pushnam(Namval_t*,void*); +static char *staknam(Namval_t*, char*); +#endif +static void ltou(const char*,char*); +static void rightjust(char*, int, int); + +struct adata +{ + char **argnam; + int attsize; + char *attval; +}; + +char nv_local = 0; +#ifndef _ENV_H +static void(*nullscan)(Namval_t*,void*); +#endif + +#if ( SFIO_VERSION <= 20010201L ) +# define _data data +#endif + +#if !SHOPT_MULTIBYTE +# define mbchar(p) (*(unsigned char*)p++) +#endif /* SHOPT_MULTIBYTE */ + +/* ======== name value pair routines ======== */ + +#include "shnodes.h" +#include "builtins.h" + +static char *getbuf(size_t len) +{ + static char *buf; + static size_t buflen; + if(buflen < len) + { + if(buflen==0) + buf = (char*)malloc(len); + else + buf = (char*)realloc(buf,len); + buflen = len; + } + return(buf); +} + +#ifdef _ENV_H +void sh_envput(Env_t* ep,Namval_t *np) +{ + int offset = staktell(); + Namarr_t *ap = nv_arrayptr(np); + char *val; + if(ap) + { + if(ap->nelem&ARRAY_UNDEF) + nv_putsub(np,"0",0L); + else if(!(val=nv_getsub(np)) || strcmp(val,"0")) + return; + } + if(!(val = nv_getval(np))) + return; + stakputs(nv_name(np)); + stakputc('='); + stakputs(val); + stakseek(offset); + env_add(ep,stakptr(offset),ENV_STRDUP); +} +#endif + +/* + * output variable name in format for re-input + */ +void nv_outname(Sfio_t *out, char *name, int len) +{ + const char *cp=name, *sp; + int c, offset = staktell(); + while(sp= strchr(cp,'[')) + { + if(len>0 && cp+len <= sp) + break; + sfwrite(out,cp,++sp-cp); + stakseek(offset); + for(; c= *sp; sp++) + { + if(c==']') + break; + else if(c=='\\') + { + if(*sp=='[' || *sp==']' || *sp=='\\') + c = *sp++; + } + stakputc(c); + } + stakputc(0); + sfputr(out,sh_fmtq(stakptr(offset)),-1); + if(len>0) + { + sfputc(out,']'); + return; + } + cp = sp; + } + if(*cp) + { + if(len>0) + sfwrite(out,cp,len); + else + sfputr(out,cp,-1); + } + stakseek(offset); +} + +/* + * Perform parameter assignment for a linked list of parameters + * <flags> contains attributes for the parameters + */ +void nv_setlist(register struct argnod *arg,register int flags) +{ + register char *cp; + register Namval_t *np; + char *trap=sh.st.trap[SH_DEBUGTRAP]; + int traceon = (sh_isoption(SH_XTRACE)!=0); + int array = (flags&(NV_ARRAY|NV_IARRAY)); + flags &= ~(NV_TYPE|NV_ARRAY); + if(sh_isoption(SH_ALLEXPORT)) + flags |= NV_EXPORT; + if(sh.prefix) + { + flags &= ~(NV_IDENT|NV_EXPORT); + flags |= NV_VARNAME; + } + for(;arg; arg=arg->argnxt.ap) + { + sh.used_pos = 0; + if(arg->argflag&ARG_MAC) + cp = sh_mactrim(arg->argval,-1); + else + { + Namval_t *mp; + stakseek(0); + if(*arg->argval==0 && arg->argchn.ap && !(arg->argflag&~(ARG_APPEND|ARG_QUOTED))) + { + int flag = (NV_VARNAME|NV_ARRAY|NV_ASSIGN); + struct fornod *fp=(struct fornod*)arg->argchn.ap; + register Shnode_t *tp=fp->fortre; + char *prefix = sh.prefix; + flag |= (flags&NV_NOSCOPE); + if(arg->argflag&ARG_QUOTED) + cp = sh_mactrim(fp->fornam,-1); + else + cp = fp->fornam; + error_info.line = fp->fortyp-sh.st.firstline; + if(sh.fn_depth && (Namval_t*)tp->com.comnamp==SYSTYPESET) + flag |= NV_NOSCOPE; + if(prefix && tp->com.comset && *cp=='[') + { + sh.prefix = 0; + np = nv_open(prefix,sh.var_tree,flag); + sh.prefix = prefix; + if(np) + { + if(!nv_isarray(np)) + { + stakputc('.'); + stakputs(cp); + cp = stakfreeze(1); + } + nv_close(np); + } + } + np = nv_open(cp,sh.var_tree,flag); + if(array) + { + if(!(flags&NV_APPEND)) + nv_unset(np); + if(array&NV_ARRAY) + { + nv_setarray(np,nv_associative); + } + else + { + nv_onattr(np,NV_ARRAY); + } + } + /* check for array assignment */ + if(tp->tre.tretyp!=TLST && tp->com.comarg && !tp->com.comset && !((mp=tp->com.comnamp) && nv_isattr(mp,BLT_DCL))) + { + int argc; + char **argv = sh_argbuild(&argc,&tp->com,0); + if(!(arg->argflag&ARG_APPEND)) + { + nv_unset(np); + } + nv_setvec(np,(arg->argflag&ARG_APPEND),argc,argv); + if(traceon || trap) + { + int n = -1; + char *name = nv_name(np); + if(arg->argflag&ARG_APPEND) + n = '+'; + if(trap) + sh_debug(trap,name,(char*)0,argv,(arg->argflag&ARG_APPEND)|ARG_ASSIGN); + if(traceon) + { + sh_trace(NIL(char**),0); + sfputr(sfstderr,name,n); + sfwrite(sfstderr,"=( ",3); + while(cp= *argv++) + sfputr(sfstderr,sh_fmtq(cp),' '); + sfwrite(sfstderr,")\n",2); + } + } + continue; + } + if(tp->tre.tretyp==TLST || !tp->com.comset || tp->com.comset->argval[0]!='[') + { + if(*cp!='.' && *cp!='[' && strchr(cp,'[')) + { + nv_close(np); + np = nv_open(cp,sh.var_tree,flag); + } + if((arg->argflag&ARG_APPEND) && !nv_isarray(np)) + nv_unset(np); + } + else + { + if(sh_isoption(SH_BASH) || (array&NV_IARRAY)) + { + if(!(arg->argflag&ARG_APPEND)) + nv_unset(np); + } + else if((arg->argflag&ARG_APPEND) && (!nv_isarray(np) || (nv_aindex(np)>=0))) + { + nv_unset(np); + nv_setarray(np,nv_associative); + } + else + nv_setarray(np,nv_associative); + } + if(prefix) + cp = stakcopy(nv_name(np)); + sh.prefix = cp; + sh_exec(tp,sh_isstate(SH_ERREXIT)); + sh.prefix = prefix; + if(nv_isarray(np) && (mp=nv_opensub(np))) + np = mp; + nv_setvtree(np); + continue; + } + cp = arg->argval; + } + if(sh.prefix && *cp=='.' && cp[1]=='=') + cp++; + np = nv_open(cp,sh.var_tree,flags); + if(!np->nvfun) + { + if(sh.used_pos) + nv_onattr(np,NV_PARAM); + else + nv_offattr(np,NV_PARAM); + } + if(traceon || trap) + { + register char *sp=cp; + char *name=nv_name(np); + char *sub=0; + int append = 0; + if(nv_isarray(np)) + sub = savesub; + if(cp=strchr(sp,'=')) + { + if(cp[-1]=='+') + append = ARG_APPEND; + cp++; + } + if(traceon) + { + sh_trace(NIL(char**),0); + nv_outname(sfstderr,name,-1); + if(sub) + sfprintf(sfstderr,"[%s]",sh_fmtq(sub)); + if(cp) + { + if(append) + sfputc(sfstderr,'+'); + sfprintf(sfstderr,"=%s\n",sh_fmtq(cp)); + } + } + if(trap) + { + char *av[2]; + av[0] = cp; + av[1] = 0; + sh_debug(trap,name,sub,av,append); + } + } + } +} + +/* + * copy the subscript onto the stack + */ +static void stak_subscript(const char *sub, int last) +{ + register int c; + stakputc('['); + while(c= *sub++) + { + if(c=='[' || c==']' || c=='\\') + stakputc('\\'); + stakputc(c); + } + stakputc(last); +} + +/* + * construct a new name from a prefix and base name on the stack + */ +static char *copystack(const char *prefix, register const char *name, const char *sub) +{ + register int last=0,offset = staktell(); + if(prefix) + { + stakputs(prefix); + if(*stakptr(staktell()-1)=='.') + stakseek(staktell()-1); + if(*name=='.' && name[1]=='[') + last = staktell()+2; + if(*name!='[' && *name!='.' && *name!='=' && *name!='+') + stakputc('.'); + } + if(last) + { + stakputs(name); + if(sh_checkid(stakptr(last),(char*)0)) + stakseek(staktell()-2); + } + if(sub) + stak_subscript(sub,']'); + if(!last) + stakputs(name); + stakputc(0); + return(stakptr(offset)); +} + +/* + * grow this stack string <name> by <n> bytes and move from cp-1 to end + * right by <n>. Returns beginning of string on the stack + */ +static char *stack_extend(const char *cname, char *cp, int n) +{ + register char *name = (char*)cname; + int offset = name - stakptr(0); + int m = cp-name; + stakseek(strlen(name)+n+1); + name = stakptr(offset); + cp = name + m; + m = strlen(cp)+1; + while(m-->0) + cp[n+m]=cp[m]; + return((char*)name); +} + +Namval_t *nv_create(const char *name, Dt_t *root, int flags, Namfun_t *dp) +{ + char *cp=(char*)name, *sp, *xp; + register int c; + register Namval_t *np=0, *nq=0; + Namfun_t *fp=0; + long mode, add=0; + int copy=1,isref,top=0,noscope=(flags&NV_NOSCOPE); + if(root==sh.var_tree) + { + if(dtvnext(root)) + top = 1; + else + flags &= ~NV_NOSCOPE; + } + if(!dp->disc) + copy = dp->nofree; + if(*cp=='.') + cp++; + while(1) + { + switch(c = *(unsigned char*)(sp = cp)) + { + case '[': + if(flags&NV_NOARRAY) + { + dp->last = cp; + return(np); + } + cp = nv_endsubscript((Namval_t*)0,sp,0); + if(sp==name || sp[-1]=='.') + c = *(sp = cp); + goto skip; + case '.': + if(flags&NV_IDENT) + return(0); + if(root==sh.var_tree) + flags &= ~NV_EXPORT; + if(!copy && !(flags&NV_NOREF)) + { + c = sp-name; + copy = cp-name; + dp->nofree = 1; + name = copystack((const char*)0, name,(const char*)0); + cp = (char*)name+copy; + sp = (char*)name+c; + c = '.'; + } + skip: + case '+': + case '=': + *sp = 0; + case 0: + isref = 0; + dp->last = cp; + mode = (c=='.' || (flags&NV_NOADD))?add:NV_ADD; + if(flags&NV_NOSCOPE) + mode |= HASH_NOSCOPE; + if(top) + nq = nv_search(name,sh.var_base,0); + if(np = nv_search(name,root,mode)) + { + isref = nv_isref(np); + if(top) + { + if(nq==np) + flags &= ~NV_NOSCOPE; + else if(nq) + { + if(nv_isnull(np) && c!='.' && (np->nvfun=nv_cover(nq))) + np->nvname = nq->nvname; + flags |= NV_NOSCOPE; + } + } + else if(add && nv_isnull(np) && c=='.') + nv_setvtree(np); + } + if(c) + *sp = c; + top = 0; + if(isref) + { + char *sub=0; + if(c=='.') /* don't optimize */ + sh.argaddr = 0; + else if(flags&NV_NOREF) + { + if(c) + nv_unref(np); + return(np); + } + while(nv_isref(np)) + { + root = nv_reftree(np); + sh.last_table = nv_reftable(np); + sub = nv_refsub(np); + np = nv_refnode(np); + if(sub && c!='.') + nv_putsub(np,sub,0L); + flags |= NV_NOSCOPE; + } + if(sub && c==0) + return(np); + if(np==nq) + flags &= ~(noscope?0:NV_NOSCOPE); + else if(c) + { + c = (cp-sp); + copy = strlen(cp=nv_name(np)); + dp->nofree = 1; + name = copystack(cp,sp,sub); + sp = (char*)name + copy; + cp = sp+c; + c = *sp; + if(!noscope) + flags &= ~NV_NOSCOPE; + } + flags |= NV_NOREF; + } + sh.last_root = root; + do + { + if(!np) + { + if(*sp=='[' && *cp==0 && cp[-1]==']') + { + /* + * for backward compatibility + * evaluate subscript for + * possible side effects + */ + cp[-1] = 0; + sh_arith(sp+1); + cp[-1] = ']'; + } + return(np); + } + if(c=='[' || (c=='.' && nv_isarray(np))) + { + int n = 0; + if(c=='[') + { + n = mode|nv_isarray(np); + if(!mode && (flags&NV_ARRAY) && ((c=sp[1])=='*' || c=='@') && sp[2]==']') + { + /* not implemented yet */ + dp->last = cp; + return(np); + } + if(n&&(flags&NV_ARRAY)) + n |= ARRAY_FILL; + cp = nv_endsubscript(np,sp,n); + } + else + cp = sp; + if((c = *cp)=='.' || c=='[' || (n&ARRAY_FILL)) + + { + int m = cp-sp; + char *sub = m?nv_getsub(np):0; + if(!sub) + sub = "0"; + n = strlen(sub)+2; + if(!copy) + { + copy = cp-name; + dp->nofree = 1; + name = copystack((const char*)0, name,(const char*)0); + cp = (char*)name+copy; + sp = cp-m; + } + if(n <= m) + { + if(n) + { + memcpy(sp+1,sub,n-2); + sp[n-1] = ']'; + } + if(n < m) + cp=strcpy(sp+n,cp); + } + else + { + int r = n-m; + m = sp-name; + name = stack_extend(name, cp-1, r); + sp = (char*)name + m; + *sp = '['; + memcpy(sp+1,sub,n-2); + sp[n-1] = ']'; + cp = sp+n; + + } + } + else if(c==0 && mode && (n=nv_aindex(np))>0) + nv_putsub(np,(char*)0,n|ARRAY_FILL); + else if(n==0 && c==0) + { + /* subscript must be 0*/ + cp[-1] = 0; + c = sh_arith(sp+1); + cp[-1] = ']'; + if(c) + return(0); + } + dp->last = cp; + if(nv_isarray(np) && (c=='[' || c=='.' || (flags&NV_ARRAY))) + { + *(sp=cp) = 0; + nq = nv_search(name,root,mode); + *sp = c; + if(nq && nv_isnull(nq)) + nq = nv_arraychild(np,nq,c); + if(!(np=nq)) + return(np); + } + } + else if(nv_isarray(np)) + nv_putsub(np,NIL(char*),ARRAY_UNDEF); + if(c=='.' && (fp=np->nvfun)) + { + for(; fp; fp=fp->next) + { + if(fp->disc && fp->disc->createf) + break; + } + if(fp) + { + if((nq = (*fp->disc->createf)(np,cp+1,flags,fp)) == np) + { + add = NV_ADD; + break; + } + else if((np=nq) && (c = *(cp=dp->last=fp->last))==0) + return(np); + } + } + } + while(c=='['); + if(c!='.') + return(np); + cp++; + break; + default: + dp->last = cp; + if((c = mbchar(cp)) && !isaletter(c)) + return(np); + while(xp=cp, c=mbchar(cp), isaname(c)); + cp = xp; + } + } + return(np); +} + +/* + * Put <arg> into associative memory. + * If <flags> & NV_ARRAY then follow array to next subscript + * If <flags> & NV_NOARRAY then subscript is not allowed + * If <flags> & NV_NOSCOPE then use the current scope only + * If <flags> & NV_ASSIGN then assignment is allowed + * If <flags> & NV_IDENT then name must be an identifier + * If <flags> & NV_VARNAME then name must be a valid variable name + * If <flags> & NV_NOADD then node will not be added if not found + * If <flags> & NV_NOREF then don't follow reference + * If <flags> & NV_NOFAIL then don't generate an error message on failure + * SH_INIT is only set while initializing the environment + */ +Namval_t *nv_open(const char *name, Dt_t *root, int flags) +{ + register char *cp=(char*)name; + register int c; + register Namval_t *np; + Namfun_t fun; + int append=0; + const char *msg = e_varname; + char *fname = 0; + int offset = staktell(); + Dt_t *funroot; + + memset(&fun,0,sizeof(fun)); + sh.last_table = sh.namespace; + if(!root) + root = sh.var_tree; + sh.last_root = root; + if(root==sh_subfuntree(1)) + { + flags |= NV_NOREF; + msg = e_badfun; + if((np=sh.namespace) || strchr(name,'.')) + { + name = cp = copystack(np?nv_name(np):0,name,(const char*)0); + fname = strrchr(cp,'.'); + *fname = 0; + fun.nofree = 1; + flags &= ~NV_IDENT; + funroot = root; + root = sh.var_tree; + } + } + else if(!(flags&(NV_IDENT|NV_VARNAME|NV_ASSIGN))) + { + long mode = ((flags&NV_NOADD)?0:NV_ADD); + if(flags&NV_NOSCOPE) + mode |= HASH_SCOPE|HASH_NOSCOPE; + np = nv_search(name,root,mode); + if(np && !(flags&NV_REF)) + { + while(nv_isref(np)) + { + sh.last_table = nv_reftable(np); + np = nv_refnode(np); + } + } + return(np); + } + else if(sh.prefix && /**name!='.' &&*/ (flags&NV_ASSIGN)) + { + name = cp = copystack(sh.prefix,name,(const char*)0); + fun.nofree = 1; + } + c = *(unsigned char*)cp; + if(root==sh.alias_tree) + { + msg = e_aliname; + while((c= *(unsigned char*)cp++) && (c!='=') && (c!='/') && + (c>=0x200 || !(c=sh_lexstates[ST_NORM][c]) || c==S_EPAT)); + if(sh.subshell && c=='=') + root = sh_subaliastree(1); + if(c= *--cp) + *cp = 0; + np = nv_search(name, root, (flags&NV_NOADD)?0:NV_ADD); + if(c) + *cp = c; + goto skip; + } + else if(flags&NV_IDENT) + msg = e_ident; + else if(c=='.') + { + c = *++cp; + flags |= NV_NOREF; + if(root==sh.var_tree) + root = sh.var_base; + sh.last_table = 0; + } + if(c= !isaletter(c)) + goto skip; + np = nv_create(name, root, flags, &fun); + cp = fun.last; + if(fname) + { + c = ((flags&NV_NOSCOPE)?HASH_NOSCOPE:0)|((flags&NV_NOADD)?0:NV_ADD); + *fname = '.'; + np = nv_search(name, funroot, c); + *fname = 0; + } + else if(*cp=='+' && cp[1]=='=') + { + append=NV_APPEND; + cp++; + } + c = *cp; +skip: + if(c=='=' && np && (flags&NV_ASSIGN)) + { + cp++; + if(sh_isstate(SH_INIT)) + { + nv_putval(np, cp, NV_RDONLY); + if(np==PWDNOD) + nv_onattr(np,NV_TAGGED); + } + else + { + char *sub=0; + if(sh_isoption(SH_XTRACE) && nv_isarray(np)) + sub = nv_getsub(np); + c = msg==e_aliname? 0: (append | (flags&NV_EXPORT)); + nv_putval(np, cp, c); + savesub = sub; + } + nv_onattr(np, flags&NV_ATTRIBUTES); + } + else if(c) + { + if(flags&NV_NOFAIL) + return(0); + if(c=='.') + msg = e_noparent; + else if(c=='[') + msg = e_noarray; + errormsg(SH_DICT,ERROR_exit(1),msg,name); + } + if(fun.nofree) + stakseek(offset); + return(np); +} + +#if SHOPT_MULTIBYTE + static int ja_size(char*, int, int); + static void ja_restore(void); + static char *savep; + static char savechars[8+1]; +#endif /* SHOPT_MULTIBYTE */ + +/* + * put value <string> into name-value node <np>. + * If <np> is an array, then the element given by the + * current index is assigned to. + * If <flags> contains NV_RDONLY, readonly attribute is ignored + * If <flags> contains NV_INTEGER, string is a pointer to a number + * If <flags> contains NV_NOFREE, previous value is freed, and <string> + * becomes value of node and <flags> becomes attributes + */ +void nv_putval(register Namval_t *np, const char *string, int flags) +{ + register const char *sp=string; + register union Value *up; + register char *cp; + register int size = 0; + register int dot; + int was_local = nv_local; + if(!(flags&NV_RDONLY) && nv_isattr (np, NV_RDONLY)) + errormsg(SH_DICT,ERROR_exit(1),e_readonly, nv_name(np)); + /* The following could cause the shell to fork if assignment + * would cause a side effect + */ + sh.argaddr = 0; + if(sh.subshell && !nv_local) + np = sh_assignok(np,1); + if(np->nvfun && !nv_isattr(np,NV_REF)) + { + /* This function contains disc */ + if(!nv_local) + { + nv_local=1; + nv_putv(np,sp,flags,np->nvfun); + if(sp && ((flags&NV_EXPORT) || nv_isattr(np,NV_EXPORT))) + sh_envput(sh.env,np); + return; + } + /* called from disc, assign the actual value */ + } + flags &= ~NV_NODISC; + if(flags&(NV_NOREF|NV_NOFREE)) + { + if(!nv_isnull(np) && np->nvalue.cp!=sp) + nv_unset(np); + nv_local=0; + np->nvalue.cp = (char*)sp; + nv_setattr(np,(flags&~NV_RDONLY)|NV_NOFREE); + return; + } + nv_local=0; + up= &np->nvalue; +#if !SHOPT_BSH + if(nv_isattr(np,NV_EXPORT)) + nv_offattr(np,NV_IMPORT); + else if(!nv_isattr(np,NV_MINIMAL)) + np->nvenv = 0; +#endif /* SHOPT_BSH */ + if(nv_isattr (np, NV_INTEGER)) + { + if(nv_isattr(np, NV_DOUBLE)) + { + if(nv_isattr(np, NV_LONG) && sizeof(double)<sizeof(Sfdouble_t)) + { + Sfdouble_t ld, old=0; + if(flags&NV_INTEGER) + { + if(flags&NV_LONG) + ld = *((Sfdouble_t*)sp); + else if(flags&NV_SHORT) + ld = *((float*)sp); + else + ld = *((double*)sp); + } + else + ld = sh_arith(sp); + if(!up->ldp) + up->ldp = new_of(Sfdouble_t,0); + else if(flags&NV_APPEND) + old = *(up->ldp); + *(up->ldp) = ld+old; + } + else + { + double d,od=0; + if(flags&NV_INTEGER) + { + if(flags&NV_LONG) + d = (double)(*(Sfdouble_t*)sp); + else if(flags&NV_SHORT) + d = (double)(*(float*)sp); + else + d = *(double*)sp; + } + else + d = sh_arith(sp); + if(!up->dp) + up->dp = new_of(double,0); + else if(flags&NV_APPEND) + od = *(up->dp); + *(up->dp) = d+od; + } + } + else + { + if(nv_isattr(np, NV_LONG) && sizeof(int32_t)<sizeof(Sflong_t)) + { + Sflong_t ll=0,oll=0; + if(flags&NV_INTEGER) + { + if(flags&NV_DOUBLE) + { + if(flags&NV_LONG) + ll = *((Sfdouble_t*)sp); + else if(flags&NV_SHORT) + ll = *((float*)sp); + else + ll = *((double*)sp); + } + else if(nv_isattr(np,NV_UNSIGN)) + { + if(flags&NV_LONG) + ll = *((Sfulong_t*)sp); + else if(flags&NV_SHORT) + ll = *((uint16_t*)sp); + else + ll = *((uint32_t*)sp); + } + else + { + if(flags&NV_LONG) + ll = *((Sflong_t*)sp); + else if(flags&NV_SHORT) + ll = *((uint16_t*)sp); + else + ll = *((uint32_t*)sp); + } + } + else if(sp) + ll = (Sflong_t)sh_arith(sp); + if(!up->llp) + up->llp = new_of(Sflong_t,0); + else if(flags&NV_APPEND) + oll = *(up->llp); + *(up->llp) = ll+oll; + } + else + { + int32_t l=0,ol=0; + if(flags&NV_INTEGER) + { + if(flags&NV_DOUBLE) + { + Sflong_t ll; + if(flags&NV_LONG) + ll = *((Sfdouble_t*)sp); + else if(flags&NV_SHORT) + ll = *((float*)sp); + else + ll = *((double*)sp); + l = (int32_t)ll; + } + else if(nv_isattr(np,NV_UNSIGN)) + { + if(flags&NV_LONG) + l = *((Sfulong_t*)sp); + else if(flags&NV_SHORT) + l = *((uint16_t*)sp); + else + l = *(uint32_t*)sp; + } + else + { + if(flags&NV_LONG) + l = *((Sflong_t*)sp); + else if(flags&NV_SHORT) + l = *((int16_t*)sp); + else + l = *(int32_t*)sp; + } + } + else if(sp) + { + Sfdouble_t ld = sh_arith(sp); + if(ld<0) + l = (int32_t)ld; + else + l = (uint32_t)ld; + } + if(nv_size(np) <= 1) + nv_setsize(np,10); + if(nv_isattr (np, NV_SHORT)) + { + int16_t s=0; + if(flags&NV_APPEND) + s = up->s; + up->s = s+(int16_t)l; + nv_onattr(np,NV_NOFREE); + } + else + { + if(!up->lp) + up->lp = new_of(int32_t,0); + else if(flags&NV_APPEND) + ol = *(up->lp); + *(up->lp) = l+ol; + } + } + } + } + else + { + const char *tofree=0; + int offset; +#if _lib_pathnative + char buff[PATH_MAX]; +#endif /* _lib_pathnative */ + if(flags&NV_INTEGER) + { + if(flags&NV_DOUBLE) + { + if(flags&NV_LONG) + sfprintf(sh.strbuf,"%.*Lg",LDBL_DIG,*((Sfdouble_t*)sp)); + else + sfprintf(sh.strbuf,"%.*g",DBL_DIG,*((double*)sp)); + } + else if(flags&NV_LONG) + sfprintf(sh.strbuf,"%lld\0",*((Sflong_t*)sp)); + else + sfprintf(sh.strbuf,"%ld\0",*((int32_t*)sp)); + sp = sfstruse(sh.strbuf); + } + if(nv_isattr(np, NV_HOST)==NV_HOST && sp) + { +#ifdef _lib_pathnative + /* + * return the host file name given the UNIX name + */ + pathnative(sp,buff,sizeof(buff)); + if(buff[1]==':' && buff[2]=='/') + { + buff[2] = '\\'; + if(*buff>='A' && *buff<='Z') + *buff += 'a'-'A'; + } + sp = buff; +#else + ; +#endif /* _lib_pathnative */ + } + else if((nv_isattr(np, NV_RJUST|NV_ZFILL|NV_LJUST)) && sp) + { + for(;*sp == ' '|| *sp=='\t';sp++); + if((nv_isattr(np,NV_ZFILL)) && (nv_isattr(np,NV_LJUST))) + for(;*sp=='0';sp++); + size = nv_size(np); +#if SHOPT_MULTIBYTE + if(size) + size = ja_size((char*)sp,size,nv_isattr(np,NV_RJUST|NV_ZFILL)); +#endif /* SHOPT_MULTIBYTE */ + } + if(!up->cp) + flags &= ~NV_APPEND; + if((flags&NV_APPEND) && !nv_isattr(np,NV_BINARY)) + { + offset = staktell(); + stakputs(up->cp); + stakputs(sp); + stakputc(0); + sp = stakptr(offset); + } + if(!nv_isattr(np, NV_NOFREE)) + { + /* delay free in case <sp> points into free region */ + tofree = up->cp; + } + nv_offattr(np,NV_NOFREE); + if (sp) + { + dot = strlen(sp); +#if (_AST_VERSION>=20030127L) + if(nv_isattr(np,NV_BINARY)) + { + int oldsize = (flags&NV_APPEND)?nv_size(np):0; + if(flags&NV_RAW) + { + if(tofree) + free((void*)tofree); + up->cp = sp; + return; + } + size = 0; + if(nv_isattr(np,NV_ZFILL)) + size = nv_size(np); + if(size==0) + size = oldsize + (3*dot/4); + cp = (char*)malloc(size+1); + if(oldsize) + memcpy((void*)cp,(void*)up->cp,oldsize); + up->cp = cp; + if(size <= oldsize) + return; + dot = base64decode(sp,dot, (void**)0, cp+oldsize, size-oldsize,(void**)0); + dot += oldsize; + if(!nv_isattr(np,NV_ZFILL) || nv_size(np)==0) + nv_setsize(np,dot); + else if(nv_isattr(np,NV_ZFILL) && (size>dot)) + memset((void*)&cp[dot],0,size-dot); + return; + } + else +#endif + if(size==0 && nv_isattr(np,NV_LJUST|NV_RJUST|NV_ZFILL)) + nv_setsize(np,size=dot); + else if(size > dot) + dot = size; + cp = (char*)malloc(((unsigned)dot+1)); + } + else + cp = 0; + up->cp = cp; + if(sp) + { + if(nv_isattr(np, NV_LTOU)) + ltou(sp,cp); + else if(nv_isattr (np, NV_UTOL)) + sh_utol(sp,cp); + else + strcpy(cp, sp); + if(nv_isattr(np, NV_RJUST) && nv_isattr(np, NV_ZFILL)) + rightjust(cp,size,'0'); + else if(nv_isattr(np, NV_RJUST)) + rightjust(cp,size,' '); + else if(nv_isattr(np, NV_LJUST)) + { + register char *dp; + dp = strlen (cp) + cp; + *(cp = (cp + size)) = 0; + for (; dp < cp; *dp++ = ' '); + } +#if SHOPT_MULTIBYTE + /* restore original string */ + if(savep) + ja_restore(); +#endif /* SHOPT_MULTIBYTE */ + } + if(flags&NV_APPEND) + stakseek(offset); + if(tofree) + free((void*)tofree); + } + if(!was_local && ((flags&NV_EXPORT) || nv_isattr(np,NV_EXPORT))) + sh_envput(sh.env,np); + return; +} + +/* + * + * Right-justify <str> so that it contains no more than + * <size> characters. If <str> contains fewer than <size> + * characters, left-pad with <fill>. Trailing blanks + * in <str> will be ignored. + * + * If the leftmost digit in <str> is not a digit, <fill> + * will default to a blank. + */ +static void rightjust(char *str, int size, int fill) +{ + register int n; + register char *cp,*sp; + n = strlen(str); + + /* ignore trailing blanks */ + for(cp=str+n;n && *--cp == ' ';n--); + if (n == size) + return; + if(n > size) + { + *(str+n) = 0; + for (sp = str, cp = str+n-size; sp <= str+size; *sp++ = *cp++); + return; + } + else *(sp = str+size) = 0; + if (n == 0) + { + while (sp > str) + *--sp = ' '; + return; + } + while(n--) + { + sp--; + *sp = *cp--; + } + if(!isdigit(*str)) + fill = ' '; + while(sp>str) + *--sp = fill; + return; +} + +#if SHOPT_MULTIBYTE + /* + * handle left and right justified fields for multi-byte chars + * given physical size, return a logical size which reflects the + * screen width of multi-byte characters + * Multi-width characters replaced by spaces if they cross the boundary + * <type> is non-zero for right justified fields + */ + + static int ja_size(char *str,int size,int type) + { + register char *cp = str; + register int c, n=size; + register int outsize; + register char *oldcp=cp; + int oldn; + wchar_t w; + while(*cp) + { + oldn = n; + w = mbchar(cp); + outsize = mbwidth(w); + size -= outsize; + c = cp-oldcp; + n += (c-outsize); + oldcp = cp; + if(size<=0 && type==0) + break; + } + /* check for right justified fields that need truncating */ + if(size <0) + { + if(type==0) + { + /* left justified and character crosses field boundary */ + n = oldn; + /* save boundary char and replace with spaces */ + size = c; + savechars[size] = 0; + while(size--) + { + savechars[size] = cp[size]; + cp[size] = ' '; + } + savep = cp; + } + size = -size; + if(type) + n -= (ja_size(str,size,0)-size); + } + return(n); + } + + static void ja_restore(void) + { + register char *cp = savechars; + while(*cp) + *savep++ = *cp++; + savep = 0; + } +#endif /* SHOPT_MULTIBYTE */ + +#ifndef _ENV_H +static char *staknam(register Namval_t *np, char *value) +{ + register char *p,*q; + q = stakalloc(strlen(nv_name(np))+(value?strlen(value):0)+2); + p=strcopy(q,nv_name(np)); + if(value) + { + *p++ = '='; + strcpy(p,value); + } + return(q); +} +#endif + +/* + * put the name and attribute into value of attributes variable + */ +#ifdef _ENV_H +static void attstore(register Namval_t *np, void *data) +{ + register int flag, c = ' '; + NOT_USED(data); + if(!(nv_isattr(np,NV_EXPORT))) + return; + flag = nv_isattr(np,NV_RDONLY|NV_UTOL|NV_LTOU|NV_RJUST|NV_LJUST|NV_ZFILL|NV_INTEGER); + stakputc('='); + if((flag&NV_DOUBLE) && (flag&NV_INTEGER)) + { + /* export doubles as integers for ksh88 compatibility */ + stakputc(c+(flag&~(NV_DOUBLE|NV_EXPNOTE))); + } + else + { + stakputc(c+flag); + if(flag&NV_INTEGER) + c += nv_size(np); + } + stakputc(c); + stakputs(nv_name(np)); +} +#else +static void attstore(register Namval_t *np, void *data) +{ + register int flag = np->nvflag; + register struct adata *ap = (struct adata*)data; + if(!(flag&NV_EXPORT) || (flag&NV_FUNCT)) + return; + flag &= (NV_RDONLY|NV_UTOL|NV_LTOU|NV_RJUST|NV_LJUST|NV_ZFILL|NV_INTEGER); + *ap->attval++ = '='; + if((flag&NV_DOUBLE) && (flag&NV_INTEGER)) + { + /* export doubles as integers for ksh88 compatibility */ + *ap->attval++ = ' '+(flag&~(NV_DOUBLE|NV_EXPNOTE)); + *ap->attval = ' '; + } + else + { + *ap->attval++ = ' '+flag; + if(flag&NV_INTEGER) + *ap->attval = ' ' + nv_size(np); + else + *ap->attval = ' '; + } + ap->attval = strcopy(++ap->attval,nv_name(np)); +} +#endif + +#ifndef _ENV_H +static void pushnam(Namval_t *np, void *data) +{ + register char *value; + register struct adata *ap = (struct adata*)data; + if(nv_isattr(np,NV_IMPORT)) + { + if(np->nvenv) + *ap->argnam++ = np->nvenv; + } + else if(value=nv_getval(np)) + *ap->argnam++ = staknam(np,value); + if(nv_isattr(np,NV_RDONLY|NV_UTOL|NV_LTOU|NV_RJUST|NV_LJUST|NV_ZFILL|NV_INTEGER)) + ap->attsize += (strlen(nv_name(np))+4); +} +#endif + +/* + * Generate the environment list for the child. + */ + +#ifdef _ENV_H +char **sh_envgen(void) +{ + int offset,tell; + register char **er; + env_delete(sh.env,"_"); + er = env_get(sh.env); + offset = staktell(); + stakputs(e_envmarker); + tell = staktell(); + nv_scan(sh.var_tree, attstore,(void*)0,0,(NV_RDONLY|NV_UTOL|NV_LTOU|NV_RJUST|NV_LJUST|NV_ZFILL|NV_INTEGER)); + if(tell ==staktell()) + stakseek(offset); + else + *--er = stakfreeze(1)+offset; + return(er); +} +#else +char **sh_envgen(void) +{ + register char **er; + register int namec; + register char *cp; + struct adata data; + /* L_ARGNOD gets generated automatically as full path name of command */ + nv_offattr(L_ARGNOD,NV_EXPORT); + data.attsize = 6; + namec = nv_scan(sh.var_tree,nullscan,(void*)0,NV_EXPORT,NV_EXPORT); + er = (char**)stakalloc((namec+4)*sizeof(char*)); + data.argnam = (er+=2); + nv_scan(sh.var_tree, pushnam,&data,NV_EXPORT, NV_EXPORT); + *data.argnam = (char*)stakalloc(data.attsize); + cp = data.attval = strcopy(*data.argnam,e_envmarker); + nv_scan(sh.var_tree, attstore,&data,0,(NV_RDONLY|NV_UTOL|NV_LTOU|NV_RJUST|NV_LJUST|NV_ZFILL|NV_INTEGER)); + *data.attval = 0; + if(cp!=data.attval) + data.argnam++; + *data.argnam = 0; + return(er); +} +#endif + +struct scan +{ + void (*scanfn)(Namval_t*, void*); + int scanmask; + int scanflags; + int scancount; + void *scandata; +}; + + +static int scanfilter(Dt_t *dict, void *arg, void *data) +{ + register Namval_t *np = (Namval_t*)arg; + register int k=np->nvflag; + register struct scan *sp = (struct scan*)data; + NOT_USED(dict); + if(sp->scanmask?(k&sp->scanmask)==sp->scanflags:(!sp->scanflags || (k&sp->scanflags))) + { + if(!np->nvalue.cp && !nv_isattr(np,~NV_DEFAULT)) + return(0); + if(sp->scanfn) + { + if(nv_isarray(np)) + nv_putsub(np,NIL(char*),0L); + (*sp->scanfn)(np,sp->scandata); + } + sp->scancount++; + } + return(0); +} + +/* + * Walk through the name-value pairs + * if <mask> is non-zero, then only nodes with (nvflags&mask)==flags + * are visited + * If <mask> is zero, and <flags> non-zero, then nodes with one or + * more of <flags> is visited + * If <mask> and <flags> are zero, then all nodes are visted + */ +int nv_scan(Dt_t *root, void (*fn)(Namval_t*,void*), void *data,int mask, int flags) +{ + Dt_t *base=0; + struct scan sdata; + int (*hashfn)(Dt_t*, void*, void*); + sdata.scanmask = mask; + sdata.scanflags = flags&~NV_NOSCOPE; + sdata.scanfn = fn; + sdata.scancount = 0; + sdata.scandata = data; + hashfn = scanfilter; + if(flags&NV_NOSCOPE) + base = dtview((Dt_t*)root,0); + dtwalk(root, hashfn,&sdata); + if(base) + dtview((Dt_t*)root,base); + return(sdata.scancount); +} + +/* + * create a new environment scope + */ +void nv_scope(struct argnod *envlist) +{ + register Dt_t *newscope; + newscope = dtopen(&_Nvdisc,Dtoset); + dtview(newscope,(Dt_t*)sh.var_tree); + sh.var_tree = (Dt_t*)newscope; + if(envlist) + nv_setlist(envlist,NV_EXPORT|NV_NOSCOPE|NV_IDENT|NV_ASSIGN); +} + +/* + * Remove freeable local space associated with the nvalue field + * of nnod. This includes any strings representing the value(s) of the + * node, as well as its dope vector, if it is an array. + */ + +void sh_envnolocal (register Namval_t *np, void *data) +{ + char *cp=0; + NOT_USED(data); + if(nv_isattr(np,NV_EXPORT) && nv_isarray(np)) + { + nv_putsub(np,NIL(char*),0); + if(cp = nv_getval(np)) + cp = strdup(cp); + } + if(nv_isattr(np,NV_EXPORT|NV_NOFREE)) + { + if(nv_isref(np)) + { + nv_offattr(np,NV_NOFREE|NV_REF); + free((void*)np->nvalue.nrp); + np->nvalue.cp = 0; + } + if(!cp) + return; + } + if(nv_isarray(np)) + nv_putsub(np,NIL(char*),ARRAY_UNDEF); + _nv_unset(np,NV_RDONLY); + nv_setattr(np,0); + if(cp) + { + nv_putval(np,cp,0); + free((void*)cp); + } +} + +/* + * Currently this is a dummy, but someday will be needed + * for reference counting + */ +void nv_close(Namval_t *np) +{ + NOT_USED(np); +} + +static void table_unset(register Dt_t *root, int flags, Dt_t *oroot) +{ + register Namval_t *np,*nq; + for(np=(Namval_t*)dtfirst(root);np;np=nq) + { + _nv_unset(np,flags); + if(oroot && (nq=nv_search(nv_name(np),oroot,0)) && nv_isattr(nq,NV_EXPORT)) + sh_envput(sh.env,nq); + nq = (Namval_t*)dtnext(root,np); + dtdelete(root,np); + free((void*)np); + } +} + +/* + * + * Set the value of <np> to 0, and nullify any attributes + * that <np> may have had. Free any freeable space occupied + * by the value of <np>. If <np> denotes an array member, it + * will retain its attributes. + * <flags> can contain NV_RDONLY to override the readonly attribute + * being cleared. + */ +void _nv_unset(register Namval_t *np,int flags) +{ + register union Value *up; + if(!(flags&NV_RDONLY) && nv_isattr (np,NV_RDONLY)) + errormsg(SH_DICT,ERROR_exit(1),e_readonly, nv_name(np)); + if(is_afunction(np) && np->nvalue.ip) + { + register struct slnod *slp = (struct slnod*)(np->nvenv); + if(slp && !nv_isattr(np,NV_NOFREE)) + { + /* free function definition */ + register char *name=nv_name(np),*cp= strrchr(name,'.'); + if(cp) + { + Namval_t *npv; + *cp = 0; + npv = nv_open(name,sh.var_tree,NV_NOARRAY|NV_VARNAME|NV_NOADD); + *cp++ = '.'; + if(npv) + nv_setdisc(npv,cp,NIL(Namval_t*),(Namfun_t*)npv); + } + stakdelete(slp->slptr); + free((void*)np->nvalue.ip); + np->nvalue.ip = 0; + } + goto done; + } + if(sh.subshell && !nv_isnull(np)) + np = sh_assignok(np,0); + nv_offattr(np,NV_NODISC); + if(np->nvfun && !nv_isref(np)) + { + /* This function contains disc */ + if(!nv_local) + { + nv_local=1; + nv_putv(np,NIL(char*),flags,np->nvfun); + return; + } + /* called from disc, assign the actual value */ + nv_local=0; + } + up = &np->nvalue; + if(up->cp) + { + if(!nv_isattr (np, NV_NOFREE)) + free((void*)up->cp); + up->cp = 0; + } +done: + if(!nv_isarray(np) || !nv_arrayptr(np)) + { + if(nv_isref(np)) + free((void*)np->nvalue.nrp); + nv_setsize(np,0); + if(!nv_isattr(np,NV_MINIMAL) || nv_isattr(np,NV_EXPORT)) + { + if(nv_isattr(np,NV_EXPORT) && !strchr(np->nvname,'[')) + env_delete(sh.env,nv_name(np)); + np->nvenv = 0; + nv_setattr(np,0); + } + else + nv_setattr(np,NV_MINIMAL); + } +} + +void nv_unset(register Namval_t *np) +{ + _nv_unset(np,0); +} + +/* + * return the node pointer in the highest level scope + */ +Namval_t *nv_scoped(register Namval_t *np) +{ + if(!dtvnext(sh.var_tree)) + return(np); + return(dtsearch(sh.var_tree,np)); +} + +#if 1 +/* + * return space separated list of names of variables in given tree + */ +static char *tableval(Dt_t *root) +{ + static Sfio_t *out; + register Namval_t *np; + register int first=1; + register Dt_t *base = dtview(root,0); + if(out) + sfseek(out,(Sfoff_t)0,SEEK_SET); + else + out = sfnew((Sfio_t*)0,(char*)0,-1,-1,SF_WRITE|SF_STRING); + for(np=(Namval_t*)dtfirst(root);np;np=(Namval_t*)dtnext(root,np)) + { + if(!nv_isnull(np) || np->nvfun || nv_isattr(np,~NV_NOFREE)) + { + if(!first) + sfputc(out,' '); + else + first = 0; + sfputr(out,np->nvname,-1); + } + } + sfputc(out,0); + if(base) + dtview(root,base); + return((char*)out->_data); +} +#endif + +#if SHOPT_OPTIMIZE +struct optimize +{ + Namfun_t hdr; + char **ptr; + struct optimize *next; + Namval_t *np; +}; + +static struct optimize *opt_free; + +static void optimize_clear(Namval_t* np, Namfun_t *fp) +{ + struct optimize *op = (struct optimize*)fp; + nv_stack(np,fp); + nv_stack(np,(Namfun_t*)0); + for(;op && op->np==np; op=op->next) + { + if(op->ptr) + { + *op->ptr = 0; + op->ptr = 0; + } + } +} + +static void put_optimize(Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + nv_putv(np,val,flags,fp); + optimize_clear(np,fp); +} + +static const Namdisc_t optimize_disc = {sizeof(struct optimize),put_optimize}; + +void nv_optimize(Namval_t *np) +{ + register Namfun_t *fp; + register struct optimize *op, *xp; + if(sh.argaddr) + { + for(fp=np->nvfun; fp; fp = fp->next) + { + if(fp->disc->getnum || fp->disc->getval) + { + sh.argaddr = 0; + return; + } + if(fp->disc== &optimize_disc) + break; + } + if((xp= (struct optimize*)fp) && xp->ptr==sh.argaddr) + return; + if(op = opt_free) + opt_free = op->next; + else + op=(struct optimize*)calloc(1,sizeof(struct optimize)); + op->ptr = sh.argaddr; + op->np = np; + if(xp) + { + op->hdr.disc = 0; + op->next = xp->next; + xp->next = op; + } + else + { + op->hdr.disc = &optimize_disc; + op->next = (struct optimize*)sh.optlist; + sh.optlist = (void*)op; + nv_stack(np,&op->hdr); + } + } +} + +void sh_optclear(Shell_t *shp, void *old) +{ + register struct optimize *op,*opnext; + for(op=(struct optimize*)shp->optlist; op; op = opnext) + { + opnext = op->next; + if(op->ptr && op->hdr.disc) + { + nv_stack(op->np,&op->hdr); + nv_stack(op->np,(Namfun_t*)0); + } + op->next = opt_free; + opt_free = op; + } + shp->optlist = old; +} + +#else +# define optimize_clear(np,fp) +#endif /* SHOPT_OPTIMIZE */ + +/* + * Return a pointer to a character string that denotes the value + * of <np>. If <np> refers to an array, return a pointer to + * the value associated with the current index. + * + * If the value of <np> is an integer, the string returned will + * be overwritten by the next call to nv_getval. + * + * If <np> has no value, 0 is returned. + */ + +char *nv_getval(register Namval_t *np) +{ + register union Value *up= &np->nvalue; + register int numeric; +#if SHOPT_OPTIMIZE + if(!nv_local && sh.argaddr) + nv_optimize(np); +#endif /* SHOPT_OPTIMIZE */ + if(!np->nvfun && !nv_isattr(np,NV_ARRAY|NV_INTEGER|NV_FUNCT|NV_REF|NV_TABLE)) + goto done; + if(nv_isref(np)) + { + sh.last_table = nv_reftable(np); + return(nv_name(nv_refnode(np))); + } + if(np->nvfun) + { + if(!nv_local) + { + nv_local=1; + return(nv_getv(np, np->nvfun)); + } + nv_local=0; + } + numeric = ((nv_isattr (np, NV_INTEGER)) != 0); + if(numeric) + { + Sflong_t ll; + if(!up->cp) + return("0"); + if(nv_isattr (np,NV_DOUBLE)) + { + Sfdouble_t ld; + double d; + char *format; + if(nv_isattr(np,NV_LONG)) + { + ld = *up->ldp; + if(nv_isattr (np,NV_EXPNOTE)) + format = "%.*Lg"; + else + format = "%.*Lf"; + sfprintf(sh.strbuf,format,nv_size(np),ld); + } + else + { + d = *up->dp; + if(nv_isattr (np,NV_EXPNOTE)) + format = "%.*g"; + else + format = "%.*f"; + sfprintf(sh.strbuf,format,nv_size(np),d); + } + return(sfstruse(sh.strbuf)); + } + else if(nv_isattr(np,NV_UNSIGN)) + { + if(nv_isattr (np,NV_LONG)) + ll = *(Sfulong_t*)up->llp; + else if(nv_isattr (np,NV_SHORT)) + ll = (uint16_t)up->s; + else + ll = *(uint32_t*)(up->lp); + } + else if(nv_isattr (np,NV_LONG)) + ll = *up->llp; + else if(nv_isattr (np,NV_SHORT)) + ll = up->s; + else + ll = *(up->lp); + if((numeric=nv_size(np))==10) + { + if(nv_isattr(np,NV_UNSIGN)) + { + sfprintf(sh.strbuf,"%I*u",sizeof(ll),ll); + return(sfstruse(sh.strbuf)); + } + numeric = 0; + } + return(fmtbasell(ll,numeric, numeric&&numeric!=10)); + } +done: +#if (_AST_VERSION>=20030127L) + /* + * if NV_RAW flag is on, return pointer to binary data + * otherwise, base64 encode the data and return this string + */ + if(up->cp && nv_isattr(np,NV_BINARY) && !nv_isattr(np,NV_RAW)) + { + char *cp; + int size= nv_size(np), insize=(4*size)/3+size/45+8; + base64encode(up->cp, size, (void**)0, cp=getbuf(insize), insize, (void**)0); + return(cp); + } +#endif + if((numeric=nv_size(np)) && up->cp && up->cp[numeric]) + { + char *cp = getbuf(numeric+1); + memcpy(cp,up->cp,numeric); + cp[numeric]=0; + return(cp); + } + return ((char*)up->cp); +} + +Sfdouble_t nv_getnum(register Namval_t *np) +{ + register union Value *up; + register Sfdouble_t r=0; + register char *str; +#if SHOPT_OPTIMIZE + if(!nv_local && sh.argaddr) + nv_optimize(np); +#endif /* SHOPT_OPTIMIZE */ + if(nv_istable(np)) + errormsg(SH_DICT,ERROR_exit(1),e_number,nv_name(np)); + if(np->nvfun) + { + if(!nv_local) + { + nv_local=1; + return(nv_getn(np, np->nvfun)); + } + nv_local=0; + } + if(nv_isattr (np, NV_INTEGER)) + { + up= &np->nvalue; + if(!up->lp) + r = 0; + else if(nv_isattr(np, NV_DOUBLE)) + { + if(nv_isattr(np, NV_LONG)) + r = *up->ldp; + else + r = *up->dp; + } + else if(nv_isattr(np, NV_UNSIGN)) + { + if(nv_isattr(np, NV_LONG)) + r = (Sflong_t)*((Sfulong_t*)up->llp); + else if(nv_isattr(np, NV_SHORT)) + r = (Sflong_t)((uint16_t)up->s); + else + r = *((uint32_t*)up->lp); + } + else + { + if(nv_isattr(np, NV_LONG)) + r = *up->llp; + else if(nv_isattr(np, NV_SHORT)) + r = up->s; + else + r = *up->lp; + } + } + else if((str=nv_getval(np)) && *str!=0) + { + if(np->nvfun || nv_isattr(np,NV_LJUST|NV_RJUST|NV_ZFILL)) + { + while(*str=='0') + str++; + } + r = sh_arith(str); + } + return(r); +} +/* + * Give <np> the attributes <newatts,> and change its current + * value to conform to <newatts>. The <size> of left and right + * justified fields may be given. + */ +void nv_newattr (register Namval_t *np, unsigned newatts, int size) +{ + register char *sp; + register char *cp = 0; + register unsigned int n; + Namarr_t *ap = 0; + int oldsize,oldatts; + + /* check for restrictions */ + if(sh_isoption(SH_RESTRICTED) && ((sp=nv_name(np))==nv_name(PATHNOD) || sp==nv_name(SHELLNOD) || sp==nv_name(ENVNOD) || sp==nv_name(FPATHNOD))) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,nv_name(np)); + /* handle attributes that do not change data separately */ + n = np->nvflag; +#if SHOPT_BSH + if(newatts&NV_EXPORT) + nv_offattr(np,NV_IMPORT); +#endif /* SHOPT_BSH */ + if(((n^newatts)&NV_EXPORT)) + { + /* record changes to the environment */ + if(n&NV_EXPORT) + env_delete(sh.env,nv_name(np)); + else + sh_envput(sh.env,np); + } + if((size==0||(n&NV_INTEGER)) && ((n^newatts)&~NV_NOCHANGE)==0) + { + if(size) + nv_setsize(np,size); + nv_offattr(np, ~NV_NOFREE); + nv_onattr(np, newatts); + return; + } + /* for an array, change all the elements */ + if((ap=nv_arrayptr(np)) && ap->nelem>0) + nv_putsub(np,NIL(char*),ARRAY_SCAN); + oldsize = nv_size(np); + oldatts = np->nvflag; + if(ap) /* add element to prevent array deletion */ + ap->nelem++; + do + { + nv_setsize(np,oldsize); + np->nvflag = oldatts; + if (sp = nv_getval(np)) + { + if(nv_isattr(np,NV_ZFILL)) + while(*sp=='0') sp++; + cp = (char*)malloc((n=strlen (sp)) + 1); + strcpy(cp, sp); + if(ap) + { + Namval_t *mp; + ap->nelem &= ~ARRAY_SCAN; + if(mp=nv_opensub(np)) + nv_onattr(mp,NV_NOFREE); + } + nv_unset(np); + if(ap) + ap->nelem |= ARRAY_SCAN; + if(size==0 && (newatts&(NV_LJUST|NV_RJUST|NV_ZFILL))) + size = n; + } + else + nv_unset(np); + nv_setsize(np,size); + np->nvflag &= NV_ARRAY; + np->nvflag |= newatts; + if (cp) + { + nv_putval (np, cp, NV_RDONLY); + free(cp); + } + } + while(ap && nv_nextsub(np)); + if(ap) + ap->nelem--; + return; +} + +#ifndef _NEXT_SOURCE +static char *oldgetenv(const char *string) +{ + register char c0,c1; + register const char *cp, *sp; + register char **av = environ; + if(!string || (c0= *string)==0) + return(0); + if((c1=*++string)==0) + c1= '='; + while(cp = *av++) + { + if(cp[0]!=c0 || cp[1]!=c1) + continue; + sp = string; + while(*sp && *sp++ == *++cp); + if(*sp==0 && *++cp=='=') + return((char*)(cp+1)); + } + return(0); +} + +/* + * This version of getenv uses the hash storage to access environment values + */ +char *getenv(const char *name) +/*@ + assume name!=0; +@*/ +{ + register Namval_t *np; + if(!sh.var_tree) + return(oldgetenv(name)); + if((np = nv_search(name,sh.var_tree,0)) && nv_isattr(np,NV_EXPORT)) + return(nv_getval(np)); + if(name[0] == 'P' && name[1] == 'A' && name[2] == 'T' && name[3] == 'H' && name[4] == 0) + return(oldgetenv(name)); + return(0); +} +#endif /* _NEXT_SOURCE */ + +#undef putenv +/* + * This version of putenv uses the hash storage to assign environment values + */ +int putenv(const char *name) +{ + register Namval_t *np; + if(name) + { + np = nv_open(name,sh.var_tree,NV_EXPORT|NV_IDENT|NV_NOARRAY|NV_ASSIGN); + if(!strchr(name,'=')) + nv_unset(np); + } + return(0); +} + + +/* + * Override libast setenv() + */ +char* setenviron(const char *name) +{ + register Namval_t *np; + if(name) + { + np = nv_open(name,sh.var_tree,NV_EXPORT|NV_IDENT|NV_NOARRAY|NV_ASSIGN); + if(strchr(name,'=')) + return(nv_getval(np)); + nv_unset(np); + } + return(""); +} + +/* + * copy <str1> to <str2> changing lower case to upper case + * <str2> must be big enough to hold <str1> + * <str1> and <str2> may point to the same place. + */ + +static void ltou(register char const *str1,register char *str2) +/*@ + assume str1!=0 && str2!=0; + return x satisfying strlen(in str1)==strlen(in str2); +@*/ +{ + register int c; + for(; c= *((unsigned char*)str1); str1++,str2++) + { + if(islower(c)) + *str2 = toupper(c); + else + *str2 = c; + } + *str2 = 0; +} + +/* + * normalize <cp> and return pointer to subscript if any + */ +static char *lastdot(register char *cp) +{ + register char *dp=cp, *ep=0; + register int c; + while(c= *cp++) + { + *dp++ = c; + if(c=='[') + ep = cp; + else if(c=='.') + { + if(*cp=='[') + { + ep = nv_endsubscript((Namval_t*)0,cp,0); + c = ep-cp; + memcpy(dp,cp,c); + dp = sh_checkid(dp+1,dp+c); + cp = ep; + } + ep = 0; + } + } + *dp = 0; + return(ep); +} + +/* + * Create a reference node from <np> to $np in dictionary <hp> + */ +void nv_setref(register Namval_t *np, Dt_t *hp, int flags) +{ + register Namval_t *nq, *nr; + register char *ep,*cp; + if(nv_isref(np)) + return; + if(nv_isarray(np)) + errormsg(SH_DICT,ERROR_exit(1),e_badref,nv_name(np)); + if(!(cp=nv_getval(np))) + errormsg(SH_DICT,ERROR_exit(1),e_noref,nv_name(np)); + if((ep = lastdot(cp)) && nv_isattr(np,NV_MINIMAL)) + errormsg(SH_DICT,ERROR_exit(1),e_badref,nv_name(np)); + if(!hp) + hp = sh.var_tree; + nr= nq = nv_open(cp, hp, flags|NV_NOREF); + while(nv_isref(nr)) + { + sh.last_table = nv_reftable(nr); + hp = nv_reftree(nr); + nr = nv_refnode(nr); + } + if(nr==np) + { + if(sh.namespace && nv_dict(sh.namespace)==hp) + errormsg(SH_DICT,ERROR_exit(1),e_selfref,nv_name(np)); + /* bind to earlier scope, or add to global scope */ + if(!(hp=dtvnext(hp)) || (nq=nv_search((char*)np,hp,NV_ADD|HASH_BUCKET))==np) + errormsg(SH_DICT,ERROR_exit(1),e_selfref,nv_name(np)); + } + if(ep) + { + /* cause subscript evaluation and return result */ +#if 0 + nv_endsubscript(nq,ep,NV_ADD); +#endif + ep = nv_getsub(nq); + } + nv_unset(np); + np->nvalue.nrp = newof(0,struct Namref,1,0); + np->nvalue.nrp->np = nq; + np->nvalue.nrp->root = hp; + if(ep) + np->nvalue.nrp->sub = strdup(ep); + np->nvalue.nrp->table = sh.last_table; + nv_onattr(np,NV_REF|NV_NOFREE); +} + +/* + * get the scope corresponding to <index> + * whence uses the same values as lseeek() + */ +Shscope_t *sh_getscope(int index, int whence) +{ + register struct sh_scoped *sp, *topmost; + if(whence==SEEK_CUR) + sp = &sh.st; + else + { + if ((struct sh_scoped*)sh.topscope != sh.st.self) + topmost = (struct sh_scoped*)sh.topscope; + else + topmost = &(sh.st); + sp = topmost; + if(whence==SEEK_SET) + { + int n =0; + while(sp = sp->prevst) + n++; + index = n - index; + sp = topmost; + } + } + if(index < 0) + return((Shscope_t*)0); + while(index-- && (sp = sp->prevst)); + return((Shscope_t*)sp); +} + +/* + * make <scoped> the top scope and return previous scope + */ +Shscope_t *sh_setscope(Shscope_t *scope) +{ + Shscope_t *old = (Shscope_t*)sh.st.self; + *sh.st.self = sh.st; + sh.st = *((struct sh_scoped*)scope); + sh.var_tree = scope->var_tree; + return(old); +} + +void nv_unscope(void) +{ + register Dt_t *root = sh.var_tree; + register Dt_t *dp = dtview(root,(Dt_t*)0); + table_unset(root,NV_RDONLY|NV_NOSCOPE,dp); + sh.var_tree=dp; + dtclose(root); +} + +/* + * The inverse of creating a reference node + */ +void nv_unref(register Namval_t *np) +{ + Namval_t *nq; + if(!nv_isref(np)) + return; + nq = nv_refnode(np); + nv_offattr(np,NV_NOFREE|NV_REF); + free((void*)np->nvalue.nrp); + np->nvalue.cp = strdup(nv_name(nq)); +#if SHOPT_OPTIMIZE + { + Namfun_t *fp; + for(fp=nq->nvfun; fp; fp = fp->next) + { + if(fp->disc== &optimize_disc) + { + optimize_clear(nq,fp); + return; + } + } + } +#endif +} + +/* + * These following are for binary compatibility with the old hash library + * They will be removed someday + */ + +#if defined(__IMPORT__) && defined(__EXPORT__) +# define extern __EXPORT__ +#endif + +#undef hashscope + +extern Dt_t *hashscope(Dt_t *root) +{ + return(dtvnext(root)); +} + +#undef hashfree + +extern Dt_t *hashfree(Dt_t *root) +{ + Dt_t *dp = dtvnext(root); + dtclose(root); + return(dp); +} + +#undef hashname + +extern char *hashname(void *obj) +{ + Namval_t *np = (Namval_t*)obj; + return(np->nvname); +} + +#undef hashlook + +extern void *hashlook(Dt_t *root, const char *name, int mode,int size) +{ + NOT_USED(size); + return((void*)nv_search(name,root,mode)); +} + +char *nv_name(register Namval_t *np) +{ + register Namval_t *table; + register Namfun_t *fp; + char *cp; + if(is_abuiltin(np) || is_afunction(np)) + return(np->nvname); + if(nv_istable(np)) +#if 1 + sh.last_table = nv_parent(np); +#else + sh.last_table = nv_create(np,0, NV_LAST,(Namfun_t*)0); +#endif + else if(!nv_isref(np)) + { + for(fp= np->nvfun ; fp; fp=fp->next) + if(fp->disc && fp->disc->namef) + { + if(np==sh.last_table) + sh.last_table = 0; + return((*fp->disc->namef)(np,fp)); + } + } + if(!(table=sh.last_table) || *np->nvname=='.' || table==sh.namespace || np==table) + return(np->nvname); + cp = nv_name(table); + sfprintf(sh.strbuf,"%s.%s",cp,np->nvname); + return(sfstruse(sh.strbuf)); +} + +Namval_t *nv_lastdict(void) +{ + return(sh.last_table); +} + +#undef nv_context +/* + * returns the data context for a builtin + */ +void *nv_context(Namval_t *np) +{ + return((void*)np->nvfun); +} + +#define DISABLE /* proto workaround */ + +int nv_isnull DISABLE (register Namval_t *np) +{ + return(nv_isnull(np)); +} + +#undef nv_setsize +int nv_setsize(register Namval_t *np, int size) +{ + int oldsize = nv_size(np); + if(size>=0) + np->nvsize = size; + return(oldsize); +} diff --git a/usr/src/lib/libshell/common/sh/nvdisc.c b/usr/src/lib/libshell/common/sh/nvdisc.c new file mode 100644 index 0000000000..533f5f7f5c --- /dev/null +++ b/usr/src/lib/libshell/common/sh/nvdisc.c @@ -0,0 +1,1263 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * AT&T Labs + * + */ + +#include "defs.h" +#include "variables.h" +#include "builtins.h" +#include "path.h" + +static const char *discnames[] = { "get", "set", "append", "unset", 0 }; + +int nv_compare(Dt_t* dict, Void_t *sp, Void_t *dp, Dtdisc_t *disc) +{ + if(sp==dp) + return(0); + return(strcmp((char*)sp,(char*)dp)); +} + +/* + * call the next getval function in the chain + */ +char *nv_getv(Namval_t *np, register Namfun_t *nfp) +{ + register Namfun_t *fp; + register char *cp; + if((fp = nfp) != NIL(Namfun_t*) && !nv_local) + fp = nfp = nfp->next; + nv_local=0; + for(; fp; fp=fp->next) + { + if(!fp->disc || (!fp->disc->getnum && !fp->disc->getval)) + continue; + if(!nv_isattr(np,NV_NODISC) || fp==(Namfun_t*)nv_arrayptr(np)) + break; + } + if(fp && fp->disc->getval) + cp = (*fp->disc->getval)(np,fp); + else if(fp && fp->disc->getnum) + { + sfprintf(sh.strbuf,"%.*Lg",12,(*fp->disc->getnum)(np,fp)); + cp = sfstruse(sh.strbuf); + } + else + { + nv_local=1; + cp = nv_getval(np); + } + return(cp); +} + +/* + * call the next getnum function in the chain + */ +Sfdouble_t nv_getn(Namval_t *np, register Namfun_t *nfp) +{ + register Namfun_t *fp; + register Sfdouble_t d=0; + char *str; + if((fp = nfp) != NIL(Namfun_t*) && !nv_local) + fp = nfp = nfp->next; + nv_local=0; + for(; fp; fp=fp->next) + { + if(!fp->disc->getnum && !fp->disc->getval) + continue; + if(!fp->disc->getnum && nv_isattr(np,NV_INTEGER)) + continue; + if(!nv_isattr(np,NV_NODISC) || fp==(Namfun_t*)nv_arrayptr(np)) + break; + } + if(fp && fp->disc->getnum) + d = (*fp->disc->getnum)(np,fp); + else if(nv_isattr(np,NV_INTEGER)) + { + nv_local = 1; + d = nv_getnum(np); + } + else + { + if(fp && fp->disc->getval) + str = (*fp->disc->getval)(np,fp); + else + str = nv_getv(np,fp?fp:nfp); + if(str && *str) + { + while(*str=='0') + str++; + d = sh_arith(str); + } + } + return(d); +} + +/* + * call the next assign function in the chain + */ +void nv_putv(Namval_t *np, const char *value, int flags, register Namfun_t *nfp) +{ + register Namfun_t *fp, *fpnext; + if((fp=nfp) != NIL(Namfun_t*) && !nv_local) + fp = nfp = nfp->next; + nv_local=0; + for(; fp; fp=fpnext) + { + fpnext = fp->next; + if(!fp->disc->putval) + { + if(!value) + { + nv_disc(np,fp,NV_POP); + if(!fp->nofree) + free((void*)fp); + } + continue; + } + if(!nv_isattr(np,NV_NODISC) || fp==(Namfun_t*)nv_arrayptr(np)) + break; + } + if(fp && fp->disc->putval) + (*fp->disc->putval)(np,value, flags, fp); + else + { + nv_local=1; + if(value) + nv_putval(np, value, flags); + else + _nv_unset(np, flags&NV_RDONLY); + } +} + +#if 0 +/* + * node creation discipline + */ +Namval_t *nv_create(register Namval_t* np,const char *name,int flag,register Namfun_t *fp) +{ + fp = fp?fp->next:np->nvfun; + while(fp && fp->disc && !fp->disc->createf) + fp = fp->next; + if(fp && fp->disc->createf) + return((*fp->disc->createf)(np,name,flag,fp)); + return(NIL(Namval_t*)); +} +#endif + +#define LOOKUP 0 +#define ASSIGN 1 +#define APPEND 2 +#define UNASSIGN 3 +#define BLOCKED ((Namval_t*)&nv_local) + +struct vardisc +{ + Namfun_t fun; + Namval_t *disc[4]; +}; + +struct blocked +{ + struct blocked *next; + Namval_t *np; + int flags; + void *sub; + int isub; +}; + +static struct blocked *blist; + +#define isblocked(bp,type) ((bp)->flags & (1<<(type))) +#define block(bp,type) ((bp)->flags |= (1<<(type))) +#define unblock(bp,type) ((bp)->flags &= ~(1<<(type))) + +/* + * returns pointer to blocking structure + */ +static struct blocked *block_info(Namval_t *np, struct blocked *pp) +{ + register struct blocked *bp; + void *sub=0; + int isub=0; + if(nv_isarray(np) && (isub=nv_aindex(np)) < 0) + sub = nv_associative(np,(const char*)0,NV_ACURRENT); + for(bp=blist ; bp; bp=bp->next) + { + if(bp->np==np && bp->sub==sub && bp->isub==isub) + return(bp); + } + if(pp) + { + pp->np = np; + pp->flags = 0; + pp->isub = isub; + pp->sub = sub; + pp->next = blist; + blist = pp; + } + return(pp); +} + +static void block_done(struct blocked *bp) +{ + blist = bp = bp->next; + if(bp && (bp->isub>=0 || bp->sub)) + nv_putsub(bp->np, bp->sub,(bp->isub<0?0:bp->isub)|ARRAY_SETSUB); +} + +/* + * free discipline if no more discipline functions + */ +static void chktfree(register Namval_t *np, register struct vardisc *vp) +{ + register int n; + for(n=0; n< sizeof(vp->disc)/sizeof(*vp->disc); n++) + { + if(vp->disc[n]) + break; + } + if(n>=sizeof(vp->disc)/sizeof(*vp->disc)) + { + /* no disc left so pop */ + Namfun_t *fp; + if((fp=nv_stack(np, NIL(Namfun_t*))) && !fp->nofree) + free((void*)fp); + } +} + +/* + * This function performs an assignment disc on the given node <np> + */ +static void assign(Namval_t *np,const char* val,int flags,Namfun_t *handle) +{ + int type = (flags&NV_APPEND)?APPEND:ASSIGN; + register struct vardisc *vp = (struct vardisc*)handle; + register Namval_t *nq = vp->disc[type]; + struct blocked block, *bp = block_info(np, &block); + Namval_t node; + if(val || isblocked(bp,type)) + { + if(!nq || isblocked(bp,type)) + { + nv_putv(np,val,flags,handle); + goto done; + } + node = *SH_VALNOD; + if(!nv_isnull(SH_VALNOD)) + { + nv_onattr(SH_VALNOD,NV_NOFREE); + nv_unset(SH_VALNOD); + } + if(flags&NV_INTEGER) + nv_onattr(SH_VALNOD,(flags&(NV_INTEGER|NV_LONG|NV_DOUBLE|NV_EXPNOTE|NV_SHORT))); + nv_putval(SH_VALNOD, val, (flags&NV_INTEGER)?flags:NV_NOFREE); + } + else + nq = vp->disc[type=UNASSIGN]; + if(nq && !isblocked(bp,type)) + { + int bflag; + block(bp,type); + if (type==APPEND && (bflag= !isblocked(bp,LOOKUP))) + block(bp,LOOKUP); + sh_fun(nq,np,(char**)0); + unblock(bp,type); + if(bflag) + unblock(bp,LOOKUP); + if(!vp->disc[type]) + chktfree(np,vp); + } + if(val) + { + register char *cp; + Sfdouble_t d; + if(nv_isnull(SH_VALNOD)) + cp=0; + else if(flags&NV_INTEGER) + { + d = nv_getnum(SH_VALNOD); + cp = (char*)(&d); + flags |= (NV_LONG|NV_DOUBLE); + flags &= ~NV_SHORT; + } + else + cp = nv_getval(SH_VALNOD); + if(cp) + nv_putv(np,cp,flags|NV_RDONLY,handle); + nv_unset(SH_VALNOD); + /* restore everything but the nvlink field */ + memcpy(&SH_VALNOD->nvname, &node.nvname, sizeof(node)-sizeof(node.nvlink)); + } + else if(sh_isstate(SH_INIT)) + { + /* don't free functions during reinitialization */ + nv_putv(np,val,flags,handle); + } + else if(!nq || !isblocked(bp,type)) + { + Dt_t *root = sh_subfuntree(1); + int n; + Namarr_t *ap; + block(bp,type); + nv_putv(np, val, flags, handle); + if(nv_isarray(np) && (ap=nv_arrayptr(np)) && ap->nelem>0) + goto done; + for(n=0; n < sizeof(vp->disc)/sizeof(*vp->disc); n++) + { + if((nq=vp->disc[n]) && !nv_isattr(nq,NV_NOFREE)) + { + nv_unset(nq); + dtdelete(root,nq); + } + } + unblock(bp,type); + nv_disc(np,handle,NV_POP); + if(!handle->nofree) + free(handle); + } +done: + if(bp== &block) + block_done(bp); +} + +/* + * This function executes a lookup disc and then performs + * the lookup on the given node <np> + */ +static char* lookup(Namval_t *np, Namfun_t *handle) +{ + register struct vardisc *vp = (struct vardisc*)handle; + struct blocked block, *bp = block_info(np, &block); + register Namval_t *nq = vp->disc[LOOKUP]; + register char *cp=0; + Namval_t node; + if(nq && !isblocked(bp,LOOKUP)) + { + node = *SH_VALNOD; + if(!nv_isnull(SH_VALNOD)) + { + nv_onattr(SH_VALNOD,NV_NOFREE); + nv_unset(SH_VALNOD); + } + block(bp,LOOKUP); + sh_fun(nq,np,(char**)0); + unblock(bp,LOOKUP); + if(!vp->disc[LOOKUP]) + chktfree(np,vp); + cp = nv_getval(SH_VALNOD); + if(!nv_isnull(&node)) + { + if(cp) + cp = strdup(cp); + /* restore everything but the nvlink field */ + memcpy(&SH_VALNOD->nvname, &node.nvname, sizeof(node)-sizeof(node.nvlink)); + } + } + if(!cp) + cp = nv_getv(np,handle); + if(bp== &block) + block_done(bp); + return(cp); +} + +static const Namdisc_t shdisc = +{ + sizeof(struct vardisc), + assign, + lookup +}; + +/* + * Set disc on given <event> to <action> + * If action==np, the current disc is returned + * A null return value indicates that no <event> is known for <np> + * If <event> is NULL, then return the event name after <action> + * If <event> is NULL, and <action> is NULL, return the first event + */ +char *nv_setdisc(register Namval_t* np,register const char *event,Namval_t *action,register Namfun_t *fp) +{ + register struct vardisc *vp = (struct vardisc*)np->nvfun; + register int type; + char *empty = ""; + if(np == (Namval_t*)fp) + { + register const char *name; + register int getname=0; + /* top level call, check for get/set */ + if(!event) + { + if(!action) + return((char*)discnames[0]); + getname=1; + event = (char*)action; + } + for(type=0; name=discnames[type]; type++) + { + if(strcmp(event,name)==0) + break; + } + if(getname) + { + event = 0; + if(name && !(name = discnames[++type])) + action = 0; + } + if(!name) + { + if((fp=(Namfun_t*)vp) && fp->disc->setdisc) + return((*fp->disc->setdisc)(np,event,action,fp)); + } + else if(getname) + return((char*)name); + } + if(!fp) + return(NIL(char*)); + if(np != (Namval_t*)fp) + { + /* not the top level */ + while(fp = fp->next) + { + if(fp->disc->setdisc) + return((*fp->disc->setdisc)(np,event,action,fp)); + } + return(NIL(char*)); + } + /* Handle GET/SET/APPEND/UNSET disc */ + if(vp && vp->fun.disc->putval!=assign) + vp = 0; + if(!vp) + { + if(action==np) + return((char*)action); + if(!(vp = newof(NIL(struct vardisc*),struct vardisc,1,0))) + return(0); + vp->fun.disc = &shdisc; + nv_stack(np, (Namfun_t*)vp); + } + if(action==np) + { + action = vp->disc[type]; + empty = 0; + } + else if(action) + vp->disc[type] = action; + else + { + struct blocked *bp; + action = vp->disc[type]; + vp->disc[type] = 0; + if(!(bp=block_info(np,(struct blocked*)0)) || !isblocked(bp,UNASSIGN)) + chktfree(np,vp); + } + return(action?(char*)action:empty); +} + + +/* + * Set disc on given <event> to <action> + * If action==np, the current disc is returned + * A null return value indicates that no <event> is known for <np> + * If <event> is NULL, then return the event name after <action> + * If <event> is NULL, and <action> is NULL, return the first event + */ +static char *setdisc(register Namval_t* np,register const char *event,Namval_t *action,register Namfun_t *fp) +{ + register Nambfun_t *vp = (Nambfun_t*)fp; + register int type,getname=0; + register const char *name; + const char **discnames = vp->bnames; + /* top level call, check for discipline match */ + if(!event) + { + if(!action) + return((char*)discnames[0]); + getname=1; + event = (char*)action; + } + for(type=0; name=discnames[type]; type++) + { + if(strcmp(event,name)==0) + break; + } + if(getname) + { + event = 0; + if(name && !(name = discnames[++type])) + action = 0; + } + if(!name) + return(nv_setdisc(np,event,action,fp)); + else if(getname) + return((char*)name); + /* Handle the disciplines */ + if(action==np) + action = vp->bltins[type]; + else if(action) + vp->bltins[type] = action; + else + { + action = vp->bltins[type]; + vp->bltins[type] = 0; + } + return(action?(char*)action:""); +} + +static void putdisc(Namval_t* np, const char* val, int flag, Namfun_t* fp) +{ + nv_putv(np,val,flag,fp); + if(!val) + { + register Nambfun_t *vp = (Nambfun_t*)fp; + register int i; + for(i=0; vp->bnames[i]; i++) + { + register Namval_t *mp; + if((mp=vp->bltins[i]) && !nv_isattr(mp,NV_NOFREE)) + { + if(is_abuiltin(mp)) + { + if(mp->nvfun && !nv_isattr(mp,NV_NOFREE)) + free((void*)mp->nvfun); + dtdelete(sh.bltin_tree,mp); + free((void*)mp); + } + } + } + nv_disc(np,fp,NV_POP); + if(!fp->nofree) + free((void*)fp); + + } +} + +static const Namdisc_t Nv_bdisc = { 0, putdisc, 0, 0, setdisc }; + +static Namfun_t *nv_clone_disc(register Namfun_t *fp) +{ + register Namfun_t *nfp; + register int size; + if(!(size=fp->dsize) && (!fp->disc || !(size=fp->disc->dsize))) + size = sizeof(Namfun_t); + if(!(nfp=newof(NIL(Namfun_t*),Namfun_t,1,size-sizeof(Namfun_t)))) + return(0); + memcpy(nfp,fp,size); + nfp->nofree = 0; + return(nfp); +} + +int nv_adddisc(Namval_t *np, const char **names, Namval_t **funs) +{ + register Nambfun_t *vp; + register int n=0; + register const char **av=names; + if(av) + { + while(*av++) + n++; + } + if(!(vp = newof(NIL(Nambfun_t*),Nambfun_t,1,n*sizeof(Namval_t*)))) + return(0); + vp->fun.dsize = sizeof(Nambfun_t)+n*sizeof(Namval_t*); + vp->fun.funs = 1; + if(funs) + memcpy((void*)vp->bltins, (void*)funs,n*sizeof(Namval_t*)); + else while(n>=0) + vp->bltins[n--] = 0; + vp->fun.disc = &Nv_bdisc; + vp->bnames = names; + nv_stack(np,&vp->fun); + return(1); +} + +/* + * push, pop, clone, or reorder disciplines onto node <np> + * mode can be one of + * NV_FIRST: Move or push <fp> to top of the stack or delete top + * NV_LAST: Move or push <fp> to bottom of stack or delete last + * NV_POP: Delete <fp> from top of the stack + * NV_CLONE: Replace fp with a copy created my malloc() and return it + */ +Namfun_t *nv_disc(register Namval_t *np, register Namfun_t* fp, int mode) +{ + Namfun_t *lp, **lpp; + if(nv_isref(np)) + return(0); + if(mode==NV_CLONE && !fp) + return(0); + if(fp) + { + if((lp=np->nvfun)==fp) + { + if(mode==NV_CLONE) + { + lp = nv_clone_disc(fp); + return(np->nvfun=lp); + } + if(mode==NV_FIRST || mode==0) + return(fp); + np->nvfun = lp->next; + if(mode==NV_POP) + return(fp); + } + /* see if <fp> is on the list already */ + lpp = &np->nvfun; + if(lp) + { + while(lp->next) + { + if(lp->next==fp) + { + if(mode==NV_CLONE) + { + fp = nv_clone_disc(fp); + lp->next = fp; + return(fp); + } + lp->next = fp->next; + if(mode==NV_POP) + return(fp); + if(mode!=NV_LAST) + break; + } + lp = lp->next; + } + if(mode==NV_LAST) + lpp = &lp->next; + } + if(mode==NV_POP) + return(0); + /* push */ + nv_offattr(np,NV_NODISC); + if(mode==NV_LAST) + fp->next = 0; + else + { + if(fp->nofree && *lpp) + fp = nv_clone_disc(fp); + fp->next = *lpp; + } + *lpp = fp; + } + else + { + if(mode==NV_FIRST) + return(np->nvfun); + else if(mode==NV_LAST) + for(lp=np->nvfun; lp; fp=lp,lp=lp->next); + else if(fp = np->nvfun) + np->nvfun = fp->next; + } + return(fp); +} + +/* + * returns discipline pointer if discipline with specified functions + * is on the discipline stack + */ +Namfun_t *nv_hasdisc(Namval_t *np, const Namdisc_t *dp) +{ + register Namfun_t *fp; + for(fp=np->nvfun; fp; fp = fp->next) + { + if(fp->disc== dp) + return(fp); + } + return(0); +} + +struct notify +{ + Namfun_t hdr; + char **ptr; +}; + +static void put_notify(Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + struct notify *pp = (struct notify*)fp; + nv_putv(np,val,flags,fp); + nv_stack(np,fp); + nv_stack(np,(Namfun_t*)0); + *pp->ptr = 0; + if(!fp->nofree) + free((void*)fp); +} + +static const Namdisc_t notify_disc = { 0, put_notify }; + +int nv_unsetnotify(Namval_t *np, char **addr) +{ + register Namfun_t *fp; + for(fp=np->nvfun;fp;fp=fp->next) + { + if(fp->disc->putval==put_notify && ((struct notify*)fp)->ptr==addr) + { + nv_stack(np,fp); + nv_stack(np,(Namfun_t*)0); + if(!fp->nofree) + free((void*)fp); + return(1); + } + } + return(0); +} + +int nv_setnotify(Namval_t *np, char **addr) +{ + struct notify *pp = newof(0,struct notify, 1,0); + if(!pp) + return(0); + pp->ptr = addr; + pp->hdr.disc = ¬ify_disc; + nv_stack(np,&pp->hdr); + return(1); +} + +static void *newnode(const char *name) +{ + register int s; + register Namval_t *np = newof(0,Namval_t,1,s=strlen(name)+1); + if(np) + { + np->nvname = (char*)np+sizeof(Namval_t); + memcpy(np->nvname,name,s); + } + return((void*)np); +} + +#if SHOPT_NAMESPACE +/* + * clone a numeric value + */ +static void *num_clone(register Namval_t *np, void *val) +{ + register int size; + void *nval; + if(!val) + return(0); + if(nv_isattr(np,NV_DOUBLE)) + { + if(nv_isattr(np,NV_LONG)) + size = sizeof(Sfdouble_t); + else if(nv_isattr(np,NV_SHORT)) + size = sizeof(float); + else + size = sizeof(double); + } + else + { + if(nv_isattr(np,NV_LONG)) + size = sizeof(Sflong_t); + else if(nv_isattr(np,NV_SHORT)) + size = sizeof(int16_t); + else + size = sizeof(int32_t); + } + if(!(nval = malloc(size))) + return(0); + memcpy(nval,val,size); + return(nval); +} + +static void clone_all_disc( Namval_t *np, Namval_t *mp, int flags) +{ + register Namfun_t *fp, **mfp = &mp->nvfun, *nfp; + for(fp=np->nvfun; fp;fp=fp->next) + { + if(fp->funs && (flags&NV_NODISC)) + nfp = 0; + if(fp->disc && fp->disc->clonef) + nfp = (*fp->disc->clonef)(np,mp,flags,fp); + else + nfp = nv_clone_disc(fp); + if(!nfp) + continue; + nfp->next = 0; + *mfp = nfp; + mfp = &nfp->next; + } +} + +/* + * clone <mp> from <np> flags can be one of the following + * NV_APPEND - append <np> onto <mp> + * NV_MOVE - move <np> to <mp> + * NV_NOFREE - mark the new node as nofree + * NV_NODISC - discplines with funs non-zero will not be copied + */ +int nv_clone(Namval_t *np, Namval_t *mp, int flags) +{ + Namfun_t *fp; + if(fp=np->nvfun) + { + if(flags&NV_MOVE) + { + mp->nvfun = fp; + goto skip; + } + clone_all_disc(np, mp, flags); + } + if(flags&NV_APPEND) + return(1); +skip: + nv_setsize(mp,nv_size(np)); + if(!nv_isattr(mp,NV_MINIMAL) || nv_isattr(mp,NV_EXPORT)) + mp->nvenv = (!nv_isattr(np,NV_MINIMAL)||nv_isattr(np,NV_EXPORT))?np->nvenv:0; + mp->nvalue.cp = np->nvalue.cp; + mp->nvflag = np->nvflag; + if(flags&NV_MOVE) + { + np->nvfun = 0; + np->nvalue.cp = 0; + if(!nv_isattr(np,NV_MINIMAL) || nv_isattr(mp,NV_EXPORT)) + np->nvenv = 0; + np->nvflag = 0; + nv_setsize(np,0); + return(1); + } + if(nv_isattr(np,NV_INTEGER)) + mp->nvalue.ip = (int*)num_clone(np,(void*)np->nvalue.ip); + else if(flags&NV_NOFREE) + nv_onattr(np,NV_NOFREE); + return(1); +} + +/* + * The following discipline is for copy-on-write semantics + */ +static char* clone_getv(Namval_t *np, Namfun_t *handle) +{ + return(np->nvalue.np?nv_getval(np->nvalue.np):0); +} + +static Sfdouble_t clone_getn(Namval_t *np, Namfun_t *handle) +{ + return(np->nvalue.np?nv_getnum(np->nvalue.np):0); +} + +static void clone_putv(Namval_t *np,const char* val,int flags,Namfun_t *handle) +{ + Namfun_t *dp = nv_stack(np,(Namfun_t*)0); + Namval_t *mp = np->nvalue.np; + if(!sh.subshell) + free((void*)dp); + if(val) + nv_clone(mp,np,NV_NOFREE); + np->nvalue.cp = 0; + nv_putval(np,val,flags); +} + +static const Namdisc_t clone_disc = +{ + 0, + clone_putv, + clone_getv, + clone_getn +}; + +Namval_t *nv_mkclone(Namval_t *mp) +{ + Namval_t *np; + Namfun_t *dp; + np = newof(0,Namval_t,1,0); + np->nvflag = mp->nvflag; + np->nvsize = mp->nvsize; + np->nvname = mp->nvname; + np->nvalue.np = mp; + np->nvflag = mp->nvflag; + dp = newof(0,Namfun_t,1,0); + dp->disc = &clone_disc; + nv_stack(np,dp); + dtinsert(nv_dict(sh.namespace),np); + return(np); +} +#endif /* SHOPT_NAMESPACE */ + +Namval_t *nv_search(const char *name, Dt_t *root, int mode) +{ + register Namval_t *np; + register Dt_t *dp = 0; + if(mode&HASH_NOSCOPE) + dp = dtview(root,0); + if(mode&HASH_BUCKET) + { + Namval_t *mp = (void*)name; + if(!(np = dtsearch(root,mp)) && (mode&NV_ADD)) + name = nv_name(mp); + } + else + { + if(*name=='.' && root==sh.var_tree) + root = sh.var_base; + np = dtmatch(root,(void*)name); + } + if(!np && (mode&NV_ADD)) + { + + if(sh.namespace && !(mode&HASH_NOSCOPE) && root==sh.var_tree) + root = nv_dict(sh.namespace); + else if(!dp && !(mode&HASH_NOSCOPE)) + { + register Dt_t *next; + while(next=dtvnext(root)) + root = next; + } + np = (Namval_t*)dtinsert(root,newnode(name)); + } + if(dp) + dtview(root,dp); + return(np); +} + +/* + * finds function or builtin for given name and the discipline variable + * if var!=0 the variable pointer is returned and the built-in name + * is put onto the stack at the current offset. + * otherwise, a pointer to the builtin (variable or type) is returned + * and var contains the poiner to the variable + * if last==0 and first component of name is a reference, nv_bfsearch() + will return 0. + */ +Namval_t *nv_bfsearch(const char *name, Dt_t *root, Namval_t **var, char **last) +{ + int offset = staktell(); + register char *sp, *cp=0; + Namval_t *np, *nq; + if(var) + *var = 0; + /* check for . in the name before = */ + for(sp=(char*)name+1; *sp; sp++) + { + if(*sp=='=') + return(0); + if(*sp=='.') + cp = sp; + } + if(!cp) + return(var?nv_search(name,root,0):0); + stakputs(name); + stakputc(0); + cp = stakptr(offset) + (cp-name); + if(last) + *last = cp; + *cp = 0; + nq=nv_open(stakptr(offset),0,NV_VARNAME|NV_NOASSIGN|NV_NOADD|NV_NOFAIL); + *cp = '.'; + if(!nq) + { + np = 0; + goto done; + } + if(!var) + { + np = nq; + goto done; + } + *var = nq; + return((Namval_t*)nv_setdisc(nq,cp+1,nq,(Namfun_t*)nq)); +done: + stakseek(offset); + return(np); +} + +/* + * add or replace built-in version of command commresponding to <path> + * The <bltin> argument is a pointer to the built-in + * if <extra>==1, the built-in will be deleted + * Special builtins cannot be added or deleted return failure + * The return value for adding builtins is a pointer to the node or NULL on + * failure. For delete NULL means success and the node that cannot be + * deleted is returned on failure. + */ +Namval_t *sh_addbuiltin(const char *path, int (*bltin)(int, char*[],void*),void *extra) +{ + register const char *name = path_basename(path); + char *cp; + register Namval_t *np, *nq=0; + int offset = staktell(); + if(name==path && (nq=nv_bfsearch(name,sh.bltin_tree,(Namval_t**)0,&cp))) + path = name = stakptr(offset); + if(np = nv_search(path,sh.bltin_tree,0)) + { + /* exists without a path */ + if(extra == (void*)1) + { + if(np->nvfun && !nv_isattr(np,NV_NOFREE)) + free((void*)np->nvfun); + dtdelete(sh.bltin_tree,np); + return(0); + } + if(!bltin) + return(np); + } + else for(np=(Namval_t*)dtfirst(sh.bltin_tree);np;np=(Namval_t*)dtnext(sh.bltin_tree,np)) + { + if(strcmp(name,path_basename(nv_name(np)))) + continue; + /* exists probably with different path so delete it */ + if(strcmp(path,nv_name(np))) + { + if(nv_isattr(np,BLT_SPC)) + return(np); + if(!bltin) + bltin = np->nvalue.bfp; + if(np->nvenv) + dtdelete(sh.bltin_tree,np); + if(extra == (void*)1) + return(0); + np = 0; + } + break; + } + if(!np && !(np = nv_search(path,sh.bltin_tree,bltin?NV_ADD:0))) + return(0); + if(nv_isattr(np,BLT_SPC)) + return(np); + np->nvenv = 0; + np->nvfun = 0; + nv_setattr(np,0); + if(bltin) + { + np->nvalue.bfp = bltin; + nv_onattr(np,NV_BLTIN|NV_NOFREE); + np->nvfun = (Namfun_t*)extra; + } + if(nq) + { + cp=nv_setdisc(nq,cp+1,np,(Namfun_t*)nq); + nv_close(nq); + if(!cp) + errormsg(SH_DICT,ERROR_exit(1),e_baddisc,name); + } + if(extra == (void*)1) + return(0); + return(np); +} + +#undef nv_stack +extern Namfun_t *nv_stack(register Namval_t *np, register Namfun_t* fp) +{ + return(nv_disc(np,fp,0)); +} + +struct table +{ + Namfun_t fun; + Namval_t *parent; + Shell_t *shp; + Dt_t *dict; +}; + +Namval_t *nv_parent(Namval_t *np) +{ + if(!nv_istable(np)) + return(0); + return(((struct table*)(np->nvfun))->parent); +} + +static Namval_t *next_table(register Namval_t* np, Dt_t *root,Namfun_t *fp) +{ + struct table *tp = (struct table *)fp; + if(root) + return((Namval_t*)dtnext(root,np)); + else + return((Namval_t*)dtfirst(tp->dict)); +} + +static Namval_t *create_table(Namval_t *np,const char *name,int flags,Namfun_t *fp) +{ + struct table *tp = (struct table *)fp; + tp->shp->last_table = np; + return(nv_create(name, tp->dict, flags, fp)); +} + +static Namfun_t *clone_table(Namval_t* np, Namval_t *mp, int flags, Namfun_t *fp) +{ + struct table *tp = (struct table*)fp; + struct table *ntp = (struct table*)nv_clone_disc(fp); + Dt_t *oroot=tp->dict,*nroot=dtopen(&_Nvdisc,Dtoset); + if(!nroot) + return(0); + memcpy((void*)ntp,(void*)fp,sizeof(struct table)); + ntp->dict = nroot; + ntp->parent = nv_lastdict(); + for(np=(Namval_t*)dtfirst(oroot);np;np=(Namval_t*)dtnext(oroot,np)) + { + mp = (Namval_t*)dtinsert(nroot,newnode(np->nvname)); + nv_clone(np,mp,flags); + } + return(&ntp->fun); +} + +static void put_table(register Namval_t* np, const char* val, int flags, Namfun_t* fp) +{ + register Dt_t *root = ((struct table*)fp)->dict; + register Namval_t *nq, *mp; + Namarr_t *ap; + nv_putv(np,val,flags,fp); + if(val) + return; + if(nv_isarray(np) && (ap=nv_arrayptr(np)) && array_elem(ap)) + return; + for(mp=(Namval_t*)dtfirst(root);mp;mp=nq) + { + _nv_unset(mp,flags); + nq = (Namval_t*)dtnext(root,mp); + dtdelete(root,mp); + free((void*)mp); + } + dtclose(root); + if(!fp->nofree) + free((void*)fp); +} + +/* + * return space separated list of names of variables in given tree + */ +static char *get_table(Namval_t *np, Namfun_t *fp) +{ + register Dt_t *root = ((struct table*)fp)->dict; + static Sfio_t *out; + register int first=1; + register Dt_t *base = dtview(root,0); + if(out) + sfseek(out,(Sfoff_t)0,SEEK_SET); + else + out = sfnew((Sfio_t*)0,(char*)0,-1,-1,SF_WRITE|SF_STRING); + for(np=(Namval_t*)dtfirst(root);np;np=(Namval_t*)dtnext(root,np)) + { + if(!nv_isnull(np) || np->nvfun || nv_isattr(np,~NV_NOFREE)) + { + if(!first) + sfputc(out,' '); + else + first = 0; + sfputr(out,np->nvname,-1); + } + } + sfputc(out,0); + if(base) + dtview(root,base); + return((char*)out->_data); +} + +static const Namdisc_t table_disc = +{ + sizeof(struct table), + put_table, + get_table, + 0, + 0, + create_table, + clone_table, + 0, + next_table, +}; + +Dt_t *nv_dict(Namval_t* np) +{ + struct table *tp = (struct table*)nv_hasdisc(np,&table_disc); + if(tp) + return(tp->dict); + np = sh.last_table; + while(np) + { + if(tp = (struct table*)nv_hasdisc(np,&table_disc)) + return(tp->dict); +#if 0 + np = nv_create(np,(const char*)0, NV_FIRST, (Namfun_t*)0); +#endif + } + return(sh.var_tree); +} + +/* + * create a mountable name-value pair tree + */ +Namval_t *nv_mount(Namval_t *np, const char *name, Dt_t *dict) +{ + Namval_t *mp, *pp=0; + struct table *tp = newof((struct table*)0, struct table,1,0); + if(name) + { + if(nv_istable(np)) + pp = np; + else + pp = nv_lastdict(); + } + if(!(tp = newof((struct table*)0, struct table,1,0))) + return(0); + if(name) + { + Namfun_t *fp = pp->nvfun; + mp = (*fp->disc->createf)(pp,name,0,fp); + } + else + mp = np; + if(!nv_isnull(mp)) + nv_unset(mp); + tp->shp = sh_getinterp(); + tp->dict = dict; + tp->parent = pp; + tp->fun.disc = &table_disc; + nv_onattr(mp,NV_TABLE); + nv_disc(mp, &tp->fun, NV_LAST); + return(mp); +} + +const Namdisc_t *nv_discfun(int which) +{ + switch(which) + { + case NV_DCADD: + return(&Nv_bdisc); + case NV_DCRESTRICT: + return(&RESTRICTED_disc); + } + return(0); +} + +/* + * This function turns variable <np> to the type <tp> + */ +int nv_settype(Namval_t* np, Namval_t *tp, int flags) +{ + int isnull = nv_isnull(np); + char *val=0; + if(isnull) + flags &= ~NV_APPEND; + else + { + val = strdup(nv_getval(np)); + if(!(flags&NV_APPEND)) + _nv_unset(np, NV_RDONLY); + } + if(!nv_clone(tp,np,flags|NV_NOFREE)) + return(0); + if(val) + { + nv_putval(np,val,NV_RDONLY); + free((void*)val); + } + return(0); +} + diff --git a/usr/src/lib/libshell/common/sh/nvtree.c b/usr/src/lib/libshell/common/sh/nvtree.c new file mode 100644 index 0000000000..82657e3a0c --- /dev/null +++ b/usr/src/lib/libshell/common/sh/nvtree.c @@ -0,0 +1,679 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +/* + * code for tree nodes and name walking + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include "name.h" +#include "argnod.h" + +struct nvdir +{ + Dt_t *root; + Namval_t *hp; + Namval_t *table; + Namval_t *(*nextnode)(Namval_t*,Dt_t*,Namfun_t*); + Namfun_t *fun; + struct nvdir *prev; + int len; + int offset; + char data[1]; +}; + +char *nv_getvtree(Namval_t*, Namfun_t *); +static void put_tree(Namval_t*, const char*, int,Namfun_t*); + +static Namval_t *create_tree(Namval_t *np,const char *name,int flag,Namfun_t *dp) +{ + register Namfun_t *fp=dp; + while(fp=fp->next) + { + if(fp->disc && fp->disc->createf) + { + if(np=(*fp->disc->createf)(np,name,flag,fp)) + dp->last = fp->last; + return(np); + } + } + return((flag&NV_NOADD)?0:np); +} + +static const Namdisc_t treedisc = +{ + 0, + put_tree, + nv_getvtree, + 0, + 0, + create_tree +}; + +static char *nextdot(const char *str) +{ + register char *cp; + if(*str=='.') + str++; + if(*str =='[') + { + cp = nv_endsubscript((Namval_t*)0,(char*)str,0); + return(*cp=='.'?cp:0); + } + else + return(strchr(str,'.')); +} + +static Namfun_t *nextdisc(Namval_t *np) +{ + register Namfun_t *fp; + if(nv_isref(np)) + return(0); + for(fp=np->nvfun;fp;fp=fp->next) + { + if(fp && fp->disc && fp->disc->nextf) + return(fp); + } + return(0); +} + +void *nv_diropen(const char *name) +{ + char *next,*last; + int c,len=strlen(name); + struct nvdir *save, *dp = new_of(struct nvdir,len); + Namval_t *np, fake; + Namfun_t *nfp; + if(!dp) + return(0); + memset((void*)dp, 0, sizeof(*dp)); + last=dp->data; + if(name[len-1]=='*' || name[len-1]=='@') + len -= 1; + name = memcpy(last,name,len); + last[len] = 0; + dp->len = len; + dp->root = sh.var_tree; + dp->table = sh.last_table; + if(*name) + { + fake.nvname = (char*)name; + dp->hp = (Namval_t*)dtprev(dp->root,&fake); + dp->hp = (Namval_t*)dtnext(dp->root,dp->hp); + } + else + dp->hp = (Namval_t*)dtfirst(dp->root); + while(next= nextdot(last)) + { + c = *next; + *next = 0; + np = nv_search(last,dp->root,0); + *next = c; + if(np && ((nfp=nextdisc(np)) || nv_istable(np))) + { + if(!(save = new_of(struct nvdir,0))) + return(0); + *save = *dp; + dp->prev = save; + if(nv_istable(np)) + dp->root = nv_dict(np); + else + dp->root = (Dt_t*)dp; + dp->offset = last-(char*)name; + if(dp->offset<len) + dp->len = len-dp->offset; + else + dp->len = 0; + if(nfp) + { + dp->nextnode = nfp->disc->nextf; + dp->table = np; + dp->fun = nfp; + dp->hp = (*dp->nextnode)(np,(Dt_t*)0,nfp); + } + else + dp->nextnode = 0; + } + else + break; + last = next+1; + } + return((void*)dp); +} + + +static Namval_t *nextnode(struct nvdir *dp) +{ + if(dp->nextnode) + return((*dp->nextnode)(dp->hp,dp->root,dp->fun)); + if(dp->len && memcmp(dp->data, dp->hp->nvname, dp->len)) + return(0); + return((Namval_t*)dtnext(dp->root,dp->hp)); +} + +char *nv_dirnext(void *dir) +{ + register struct nvdir *save, *dp = (struct nvdir*)dir; + register Namval_t *np, *last_table; + register char *cp; + Namfun_t *nfp; + while(1) + { + while(np=dp->hp) + { + dp->hp = nextnode(dp); + if(nv_isnull(np)) + continue; + last_table = sh.last_table; + sh.last_table = dp->table; + cp = nv_name(np); + sh.last_table = last_table; + if(!dp->len || memcmp(cp+dp->offset,dp->data,dp->len)==0) + { + if((nfp=nextdisc(np)) || nv_istable(np)) + { + Dt_t *root; + if(nv_istable(np)) + root = nv_dict(np); + else + root = (Dt_t*)dp; + /* check for recursive walk */ + for(save=dp; save; save=save->prev) + { + if(save->root==root) + break; + } + if(save) + continue; + if(!(save = new_of(struct nvdir,0))) + return(0); + *save = *dp; + dp->prev = save; + dp->root = root; + dp->len = 0; + if(nfp && np->nvfun) + { + dp->nextnode = nfp->disc->nextf; + dp->table = np; + dp->fun = nfp; + dp->hp = (*dp->nextnode)(np,(Dt_t*)0,nfp); + } + else + dp->nextnode = 0; + } + return(cp); + } + } + if(!(save=dp->prev)) + break; +#if 0 + sh.last_table = dp->table; +#endif + *dp = *save; + free((void*)save); + } + return(0); +} + +void nv_dirclose(void *dir) +{ + struct nvdir *dp = (struct nvdir*)dir; + if(dp->prev) + nv_dirclose((void*)dp->prev); + free(dir); +} + +static void outtype(Namval_t *np, Namfun_t *fp, Sfio_t* out, const char *prefix) +{ + char *type=0; + Namval_t *tp = fp->type; + if(!tp && fp->disc && fp->disc->typef) + tp = (*fp->disc->typef)(np,fp); + for(fp=fp->next;fp;fp=fp->next) + { + if(fp->type || (fp->disc && fp->disc->typef &&(*fp->disc->typef)(np,fp))) + { + outtype(np,fp,out,prefix); + break; + } + } + if(prefix && *prefix=='t') + type = "-T"; + else if(!prefix) + type = "type"; + if(type) + sfprintf(out,"%s %s ",type,tp->nvname); +} + +/* + * print the attributes of name value pair give by <np> + */ +void nv_attribute(register Namval_t *np,Sfio_t *out,char *prefix,int noname) +{ + register const Shtable_t *tp; + register char *cp; + register unsigned val; + register unsigned mask; + register unsigned attr; + Namfun_t *fp=0; + for(fp=np->nvfun;fp;fp=fp->next) + { + if(fp->type || (fp->disc && fp->disc->typef &&(*fp->disc->typef)(np,fp))) + break; + } +#if 0 + if(!fp && !nv_isattr(np,~NV_ARRAY)) + { + if(!nv_isattr(np,NV_ARRAY) || nv_aindex(np)>=0) + return; + } +#else + if(!fp && !nv_isattr(np,~NV_MINIMAL)) + return; +#endif + + if ((attr=nv_isattr(np,~NV_NOFREE)) || fp) + { + if((attr&NV_NOPRINT)==NV_NOPRINT) + attr &= ~NV_NOPRINT; + if(!attr && !fp) + return; + if(prefix) + sfputr(out,prefix,' '); + for(tp = shtab_attributes; *tp->sh_name;tp++) + { + val = tp->sh_number; + mask = val; + if(fp && (val&NV_INTEGER)) + break; + /* + * the following test is needed to prevent variables + * with E attribute from being given the F + * attribute as well + */ + if(val==(NV_INTEGER|NV_DOUBLE) && (attr&NV_EXPNOTE)) + continue; + if(val&NV_INTEGER) + mask |= NV_DOUBLE; + else if(val&NV_HOST) + mask = NV_HOST; + if((attr&mask)==val) + { + if(val==NV_ARRAY) + { + Namarr_t *ap = nv_arrayptr(np); + if(array_assoc(ap)) + { + if(tp->sh_name[1]!='A') + continue; + } + else if(tp->sh_name[1]=='A') + continue; +#if 0 + cp = "associative"; + else + cp = "indexed"; + if(!prefix) + sfputr(out,cp,' '); + else if(*cp=='i') + tp++; +#endif + } + if(prefix) + { + if(*tp->sh_name=='-') + sfprintf(out,"%.2s ",tp->sh_name); + } + else + sfputr(out,tp->sh_name+2,' '); + if ((val&(NV_LJUST|NV_RJUST|NV_ZFILL)) && !(val&NV_INTEGER) && val!=NV_HOST) + sfprintf(out,"%d ",nv_size(np)); + } + if(val==NV_INTEGER && nv_isattr(np,NV_INTEGER)) + { + if(nv_size(np) != 10) + { + if(nv_isattr(np, NV_DOUBLE)) + cp = "precision"; + else + cp = "base"; + if(!prefix) + sfputr(out,cp,' '); + sfprintf(out,"%d ",nv_size(np)); + } + break; + } + } + if(fp) + outtype(np,fp,out,prefix); + if(noname) + return; + sfputr(out,nv_name(np),'\n'); + } +} + +struct Walk +{ + Sfio_t *out; + Dt_t *root; + int noscope; + int indent; +}; + +static void outval(char *name, const char *vname, struct Walk *wp) +{ + register Namval_t *np, *nq; + register Namfun_t *fp; + int isarray=0, associative=0, special=0; + if(!(np=nv_open(vname,wp->root,NV_ARRAY|NV_VARNAME|NV_NOADD|NV_NOASSIGN|wp->noscope))) + return; + if(nv_isarray(np) && *name=='.') + special = 1; + if(!special && (fp=nv_hasdisc(np,&treedisc))) + { + if(!wp->out) + { + fp = nv_stack(np,fp); + if(fp = nv_stack(np,NIL(Namfun_t*))) + free((void*)fp); + np->nvfun = 0; + } + return; + } + if(nv_isnull(np)) + return; + if(special || nv_isarray(np)) + { + isarray=1; + associative= nv_aindex(np)<0; + if(array_elem(nv_arrayptr(np))==0) + isarray=2; + else + nq = nv_putsub(np,NIL(char*),ARRAY_SCAN|(wp->out?ARRAY_NOCHILD:0)); + } + if(!wp->out) + { + _nv_unset(np,NV_RDONLY); + nv_close(np); + return; + } + if(isarray==1 && !nq) + return; + if(special) + { + associative = 1; + sfnputc(wp->out,'\t',wp->indent); + } + else + { + sfnputc(wp->out,'\t',wp->indent); + nv_attribute(np,wp->out,"typeset",'='); + nv_outname(wp->out,name,-1); + sfputc(wp->out,(isarray==2?'\n':'=')); + if(isarray) + { + if(isarray==2) + return; + sfwrite(wp->out,"(\n",2); + sfnputc(wp->out,'\t',++wp->indent); + } + } + while(1) + { + char *fmtq,*ep; + if(isarray && associative) + { + if(!(fmtq = nv_getsub(np))) + break; + sfprintf(wp->out,"[%s]",sh_fmtq(fmtq)); + sfputc(wp->out,'='); + } + if(!(fmtq = sh_fmtq(nv_getval(np)))) + fmtq = ""; + else if(!associative && (ep=strchr(fmtq,'='))) + { + char *qp = strchr(fmtq,'\''); + if(!qp || qp>ep) + { + sfwrite(wp->out,fmtq,ep-fmtq); + sfputc(wp->out,'\\'); + fmtq = ep; + } + } + if(*name=='[' && !isarray) + sfprintf(wp->out,"(%s)\n",fmtq); + else + sfputr(wp->out,fmtq,'\n'); + if(!nv_nextsub(np)) + break; + sfnputc(wp->out,'\t',wp->indent); + } + if(isarray && !special) + { + sfnputc(wp->out,'\t',--wp->indent); + sfwrite(wp->out,")\n",2); + } +} + +/* + * format initialization list given a list of assignments <argp> + */ +static char **genvalue(char **argv, const char *prefix, int n, struct Walk *wp) +{ + register char *cp,*nextcp,*arg; + register int m,r; + register Sfio_t *outfile = wp->out; + if(n==0) + m = strlen(prefix); + else if(cp=nextdot(prefix)) + m = cp-prefix; + else + m = strlen(prefix)-1; + m++; + if(outfile) + { + sfwrite(outfile,"(\n",2); + wp->indent++; + } + for(; arg= *argv; argv++) + { + cp = arg + n; + if(n==0 && cp[m-1]!='.') + continue; + if(n && cp[m-1]==0) + break; + if(n==0 || strncmp(arg,prefix-n,m+n)==0) + { + cp +=m; + r = 0; + if(*cp=='.') + cp++,r++; + if(nextcp=nextdot(cp)) + { + if(outfile) + { + sfnputc(outfile,'\t',wp->indent); + nv_outname(outfile,cp,nextcp-cp); + sfputc(outfile,'='); + } + argv = genvalue(argv,cp,n+m+r,wp); + if(outfile) + sfputc(outfile,'\n'); + if(*argv) + continue; + break; + } + else if(outfile && argv[1] && memcmp(arg,argv[1],r=strlen(arg))==0 && argv[1][r]=='[') + { + Namval_t *np = nv_open(arg,wp->root,NV_VARNAME|NV_NOADD|NV_NOASSIGN|wp->noscope); + if(!np) + continue; + sfnputc(outfile,'\t',wp->indent); + nv_attribute(np,outfile,"typeset",1); + nv_close(np); + sfputr(outfile,arg+m+(n?n+1:0),'='); + argv = genvalue(++argv,cp,cp-arg ,wp); + sfputc(outfile,'\n'); + } + else if(outfile && *cp=='[') + { + sfnputc(outfile,'\t',wp->indent); + sfputr(outfile,cp,'='); + argv = genvalue(++argv,cp,cp-arg ,wp); + sfputc(outfile,'\n'); + } + else + outval(cp,arg,wp); + } + else + break; + } + if(outfile) + { + int c = prefix[m-1]; + cp = (char*)prefix; + if(c=='.') + cp[m-1] = 0; + outval(".",prefix-n,wp); + if(c=='.') + cp[m-1] = c; + sfnputc(outfile,'\t',wp->indent-1); + sfputc(outfile,')'); + } + return(--argv); +} + +/* + * walk the virtual tree and print or delete name-value pairs + */ +static char *walk_tree(register Namval_t *np, int dlete) +{ + static Sfio_t *out; + struct Walk walk; + Sfio_t *outfile; + int savtop = staktell(); + char *savptr = stakfreeze(0); + register struct argnod *ap=0; + struct argnod *arglist=0; + char *name,*cp, **argv; + char *subscript=0; + void *dir; + int n=0, noscope=(dlete&NV_NOSCOPE); + stakputs(nv_name(np)); + if(subscript = nv_getsub(np)) + { + stakputc('['); + stakputs(subscript); + stakputc(']'); + stakputc('.'); + } + name = stakfreeze(1); + dir = nv_diropen(name); + if(subscript) + name[strlen(name)-1] = 0; + while(cp = nv_dirnext(dir)) + { + stakseek(ARGVAL); + stakputs(cp); + ap = (struct argnod*)stakfreeze(1); + ap->argflag = ARG_RAW; + ap->argchn.ap = arglist; + n++; + arglist = ap; + } + argv = (char**)stakalloc((n+1)*sizeof(char*)); + argv += n; + *argv = 0; + for(; ap; ap=ap->argchn.ap) + *--argv = ap->argval; + nv_dirclose(dir); + if(dlete&1) + outfile = 0; + else if(!(outfile=out)) + outfile = out = sfnew((Sfio_t*)0,(char*)0,-1,-1,SF_WRITE|SF_STRING); + else + sfseek(outfile,0L,SEEK_SET); + walk.out = outfile; + walk.root = sh.last_root; + walk.indent = 0; + walk.noscope = noscope; + genvalue(argv,name,0,&walk); + stakset(savptr,savtop); + if(!outfile) + return((char*)0); + sfputc(out,0); + return((char*)out->_data); +} + +/* + * get discipline for compound initializations + */ +char *nv_getvtree(register Namval_t *np, Namfun_t *fp) +{ + NOT_USED(fp); + if(nv_isattr(np,NV_BINARY) && nv_isattr(np,NV_RAW)) + return(nv_getv(np,fp)); + if(nv_isattr(np,NV_ARRAY) && nv_arraychild(np,(Namval_t*)0,0)==np) + return(nv_getv(np,fp)); + return(walk_tree(np,0)); +} + +/* + * put discipline for compound initializations + */ +static void put_tree(register Namval_t *np, const char *val, int flags,Namfun_t *fp) +{ + struct Namarray *ap; + int nleft = 0; + if(!nv_isattr(np,NV_INTEGER)) + walk_tree(np,(flags&NV_NOSCOPE)|1); + nv_putv(np, val, flags,fp); + if(nv_isattr(np,NV_INTEGER)) + return; + if(ap= nv_arrayptr(np)) + nleft = array_elem(ap); + if(nleft==0) + { + fp = nv_stack(np,fp); + if(fp = nv_stack(np,NIL(Namfun_t*))) + { + free((void*)fp); + } + } +} + +/* + * Insert discipline to cause $x to print current tree + */ +void nv_setvtree(register Namval_t *np) +{ + register Namfun_t *nfp; + if(nv_hasdisc(np, &treedisc)) + return; + nfp = newof(NIL(void*),Namfun_t,1,0); + nfp->disc = &treedisc; + nv_stack(np, nfp); +} + diff --git a/usr/src/lib/libshell/common/sh/parse.c b/usr/src/lib/libshell/common/sh/parse.c new file mode 100644 index 0000000000..1cacd79e57 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/parse.c @@ -0,0 +1,1757 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * UNIX shell + * + * S. R. Bourne + * Rewritten by David Korn + * AT&T Labs + * + * This is the parser for a shell language + */ + +#if KSHELL +#include "defs.h" +#else +#include <shell.h> +#endif +#include <ctype.h> +#include <fcin.h> +#include <error.h> +#include "shlex.h" +#include "history.h" +#include "builtins.h" +#include "test.h" +#include "history.h" + +#define HERE_MEM 1024 /* size of here-docs kept in memory */ + +#define hash nvlink.hl._hash + +/* These routines are local to this module */ + +static Shnode_t *makeparent(int, Shnode_t*); +static Shnode_t *makelist(int, Shnode_t*, Shnode_t*); +static struct argnod *qscan(struct comnod*, int); +static struct ionod *inout(struct ionod*, int); +static Shnode_t *sh_cmd(int,int); +static Shnode_t *term(int); +static Shnode_t *list(int); +static struct regnod *syncase(int); +static Shnode_t *item(int); +static Shnode_t *simple(int, struct ionod*); +static int skipnl(int); +static Shnode_t *test_expr(int); +static Shnode_t *test_and(void); +static Shnode_t *test_or(void); +static Shnode_t *test_primary(void); + +#define sh_getlineno() (shlex.lastline) + +#ifndef NIL +# define NIL(type) ((type)0) +#endif /* NIL */ +#define CNTL(x) ((x)&037) + + +#if !KSHELL +static struct stdata +{ + struct slnod *staklist; + int cmdline; +} st; +#endif + +static int loop_level; +static struct argnod *label_list; +static struct argnod *label_last; + +#define getnode(type) ((Shnode_t*)stakalloc(sizeof(struct type))) + +#if SHOPT_KIA +#include "path.h" +/* + * write out entities for each item in the list + * type=='V' for variable assignment lists + * Otherwise type is determined by the command */ +static unsigned long writedefs(struct argnod *arglist, int line, int type, struct argnod *cmd) +{ + register struct argnod *argp = arglist; + register char *cp; + register int n,eline; + int width=0; + unsigned long r=0; + static char atbuff[20]; + int justify=0; + char *attribute = atbuff; + unsigned long parent=shlex.script; + if(type==0) + { + parent = shlex.current; + type = 'v'; + switch(*argp->argval) + { + case 'a': + type='p'; + justify = 'a'; + break; + case 'e': + *attribute++ = 'x'; + break; + case 'r': + *attribute++ = 'r'; + break; + case 'l': + break; + } + while(argp = argp->argnxt.ap) + { + if((n= *(cp=argp->argval))!='-' && n!='+') + break; + if(cp[1]==n) + break; + while((n= *++cp)) + { + if(isdigit(n)) + width = 10*width + n-'0'; + else if(n=='L' || n=='R' || n =='Z') + justify=n; + else + *attribute++ = n; + } + } + } + else if(cmd) + parent=kiaentity(sh_argstr(cmd),-1,'p',-1,-1,shlex.unknown,'b',0,""); + *attribute = 0; + while(argp) + { + if((cp=strchr(argp->argval,'='))||(cp=strchr(argp->argval,'?'))) + n = cp-argp->argval; + else + n = strlen(argp->argval); + eline = sh.inlineno-(shlex.token==NL); + r=kiaentity(argp->argval,n,type,line,eline,parent,justify,width,atbuff); + sfprintf(shlex.kiatmp,"p;%..64d;v;%..64d;%d;%d;s;\n",shlex.current,r,line,eline); + argp = argp->argnxt.ap; + } + return(r); +} +#endif /* SHOPT_KIA */ +/* + * Make a parent node for fork() or io-redirection + */ +static Shnode_t *makeparent(int flag, Shnode_t *child) +{ + register Shnode_t *par = getnode(forknod); + par->fork.forktyp = flag; + par->fork.forktre = child; + par->fork.forkio = 0; + par->fork.forkline = sh_getlineno()-1; + return(par); +} + +static Shnode_t *getanode(struct argnod *ap) +{ + register Shnode_t *t = getnode(arithnod); + t->ar.artyp = TARITH; + t->ar.arline = sh_getlineno(); + t->ar.arexpr = ap; + if(ap->argflag&ARG_RAW) + t->ar.arcomp = sh_arithcomp(ap->argval); + else + t->ar.arcomp = 0; + return(t); +} + +/* + * Make a node corresponding to a command list + */ +static Shnode_t *makelist(int type, Shnode_t *l, Shnode_t *r) +{ + register Shnode_t *t; + if(!l || !r) + sh_syntax(); + else + { + if((type&COMMSK) == TTST) + t = getnode(tstnod); + else + t = getnode(lstnod); + t->lst.lsttyp = type; + t->lst.lstlef = l; + t->lst.lstrit = r; + } + return(t); +} + +/* + * entry to shell parser + * Flag can be the union of SH_EOF|SH_NL + */ + +void *sh_parse(Shell_t *shp, Sfio_t *iop, int flag) +{ + register Shnode_t *t; + Fcin_t sav_input; + struct argnod *sav_arg = shlex.arg; + int sav_prompt = shp->nextprompt; + if(shp->binscript && sffileno(iop)==shp->infd) + return((void*)sh_trestore(iop)); + fcsave(&sav_input); + shp->st.staklist = 0; + shlex.heredoc = 0; + shlex.inlineno = shp->inlineno; + shlex.firstline = shp->st.firstline; + shp->nextprompt = 1; + loop_level = 0; + label_list = label_last = 0; + if(sh_isoption(SH_INTERACTIVE)) + sh_onstate(SH_INTERACTIVE); + if(sh_isoption(SH_VERBOSE)) + sh_onstate(SH_VERBOSE); + sh_lexopen((Lex_t*)shp->lex_context,shp,0); + if(fcfopen(iop) < 0) + return(NIL(void*)); + if(fcfile()) + { + char *cp = fcfirst(); + if( cp[0]==CNTL('k') && cp[1]==CNTL('s') && cp[2]==CNTL('h') && cp[3]==0) + { + int version; + fcseek(4); + fcgetc(version); + fcclose(); + fcrestore(&sav_input); + shlex.arg = sav_arg; + if(version > 3) + errormsg(SH_DICT,ERROR_exit(1),e_lexversion); + if(sffileno(iop)==shp->infd) + shp->binscript = 1; + sfgetc(iop); + return((void*)sh_trestore(iop)); + } + } + if((flag&SH_NL) && (shp->inlineno=error_info.line+shp->st.firstline)==0) + shp->inlineno=1; +#if KSHELL + shp->nextprompt = 2; +#endif + t = sh_cmd((flag&SH_EOF)?EOFSYM:'\n',SH_SEMI|SH_EMPTY|(flag&SH_NL)); + fcclose(); + fcrestore(&sav_input); + shlex.arg = sav_arg; + /* unstack any completed alias expansions */ + if((sfset(iop,0,0)&SF_STRING) && !sfreserve(iop,0,-1)) + { + Sfio_t *sp = sfstack(iop,NULL); + if(sp) + sfclose(sp); + } + shp->nextprompt = sav_prompt; + if(flag&SH_NL) + { + shp->st.firstline = shlex.firstline; + shp->inlineno = shlex.inlineno; + } + stakseek(0); + return((void*)t); +} + +/* + * This routine parses up the matching right parenthesis and returns + * the parse tree + */ +Shnode_t *sh_dolparen(void) +{ + register Shnode_t *t=0; + register Lex_t *lp = (Lex_t*)sh.lex_context; + Sfio_t *sp = fcfile(); + int line = sh.inlineno; + sh.inlineno = error_info.line+sh.st.firstline; + sh_lexopen(lp,&sh,1); + shlex.comsub = 1; + switch(sh_lex()) + { + /* ((...)) arithmetic expression */ + case EXPRSYM: + t = getanode(shlex.arg); + break; + case LPAREN: + t = sh_cmd(RPAREN,SH_NL|SH_EMPTY); + break; + } + shlex.comsub = 0; + if(!sp && (sp=fcfile())) + { + /* + * This code handles the case where string has been converted + * to a file by an alias setup + */ + register int c; + char *cp; + if(fcgetc(c) > 0) + fcseek(-1); + cp = fcseek(0); + fcclose(); + fcsopen(cp); + sfclose(sp); + } + sh.inlineno = line; + return(t); +} + +/* + * remove temporary files and stacks + */ + +void sh_freeup(void) +{ + if(sh.st.staklist) + sh_funstaks(sh.st.staklist,-1); + sh.st.staklist = 0; +} + +/* + * increase reference count for each stack in function list when flag>0 + * decrease reference count for each stack in function list when flag<=0 + * stack is freed when reference count is zero + */ + +void sh_funstaks(register struct slnod *slp,int flag) +{ + register struct slnod *slpold; + while(slpold=slp) + { + if(slp->slchild) + sh_funstaks(slp->slchild,flag); + slp = slp->slnext; + if(flag<=0) + stakdelete(slpold->slptr); + else + staklink(slpold->slptr); + } +} +/* + * cmd + * empty + * list + * list & [ cmd ] + * list [ ; cmd ] + */ + +static Shnode_t *sh_cmd(register int sym, int flag) +{ + register Shnode_t *left, *right; + register int type = FINT|FAMP; + if(sym==NL) + shlex.lasttok = 0; + left = list(flag); + if(shlex.token==NL) + { + if(flag&SH_NL) + shlex.token=';'; + } + else if(!left && !(flag&SH_EMPTY)) + sh_syntax(); + switch(shlex.token) + { + case COOPSYM: /* set up a cooperating process */ + type |= (FPIN|FPOU|FPCL|FCOOP); + /* FALL THRU */ + case '&': + if(left) + { + /* (...)& -> {...;} & */ + if(left->tre.tretyp==TPAR) + left = left->par.partre; + left = makeparent(TFORK|type, left); + } + /* FALL THRU */ + case ';': + if(!left) + sh_syntax(); + if(right=sh_cmd(sym,flag|SH_EMPTY)) + left=makelist(TLST, left, right); + break; + case EOFSYM: + if(sym==NL) + break; + default: + if(sym && sym!=shlex.token) + { + if(sym!=ELSESYM || (shlex.token!=ELIFSYM && shlex.token!=FISYM)) + sh_syntax(); + } + } + return(left); +} + +/* + * list + * term + * list && term + * list || term + * unfortunately, these are equal precedence + */ +static Shnode_t *list(register int flag) +{ + register Shnode_t *t = term(flag); + register int token; + while(t && ((token=shlex.token)==ANDFSYM || token==ORFSYM)) + t = makelist((token==ANDFSYM?TAND:TORF), t, term(SH_NL|SH_SEMI)); + return(t); +} + +/* + * term + * item + * item | term + */ +static Shnode_t *term(register int flag) +{ + register Shnode_t *t; + register int token; + if(flag&SH_NL) + token = skipnl(flag); + else + token = sh_lex(); + /* check to see if pipeline is to be timed */ + if(token==TIMESYM || token==NOTSYM) + { + t = getnode(parnod); + t->par.partyp=TTIME; + if(shlex.token==NOTSYM) + t->par.partyp |= COMSCAN; + t->par.partre = term(0); + } + else if((t=item(SH_NL|SH_EMPTY|(flag&SH_SEMI))) && shlex.token=='|') + { + register Shnode_t *tt; + int showme = t->tre.tretyp&FSHOWME; + t = makeparent(TFORK|FPOU,t); + if(tt=term(SH_NL)) + { + switch(tt->tre.tretyp&COMMSK) + { + case TFORK: + tt->tre.tretyp |= FPIN|FPCL; + break; + case TFIL: + tt->lst.lstlef->tre.tretyp |= FPIN|FPCL; + break; + default: + tt= makeparent(TSETIO|FPIN|FPCL,tt); + } + t=makelist(TFIL,t,tt); + t->tre.tretyp |= showme; + } + else if(shlex.token) + sh_syntax(); + } + return(t); +} + +/* + * case statement + */ +static struct regnod* syncase(register int esym) +{ + register int tok = skipnl(0); + register struct regnod *r; + if(tok==esym) + return(NIL(struct regnod*)); + r = (struct regnod*)stakalloc(sizeof(struct regnod)); + r->regptr=0; + r->regflag=0; + if(tok==LPAREN) + skipnl(0); + while(1) + { + if(!shlex.arg) + sh_syntax(); + shlex.arg->argnxt.ap=r->regptr; + r->regptr = shlex.arg; + if((tok=sh_lex())==RPAREN) + break; + else if(tok=='|') + sh_lex(); + else + sh_syntax(); + } + r->regcom=sh_cmd(0,SH_NL|SH_EMPTY|SH_SEMI); + if((tok=shlex.token)==BREAKCASESYM) + r->regnxt=syncase(esym); + else if(tok==FALLTHRUSYM) + { + r->regflag++; + r->regnxt=syncase(esym); + } + else + { + if(tok!=esym && tok!=EOFSYM) + sh_syntax(); + r->regnxt=0; + } + if(shlex.token==EOFSYM) + return(NIL(struct regnod*)); + return(r); +} + +/* + * This routine creates the parse tree for the arithmetic for + * When called, shlex.arg contains the string inside ((...)) + * When the first argument is missing, a while node is returned + * Otherise a list containing an arithmetic command and a while + * is returned. + */ +static Shnode_t *arithfor(register Shnode_t *tf) +{ + register Shnode_t *t, *tw = tf; + register int offset; + register struct argnod *argp; + register int n; + int argflag = shlex.arg->argflag; + /* save current input */ + Fcin_t sav_input; + fcsave(&sav_input); + fcsopen(shlex.arg->argval); + /* split ((...)) into three expressions */ + for(n=0; ; n++) + { + register int c; + argp = (struct argnod*)stakseek(ARGVAL); + argp->argnxt.ap = 0; + argp->argchn.cp = 0; + argp->argflag = argflag; + if(n==2) + break; + /* copy up to ; onto the stack */ + sh_lexskip(';',1,ST_NESTED); + offset = staktell()-1; + if((c=fcpeek(-1))!=';') + break; + /* remove trailing white space */ + while(offset>ARGVAL && ((c= *stakptr(offset-1)),isspace(c))) + offset--; + /* check for empty initialization expression */ + if(offset==ARGVAL && n==0) + continue; + stakseek(offset); + /* check for empty condition and treat as while((1)) */ + if(offset==ARGVAL) + stakputc('1'); + argp = (struct argnod*)stakfreeze(1); + t = getanode(argp); + if(n==0) + tf = makelist(TLST,t,tw); + else + tw->wh.whtre = t; + } + while((offset=fcpeek(0)) && isspace(offset)) + fcseek(1); + stakputs(fcseek(0)); + argp = (struct argnod*)stakfreeze(1); + fcrestore(&sav_input); + if(n<2) + { + shlex.token = RPAREN|SYMREP; + sh_syntax(); + } + /* check whether the increment is present */ + if(*argp->argval) + { + t = getanode(argp); + tw->wh.whinc = (struct arithnod*)t; + } + else + tw->wh.whinc = 0; + sh_lexopen((Lex_t*)sh.lex_context, &sh,1); + if((n=sh_lex())==NL) + n = skipnl(0); + else if(n==';') + n = sh_lex(); + if(n!=DOSYM && n!=LBRACE) + sh_syntax(); + tw->wh.dotre = sh_cmd(n==DOSYM?DONESYM:RBRACE,SH_NL); + tw->wh.whtyp = TWH; + return(tf); + +} + +static Shnode_t *funct(void) +{ + register Shnode_t *t; + register int flag; + struct slnod *volatile slp=0; + Stak_t *savstak; + Sfoff_t first, last; + struct functnod *fp; + Sfio_t *iop; +#if SHOPT_KIA + unsigned long current = shlex.current; +#endif /* SHOPT_KIA */ + int jmpval, saveloop=loop_level; + struct argnod *savelabel = label_last; + struct checkpt buff; + t = getnode(functnod); + t->funct.functline = sh.inlineno; + t->funct.functtyp=TFUN; + t->funct.functargs = 0; + if(!(flag = (shlex.token==FUNCTSYM))) + t->funct.functtyp |= FPOSIX; + else if(sh_lex()) + sh_syntax(); + if(!(iop=fcfile())) + { + iop = sfopen(NIL(Sfio_t*),fcseek(0),"s"); + fcclose(); + fcfopen(iop); + } + t->funct.functloc = first = fctell(); + if(!sh.st.filename || sffileno(iop)<0) + { + if(fcfill() >= 0) + fcseek(-1); + if(sh_isstate(SH_HISTORY)) + t->funct.functloc = sfseek(sh.hist_ptr->histfp,(off_t)0,SEEK_CUR); + else + { + /* copy source to temporary file */ + t->funct.functloc = 0; + if(shlex.sh->heredocs) + t->funct.functloc = sfseek(shlex.sh->heredocs,(Sfoff_t)0, SEEK_END); + else + shlex.sh->heredocs = sftmp(HERE_MEM); + shlex.sh->funlog = shlex.sh->heredocs; + t->funct.functtyp |= FPIN; + } + } + t->funct.functnam= (char*)shlex.arg->argval; +#if SHOPT_KIA + if(shlex.kiafile) + shlex.current = kiaentity(t->funct.functnam,-1,'p',-1,-1,shlex.script,'p',0,""); +#endif /* SHOPT_KIA */ + if(flag) + { + shlex.token = sh_lex(); +#if SHOPT_BASH + if(shlex.token == LPAREN) + { + if((shlex.token = sh_lex()) == RPAREN) + t->funct.functtyp |= FPOSIX; + else + sh_syntax(); + } +#endif + } + if(t->funct.functtyp&FPOSIX) + skipnl(0); + else + { + if(shlex.token==0) + t->funct.functargs = (struct comnod*)simple(SH_NOIO|SH_FUNDEF,NIL(struct ionod*)); + while(shlex.token==NL) + shlex.token = sh_lex(); + } + if((flag && shlex.token!=LBRACE) || shlex.token==EOFSYM) + sh_syntax(); + sh_pushcontext(&buff,1); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval == 0) + { + /* create a new stak frame to compile the command */ + savstak = stakcreate(STAK_SMALL); + savstak = stakinstall(savstak, 0); + slp = (struct slnod*)stakalloc(sizeof(struct slnod)+sizeof(struct functnod)); + slp->slchild = 0; + slp->slnext = sh.st.staklist; + sh.st.staklist = 0; + t->funct.functstak = (struct slnod*)slp; + /* + * store the pathname of function definition file on stack + * in name field of fake for node + */ + fp = (struct functnod*)(slp+1); + fp->functtyp = TFUN|FAMP; + fp->functnam = 0; + fp->functline = t->funct.functline; + if(sh.st.filename) + fp->functnam = stakcopy(sh.st.filename); + loop_level = 0; + label_last = label_list; + if(!flag && shlex.token==0) + { + /* copy current word token to current stak frame */ + struct argnod *ap; + flag = ARGVAL + strlen(shlex.arg->argval); + ap = (struct argnod*)stakalloc(flag); + memcpy(ap,shlex.arg,flag); + shlex.arg = ap; + } + t->funct.functtre = item(SH_NOIO); + } + sh_popcontext(&buff); + loop_level = saveloop; + label_last = savelabel; + /* restore the old stack */ + if(slp) + { + slp->slptr = stakinstall(savstak,0); + slp->slchild = sh.st.staklist; + } +#if SHOPT_KIA + shlex.current = current; +#endif /* SHOPT_KIA */ + if(jmpval) + { + if(slp && slp->slptr) + { + sh.st.staklist = slp->slnext; + stakdelete(slp->slptr); + } + siglongjmp(*sh.jmplist,jmpval); + } + sh.st.staklist = (struct slnod*)slp; + last = fctell(); + fp->functline = (last-first); + fp->functtre = t; + if(shlex.sh->funlog) + { + if(fcfill()>0) + fcseek(-1); + shlex.sh->funlog = 0; + } +#if SHOPT_KIA + if(shlex.kiafile) + kiaentity(t->funct.functnam,-1,'p',t->funct.functline,sh.inlineno-1,shlex.current,'p',0,""); +#endif /* SHOPT_KIA */ + return(t); +} + +/* + * Compound assignment + */ +static struct argnod *assign(register struct argnod *ap) +{ + register int n; + register Shnode_t *t, **tp; + register struct comnod *ac; + int array=0; + Namval_t *np; + n = strlen(ap->argval)-1; + if(ap->argval[n]!='=') + sh_syntax(); + if(ap->argval[n-1]=='+') + { + ap->argval[n--]=0; + array = ARG_APPEND; + } + /* shift right */ + while(n > 0) + { + ap->argval[n] = ap->argval[n-1]; + n--; + } + *ap->argval=0; + t = getnode(fornod); + t->for_.fornam = (char*)(ap->argval+1); + t->for_.fortyp = sh_getlineno(); + tp = &t->for_.fortre; + ap->argchn.ap = (struct argnod*)t; + ap->argflag &= ARG_QUOTED; + ap->argflag |= array; + shlex.assignok = SH_ASSIGN; + array=0; + if((n=skipnl(0))==RPAREN || n==LPAREN) + { + int index= 0; + struct argnod **settail; + ac = (struct comnod*)getnode(comnod); + settail= &ac->comset; + memset((void*)ac,0,sizeof(*ac)); + ac->comline = sh_getlineno(); + while(n==LPAREN) + { + struct argnod *ap; + ap = (struct argnod*)stakseek(ARGVAL); + ap->argflag= ARG_ASSIGN; + sfprintf(stkstd,"[%d]=",index++); + ap = (struct argnod*)stakfreeze(1); + ap->argnxt.ap = 0; + ap = assign(ap); + ap->argflag |= ARG_MESSAGE; + *settail = ap; + settail = &(ap->argnxt.ap); + n = skipnl(0); + } + } + else if(n) + sh_syntax(); + else if(!(shlex.arg->argflag&ARG_ASSIGN) && !((np=nv_search(shlex.arg->argval,sh.fun_tree,0)) && nv_isattr(np,BLT_DCL))) + array=SH_ARRAY; + while(1) + { + if((n=shlex.token)==RPAREN) + break; + if(n==FUNCTSYM || n==SYMRES) + ac = (struct comnod*)funct(); + else + ac = (struct comnod*)simple(SH_NOIO|SH_ASSIGN|array,NIL(struct ionod*)); + if((n=shlex.token)==RPAREN) + break; + if(n!=NL && n!=';') + sh_syntax(); + shlex.assignok = SH_ASSIGN; + if((n=skipnl(0)) || array) + { + if(n==RPAREN) + break; + if(array || n!=FUNCTSYM) + sh_syntax(); + } + if((n!=FUNCTSYM) && !(shlex.arg->argflag&ARG_ASSIGN) && !((np=nv_search(shlex.arg->argval,sh.fun_tree,0)) && nv_isattr(np,BLT_DCL))) + { + struct argnod *arg = shlex.arg; + if(n!=0) + sh_syntax(); + /* check for sys5 style function */ + if(sh_lex()!=LPAREN || sh_lex()!=RPAREN) + { + shlex.arg = arg; + shlex.token = 0; + sh_syntax(); + } + shlex.arg = arg; + shlex.token = SYMRES; + } + t = makelist(TLST,(Shnode_t*)ac,t); + *tp = t; + tp = &t->lst.lstrit; + } + *tp = (Shnode_t*)ac; + shlex.assignok = 0; + return(ap); +} + +/* + * item + * + * ( cmd ) [ < in ] [ > out ] + * word word* [ < in ] [ > out ] + * if ... then ... else ... fi + * for ... while ... do ... done + * case ... in ... esac + * begin ... end + */ + +static Shnode_t *item(int flag) +{ + register Shnode_t *t; + register struct ionod *io; + register int tok = (shlex.token&0xff); + int savwdval = shlex.lasttok; + int savline = shlex.lastline; + int showme=0; + if(!(flag&SH_NOIO) && (tok=='<' || tok=='>')) + io=inout(NIL(struct ionod*),1); + else + io=0; + if((tok=shlex.token) && tok!=EOFSYM && tok!=FUNCTSYM) + { + shlex.lastline = sh_getlineno(); + shlex.lasttok = shlex.token; + } + switch(tok) + { + /* [[ ... ]] test expression */ + case BTESTSYM: + t = test_expr(ETESTSYM); + t->tre.tretyp &= ~TTEST; + break; + /* ((...)) arithmetic expression */ + case EXPRSYM: + t = getanode(shlex.arg); + sh_lex(); + goto done; + + /* case statement */ + case CASESYM: + { + int savetok = shlex.lasttok; + int saveline = shlex.lastline; + t = getnode(swnod); + if(sh_lex()) + sh_syntax(); + t->sw.swarg=shlex.arg; + t->sw.swtyp=TSW; + t->sw.swio = 0; + t->sw.swtyp |= FLINENO; + t->sw.swline = sh.inlineno; + if((tok=skipnl(0))!=INSYM && tok!=LBRACE) + sh_syntax(); + if(!(t->sw.swlst=syncase(tok==INSYM?ESACSYM:RBRACE)) && shlex.token==EOFSYM) + { + shlex.lasttok = savetok; + shlex.lastline = saveline; + sh_syntax(); + } + break; + } + + /* if statement */ + case IFSYM: + { + register Shnode_t *tt; + t = getnode(ifnod); + t->if_.iftyp=TIF; + t->if_.iftre=sh_cmd(THENSYM,SH_NL); + t->if_.thtre=sh_cmd(ELSESYM,SH_NL|SH_SEMI); + tok = shlex.token; + t->if_.eltre=(tok==ELSESYM?sh_cmd(FISYM,SH_NL|SH_SEMI): + (tok==ELIFSYM?(shlex.token=IFSYM, tt=item(SH_NOIO)):0)); + if(tok==ELIFSYM) + { + if(!tt || tt->tre.tretyp!=TSETIO) + goto done; + t->if_.eltre = tt->fork.forktre; + tt->fork.forktre = t; + t = tt; + goto done; + } + break; + } + + /* for and select statement */ + case FORSYM: + case SELECTSYM: + { + t = getnode(fornod); + t->for_.fortyp=(shlex.token==FORSYM?TFOR:TSELECT); + t->for_.forlst=0; + t->for_.forline = sh.inlineno; + if(sh_lex()) + { + if(shlex.token!=EXPRSYM || t->for_.fortyp!=TFOR) + sh_syntax(); + /* arithmetic for */ + t = arithfor(t); + break; + } + t->for_.fornam=(char*) shlex.arg->argval; + t->for_.fortyp |= FLINENO; +#if SHOPT_KIA + if(shlex.kiafile) + writedefs(shlex.arg,sh.inlineno,'v',NIL(struct argnod*)); +#endif /* SHOPT_KIA */ + while((tok=sh_lex())==NL); + if(tok==INSYM) + { + if(sh_lex()) + { + if(shlex.token != NL && shlex.token !=';') + sh_syntax(); + /* some Linux scripts assume this */ + if(sh_isoption(SH_NOEXEC)) + errormsg(SH_DICT,ERROR_warn(0),e_lexemptyfor,sh.inlineno-(shlex.token=='\n')); + t->for_.forlst = (struct comnod*)getnode(comnod); + (t->for_.forlst)->comarg = 0; + (t->for_.forlst)->comset = 0; + (t->for_.forlst)->comnamp = 0; + (t->for_.forlst)->comnamq = 0; + (t->for_.forlst)->comstate = 0; + (t->for_.forlst)->comio = 0; + (t->for_.forlst)->comtyp = 0; + } + else + t->for_.forlst=(struct comnod*)simple(SH_NOIO,NIL(struct ionod*)); + if(shlex.token != NL && shlex.token !=';') + sh_syntax(); + tok = skipnl(0); + } + /* 'for i;do cmd' is valid syntax */ + else if(tok==';') + tok=sh_lex(); + if(tok!=DOSYM && tok!=LBRACE) + sh_syntax(); + loop_level++; + t->for_.fortre=sh_cmd(tok==DOSYM?DONESYM:RBRACE,SH_NL|SH_SEMI); + if(--loop_level==0) + label_last = label_list; + break; + } + + /* This is the code for parsing function definitions */ + case FUNCTSYM: + return(funct()); + +#if SHOPT_NAMESPACE + case NSPACESYM: + t = getnode(fornod); + t->for_.fortyp=TNSPACE; + t->for_.forlst=0; + if(sh_lex()) + sh_syntax(); + t->for_.fornam=(char*) shlex.arg->argval; + while((tok=sh_lex())==NL); + if(tok!=LBRACE) + sh_syntax(); + t->for_.fortre = sh_cmd(RBRACE,SH_NL); + break; +#endif /* SHOPT_NAMESPACE */ + + /* while and until */ + case WHILESYM: + case UNTILSYM: + t = getnode(whnod); + t->wh.whtyp=(shlex.token==WHILESYM ? TWH : TUN); + loop_level++; + t->wh.whtre = sh_cmd(DOSYM,SH_NL); + t->wh.dotre = sh_cmd(DONESYM,SH_NL|SH_SEMI); + if(--loop_level==0) + label_last = label_list; + t->wh.whinc = 0; + break; + + case LABLSYM: + { + register struct argnod *argp = label_list; + while(argp) + { + if(strcmp(argp->argval,shlex.arg->argval)==0) + errormsg(SH_DICT,ERROR_exit(3),e_lexsyntax3,sh.inlineno,argp->argval); + argp = argp->argnxt.ap; + } + shlex.arg->argnxt.ap = label_list; + label_list = shlex.arg; + label_list->argchn.len = sh_getlineno(); + label_list->argflag = loop_level; + skipnl(flag); + if(!(t = item(SH_NL))) + sh_syntax(); + tok = (t->tre.tretyp&(COMSCAN|COMSCAN-1)); + if(sh_isoption(SH_NOEXEC) && tok!=TWH && tok!=TUN && tok!=TFOR && tok!=TSELECT) + errormsg(SH_DICT,ERROR_warn(0),e_lexlabignore,label_list->argchn.len,label_list->argval); + return(t); + } + + /* command group with {...} */ + case LBRACE: + t = sh_cmd(RBRACE,SH_NL); + break; + + case LPAREN: + t = getnode(parnod); + t->par.partre=sh_cmd(RPAREN,SH_NL); + t->par.partyp=TPAR; + break; + + default: + if(io==0) + return(0); + + case ';': + if(io==0) + { + if(!(flag&SH_SEMI)) + return(0); + if(sh_lex()==';') + sh_syntax(); + showme = FSHOWME; + } + /* simple command */ + case 0: + t = (Shnode_t*)simple(flag,io); + t->tre.tretyp |= showme; + return(t); + } + sh_lex(); + if(io=inout(io,0)) + { + if((tok=t->tre.tretyp&COMMSK) != TFORK) + tok = TSETIO; + t=makeparent(tok,t); + t->tre.treio=io; + } +done: + shlex.lasttok = savwdval; + shlex.lastline = savline; + return(t); +} + +/* + * This is for a simple command, for list, or compound assignment + */ +static Shnode_t *simple(int flag, struct ionod *io) +{ + register struct comnod *t; + register struct argnod *argp; + register int tok; + struct argnod **argtail; + struct argnod **settail; + int argno = 0; + int assignment = 0; + int key_on = (!(flag&SH_NOIO) && sh_isoption(SH_KEYWORD)); + int associative=0; + if((argp=shlex.arg) && (argp->argflag&ARG_ASSIGN) && argp->argval[0]=='[') + { + flag |= SH_ARRAY; + associative = 1; + } + t = (struct comnod*)getnode(comnod); + t->comio=io; /*initial io chain*/ + /* set command line number for error messages */ + t->comline = sh_getlineno(); + argtail = &(t->comarg); + t->comset = 0; + t->comnamp = 0; + t->comnamq = 0; + t->comstate = 0; + settail = &(t->comset); + while(shlex.token==0) + { + argp = shlex.arg; + if(*argp->argval==LBRACE && (flag&SH_FUNDEF) && argp->argval[1]==0) + { + shlex.token = LBRACE; + break; + } + if(associative && argp->argval[0]!='[') + sh_syntax(); + /* check for assignment argument */ + if((argp->argflag&ARG_ASSIGN) && assignment!=2) + { + *settail = argp; + settail = &(argp->argnxt.ap); + shlex.assignok = (flag&SH_ASSIGN)?SH_ASSIGN:1; + if(assignment) + { + struct argnod *ap=argp; + char *last, *cp; + if(assignment==1) + { + last = strchr(argp->argval,'='); + if((cp=strchr(argp->argval,'[')) && (cp < last)) + last = cp; + stakseek(ARGVAL); + stakwrite(argp->argval,last-argp->argval); + ap=(struct argnod*)stakfreeze(1); + ap->argflag = ARG_RAW; + ap->argchn.ap = 0; + } + *argtail = ap; + argtail = &(ap->argnxt.ap); + if(argno>=0) + argno++; + } + else /* alias substitutions allowed */ + shlex.aliasok = 1; + } + else + { + if(!(argp->argflag&ARG_RAW)) + argno = -1; + if(argno>=0 && argno++==0 && !(flag&SH_ARRAY) && *argp->argval!='/') + { + /* check for builtin command */ + Namval_t *np=nv_bfsearch(argp->argval,sh.fun_tree, (Namval_t**)&t->comnamq,(char**)0); + if((t->comnamp=(void*)np) && is_abuiltin(np) && + nv_isattr(np,BLT_DCL)) + { + assignment = 1+(*argp->argval=='a'); + key_on = 1; + } + } + *argtail = argp; + argtail = &(argp->argnxt.ap); + if(!(shlex.assignok=key_on) && !(flag&SH_NOIO)) + shlex.assignok = SH_COMPASSIGN; + shlex.aliasok = 0; + } + retry: + tok = sh_lex(); +#if SHOPT_DEVFD + if((tok==IPROCSYM || tok==OPROCSYM)) + { + Shnode_t *t; + int mode = (tok==OPROCSYM); + t = sh_cmd(RPAREN,SH_NL); + argp = (struct argnod*)stakalloc(sizeof(struct argnod)); + *argp->argval = 0; + argno = -1; + *argtail = argp; + argtail = &(argp->argnxt.ap); + argp->argchn.ap = (struct argnod*)makeparent(mode?TFORK|FPIN|FAMP|FPCL:TFORK|FPOU,t); + argp->argflag = (ARG_EXP|mode); + goto retry; + } +#endif /* SHOPT_DEVFD */ + if(tok==LPAREN) + { + if(argp->argflag&ARG_ASSIGN) + { + argp = assign(argp); + if(associative) + shlex.assignok |= SH_ASSIGN; + goto retry; + } + else if(argno==1 && !t->comset) + { + /* SVR2 style function */ + if(sh_lex() == RPAREN) + { + shlex.arg = argp; + return(funct()); + } + shlex.token = LPAREN; + } + } + else if(flag&SH_ASSIGN) + { + if(tok==RPAREN) + break; + else if(tok==NL && (flag&SH_ARRAY)) + goto retry; + } + if(!(flag&SH_NOIO)) + { + if(io) + { + while(io->ionxt) + io = io->ionxt; + io->ionxt = inout((struct ionod*)0,0); + } + else + t->comio = io = inout((struct ionod*)0,0); + } + } + *argtail = 0; + t->comtyp = TCOM; +#if SHOPT_KIA + if(shlex.kiafile && !(flag&SH_NOIO)) + { + register Namval_t *np=(Namval_t*)t->comnamp; + unsigned long r=0; + int line = t->comline; + argp = t->comarg; + if(np) + r = kiaentity(nv_name(np),-1,'p',-1,0,shlex.unknown,'b',0,""); + else if(argp) + r = kiaentity(sh_argstr(argp),-1,'p',-1,0,shlex.unknown,'c',0,""); + if(r>0) + sfprintf(shlex.kiatmp,"p;%..64d;p;%..64d;%d;%d;c;\n",shlex.current,r,line,line); + if(t->comset && argno==0) + writedefs(t->comset,line,'v',t->comarg); + else if(np && nv_isattr(np,BLT_DCL)) + writedefs(argp,line,0,NIL(struct argnod*)); + else if(argp && strcmp(argp->argval,"read")==0) + writedefs(argp,line,0,NIL(struct argnod*)); +#if 0 + else if(argp && strcmp(argp->argval,"unset")==0) + writedefs(argp,line,'u',NIL(struct argnod*)); +#endif + else if(argp && *argp->argval=='.' && argp->argval[1]==0 && (argp=argp->argnxt.ap)) + { + r = kiaentity(sh_argstr(argp),-1,'p',0,0,shlex.script,'d',0,""); + sfprintf(shlex.kiatmp,"p;%..64d;p;%..64d;%d;%d;d;\n",shlex.current,r,line,line); + } + } +#endif /* SHOPT_KIA */ + if(t->comnamp && (argp=t->comarg->argnxt.ap)) + { + Namval_t *np=(Namval_t*)t->comnamp; + if((np==SYSBREAK || np==SYSCONT) && (argp->argflag&ARG_RAW) && !isdigit(*argp->argval)) + { + register char *cp = argp->argval; + /* convert break/continue labels to numbers */ + tok = 0; + for(argp=label_list;argp!=label_last;argp=argp->argnxt.ap) + { + if(strcmp(cp,argp->argval)) + continue; + tok = loop_level-argp->argflag; + if(tok>=1) + { + argp = t->comarg->argnxt.ap; + if(tok>9) + { + argp->argval[1] = '0'+tok%10; + argp->argval[2] = 0; + tok /= 10; + } + else + argp->argval[1] = 0; + *argp->argval = '0'+tok; + } + break; + } + if(sh_isoption(SH_NOEXEC) && tok==0) + errormsg(SH_DICT,ERROR_warn(0),e_lexlabunknown,sh.inlineno-(shlex.token=='\n'),cp); + } + else if(sh_isoption(SH_NOEXEC) && np==SYSSET && ((tok= *argp->argval)=='-'||tok=='+') && + (argp->argval[1]==0||strchr(argp->argval,'k'))) + errormsg(SH_DICT,ERROR_warn(0),e_lexobsolete5,sh.inlineno-(shlex.token=='\n'),argp->argval); + } + /* expand argument list if possible */ + if(argno>0) + t->comarg = qscan(t,argno); + else if(t->comarg) + t->comtyp |= COMSCAN; + shlex.aliasok = 0; + return((Shnode_t*)t); +} + +/* + * skip past newlines but issue prompt if interactive + */ +static int skipnl(int flag) +{ + register int token; + while((token=sh_lex())==NL); + if(token==';' && !(flag&SH_SEMI)) + sh_syntax(); + return(token); +} + +/* + * check for and process and i/o redirections + * if flag>0 then an alias can be in the next word + * if flag<0 only one redirection will be processed + */ +static struct ionod *inout(struct ionod *lastio,int flag) +{ + register int iof = shlex.digits, token=shlex.token; + register struct ionod *iop; + char *iovname=0; +#if SHOPT_BASH + register int errout=0; +#endif + if(token==IOVNAME) + { + iovname=shlex.arg->argval+1; + token= sh_lex(); + iof = 0; + } + switch(token&0xff) + { + case '<': + if(token==IODOCSYM) + iof |= (IODOC|IORAW); + else if(token==IOMOV0SYM) + iof |= IOMOV; + else if(token==IORDWRSYM) + iof |= IORDW; + else if((token&SYMSHARP) == SYMSHARP) + { + int n; + iof |= IOLSEEK; + if(fcgetc(n)=='#') + iof |= IOCOPY; + else if(n>0) + fcseek(-1); + } + break; + + case '>': +#if SHOPT_BASH + if(iof<0) + { + errout = 1; + iof = 1; + } +#endif + iof |= IOPUT; + if(token==IOAPPSYM) + iof |= IOAPP; + else if(token==IOMOV1SYM) + iof |= IOMOV; + else if(token==IOCLOBSYM) + iof |= IOCLOB; + else if((token&SYMSHARP) == SYMSHARP) + iof |= IOLSEEK; + break; + + default: + return(lastio); + } + shlex.digits=0; + iop=(struct ionod*) stakalloc(sizeof(struct ionod)); + iop->iodelim = 0; + if(token=sh_lex()) + { + if(token==RPAREN && (iof&IOLSEEK) && shlex.comsub) + { + shlex.arg = (struct argnod*)stakalloc(sizeof(struct argnod)+3); + strcpy(shlex.arg->argval,"CUR"); + shlex.arg->argflag = ARG_RAW; + iof |= IOARITH; + fcseek(-1); + } + else if(token==EXPRSYM && (iof&IOLSEEK)) + iof |= IOARITH; + else + sh_syntax(); + } + iop->ioname=shlex.arg->argval; + iop->iovname = iovname; + if(iof&IODOC) + { + if(shlex.digits==2) + { + iof |= IOSTRG; + if(!(shlex.arg->argflag&ARG_RAW)) + iof &= ~IORAW; + } + else + { + if(!shlex.sh->heredocs) + shlex.sh->heredocs = sftmp(HERE_MEM); + iop->iolst=shlex.heredoc; + shlex.heredoc=iop; + if(shlex.arg->argflag&ARG_QUOTED) + iof |= IOQUOTE; + if(shlex.digits==3) + iof |= IOLSEEK; + if(shlex.digits) + iof |= IOSTRIP; + } + } + else + { + iop->iolst = 0; + if(shlex.arg->argflag&ARG_RAW) + iof |= IORAW; + } + iop->iofile=iof; + if(flag>0) + /* allow alias substitutions and parameter assignments */ + shlex.aliasok = shlex.assignok = 1; +#if SHOPT_KIA + if(shlex.kiafile) + { + int n = sh.inlineno-(shlex.token=='\n'); + if(!(iof&IOMOV)) + { + unsigned long r=kiaentity((iof&IORAW)?sh_fmtq(iop->ioname):iop->ioname,-1,'f',0,0,shlex.script,'f',0,""); + sfprintf(shlex.kiatmp,"p;%..64d;f;%..64d;%d;%d;%c;%d\n",shlex.current,r,n,n,(iof&IOPUT)?((iof&IOAPP)?'a':'w'):((iof&IODOC)?'h':'r'),iof&IOUFD); + } + } +#endif /* SHOPT_KIA */ + if(flag>=0) + { + struct ionod *ioq=iop; + sh_lex(); +#if SHOPT_BASH + if(errout) + { + /* redirect standard output to standard error */ + ioq = (struct ionod*)stakalloc(sizeof(struct ionod)); + ioq->ioname = "1"; + ioq->iolst = 0; + ioq->iodelim = 0; + ioq->iofile = IORAW|IOPUT|IOMOV|2; + iop->ionxt=ioq; + } +#endif + ioq->ionxt=inout(lastio,flag); + } + else + iop->ionxt=0; + return(iop); +} + +/* + * convert argument chain to argument list when no special arguments + */ + +static struct argnod *qscan(struct comnod *ac,int argn) +{ + register char **cp; + register struct argnod *ap; + register struct dolnod* dp; + register int special=0; + /* special hack for test -t compatibility */ + if((Namval_t*)ac->comnamp==SYSTEST) + special = 2; + else if(*(ac->comarg->argval)=='[' && ac->comarg->argval[1]==0) + special = 3; + if(special) + { + ap = ac->comarg->argnxt.ap; + if(argn==(special+1) && ap->argval[1]==0 && *ap->argval=='!') + ap = ap->argnxt.ap; + else if(argn!=special) + special=0; + } + if(special) + { + const char *message; + if(strcmp(ap->argval,"-t")) + { + message = "line %d: Invariant test"; + special=0; + } + else + { + message = "line %d: -t requires argument"; + argn++; + } + if(sh_isoption(SH_NOEXEC)) + errormsg(SH_DICT,ERROR_warn(0),message,ac->comline); + } + /* leave space for an extra argument at the front */ + dp = (struct dolnod*)stakalloc((unsigned)sizeof(struct dolnod) + ARG_SPARE*sizeof(char*) + argn*sizeof(char*)); + cp = dp->dolval+ARG_SPARE; + dp->dolnum = argn; + dp->dolbot = ARG_SPARE; + ap = ac->comarg; + while(ap) + { + *cp++ = ap->argval; + ap = ap->argnxt.ap; + } + if(special==3) + { + cp[0] = cp[-1]; + cp[-1] = "1"; + cp++; + } + else if(special) + *cp++ = "1"; + *cp = 0; + return((struct argnod*)dp); +} + +static Shnode_t *test_expr(int sym) +{ + register Shnode_t *t = test_or(); + if(shlex.token!=sym) + sh_syntax(); + return(t); +} + +static Shnode_t *test_or(void) +{ + register Shnode_t *t = test_and(); + while(shlex.token==ORFSYM) + t = makelist(TORF|TTEST,t,test_and()); + return(t); +} + +static Shnode_t *test_and(void) +{ + register Shnode_t *t = test_primary(); + while(shlex.token==ANDFSYM) + t = makelist(TAND|TTEST,t,test_primary()); + return(t); +} + +/* + * convert =~ into == ~(E) + */ +static void ere_match(void) +{ + Sfio_t *base, *iop = sfopen((Sfio_t*)0," ~(E)","s"); + register int c; + while( fcgetc(c),(c==' ' || c=='\t')); + if(c) + fcseek(-1); + if(!(base=fcfile())) + base = sfopen(NIL(Sfio_t*),fcseek(0),"s"); + fcclose(); + sfstack(base,iop); + fcfopen(base); +} + +static Shnode_t *test_primary(void) +{ + register struct argnod *arg; + register Shnode_t *t; + register int num,token; + token = skipnl(0); + num = shlex.digits; + switch(token) + { + case '(': + t = test_expr(')'); + t = makelist(TTST|TTEST|TPAREN ,t, (Shnode_t*)pointerof(sh.inlineno)); + break; + case '!': + if(!(t = test_primary())) + sh_syntax(); + t->tre.tretyp |= TNEGATE; + return(t); + case TESTUNOP: + if(sh_lex()) + sh_syntax(); +#if SHOPT_KIA + if(shlex.kiafile && !strchr("sntzoOG",num)) + { + int line = sh.inlineno- (shlex.token==NL); + unsigned long r; + r=kiaentity(sh_argstr(shlex.arg),-1,'f',0,0,shlex.script,'t',0,""); + sfprintf(shlex.kiatmp,"p;%..64d;f;%..64d;%d;%d;t;\n",shlex.current,r,line,line); + } +#endif /* SHOPT_KIA */ + t = makelist(TTST|TTEST|TUNARY|(num<<TSHIFT), + (Shnode_t*)shlex.arg,(Shnode_t*)shlex.arg); + t->tst.tstline = sh.inlineno; + break; + /* binary test operators */ + case 0: + arg = shlex.arg; + if((token=sh_lex())==TESTBINOP) + { + num = shlex.digits; + if(num==TEST_REP) + { + ere_match(); + num = TEST_PEQ; + } + } + else if(token=='<') + num = TEST_SLT; + else if(token=='>') + num = TEST_SGT; + else if(token==ANDFSYM||token==ORFSYM||token==ETESTSYM||token==RPAREN) + { + t = makelist(TTST|TTEST|TUNARY|('n'<<TSHIFT), + (Shnode_t*)arg,(Shnode_t*)arg); + t->tst.tstline = sh.inlineno; + return(t); + } + else + sh_syntax(); +#if SHOPT_KIA + if(shlex.kiafile && (num==TEST_EF||num==TEST_NT||num==TEST_OT)) + { + int line = sh.inlineno- (shlex.token==NL); + unsigned long r; + r=kiaentity(sh_argstr(shlex.arg),-1,'f',0,0,shlex.current,'t',0,""); + sfprintf(shlex.kiatmp,"p;%..64d;f;%..64d;%d;%d;t;\n",shlex.current,r,line,line); + } +#endif /* SHOPT_KIA */ + if(sh_lex()) + sh_syntax(); + if(num&TEST_PATTERN) + { + if(shlex.arg->argflag&(ARG_EXP|ARG_MAC)) + num &= ~TEST_PATTERN; + } + t = getnode(tstnod); + t->lst.lsttyp = TTST|TTEST|TBINARY|(num<<TSHIFT); + t->lst.lstlef = (Shnode_t*)arg; + t->lst.lstrit = (Shnode_t*)shlex.arg; + t->tst.tstline = sh.inlineno; +#if SHOPT_KIA + if(shlex.kiafile && (num==TEST_EF||num==TEST_NT||num==TEST_OT)) + { + int line = sh.inlineno-(shlex.token==NL); + unsigned long r; + r=kiaentity(sh_argstr(shlex.arg),-1,'f',0,0,shlex.current,'t',0,""); + sfprintf(shlex.kiatmp,"p;%..64d;f;%..64d;%d;%d;t;\n",shlex.current,r,line,line); + } +#endif /* SHOPT_KIA */ + break; + default: + return(0); + } + skipnl(0); + return(t); +} + +#if SHOPT_KIA +/* + * return an entity checksum + * The entity is created if it doesn't exist + */ +unsigned long kiaentity(const char *name,int len,int type,int first,int last,unsigned long parent, int pkind, int width, const char *attr) +{ + Namval_t *np; + long offset = staktell(); + stakputc(type); + if(len>0) + stakwrite(name,len); + else + { + if(type=='p') + stakputs(path_basename(name)); + else + stakputs(name); + } + stakputc(0); + np = nv_search(stakptr(offset),shlex.entity_tree,NV_ADD); + stakseek(offset); + np->nvalue.i = pkind; + nv_setsize(np,width); + if(!nv_isattr(np,NV_TAGGED) && first>=0) + { + nv_onattr(np,NV_TAGGED); + if(!pkind) + pkind = '0'; + if(len>0) + sfprintf(shlex.kiafile,"%..64d;%c;%.*s;%d;%d;%..64d;%..64d;%c;%d;%s\n",np->hash,type,len,name,first,last,parent,shlex.fscript,pkind,width,attr); + else + sfprintf(shlex.kiafile,"%..64d;%c;%s;%d;%d;%..64d;%..64d;%c;%d;%s\n",np->hash,type,name,first,last,parent,shlex.fscript,pkind,width,attr); + } + return(np->hash); +} + +static void kia_add(register Namval_t *np, void *data) +{ + char *name = nv_name(np); + NOT_USED(data); + kiaentity(name+1,-1,*name,0,-1,(*name=='p'?shlex.unknown:shlex.script),np->nvalue.i,nv_size(np),""); +} + +int kiaclose(void) +{ + register off_t off1,off2; + register int n; + if(shlex.kiafile) + { + unsigned long r = kiaentity(shlex.scriptname,-1,'p',-1,sh.inlineno-1,0,'s',0,""); + kiaentity(shlex.scriptname,-1,'p',1,sh.inlineno-1,r,'s',0,""); + kiaentity(shlex.scriptname,-1,'f',1,sh.inlineno-1,r,'s',0,""); + nv_scan(shlex.entity_tree,kia_add,(void*)0,NV_TAGGED,0); + off1 = sfseek(shlex.kiafile,(off_t)0,SEEK_END); + sfseek(shlex.kiatmp,(off_t)0,SEEK_SET); + sfmove(shlex.kiatmp,shlex.kiafile,SF_UNBOUND,-1); + off2 = sfseek(shlex.kiafile,(off_t)0,SEEK_END); +#ifdef SF_BUFCONST + if(off2==off1) + n= sfprintf(shlex.kiafile,"DIRECTORY\nENTITY;%lld;%d\nDIRECTORY;",(Sflong_t)shlex.kiabegin,(size_t)(off1-shlex.kiabegin)); + else + n= sfprintf(shlex.kiafile,"DIRECTORY\nENTITY;%lld;%d\nRELATIONSHIP;%lld;%d\nDIRECTORY;",(Sflong_t)shlex.kiabegin,(size_t)(off1-shlex.kiabegin),(Sflong_t)off1,(size_t)(off2-off1)); + if(off2 >= INT_MAX) + off2 = -(n+12); + sfprintf(shlex.kiafile,"%010.10lld;%010d\n",(Sflong_t)off2+10, n+12); +#else + if(off2==off1) + n= sfprintf(shlex.kiafile,"DIRECTORY\nENTITY;%d;%d\nDIRECTORY;",shlex.kiabegin,off1-shlex.kiabegin); + else + n= sfprintf(shlex.kiafile,"DIRECTORY\nENTITY;%d;%d\nRELATIONSHIP;%d;%d\nDIRECTORY;",shlex.kiabegin,off1-shlex.kiabegin,off1,off2-off1); + sfprintf(shlex.kiafile,"%010d;%010d\n",off2+10, n+12); +#endif + } + return(sfclose(shlex.kiafile)); +} +#endif /* SHOPT_KIA */ diff --git a/usr/src/lib/libshell/common/sh/path.c b/usr/src/lib/libshell/common/sh/path.c new file mode 100644 index 0000000000..a07a9d6e00 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/path.c @@ -0,0 +1,1697 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <fcin.h> +#include <ls.h> +#include <nval.h> +#include <dlldefs.h> +#include "variables.h" +#include "path.h" +#include "io.h" +#include "jobs.h" +#include "history.h" +#include "test.h" +#include "FEATURE/externs" +#if SHOPT_PFSH +# ifdef _hdr_exec_attr +# include <exec_attr.h> +# else +# undef SHOPT_PFSH +# endif +#endif + +#define RW_ALL (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH) +#define LIBCMD "cmd" + + +static int canexecute(char*,int); +static void funload(Shell_t*,int,const char*); +static void exscript(Shell_t*,char*, char*[], char**); +static int path_chkpaths(Pathcomp_t*,Pathcomp_t*,Pathcomp_t*,int); + +static const char *std_path; + +static int onstdpath(const char *name) +{ + register const char *cp = std_path, *sp; + if(cp) + while(*cp) + { + for(sp=name; *sp && (*cp == *sp); sp++,cp++); + if(*sp==0 && (*cp==0 || *cp==':')) + return(1); + while(*cp && *cp++!=':'); + } + return(0); +} + +static int path_pfexecve(const char *path, char *argv[],char *const envp[]) +{ +#if SHOPT_PFSH + char resolvedpath[PATH_MAX + 1]; + if(!sh_isoption(SH_PFSH)) + return(execve(path, argv, envp)); + /* Solaris implements realpath(3C) using the resolvepath(2) */ + /* system call so we can save us to call access(2) first */ + if (!realpath(path, resolvedpath)) + return -1; + + /* we can exec the command directly instead of via pfexec(1) if */ + /* there is a matching entry without attributes in exec_attr(4) */ + if (sh.user && *sh.user) + { + execattr_t *pf; + if(pf=getexecuser(sh.user, KV_COMMAND, resolvedpath, GET_ONE)) + { + if (!pf->attr || pf->attr->length == 0) + { + int r = execve(path, argv, envp); + free_execattr(pf); + return r; + } + free_execattr(pf); + } + else + { + errno = ENOENT; + return -1; + } + } + --argv; + argv[0] = argv[1]; + argv[1] = resolvedpath; + return(execve("/usr/bin/pfexec", argv, envp)); +#else + return(execve(path, argv, envp)); +#endif +} + + +static pid_t _spawnveg(const char *path, char* const argv[], char* const envp[], pid_t pid) +{ + int waitsafe = job.waitsafe; + job_lock(); + pid = spawnveg(path,argv,envp,pid); + job.waitsafe = waitsafe; + job_unlock(); + return(pid); +} +/* + * used with command -x to run the command in multiple passes + * spawn is non-zero when invoked via spawn + * the exitval is set to the maximum for each execution + */ +static pid_t path_xargs(const char *path, char *argv[],char *const envp[], int spawn) +{ + register char *cp, **av, **xv; + char **avlast= &argv[sh.xargmax], **saveargs=0; + char *const *ev; + long size, left; + int nlast=1,n,exitval=0; + pid_t pid; + if(sh.xargmin < 0) + return((pid_t)-1); + size = sh.lim.arg_max-1024; + for(ev=envp; cp= *ev; ev++) + size -= strlen(cp)-1; + for(av=argv; (cp= *av) && av< &argv[sh.xargmin]; av++) + size -= strlen(cp)-1; + for(av=avlast; cp= *av; av++,nlast++) + size -= strlen(cp)-1; + av = &argv[sh.xargmin]; + if(!spawn) + job_clear(); + sh.exitval = 0; + while(av<avlast) + { + for(xv=av,left=size; left>0 && av<avlast;) + left -= strlen(*av++)+1; + /* leave at least two for last */ + if(left<0 && (avlast-av)<2) + av--; + if(xv==&argv[sh.xargmin]) + { + n = nlast*sizeof(char*); + saveargs = (char**)malloc(n); + memcpy((void*)saveargs, (void*)av, n); + memcpy((void*)av,(void*)avlast,n); + } + else + { + for(n=sh.xargmin; xv < av; xv++) + argv[n++] = *xv; + for(xv=avlast; cp= *xv; xv++) + argv[n++] = cp; + argv[n] = 0; + } + if(saveargs || av<avlast || (exitval && !spawn)) + { + if((pid=_spawnveg(path,argv,envp,0)) < 0) + return(-1); + job_post(pid,0); + job_wait(pid); + if(sh.exitval>exitval) + exitval = sh.exitval; + if(saveargs) + { + memcpy((void*)av,saveargs,n); + free((void*)saveargs); + saveargs = 0; + } + } + else if(spawn && !sh_isoption(SH_PFSH)) + { + sh.xargexit = exitval; + return(_spawnveg(path,argv,envp,spawn>>1)); + } + else + return((pid_t)path_pfexecve(path,argv,envp)); + } + if(!spawn) + exit(exitval); + return((pid_t)-1); +} + +/* + * make sure PWD is set up correctly + * Return the present working directory + * Invokes getcwd() if flag==0 and if necessary + * Sets the PWD variable to this value + */ +char *path_pwd(int flag) +{ + register char *cp; + register char *dfault = (char*)e_dot; + register int count = 0; + Shell_t *shp = &sh; + if(shp->pwd) + return((char*)shp->pwd); + while(1) + { + /* try from lowest to highest */ + switch(count++) + { + case 0: + cp = nv_getval(PWDNOD); + break; + case 1: + cp = nv_getval(HOME); + break; + case 2: + cp = "/"; + break; + case 3: + cp = (char*)e_crondir; + if(flag) /* skip next case when non-zero flag */ + ++count; + break; + case 4: + { + if(cp=getcwd(NIL(char*),0)) + { + nv_offattr(PWDNOD,NV_NOFREE); + nv_unset(PWDNOD); + PWDNOD->nvalue.cp = cp; + goto skip; + } + break; + } + case 5: + return(dfault); + } + if(cp && *cp=='/' && test_inode(cp,e_dot)) + break; + } + if(count>1) + { + nv_offattr(PWDNOD,NV_NOFREE); + nv_putval(PWDNOD,cp,NV_RDONLY); + } +skip: + nv_onattr(PWDNOD,NV_NOFREE|NV_EXPORT); + shp->pwd = (char*)(PWDNOD->nvalue.cp); + return(cp); +} + +static void free_bltin(Namval_t *np,void *data) +{ + register Pathcomp_t *pp= (Pathcomp_t*)data; + if(pp->flags&PATH_STD_DIR) + { + int offset=staktell();; + if(strcmp(pp->name,"/bin")==0 || memcmp(pp->name,np->nvname,pp->len) || np->nvname[pp->len]!='/') + return; + stakputs("/bin"); + stakputs(np->nvname+pp->len+1); + stakputc(0); + sh_addbuiltin(stakptr(offset),np->nvalue.bfp,NiL); + stakseek(offset); + return; + } + if((void*)np->nvenv==pp->bltin_lib) + dtdelete(sh_bltin_tree(),np); +} + +/* + * delete current Pathcomp_t structure + */ +void path_delete(Pathcomp_t *first) +{ + register Pathcomp_t *pp=first, *old=0, *ppnext; + while(pp) + { + ppnext = pp->next; + if(--pp->refcount<=0) + { + if(pp->lib) + free((void*)pp->lib); + if(pp->blib) + free((void*)pp->blib); + if(pp->bltin_lib || (pp->flags&PATH_STD_DIR)) + { + nv_scan(sh_bltin_tree(),free_bltin,pp,0,0); + if(pp->bltin_lib) + dlclose(pp->bltin_lib); + } + free((void*)pp); + if(old) + old->next = ppnext; + } + else + old = pp; + pp = ppnext; + } +} + +/* + * returns library variable from .paths + * The value might be returned on the stack overwriting path + */ +static char *path_lib(Pathcomp_t *pp, char *path) +{ + register char *last = strrchr(path,'/'); + register int r; + struct stat statb; + if(last) + *last = 0; + else + path = "."; + r = stat(path,&statb); + if(last) + *last = '/'; + if(r>=0) + { + Pathcomp_t pcomp; + char save[8]; + for( ;pp; pp=pp->next) + { + if(pp->ino==statb.st_ino && pp->dev==statb.st_dev) + return(pp->lib); + } + pcomp.len = 0; + if(last) + pcomp.len = last-path; + memcpy((void*)save, (void*)stakptr(PATH_OFFSET+pcomp.len),sizeof(save)); + if(path_chkpaths((Pathcomp_t*)0,(Pathcomp_t*)0,&pcomp,PATH_OFFSET)) + return(stakfreeze(1)); + memcpy((void*)stakptr(PATH_OFFSET+pcomp.len),(void*)save,sizeof(save)); + } + return(0); +} + +#if 0 +void path_dump(register Pathcomp_t *pp) +{ + sfprintf(sfstderr,"dump\n"); + while(pp) + { + sfprintf(sfstderr,"pp=%x dev=%d ino=%d len=%d flags=%o name=%.*s\n", + pp,pp->dev,pp->ino,pp->len,pp->flags,pp->len,pp->name); + pp = pp->next; + } +} +#endif + +/* + * write the next path to search on the current stack + * if last is given, all paths that come before <last> are skipped + * the next pathcomp is returned. + */ +Pathcomp_t *path_nextcomp(register Pathcomp_t *pp, const char *name, Pathcomp_t *last) +{ + stakseek(PATH_OFFSET); + if(*name=='/') + pp = 0; + else + { + for(;pp && pp!=last;pp=pp->next) + { + if(pp->flags&PATH_SKIP) + continue; + if(!last || *pp->name!='/') + break; + } + if(!pp) /* this should not happen */ + pp = last; + } + if(pp && (pp->name[0]!='.' || pp->name[1])) + { + if(*pp->name!='/') + { + stakputs(path_pwd(1)); + if(*stakptr(staktell()-1)!='/') + stakputc('/'); + } + stakwrite(pp->name,pp->len); + if(pp->name[pp->len-1]!='/') + stakputc('/'); + } + stakputs(name); + stakputc(0); + while(pp && pp!=last && (pp=pp->next)) + { + if(!(pp->flags&PATH_SKIP)) + return(pp); + } + return((Pathcomp_t*)0); +} + +static Pathcomp_t* defpath_init(Shell_t *shp) +{ + Pathcomp_t *pp = (void*)path_addpath((Pathcomp_t*)0,(std_path),PATH_PATH); + if(shp->defpathlist = (void*)pp) + pp->shp = shp; + return(pp); +} + +static void path_init(Shell_t *shp) +{ + const char *val; + Pathcomp_t *pp; + if(!std_path && !(std_path=astconf("PATH",NIL(char*),NIL(char*)))) + std_path = e_defpath; + if(val=nv_scoped((PATHNOD))->nvalue.cp) + { + pp = (void*)path_addpath((Pathcomp_t*)shp->pathlist,val,PATH_PATH); + if(shp->pathlist = (void*)pp) + pp->shp = shp; + } + else + { + if(!(pp=(Pathcomp_t*)shp->defpathlist)) + pp = defpath_init(shp); + shp->pathlist = (void*)path_dup(pp); + } + if(val=nv_scoped((FPATHNOD))->nvalue.cp) + { + pp = (void*)path_addpath((Pathcomp_t*)shp->pathlist,val,PATH_FPATH); + if(shp->pathlist = (void*)pp) + pp->shp = shp; + } +} + +/* + * returns that pathlist to search + */ +Pathcomp_t *path_get(register const char *name) +{ + register Shell_t *shp = &sh; + register Pathcomp_t *pp=0; + if(*name && strchr(name,'/')) + return(0); + if(!sh_isstate(SH_DEFPATH)) + { + if(!shp->pathlist) + path_init(shp); + pp = (Pathcomp_t*)shp->pathlist; + } + if(!pp && (!(PATHNOD)->nvalue.cp) || sh_isstate(SH_DEFPATH)) + { + if(!(pp=(Pathcomp_t*)shp->defpathlist)) + pp = defpath_init(shp); + } + return(pp); +} + +/* + * open file corresponding to name using path give by <pp> + */ +static int path_opentype(const char *name, register Pathcomp_t *pp, int fun) +{ + register int fd= -1; + struct stat statb; + Pathcomp_t *oldpp; + Shell_t *shp; + if(pp) + shp = pp->shp; + else + { + shp = sh_getinterp(); + if(!shp->pathlist) + path_init(shp); + } + if(!fun && strchr(name,'/')) + { + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,name); + } + do + { + pp = path_nextcomp(oldpp=pp,name,0); + while(oldpp && (oldpp->flags&PATH_SKIP)) + oldpp = oldpp->next; + if(fun && (!oldpp || !(oldpp->flags&PATH_FPATH))) + continue; + if((fd = sh_open(path_relative(stakptr(PATH_OFFSET)),O_RDONLY,0)) >= 0) + { + if(fstat(fd,&statb)<0 || S_ISDIR(statb.st_mode)) + { + errno = EISDIR; + sh_close(fd); + fd = -1; + } + } + } + while( fd<0 && pp); + if(fd>=0 && (fd = sh_iomovefd(fd)) > 0) + { + fcntl(fd,F_SETFD,FD_CLOEXEC); + shp->fdstatus[fd] |= IOCLEX; + } + return(fd); +} + +/* + * open file corresponding to name using path give by <pp> + */ +int path_open(const char *name, register Pathcomp_t *pp) +{ + return(path_opentype(name,pp,0)); +} + +/* + * given a pathname return the base name + */ + +char *path_basename(register const char *name) +{ + register const char *start = name; + while (*name) + if ((*name++ == '/') && *name) /* don't trim trailing / */ + start = name; + return ((char*)start); +} + +char *path_fullname(const char *name) +{ + int len=strlen(name)+1,dirlen=0; + char *path,*pwd; + if(*name!='/') + { + pwd = path_pwd(1); + dirlen = strlen(pwd)+1; + } + path = (char*)malloc(len+dirlen); + if(dirlen) + { + memcpy((void*)path,(void*)pwd,dirlen); + path[dirlen-1] = '/'; + } + memcpy((void*)&path[dirlen],(void*)name,len); + pathcanon(path,0); + return(path); +} + +/* + * load functions from file <fno> + */ +static void funload(Shell_t *shp,int fno, const char *name) +{ + char *oldname=shp->st.filename, buff[IOBSIZE+1]; + int savestates = sh_getstate(); + sh_onstate(SH_NOLOG); + sh_onstate(SH_NOALIAS); + shp->readscript = (char*)name; + shp->st.filename = path_fullname(stakptr(PATH_OFFSET)); + error_info.line = 0; + sh_eval(sfnew(NIL(Sfio_t*),buff,IOBSIZE,fno,SF_READ),0); + shp->readscript = 0; + free((void*)shp->st.filename); + shp->st.filename = oldname; + sh_setstate(savestates); +} + +/* + * do a path search and track alias if requested + * if flag is 0, or if name not found, then try autoloading function + * if flag==2, returns 1 if name found on FPATH + * returns 1, if function was autoloaded. + * If endpath!=NULL, Path search ends when path matches endpath. + */ + +int path_search(register const char *name,Pathcomp_t *endpath, int flag) +{ + register Namval_t *np; + register int fno; + Pathcomp_t *pp=0; + Shell_t *shp = &sh; + if(name && strchr(name,'/')) + { + stakseek(PATH_OFFSET); + stakputs(name); + if(canexecute(stakptr(PATH_OFFSET),0)<0) + { + *stakptr(PATH_OFFSET) = 0; + return(0); + } + if(*name=='/') + return(1); + stakseek(PATH_OFFSET); + stakputs(path_pwd(1)); + stakputc('/'); + stakputs(name); + stakputc(0); + return(0); + } + if(sh_isstate(SH_DEFPATH)) + { + if(!shp->defpathlist) + defpath_init(shp); + } + else if(!shp->pathlist) + path_init(shp); + if(flag) + { + if(!(pp=path_absolute(name,endpath)) && endpath) + pp = path_absolute(name,NIL(Pathcomp_t*)); + if(!pp && (np=nv_search(name,sh.fun_tree,HASH_NOSCOPE))&&np->nvalue.ip) + return(1); + if(!pp) + *stakptr(PATH_OFFSET) = 0; + } + if(flag==0 || !pp || (pp->flags&PATH_FPATH)) + { + if(!pp) + pp=sh_isstate(SH_DEFPATH)?shp->defpathlist:shp->pathlist; + if(pp && strmatch(name,e_alphanum) && (fno=path_opentype(name,pp,1))>=0) + { + if(flag==2) + { + sh_close(fno); + return(1); + } + funload(shp,fno,name); + return(1); + } + *stakptr(PATH_OFFSET) = 0; + return(0); + } + else if(pp && !sh_isstate(SH_DEFPATH) && *name!='/') + { + if(np=nv_search(name,shp->track_tree,NV_ADD)) + path_alias(np,pp); + } + return(0); +} + + +/* + * do a path search and find the full pathname of file name + * end search of path matches endpath without checking execute permission + */ + +Pathcomp_t *path_absolute(register const char *name, Pathcomp_t *endpath) +{ + register int f,isfun; + int noexec=0; + Pathcomp_t *pp,*oldpp; + Shell_t *shp = &sh; + Namval_t *np; + shp->path_err = ENOENT; + if(!(pp=path_get(""))) + return(0); + shp->path_err = 0; + while(1) + { + sh_sigcheck(); + isfun = (pp->flags&PATH_FPATH); + if(oldpp=pp) + pp = path_nextcomp(pp,name,0); + if(endpath) + return(endpath); + if(!isfun && !sh_isoption(SH_RESTRICTED)) + { + if(nv_search(stakptr(PATH_OFFSET),sh.bltin_tree,0)) + return(oldpp); + if(oldpp->blib) + { + typedef int (*Fptr_t)(int, char*[], void*); + Fptr_t addr; + int n = staktell(); + char *cp; + stakputs("b_"); + stakputs(name); + stakputc(0); + if(!oldpp->bltin_lib) + { + if(cp = strrchr(oldpp->blib,'/')) + cp++; + else + cp = oldpp->blib; + if(strcmp(cp,LIBCMD)==0 && (addr=(Fptr_t)dlllook((void*)0,stakptr(n)))) + { + np = sh_addbuiltin(stakptr(PATH_OFFSET),addr,NiL); + np->nvfun = (Namfun_t*)np->nvname; + return(oldpp); + } +#if (_AST_VERSION>=20040404) + if (oldpp->bltin_lib = dllplug(SH_ID, oldpp->blib, NiL, RTLD_LAZY, NiL, 0)) +#else + if (oldpp->bltin_lib = dllfind(oldpp->blib, NiL, RTLD_LAZY, NiL, 0)) +#endif + sh_addlib(oldpp->bltin_lib); + } + if((addr=(Fptr_t)dlllook(oldpp->bltin_lib,stakptr(n))) && + (!(np = sh_addbuiltin(stakptr(PATH_OFFSET),NiL,NiL)) || np->nvalue.bfp!=addr) && + (np = sh_addbuiltin(stakptr(PATH_OFFSET),addr,NiL))) + { + np->nvenv = oldpp->bltin_lib; + return(oldpp); + } + } + } + f = canexecute(stakptr(PATH_OFFSET),isfun); + if(isfun && f>=0) + { + nv_onattr(nv_open(name,shp->fun_tree,NV_NOARRAY|NV_IDENT|NV_NOSCOPE),NV_LTOU|NV_FUNCTION); + close(f); + f = -1; + return(0); + } + else if(f>=0 && (oldpp->flags & PATH_STD_DIR)) + { + int offset = staktell(); + stakputs("/bin/"); + stakputs(name); + stakputc(0); + np = nv_search(stakptr(offset),sh.bltin_tree,0); + stakseek(offset); + if(np) + { + np = sh_addbuiltin(stakptr(PATH_OFFSET),np->nvalue.bfp,NiL); + np->nvfun = (Namfun_t*)np->nvname; + } + } + if(!pp || f>=0) + break; + if(errno!=ENOENT) + noexec = errno; + } + if(f<0) + { + if(!endpath) + shp->path_err = (noexec?noexec:ENOENT); + return(0); + } + stakputc(0); + return(oldpp); +} + +/* + * returns 0 if path can execute + * sets exec_err if file is found but can't be executable + */ +#undef S_IXALL +#ifdef S_IXUSR +# define S_IXALL (S_IXUSR|S_IXGRP|S_IXOTH) +#else +# ifdef S_IEXEC +# define S_IXALL (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6)) +# else +# define S_IXALL 0111 +# endif /*S_EXEC */ +#endif /* S_IXUSR */ + +static int canexecute(register char *path, int isfun) +{ + struct stat statb; + register int fd=0; + path = path_relative(path); + if(isfun) + { + if((fd=open(path,O_RDONLY,0))<0 || fstat(fd,&statb)<0) + goto err; + } + else if(stat(path,&statb) < 0) + { +#if _WINIX + /* check for .exe or .bat suffix */ + char *cp; + if(errno==ENOENT && (!(cp=strrchr(path,'.')) || strlen(cp)>4 || strchr(cp,'/'))) + { + int offset = staktell()-1; + stakseek(offset); + stakputs(".bat"); + path = stakptr(PATH_OFFSET); + if(stat(path,&statb) < 0) + { + if(errno!=ENOENT) + goto err; + memcpy(stakptr(offset),".sh",4); + if(stat(path,&statb) < 0) + goto err; + } + } + else +#endif /* _WINIX */ + goto err; + } + errno = EPERM; + if(S_ISDIR(statb.st_mode)) + errno = EISDIR; + else if((statb.st_mode&S_IXALL)==S_IXALL || sh_access(path,X_OK)>=0) + return(fd); + if(isfun && fd>=0) + sh_close(fd); +err: + return(-1); +} + +/* + * Return path relative to present working directory + */ + +char *path_relative(register const char* file) +{ + register const char *pwd; + register const char *fp = file; + /* can't relpath when sh.pwd not set */ + if(!(pwd=sh.pwd)) + return((char*)fp); + while(*pwd==*fp) + { + if(*pwd++==0) + return((char*)e_dot); + fp++; + } + if(*pwd==0 && *fp == '/') + { + while(*++fp=='/'); + if(*fp) + return((char*)fp); + return((char*)e_dot); + } + return((char*)file); +} + +void path_exec(register const char *arg0,register char *argv[],struct argnod *local) +{ + char **envp; + const char *opath; + Pathcomp_t *libpath, *pp=0; + Shell_t *shp = &sh; + int slash=0; + nv_setlist(local,NV_EXPORT|NV_IDENT|NV_ASSIGN); + envp = sh_envgen(); + if(strchr(arg0,'/')) + { + slash=1; + /* name containing / not allowed for restricted shell */ + if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,arg0); + } + else + pp=path_get(arg0); + shp->path_err= ENOENT; + sfsync(NIL(Sfio_t*)); + timerdel(NIL(void*)); + /* find first path that has a library component */ + if(pp || slash) do + { + sh_sigcheck(); + if(libpath=pp) + { + pp = path_nextcomp(pp,arg0,0); + opath = stakfreeze(1)+PATH_OFFSET; + } + else + opath = arg0; + path_spawn(opath,argv,envp,libpath,0); + while(pp && (pp->flags&PATH_FPATH)) + pp = path_nextcomp(pp,arg0,0); + } + while(pp); + /* force an exit */ + ((struct checkpt*)shp->jmplist)->mode = SH_JMPEXIT; + if((errno=shp->path_err)==ENOENT) + errormsg(SH_DICT,ERROR_exit(ERROR_NOENT),e_found,arg0); + else + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,arg0); +} + +pid_t path_spawn(const char *opath,register char **argv, char **envp, Pathcomp_t *libpath, int spawn) +{ + Shell_t *shp = sh_getinterp(); + register char *path; + char **xp=0, *xval, *libenv = (libpath?libpath->lib:0); + Namval_t* np; + char *s, *v; + int r, n; + pid_t pid= -1; + /* leave room for inserting _= pathname in environment */ + envp--; +#if _lib_readlink + /* save original pathname */ + stakseek(PATH_OFFSET); + stakputs(opath); + opath = stakfreeze(1)+PATH_OFFSET; + np=nv_search(argv[0],shp->track_tree,0); + while(libpath && !libpath->lib) + libpath=libpath->next; + if(libpath && (!np || nv_size(np)>0)) + { + /* check for symlink and use symlink name */ + char buff[PATH_MAX+1]; + char save[PATH_MAX+1]; + stakseek(PATH_OFFSET); + stakputs(opath); + path = stakptr(PATH_OFFSET); + while((n=readlink(path,buff,PATH_MAX))>0) + { + buff[n] = 0; + n = PATH_OFFSET; + r = 0; + if((v=strrchr(path,'/')) && *buff!='/') + { + if(buff[0]=='.' && buff[1]=='.' && (r = strlen(path) + 1) <= PATH_MAX) + memcpy(save, path, r); + else + r = 0; + n += (v+1-path); + } + stakseek(n); + stakputs(buff); + stakputc(0); + path = stakptr(PATH_OFFSET); + if(v && buff[0]=='.' && buff[1]=='.') + { + pathcanon(path, 0); + if(r && access(path,X_OK)) + { + memcpy(path, save, r); + break; + } + } + if(libenv = path_lib(libpath,path)) + break; + } + stakseek(0); + } +#endif + if(libenv && (v = strchr(libenv,'='))) + { + n = v - libenv; + *v = 0; + np = nv_open(libenv,shp->var_tree,0); + *v = '='; + s = nv_getval(np); + stakputs(libenv); + if(s) + { + stakputc(':'); + stakputs(s); + } + v = stakfreeze(1); + r = 1; + xp = envp + 2; + while (s = *xp++) + { + if (strneq(s, v, n) && s[n] == '=') + { + xval = *--xp; + *xp = v; + r = 0; + break; + } + } + if (r) + { + *envp-- = v; + xp = 0; + } + } + if(!opath) + opath = stakptr(PATH_OFFSET); + envp[0] = (char*)opath-PATH_OFFSET; + envp[0][0] = '_'; + envp[0][1] = '='; + sfsync(sfstderr); + sh_sigcheck(); + path = path_relative(opath); +#ifdef SHELLMAGIC + if(*path!='/' && path!=opath) + { + /* + * The following code because execv(foo,) and execv(./foo,) + * may not yield the same results + */ + char *sp = (char*)malloc(strlen(path)+3); + sp[0] = '.'; + sp[1] = '/'; + strcpy(sp+2,path); + path = sp; + } +#endif /* SHELLMAGIC */ + if(sh_isoption(SH_RESTRICTED)) + { + int fd; + if((fd = sh_open(opath,O_RDONLY,0)) >= 0) + { + char buff[PATH_MAX]; + n = read(fd,buff,sizeof(buff)); + close(fd); + if(n>2 && buff[0]=='#' && buff[1]=='!') + { + for(s=buff; n>0 && *s!='\n'; n--,s++) + { + if(*s=='/') + errormsg(SH_DICT,ERROR_exit(1),e_restricted,opath); + } + } + } + } + if(spawn && !sh_isoption(SH_PFSH)) + pid = _spawnveg(opath, &argv[0],envp, spawn>>1); + else + path_pfexecve(opath, &argv[0] ,envp); + if(xp) + *xp = xval; +#ifdef SHELLMAGIC + if(*path=='.' && path!=opath) + { + free(path); + path = path_relative(opath); + } +#endif /* SHELLMAGIC */ + if(pid>0) + return(pid); +retry: + switch(sh.path_err = errno) + { +#ifdef apollo + /* + * On apollo's execve will fail with eacces when + * file has execute but not read permissions. So, + * for now we will pretend that EACCES and ENOEXEC + * mean the same thing. + */ + case EACCES: +#endif /* apollo */ + case ENOEXEC: +#if SHOPT_SUID_EXEC + case EPERM: + /* some systems return EPERM if setuid bit is on */ +#endif + errno = ENOEXEC; + if(spawn) + { +#ifdef _lib_fork + if(sh.subshell) + return(-1); + do + { + if((pid=fork())>0) + return(pid); + } + while(_sh_fork(pid,0,(int*)0) < 0); +#else + return(-1); +#endif + } + exscript(shp,path,argv,envp); +#ifndef apollo + case EACCES: + { + struct stat statb; + if(stat(path,&statb)>=0) + { + if(S_ISDIR(statb.st_mode)) + errno = EISDIR; +#ifdef S_ISSOCK + if(S_ISSOCK(statb.st_mode)) + exscript(shp,path,argv,envp); +#endif + } + } + /* FALL THROUGH */ +#endif /* !apollo */ +#ifdef ENAMETOOLONG + case ENAMETOOLONG: +#endif /* ENAMETOOLONG */ +#if !SHOPT_SUID_EXEC + case EPERM: +#endif + shp->path_err = errno; + return(-1); + case ENOTDIR: + case ENOENT: + case EINTR: +#ifdef EMLINK + case EMLINK: +#endif /* EMLINK */ + return(-1); + case E2BIG: + if(sh.xargmin) + { + pid = path_xargs(opath, &argv[0] ,envp,spawn); + if(pid<0) + goto retry; + return(pid); + } + default: + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,path); + } + return 0; +} + +/* + * File is executable but not machine code. + * Assume file is a Shell script and execute it. + */ + +static void exscript(Shell_t *shp,register char *path,register char *argv[],char **envp) +{ + register Sfio_t *sp; + path = path_relative(path); + shp->comdiv=0; + shp->bckpid = 0; + shp->st.ioset=0; + /* clean up any cooperating processes */ + if(shp->cpipe[0]>0) + sh_pclose(shp->cpipe); + if(shp->cpid && shp->outpipe) + sh_close(*shp->outpipe); + shp->cpid = 0; + if(sp=fcfile()) + while(sfstack(sp,SF_POPSTACK)); + job_clear(); + if(shp->infd>0 && (shp->fdstatus[shp->infd]&IOCLEX)) + sh_close(shp->infd); + sh_setstate(sh_state(SH_FORKED)); + sfsync(sfstderr); +#if SHOPT_SUID_EXEC && !SHOPT_PFSH + /* check if file cannot open for read or script is setuid/setgid */ + { + static char name[] = "/tmp/euidXXXXXXXXXX"; + register int n; + register uid_t euserid; + char *savet=0; + struct stat statb; + if((n=sh_open(path,O_RDONLY,0)) >= 0) + { + /* move <n> if n=0,1,2 */ + n = sh_iomovefd(n); + if(fstat(n,&statb)>=0 && !(statb.st_mode&(S_ISUID|S_ISGID))) + goto openok; + sh_close(n); + } + if((euserid=geteuid()) != shp->userid) + { + strncpy(name+9,fmtbase((long)getpid(),10,0),sizeof(name)-10); + /* create a suid open file with owner equal effective uid */ + if((n=open(name,O_CREAT|O_TRUNC|O_WRONLY,S_ISUID|S_IXUSR)) < 0) + goto fail; + unlink(name); + /* make sure that file has right owner */ + if(fstat(n,&statb)<0 || statb.st_uid != euserid) + goto fail; + if(n!=10) + { + sh_close(10); + fcntl(n, F_DUPFD, 10); + sh_close(n); + n=10; + } + } + savet = *--argv; + *argv = path; + path_pfexecve(e_suidexec,argv,envp); + fail: + /* + * The following code is just for compatibility + */ + if((n=open(path,O_RDONLY,0)) < 0) + errormsg(SH_DICT,ERROR_system(1),e_open,path); + if(savet) + *argv++ = savet; + openok: + shp->infd = n; + } +#else + shp->infd = sh_chkopen(path); +#endif + shp->infd = sh_iomovefd(shp->infd); +#if SHOPT_ACCT + sh_accbegin(path) ; /* reset accounting */ +#endif /* SHOPT_ACCT */ + shp->arglist = sh_argcreate(argv); + shp->lastarg = strdup(path); + /* save name of calling command */ + shp->readscript = error_info.id; + /* close history file if name has changed */ + if(shp->hist_ptr && (path=nv_getval(HISTFILE)) && strcmp(path,shp->hist_ptr->histname)) + { + hist_close(shp->hist_ptr); + (HISTCUR)->nvalue.lp = 0; + } + sh_offstate(SH_FORKED); + siglongjmp(*shp->jmplist,SH_JMPSCRIPT); +} + +#if SHOPT_ACCT +# include <sys/acct.h> +# include "FEATURE/time" + + static struct acct sabuf; + static struct tms buffer; + static clock_t before; + static char *SHACCT; /* set to value of SHACCT environment variable */ + static shaccton; /* non-zero causes accounting record to be written */ + static int compress(time_t); + /* + * initialize accounting, i.e., see if SHACCT variable set + */ + void sh_accinit(void) + { + SHACCT = getenv("SHACCT"); + } + /* + * suspend accounting unitl turned on by sh_accbegin() + */ + void sh_accsusp(void) + { + shaccton=0; +#ifdef AEXPAND + sabuf.ac_flag |= AEXPND; +#endif /* AEXPAND */ + } + + /* + * begin an accounting record by recording start time + */ + void sh_accbegin(const char *cmdname) + { + if(SHACCT) + { + sabuf.ac_btime = time(NIL(time_t *)); + before = times(&buffer); + sabuf.ac_uid = getuid(); + sabuf.ac_gid = getgid(); + strncpy(sabuf.ac_comm, (char*)path_basename(cmdname), + sizeof(sabuf.ac_comm)); + shaccton = 1; + } + } + /* + * terminate an accounting record and append to accounting file + */ + void sh_accend(void) + { + int fd; + clock_t after; + + if(shaccton) + { + after = times(&buffer); + sabuf.ac_utime = compress(buffer.tms_utime + buffer.tms_cutime); + sabuf.ac_stime = compress(buffer.tms_stime + buffer.tms_cstime); + sabuf.ac_etime = compress( (time_t)(after-before)); + fd = open( SHACCT , O_WRONLY | O_APPEND | O_CREAT,RW_ALL); + write(fd, (const char*)&sabuf, sizeof( sabuf )); + close( fd); + } + } + + /* + * Produce a pseudo-floating point representation + * with 3 bits base-8 exponent, 13 bits fraction. + */ + static int compress(register time_t t) + { + register int exp = 0, rund = 0; + + while (t >= 8192) + { + exp++; + rund = t&04; + t >>= 3; + } + if (rund) + { + t++; + if (t >= 8192) + { + t >>= 3; + exp++; + } + } + return((exp<<13) + t); + } +#endif /* SHOPT_ACCT */ + + + +/* + * add a pathcomponent to the path search list and eliminate duplicates + * and non-existing absolute paths. + */ +static Pathcomp_t *path_addcomp(Pathcomp_t *first, Pathcomp_t *old,const char *name, int flag) +{ + register Pathcomp_t *pp, *oldpp; + struct stat statb; + int len, offset=staktell(); + if(!(flag&PATH_BFPATH)) + { + register const char *cp = name; + while(*cp && *cp!=':') + stakputc(*cp++); + len = staktell()-offset; + stakputc(0); + stakseek(offset); + name = (const char*)stakptr(offset); + } + else + len = strlen(name); + for(pp=first; pp; pp=pp->next) + { + if(memcmp(name,pp->name,len)==0 && (pp->name[len]==':' || pp->name[len]==0)) + { + pp->flags |= flag; + return(first); + } + } + if(old && (old=path_dirfind(old,name,0))) + { + statb.st_ino = old->ino; + statb.st_dev = old->dev; + if(old->ino==0 && old->dev==0) + flag |= PATH_SKIP; + } + else if(stat(name,&statb)<0 || !S_ISDIR(statb.st_mode)) + { + if(*name=='/') + { + if(strcmp(name,SH_CMDLIB_DIR)) + return(first); + statb.st_dev = 1; + } + else + { + flag |= PATH_SKIP; + statb.st_dev = 0; + } + statb.st_ino = 0; + } + if(*name=='/' && onstdpath(name)) + flag |= PATH_STD_DIR; + for(pp=first, oldpp=0; pp; oldpp=pp, pp=pp->next) + { + if(pp->ino==statb.st_ino && pp->dev==statb.st_dev) + { + /* if both absolute paths, eliminate second */ + pp->flags |= flag; + if(*name=='/' && *pp->name=='/') + return(first); + /* keep the path but mark it as skip */ + flag |= PATH_SKIP; + } + } + pp = newof((Pathcomp_t*)0,Pathcomp_t,1,len+1); + pp->refcount = 1; + memcpy((char*)(pp+1),name,len+1); + pp->name = (char*)(pp+1); + pp->len = len; + pp->dev = statb.st_dev; + pp->ino = statb.st_ino; + if(oldpp) + oldpp->next = pp; + else + first = pp; + pp->flags = flag; + if(pp->ino==0 && pp->dev==1) + { + pp->flags |= PATH_BUILTIN_LIB; + pp->blib = malloc(4); + strcpy(pp->blib,LIBCMD); + return(first); + } + if((flag&(PATH_PATH|PATH_SKIP))==PATH_PATH) + path_chkpaths(first,old,pp,offset); + return(first); +} + +/* + * This function checks for the .paths file in directory in <pp> + * it assumes that the directory is on the stack at <offset> + */ +static int path_chkpaths(Pathcomp_t *first, Pathcomp_t* old,Pathcomp_t *pp, int offset) +{ + struct stat statb; + int k,m,n,fd; + char *sp,*cp,*ep; + stakseek(offset+pp->len); + if(pp->len==1 && *stakptr(offset)=='/') + stakseek(offset); + stakputs("/.paths"); + if((fd=open(stakptr(offset),O_RDONLY))>=0) + { + fstat(fd,&statb); + n = statb.st_size; + stakseek(offset+pp->len+n+2); + sp = stakptr(offset+pp->len); + *sp++ = '/'; + n=read(fd,cp=sp,n); + sp[n] = 0; + close(fd); + for(ep=0; n--; cp++) + { + if(*cp=='=') + { + ep = cp+1; + continue; + } + else if(*cp!='\r' && *cp!='\n') + continue; + if(*sp=='#' || sp==cp) + { + sp = cp+1; + continue; + } + *cp = 0; + m = ep ? (ep-sp) : 0; + if(!m || m==6 && memcmp((void*)sp,(void*)"FPATH=",6)==0) + { + if(first) + { + char *ptr = stakptr(offset+pp->len+1); + if(ep) + strcpy(ptr,ep); + path_addcomp(first,old,stakptr(offset),PATH_FPATH|PATH_BFPATH); + } + } + else if(m==12 && memcmp((void*)sp,(void*)"BUILTIN_LIB=",12)==0) + { + if(!(pp->flags & PATH_BUILTIN_LIB)) + { + pp->flags |= PATH_BUILTIN_LIB; + if (*ep == '.' && !*(ep + 1)) + pp->flags |= PATH_STD_DIR; + else + { + k = strlen(ep)+1; + if (*ep != '/') + k += pp->len+1; + pp->blib = sp = malloc(k); + if (*ep != '/') + { + strcpy(pp->blib,pp->name); + sp += pp->len; + *sp++ = '/'; + } + strcpy(sp,ep); + } + } + } + else if(m) + { + pp->lib = (char*)malloc(cp-sp+pp->len+2); + memcpy((void*)pp->lib,(void*)sp,m); + memcpy((void*)&pp->lib[m],stakptr(offset),pp->len); + pp->lib[k=m+pp->len] = '/'; + strcpy((void*)&pp->lib[k+1],ep); + pathcanon(&pp->lib[m],0); + if(!first) + { + stakseek(0); + stakputs(pp->lib); + free((void*)pp->lib); + return(1); + } + } + sp = cp+1; + ep = 0; + } + } + return(0); +} + + +Pathcomp_t *path_addpath(Pathcomp_t *first, register const char *path,int type) +{ + register const char *cp; + Pathcomp_t *old=0; + int offset = staktell(); + char *savptr; + + if(!path && type!=PATH_PATH) + return(first); + if(type!=PATH_FPATH) + { + old = first; + first = 0; + } + if(offset) + savptr = stakfreeze(0); + if(path) while(*(cp=path)) + { + if(*cp==':') + { + if(type!=PATH_FPATH) + first = path_addcomp(first,old,".",type); + while(*++path == ':'); + } + else + { + int c; + while(*path && *path!=':') + path++; + c = *path++; + first = path_addcomp(first,old,cp,type); + if(c==0) + break; + if(*path==0) + path--; + } + } + if(old) + { + if(!first && !path) + { + Pathcomp_t *pp = (Pathcomp_t*)old->shp->defpathlist; + if(!pp) + pp = defpath_init(old->shp); + first = path_dup(pp); + } + if(cp=(FPATHNOD)->nvalue.cp) + first = (void*)path_addpath((Pathcomp_t*)first,cp,PATH_FPATH); + path_delete(old); + } + if(offset) + stakset(savptr,offset); + else + stakseek(0); + return(first); +} + +/* + * duplicate the path give by <first> by incremented reference counts + */ +Pathcomp_t *path_dup(Pathcomp_t *first) +{ + register Pathcomp_t *pp=first; + while(pp) + { + pp->refcount++; + pp = pp->next; + } + return(first); +} + +/* + * called whenever the directory is changed + */ +void path_newdir(Pathcomp_t *first) +{ + register Pathcomp_t *pp=first, *next, *pq; + struct stat statb; + for(pp=first; pp; pp=pp->next) + { + pp->flags &= ~PATH_SKIP; + if(*pp->name=='/') + continue; + /* delete .paths component */ + if((next=pp->next) && (next->flags&PATH_BFPATH)) + { + pp->next = next->next; + if(--next->refcount<=0) + free((void*)next); + } + if(stat(pp->name,&statb)<0 || !S_ISDIR(statb.st_mode)) + { + pp->dev = 0; + pp->ino = 0; + continue; + } + pp->dev = statb.st_dev; + pp->ino = statb.st_ino; + for(pq=first;pq!=pp;pq=pq->next) + { + if(pp->ino==pq->ino && pp->dev==pq->dev) + pp->flags |= PATH_SKIP; + } + for(pq=pp;pq=pq->next;) + { + if(pp->ino==pq->ino && pp->dev==pq->dev) + pq->flags |= PATH_SKIP; + } + if((pp->flags&(PATH_PATH|PATH_SKIP))==PATH_PATH) + { + /* try to insert .paths component */ + int offset = staktell(); + stakputs(pp->name); + stakseek(offset); + next = pp->next; + pp->next = 0; + path_chkpaths(first,(Pathcomp_t*)0,pp,offset); + if(pp->next) + pp = pp->next; + pp->next = next; + } + } +#if 0 + path_dump(first); +#endif +} + +Pathcomp_t *path_unsetfpath(Pathcomp_t *first) +{ + register Pathcomp_t *pp=first, *old=0; + while(pp) + { + if((pp->flags&PATH_FPATH) && !(pp->flags&PATH_BFPATH)) + { + if(pp->flags&PATH_PATH) + pp->flags &= ~PATH_FPATH; + else + { + Pathcomp_t *ppsave=pp; + if(old) + old->next = pp->next; + else + first = pp->next; + pp = pp->next; + if(--ppsave->refcount<=0) + { + if(ppsave->lib) + free((void*)ppsave->lib); + free((void*)ppsave); + } + continue; + } + + } + old = pp; + pp = pp->next; + } + return(first); +} + +Pathcomp_t *path_dirfind(Pathcomp_t *first,const char *name,int c) +{ + register Pathcomp_t *pp=first; + while(pp) + { + if(memcmp(name,pp->name,pp->len)==0 && name[pp->len]==c) + return(pp); + pp = pp->next; + } + return(0); +} + +/* + * get discipline for tracked alias + */ +static char *talias_get(Namval_t *np, Namfun_t *nvp) +{ + Pathcomp_t *pp = (Pathcomp_t*)np->nvalue.cp; + char *ptr; + if(!pp) + return(NULL); + path_nextcomp(pp,nv_name(np),pp); + ptr = stakfreeze(0); + return(ptr+PATH_OFFSET); +} + +static void talias_put(register Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + if(!val && np->nvalue.cp) + { + Pathcomp_t *pp = (Pathcomp_t*)np->nvalue.cp; + if(--pp->refcount<=0) + free((void*)pp); + } + nv_putv(np,val,flags,fp); +} + +static const Namdisc_t talias_disc = { 0, talias_put, talias_get }; +static Namfun_t talias_init = { &talias_disc, 1 }; + +/* + * set tracked alias node <np> to value <pp> + */ +void path_alias(register Namval_t *np,register Pathcomp_t *pp) +{ + if(pp) + { + struct stat statb; + char *sp; + nv_offattr(np,NV_NOPRINT); + nv_stack(np,&talias_init); + np->nvalue.cp = (char*)pp; + pp->refcount++; + nv_setattr(np,NV_TAGGED|NV_NOFREE); + path_nextcomp(pp,nv_name(np),pp); + sp = stakptr(PATH_OFFSET); + if(sp && lstat(sp,&statb)>=0 && S_ISLNK(statb.st_mode)) + nv_setsize(np,statb.st_size+1); + else + nv_setsize(np,0); + } + else + nv_unset(np); +} + diff --git a/usr/src/lib/libshell/common/sh/pmain.c b/usr/src/lib/libshell/common/sh/pmain.c new file mode 100644 index 0000000000..9cf8a886f9 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/pmain.c @@ -0,0 +1,30 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <shell.h> + +typedef int (*Shnote_f)(int, long, int); + +int main(int argc, char *argv[]) +{ + sh_waitnotify((Shnote_f)0); + return(sh_main(argc, argv, 0)); +} diff --git a/usr/src/lib/libshell/common/sh/shcomp.c b/usr/src/lib/libshell/common/sh/shcomp.c new file mode 100644 index 0000000000..454ac3e5be --- /dev/null +++ b/usr/src/lib/libshell/common/sh/shcomp.c @@ -0,0 +1,172 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * David Korn + * AT&T Labs + * + * shell script to shell binary converter + * + */ + +static const char usage[] = +"[-?\n@(#)$Id: shcomp (AT&T Research) 2003-03-02 $\n]" +USAGE_LICENSE +"[+NAME?shcomp - compile a shell script]" +"[+DESCRIPTION?Unless \b-D\b is specified, \bshcomp\b takes a shell script, " + "\ainfile\a, and creates a binary format file, \aoutfile\a, that " + "\bksh\b can read and execute with the same effect as the original " + "script.]" +"[+?Since aliases are processed as the script is read, alias definitions " + "whose value requires variable expansion will not work correctly.]" +"[+?If \b-D\b is specifed, all double quoted strings that are preceded by " + "\b$\b are output. These are the messages that need to be " + "translated to locale specific versions for internationalization.]" +"[+?If \aoutfile\a is omitted, then the results will be written to " + "standard output. If \ainfile\a is also omitted, the shell script " + "will be read from standard input.]" +"[D:dictionary?Generate a list of strings that need to be placed in a message " + "catalog for internationalization.]" +"[n:noexec?Displays warning messages for obsolete or non-conforming " + "constructs.] " +"[v:verbose?Displays input from \ainfile\a onto standard error as it " + "reads it.]" +"\n" +"\n[infile [outfile]]\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Successful completion.]" + "[+>0?An error occurred.]" +"}" +"[+SEE ALSO?\bksh\b(1)]" +; + +#include <shell.h> +#include "shnodes.h" +#include "sys/stat.h" + +#define CNTL(x) ((x)&037) +#define VERSION 3 +static const char header[6] = { CNTL('k'),CNTL('s'),CNTL('h'),0,VERSION,0 }; + +int main(int argc, char *argv[]) +{ + Sfio_t *in, *out; + Shell_t *shp; + Namval_t *np; + Shnode_t *t; + char *cp; + int n, nflag=0, vflag=0, dflag=0; + error_info.id = argv[0]; + while(n = optget(argv, usage )) switch(n) + { + case 'D': + dflag=1; + break; + case 'v': + vflag=1; + break; + case 'n': + nflag=1; + break; + case ':': + errormsg(SH_DICT,2,"%s",opt_info.arg); + break; + case '?': + errormsg(SH_DICT,ERROR_usage(2),"%s",opt_info.arg); + break; + } + shp = sh_init(argc,argv,(Shinit_f)0); + argv += opt_info.index; + argc -= opt_info.index; + if(error_info.errors || argc>2) + errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0)); + if(cp= *argv) + { + argv++; + in = sh_pathopen(cp); + } + else + in = sfstdin; + if(cp= *argv) + { + struct stat statb; + if(!(out = sfopen((Sfio_t*)0,cp,"w"))) + errormsg(SH_DICT,ERROR_system(1),"%s: cannot create",cp); + if(fstat(sffileno(out),&statb) >=0) + chmod(cp,(statb.st_mode&~S_IFMT)|S_IXUSR|S_IXGRP|S_IXOTH); + } + else + out = sfstdout; + if(dflag) + { + sh_onoption(SH_DICTIONARY); + sh_onoption(SH_NOEXEC); + } + if(nflag) + sh_onoption(SH_NOEXEC); + if(vflag) + sh_onoption(SH_VERBOSE); + if(!dflag) + sfwrite(out,header,sizeof(header)); + shp->inlineno = 1; + while(1) + { + stakset((char*)0,0); + if(t = (Shnode_t*)sh_parse(shp,in,0)) + { + if(t->tre.tretyp==0 && t->com.comnamp && strcmp(nv_name((Namval_t*)t->com.comnamp),"alias")==0) + sh_exec(t,0); + if(!dflag && sh_tdump(out,t) < 0) + errormsg(SH_DICT,ERROR_exit(1),"dump failed"); + } + else if(sfeof(in)) + break; + if(sferror(in)) + errormsg(SH_DICT,ERROR_system(1),"I/O error"); + if(t && ((t->tre.tretyp&COMMSK)==TCOM) && (np=t->com.comnamp) && (cp=nv_name(np))) + { + if(strcmp(cp,"exit")==0) + break; + /* check for exec of a command */ + if(strcmp(cp,"exec")==0) + { + if(t->com.comtyp&COMSCAN) + { + if(t->com.comarg->argnxt.ap) + break; + } + else + { + struct dolnod *ap = (struct dolnod*)t->com.comarg; + if(ap->dolnum>1) + break; + } + } + } + } + /* copy any remaining input */ + sfmove(in,out,SF_UNBOUND,-1); + if(in!=sfstdin) + sfclose(in); + if(out!=sfstdout) + sfclose(out); + return(0); +} diff --git a/usr/src/lib/libshell/common/sh/streval.c b/usr/src/lib/libshell/common/sh/streval.c new file mode 100644 index 0000000000..fd74d47cd6 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/streval.c @@ -0,0 +1,904 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +/* + * D. G. Korn + * AT&T Labs + * + * arithmetic expression evaluator + * + * this version compiles the expression onto a stack + * and has a separate executor + */ + +#include "streval.h" +#include <ctype.h> +#include <error.h> +#include <stak.h> +#include "FEATURE/externs" + +#ifndef ERROR_dictionary +# define ERROR_dictionary(s) (s) +#endif +#ifndef SH_DICT +# define SH_DICT "libshell" +#endif + +#define MAXLEVEL 9 +#define SMALL_STACK 12 + +/* + * The following are used with tokenbits() macro + */ +#define T_OP 0x3f /* mask for operator number */ +#define T_BINARY 0x40 /* binary operators */ +#define T_NOFLOAT 0x80 /* non floating point operator */ +#define A_LVALUE (2*MAXPREC+2) + +#define pow2size(x) ((x)<=2?2:(x)<=4?4:(x)<=8?8:(x)<=16?16:(x)<=32?32:64) +#define round(x,size) (((x)+(size)-1)&~((size)-1)) +#define stakpush(v,val,type) ((((v)->offset=round(staktell(),pow2size(sizeof(type)))),\ + stakseek((v)->offset+sizeof(type)), \ + *((type*)stakptr((v)->offset)) = (val)),(v)->offset) +#define roundptr(ep,cp,type) (((unsigned char*)(ep))+round(cp-((unsigned char*)(ep)),pow2size(sizeof(type)))) + +static int level; + +struct vars /* vars stacked per invocation */ +{ + const char *expr; /* current expression */ + const char *nextchr; /* next char in current expression */ + const char *errchr; /* next char after error */ + const char *errstr; /* error string */ + struct lval errmsg; /* error message text */ + int offset; /* offset for pushchr macro */ + int staksize; /* current stack size needed */ + int stakmaxsize; /* maximum stack size needed */ + unsigned char paren; /* parenthesis level */ + char infun; /* incremented by comma inside function */ + int emode; + Sfdouble_t (*convert)(const char**,struct lval*,int,Sfdouble_t); +}; + +typedef int (*Math_0_f)(Sfdouble_t); +typedef Sfdouble_t (*Fun_t)(Sfdouble_t,...); +typedef Sfdouble_t (*Math_1_f)(Sfdouble_t); +typedef Sfdouble_t (*Math_2_f)(Sfdouble_t,Sfdouble_t); +typedef Sfdouble_t (*Math_3_f)(Sfdouble_t,Sfdouble_t,Sfdouble_t); + +#define getchr(vp) (*(vp)->nextchr++) +#define peekchr(vp) (*(vp)->nextchr) +#define ungetchr(vp) ((vp)->nextchr--) + +#if ('a'==97) /* ASCII encodings */ +# define getop(c) (((c) >= sizeof(strval_states))? \ + ((c)=='|'?A_OR:((c)=='^'?A_XOR:((c)=='~'?A_TILDE:A_REG))):\ + strval_states[(c)]) +#else +# define getop(c) (isdigit(c)?A_DIG:((c==' '||c=='\t'||c=='\n'||c=='"')?0: \ + (c=='<'?A_LT:(c=='>'?A_GT:(c=='='?A_ASSIGN: \ + (c=='+'?A_PLUS:(c=='-'?A_MINUS:(c=='*'?A_TIMES: \ + (c=='/'?A_DIV:(c=='%'?A_MOD:(c==','?A_COMMA: \ + (c=='&'?A_AND:(c=='!'?A_NOT:(c=='('?A_LPAR: \ + (c==')'?A_RPAR:(c==0?A_EOF:(c==':'?A_COLON: \ + (c=='?'?A_QUEST:(c=='|'?A_OR:(c=='^'?A_XOR: \ + (c=='\''?A_LIT: \ + (c=='.'?A_DOT:(c=='~'?A_TILDE:A_REG))))))))))))))))))))))) +#endif + +#define seterror(v,msg) _seterror(v,ERROR_dictionary(msg)) +#define ERROR(vp,msg) return(seterror((vp),msg)) + +/* + * set error message string and return(0) + */ +static int _seterror(struct vars *vp,const char *msg) +{ + if(!vp->errmsg.value) + vp->errmsg.value = (char*)msg; + vp->errchr = vp->nextchr; + vp->nextchr = ""; + level = 0; + return(0); +} + + +static void arith_error(const char *message,const char *expr, int mode) +{ + level = 0; + mode = (mode&3)!=0; + errormsg(SH_DICT,ERROR_exit(mode),message,expr); +} + +#if _ast_no_um2fm +static Sfdouble_t U2F(Sfulong_t u) +{ + Sflong_t s = u; + Sfdouble_t f; + + if (s >= 0) + return s; + s = u / 2; + f = s; + f *= 2; + if (u & 1) + f++; + return f; +} +#else +#define U2F(x) x +#endif + +Sfdouble_t arith_exec(Arith_t *ep) +{ + register Sfdouble_t num=0,*dp,*sp; + register unsigned char *cp = ep->code; + register int c,type=0; + register char *tp; + Sfdouble_t small_stack[SMALL_STACK+1]; + const char *ptr = ""; + Fun_t fun; + struct lval node; + node.emode = ep->emode; + node.expr = ep->expr; + node.elen = ep->elen; + if(level++ >=MAXLEVEL) + { + arith_error(e_recursive,ep->expr,ep->emode); + return(0); + } + if(ep->staksize < SMALL_STACK) + sp = small_stack; + else + sp = (Sfdouble_t*)stakalloc(ep->staksize*(sizeof(Sfdouble_t)+1)); + tp = (char*)(sp+ep->staksize); + tp--,sp--; + while(c = *cp++) + { + if(c&T_NOFLOAT) + { + if(type==1 || ((c&T_BINARY) && (c&T_OP)!=A_MOD && tp[-1]==1)) + arith_error(e_incompatible,ep->expr,ep->emode); + } + switch(c&T_OP) + { + case A_JMP: case A_JMPZ: case A_JMPNZ: + c &= T_OP; + cp = roundptr(ep,cp,short); + if((c==A_JMPZ && num) || (c==A_JMPNZ &&!num)) + cp += sizeof(short); + else + cp = (unsigned char*)ep + *((short*)cp); + continue; + case A_NOTNOT: + num = (num!=0); + type=0; + break; + case A_PLUSPLUS: + (*ep->fun)(&ptr,&node,ASSIGN,num+1); + break; + case A_MINUSMINUS: + (*ep->fun)(&ptr,&node,ASSIGN,num-1); + break; + case A_INCR: + num = num+1; + num = (*ep->fun)(&ptr,&node,ASSIGN,num); + break; + case A_DECR: + num = num-1; + num = (*ep->fun)(&ptr,&node,ASSIGN,num); + break; + case A_SWAP: + num = sp[-1]; + sp[-1] = *sp; + type = tp[-1]; + tp[-1] = *tp; + break; + case A_POP: + sp--; + continue; + case A_PUSHV: + cp = roundptr(ep,cp,Sfdouble_t*); + dp = *((Sfdouble_t**)cp); + cp += sizeof(Sfdouble_t*); + c = *(short*)cp; + cp += sizeof(short); + node.value = (char*)dp; + node.flag = c; + node.isfloat=0; + node.level = level; + num = (*ep->fun)(&ptr,&node,VALUE,num); + if(node.value != (char*)dp) + arith_error(node.value,ptr,ep->emode); + *++sp = num; + type = node.isfloat; + if(num > LDBL_ULLONG_MAX || num < LDBL_LLONG_MIN) + type = 1; + else + { + Sfdouble_t d=num; + if(num > LDBL_LLONG_MAX && num <= LDBL_ULLONG_MAX) + { + type = 2; + d -= LDBL_LLONG_MAX; + } + if((Sflong_t)d!=d) + type = 1; + } + *++tp = type; + c = 0; + break; + case A_STORE: + cp = roundptr(ep,cp,Sfdouble_t*); + dp = *((Sfdouble_t**)cp); + cp += sizeof(Sfdouble_t*); + c = *(short*)cp; + if(c<0) + c = 0; + cp += sizeof(short); + node.value = (char*)dp; + node.flag = c; + num = (*ep->fun)(&ptr,&node,ASSIGN,num); + break; + case A_PUSHF: + cp = roundptr(ep,cp,Fun_t); + *++sp = (Sfdouble_t)(cp-ep->code); + cp += sizeof(Fun_t); + *++tp = *cp++; + continue; + case A_PUSHN: + cp = roundptr(ep,cp,Sfdouble_t); + num = *((Sfdouble_t*)cp); + cp += sizeof(Sfdouble_t); + *++sp = num; + *++tp = type = *cp++; + break; + case A_NOT: + type=0; + num = !num; + break; + case A_UMINUS: + num = -num; + break; + case A_TILDE: + num = ~((Sflong_t)(num)); + break; + case A_PLUS: + num += sp[-1]; + break; + case A_MINUS: + num = sp[-1] - num; + break; + case A_TIMES: + num *= sp[-1]; + break; + case A_POW: + num = pow(sp[-1],num); + break; + case A_MOD: + if(!(Sflong_t)num) + arith_error(e_divzero,ep->expr,ep->emode); + if(type==2 || tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) % (Sfulong_t)(num)); + else + num = (Sflong_t)(sp[-1]) % (Sflong_t)(num); + break; + case A_DIV: + if(type==1 || tp[-1]==1) + { + num = sp[-1]/num; + type = 1; + } + else if((Sfulong_t)(num)==0) + arith_error(e_divzero,ep->expr,ep->emode); + else if(type==2 || tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) / (Sfulong_t)(num)); + else + num = (Sflong_t)(sp[-1]) / (Sflong_t)(num); + break; + case A_LSHIFT: + if(tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) << (long)(num)); + else + num = (Sflong_t)(sp[-1]) << (long)(num); + break; + case A_RSHIFT: + if(tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) >> (long)(num)); + else + num = (Sflong_t)(sp[-1]) >> (long)(num); + break; + case A_XOR: + if(type==2 || tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) ^ (Sfulong_t)(num)); + else + num = (Sflong_t)(sp[-1]) ^ (Sflong_t)(num); + break; + case A_OR: + if(type==2 || tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) | (Sfulong_t)(num)); + else + num = (Sflong_t)(sp[-1]) | (Sflong_t)(num); + break; + case A_AND: + if(type==2 || tp[-1]==2) + num = U2F((Sfulong_t)(sp[-1]) & (Sfulong_t)(num)); + else + num = (Sflong_t)(sp[-1]) & (Sflong_t)(num); + break; + case A_EQ: + num = (sp[-1]==num); + type=0; + break; + case A_NEQ: + num = (sp[-1]!=num); + type=0; + break; + case A_LE: + num = (sp[-1]<=num); + type=0; + break; + case A_GE: + num = (sp[-1]>=num); + type=0; + break; + case A_GT: + num = (sp[-1]>num); + type=0; + break; + case A_LT: + num = (sp[-1]<num); + type=0; + break; + case A_CALL0: + sp--,tp--; + fun = *((Fun_t*)(ep->code+(int)(*sp))); + type = 0; + num = (*((Math_0_f)fun))(num); + break; + case A_CALL1: + sp--,tp--; + fun = *((Fun_t*)(ep->code+(int)(*sp))); + type = *tp; + num = (*fun)(num); + break; + case A_CALL2: + sp-=2,tp-=2; + fun = *((Fun_t*)(ep->code+(int)(*sp))); + type = *tp; + num = (*((Math_2_f)fun))(sp[1],num); + break; + case A_CALL3: + sp-=3,tp-=3; + fun = *((Fun_t*)(ep->code+(int)(*sp))); + type = *tp; + num = (*((Math_3_f)fun))(sp[1],sp[2],num); + break; + } + if(c&T_BINARY) + sp--,tp--; + *sp = num; + *tp = type; + } + if(level>0) + level--; + return(num); +} + +/* + * This returns operator tokens or A_REG or A_NUM + */ +static int gettok(register struct vars *vp) +{ + register int c,op; + vp->errchr = vp->nextchr; + while(1) + { + c = getchr(vp); + switch(op=getop(c)) + { + case 0: + vp->errchr = vp->nextchr; + continue; + case A_EOF: + vp->nextchr--; + break; + /*FALL THRU*/ + case A_DIG: case A_REG: case A_DOT: case A_LIT: + if(op==A_DOT) + { + if((c=peekchr(vp))>='0' && c<='9') + op = A_DIG; + else + op = A_REG; + } + ungetchr(vp); + break; + case A_QUEST: + if(peekchr(vp)==':') + { + getchr(vp); + op = A_QCOLON; + } + break; + case A_LT: case A_GT: + if(peekchr(vp)==c) + { + getchr(vp); + op -= 2; + break; + } + /* FALL THRU */ + case A_NOT: case A_COLON: + c = '='; + /* FALL THRU */ + case A_ASSIGN: + case A_TIMES: + case A_PLUS: case A_MINUS: + case A_OR: case A_AND: + if(peekchr(vp)==c) + { + getchr(vp); + op--; + } + } + return(op); + } +} + +/* + * evaluate a subexpression with precedence + */ + +static int expr(register struct vars *vp,register int precedence) +{ + register int c, op; + int invalid,wasop=0; + struct lval lvalue,assignop; + const char *pos; + Sfdouble_t d; + + lvalue.value = 0; + lvalue.fun = 0; +again: + op = gettok(vp); + c = 2*MAXPREC+1; + switch(op) + { + case A_PLUS: + goto again; + case A_EOF: + if(precedence>5) + ERROR(vp,e_moretokens); + return(1); + case A_MINUS: + op = A_UMINUS; + goto common; + case A_NOT: + goto common; + case A_MINUSMINUS: + c = A_LVALUE; + op = A_DECR|T_NOFLOAT; + goto common; + case A_PLUSPLUS: + c = A_LVALUE; + op = A_INCR|T_NOFLOAT; + /* FALL THRU */ + case A_TILDE: + op |= T_NOFLOAT; + common: + if(!expr(vp,c)) + return(0); + stakputc(op); + break; + default: + vp->nextchr = vp->errchr; + wasop = 1; + } + invalid = wasop; + while(1) + { + assignop.value = 0; + op = gettok(vp); + if(op==A_DIG || op==A_REG || op==A_LIT) + { + if(!wasop) + ERROR(vp,e_synbad); + goto number; + } + if(wasop++ && op!=A_LPAR) + ERROR(vp,e_synbad); + /* check for assignment operation */ + if(peekchr(vp)== '=' && !(strval_precedence[op]&NOASSIGN)) + { + if((!lvalue.value || precedence > 3)) + ERROR(vp,e_notlvalue); + if(precedence==3) + precedence = 2; + assignop = lvalue; + getchr(vp); + c = 3; + } + else + { + c = (strval_precedence[op]&PRECMASK); + if(c==MAXPREC || op==A_POW) + c++; + c *= 2; + } + /* from here on c is the new precedence level */ + if(lvalue.value && (op!=A_ASSIGN)) + { + if(vp->staksize++>=vp->stakmaxsize) + vp->stakmaxsize = vp->staksize; + stakputc(A_PUSHV); + stakpush(vp,lvalue.value,char*); + if(lvalue.flag<0) + lvalue.flag = 0; + stakpush(vp,lvalue.flag,short); + if(vp->nextchr==0) + ERROR(vp,e_badnum); + if(!(strval_precedence[op]&SEQPOINT)) + lvalue.value = 0; + invalid = 0; + } + else if(precedence==A_LVALUE) + ERROR(vp,e_notlvalue); + if(invalid && op>A_ASSIGN) + ERROR(vp,e_synbad); + if(precedence >= c) + goto done; + if(strval_precedence[op]&RASSOC) + c--; + if((c < (2*MAXPREC+1)) && !(strval_precedence[op]&SEQPOINT)) + { + wasop = 0; + if(!expr(vp,c)) + return(0); + } + switch(op) + { + case A_RPAR: + if(!vp->paren) + ERROR(vp,e_paren); + if(invalid) + ERROR(vp,e_synbad); + goto done; + + case A_COMMA: + wasop = 0; + if(vp->infun) + vp->infun++; + else + { + stakputc(A_POP); + vp->staksize--; + } + if(!expr(vp,c)) + return(0); + lvalue.value = 0; + break; + + case A_LPAR: + { + int infun = vp->infun; + Sfdouble_t (*fun)(Sfdouble_t,...); + int nargs = lvalue.nargs; + fun = lvalue.fun; + lvalue.fun = 0; + if(fun) + { + if(vp->staksize++>=vp->stakmaxsize) + vp->stakmaxsize = vp->staksize; + vp->infun=1; + stakputc(A_PUSHF); + stakpush(vp,fun,Fun_t); + stakputc(1); + } + else + vp->infun = 0; + if(!invalid) + ERROR(vp,e_synbad); + vp->paren++; + if(!expr(vp,1)) + return(0); + vp->paren--; + if(fun) + { + int x= (nargs>7); + nargs &= 7; + if(vp->infun != nargs) + ERROR(vp,e_argcount); + if(vp->staksize+=nargs>=vp->stakmaxsize) + vp->stakmaxsize = vp->staksize+nargs; + stakputc(A_CALL0+nargs -x); + vp->staksize -= nargs; + } + vp->infun = infun; + if (gettok(vp) != A_RPAR) + ERROR(vp,e_paren); + wasop = 0; + break; + } + + case A_PLUSPLUS: + case A_MINUSMINUS: + wasop=0; + op |= T_NOFLOAT; + case A_ASSIGN: + if(!lvalue.value) + ERROR(vp,e_notlvalue); + if(op==A_ASSIGN) + { + stakputc(A_STORE); + stakpush(vp,lvalue.value,char*); + stakpush(vp,lvalue.flag,short); + vp->staksize--; + } + else + stakputc(op); + lvalue.value = 0; + break; + + case A_QUEST: + { + int offset1,offset2; + stakputc(A_JMPZ); + offset1 = stakpush(vp,0,short); + stakputc(A_POP); + if(!expr(vp,1)) + return(0); + if(gettok(vp)!=A_COLON) + ERROR(vp,e_questcolon); + stakputc(A_JMP); + offset2 = stakpush(vp,0,short); + *((short*)stakptr(offset1)) = staktell(); + stakputc(A_POP); + if(!expr(vp,3)) + return(0); + *((short*)stakptr(offset2)) = staktell(); + lvalue.value = 0; + wasop = 0; + break; + } + + case A_COLON: + ERROR(vp,e_badcolon); + break; + + case A_QCOLON: + case A_ANDAND: + case A_OROR: + { + int offset; + if(op==A_ANDAND) + op = A_JMPZ; + else + op = A_JMPNZ; + stakputc(op); + offset = stakpush(vp,0,short); + stakputc(A_POP); + if(!expr(vp,c)) + return(0); + *((short*)stakptr(offset)) = staktell(); + if(op!=A_QCOLON) + stakputc(A_NOTNOT); + lvalue.value = 0; + wasop=0; + break; + } + case A_AND: case A_OR: case A_XOR: case A_LSHIFT: + case A_RSHIFT: case A_MOD: + op |= T_NOFLOAT; + /* FALL THRU */ + case A_PLUS: case A_MINUS: case A_TIMES: case A_DIV: + case A_EQ: case A_NEQ: case A_LT: case A_LE: + case A_GT: case A_GE: case A_POW: + stakputc(op|T_BINARY); + vp->staksize--; + break; + case A_NOT: case A_TILDE: + default: + ERROR(vp,e_synbad); + number: + wasop = 0; + if(*vp->nextchr=='L' && vp->nextchr[1]=='\'') + { + vp->nextchr++; + op = A_LIT; + } + pos = vp->nextchr; + lvalue.isfloat = 0; + lvalue.expr = vp->expr; + lvalue.emode = vp->emode; + if(op==A_LIT) + { + /* character constants */ + if(pos[1]=='\\' && pos[2]=='\'' && pos[3]!='\'') + { + d = '\\'; + vp->nextchr +=2; + } + else + d = chresc(pos+1,(char**)&vp->nextchr); + /* posix allows the trailing ' to be optional */ + if(*vp->nextchr=='\'') + vp->nextchr++; + } + else + d = (*vp->convert)(&vp->nextchr, &lvalue, LOOKUP, 0); + if (vp->nextchr == pos) + { + if(vp->errmsg.value = lvalue.value) + vp->errstr = pos; + ERROR(vp,op==A_LIT?e_charconst:e_synbad); + } + if(op==A_DIG || op==A_LIT) + { + stakputc(A_PUSHN); + if(vp->staksize++>=vp->stakmaxsize) + vp->stakmaxsize = vp->staksize; + stakpush(vp,d,Sfdouble_t); + stakputc(lvalue.isfloat); + } + + /* check for function call */ + if(lvalue.fun) + continue; + break; + } + invalid = 0; + if(assignop.value) + { + if(vp->staksize++>=vp->stakmaxsize) + vp->stakmaxsize = vp->staksize; + if(assignop.flag<0) + assignop.flag = 0; + stakputc(A_STORE); + stakpush(vp,assignop.value,char*); + stakpush(vp,assignop.flag,short); + } + } + done: + vp->nextchr = vp->errchr; + return(1); +} + +Arith_t *arith_compile(const char *string,char **last,Sfdouble_t(*fun)(const char**,struct lval*,int,Sfdouble_t),int emode) +{ + struct vars cur; + register Arith_t *ep; + int offset; + memset((void*)&cur,0,sizeof(cur)); + cur.emode = emode; + cur.expr = cur.nextchr = string; + cur.convert = fun; + cur.emode = emode; + cur.errmsg.value = 0; + cur.errmsg.emode = emode; + stakseek(sizeof(Arith_t)); + if(!expr(&cur,0) && cur.errmsg.value) + { + if(cur.errstr) + string = cur.errstr; + (*fun)( &string , &cur.errmsg, MESSAGE, 0); + cur.nextchr = cur.errchr; + } + stakputc(0); + offset = staktell(); + ep = (Arith_t*)stakfreeze(0); + ep->expr = string; + ep->elen = strlen(string); + ep->code = (unsigned char*)(ep+1); + ep->fun = fun; + ep->emode = emode; + ep->size = offset - sizeof(Arith_t); + ep->staksize = cur.stakmaxsize+1; + if(last) + *last = (char*)(cur.nextchr); + return(ep); +} + +/* + * evaluate an integer arithmetic expression in s + * + * (Sfdouble_t)(*convert)(char** end, struct lval* string, int type, Sfdouble_t value) + * is a user supplied conversion routine that is called when unknown + * chars are encountered. + * *end points to the part to be converted and must be adjusted by convert to + * point to the next non-converted character; if typ is MESSAGE then string + * points to an error message string + * + * NOTE: (*convert)() may call strval() + */ + +Sfdouble_t strval(const char *s,char **end,Sfdouble_t(*conv)(const char**,struct lval*,int,Sfdouble_t),int emode) +{ + Arith_t *ep; + Sfdouble_t d; + char *sp=0; + int offset; + if(offset=staktell()) + sp = stakfreeze(1); + ep = arith_compile(s,end,conv,emode); + ep->emode = emode; + d = arith_exec(ep); + stakset(sp?sp:(char*)ep,offset); + return(d); +} + +#if _mem_name__exception +#undef _mem_name_exception +#define _mem_name_exception 1 +#undef exception +#define exception _exception +#undef matherr +#endif + +#if _mem_name_exception + +#undef error + +#if _BLD_shell && defined(__EXPORT__) +#define extern __EXPORT__ +#endif + +#ifndef DOMAIN +#define DOMAIN _DOMAIN +#endif +#ifndef OVERFLOW +#define OVERFLOW _OVERFLOW +#endif +#ifndef SING +#define SING _SING +#endif + + extern int matherr(struct exception *ep) + { + const char *message; + switch(ep->type) + { +#ifdef DOMAIN + case DOMAIN: + message = ERROR_dictionary(e_domain); + break; +#endif +#ifdef OVERFLOW + case OVERFLOW: + message = ERROR_dictionary(e_overflow); + break; +#endif +#ifdef SING + case SING: + message = ERROR_dictionary(e_singularity); + break; +#endif + default: + return(1); + } + level=0; + errormsg(SH_DICT,ERROR_exit(1),message,ep->name); + return(0); + } + +#undef extern + +#endif /* _mem_name_exception */ diff --git a/usr/src/lib/libshell/common/sh/string.c b/usr/src/lib/libshell/common/sh/string.c new file mode 100644 index 0000000000..2d5fcaaac3 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/string.c @@ -0,0 +1,704 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * string processing routines for Korn shell + * + */ + +#include <ast.h> +#include <ast_wchar.h> +#include "defs.h" +#include <stak.h> +#include <ctype.h> +#include <ccode.h> +#include "shtable.h" +#include "lexstates.h" +#include "national.h" + +#if !SHOPT_MULTIBYTE +#define mbchar(p) (*(unsigned char*)p++) +#endif + +#if _hdr_wctype +# include <wctype.h> +#endif + +#if !_lib_iswprint && !defined(iswprint) +# define iswprint(c) (((c)&~0377) || isprint(c)) +#endif + + +/* + * Table lookup routine + * <table> is searched for string <sp> and corresponding value is returned + * This is only used for small tables and is used to save non-sharable memory + */ + +const Shtable_t *sh_locate(register const char *sp,const Shtable_t *table,int size) +{ + register int first; + register const Shtable_t *tp; + register int c; + static const Shtable_t empty = {0,0}; + if(sp==0 || (first= *sp)==0) + return(&empty); + tp=table; + while((c= *tp->sh_name) && (CC_NATIVE!=CC_ASCII || c <= first)) + { + if(first == c && strcmp(sp,tp->sh_name)==0) + return(tp); + tp = (Shtable_t*)((char*)tp+size); + } + return(&empty); +} + +/* + * shtab_options lookup routine + */ + +#define sep(c) ((c)=='-'||(c)=='_') + +int sh_lookopt(register const char *sp, int *invert) +{ + register int first; + register const Shtable_t *tp; + register int c; + register const char *s, *t, *sw, *tw; + int amb; + int hit; + int inv; + int no; + if(sp==0) + return(0); + if(*sp=='n' && *(sp+1)=='o' && (*(sp+2)!='t' || *(sp+3)!='i')) + { + sp+=2; + if(sep(*sp)) + sp++; + *invert = !*invert; + } + if((first= *sp)==0) + return(0); + tp=shtab_options; + amb=hit=0; + for(;;) + { + t=tp->sh_name; + if(no = *t=='n' && *(t+1)=='o' && *(t+2)!='t') + t+=2; + if(!(c= *t)) + break; + if(first == c) + { + if(strcmp(sp,t)==0) + { + *invert ^= no; + return(tp->sh_number); + } + s=sw=sp; + tw=t; + for(;;) + { + if(!*s || *s=='=') + { + if (*s == '=' && !strtol(s+1, NiL, 0)) + no = !no; + if (!*t) + { + *invert ^= no; + return(tp->sh_number); + } + if (hit || amb) + { + hit = 0; + amb = 1; + } + else + { + hit = tp->sh_number; + inv = no; + } + break; + } + else if(!*t) + break; + else if(sep(*s)) + sw = ++s; + else if(sep(*t)) + tw = ++t; + else if(*s==*t) + { + s++; + t++; + } + else if(s==sw && t==tw) + break; + else + { + if(t!=tw) + { + while(*t && !sep(*t)) + t++; + if(!*t) + break; + tw = ++t; + } + while (s>sw && *s!=*t) + s--; + } + } + } + tp = (Shtable_t*)((char*)tp+sizeof(*shtab_options)); + } + if(hit) + *invert ^= inv; + return(hit); +} + +/* + * look for the substring <oldsp> in <string> and replace with <newsp> + * The new string is put on top of the stack + */ +char *sh_substitute(const char *string,const char *oldsp,char *newsp) +/*@ + assume string!=NULL && oldsp!=NULL && newsp!=NULL; + return x satisfying x==NULL || + strlen(x)==(strlen(in string)+strlen(in newsp)-strlen(in oldsp)); +@*/ +{ + register const char *sp = string; + register const char *cp; + const char *savesp = 0; + stakseek(0); + if(*sp==0) + return((char*)0); + if(*(cp=oldsp) == 0) + goto found; +#if SHOPT_MULTIBYTE + mbinit(); +#endif /* SHOPT_MULTIBYTE */ + do + { + /* skip to first character which matches start of oldsp */ + while(*sp && (savesp==sp || *sp != *cp)) + { +#if SHOPT_MULTIBYTE + /* skip a whole character at a time */ + int c = mbsize(sp); + if(c < 0) + sp++; + while(c-- > 0) +#endif /* SHOPT_MULTIBYTE */ + stakputc(*sp++); + } + if(*sp == 0) + return((char*)0); + savesp = sp; + for(;*cp;cp++) + { + if(*cp != *sp++) + break; + } + if(*cp==0) + /* match found */ + goto found; + sp = savesp; + cp = oldsp; + } + while(*sp); + return((char*)0); + +found: + /* copy new */ + stakputs(newsp); + /* copy rest of string */ + stakputs(sp); + return(stakfreeze(1)); +} + +/* + * TRIM(sp) + * Remove escape characters from characters in <sp> and eliminate quoted nulls. + */ + +void sh_trim(register char *sp) +/*@ + assume sp!=NULL; + promise strlen(in sp) <= in strlen(sp); +@*/ +{ + register char *dp; + register int c; + if(sp) + { + dp = sp; + while(c= *sp) + { +#if SHOPT_MULTIBYTE + int len; + if(mbwide() && (len=mbsize(sp))>1) + { + dp += len; + sp += len; + continue; + } +#endif /* SHOPT_MULTIBYTE */ + sp++; + if(c == '\\') + c = *sp++; + if(c) + *dp++ = c; + } + *dp = 0; + } +} + +/* + * copy <str1> to <str2> changing upper case to lower case + * <str2> must be big enough to hold <str1> + * <str1> and <str2> may point to the same place. + */ + +void sh_utol(register char const *str1,register char *str2) +/*@ + assume str1!=0 && str2!=0 + return x satisfying strlen(in str1)==strlen(in str2); +@*/ +{ + register int c; + for(; c= *((unsigned char*)str1); str1++,str2++) + { + if(isupper(c)) + *str2 = tolower(c); + else + *str2 = c; + } + *str2 = 0; +} + +/* + * print <str> quoting chars so that it can be read by the shell + * puts null terminated result on stack, but doesn't freeze it + */ +char *sh_fmtq(const char *string) +{ + register const char *cp = string; + register int c, state; + int offset; + if(!cp) + return((char*)0); + offset = staktell(); +#if SHOPT_MULTIBYTE + state = ((c= mbchar(cp))==0); +#else + state = ((c= *(unsigned char*)cp++)==0); +#endif + if(isaletter(c)) + { +#if SHOPT_MULTIBYTE + while((c=mbchar(cp)),isaname(c)); +#else + while((c = *(unsigned char*)cp++),isaname(c)); +#endif + if(c==0) + return((char*)string); + if(c=='=') + { + if(*cp==0) + return((char*)string); + c = cp - string; + stakwrite(string,c); + string = cp; +#if SHOPT_MULTIBYTE + c = mbchar(cp); +#else + c = *(unsigned char*)cp++; +#endif + } + } + if(c==0 || c=='#' || c=='~') + state = 1; +#if SHOPT_MULTIBYTE + for(;c;c= mbchar(cp)) +#else + for(;c; c= *(unsigned char*)cp++) +#endif + { +#if SHOPT_MULTIBYTE + if(c>=0x200) + continue; + if(c=='\'' || !iswprint(c)) +#else + if(c=='\'' || !isprint(c)) +#endif /* SHOPT_MULTIBYTE */ + state = 2; + else if(c==']' || (c!=':' && (c=sh_lexstates[ST_NORM][c]) && c!=S_EPAT)) + state |=1; + } + if(state<2) + { + if(state==1) + stakputc('\''); + if(c = --cp - string) + stakwrite(string,c); + if(state==1) + stakputc('\''); + } + else + { + stakwrite("$'",2); + cp = string; +#if SHOPT_MULTIBYTE + while(c= mbchar(cp)) +#else + while(c= *(unsigned char*)cp++) +#endif + { + state=1; + switch(c) + { + case ('a'==97?'\033':39): + c = 'E'; + break; + case '\n': + c = 'n'; + break; + case '\r': + c = 'r'; + break; + case '\t': + c = 't'; + break; + case '\f': + c = 'f'; + break; + case '\b': + c = 'b'; + break; + case '\a': + c = 'a'; + break; + case '\\': case '\'': + break; + default: +#if SHOPT_MULTIBYTE + if(!iswprint(c)) +#else + if(!isprint(c)) +#endif + { + sfprintf(staksp,"\\%.3o",c); + continue; + } + state=0; + break; + } + if(state) + stakputc('\\'); + stakputc(c); + } + stakputc('\''); + } + stakputc(0); + return(stakptr(offset)); +} + +/* + * print <str> quoting chars so that it can be read by the shell + * puts null terminated result on stack, but doesn't freeze it + * single!=0 limits quoting to '...' + * fold>0 prints raw newlines and inserts appropriately + * escaped newlines every (fold-x) chars + */ +char *sh_fmtqf(const char *string, int single, int fold) +{ + register const char *cp = string; + register const char *bp; + register const char *vp; + register int c; + register int n; + register int q; + register int a; + int offset; + + if (--fold < 8) + fold = 0; + if (!cp || !*cp || !single && !fold || fold && strlen(string) < fold) + return sh_fmtq(cp); + offset = staktell(); + single = single ? 1 : 3; + c = mbchar(string); + a = isaletter(c) ? '=' : 0; + vp = cp + 1; + do + { + q = 0; + n = fold; + bp = cp; + while ((!n || n-- > 0) && (c = mbchar(cp))) + { + if (a && !isaname(c)) + a = 0; +#if SHOPT_MULTIBYTE + if (c >= 0x200) + continue; + if (c == '\'' || !iswprint(c)) +#else + if (c == '\'' || !isprint(c)) +#endif /* SHOPT_MULTIBYTE */ + { + q = single; + break; + } + if (c == '\n') + q = 1; + else if (c == a) + { + stakwrite(bp, cp - bp); + bp = cp; + vp = cp + 1; + a = 0; + } + else if ((c == '#' || c == '~') && cp == vp || c == ']' || c != ':' && (c = sh_lexstates[ST_NORM][c]) && c != S_EPAT) + q = 1; + } + if (q & 2) + { + stakputc('$'); + stakputc('\''); + cp = bp; + n = fold - 3; + q = 1; + while (c = mbchar(cp)) + { + switch (c) + { + case ('a'==97?'\033':39): + c = 'E'; + break; + case '\n': + q = 0; + n = fold - 1; + break; + case '\r': + c = 'r'; + break; + case '\t': + c = 't'; + break; + case '\f': + c = 'f'; + break; + case '\b': + c = 'b'; + break; + case '\a': + c = 'a'; + break; + case '\\': + if (*cp == 'n') + { + c = '\n'; + q = 0; + n = fold - 1; + break; + } + case '\'': + break; + default: +#if SHOPT_MULTIBYTE + if(!iswprint(c)) +#else + if(!isprint(c)) +#endif + { + if ((n -= 4) <= 0) + { + stakwrite("'\\\n$'", 5); + n = fold - 7; + } + sfprintf(staksp, "\\%03o", c); + continue; + } + q = 0; + break; + } + if ((n -= q + 1) <= 0) + { + if (!q) + { + stakputc('\''); + cp = bp; + break; + } + stakwrite("'\\\n$'", 5); + n = fold - 5; + } + if (q) + stakputc('\\'); + else + q = 1; + stakputc(c); + bp = cp; + } + if (!c) + stakputc('\''); + } + else if (q & 1) + { + stakputc('\''); + cp = bp; + n = fold ? (fold - 2) : 0; + while (c = mbchar(cp)) + { + if (c == '\n') + n = fold - 1; + else if (n && --n <= 0) + { + n = fold - 2; + stakwrite(bp, --cp - bp); + bp = cp; + stakwrite("'\\\n'", 4); + } + else if (n == 1 && *cp == '\'') + { + n = fold - 5; + stakwrite(bp, --cp - bp); + bp = cp; + stakwrite("'\\\n\\''", 6); + } + else if (c == '\'') + { + stakwrite(bp, cp - bp - 1); + bp = cp; + if (n && (n -= 4) <= 0) + { + n = fold - 5; + stakwrite("'\\\n\\''", 6); + } + else + stakwrite("'\\''", 4); + } + } + stakwrite(bp, cp - bp - 1); + stakputc('\''); + } + else if (n = fold) + { + cp = bp; + while (c = mbchar(cp)) + { + if (--n <= 0) + { + n = fold; + stakwrite(bp, --cp - bp); + bp = cp; + stakwrite("\\\n", 2); + } + } + stakwrite(bp, cp - bp - 1); + } + else + stakwrite(bp, cp - bp); + if (c) + { + stakputc('\\'); + stakputc('\n'); + } + } while (c); + stakputc(0); + return(stakptr(offset)); +} + +#if SHOPT_MULTIBYTE + int sh_strchr(const char *string, register const char *dp) + { + wchar_t c, d; + register const char *cp=string; + mbinit(); + d = mbchar(dp); + mbinit(); + while(c = mbchar(cp)) + { + if(c==d) + return(cp-string); + } + if(d==0) + return(cp-string); + return(-1); + } +#endif /* SHOPT_MULTIBYTE */ + +const char *_sh_translate(const char *message) +{ +#if ERROR_VERSION >= 20000317L + return(ERROR_translate(0,0,e_dict,message)); +#else +#if ERROR_VERSION >= 20000101L + return(ERROR_translate(e_dict,message)); +#else + return(ERROR_translate(message,1)); +#endif +#endif +} + +/* + * change '['identifier']' to identifier + * character before <str> must be a '[' + * returns pointer to last character + */ +char *sh_checkid(char *str, char *last) +{ + register unsigned char *cp = (unsigned char*)str; + register unsigned char *v = cp; + register int c; + if(c= *cp++,isaletter(c)) + while(c= *cp++,isaname(c)); + if(c==']' && (!last || ((char*)cp==last))) + { + /* eliminate [ and ] */ + while(v < cp) + { + v[-1] = *v; + v++; + } + if(last) + last -=2; + else + { + while(*v) + { + v[-2] = *v; + v++; + } + v[-2] = 0; + last = (char*)v; + } + } + return(last); +} + +#if _AST_VERSION <= 20000317L +char *fmtident(const char *string) +{ + return((char*)string); +} +#endif diff --git a/usr/src/lib/libshell/common/sh/subshell.c b/usr/src/lib/libshell/common/sh/subshell.c new file mode 100644 index 0000000000..550e299d42 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/subshell.c @@ -0,0 +1,555 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * Create and manage subshells avoiding forks when possible + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <ls.h> +#include "io.h" +#include "fault.h" +#include "shnodes.h" +#include "shlex.h" +#include "jobs.h" +#include "variables.h" +#include "path.h" + +#ifndef PIPE_BUF +# define PIPE_BUF 512 +#endif + +/* + * Note that the following structure must be the same + * size as the Dtlink_t structure + */ +struct Link +{ + struct Link *next; + Namval_t *node; +}; + +/* + * The following structure is used for command substitution and (...) + */ +static struct subshell +{ + struct subshell *prev; /* previous subshell data */ + struct subshell *pipe; /* subshell where output goes to pipe on fork */ + Dt_t *var; /* variable table at time of subshell */ + struct Link *svar; /* save shell variable table */ + Dt_t *sfun; /* function scope for subshell */ + Dt_t *salias;/* alias scope for subshell */ +#ifdef PATH_BFPATH + Pathcomp_t *pathlist; /* for PATH variable */ +#endif +#if (ERROR_VERSION >= 20030214L) + struct Error_context_s *errcontext; +#else + struct errorcontext *errcontext; +#endif + Shopt_t options;/* save shell options */ + pid_t subpid; /* child process id */ + Sfio_t* saveout;/*saved standard output */ + char *pwd; /* present working directory */ + const char *shpwd; /* saved pointer to sh.pwd */ + void *jobs; /* save job info */ + mode_t mask; /* saved umask */ + short tmpfd; /* saved tmp file descriptor */ + short pipefd; /* read fd if pipe is created */ + char jobcontrol; + char monitor; + unsigned char fdstatus; + int fdsaved; /* bit make for saved files */ + int bckpid; +} *subshell_data; + +static int subenv; + +/* + * This routine will turn the sftmp() file into a real /tmp file or pipe + * if the /tmp file create fails + */ +void sh_subtmpfile(void) +{ + if(sfset(sfstdout,0,0)&SF_STRING) + { + register int fd; + register struct checkpt *pp = (struct checkpt*)sh.jmplist; + register struct subshell *sp = subshell_data->pipe; + /* save file descriptor 1 if open */ + if((sp->tmpfd = fd = fcntl(1,F_DUPFD,10)) >= 0) + { + fcntl(fd,F_SETFD,FD_CLOEXEC); + sh.fdstatus[fd] = sh.fdstatus[1]|IOCLEX; + close(1); + } + else if(errno!=EBADF) + errormsg(SH_DICT,ERROR_system(1),e_toomany); + /* popping a discipline forces a /tmp file create */ + sfdisc(sfstdout,SF_POPDISC); + if((fd=sffileno(sfstdout))<0) + { + /* unable to create the /tmp file so use a pipe */ + int fds[2]; + Sfoff_t off; + sh_pipe(fds); + sp->pipefd = fds[0]; + sh_fcntl(sp->pipefd,F_SETFD,FD_CLOEXEC); + /* write the data to the pipe */ + if(off = sftell(sfstdout)) + write(fds[1],sfsetbuf(sfstdout,(Void_t*)sfstdout,0),(size_t)off); + sfclose(sfstdout); + if((sh_fcntl(fds[1],F_DUPFD, 1)) != 1) + errormsg(SH_DICT,ERROR_system(1),e_file+4); + sh_close(fds[1]); + } + else + { + sh.fdstatus[fd] = IOREAD|IOWRITE; + sfsync(sfstdout); + if(fd==1) + fcntl(1,F_SETFD,0); + else + { + sfsetfd(sfstdout,1); + sh.fdstatus[1] = sh.fdstatus[fd]; + sh.fdstatus[fd] = IOCLOSE; + } + } + sh_iostream(1); + sfset(sfstdout,SF_SHARE|SF_PUBLIC,1); + sfpool(sfstdout,sh.outpool,SF_WRITE); + if(pp && pp->olist && pp->olist->strm == sfstdout) + pp->olist->strm = 0; + } +} + +/* + * This routine creates a temp file if necessary and creates a subshell. + * The parent routine longjmps back to sh_subshell() + * The child continues possibly with its standard output replaced by temp file + */ +void sh_subfork(void) +{ + register struct subshell *sp = subshell_data; + pid_t pid; + /* see whether inside $(...) */ + if(sp->pipe) + sh_subtmpfile(); + if(pid = sh_fork(0,NIL(int*))) + { + /* this is the parent part of the fork */ + if(sp->subpid==0) + sp->subpid = pid; + siglongjmp(*sh.jmplist,SH_JMPSUB); + } + else + { + int16_t subshell; + /* this is the child part of the fork */ + /* setting subpid to 1 causes subshell to exit when reached */ + sh_onstate(SH_FORKED); + sh_onstate(SH_NOLOG); + sh_offstate(SH_MONITOR); + subshell_data = 0; + subshell = sh.subshell = 0; + nv_putval(SH_SUBSHELLNOD, (char*)&subshell, NV_INT16); + sp->subpid=0; + } +} + +/* + * This routine will make a copy of the given node in the + * layer created by the most recent subshell_fork if the + * node hasn't already been copied + */ +Namval_t *sh_assignok(register Namval_t *np,int add) +{ + register Namval_t *mp; + register struct Link *lp; + register struct subshell *sp = (struct subshell*)subshell_data; + int save; + /* don't bother with this */ + if(!sp->shpwd || (nv_isnull(np) && !add)) + return(np); + /* don't bother to save if in newer scope */ + if(nv_search((char*)np,sp->var,HASH_BUCKET)!=np) + return(np); + for(lp=subshell_data->svar; lp; lp = lp->next) + { + if(lp->node==np) + return(np); + } + mp = newof(0,Namval_t,1,0); + lp = (struct Link*)mp; + lp->node = np; + lp->next = subshell_data->svar; + subshell_data->svar = lp; + save = sh.subshell; + sh.subshell = 0;; + nv_clone(np,mp,NV_NOFREE); + sh.subshell = save; + return(np); +} + +/* + * restore the variables + */ +static void nv_restore(struct subshell *sp) +{ + register struct Link *lp, *lq; + register Namval_t *mp, *np; + const char *save = sp->shpwd; + sp->shpwd = 0; /* make sure sh_assignok doesn't save with nv_unset() */ + for(lp=sp->svar; lp; lp=lq) + { + np = (Namval_t*)lp; + mp = lp->node; + lq = lp->next; + if(nv_isarray(mp)) + nv_putsub(mp,NIL(char*),ARRAY_SCAN); + _nv_unset(mp,NV_RDONLY); + nv_setsize(mp,nv_size(np)); + if(!nv_isattr(np,NV_MINIMAL) || nv_isattr(np,NV_EXPORT)) + mp->nvenv = np->nvenv; + mp->nvfun = np->nvfun; + mp->nvflag = np->nvflag; + if((mp==nv_scoped(PATHNOD)) || (mp==nv_scoped(IFSNOD))) + nv_putval(mp, np->nvalue.cp,0); + else + mp->nvalue.cp = np->nvalue.cp; + np->nvfun = 0; + if(nv_isattr(mp,NV_EXPORT)) + { + char *name = nv_name(mp); + sh_envput(sh.env,mp); + if(*name=='_' && strcmp(name,"_AST_FEATURES")==0) + astconf(NiL, NiL, NiL); + } + else if(nv_isattr(np,NV_EXPORT)) + env_delete(sh.env,nv_name(mp)); + free((void*)np); + } + sp->shpwd=save; +} + +/* + * return pointer to alias tree + * create new one if in a subshell and one doesn't exist and create is non-zero + */ +Dt_t *sh_subaliastree(int create) +{ + register struct subshell *sp = subshell_data; + if(!sp || sh.curenv==0) + return(sh.alias_tree); + if(!sp->salias && create) + { + sp->salias = dtopen(&_Nvdisc,Dtoset); + dtview(sp->salias,sh.alias_tree); + sh.alias_tree = sp->salias; + } + return(sp->salias); +} + +/* + * return pointer to function tree + * create new one if in a subshell and one doesn't exist and create is non-zero + */ +Dt_t *sh_subfuntree(int create) +{ + register struct subshell *sp = subshell_data; + if(!sp || sh.curenv==0) + return(sh.fun_tree); + if(!sp->sfun && create) + { + sp->sfun = dtopen(&_Nvdisc,Dtoset); + dtview(sp->sfun,sh.fun_tree); + sh.fun_tree = sp->sfun; + } + return(sp->sfun); +} + +static void table_unset(register Dt_t *root) +{ + register Namval_t *np,*nq; + for(np=(Namval_t*)dtfirst(root);np;np=nq) + { + _nv_unset(np,NV_RDONLY); + nq = (Namval_t*)dtnext(root,np); + dtdelete(root,np); + free((void*)np); + } +} + +int sh_subsavefd(register int fd) +{ + register struct subshell *sp = subshell_data; + register int old=0; + if(sp) + { + old = !(sp->fdsaved&(1<<(fd-1))); + sp->fdsaved |= (1<<(fd-1)); + } + return(old); +} + +/* + * Run command tree <t> in a virtual sub-shell + * If comsub is not null, then output will be placed in temp file (or buffer) + * If comsub is not null, the return value will be a stream consisting of + * output of command <t>. Otherwise, NULL will be returned. + */ + +Sfio_t *sh_subshell(Shnode_t *t, int flags, int comsub) +{ + Shell_t *shp = &sh; + struct subshell sub_data; + register struct subshell *sp = &sub_data; + int jmpval,nsig; + int savecurenv = shp->curenv; + int16_t subshell; + char *savsig; + Sfio_t *iop=0; + struct checkpt buff; + struct sh_scoped savst; + struct dolnod *argsav=0; + memset((char*)sp, 0, sizeof(*sp)); + sfsync(shp->outpool); + argsav = sh_arguse(); + if(shp->curenv==0) + { + subshell_data=0; + subenv = 0; + } + shp->curenv = ++subenv; + savst = shp->st; + sh_pushcontext(&buff,SH_JMPSUB); + subshell = shp->subshell+1; + nv_putval(SH_SUBSHELLNOD, (char*)&subshell, NV_INT16); + shp->subshell = subshell; + sp->prev = subshell_data; + subshell_data = sp; + sp->errcontext = &buff.err; + sp->var = shp->var_tree; + sp->options = shp->options; + sp->jobs = job_subsave(); +#ifdef PATH_BFPATH + /* make sure initialization has occurred */ + if(!shp->pathlist) + path_get("."); + sp->pathlist = path_dup((Pathcomp_t*)shp->pathlist); +#endif + if(!shp->pwd) + path_pwd(0); + sp->bckpid = shp->bckpid; + if(!comsub || !sh_isoption(SH_SUBSHARE)) + { + sp->shpwd = shp->pwd; + sp->pwd = (shp->pwd?strdup(shp->pwd):0); + sp->mask = shp->mask; + /* save trap table */ + shp->st.otrapcom = 0; + if((nsig=shp->st.trapmax*sizeof(char*))>0 || shp->st.trapcom[0]) + { + nsig += sizeof(char*); + memcpy(savsig=malloc(nsig),(char*)&shp->st.trapcom[0],nsig); + /* this nonsense needed for $(trap) */ + shp->st.otrapcom = (char**)savsig; + } + sh_sigreset(0); + } + jmpval = sigsetjmp(buff.buff,0); + if(jmpval==0) + { + if(comsub) + { + /* disable job control */ + sp->jobcontrol = job.jobcontrol; + sp->monitor = (sh_isstate(SH_MONITOR)!=0); + job.jobcontrol=0; + sh_offstate(SH_MONITOR); + sp->pipe = sp; + /* save sfstdout and status */ + sp->saveout = sfswap(sfstdout,NIL(Sfio_t*)); + sp->fdstatus = shp->fdstatus[1]; + sp->tmpfd = -1; + sp->pipefd = -1; + /* use sftmp() file for standard output */ + if(!(iop = sftmp(PIPE_BUF))) + { + sfswap(sp->saveout,sfstdout); + errormsg(SH_DICT,ERROR_system(1),e_tmpcreate); + } + sfswap(iop,sfstdout); + sfset(sfstdout,SF_READ,0); + shp->fdstatus[1] = IOWRITE; + } + else if(sp->prev) + { + sp->pipe = sp->prev->pipe; + flags &= ~sh_state(SH_NOFORK); + } + sh_exec(t,flags); + } + if(jmpval!=SH_JMPSUB && shp->st.trapcom[0] && shp->subshell) + { + /* trap on EXIT not handled by child */ + char *trap=shp->st.trapcom[0]; + shp->st.trapcom[0] = 0; /* prevent recursion */ + shp->oldexit = shp->exitval; + sh_trap(trap,0); + free(trap); + } + sh_popcontext(&buff); + if(shp->subshell==0) /* must be child process */ + { + subshell_data = sp->prev; + if(jmpval==SH_JMPSCRIPT) + siglongjmp(*shp->jmplist,jmpval); + sh_done(0); + } + if(comsub) + { + /* re-enable job control */ + job.jobcontrol = sp->jobcontrol; + if(sp->monitor) + sh_onstate(SH_MONITOR); + if(sp->pipefd>=0) + { + /* sftmp() file has been returned into pipe */ + iop = sh_iostream(sp->pipefd); + sfdisc(iop,SF_POPDISC); + sfclose(sfstdout); + } + else + { + /* move tmp file to iop and restore sfstdout */ + iop = sfswap(sfstdout,NIL(Sfio_t*)); + if(!iop) + { + /* maybe locked try again */ + sfclrlock(sfstdout); + iop = sfswap(sfstdout,NIL(Sfio_t*)); + } + if(iop && sffileno(iop)==1) + { + int fd=sfsetfd(iop,3); + if(fd<0) + errormsg(SH_DICT,ERROR_system(1),e_toomany); + shp->sftable[fd] = iop; + fcntl(fd,F_SETFD,FD_CLOEXEC); + shp->fdstatus[fd] = (shp->fdstatus[1]|IOCLEX); + shp->fdstatus[1] = IOCLOSE; + } + sfset(iop,SF_READ,1); + } + sfswap(sp->saveout,sfstdout); + /* check if standard output was preserved */ + if(sp->tmpfd>=0) + { + close(1); + fcntl(sp->tmpfd,F_DUPFD,1); + sh_close(sp->tmpfd); + } + shp->fdstatus[1] = sp->fdstatus; + } + if(sp->subpid) + job_wait(sp->subpid); + if(comsub && iop) + sfseek(iop,(off_t)0,SEEK_SET); + if(shp->subshell) + shp->subshell--; + subshell = shp->subshell; + nv_putval(SH_SUBSHELLNOD, (char*)&subshell, NV_INT16); +#ifdef PATH_BFPATH + path_delete((Pathcomp_t*)shp->pathlist); + shp->pathlist = (void*)sp->pathlist; +#endif + job_subrestore(sp->jobs); + shp->jobenv = savecurenv; + shp->bckpid = sp->bckpid; + if(sp->shpwd) /* restore environment if saved */ + { + shp->options = sp->options; + nv_restore(sp); + if(sp->salias) + { + shp->alias_tree = dtview(sp->salias,0); + table_unset(sp->salias); + dtclose(sp->salias); + } + if(sp->sfun) + { + shp->fun_tree = dtview(sp->sfun,0); + table_unset(sp->sfun); + dtclose(sp->sfun); + } + sh_sigreset(1); + shp->st = savst; + shp->curenv = savecurenv; + if(nsig) + { + memcpy((char*)&shp->st.trapcom[0],savsig,nsig); + free((void*)savsig); + } + shp->options = sp->options; + if(!shp->pwd || strcmp(sp->pwd,shp->pwd)) + { + /* restore PWDNOD */ + Namval_t *pwdnod = nv_scoped(PWDNOD); + if(shp->pwd) + { + chdir(shp->pwd=sp->pwd); +#ifdef PATH_BFPATH + path_newdir(shp->pathlist); +#endif + } + if(nv_isattr(pwdnod,NV_NOFREE)) + pwdnod->nvalue.cp = (const char*)sp->pwd; + } + else if(sp->shpwd != shp->pwd) + { + shp->pwd = sp->pwd; + if(PWDNOD->nvalue.cp==sp->shpwd) + PWDNOD->nvalue.cp = sp->pwd; + } + else + free((void*)sp->pwd); + if(sp->mask!=shp->mask) + umask(shp->mask); + } + subshell_data = sp->prev; + sh_argfree(argsav,0); + shp->trapnote = 0; + if(shp->topfd != buff.topfd) + sh_iorestore(buff.topfd|IOSUBSHELL,jmpval); + if(shp->exitval > SH_EXITSIG) + { + int sig = shp->exitval&SH_EXITMASK; + if(sig==SIGINT || sig== SIGQUIT) + sh_fault(sig); + } + return(iop); +} diff --git a/usr/src/lib/libshell/common/sh/suid_exec.c b/usr/src/lib/libshell/common/sh/suid_exec.c new file mode 100644 index 0000000000..0495d29890 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/suid_exec.c @@ -0,0 +1,509 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * This is a program to execute 'execute only' and suid/sgid shell scripts. + * This program must be owned by root and must have the set uid bit set. + * It must not have the set group id bit set. This program must be installed + * where the define parameter THISPROG indicates to work correctly on system V + * + * Written by David Korn + * AT&T Labs + * Enhanced by Rob Stampfli + */ + +/* The file name of the script to execute is argv[0] + * Argv[1] is the program name + * The basic idea is to open the script as standard input, set the effective + * user and group id correctly, and then exec the shell. + * The complicated part is getting the effective uid of the caller and + * setting the effective uid/gid. The program which execs this program + * may pass file descriptor FDIN as an open file with mode SPECIAL if + * the effective user id is not the real user id. The effective + * user id for authentication purposes will be the owner of this + * open file. On systems without the setreuid() call, e[ug]id is set + * by copying this program to a /tmp/file, making it a suid and/or sgid + * program, and then execing this program. + * A forked version of this program waits until it can unlink the /tmp + * file and then exits. Actually, we fork() twice so the parent can + * wait for the child to complete. A pipe is used to guarantee that we + * do not remove the /tmp file too soon. + */ + +#include <ast.h> +#include "FEATURE/externs" +#include <ls.h> +#include <sig.h> +#include <error.h> +#include <sys/wait.h> +#include "version.h" + +#define SPECIAL 04100 /* setuid execute only by owner */ +#define FDIN 10 /* must be same as /dev/fd below */ +#undef FDSYNC +#define FDSYNC 11 /* used on sys5 to synchronize cleanup */ +#define FDVERIFY 12 /* used to validate /tmp process */ +#undef BLKSIZE +#define BLKSIZE sizeof(char*)*1024 +#define THISPROG "/etc/suid_exec" +#define DEFSHELL "/bin/sh" + +static void error_exit(const char*); +static int in_dir(const char*, const char*); +static int endsh(const char*); +#ifndef _lib_setregid +# undef _lib_setreuid +#endif +#ifndef _lib_setreuid + static void setids(int,uid_t,gid_t); + static int mycopy(int, int); + static void maketemp(char*); +#else + static void setids(int,int,int); +#endif /* _lib_setreuid */ + +static const char version[] = "\n@(#)$Id: suid_exec "SH_RELEASE" $\n"; +static const char badopen[] = "cannot open"; +static const char badexec[] = "cannot exec"; +static const char devfd[] = "/dev/fd/10"; /* must match FDIN above */ +static char tmpname[] = "/tmp/SUIDXXXXXX"; +static char **arglist; + +static char *shell; +static char *command; +static uid_t ruserid; +static uid_t euserid; +static gid_t rgroupid; +static gid_t egroupid; +static struct stat statb; + +int main(int argc,char *argv[]) +{ + register int m,n; + register char *p; + struct stat statx; + int mode; + uid_t effuid; + gid_t effgid; + NOT_USED(argc); + arglist = argv; + if((command = argv[1]) == 0) + error_exit(badexec); + ruserid = getuid(); + euserid = geteuid(); + rgroupid = getgid(); + egroupid = getegid(); + p = argv[0]; +#ifndef _lib_setreuid + maketemp(tmpname); + if(strcmp(p,tmpname)==0) + { + /* At this point, the presumption is that we are the + * version of THISPROG copied into /tmp, with the owner, + * group, and setuid/gid bits correctly set. This copy of + * the program is executable by anyone, so we must be careful + * not to allow just any invocation of it to succeed, since + * it is setuid/gid. Validate the proper execution by + * examining the FDVERIFY file descriptor -- if it is owned + * by root and is mode SPECIAL, then this is proof that it was + * passed by a program with superuser privileges -- hence we + * can presume legitimacy. Otherwise, bail out, as we suspect + * an impostor. + */ + if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 || + (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0) + error_exit(badexec); + /* This enables the grandchild to clean up /tmp file */ + close(FDSYNC); + /* Make sure that this is a valid invocation of the clone. + * Perhaps unnecessary, given FDVERIFY, but what the heck... + */ + if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 || + !S_ISREG(statb.st_mode)) + error_exit(badexec); + if(ruserid != euserid && + ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid)) + error_exit(badexec); + goto exec; + } + /* Make sure that this is the real setuid program, not the clone. + * It is possible by clever hacking to get past this point in the + * clone, but it doesn't do the hacker any good that I can see. + */ + if(euserid) + error_exit(badexec); +#endif /* _lib_setreuid */ + /* Open the script for reading first and then validate it. This + * prevents someone from pulling a switcheroo while we are validating. + */ + n = open(p,0); + if(n == FDIN) + { + n = dup(n); + close(FDIN); + } + if(n < 0) + error_exit(badopen); + /* validate execution rights to this script */ + if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL) + euserid = ruserid; + else + euserid = statb.st_uid; + /* do it the easy way if you can */ + if(euserid == ruserid && egroupid == rgroupid) + { + if(access(p,X_OK) < 0) + error_exit(badexec); + } + else + { + /* have to check access on each component */ + while(*p++) + { + if(*p == '/' || *p == 0) + { + m = *p; + *p = 0; + if(eaccess(argv[0],X_OK) < 0) + error_exit(badexec); + *p = m; + } + } + p = argv[0]; + } + if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode)) + error_exit(badopen); + if(stat(p, &statx) < 0 || + statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev) + error_exit(badexec); + if(stat(THISPROG, &statx) < 0 || + (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev)) + error_exit(badexec); + close(FDIN); + if(fcntl(n,F_DUPFD,FDIN) != FDIN) + error_exit(badexec); + close(n); + + /* compute the desired new effective user and group id */ + effuid = euserid; + effgid = egroupid; + mode = 0; + if(statb.st_mode & S_ISUID) + effuid = statb.st_uid; + if(statb.st_mode & S_ISGID) + effgid = statb.st_gid; + + /* see if group needs setting */ + if(effgid != egroupid) + if(effgid != rgroupid || setgid(rgroupid) < 0) + mode = S_ISGID; + + /* now see if the uid needs setting */ + if(mode) + { + if(effuid != ruserid) + mode |= S_ISUID; + } + else if(effuid) + { + if(effuid != ruserid || setuid(ruserid) < 0) + mode = S_ISUID; + } + + if(mode) + setids(mode, effuid, effgid); +#ifndef _lib_setreuid +exec: +#endif /* _lib_setreuid */ + /* only use SHELL if file is in trusted directory and ends in sh */ + shell = getenv("SHELL"); + if(shell == 0 || !endsh(shell) || ( + !in_dir("/bin",shell) && + !in_dir("/usr/bin",shell) && + !in_dir("/usr/lbin",shell) && + !in_dir("/usr/local/bin",shell))) + shell = DEFSHELL; + argv[0] = command; + argv[1] = (char*)devfd; + execv(shell,argv); + error_exit(badexec); +} + +/* + * return true of shell ends in sh of ksh + */ + +static int endsh(register const char *shell) +{ + while(*shell) + shell++; + if(*--shell != 'h' || *--shell != 's') + return(0); + if(*--shell=='/') + return(1); + if(*shell=='k' && *--shell=='/') + return(1); + return(0); +} + + +/* + * return true of shell is in <dir> directory + */ + +static int in_dir(register const char *dir,register const char *shell) +{ + while(*dir) + { + if(*dir++ != *shell++) + return(0); + } + /* return true if next character is a '/' */ + return(*shell=='/'); +} + +static void error_exit(const char *message) +{ + sfprintf(sfstdout,"%s: %s\n",command,message); + exit(126); +} + + +/* + * This version of access checks against effective uid and effective gid + */ + +int eaccess(register const char *name, register int mode) +{ + struct stat statb; + if (stat(name, &statb) == 0) + { + if(euserid == 0) + { + if(!S_ISREG(statb.st_mode) || mode != 1) + return(0); + /* root needs execute permission for someone */ + mode = (S_IXUSR|S_IXGRP|S_IXOTH); + } + else if(euserid == statb.st_uid) + mode <<= 6; + else if(egroupid == statb.st_gid) + mode <<= 3; +#ifdef _lib_getgroups + /* on some systems you can be in several groups */ + else + { + static int maxgroups; + gid_t *groups=0; + register int n; + if(maxgroups==0) + { + /* first time */ + if((maxgroups=getgroups(0,groups)) < 0) + { + /* pre-POSIX system */ + maxgroups=NGROUPS_MAX; + } + } + groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t)); + n = getgroups(maxgroups,groups); + while(--n >= 0) + { + if(groups[n] == statb.st_gid) + { + mode <<= 3; + break; + } + } + } +#endif /* _lib_getgroups */ + if(statb.st_mode & mode) + return(0); + } + return(-1); +} + +#ifdef _lib_setreuid +static void setids(int mode,int owner,int group) +{ + if(mode & S_ISGID) + setregid(rgroupid,group); + + /* set effective uid even if S_ISUID is not set. This is because + * we are *really* executing EUID root at this point. Even if S_ISUID + * is not set, the value for owner that is passsed should be correct. + */ + setreuid(ruserid,owner); +} + +#else +/* + * This version of setids creats a /tmp file and copies itself into it. + * The "clone" file is made executable with appropriate suid/sgid bits. + * Finally, the clone is exec'ed. This file is unlinked by a grandchild + * of this program, who waits around until the text is free. + */ + +static void setids(int mode,uid_t owner,gid_t group) +{ + register int n,m; + int pv[2]; + + /* + * Create a token to pass to the new program for validation. + * This token can only be procured by someone running with an + * effective userid of root, and hence gives the clone a way to + * certify that it was really invoked by THISPROG. Someone who + * is already root could spoof us, but why would they want to? + * + * Since we are root here, we must be careful: What if someone + * linked a valuable file to tmpname? + */ + unlink(tmpname); /* should normally fail */ +#ifdef O_EXCL + if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 || + unlink(tmpname) < 0) +#else + if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0) +#endif + error_exit(badexec); + if(n != FDVERIFY) + { + close(FDVERIFY); + if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY) + error_exit(badexec); + } + mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6); + /* create a pipe for synchronization */ + if(pipe(pv) < 0) + error_exit(badexec); + if((n=fork()) == 0) + { /* child */ + close(FDVERIFY); + close(pv[1]); + if((n=fork()) == 0) + { /* grandchild -- cleans up clone file */ + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + read(pv[0],pv,1); /* wait for clone to close pipe */ + while(unlink(tmpname) < 0 && errno == ETXTBSY) + sleep(1); + exit(0); + } + else if(n == -1) + exit(1); + else + { + /* Create a set[ug]id file that will become the clone. + * To make this atomic, without need for chown(), the + * child takes on desired user and group. The only + * downsize of this that I can see is that it may + * screw up some per- * user accounting. + */ + if((m = open(THISPROG, O_RDONLY)) < 0) + exit(1); + if((mode & S_ISGID) && setgid(group) < 0) + exit(1); + if((mode & S_ISUID) && owner && setuid(owner) < 0) + exit(1); +#ifdef O_EXCL + if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0) +#else + unlink(tmpname); + if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0) +#endif /* O_EXCL */ + exit(1); + /* populate the clone */ + m = mycopy(m,n); + if(chmod(tmpname,mode) <0) + exit(1); + exit(m); + } + } + else if(n == -1) + error_exit(badexec); + else + { + arglist[0] = (char*)tmpname; + close(pv[0]); + /* move write end of pipe into FDSYNC */ + if(pv[1] != FDSYNC) + { + close(FDSYNC); + if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC) + error_exit(badexec); + } + /* wait for child to die */ + while((m = wait(0)) != n) + if(m == -1 && errno != EINTR) + break; + /* Kill any setuid status at this point. That way, if the + * clone is not setuid, we won't exec it as root. Also, don't + * neglect to consider that someone could have switched the + * clone file on us. + */ + if(setuid(ruserid) < 0) + error_exit(badexec); + execv(tmpname,arglist); + error_exit(badexec); + } +} + +/* + * create a unique name into the <template> + */ + +static void maketemp(char *template) +{ + register char *cp = template; + register pid_t n = getpid(); + /* skip to end of string */ + while(*++cp); + /* convert process id to string */ + while(n > 0) + { + *--cp = (n%10) + '0'; + n /= 10; + } + +} + +/* + * copy THISPROG into the open file number <fdo> and close <fdo> + */ + +static int mycopy(int fdi, int fdo) +{ + char buffer[BLKSIZE]; + register int n; + + while((n = read(fdi,buffer,BLKSIZE)) > 0) + if(write(fdo,buffer,n) != n) + break; + close(fdi); + close(fdo); + return n; +} + +#endif /* _lib_setreuid */ + + diff --git a/usr/src/lib/libshell/common/sh/tdump.c b/usr/src/lib/libshell/common/sh/tdump.c new file mode 100644 index 0000000000..80ef55a9aa --- /dev/null +++ b/usr/src/lib/libshell/common/sh/tdump.c @@ -0,0 +1,260 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * David Korn + * AT&T Labs + * + * shell parse tree dump + * + */ + +#include <ccode.h> +#include "defs.h" +#include "shnodes.h" +#include "path.h" +#include "io.h" + +static int p_comlist(const struct dolnod*); +static int p_arg(const struct argnod*); +static int p_comarg(const struct comnod*); +static int p_redirect(const struct ionod*); +static int p_switch(const struct regnod*); +static int p_tree(const Shnode_t*); +static int p_string(const char*); + +static Sfio_t *outfile; + +int sh_tdump(Sfio_t *out, const Shnode_t *t) +{ + outfile = out; + return(p_tree(t)); +} + +/* + * convert to ASCII to write and back again if needed + */ +static int outstring(Sfio_t *out, const char *string, int n) +{ + int r; + char *cp = (char*)string; + ccmaps(cp, n, CC_NATIVE, CC_ASCII); + r = sfwrite(out,cp,n); + ccmaps(cp, n, CC_ASCII, CC_NATIVE); + return(r); +} + +/* + * print script corresponding to shell tree <t> + */ +static int p_tree(register const Shnode_t *t) +{ + if(!t) + return(sfputl(outfile,-1)); + if(sfputl(outfile,t->tre.tretyp)<0) + return(-1); + switch(t->tre.tretyp&COMMSK) + { + case TTIME: + case TPAR: + return(p_tree(t->par.partre)); + case TCOM: + return(p_comarg((struct comnod*)t)); + case TSETIO: + case TFORK: + if(sfputu(outfile,t->fork.forkline)<0) + return(-1); + if(p_tree(t->fork.forktre)<0) + return(-1); + return(p_redirect(t->fork.forkio)); + case TIF: + if(p_tree(t->if_.iftre)<0) + return(-1); + if(p_tree(t->if_.thtre)<0) + return(-1); + return(p_tree(t->if_.eltre)); + case TWH: + if(t->wh.whinc) + { + if(p_tree((Shnode_t*)(t->wh.whinc))<0) + return(-1); + } + else + { + if(sfputl(outfile,-1)<0) + return(-1); + } + if(p_tree(t->wh.whtre)<0) + return(-1); + return(p_tree(t->wh.dotre)); + case TLST: + case TAND: + case TORF: + case TFIL: + if(p_tree(t->lst.lstlef)<0) + return(-1); + return(p_tree(t->lst.lstrit)); + case TARITH: + if(sfputu(outfile,t->ar.arline)<0) + return(-1); + return(p_arg(t->ar.arexpr)); + case TFOR: + if(sfputu(outfile,t->for_.forline)<0) + return(-1); + if(p_tree(t->for_.fortre)<0) + return(-1); + if(p_string(t->for_.fornam)<0) + return(-1); + return(p_tree((Shnode_t*)t->for_.forlst)); + case TSW: + if(sfputu(outfile,t->sw.swline)<0) + return(-1); + if(p_arg(t->sw.swarg)<0) + return(-1); + return(p_switch(t->sw.swlst)); + case TFUN: + if(sfputu(outfile,t->funct.functline)<0) + return(-1); + if(p_string(t->funct.functnam)<0) + return(-1); + if(p_tree(t->funct.functtre)<0) + return(-1); + return(p_tree((Shnode_t*)t->funct.functargs)); + case TTST: + if(sfputu(outfile,t->tst.tstline)<0) + return(-1); + if((t->tre.tretyp&TPAREN)==TPAREN) + return(p_tree(t->lst.lstlef)); + else + { + if(p_arg(&(t->lst.lstlef->arg))<0) + return(-1); + if((t->tre.tretyp&TBINARY)) + return(p_arg(&(t->lst.lstrit->arg))); + return(0); + } + } + return(-1); +} + +static int p_arg(register const struct argnod *arg) +{ + register int n; + struct fornod *fp; + while(arg) + { + if((n = strlen(arg->argval)) || (arg->argflag&~ARG_APPEND)) + fp=0; + else + { + fp=(struct fornod*)arg->argchn.ap; + n = strlen(fp->fornam)+1; + } + sfputu(outfile,n+1); + if(fp) + { + sfputc(outfile,0); + outstring(outfile,fp->fornam,n-1); + } + else + outstring(outfile,arg->argval,n); + sfputc(outfile,arg->argflag); + if(fp) + { + sfputu(outfile,fp->fortyp); + p_tree(fp->fortre); + } + arg = arg->argnxt.ap; + } + return(sfputu(outfile,0)); +} + +static int p_redirect(register const struct ionod *iop) +{ + while(iop) + { + if(iop->iovname) + sfputl(outfile,iop->iofile|IOVNM); + else + sfputl(outfile,iop->iofile); + p_string(iop->ioname); + if(iop->iodelim) + { + p_string(iop->iodelim); + sfputl(outfile,iop->iosize); + sfseek(sh.heredocs,iop->iooffset,SEEK_SET); + sfmove(sh.heredocs,outfile, iop->iosize,-1); + } + else + sfputu(outfile,0); + if(iop->iovname) + p_string(iop->iovname); + iop = iop->ionxt; + } + return(sfputl(outfile,-1)); +} + +static int p_comarg(register const struct comnod *com) +{ + p_redirect(com->comio); + p_arg(com->comset); + if(!com->comarg) + sfputl(outfile,-1); + else if(com->comtyp&COMSCAN) + p_arg(com->comarg); + else + p_comlist((struct dolnod*)com->comarg); + return(sfputu(outfile,com->comline)); +} + +static int p_comlist(const struct dolnod *dol) +{ + register char *cp, *const*argv; + register int n; + argv = dol->dolval+ARG_SPARE; + while(cp = *argv) + argv++; + n = argv - (dol->dolval+1); + sfputl(outfile,n); + argv = dol->dolval+ARG_SPARE; + while(cp = *argv++) + p_string(cp); + return(sfputu(outfile,0)); +} + +static int p_switch(register const struct regnod *reg) +{ + while(reg) + { + sfputl(outfile,reg->regflag); + p_arg(reg->regptr); + p_tree(reg->regcom); + reg = reg->regnxt; + } + return(sfputl(outfile,-1)); +} + +static int p_string(register const char *string) +{ + register size_t n=strlen(string); + if(sfputu(outfile,n+1)<0) + return(-1); + return(outstring(outfile,string,n)); +} diff --git a/usr/src/lib/libshell/common/sh/timers.c b/usr/src/lib/libshell/common/sh/timers.c new file mode 100644 index 0000000000..ae57a3336f --- /dev/null +++ b/usr/src/lib/libshell/common/sh/timers.c @@ -0,0 +1,248 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include <ast.h> +#include <sig.h> +#include <error.h> +#include "fault.h" +#include "defs.h" +#include "FEATURE/sigfeatures" +#include "FEATURE/time" + +typedef struct _timer +{ + double wakeup; + double incr; + struct _timer *next; + void (*action)(void*); + void *handle; +} Timer_t; + +#define IN_ADDTIMEOUT 1 +#define IN_SIGALRM 2 +#define DEFER_SIGALRM 4 +#define SIGALRM_CALL 8 + +static Timer_t *tptop, *tpmin, *tpfree; +static char time_state; + +static double getnow(void) +{ + register double now; +#ifdef timeofday + struct timeval tp; + timeofday(&tp); + now = tp.tv_sec + 1.e-6*tp.tv_usec; + +#else + now = (double)time((time_t*)0); +#endif /* timeofday */ + return(now+.001); +} + +/* + * set an alarm for <t> seconds + */ +static double setalarm(register double t) +{ +#if defined(_lib_setitimer) && defined(ITIMER_REAL) + struct itimerval tnew, told; + tnew.it_value.tv_sec = t; + tnew.it_value.tv_usec = 1.e6*(t- (double)tnew.it_value.tv_sec); + if(t && tnew.it_value.tv_sec==0 && tnew.it_value.tv_usec<1000) + tnew.it_value.tv_usec = 1000; + tnew.it_interval.tv_sec = 0; + tnew.it_interval.tv_usec = 0; + if(setitimer(ITIMER_REAL,&tnew,&told) < 0) + errormsg(SH_DICT,ERROR_system(1),e_alarm); + t = told.it_value.tv_sec + 1.e-6*told.it_value.tv_usec; +#else + unsigned seconds = (unsigned)t; + if(t && seconds<1) + seconds=1; + t = (double)alarm(seconds); +#endif + return(t); +} + +/* signal handler for alarm call */ +static void sigalrm(int sig) +{ + register Timer_t *tp, *tplast, *tpold, *tpnext; + double now; + static double left; + NOT_USED(sig); + left = 0; + if(time_state&SIGALRM_CALL) + time_state &= ~SIGALRM_CALL; + else if(alarm(0)) + sh_fault(SIGALRM|SH_TRAP); + if(time_state) + { + if(time_state&IN_ADDTIMEOUT) + time_state |= DEFER_SIGALRM; + errno = EINTR; + return; + } + time_state |= IN_SIGALRM; + sigrelease(SIGALRM); + while(1) + { + now = getnow(); + tpold = tpmin = 0; + for(tplast=0,tp=tptop; tp; tp=tpnext) + { + tpnext = tp->next; + if(tp->action) + { + if(tp->wakeup <=now) + { + if(!tpold || tpold->wakeup>tp->wakeup) + tpold = tp; + } + else + { + if(!tpmin || tpmin->wakeup>tp->wakeup) + tpmin=tp; + } + tplast = tp; + } + else + { + if(tplast) + tplast->next = tp->next; + else + tptop = tp->next; + tp->next = tpfree; + tpfree = tp; + } + } + if((tp=tpold) && tp->incr) + { + while((tp->wakeup += tp->incr) <= now); + if(!tpmin || tpmin->wakeup>tp->wakeup) + tpmin=tp; + } + if(tpmin && (left==0 || (tp && tpmin->wakeup < (now+left)))) + { + if(left==0) + signal(SIGALRM,sigalrm); + left = setalarm(tpmin->wakeup-now); + if(left && (now+left) < tpmin->wakeup) + setalarm(left); + else + left=tpmin->wakeup-now; + } + if(tp) + { + void (*action)(void*); + action = tp->action; + if(!tp->incr) + tp->action = 0; + errno = EINTR; + time_state &= ~IN_SIGALRM; + (*action)(tp->handle); + time_state |= IN_SIGALRM; + } + else + break; + } + if(!tpmin) + signal(SIGALRM,(sh.sigflag[SIGALRM]&SH_SIGFAULT)?sh_fault:SIG_DFL); + time_state &= ~IN_SIGALRM; + errno = EINTR; +} + +static void oldalrm(void *handle) +{ + Handler_t fn = *(Handler_t*)handle; + free(handle); + (*fn)(SIGALRM); +} + +void *sh_timeradd(unsigned long msec,int flags,void (*action)(void*),void *handle) +{ + register Timer_t *tp; + double t; + Handler_t fn; + t = ((double)msec)/1000.; + if(t<=0 || !action) + return((void*)0); + if(tp=tpfree) + tpfree = tp->next; + else if(!(tp=(Timer_t*)malloc(sizeof(Timer_t)))) + return((void*)0); + tp->wakeup = getnow() + t; + tp->incr = (flags?t:0); + tp->action = action; + tp->handle = handle; + time_state |= IN_ADDTIMEOUT; + tp->next = tptop; + tptop = tp; + if(!tpmin || tp->wakeup < tpmin->wakeup) + { + tpmin = tp; + fn = (Handler_t)signal(SIGALRM,sigalrm); + if((t= setalarm(t))>0 && fn && fn!=(Handler_t)sigalrm) + { + Handler_t *hp = (Handler_t*)malloc(sizeof(Handler_t)); + if(hp) + { + *hp = fn; + sh_timeradd((long)(1000*t), 0, oldalrm, (void*)hp); + } + } + tp = tptop; + } + else if(tpmin && !tpmin->action) + time_state |= DEFER_SIGALRM; + time_state &= ~IN_ADDTIMEOUT; + if(time_state&DEFER_SIGALRM) + { + time_state=SIGALRM_CALL; + sigalrm(SIGALRM); + if(tp!=tptop) + tp=0; + } + return((void*)tp); +} + +/* + * delete timer <tp>. If <tp> is NULL, all timers are deleted + */ +void timerdel(void *handle) +{ + register Timer_t *tp = (Timer_t*)handle; + if(tp) + tp->action = 0; + else + { + for(tp=tptop; tp; tp=tp->next) + tp->action = 0; + if(tpmin) + { + tpmin = 0; + setalarm((double)0); + } + signal(SIGALRM,(sh.sigflag[SIGALRM]&SH_SIGFAULT)?sh_fault:SIG_DFL); + } +} + diff --git a/usr/src/lib/libshell/common/sh/trestore.c b/usr/src/lib/libshell/common/sh/trestore.c new file mode 100644 index 0000000000..d866ca712f --- /dev/null +++ b/usr/src/lib/libshell/common/sh/trestore.c @@ -0,0 +1,342 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * David Korn + * AT&T Labs + * + * shell intermediate code reader + * + */ + +#include <ccode.h> +#include "defs.h" +#include "shnodes.h" +#include "path.h" +#include "io.h" + +static struct dolnod *r_comlist(void); +static struct argnod *r_arg(void); +static struct ionod *r_redirect(void); +static struct regnod *r_switch(void); +static Shnode_t *r_tree(void); +static char *r_string(void); +static void r_comarg(struct comnod*); + +static Sfio_t *infile; + +#define getnode(type) ((Shnode_t*)stakalloc(sizeof(struct type))) + +Shnode_t *sh_trestore(Sfio_t *in) +{ + Shnode_t *t; + infile = in; + t = r_tree(); + return(t); +} +/* + * read in a shell tree + */ +static Shnode_t *r_tree() +{ + long l = sfgetl(infile); + register int type; + register Shnode_t *t=0; + if(l<0) + return(t); + type = l; + switch(type&COMMSK) + { + case TTIME: + case TPAR: + t = getnode(parnod); + t->par.partre = r_tree(); + break; + case TCOM: + t = getnode(comnod); + t->tre.tretyp = type; + r_comarg((struct comnod*)t); + break; + case TSETIO: + case TFORK: + t = getnode(forknod); + t->fork.forkline = sfgetu(infile); + t->fork.forktre = r_tree(); + t->fork.forkio = r_redirect(); + break; + case TIF: + t = getnode(ifnod); + t->if_.iftre = r_tree(); + t->if_.thtre = r_tree(); + t->if_.eltre = r_tree(); + break; + case TWH: + t = getnode(whnod); + t->wh.whinc = (struct arithnod*)r_tree(); + t->wh.whtre = r_tree(); + t->wh.dotre = r_tree(); + break; + case TLST: + case TAND: + case TORF: + case TFIL: + t = getnode(lstnod); + t->lst.lstlef = r_tree(); + t->lst.lstrit = r_tree(); + break; + case TARITH: + t = getnode(arithnod); + t->ar.arline = sfgetu(infile); + t->ar.arexpr = r_arg(); + t->ar.arcomp = 0; + if((t->ar.arexpr)->argflag&ARG_RAW) + t->ar.arcomp = sh_arithcomp((t->ar.arexpr)->argval); + break; + case TFOR: + t = getnode(fornod); + t->for_.forline = 0; + if(type&FLINENO) + t->for_.forline = sfgetu(infile); + t->for_.fortre = r_tree(); + t->for_.fornam = r_string(); + t->for_.forlst = (struct comnod*)r_tree(); + break; + case TSW: + t = getnode(swnod); + t->sw.swline = 0; + if(type&FLINENO) + t->sw.swline = sfgetu(infile); + t->sw.swarg = r_arg(); + if(type&COMSCAN) + t->sw.swio = r_redirect(); + else + t->sw.swio = 0; + t->sw.swlst = r_switch(); + break; + case TFUN: + { + Stak_t *savstak; + struct slnod *slp; + t = getnode(functnod); + t->funct.functloc = -1; + t->funct.functline = sfgetu(infile); + t->funct.functnam = r_string(); + savstak = stakcreate(STAK_SMALL); + savstak = stakinstall(savstak, 0); + slp = (struct slnod*)stakalloc(sizeof(struct slnod)); + slp->slchild = 0; + slp->slnext = sh.st.staklist; + sh.st.staklist = 0; + t->funct.functtre = r_tree(); + t->funct.functstak = slp; + slp->slptr = stakinstall(savstak,0); + slp->slchild = sh.st.staklist; + t->funct.functargs = (struct comnod*)r_tree(); + break; + } + case TTST: + t = getnode(tstnod); + t->tst.tstline = sfgetu(infile); + if((type&TPAREN)==TPAREN) + t->lst.lstlef = r_tree(); + else + { + t->lst.lstlef = (Shnode_t*)r_arg(); + if((type&TBINARY)) + t->lst.lstrit = (Shnode_t*)r_arg(); + } + } + if(t) + t->tre.tretyp = type; + return(t); +} + +static struct argnod *r_arg(void) +{ + register struct argnod *ap=0, *apold, *aptop=0; + register long l; + while((l=sfgetu(infile))>0) + { + ap = (struct argnod*)stakseek((unsigned)l+ARGVAL); + if(!aptop) + aptop = ap; + else + apold->argnxt.ap = ap; + if(--l > 0) + { + sfread(infile,ap->argval,(size_t)l); + ccmaps(ap->argval, l, CC_ASCII, CC_NATIVE); + } + ap->argval[l] = 0; + ap->argchn.cp = 0; + ap->argflag = sfgetc(infile); + if(ap->argflag&ARG_MESSAGE) + { + /* replace international messages */ + ap = sh_endword(1); + ap->argflag &= ~ARG_MESSAGE; + if(!(ap->argflag&(ARG_MAC|ARG_EXP))) + ap = sh_endword(0); + else + { + ap = (struct argnod*)stakfreeze(0); + if(ap->argflag==0) + ap->argflag = ARG_RAW; + } + } + else + ap = (struct argnod*)stakfreeze(0); + if(*ap->argval==0 && (ap->argflag&~ARG_APPEND)==0) + { + struct fornod *fp = (struct fornod*)getnode(fornod); + fp->fortyp = sfgetu(infile); + fp->fortre = r_tree(); + fp->fornam = ap->argval+1; + ap->argchn.ap = (struct argnod*)fp; + } + apold = ap; + } + if(ap) + ap->argnxt.ap = 0; + return(aptop); +} + +static struct ionod *r_redirect(void) +{ + register long l; + register struct ionod *iop=0, *iopold, *ioptop=0; + while((l=sfgetl(infile))>=0) + { + iop = (struct ionod*)getnode(ionod); + if(!ioptop) + ioptop = iop; + else + iopold->ionxt = iop; + iop->iofile = l; + iop->ioname = r_string(); + if(iop->iodelim = r_string()) + { + iop->iosize = sfgetl(infile); + if(sh.heredocs) + iop->iooffset = sfseek(sh.heredocs,(off_t)0,SEEK_END); + else + { + sh.heredocs = sftmp(512); + iop->iooffset = 0; + } + sfmove(infile,sh.heredocs, iop->iosize, -1); + } + iopold = iop; + if(iop->iofile&IOVNM) + iop->iovname = r_string(); + else + iop->iovname = 0; + iop->iofile &= ~IOVNM; + } + if(iop) + iop->ionxt = 0; + return(ioptop); +} + +static void r_comarg(struct comnod *com) +{ + char *cmdname=0; + com->comio = r_redirect(); + com->comset = r_arg(); + com->comstate = 0; + if(com->comtyp&COMSCAN) + { + com->comarg = r_arg(); + if(com->comarg->argflag==ARG_RAW) + cmdname = com->comarg->argval; + } + else if(com->comarg = (struct argnod*)r_comlist()) + cmdname = ((struct dolnod*)(com->comarg))->dolval[ARG_SPARE]; + com->comline = sfgetu(infile); + com->comnamq = 0; + if(cmdname) + { + char *cp; + com->comnamp = (void*)nv_search(cmdname,sh.fun_tree,0); + if(com->comnamp && (cp =strrchr(cmdname+1,'.'))) + { + *cp = 0; + com->comnamp = (void*)nv_open(cmdname,sh.var_tree,NV_VARNAME|NV_NOADD|NV_NOARRAY); + *cp = '.'; + } + } + else + com->comnamp = 0; +} + +static struct dolnod *r_comlist(void) +{ + register struct dolnod *dol=0; + register long l; + register char **argv; + if((l=sfgetl(infile))>0) + { + dol = (struct dolnod*)stakalloc(sizeof(struct dolnod) + sizeof(char*)*(l+ARG_SPARE)); + dol->dolnum = l; + dol->dolbot = ARG_SPARE; + argv = dol->dolval+ARG_SPARE; + while(*argv++ = r_string()); + } + return(dol); +} + +static struct regnod *r_switch(void) +{ + register long l; + struct regnod *reg=0,*regold,*regtop=0; + while((l=sfgetl(infile))>=0) + { + reg = (struct regnod*)getnode(regnod); + if(!regtop) + regtop = reg; + else + regold->regnxt = reg; + reg->regflag = l; + reg->regptr = r_arg(); + reg->regcom = r_tree(); + regold = reg; + } + if(reg) + reg->regnxt = 0; + return(regtop); +} + +static char *r_string(void) +{ + register Sfio_t *in = infile; + register unsigned long l = sfgetu(in); + register char *ptr; + if(l == 0) + return(NIL(char*)); + ptr = stakalloc((unsigned)l); + if(--l > 0) + { + if(sfread(in,ptr,(size_t)l)!=(size_t)l) + return(NIL(char*)); + ccmaps(ptr, l, CC_ASCII, CC_NATIVE); + } + ptr[l] = 0; + return(ptr); +} diff --git a/usr/src/lib/libshell/common/sh/waitevent.c b/usr/src/lib/libshell/common/sh/waitevent.c new file mode 100644 index 0000000000..fb7329f471 --- /dev/null +++ b/usr/src/lib/libshell/common/sh/waitevent.c @@ -0,0 +1,54 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped + +#include "defs.h" +/* + * This installs a hook to allow the processing of events when + * the shell is waiting for input and when the shell is + * waiting for job completion. + * The previous waitevent hook function is returned + */ + + +void *sh_waitnotify(int(*newevent)(int,long,int)) +{ + int (*old)(int,long,int); + old = sh.waitevent; + sh.waitevent = newevent; + return((void*)old); +} + +#if __OBSOLETE__ < 20080101 +/* + * this used to be a private symbol + * retain the old name for a bit for a smooth transition + */ + +#if defined(__EXPORT__) +#define extern __EXPORT__ +#endif + +extern void *_sh_waitnotify(int(*newevent)(int,long,int)) +{ + return sh_waitnotify(newevent); +} + +#endif diff --git a/usr/src/lib/libshell/common/sh/xec.c b/usr/src/lib/libshell/common/sh/xec.c new file mode 100644 index 0000000000..378492256e --- /dev/null +++ b/usr/src/lib/libshell/common/sh/xec.c @@ -0,0 +1,2947 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * UNIX shell parse tree executer + * + * David Korn + * AT&T Labs + * + */ + +#include "defs.h" +#include <fcin.h> +#include "variables.h" +#include "path.h" +#include "name.h" +#include "io.h" +#include "shnodes.h" +#include "jobs.h" +#include "test.h" +#include "builtins.h" +#include "FEATURE/time" +#include "FEATURE/externs" +#include "FEATURE/locale" +#include "streval.h" + +#if !_std_malloc +# include <vmalloc.h> +#endif + +#define SH_NTFORK SH_TIMING + +#if _lib_nice + extern int nice(int); +#endif /* _lib_nice */ +#if !_lib_spawnveg +# define spawnveg(a,b,c,d) spawnve(a,b,c) +#endif /* !_lib_spawnveg */ +#if SHOPT_SPAWN + static pid_t sh_ntfork(const Shnode_t*,char*[],int*,int); +#endif /* SHOPT_SPAWN */ + +static void sh_funct(Namval_t*, int, char*[], struct argnod*,int); +static int trim_eq(const char*, const char*); +static void coproc_init(int pipes[]); + +static void *timeout; +static char pipejob; + +struct funenv +{ + Namval_t *node; + struct argnod *env; +}; + +/* ======== command execution ========*/ + +/* + * print time <t> in h:m:s format with precision <p> + */ +static void l_time(Sfio_t *outfile,register clock_t t,int p) +{ + register int min, sec, frac; + register int hr; + if(p) + { + frac = t%sh.lim.clk_tck; + frac = (frac*100)/sh.lim.clk_tck; + } + t /= sh.lim.clk_tck; + sec = t%60; + t /= 60; + min = t%60; + if(hr=t/60) + sfprintf(outfile,"%dh",hr); + if(p) + sfprintf(outfile,"%dm%d%c%0*ds",min,sec,GETDECIMAL(0),p,frac); + else + sfprintf(outfile,"%dm%ds",min,sec); +} + +static int p_time(Sfio_t *out, const char *format, clock_t *tm) +{ + int c,p,l,n,offset = staktell(); + const char *first; + double d; + for(first=format ; c= *format; format++) + { + if(c!='%') + continue; + stakwrite(first, format-first); + n = l = 0; + p = 3; + if((c= *++format) == '%') + { + first = format; + continue; + } + if(c>='0' && c <='9') + { + p = (c>'3')?3:(c-'0'); + c = *++format; + } + else if(c=='P') + { + if(d=tm[0]) + d = 100.*(((double)(tm[1]+tm[2]))/d); + p = 2; + goto skip; + } + if(c=='l') + { + l = 1; + c = *++format; + } + if(c=='U') + n = 1; + else if(c=='S') + n = 2; + else if(c!='R') + { + stakseek(offset); + errormsg(SH_DICT,ERROR_exit(0),e_badtformat,c); + return(0); + } + d = (double)tm[n]/sh.lim.clk_tck; + skip: + if(l) + l_time(stkstd, tm[n], p); + else + sfprintf(stkstd,"%.*f",p, d); + first = format+1; + } + if(format>first) + stakwrite(first, format-first); + stakputc('\n'); + n = staktell()-offset; + sfwrite(out,stakptr(offset),n); + stakseek(offset); + return(n); +} + +#if SHOPT_OPTIMIZE +/* + * clear argument pointers that point into the stack + */ +static int p_arg(struct argnod*,int); +static int p_switch(struct regnod*); +static int p_comarg(register struct comnod *com) +{ + Namval_t *np=com->comnamp; + int n = p_arg(com->comset,ARG_ASSIGN); + if(com->comarg && (com->comtyp&COMSCAN)) + n+= p_arg(com->comarg,0); + if(com->comstate && np) + { + /* call builtin to cleanup state */ + Nambltin_t bdata; + bdata.shp = &sh; + bdata.np = com->comnamq; + bdata.ptr =nv_context(np); + bdata.data = com->comstate; + bdata.flags = SH_END_OPTIM; + (*funptr(np))(0,(char**)0, &bdata); + } + com->comstate = 0; + if(com->comarg && !np) + n++; + return(n); +} + +extern void sh_optclear(Shell_t*, void*); + +static int sh_tclear(register Shnode_t *t) +{ + int n=0; + if(!t) + return(0); + switch(t->tre.tretyp&COMMSK) + { + case TTIME: + case TPAR: + return(sh_tclear(t->par.partre)); + case TCOM: + return(p_comarg((struct comnod*)t)); + case TSETIO: + case TFORK: + return(sh_tclear(t->fork.forktre)); + case TIF: + n=sh_tclear(t->if_.iftre); + n+=sh_tclear(t->if_.thtre); + n+=sh_tclear(t->if_.eltre); + return(n); + case TWH: + if(t->wh.whinc) + n=sh_tclear((Shnode_t*)(t->wh.whinc)); + n+=sh_tclear(t->wh.whtre); + n+=sh_tclear(t->wh.dotre); + return(n); + case TLST: + case TAND: + case TORF: + case TFIL: + n=sh_tclear(t->lst.lstlef); + return(n+sh_tclear(t->lst.lstrit)); + case TARITH: + return(p_arg(t->ar.arexpr,ARG_ARITH)); + case TFOR: + n=sh_tclear(t->for_.fortre); + return(n+sh_tclear((Shnode_t*)t->for_.forlst)); + case TSW: + n=p_arg(t->sw.swarg,0); + return(n+p_switch(t->sw.swlst)); + case TFUN: + n=sh_tclear(t->funct.functtre); + return(n+sh_tclear((Shnode_t*)t->funct.functargs)); + case TTST: + if((t->tre.tretyp&TPAREN)==TPAREN) + return(sh_tclear(t->lst.lstlef)); + else + { + n=p_arg(&(t->lst.lstlef->arg),0); + if(t->tre.tretyp&TBINARY) + n+=p_arg(&(t->lst.lstrit->arg),0); + } + } + return(n); +} + +static int p_arg(register struct argnod *arg,int flag) +{ + while(arg) + { + if(strlen(arg->argval) || (arg->argflag==ARG_RAW)) + arg->argchn.ap = 0; + else if(flag==0) + sh_tclear((Shnode_t*)arg->argchn.ap); + else + sh_tclear(((struct fornod*)arg->argchn.ap)->fortre); + arg = arg->argnxt.ap; + } + return(0); +} + +static int p_switch(register struct regnod *reg) +{ + int n=0; + while(reg) + { + n+=p_arg(reg->regptr,0); + n+=sh_tclear(reg->regcom); + reg = reg->regnxt; + } + return(n); +} +# define OPTIMIZE_FLAG (ARG_OPTIMIZE) +# define OPTIMIZE (flags&OPTIMIZE_FLAG) +#else +# define OPTIMIZE_FLAG (0) +# define OPTIMIZE (0) +# define sh_tclear(x) +#endif /* SHOPT_OPTIMIZE */ + +static void out_pattern(Sfio_t *iop, register const char *cp, int n) +{ + register int c; + do + { + switch(c= *cp) + { + case 0: + if(n<0) + return; + c = n; + break; + case '\n': + sfputr(iop,"$'\\n",'\''); + continue; + case '\\': + if (!(c = *++cp)) + c = '\\'; + /*FALLTHROUGH*/ + case ' ': + case '<': case '>': case ';': + case '$': case '`': case '\t': + sfputc(iop,'\\'); + break; + } + sfputc(iop,c); + } + while(*cp++); +} + +static void out_string(Sfio_t *iop, register const char *cp, int c, int quoted) +{ + if(quoted) + { + int n = staktell(); + cp = sh_fmtq(cp); + if(iop==stkstd && cp==stkptr(stkstd,n)) + { + *stkptr(stkstd,stktell(stkstd)-1) = c; + return; + } + } + sfputr(iop,cp,c); +} + +struct Level +{ + Namfun_t hdr; + short maxlevel; +}; + +/* + * this is for a debugger but it hasn't been tested yet + * if a debug script sets .sh.level it should set up the scope + * as if you were executing in that level + */ +static void put_level(Namval_t* np,const char *val,int flags,Namfun_t *fp) +{ + Shscope_t *sp; + struct Level *lp = (struct Level*)fp; + int16_t level, oldlevel = (int16_t)nv_getnum(np); + nv_putv(np,val,flags,fp); + level = nv_getnum(np); + if(level<0 || level > lp->maxlevel) + { + nv_putv(np, (char*)&oldlevel, flags, fp); + /* perhaps this should be an error */ + return; + } + if(level==oldlevel) + return; + if(sp = sh_getscope(level,SEEK_SET)) + { + sh_setscope(sp); + error_info.line = sp->lineno; + } + nv_putval(SH_PATHNAMENOD, sh.st.filename ,NV_NOFREE); +} + +static const Namdisc_t level_disc = { 0, put_level }; + +/* + * write the current common on the stack and make it available as .sh.command + */ +int sh_debug(const char *trap, const char *name, const char *subscript, char *const argv[], int flags) +{ + struct sh_scoped savst; + Shscope_t *sp, *topmost; + Namval_t *np = SH_COMMANDNOD; + struct Level lev; + char *sav = stakptr(0); + int n=4, offset=staktell(); + const char *cp = "+=( "; + Sfio_t *iop = stkstd; + int16_t level; + if(name) + { + sfputr(iop,name,-1); + if(subscript) + { + sfputc(iop,'['); + out_string(iop,subscript,']',1); + } + if(!(flags&ARG_APPEND)) + cp+=1, n-=1; + if(!(flags&ARG_ASSIGN)) + n -= 2; + sfwrite(iop,cp,n); + } + if(!(flags&ARG_RAW)) + out_string(iop, *argv++,' ', 0); + n = (flags&ARG_ARITH); + while(cp = *argv++) + { + if((flags&ARG_EXP) && argv[1]==0) + out_pattern(iop, cp,' '); + else + out_string(iop, cp,' ',n?0: (flags&(ARG_RAW|ARG_NOGLOB))||*argv); + } + if(flags&ARG_ASSIGN) + sfputc(iop,')'); + else if(iop==stkstd) + *stakptr(staktell()-1) = 0; + np->nvalue.cp = stakfreeze(1); + sh.st.lineno = error_info.line; + /* now setup .sh.level variable */ + topmost = sh_getscope(0,SEEK_END); + for(level=0, sp=topmost; sp; sp=sp->par_scope) + level++; + memset((void*)&lev,0,sizeof(lev)); + lev.hdr.disc = &level_disc; + lev.maxlevel = --level; + nv_unset(SH_LEVELNOD); + nv_onattr(SH_LEVELNOD,NV_INT16|NV_NOFREE); + nv_putval(SH_LEVELNOD,(char*)&level,NV_INT16); + nv_disc(SH_LEVELNOD,&lev.hdr,NV_FIRST); + savst = sh.st; + sh.st.trap[SH_DEBUGTRAP] = 0; + n = sh_trap(trap,0); + np->nvalue.cp = 0; + nv_putval(SH_LEVELNOD,(char*)&level,NV_INT16); + nv_disc(SH_LEVELNOD,&lev.hdr,NV_POP); + nv_unset(SH_LEVELNOD); + nv_putval(SH_PATHNAMENOD, sh.st.filename ,NV_NOFREE); + sh.st = savst; + if(sav != stakptr(0)) + stakset(sav,0); + else + stakseek(offset); + return(n); +} + +/* + * Given stream <iop> compile and execute + */ +int sh_eval(register Sfio_t *iop, int mode) +{ + register Shnode_t *t; + Shell_t *shp = sh_getinterp(); + struct slnod *saveslp = shp->st.staklist; + int jmpval; + struct checkpt *pp = (struct checkpt*)shp->jmplist; + struct checkpt buff; + static Sfio_t *io_save; + io_save = iop; /* preserve correct value across longjmp */ + sh_pushcontext(&buff,SH_JMPEVAL); + buff.olist = pp->olist; + jmpval = sigsetjmp(buff.buff,0); + if(jmpval==0) + { + t = (Shnode_t*)sh_parse(shp,iop,SH_NL); + sfclose(iop); + io_save = 0; + if(!sh_isoption(SH_VERBOSE)) + sh_offstate(SH_VERBOSE); + if(mode && shp->hist_ptr) + { + hist_flush(shp->hist_ptr); + mode = sh_state(SH_INTERACTIVE); + } + sh_exec(t,sh_isstate(SH_ERREXIT)|mode); + } + sh_popcontext(&buff); + if(io_save) + sfclose(io_save); + sh_freeup(); + shp->st.staklist = saveslp; + if(jmpval>SH_JMPEVAL) + siglongjmp(*shp->jmplist,jmpval); + return(sh.exitval); +} + +#if SHOPT_FASTPIPE +static int pipe_exec(int pv[], Shnode_t *t, int errorflg) +{ + struct checkpt buff; + register Shnode_t *tchild = t->fork.forktre; + Namval_t *np; + Sfio_t *iop; + int jmpval,r; + if((tchild->tre.tretyp&COMMSK)!=TCOM || !(np=(Namval_t*)(tchild->com.comnamp))) + { + sh_pipe(pv); + return(sh_exec(t,errorflg)); + } + pv[0] = sh.lim.open_max; + sh.fdstatus[pv[0]] = IOREAD|IODUP|IOSEEK; + pv[1] = sh.lim.open_max+1; + sh.fdstatus[pv[1]] = IOWRITE|IOSEEK; + iop = sftmp(IOBSIZE+1); + sh.sftable[sh.lim.open_max+1] = iop; + sh_pushcontext(&buff,SH_JMPIO); + if(t->tre.tretyp&FPIN) + sh_iosave(0,sh.topfd); + sh_iosave(1,sh.topfd); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval==0) + { + if(t->tre.tretyp&FPIN) + sh_iorenumber(sh.inpipe[0],0); + sh_iorenumber(sh.lim.open_max+1,1); + r = sh_exec(tchild,errorflg); + if(sffileno(sfstdout)>=0) + pv[0] = sfsetfd(sfstdout,10); + iop = sfswap(sfstdout,0); + } + sh_popcontext(&buff); + sh.sftable[pv[0]] = iop; + sh.fdstatus[pv[0]] = IOREAD|IODUP|IOSEEK; + sfset(iop,SF_WRITE,0); + sfseek(iop,0L,SEEK_SET); + sh_iorestore(buff.topfd,jmpval); + if(jmpval>SH_JMPIO) + siglongjmp(*sh.jmplist,jmpval); + return(r); +} +#endif /* SHOPT_FASTPIPE */ + +/* + * returns 1 when option -<c> is specified + */ +static int checkopt(char *argv[], int c) +{ + char *cp; + while(cp = *++argv) + { + if(*cp=='+') + continue; + if(*cp!='-' || cp[1]=='-') + break; + if(strchr(cp,c)) + return(1); + } + return(0); +} + +static void free_list(struct openlist *olist) +{ + struct openlist *item,*next; + for(item=olist;item;item=next) + { + next = item->next; + free((void*)item); + } +} + + +int sh_exec(register const Shnode_t *t, int flags) +{ + sh_sigcheck(); + if(t && !sh.st.execbrk && !sh_isoption(SH_NOEXEC)) + { + register int type = flags; + register char *com0 = 0; + int errorflg = (type&sh_state(SH_ERREXIT))|OPTIMIZE; + int execflg = (type&sh_state(SH_NOFORK)); + int mainloop = (type&sh_state(SH_INTERACTIVE)); +#if SHOPT_SPAWN + int ntflag = (type&sh_state(SH_NTFORK)); +#endif + int topfd = sh.topfd; + char *sav=stakptr(0); + char *cp=0, **com=0; + int argn; + int skipexitset = 0; + int was_interactive = 0; + int was_errexit = sh_isstate(SH_ERREXIT); + int was_monitor = sh_isstate(SH_MONITOR); + int echeck = 0; + if(flags&sh_state(SH_INTERACTIVE)) + { + pipejob = 0; + job.curpgid = 0; + flags &= ~sh_state(SH_INTERACTIVE); + } + sh_offstate(SH_ERREXIT); + sh_offstate(SH_DEFPATH); + if(was_errexit&flags) + sh_onstate(SH_ERREXIT); + if(was_monitor&flags) + sh_onstate(SH_MONITOR); + type = t->tre.tretyp; + if(!sh.intrap) + sh.oldexit=sh.exitval; + sh.exitval=0; + sh.lastsig = 0; + sh.lastpath = 0; + switch(type&COMMSK) + { + case TCOM: + { + register struct argnod *argp; + char *trap; + Namval_t *np, *nq, *last_table; + struct ionod *io; + int command=0; + error_info.line = t->com.comline-sh.st.firstline; + com = sh_argbuild(&argn,&(t->com),OPTIMIZE); + echeck = 1; + if(t->tre.tretyp&COMSCAN) + { + argp = t->com.comarg; + if(argp && *com && !(argp->argflag&ARG_RAW)) + sh_sigcheck(); + } + np = (Namval_t*)(t->com.comnamp); + nq = (Namval_t*)(t->com.comnamq); + com0 = com[0]; + sh.xargexit = 0; + while(np==SYSCOMMAND) + { + register int n = b_command(0,com,&sh); + if(n==0) + break; + command += n; + np = 0; + if(!(com0= *(com+=n))) + break; + np = nv_bfsearch(com0, sh.bltin_tree, &nq, &cp); + } + if(sh.xargexit) + { + sh.xargmin -= command; + sh.xargmax -= command; + } + else + sh.xargmin = 0; + argn -= command; + if(!command && np && is_abuiltin(np)) + np = dtsearch(sh.fun_tree,np); + if(com0 && !np && !strchr(com0,'/')) + { + Dt_t *root = command?sh.bltin_tree:sh.fun_tree; + np = nv_bfsearch(com0, root, &nq, &cp); +#if SHOPT_NAMESPACE + if(sh.namespace && !nq && !cp) + { + int offset = staktell(); + stakputs(nv_name(sh.namespace)); + stakputc('.'); + stakputs(com0); + stakseek(offset); + np = nv_bfsearch(stakptr(offset), root, &nq, &cp); + } +#endif /* SHOPT_NAMESPACE */ + } + io = t->tre.treio; + if(sh.envlist = argp = t->com.comset) + { + if(argn==0 || (np && !command && nv_isattr(np,BLT_SPC))) + { + register int flgs=NV_VARNAME|NV_ASSIGN; +#if SHOPT_BASH + if(np==SYSLOCAL) + { + if(!nv_getval(SH_FUNNAMENOD)) + errormsg(SH_DICT,ERROR_exit(1),"%s: can only be used in a function",com0); + if(!sh.st.var_local) + { + nv_scope((struct argnod*)0); + sh.st.var_local = sh.var_tree; + } + + } + if(np==SYSTYPESET || np==SYSLOCAL) +#else + if(np==SYSTYPESET) +#endif + { + if(checkopt(com,'n')) + flgs |= NV_NOREF; +#if SHOPT_TYPEDEF + else if(checkopt(com,'T')) + { + sh.prefix = NV_CLASS; + flgs |= NV_TYPE; + + } +#endif /* SHOPT_TYPEDEF */ + if(checkopt(com,'A')) + flgs |= NV_ARRAY; + else if(checkopt(com,'a')) + flgs |= NV_IARRAY; + if((sh.fn_depth && !sh.prefix) || np==SYSLOCAL) + flgs |= NV_NOSCOPE; + } + else if(np==SYSEXPORT) + flgs |= NV_EXPORT; + else if(np) + flgs = NV_IDENT|NV_ASSIGN; +#if 0 + if(OPTIMIZE) + flgs |= NV_TAGGED; +#endif + nv_setlist(argp,flgs); + argp = NULL; + } + } + last_table = sh.last_table; + sh.last_table = 0; + if((io||argn)) + { + static char *argv[1]; + if(argn==0) + { + /* fake 'true' built-in */ + argn=1; + np = SYSTRUE; + *argv = nv_name(np); + com = argv; + } + /* set +x doesn't echo */ + else if((np!=SYSSET) && sh_isoption(SH_XTRACE)) + sh_trace(com-command,1); + else if((t->tre.tretyp&FSHOWME) && sh_isoption(SH_SHOWME)) + { + int ison = sh_isoption(SH_XTRACE); + if(!ison) + sh_onoption(SH_XTRACE); + sh_trace(com-command,1); + if(io) + sh_redirect(io,SH_SHOWME); + if(!ison) + sh_offoption(SH_XTRACE); + break; + } + if(trap=sh.st.trap[SH_DEBUGTRAP]) + sh_debug(trap,(char*)0, (char*)0, com, ARG_RAW); + if(io) + sfsync(sh.outpool); + sh.lastpath = 0; + if(!np && !strchr(com0,'/')) + { +#ifdef PATH_BFPATH + if(path_search(com0,NIL(Pathcomp_t*),1)) + np=nv_search(com0,sh.fun_tree,0); + else + { + if((np=nv_search(com0,sh.track_tree,0)) && !nv_isattr(np,NV_NOALIAS) && np->nvalue.cp) + np=nv_search(nv_getval(np),sh.bltin_tree,0); + else + np = 0; + } +#else + if(path_search(com0,NIL(char*),1)) + np=nv_search(com0,sh.fun_tree,0); + if(sh.lastpath) + np=nv_search(sh.lastpath,sh.bltin_tree,0); +#endif + } + /* check for builtins */ + if(np && is_abuiltin(np)) + { + Nambltin_t bdata; + void *context; + int scope=0, jmpval, save_prompt,share; + struct checkpt buff; + unsigned long was_vi=0, was_emacs=0, was_gmacs=0; + struct stat statb; + if(strchr(nv_name(np),'/')) + { + /* + * disable editors for built-in + * versions of commands on PATH + */ + was_vi = sh_isoption(SH_VI); + was_emacs = sh_isoption(SH_EMACS); + was_gmacs = sh_isoption(SH_GMACS); + sh_offoption(SH_VI); + sh_offoption(SH_EMACS); + sh_offoption(SH_GMACS); + } + sh_pushcontext(&buff,SH_JMPCMD); + jmpval = sigsetjmp(buff.buff,1); + if(jmpval == 0) + { + if(!(nv_isattr(np,BLT_ENV))) + error_info.flags |= ERROR_SILENT; + errorpush(&buff.err,0); + if(io) + { + struct openlist *item; + if(np==SYSLOGIN) + type=1; + else if(np==SYSEXEC) + type=1+!com[1]; + else + type = (execflg && !sh.subshell && !sh.st.trapcom[0]); + sh_redirect(io,type); + for(item=buff.olist;item;item=item->next) + item->strm=0; + } + if(!(nv_isattr(np,BLT_ENV))) + { + if(!sh.pwd) + path_pwd(0); + if(sh.pwd) + stat(".",&statb); + share = sfset(sfstdin,SF_SHARE,0); + sh_onstate(SH_STOPOK); + sfpool(sfstderr,NIL(Sfio_t*),SF_WRITE); + sfset(sfstderr,SF_LINE,1); + save_prompt = sh.nextprompt; + sh.nextprompt = 0; + } + if(argp) + { + scope++; + nv_scope(argp); + } + opt_info.index = opt_info.offset = 0; + opt_info.disc = 0; + error_info.id = *com; + sh.exitval = 0; + if(!(context=nv_context(np))) + context = (void*)&sh; + sh.bltinfun = funptr(np); + if(nv_isattr(np,NV_BLTINOPT)) + { + bdata.shp = &sh; + bdata.np = nq; + bdata.ptr = context; + bdata.data = t->com.comstate; + bdata.flags = (OPTIMIZE!=0); + context = (void*)&bdata; + } + if(execflg && !sh.subshell && + !sh.st.trapcom[0] && !sh.st.trap[SH_ERRTRAP] && sh.fn_depth==0 && !nv_isattr(np,BLT_ENV)) + { + /* do close-on-exec */ + int fd; + for(fd=0; fd < sh.lim.open_max; fd++) + if((sh.fdstatus[fd]&IOCLEX)&&fd!=sh.infd) + sh_close(fd); + } + sh.exitval = (*sh.bltinfun)(argn,com,context); + if(error_info.flags&ERROR_INTERACTIVE) + tty_check(ERRIO); + if(nv_isattr(np,NV_BLTINOPT)) + ((Shnode_t*)t)->com.comstate = bdata.data; + if(!nv_isattr(np,BLT_EXIT) && sh.exitval!=SH_RUNPROG) + sh.exitval &= SH_EXITMASK; + } + else + { + struct openlist *item; + for(item=buff.olist;item;item=item->next) + { + if(item->strm) + { + sfclrlock(item->strm); + if(sh.hist_ptr && item->strm == sh.hist_ptr->histfp) + hist_close(sh.hist_ptr); + else + sfclose(item->strm); + } + } + /* failure on special built-ins fatal */ + if(jmpval<=SH_JMPCMD && (!nv_isattr(np,BLT_SPC) || command)) + jmpval=0; + } + if(!(nv_isattr(np,BLT_ENV))) + { + if(sh.pwd) + { + struct stat stata; + stat(".",&stata); + /* restore directory changed */ + if(statb.st_ino!=stata.st_ino || statb.st_dev!=stata.st_dev) + chdir(sh.pwd); + } + sh_offstate(SH_STOPOK); + if(share&SF_SHARE) + sfset(sfstdin,SF_PUBLIC|SF_SHARE,1); + sfset(sfstderr,SF_LINE,0); + sfpool(sfstderr,sh.outpool,SF_WRITE); + sfpool(sfstdin,NIL(Sfio_t*),SF_WRITE); + sh.nextprompt = save_prompt; + } + sh_popcontext(&buff); + errorpop(&buff.err); + error_info.flags &= ~ERROR_SILENT; + sh.bltinfun = 0; + if(buff.olist) + free_list(buff.olist); + if(was_vi) + sh_onoption(SH_VI); + else if(was_emacs) + sh_onoption(SH_EMACS); + else if(was_gmacs) + sh_onoption(SH_GMACS); + if(scope) + nv_unscope(); + /* don't restore for subshell exec */ + if((sh.topfd>topfd) && !(sh.subshell && np==SYSEXEC)) + sh_iorestore(topfd,jmpval); + if(jmpval) + siglongjmp(*sh.jmplist,jmpval); + if(sh.exitval >=0) + goto setexit; + np = 0; + type=0; + } + /* check for functions */ + if(!command && np && nv_isattr(np,NV_FUNCTION)) + { + int indx,jmpval=0; + struct checkpt buff; + Namval_t node; + register struct slnod *slp; + if(!np->nvalue.ip) + { +#ifdef PATH_BFPATH + indx = path_search(com0,NIL(Pathcomp_t*),0); +#else + indx = path_search(com0,NIL(char*),0); +#endif + if(indx==1) + np = nv_search(com0,sh.fun_tree,HASH_NOSCOPE); + if(!np->nvalue.ip) + { + if(indx==1) + { + errormsg(SH_DICT,ERROR_exit(0),e_defined,com0); + sh.exitval = ERROR_NOEXEC; + } + else + { + errormsg(SH_DICT,ERROR_exit(0),e_found,"function"); + sh.exitval = ERROR_NOENT; + } + goto setexit; + } + } + /* increase refcnt for unset */ + slp = (struct slnod*)np->nvenv; + sh_funstaks(slp->slchild,1); + staklink(slp->slptr); + if(nq) + { + struct Namref nr; + sh.last_table = last_table; + memset(&nr,0,sizeof(nr)); + nr.np = nq; + nv_putval(SH_NAMENOD, nv_name(nq), NV_NOFREE); + memcpy(&node,L_ARGNOD,sizeof(node)); + L_ARGNOD->nvalue.nrp = &nr; + L_ARGNOD->nvenv = 0; + L_ARGNOD->nvfun = (Namfun_t*)sh.last_table; + L_ARGNOD->nvflag = NV_REF|NV_NOFREE; + if(nv_arrayptr(nq)) + { + nv_putval(SH_SUBSCRNOD,nv_getsub(nq),NV_NOFREE); + L_ARGNOD->nvenv = (char*)SH_SUBSCRNOD->nvalue.cp; + } + } + if(io) + { + indx = sh.topfd; + sh_pushcontext(&buff,SH_JMPCMD); + jmpval = sigsetjmp(buff.buff,0); + } + if(jmpval == 0) + { + if(io) + indx = sh_redirect(io,execflg); + sh_funct(np,argn,com,t->com.comset,(flags&~OPTIMIZE_FLAG)); + } + if(io) + { + if(buff.olist) + free_list(buff.olist); + sh_popcontext(&buff); + sh_iorestore(indx,jmpval); + } + if(nq) + { + L_ARGNOD->nvalue.np = node.nvalue.np; + L_ARGNOD->nvenv = node.nvenv; + L_ARGNOD->nvflag = node.nvflag; + L_ARGNOD->nvfun = node.nvfun; + nv_unset(SH_NAMENOD); + nv_unset(SH_SUBSCRNOD); + } + sh_funstaks(slp->slchild,-1); + stakdelete(slp->slptr); + if(jmpval > SH_JMPFUN) + siglongjmp(*sh.jmplist,jmpval); + goto setexit; + } + } + else if(!io) + { + setexit: + exitset(); + break; + } + } + case TFORK: + { + register pid_t parent; + int no_fork,jobid; + int pipes[2]; + no_fork = (execflg && !(type&(FAMP|FPOU)) && + !sh.subshell && !sh.st.trapcom[0] && + !sh.st.trap[SH_ERRTRAP] && sh.fn_depth==0); + if(sh.subshell) + sh_subtmpfile(); + if(sh_isstate(SH_PROFILE) || sh.dot_depth) + { + /* disable foreground job monitor */ + if(!(type&FAMP)) + sh_offstate(SH_MONITOR); +#if SHOPT_DEVFD + else if(!(type&FINT)) + sh_offstate(SH_MONITOR); +#endif /* SHOPT_DEVFD */ + } + if(no_fork) + job.parent=parent=0; + else + { + if(type&FCOOP) + coproc_init(pipes); + nv_getval(RANDNOD); +#if SHOPT_AMP + if((type&(FAMP|FINT)) == (FAMP|FINT)) + parent = sh_ntfork(t,com,&jobid,ntflag); + else + parent = sh_fork(type,&jobid); + if(parent<0) + break; +#else +#if SHOPT_SPAWN +# ifdef _lib_fork + if(com) + parent = sh_ntfork(t,com,&jobid,ntflag); + else + parent = sh_fork(type,&jobid); +# else + if((parent = sh_ntfork(t,com,&jobid,ntflag))<=0) + break; +# endif /* _lib_fork */ + if(parent<0) + break; +#else + parent = sh_fork(type,&jobid); +#endif /* SHOPT_SPAWN */ +#endif + } + if(job.parent=parent) + /* This is the parent branch of fork + * It may or may not wait for the child + */ + { + if(type&FPCL) + sh_close(sh.inpipe[0]); + if(type&(FCOOP|FAMP)) + sh.bckpid = parent; + if(!(type&(FAMP|FPOU))) + { + if(sh.topfd > topfd) + sh_iorestore(topfd,0); + job_wait(parent); + } + if(type&FAMP) + { + if(sh_isstate(SH_PROFILE) || sh_isstate(SH_INTERACTIVE)) + { + /* print job number */ +#ifdef JOBS + sfprintf(sfstderr,"[%d]\t%d\n",jobid,parent); +#else + sfprintf(sfstderr,"%d\n",parent); +#endif /* JOBS */ + } + } + break; + } + else + /* + * this is the FORKED branch (child) of execute + */ + { + int jmpval; + struct checkpt buff; + if(no_fork) + sh_sigreset(2); + sh_pushcontext(&buff,SH_JMPEXIT); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval) + goto done; + if((type&FINT) && !sh_isstate(SH_MONITOR)) + { + /* default std input for & */ + signal(SIGINT,SIG_IGN); + signal(SIGQUIT,SIG_IGN); + if(!sh.st.ioset) + { + if(sh_close(0)>=0) + sh_chkopen(e_devnull); + } + } + sh_offstate(SH_MONITOR); + /* pipe in or out */ +#ifdef _lib_nice + if((type&FAMP) && sh_isoption(SH_BGNICE)) + nice(4); +#endif /* _lib_nice */ + if(type&FPIN) + { + sh_iorenumber(sh.inpipe[0],0); + if(!(type&FPOU) || (type&FCOOP)) + sh_close(sh.inpipe[1]); + } + if(type&FPOU) + { + sh_iorenumber(sh.outpipe[1],1); + sh_pclose(sh.outpipe); + } + if((type&COMMSK)!=TCOM) + error_info.line = t->fork.forkline-sh.st.firstline; + sh_redirect(t->tre.treio,1); + if(sh.topfd) + sh_iounsave(); + if((type&COMMSK)!=TCOM) + { + /* don't clear job table for out + pipes so that jobs comand can + be used in a pipeline + */ + if(!no_fork && !(type&FPOU)) + job_clear(); + sh_exec(t->fork.forktre,flags|sh_state(SH_NOFORK)); + } + else if(com0) + { + sh_offoption(SH_ERREXIT); + sh_freeup(); + path_exec(com0,com,t->com.comset); + } + done: + sh_popcontext(&buff); + if(jmpval>SH_JMPEXIT) + siglongjmp(*sh.jmplist,jmpval); + sh_done(0); + } + } + + case TSETIO: + { + /* + * don't create a new process, just + * save and restore io-streams + */ + pid_t pid; + int jmpval, waitall; + struct checkpt buff; + if(sh.subshell) + { + flags &= ~sh_state(SH_NOFORK); + execflg = 0; + } + sh_pushcontext(&buff,SH_JMPIO); + if(type&FPIN) + { + was_interactive = sh_isstate(SH_INTERACTIVE); + sh_offstate(SH_INTERACTIVE); + if(!execflg) + sh_iosave(0,sh.topfd); + sh_iorenumber(sh.inpipe[0],0); + /* + * if read end of pipe is a simple command + * treat as non-sharable to improve performance + */ + if((t->fork.forktre->tre.tretyp&COMMSK)==TCOM) + sfset(sfstdin,SF_PUBLIC|SF_SHARE,0); + waitall = job.waitall; + job.waitall = 0; + pid = job.parent; + } + else + error_info.line = t->fork.forkline-sh.st.firstline; + jmpval = sigsetjmp(buff.buff,0); + if(jmpval==0) + { + sh_redirect(t->fork.forkio,execflg); + (t->fork.forktre)->tre.tretyp |= t->tre.tretyp&FSHOWME; + sh_exec(t->fork.forktre,flags); + } + sh_popcontext(&buff); + sh_iorestore(buff.topfd,jmpval); + if(buff.olist) + free_list(buff.olist); + if(type&FPIN) + { + job.waitall = waitall; + type = sh.exitval; + if(!(type&SH_EXITSIG)) + { + /* wait for remainder of pipline */ + job_wait(waitall?pid:0); + if(type || !sh_isoption(SH_PIPEFAIL)) + sh.exitval = type; + } + sh.st.ioset = 0; + } + if(jmpval>SH_JMPIO) + siglongjmp(*sh.jmplist,jmpval); + break; + } + + case TPAR: + echeck = 1; + flags &= ~OPTIMIZE_FLAG; + if(!sh.subshell && !sh.st.trapcom[0] && !sh.st.trap[SH_ERRTRAP] && (flags&sh_state(SH_NOFORK))) + { + int jmpval; + struct checkpt buff; + sh_pushcontext(&buff,SH_JMPEXIT); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval==0) + sh_exec(t->par.partre,flags); + sh_popcontext(&buff); + if(jmpval > SH_JMPEXIT) + siglongjmp(*sh.jmplist,jmpval); + sh_done(0); + } + else + sh_subshell(t->par.partre,flags,0); + break; + + case TFIL: + { + /* + * This code sets up a pipe. + * All elements of the pipe are started by the parent. + * The last element executes in current environment + */ + int pvo[2]; /* old pipe for multi-stage */ + int pvn[2]; /* current set up pipe */ + int savepipe = pipejob; + int showme = t->tre.tretyp&FSHOWME; + pid_t savepgid = job.curpgid; + if(sh.subshell) + sh_subtmpfile(); + sh.inpipe = pvo; + sh.outpipe = pvn; + pvo[1] = -1; + if(sh_isoption(SH_PIPEFAIL)) + job.waitall = 1; + else + job.waitall |= !pipejob && sh_isstate(SH_MONITOR); + do + { +#if SHOPT_FASTPIPE + type = pipe_exec(pvn,t->lst.lstlef, errorflg); +#else + /* create the pipe */ + sh_pipe(pvn); + /* execute out part of pipe no wait */ + (t->lst.lstlef)->tre.tretyp |= showme; + type = sh_exec(t->lst.lstlef, errorflg); +#endif /* SHOPT_FASTPIPE */ + pipejob=1; + /* save the pipe stream-ids */ + pvo[0] = pvn[0]; + /* close out-part of pipe */ + sh_close(pvn[1]); + /* pipeline all in one process group */ + t = t->lst.lstrit; + } + /* repeat until end of pipeline */ + while(!type && t->tre.tretyp==TFIL); + sh.inpipe = pvn; + sh.outpipe = 0; + if(type == 0) + { + /* + * execute last element of pipeline + * in the current process + */ + ((Shnode_t*)t)->tre.tretyp |= showme; + sh_exec(t,flags); + } + else + /* execution failure, close pipe */ + sh_pclose(pvn); + pipejob = savepipe; +#ifdef SIGTSTP + if(!pipejob && sh_isstate(SH_MONITOR)) + tcsetpgrp(JOBTTY,sh.pid); +#endif /*SIGTSTP */ + job.curpgid = savepgid; + break; + } + + case TLST: + { + /* a list of commands are executed here */ + do + { + sh_exec(t->lst.lstlef,errorflg|OPTIMIZE); + t = t->lst.lstrit; + } + while(t->tre.tretyp == TLST); + sh_exec(t,flags); + break; + } + + case TAND: + if(type&TTEST) + skipexitset++; + if(sh_exec(t->lst.lstlef,OPTIMIZE)==0) + sh_exec(t->lst.lstrit,flags); + break; + + case TORF: + if(type&TTEST) + skipexitset++; + if(sh_exec(t->lst.lstlef,OPTIMIZE)!=0) + sh_exec(t->lst.lstrit,flags); + break; + + case TFOR: /* for and select */ + { + register char **args; + register int nargs; + register Namval_t *np; + int flag = errorflg|OPTIMIZE_FLAG; + struct dolnod *argsav=0; + struct comnod *tp; + char *cp, *trap, *nullptr = 0; + int nameref, refresh=1; + static char *av[5] = { "for", 0, "in" }; +#if SHOPT_OPTIMIZE + int jmpval = ((struct checkpt*)sh.jmplist)->mode; + struct checkpt buff; + void *optlist = sh.optlist; + sh.optlist = 0; + sh_tclear(t->for_.fortre); + sh_pushcontext(&buff,jmpval); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval) + goto endfor; +#endif /* SHOPT_OPTIMIZE */ + error_info.line = t->for_.forline-sh.st.firstline; + if(!(tp=t->for_.forlst)) + { + args=sh.st.dolv+1; + nargs = sh.st.dolc; + argsav=sh_arguse(); + } + else + { + args=sh_argbuild(&argn,tp,0); + nargs = argn; + } + np = nv_open(t->for_.fornam, sh.var_tree,NV_NOASSIGN|NV_NOARRAY|NV_VARNAME|NV_NOREF); + nameref = nv_isref(np)!=0; + sh.st.loopcnt++; + cp = *args; + while(cp && sh.st.execbrk==0) + { + if(t->tre.tretyp&COMSCAN) + { + char *val; + int save_prompt; + /* reuse register */ + if(refresh) + { + sh_menu(sfstderr,nargs,args); + refresh = 0; + } + save_prompt = sh.nextprompt; + sh.nextprompt = 3; + sh.timeout = 0; + sh.exitval=sh_readline(&sh,&nullptr,0,1,1000*sh.st.tmout); + sh.nextprompt = save_prompt; + if(sh.exitval||sfeof(sfstdin)||sferror(sfstdin)) + { + sh.exitval = 1; + break; + } + if(!(val=nv_getval(nv_scoped(REPLYNOD)))) + continue; + else + { + if(*(cp=val) == 0) + { + refresh++; + goto check; + } + while(type = *cp++) + if(type < '0' && type > '9') + break; + if(type!=0) + type = nargs; + else + type = (int)strtol(val, (char**)0, 10)-1; + if(type<0 || type >= nargs) + cp = ""; + else + cp = args[type]; + } + } + if(nameref) + nv_offattr(np,NV_REF); + else if(nv_isattr(np, NV_ARRAY)) + nv_putsub(np,NIL(char*),0L); + nv_putval(np,cp,0); + if(nameref) + nv_setref(np,(Dt_t*)0,NV_VARNAME); + if(trap=sh.st.trap[SH_DEBUGTRAP]) + { + av[0] = (t->tre.tretyp&COMSCAN)?"select":"for"; + av[1] = t->for_.fornam; + av[3] = cp; + sh_debug(trap,(char*)0,(char*)0,av,0); + } + sh_exec(t->for_.fortre,flag); + flag &= ~OPTIMIZE_FLAG; + if(t->tre.tretyp&COMSCAN) + { + if((cp=nv_getval(nv_scoped(REPLYNOD))) && *cp==0) + refresh++; + } + else + cp = *++args; + check: + if(sh.st.breakcnt<0) + sh.st.execbrk = (++sh.st.breakcnt !=0); + } +#if SHOPT_OPTIMIZE + endfor: + sh_popcontext(&buff); + sh_tclear(t->for_.fortre); + sh_optclear(&sh,optlist); + if(jmpval) + siglongjmp(*sh.jmplist,jmpval); +#endif /*SHOPT_OPTIMIZE */ + if(sh.st.breakcnt>0) + sh.st.execbrk = (--sh.st.breakcnt !=0); + sh.st.loopcnt--; + sh_argfree(argsav,0); + nv_close(np); + break; + } + + case TWH: /* while and until */ + { + register int r=0; + int first = OPTIMIZE_FLAG; + Shnode_t *tt = t->wh.whtre; +#if SHOPT_FILESCAN + Sfio_t *iop=0; + int savein,fd; +#endif /*SHOPT_FILESCAN*/ +#if SHOPT_OPTIMIZE + int jmpval = ((struct checkpt*)sh.jmplist)->mode; + struct checkpt buff; + void *optlist = sh.optlist; + sh.optlist = 0; + sh_tclear(t->wh.whtre); + sh_tclear(t->wh.dotre); + sh_pushcontext(&buff,jmpval); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval) + goto endwhile; +#endif /* SHOPT_OPTIMIZE */ +#if SHOPT_FILESCAN + if(type==TWH && tt->tre.tretyp==TCOM && !tt->com.comarg && tt->com.comio) + { + fd = sh_redirect(tt->com.comio,3); + savein = dup(0); + if(fd==0) + fd = savein; + iop = sfnew(NULL,NULL,SF_UNBOUND,fd,SF_READ); + close(0); + open("/dev/null",O_RDONLY); + sh.offsets[0] = -1; + sh.offsets[1] = 0; + if(tt->com.comset) + nv_setlist(tt->com.comset,NV_IDENT|NV_ASSIGN); + } +#endif /*SHOPT_FILESCAN */ + sh.st.loopcnt++; + while(sh.st.execbrk==0) + { +#if SHOPT_FILESCAN + if(iop) + { + if(!(sh.cur_line=sfgetr(iop,'\n',SF_STRING))) + break; + } + else +#endif /*SHOPT_FILESCAN */ + if((sh_exec(tt,first)==0)!=(type==TWH)) + break; + r = sh_exec(t->wh.dotre,first|errorflg); + if(sh.st.breakcnt<0) + sh.st.execbrk = (++sh.st.breakcnt !=0); + /* This is for the arithmetic for */ + if(sh.st.execbrk==0 && t->wh.whinc) + sh_exec((Shnode_t*)t->wh.whinc,first); + first = 0; + errorflg &= ~OPTIMIZE_FLAG; +#if SHOPT_FILESCAN + sh.offsets[0] = -1; + sh.offsets[1] = 0; +#endif /*SHOPT_FILESCAN */ + } +#if SHOPT_OPTIMIZE + endwhile: + sh_popcontext(&buff); + sh_tclear(t->wh.whtre); + sh_tclear(t->wh.dotre); + sh_optclear(&sh,optlist); + if(jmpval) + siglongjmp(*sh.jmplist,jmpval); +#endif /*SHOPT_OPTIMIZE */ + if(sh.st.breakcnt>0) + sh.st.execbrk = (--sh.st.breakcnt !=0); + sh.st.loopcnt--; + sh.exitval= r; +#if SHOPT_FILESCAN + if(iop) + { + sfclose(iop); + close(0); + dup(savein); + sh.cur_line = 0; + } +#endif /*SHOPT_FILESCAN */ + break; + } + case TARITH: /* (( expression )) */ + { + register char *trap; + static char *arg[4]= {"((", 0, "))"}; + error_info.line = t->ar.arline-sh.st.firstline; + if(!(t->ar.arexpr->argflag&ARG_RAW)) + arg[1] = sh_macpat(t->ar.arexpr,OPTIMIZE|ARG_ARITH); + else + arg[1] = t->ar.arexpr->argval; + if(trap=sh.st.trap[SH_DEBUGTRAP]) + sh_debug(trap,(char*)0, (char*)0, arg, ARG_ARITH); + if(sh_isoption(SH_XTRACE)) + { + sh_trace(NIL(char**),0); + sfprintf(sfstderr,"((%s))\n",arg[1]); + } + if(t->ar.arcomp) + sh.exitval = !arith_exec((Arith_t*)t->ar.arcomp); + else + sh.exitval = !sh_arith(arg[1]); + break; + } + + case TIF: + if(sh_exec(t->if_.iftre,OPTIMIZE)==0) + sh_exec(t->if_.thtre,flags); + else if(t->if_.eltre) + sh_exec(t->if_.eltre, flags); + else + sh.exitval=0; /* force zero exit for if-then-fi */ + break; + + case TSW: + { + Shnode_t *tt = (Shnode_t*)t; + char *trap, *r = sh_macpat(tt->sw.swarg,OPTIMIZE); + error_info.line = t->sw.swline-sh.st.firstline; + t= (Shnode_t*)(tt->sw.swlst); + if(trap=sh.st.trap[SH_DEBUGTRAP]) + { + static char *av[4] = {"case", 0, "in" }; + av[1] = r; + sh_debug(trap, (char*)0, (char*)0, av, 0); + } + while(t) + { + register struct argnod *rex=(struct argnod*)t->reg.regptr; + while(rex) + { + register char *s; + if(rex->argflag&ARG_MAC) + { + s = sh_macpat(rex,OPTIMIZE|ARG_EXP); + while(*s=='\\' && s[1]==0) + s+=2; + } + else + s = rex->argval; + type = (rex->argflag&ARG_RAW); + if((type && strcmp(r,s)==0) || + (!type && (strmatch(r,s) + || trim_eq(r,s)))) + { + do sh_exec(t->reg.regcom,(t->reg.regflag?0:flags)); + while(t->reg.regflag && + (t=(Shnode_t*)t->reg.regnxt)); + t=0; + break; + } + else + rex=rex->argnxt.ap; + } + if(t) + t=(Shnode_t*)t->reg.regnxt; + } + break; + } + + case TTIME: + { + /* time the command */ + struct tms before,after; + const char *format = e_timeformat; + clock_t at, tm[3]; +#ifdef timeofday + struct timeval tb,ta; +#else + clock_t bt; +#endif /* timeofday */ + if(type!=TTIME) + { + sh_exec(t->par.partre,OPTIMIZE); + sh.exitval = !sh.exitval; + break; + } + if(t->par.partre) + { + long timer_on; + timer_on = sh_isstate(SH_TIMING); +#ifdef timeofday + timeofday(&tb); + times(&before); +#else + bt = times(&before); +#endif /* timeofday */ + job.waitall = 1; + sh_onstate(SH_TIMING); + sh_exec(t->par.partre,OPTIMIZE); + if(!timer_on) + sh_offstate(SH_TIMING); + job.waitall = 0; + } + else + { +#ifndef timeofday + bt = 0; +#endif /* timeofday */ + before.tms_utime = before.tms_cutime = 0; + before.tms_stime = before.tms_cstime = 0; + } +#ifdef timeofday + times(&after); + timeofday(&ta); + at = sh.lim.clk_tck*(ta.tv_sec-tb.tv_sec); + at += ((sh.lim.clk_tck*(((1000000L/2)/sh.lim.clk_tck)+(ta.tv_usec-tb.tv_usec)))/1000000L); +#else + at = times(&after) - bt; +#endif /* timeofday */ + tm[0] = at; + if(t->par.partre) + { + Namval_t *np = nv_open("TIMEFORMAT",sh.var_tree,NV_NOADD); + if(np) + { + format = nv_getval(np); + nv_close(np); + } + if(!format) + format = e_timeformat; + } + else + { + format = strchr(format+1,'\n')+1; +#if 0 + if(sh.optcount) + sfprintf(sfstderr,"%d optimizations\n",sh.optcount); +#endif + } + tm[1] = after.tms_utime - before.tms_utime; + tm[1] += after.tms_cutime - before.tms_cutime; + tm[2] = after.tms_stime - before.tms_stime; + tm[2] += after.tms_cstime - before.tms_cstime; + if(format && *format) + p_time(sfstderr,sh_translate(format),tm); + break; + } + case TFUN: + { + register Namval_t *np; + register struct slnod *slp; + register char *fname = ((struct functnod*)t)->functnam; + register char *cp = strrchr(fname,'.'); + register Namval_t *npv=0; +#if SHOPT_NAMESPACE + if(t->tre.tretyp==TNSPACE) + { + Dt_t *root,*oldroot, *top=0; + Namval_t *oldnspace = sh.namespace; + int offset = staktell(); + long optindex = sh.st.optindex; + if(cp) + errormsg(SH_DICT,ERROR_exit(1),e_ident,fname); + stakputc('.'); + stakputs(fname); + stakputc(0); + np = nv_open(stakptr(offset),sh.var_base,NV_NOASSIGN|NV_NOARRAY|NV_VARNAME); + offset = staktell(); + sh.namespace = np; + if(!(root=nv_dict(np))) + { + root = dtopen(&_Nvdisc,Dtoset); + nv_putval(np,(char*)root,NV_TABLE|NV_NOFREE); + sh.st.optindex = 1; + } + if(oldnspace && dtvnext(dtvnext(sh.var_tree))) + top = dtview(sh.var_tree,0); + else if(dtvnext(sh.var_tree)) + top = dtview(sh.var_tree,0); + oldroot = sh.var_tree; + dtview(root,sh.var_base); + sh.var_tree = root; + if(top) + dtview(sh.var_tree,top); + sh_exec(t->for_.fortre,flags); + if(dtvnext(sh.var_tree)) + top = dtview(sh.var_tree,0); + sh.var_tree = oldroot; + if(top) + dtview(top,sh.var_tree); + sh.namespace = oldnspace; + sh.st.optindex = optindex; + break; + } +#endif /* SHOPT_NAMESPACE */ + /* look for discipline functions */ + error_info.line = t->funct.functline-sh.st.firstline; + /* Function names cannot be special builtin */ + if(cp || sh.prefix) + { + int offset = staktell(); + if(sh.prefix) + { + cp = sh.prefix; + sh.prefix = 0; + npv = nv_open(cp,sh.var_tree,NV_NOASSIGN|NV_NOARRAY|NV_VARNAME); + sh.prefix = cp; + cp = fname; + } + else + { + stakwrite(fname,cp-fname); + stakputc(0); + npv = nv_open(stakptr(offset),sh.var_tree,NV_NOASSIGN|NV_NOARRAY|NV_VARNAME); + } + offset = staktell(); + stakputs(nv_name(npv)); + if(*cp!='.') + stakputc('.'); + stakputs(cp); + stakputc(0); + fname = stakptr(offset); + } + else if((np=nv_search(fname,sh.bltin_tree,0)) && nv_isattr(np,BLT_SPC)) + errormsg(SH_DICT,ERROR_exit(1),e_badfun,fname); +#if SHOPT_NAMESPACE + else if(sh.namespace) + { + int offset = staktell(); + stakputs(nv_name(sh.namespace)); + stakputc('.'); + stakputs(fname); + stakputc(0); + fname = stakptr(offset); + } +#endif /* SHOPT_NAMESPACE */ + np = nv_open(fname,sh_subfuntree(1),NV_NOASSIGN|NV_NOARRAY|NV_VARNAME|NV_NOSCOPE); + if(npv) + { + if(!sh.mktype) + cp = nv_setdisc(npv,cp+1,np,(Namfun_t*)npv); + nv_close(npv); + if(!cp) + errormsg(SH_DICT,ERROR_exit(1),e_baddisc,fname); + } + if(np->nvalue.rp) + { + slp = (struct slnod*)np->nvenv; + sh_funstaks(slp->slchild,-1); + stakdelete(slp->slptr); + } + else + np->nvalue.rp = new_of(struct Ufunction,0); + if(t->funct.functstak) + { + struct functnod *fp; + slp = t->funct.functstak; + sh_funstaks(slp->slchild,1); + staklink(slp->slptr); + np->nvenv = (char*)slp; + nv_funtree(np) = (int*)(t->funct.functtre); + np->nvalue.rp->hoffset = t->funct.functloc; + np->nvalue.rp->lineno = t->funct.functline; + np->nvalue.rp->nspace = sh.namespace; + np->nvalue.rp->fname = 0; + fp = (struct functnod*)(slp+1); + if(fp->functtyp==(TFUN|FAMP)) + np->nvalue.rp->fname = fp->functnam; + nv_setsize(np,fp->functline); + nv_offattr(np,NV_FPOSIX); + } + else + nv_unset(np); + if(type&FPOSIX) + nv_onattr(np,NV_FUNCTION|NV_FPOSIX); + else + nv_onattr(np,NV_FUNCTION); + if(type&FPIN) + nv_onattr(np,NV_FTMP); + break; + } + + /* new test compound command */ + case TTST: + { + register int n; + register char *left; + int negate = (type&TNEGATE)!=0; + if(type&TTEST) + skipexitset++; + error_info.line = t->tst.tstline-sh.st.firstline; + echeck = 1; + if((type&TPAREN)==TPAREN) + { + sh_exec(t->lst.lstlef,OPTIMIZE); + n = !sh.exitval; + } + else + { + register int traceon=0; + register char *right; + register char *trap; + char *argv[6]; + n = type>>TSHIFT; + left = sh_macpat(&(t->lst.lstlef->arg),OPTIMIZE); + if(type&TBINARY) + right = sh_macpat(&(t->lst.lstrit->arg),((n==TEST_PEQ||n==TEST_PNE)?ARG_EXP:0)|OPTIMIZE); + if(trap=sh.st.trap[SH_DEBUGTRAP]) + argv[0] = (type&TNEGATE)?((char*)e_tstbegin):"[["; + if(sh_isoption(SH_XTRACE)) + { + traceon = sh_trace(NIL(char**),0); + sfwrite(sfstderr,e_tstbegin,(type&TNEGATE?5:3)); + } + if(type&TUNARY) + { + if(traceon) + sfprintf(sfstderr,"-%c %s",n,sh_fmtq(left)); + if(trap) + { + char unop[3]; + unop[0] = '-'; + unop[1] = n; + unop[2] = 0; + argv[1] = unop; + argv[2] = left; + argv[3] = "]]"; + argv[4] = 0; + sh_debug(trap,(char*)0,(char*)0,argv, 0); + } + n = test_unop(n,left); + } + else if(type&TBINARY) + { + char *op; + int pattern = 0; + if(trap || traceon) + op = (char*)(shtab_testops+(n&037)-1)->sh_name; + type >>= TSHIFT; + if(type==TEST_PEQ || type==TEST_PNE) + pattern=ARG_EXP; + if(trap) + { + argv[1] = left; + argv[2] = op; + argv[3] = right; + argv[4] = "]]"; + argv[5] = 0; + sh_debug(trap,(char*)0,(char*)0,argv, pattern); + } + n = test_binop(n,left,right); + if(traceon) + { + sfprintf(sfstderr,"%s %s ",sh_fmtq(left),op); + if(pattern) + out_pattern(sfstderr,right,-1); + else + sfputr(sfstderr,sh_fmtq(right),-1); + } + } + if(traceon) + sfwrite(sfstderr,e_tstend,4); + } + sh.exitval = ((!n)^negate); + if(!skipexitset) + exitset(); + break; + } + } + if(sh.trapnote || (sh.exitval && sh_isstate(SH_ERREXIT)) && + t && echeck) + sh_chktrap(); + /* set $_ */ + if(mainloop && com0) + { + /* store last argument here if it fits */ + static char lastarg[32]; + if(sh_isstate(SH_FORKED)) + sh_done(0); + if(sh.lastarg!= lastarg && sh.lastarg) + free(sh.lastarg); + if(strlen(com[argn-1]) < sizeof(lastarg)) + { + nv_onattr(L_ARGNOD,NV_NOFREE); + sh.lastarg = strcpy(lastarg,com[argn-1]); + } + else + { + nv_offattr(L_ARGNOD,NV_NOFREE); + sh.lastarg = strdup(com[argn-1]); + } + } + if(!skipexitset) + exitset(); + if(!(OPTIMIZE)) + { + if(sav != stakptr(0)) + stakset(sav,0); + else if(staktell()) + stakseek(0); + } + if(sh.trapnote&SH_SIGSET) + sh_exit(SH_EXITSIG|sh.lastsig); + if(was_interactive) + sh_onstate(SH_INTERACTIVE); + if(was_monitor && sh_isoption(SH_MONITOR)) + sh_onstate(SH_MONITOR); + if(was_errexit) + sh_onstate(SH_ERREXIT); + } + return(sh.exitval); +} + +/* + * test for equality with second argument trimmed + * returns 1 if r == trim(s) otherwise 0 + */ + +static int trim_eq(register const char *r,register const char *s) +{ + register char c; + while(c = *s++) + { + if(c=='\\') + c = *s++; + if(c && c != *r++) + return(0); + } + return(*r==0); +} + +/* + * print out the command line if set -x is on + */ + +int sh_trace(register char *argv[], register int nl) +{ + register char *cp; + register int bracket = 0; + if(sh_isoption(SH_XTRACE)) + { + /* make this trace atomic */ + sfset(sfstderr,SF_SHARE|SF_PUBLIC,0); + if(!(cp=nv_getval(nv_scoped(PS4NOD)))) + cp = "+ "; + else + { + sh_offoption(SH_XTRACE); + cp = sh_mactry(cp); + sh_onoption(SH_XTRACE); + } + if(*cp) + sfputr(sfstderr,cp,-1); + if(argv) + { + char *argv0 = *argv; + nl = (nl?'\n':-1); + /* don't quote [ and [[ */ + if(*(cp=argv[0])=='[' && (!cp[1] || !cp[2]&&cp[1]=='[')) + { + sfputr(sfstderr,cp,*++argv?' ':nl); + bracket = 1; + } + while(cp = *argv++) + { + if(bracket==0 || *argv || *cp!=']') + cp = sh_fmtq(cp); + if(sh.prefix && cp!=argv0 && *cp!='-') + { + if(*cp=='.' && cp[1]==0) + cp = sh.prefix; + else + sfputr(sfstderr,sh.prefix,'.'); + } + sfputr(sfstderr,cp,*argv?' ':nl); + } + sfset(sfstderr,SF_SHARE|SF_PUBLIC,1); + } + return(1); + } + return(0); +} + +/* + * This routine creates a subshell by calling fork() or vfork() + * If ((flags&COMASK)==TCOM), then vfork() is permitted + * If fork fails, the shell sleeps for exponentially longer periods + * and tries again until a limit is reached. + * SH_FORKLIM is the max period between forks - power of 2 usually. + * Currently shell tries after 2,4,8,16, and 32 seconds and then quits + * Failures cause the routine to error exit. + * Parent links to here-documents are removed by the child + * Traps are reset by the child + * The process-id of the child is returned to the parent, 0 to the child. + */ + +static void timed_out(void *handle) +{ + NOT_USED(handle); + timeout = 0; +} + + +/* + * called by parent and child after fork by sh_fork() + */ +pid_t _sh_fork(register pid_t parent,int flags,int *jobid) +{ + static long forkcnt = 1000L; + pid_t curpgid = job.curpgid; + pid_t postid = (flags&FAMP)?0:curpgid; + int sig; + if(parent<0) + { + if((forkcnt *= 2) > 1000L*SH_FORKLIM) + { + forkcnt=1000L; + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_nofork); + } + sh_sigcheck(); + timeout = (void*)sh_timeradd(forkcnt, 0, timed_out, NIL(void*)); + job_wait((pid_t)1); + if(timeout) + { + timerdel(timeout); + forkcnt /= 2; + } + return(-1); + } + forkcnt=1000L; + if(parent) + { + int myjob; + sh.nforks++; + if(job.toclear) + job_clear(); +#ifdef JOBS + /* first process defines process group */ + if(sh_isstate(SH_MONITOR)) + { + /* + * errno==EPERM means that an earlier processes + * completed. Make parent the job group id. + */ + if(postid==0) + job.curpgid = parent; + if(job.jobcontrol || (flags&FAMP)) + { + if(setpgid(parent,job.curpgid)<0 && errno==EPERM) + setpgid(parent,parent); + } + } +#endif /* JOBS */ + if(!sh_isstate(SH_MONITOR) && job.waitall && postid==0) + job.curpgid = parent; + if(flags&FCOOP) + sh.cpid = parent; + myjob = job_post(parent,postid); + if(flags&FAMP) + job.curpgid = curpgid; + if(jobid) + *jobid = myjob; + return(parent); + } +#if !_std_malloc + vmtrace(-1); +#endif + /* This is the child process */ + if(sh.trapnote&SH_SIGTERM) + sh_exit(SH_EXITSIG|SIGTERM); + sh.nforks=0; + timerdel(NIL(void*)); +#ifdef JOBS + if(!job.jobcontrol && !(flags&FAMP)) + sh_offstate(SH_MONITOR); + if(sh_isstate(SH_MONITOR)) + { + parent = getpid(); + if(postid==0) + job.curpgid = parent; + while(setpgid(0,job.curpgid)<0 && job.curpgid!=parent) + job.curpgid = parent; +# ifdef SIGTSTP + if(job.curpgid==parent && !(flags&FAMP)) + tcsetpgrp(job.fd,job.curpgid); +# endif /* SIGTSTP */ + } +# ifdef SIGTSTP + if(job.jobcontrol) + { + signal(SIGTTIN,SIG_DFL); + signal(SIGTTOU,SIG_DFL); + signal(SIGTSTP,SIG_DFL); + } +# endif /* SIGTSTP */ + job.jobcontrol = 0; +#endif /* JOBS */ + job.toclear = 1; + sh.login_sh = 0; + sh_offoption(SH_LOGIN_SHELL); + sh_onstate(SH_FORKED); + sh_onstate(SH_NOLOG); + sh.fn_depth = 0; +#if SHOPT_ACCT + sh_accsusp(); +#endif /* SHOPT_ACCT */ + /* Reset remaining signals to parent */ + /* except for those `lost' by trap */ + sh_sigreset(2); + sh.subshell = 0; + if((flags&FAMP) && sh.coutpipe>1) + sh_close(sh.coutpipe); + sig = sh.savesig; + sh.savesig = 0; + if(sig>0) + sh_fault(sig); + sh_sigcheck(); + return(0); +} + +pid_t sh_fork(int flags, int *jobid) +{ + register pid_t parent; + register int sig; +#if SHOPT_FASTPIPE + if(sffileno(sfstdin)<0) + { + off_t current = sfseek(sfstdin,(off_t)0,SEEK_CUR); + sfseek(sfstdin,(off_t)0,SEEK_END); + sfdisc(sfstdin,SF_POPDISC); + fcntl(sffileno(sfstdin),F_SETFD,0); + sh_iostream(0); + sfseek(sfstdin,current,SEEK_SET); + } +#endif /* SHOPT_FASTPIPE */ + if(!sh.pathlist) + path_get(""); + sfsync(NIL(Sfio_t*)); + sh.trapnote &= ~SH_SIGTERM; + job_fork(-1); + sh.savesig = -1; + while(_sh_fork(parent=fork(),flags,jobid) < 0); + sig = sh.savesig; + sh.savesig = 0; + if(sig>0) + sh_fault(sig); + job_fork(parent); + return(parent); +} + +/* + * add exports from previous scope to the new scope + */ +static void local_exports(register Namval_t *np, void *data) +{ + register Namval_t *mp; + register char *cp; + if(nv_isarray(np)) + nv_putsub(np,NIL(char*),0); + if((cp = nv_getval(np)) && (mp = nv_search(nv_name(np), sh.var_tree, NV_ADD|HASH_NOSCOPE)) && nv_isnull(mp)) + nv_putval(mp, cp, 0); +} + +/* + * This routine is used to execute the given function <fun> in a new scope + * If <fun> is NULL, then arg points to a structure containing a pointer + * to a function that will be executed in the current environment. + */ +int sh_funscope(int argn, char *argv[],int(*fun)(void*),void *arg,int execflg) +{ + register char *trap; + register int nsig; + struct dolnod *argsav=0,*saveargfor; + struct sh_scoped savst, *prevscope = sh.st.self; + struct argnod *envlist=0; + Shopt_t savopt; + int jmpval; + int r = 0; + char *savstak; + struct funenv *fp; + struct checkpt buff; + Namval_t *nspace = sh.namespace; + savopt = sh.options; + sh.st.lineno = error_info.line; + *prevscope = sh.st; + sh_offoption(SH_ERREXIT); + sh.st.prevst = prevscope; + sh.st.self = &savst; + sh.topscope = (Shscope_t*)sh.st.self; + sh.st.opterror = sh.st.optchar = 0; + sh.st.optindex = 1; + sh.st.loopcnt = 0; + if(!fun) + { + fp = (struct funenv*)arg; + envlist = fp->env; + } + prevscope->save_tree = sh.var_tree; + nv_scope(envlist); + if(dtvnext(prevscope->save_tree)!= (sh.namespace?sh.var_base:0)) + { + /* eliminate parent scope */ + Dt_t *dt = dtview(sh.var_tree,0); + dtview(sh.var_tree,dtvnext(prevscope->save_tree)); + nv_scan(prevscope->save_tree, local_exports,(void*)0, NV_EXPORT, NV_EXPORT|NV_NOSCOPE); + } + sh.st.save_tree = sh.var_tree; + if(!fun) + { + Namval_t *np; + if(nv_isattr(fp->node,NV_TAGGED)) + sh_onoption(SH_XTRACE); + else + sh_offoption(SH_XTRACE); +#if SHOPT_NAMESPACE + if((np=(fp->node)->nvalue.rp->nspace) && np!=sh.namespace) + { + Dt_t *dt = sh.var_tree; + dtview(dt,0); + dtview(dt,nv_dict(np)); + sh.var_tree = nv_dict(np); + sh.namespace = np; + } +#endif /* SHOPT_NAMESPACE */ + } + sh.st.cmdname = argv[0]; + /* save trap table */ + if((nsig=sh.st.trapmax*sizeof(char*))>0 || sh.st.trapcom[0]) + { + nsig += sizeof(char*); + memcpy(savstak=stakalloc(nsig),(char*)&sh.st.trapcom[0],nsig); + } + sh_sigreset(0); + argsav = sh_argnew(argv,&saveargfor); + sh_pushcontext(&buff,SH_JMPFUN); + errorpush(&buff.err,0); + error_info.id = argv[0]; + sh.st.var_local = sh.var_tree; + jmpval = sigsetjmp(buff.buff,0); + if(!fun) + { + sh.st.filename = fp->node->nvalue.rp->fname; + nv_putval(SH_PATHNAMENOD, sh.st.filename ,NV_NOFREE); + nv_putval(SH_FUNNAMENOD,nv_name(fp->node),NV_NOFREE); + } + if(jmpval == 0) + { + if(sh.fn_depth++ > MAXDEPTH) + siglongjmp(*sh.jmplist,SH_JMPERRFN); + else if(fun) + r= (*fun)(arg); + else + { + sh_exec((Shnode_t*)(nv_funtree((fp->node))),execflg|SH_ERREXIT); + r = sh.exitval; + } + } + if(--sh.fn_depth==1 && jmpval==SH_JMPERRFN) + errormsg(SH_DICT,ERROR_exit(1),e_toodeep,argv[0]); + sh_popcontext(&buff); + if (sh.st.self != &savst) + sh.var_tree = (Dt_t*)savst.save_tree; + nv_unscope(); + sh.namespace = nspace; + sh.var_tree = (Dt_t*)prevscope->save_tree; + sh_argreset(argsav,saveargfor); + trap = sh.st.trapcom[0]; + sh.st.trapcom[0] = 0; + sh_sigreset(1); + if (sh.st.self != &savst) + *sh.st.self = sh.st; + sh.st = *prevscope; + sh.topscope = (Shscope_t*)prevscope; + nv_getval(nv_scoped(IFSNOD)); + if(nsig) + memcpy((char*)&sh.st.trapcom[0],savstak,nsig); + sh.trapnote=0; + if(nsig) + stakset(savstak,0); + sh.options = savopt; + if(trap) + { + sh_trap(trap,0); + free(trap); + } + if(sh.exitval > SH_EXITSIG) + sh_fault(sh.exitval&SH_EXITMASK); + if(jmpval > SH_JMPFUN) + { + sh_chktrap(); + siglongjmp(*sh.jmplist,jmpval); + } + return(r); +} + + +static void sh_funct(Namval_t *np,int argn, char *argv[],struct argnod *envlist,int execflg) +{ + struct funenv fun; + char *fname = nv_getval(SH_FUNNAMENOD); + if(nv_isattr(np,NV_FPOSIX)) + { + char *save; + int loopcnt = sh.st.loopcnt; + sh.posix_fun = np; + opt_info.index = opt_info.offset = 0; + error_info.errors = 0; + save = argv[-1]; + argv[-1] = 0; + nv_putval(SH_FUNNAMENOD, nv_name(np),NV_NOFREE); + sh.st.loopcnt = 0; + b_dot_cmd(argn+1,argv-1,&sh); + sh.st.loopcnt = loopcnt; + argv[-1] = save; + } + else + { + fun.env = envlist; + fun.node = np; + sh_funscope(argn,argv,0,&fun,execflg); + } + nv_putval(SH_FUNNAMENOD,fname,NV_NOFREE); + nv_putval(SH_PATHNAMENOD, sh.st.filename ,0); +} + +/* + * external interface to execute a function without arguments + * <np> is the function node + * If <nq> is not-null, then sh.name and sh.subscript will be set + */ +int sh_fun(Namval_t *np, Namval_t *nq, char *argv[]) +{ + register int offset; + register char *base; + Namval_t node; + int n=0; + char *av[2]; + Fcin_t save; + fcsave(&save); + if((offset=staktell())>0) + base=stakfreeze(0); + if(!argv) + { + argv = av; + argv[1]=0; + } + argv[0] = nv_name(np); + while(argv[n]) + n++; + if(nq) + { + /* + * set ${.sh.name} and ${.sh.subscript} + * set _ to reference for ${.sh.name}[$.sh.subscript] + */ + struct Namref nr; + memset(&nr,0,sizeof(nr)); + nr.np = nq; + nv_putval(SH_NAMENOD, nv_name(nq), NV_NOFREE); + memcpy(&node,L_ARGNOD,sizeof(node)); + L_ARGNOD->nvalue.nrp = &nr; + L_ARGNOD->nvenv = 0; + L_ARGNOD->nvfun = (Namfun_t*)sh.last_table; + L_ARGNOD->nvflag = NV_REF|NV_NOFREE; + if(nv_arrayptr(nq)) + { + nv_putval(SH_SUBSCRNOD,nv_getsub(nq),NV_NOFREE); + L_ARGNOD->nvenv = (char*)SH_SUBSCRNOD->nvalue.cp; + } + } + if(is_abuiltin(np)) + { + int jmpval; + struct checkpt buff; + sh_pushcontext(&buff,SH_JMPCMD); + jmpval = sigsetjmp(buff.buff,1); + if(jmpval == 0) + { + void *context = nv_context(np); + errorpush(&buff.err,0); + error_info.id = argv[0]; + opt_info.index = opt_info.offset = 0; + opt_info.disc = 0; + sh.exitval = 0; + if(!context) + context = (void*)&sh; + sh.exitval = (*funptr(np))(n,argv,context); + } + sh_popcontext(&buff); + if(jmpval>SH_JMPCMD) + siglongjmp(*sh.jmplist,jmpval); + } + else + sh_funct(np,n,argv,(struct argnod*)0,sh_isstate(SH_ERREXIT)); + if(nq) + { + L_ARGNOD->nvalue.np = node.nvalue.np; + L_ARGNOD->nvenv = node.nvenv; + L_ARGNOD->nvflag = node.nvflag; + L_ARGNOD->nvfun = node.nvfun; + nv_unset(SH_NAMENOD); + nv_unset(SH_SUBSCRNOD); + } + fcrestore(&save); + if(offset>0) + stakset(base,offset); + return(sh.exitval); +} + +/* + * This dummy routine is called by built-ins that do recursion + * on the file system (chmod, chgrp, chown). It causes + * the shell to invoke the non-builtin version in this case + */ +int cmdrecurse(int argc, char* argv[], int ac, char* av[]) +{ + NOT_USED(argc); + NOT_USED(argv[0]); + NOT_USED(ac); + NOT_USED(av[0]); + return(SH_RUNPROG); +} + +/* + * set up pipe for cooperating process + */ +static void coproc_init(int pipes[]) +{ + int outfd; + if(sh.coutpipe>=0 && sh.cpid) + errormsg(SH_DICT,ERROR_exit(1),e_pexists); + sh.cpid = 0; + if(sh.cpipe[0]<=0 || sh.cpipe[1]<=0) + { + /* first co-process */ + sh_pclose(sh.cpipe); + sh_pipe(sh.cpipe); + if((outfd=sh.cpipe[1]) < 10) + { + int fd=fcntl(sh.cpipe[1],F_DUPFD,10); + if(fd>=10) + { + sh.fdstatus[fd] = (sh.fdstatus[outfd]&~IOCLEX); + close(outfd); + sh.fdstatus[outfd] = IOCLOSE; + sh.cpipe[1] = fd; + } + } + if(fcntl(*sh.cpipe,F_SETFD,FD_CLOEXEC)>=0) + sh.fdstatus[sh.cpipe[0]] |= IOCLEX; + sh.fdptrs[sh.cpipe[0]] = sh.cpipe; + + if(fcntl(sh.cpipe[1],F_SETFD,FD_CLOEXEC) >=0) + sh.fdstatus[sh.cpipe[1]] |= IOCLEX; + } + sh.outpipe = sh.cpipe; + sh_pipe(sh.inpipe=pipes); + sh.coutpipe = sh.inpipe[1]; + sh.fdptrs[sh.coutpipe] = &sh.coutpipe; + if(fcntl(sh.outpipe[0],F_SETFD,FD_CLOEXEC)>=0) + sh.fdstatus[sh.outpipe[0]] |= IOCLEX; +} + +#if SHOPT_SPAWN + + +#if SHOPT_AMP || !defined(_lib_fork) +/* + * print out function definition + */ +static void print_fun(register Namval_t* np, void *data) +{ + register char *format; + NOT_USED(data); + if(!is_afunction(np) || !np->nvalue.ip) + return; + if(nv_isattr(np,NV_FPOSIX)) + format="%s()\n{ "; + else + format="function %s\n{ "; + sfprintf(sfstdout,format,nv_name(np)); + sh_deparse(sfstdout,(Shnode_t*)(nv_funtree(np)),0); + sfwrite(sfstdout,"}\n",2); +} + +/* + * create a shell script consisting of t->fork.forktre and execute it + */ +static int run_subshell(const Shnode_t *t,pid_t grp) +{ + static char prolog[] = "(print $(typeset +A);set; typeset -p; print .sh.dollar=$$;set +o)"; + register int i, fd, trace = sh_isoption(SH_XTRACE); + int pin,pout; + pid_t pid; + char *arglist[2], *envlist[2], devfd[12], *cp; + Sfio_t *sp = sftmp(0); + envlist[0] = "_=" SH_ID; + envlist[1] = 0; + arglist[0] = error_info.id?error_info.id:sh.shname; + if(*arglist[0]=='-') + arglist[0]++; + arglist[1] = devfd; + strncpy(devfd,e_devfdNN,sizeof(devfd)); + arglist[2] = 0; + sfstack(sfstdout,sp); + if(trace) + sh_offoption(SH_XTRACE); + sfwrite(sfstdout,"typeset -A -- ",14); + sh_trap(prolog,0); + nv_scan(sh.fun_tree, print_fun, (void*)0,0, 0); + if(sh.st.dolc>0) + { + /* pass the positional parameters */ + char **argv = sh.st.dolv+1; + sfwrite(sfstdout,"set --",6); + while(*argv) + sfprintf(sfstdout," %s",sh_fmtq(*argv++)); + sfputc(sfstdout,'\n'); + } + pin = (sh.inpipe?sh.inpipe[1]:0); + pout = (sh.outpipe?sh.outpipe[0]:0); + for(i=3; i < 10; i++) + { + if(sh.fdstatus[i]&IOCLEX && i!=pin && i!=pout) + { + sfprintf(sfstdout,"exec %d<&%d\n",i,i); + fcntl(i,F_SETFD,0); + } + } + sfprintf(sfstdout,"LINENO=%d\n",t->fork.forkline); + if(trace) + { + sfwrite(sfstdout,"set -x\n",7); + sh_onoption(SH_XTRACE); + } + sfstack(sfstdout,NIL(Sfio_t*)); + sh_deparse(sp,t->fork.forktre,0); + sfseek(sp,(Sfoff_t)0,SEEK_SET); + fd = sh_dup(sffileno(sp)); + cp = devfd+8; + if(fd>9) + *cp++ = '0' + (fd/10); + *cp++ = '0' + fd%10; + *cp = 0; + sfclose(sp); + sfsync(NIL(Sfio_t*)); + if(!sh.shpath) + sh.shpath = pathshell(); + pid = spawnveg(sh.shpath,arglist,envlist,grp); + close(fd); + for(i=3; i < 10; i++) + { + if(sh.fdstatus[i]&IOCLEX && i!=pin && i!=pout) + fcntl(i,F_SETFD,FD_CLOEXEC); + } + if(pid <=0) + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,arglist[0]); + return(pid); +} +#endif /* !_lib_fork */ + +static void sigreset(int mode) +{ + register char *trap; + register int sig=sh.st.trapmax; + while(sig-- > 0) + { + if((trap=sh.st.trapcom[sig]) && *trap==0) + signal(sig,mode?sh_fault:SIG_IGN); + } +} + +/* + * A combined fork/exec for systems with slow or non-existent fork() + */ +static pid_t sh_ntfork(const Shnode_t *t,char *argv[],int *jobid,int flag) +{ + static pid_t spawnpid; + static int savetype; + static int savejobid; + Shell_t *shp = sh_getinterp(); + struct checkpt buff; + int otype=0, scope=0, jmpval; + int jobwasset=0, sigwasset=0; + char **arge, *path; + pid_t grp = 0; + Pathcomp_t *pp; + if(flag) + { + otype = savetype; + savetype=0; + } +# if SHOPT_AMP || !defined(_lib_fork) + if(!argv) + { + register Shnode_t *tchild = t->fork.forktre; + int optimize=0; + otype = t->tre.tretyp; + savetype = otype; + spawnpid = 0; +# ifndef _lib_fork + if((tchild->tre.tretyp&COMMSK)==TCOM) + { + Namval_t *np = (Namval_t*)(tchild->com.comnamp); + if(np) + { + path = nv_name(np); + if(!nv_isattr(np,BLT_ENV)) + np=0; + else if(strcmp(path,"echo")==0 || memcmp(path,"print",5)==0) + np=0; + } + else if(!tchild->com.comarg) + optimize=1; + else if(tchild->com.comtyp&COMSCAN) + { + if(tchild->com.comarg->argflag&ARG_RAW) + path = tchild->com.comarg->argval; + else + path = 0; + } + else + path = ((struct dolnod*)tchild->com.comarg)->dolval[ARG_SPARE]; + if(!np && path && !nv_search(path,shp->fun_tree,0)) + optimize=1; + } +# endif + sh_pushcontext(&buff,SH_JMPIO); + jmpval = sigsetjmp(buff.buff,0); + { + if((otype&FINT) && !sh_isstate(SH_MONITOR)) + { + signal(SIGQUIT,SIG_IGN); + signal(SIGINT,SIG_IGN); + if(!shp->st.ioset) + { + sh_iosave(0,buff.topfd); + sh_iorenumber(sh_chkopen(e_devnull),0); + } + } + if(otype&FPIN) + { + int fd = shp->inpipe[1]; + sh_iosave(0,buff.topfd); + sh_iorenumber(shp->inpipe[0],0); + if(fd>=0 && (!(otype&FPOU) || (otype&FCOOP)) && fcntl(fd,F_SETFD,FD_CLOEXEC)>=0) + shp->fdstatus[fd] |= IOCLEX; + } + if(otype&FPOU) + { + sh_iosave(1,buff.topfd); + sh_iorenumber(sh_dup(shp->outpipe[1]),1); + if(fcntl(shp->outpipe[0],F_SETFD,FD_CLOEXEC)>=0) + shp->fdstatus[shp->outpipe[0]] |= IOCLEX; + } + + if(t->fork.forkio) + sh_redirect(t->fork.forkio,0); + if(optimize==0) + { +#ifdef SIGTSTP + if(job.jobcontrol) + { + signal(SIGTTIN,SIG_DFL); + signal(SIGTTOU,SIG_DFL); + } +#endif /* SIGTSTP */ +#ifdef JOBS + if(sh_isstate(SH_MONITOR) && (job.jobcontrol || (otype&FAMP))) + { + if((otype&FAMP) || job.curpgid==0) + grp = 1; + else + grp = job.curpgid; + } +#endif /* JOBS */ + spawnpid = run_subshell(t,grp); + } + else + { + sh_exec(tchild,SH_NTFORK); + if(jobid) + *jobid = savejobid; + } + } + sh_popcontext(&buff); + if((otype&FINT) && !sh_isstate(SH_MONITOR)) + { + signal(SIGQUIT,sh_fault); + signal(SIGINT,sh_fault); + } + if((otype&FPIN) && (!(otype&FPOU) || (otype&FCOOP)) && fcntl(shp->inpipe[1],F_SETFD,FD_CLOEXEC)>=0) + shp->fdstatus[shp->inpipe[1]] &= ~IOCLEX; + if(t->fork.forkio || otype) + sh_iorestore(buff.topfd,jmpval); + if(optimize==0) + { +#ifdef SIGTSTP + if(job.jobcontrol) + { + signal(SIGTTIN,SIG_IGN); + signal(SIGTTOU,SIG_IGN); + } +#endif /* SIGTSTP */ + if(spawnpid>0) + _sh_fork(spawnpid,otype,jobid); + if(grp>0 && !(otype&FAMP)) + { + while(tcsetpgrp(job.fd,job.curpgid)<0 && job.curpgid!=spawnpid) + job.curpgid = spawnpid; + } + } + savetype=0; + if(jmpval>SH_JMPIO) + siglongjmp(*shp->jmplist,jmpval); + if(spawnpid<0 && (otype&FCOOP)) + { + sh_close(shp->coutpipe); + sh_close(shp->cpipe[1]); + shp->cpipe[1] = -1; + shp->coutpipe = -1; + } + shp->exitval = 0; + return(spawnpid); + } +# endif /* !_lib_fork */ + sh_pushcontext(&buff,SH_JMPCMD); + errorpush(&buff.err,ERROR_SILENT); + jmpval = sigsetjmp(buff.buff,0); + if(jmpval == 0) + { + if((otype&FINT) && !sh_isstate(SH_MONITOR)) + { + signal(SIGQUIT,SIG_IGN); + signal(SIGINT,SIG_IGN); + } + spawnpid = -1; + if(t->com.comio) + sh_redirect(t->com.comio,0); + error_info.id = *argv; + if(t->com.comset) + { + scope++; + nv_scope(t->com.comset); + } + if(!strchr(path=argv[0],'/')) + { +#ifdef PATH_BFPATH + Namval_t *np; + if((np=nv_search(path,shp->track_tree,0)) && !nv_isattr(np,NV_NOALIAS) && np->nvalue.cp) + path = nv_getval(np); + else if(path_absolute(path,NIL(Pathcomp_t*))) + { + path = stakptr(PATH_OFFSET); + stakfreeze(0); + } + else + { + pp=path_get(path); + while(pp) + { + if(pp->len==1 && *pp->name=='.') + break; + pp = pp->next; + } + if(!pp) + path = 0; + } +#else + path = shp->lastpath; +#endif + } + else if(sh_isoption(SH_RESTRICTED)) + errormsg(SH_DICT,ERROR_exit(1),e_restricted,path); + if(!path) + { + spawnpid = -1; + goto fail; + } + arge = sh_envgen(); + shp->exitval = 0; +#ifdef SIGTSTP + if(job.jobcontrol) + { + signal(SIGTTIN,SIG_DFL); + signal(SIGTTOU,SIG_DFL); + jobwasset++; + } +#endif /* SIGTSTP */ +#ifdef JOBS + if(sh_isstate(SH_MONITOR) && (job.jobcontrol || (otype&FAMP))) + { + if((otype&FAMP) || job.curpgid==0) + grp = 1; + else + grp = job.curpgid; + } +#endif /* JOBS */ + + sfsync(NIL(Sfio_t*)); + sigreset(0); /* set signals to ignore */ + sigwasset++; + /* find first path that has a library component */ + for(pp=path_get(argv[0]); pp && !pp->lib ; pp=pp->next); + spawnpid = path_spawn(path,argv,arge,pp,(grp<<1)|1); + if(spawnpid < 0 && errno==ENOEXEC) + { + char *devfd; + int fd = open(path,O_RDONLY); + argv[-1] = argv[0]; + argv[0] = path; + if(fd>=0) + { + struct stat statb; + sfprintf(sh.strbuf,"/dev/fd/%d",fd); + if(stat(devfd=sfstruse(sh.strbuf),&statb)>=0) + argv[0] = devfd; + } + if(!shp->shpath) + shp->shpath = pathshell(); + spawnpid = path_spawn(shp->shpath,&argv[-1],arge,pp,(grp<<1)|1); + if(fd>=0) + close(fd); + argv[0] = argv[-1]; + } + fail: + if(spawnpid < 0) switch(errno=shp->path_err) + { + case ENOENT: + errormsg(SH_DICT,ERROR_system(ERROR_NOENT),e_found+4); + default: + errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec+4); + } + } + else + exitset(); + sh_popcontext(&buff); + if(buff.olist) + free_list(buff.olist); +#ifdef SIGTSTP + if(jobwasset) + { + signal(SIGTTIN,SIG_IGN); + signal(SIGTTOU,SIG_IGN); + } +#endif /* SIGTSTP */ + if(sigwasset) + sigreset(1); /* restore ignored signals */ + if(scope) + { + nv_unscope(); + if(jmpval==SH_JMPSCRIPT) + nv_setlist(t->com.comset,NV_EXPORT|NV_IDENT|NV_ASSIGN); + } + if(t->com.comio) + sh_iorestore(buff.topfd,jmpval); + if(jmpval>SH_JMPCMD) + siglongjmp(*shp->jmplist,jmpval); + if(spawnpid>0) + { + _sh_fork(spawnpid,otype,jobid); +#ifdef JOBS + if(grp==1) + job.curpgid = spawnpid; +# ifdef SIGTSTP + if(grp>0 && !(otype&FAMP)) + { + while(tcsetpgrp(job.fd,job.curpgid)<0 && job.curpgid!=spawnpid) + job.curpgid = spawnpid; + } +# endif /* SIGTSTP */ +#endif /* JOBS */ + savejobid = *jobid; + if(otype) + return(0); + } + return(spawnpid); +} + +# ifdef _was_lib_fork +# define _lib_fork 1 +# endif +# ifndef _lib_fork + pid_t fork(void) + { + errormsg(SH_DICT,ERROR_exit(3),e_notimp,"fork"); + return(-1); + } +# endif /* _lib_fork */ +#endif /* SHOPT_SPAWN */ + +/* + * override procrun() since it is used in libcmd + */ +#include <proc.h> +int procrun(const char *path, char *argv[]) +{ + if(sh.subshell) + sh_subtmpfile(); + return(procclose(procopen(path, argv, NiL, NiL, PROC_FOREGROUND|PROC_GID +|PROC_UID))); +} diff --git a/usr/src/lib/libshell/common/shell.3 b/usr/src/lib/libshell/common/shell.3 new file mode 100644 index 0000000000..e6fbbf026e --- /dev/null +++ b/usr/src/lib/libshell/common/shell.3 @@ -0,0 +1,408 @@ +.fp 5 CW +.TH SHELL 3 "28 Feb 2003" +.PP +.SH NAME +.PP +\fBshell\fR \- a \f5ksh\fP library interface +.PP +.SH SYNOPSIS +.ta .8i 1.6i 2.4i 3.2i 4.0i 4.8i 5.6i +.SS "HEADERS/LIBRARIES" +.nf +.ft 5 +#include <shell.h> +libshell.a -lshell +.ft R +.fi +.SS "DATA TYPES" +.nf +.ft 5 +Shell_t; +Shopt_t; +Shscope_t; +Shbltin_f; +Shinit_f; +Shwait_f; +.ft R +.fi +.SS "FUNCTIONS" +.nf +.ft 5 +int sh_main(int \fIargc\fP, char *\fIargv\fP[], Sh_init \fIfn\fP); +Shell_t *sh_init(int \fIargc\fP, char *\fIargv\fP); +Shell_t *sh_getinterp(void); + +Namval_t *sh_addbuiltin(const char *\fIname\fP,Sh_bltin_f \fIfn\fP,void *\fIarg\fP); + +unsigned int sh_isoption(int \fIoption\fP); +unsigned int sh_onoption(int \fIoption\fP); +unsigned int sh_offoption(int \fIoption\fP); + +void *sh_parse(Shell_t *\fIshp\fP, Sfio_t *\fIsp\fP, int \fIflags\fP); +int sh_trap(const char *\fIstring\fP, int \fImode\fP); +int sh_eval(Sfio_t *\fIsp\fP,int \fImode\fP); +int sh_fun(Namval_t *\fIfunnode\fP, Namval_t *\fIvarnode\fP, char *\fIargv\fP[]); +int sh_funscope(int \fIargc\fP,char *\fIargv\fP[],int(*\fIfn\fP)(void*),void *\fIarg\fP,int \fIflags\fP); +Shscope_t *sh_getscope(int \fIindex\fP,int \fIwhence\fP); +Shscope_t *sh_setscope(Shscope_t *\fIscope\fP); + +int (*sh_fdnotify(int(*\fIfn\fP)(int,int)))(int,int); +char *sh_fmtq(const char *\fIstring\fP); +void *sh_waitnotify(Shwait_f \fIfn\fP); +void sh_delay(double \fIsec\fP); +Sfio_t *sh_iogetiop(int \fIfd\fP, int \fImode\fP); +int sh_sigcheck(void); +.ft R +.fi +.PP +.SH DESCRIPTION +.PP +The \fIShell\fP library is a set of functions used for +writing extensions to \f5ksh\fP or writing commands +that embed shell command processing. +The include file \f5<shell.h>\fP contains the type definitions, +function prototypes and symbolic constants defined by +this interface. It also defines replacement definitions for +the standard library functions +\f5access()\fP, +\f5close()\fP, +\f5dup()\fP, +\f5exit()\fP, +\f5fcntl()\fP, +\f5lseek()\fP, +\f5open()\fP, +\f5pipe()\fP, +\f5read()\fP, +and +\f5write()\fP +that must be used +with all code +intended to be compiled as built-in commands. +.P +The \f5<shell.h>\fP header includes \f5<ast.h>\fP which +in turn includes the standard include files, \f5<stddef.h>\fP, +\f5<stdlib.h>\fP, \f5<stdarg.h>\fP, \f5<limits.h>\fP, +\f5<stdio.h>\fP, \f5<string.h>\fP, \f5<unistd.h>\fP, +\f5<sys/types.h>\fP, \f5<fcntl.h>\fP, and \f5<locale.h>\fP. +The \f5<shell.h>\fP header also includes the headers +\f5<cdt.h>\fP, +\f5<cmd.h>\fP, +\f5<sfio.h>\fP, +\f5<nval.h>\fP, +\f5<stk.h>\fP, +and \f5<error.h>\fP +so that in most cases, programs only require the single +header \f5<shell.h>\fP. +.PP +Programs can use this library in one of the following ways: +.PD 0 +.TP +.B 1 +To write builtin commands and/or other code that will be loaded +into the shell by loading dynamic libraries +at run time using the \f5builtin\fP(1) command. +In this case the shell will look for a function named \f5lib_init\fP +in your library and, if found, will execute this function with +argument \f50\fP when the library is loaded. +In addition, for each argument named on the \f5builtin\fP +command line, it will look for a function named \f5b_\fP\fIname\fP\f5()\fP +in your library and will \fIname\fP as a built-in. +.TP +.B 2 +To build separate a separate command that uses the shell as a +library at compile or run time. +In this case the \f5sh_init()\fP function must be called to +initialize this library before any other commands in this library +are invoked. +The arguments \fIargc\fP and \fIargv\fP are the number +of arguments and the vector of arguments as supplied by the shell. +It returns a pointer the \f5Shell_t\fP. +.TP +.B 3 +To build a new version of \f5ksh\fP with extended capabilities, +for example \f5tksh\fP(1). +In this case, the user writes a \f5main()\fP function that +calls \f5sh_main()\fP with the \fIargc\fP and \fIargv\fP arguments +from \f5main\fP and pointer to function, \fIfn\fP as a third +argument.. The function \fIfn\fP will +be invoked with argument \f50\fP after \f5ksh\fP has done initialization, +but before \f5ksh\fP has processed any start up files or executed +any commands. The function \fIfn\fP +will be invoked with an argument of \f51\fP before \f5ksh\fP +begins to execute a script that has been invoked by name +since \f5ksh\fP cleans up memory and long jumps back to +the beginning of the shell in this case. +The function \fIfn\fP will be called with argument \f5-1\fP before +the shell exits. +.PD +.PP +The \f5Shell_t\fP structure contains the following fields: +.nf +.ft 5 + Shopt_t \fIoptions\fP; \fR/* set -o options */\fP + Dt_t *\fIvar_tree\fP; \fR/* shell variable dictionary */\fP + Dt_t *\fIfun_tree\fP; \fR/* shell function dictionary */\fP + Dt_t *\fIalias_tree\fP; \fR/* shell alias dictionary */\fP + Dt_t *\fIbltin_tree\fP; \fR/* shell built-in dictionary */\fP + Shscope_t *\fItopscope\fP; \fR/* pointer to top-level scope */\fP + char *\fIinfile_name\fP; \fR/* path name of current input file*/\fP + int \fIinlineno\fP; \fR/* line number of current input file*/\fP + int \fIexitval\fP; \fR/* most recent exit value*/\fP +.ft R +.fi +This structure is returned by \f5sh_init()\fP but can also be retrieved +by a call to \f5sh_getinterp()\fP. +.PP +All built-in commands to the shell are invoked with +three arguments. The first two arguments give the +number of arguments and the argument list +and uses the same conventions as the \f5main()\fP function +of a program. The third argument is a pointer that +can be associated with each built-in. +The \f5sh_addbuiltin()\fP function is used to add, replace or delete +built-in commands. +It takes the name of the built-in, \fIname\fP, a pointer +to the function that implements the built-in, \fIfn\fP, and +a pointer that will be passed to the function when +it is invoked. +If, \fIfn\fP is non-\f5NULL\fP the built-in command +is added or replaced. Otherwise, the given +built-in command will be deleted. +The \fIname\fP argument can be in the format of a pathname. +It cannot be the name of any of the special built-in commands. +If \fIname\fP contains a \f5/\fP, the built-in is the basename of +the pathname and the built-in will only be executed +if the given pathname is encountered when performing +a path search. +When adding or replacing a built-in, +\f5sh_addbuiltin()\fP function returns a pointer to +the name-value pair corresponding to the built-in on success and \f5NULL\fP +if it is unable to add or replace the built-in. +When deleting a built-in, \f5NULL\fP is returned on success or +if not found, and the name-value pair pointer is returned if the built-in +cannot be deleted. +.PP +The functions \f5sh_onoption()\fP, \f5sh_offoption()\fP, \f5sh_isoption()\fP +are used to set, unset, and test for shell options respectively. +The \fIoption\fP argument can be any one of the following: +.IP +\f5SH_ALLEXPORT\fP: +The \f5NV_EXPORT\fP attribute is given to each variable whose +name is an identifier when a value is assigned. +.IP +\f5SH_BGNICE\fP: +Each background process is run at a lower priority. +.IP +\f5SH_ERREXIT\fP: +Causes a non-interactive shell to exit when a command, +other than a conditional command, returns non-zero. +.IP +\f5SH_EMACS\fP: +The emacs editing mode. +.IP +\f5SH_GMACS\fP: +Same as the emacs editing mode except for the behavior of CONTROL-T. +.IP +\f5SH_HISTORY\fP: +Indicates that the history file has been created and that +commands can be logged. +.IP +\f5SH_IGNOREEOF\fP: +Do not treat end-of-file as exit. +.IP +\f5SH_INTERACTIVE\fP: +.IP +Set for interactive shells. +Do not set or unset this option. +\f5SH_MARKDIRS\fP: +A \fB/\fP is added to the end of each directory generated by pathname +expansion. +.IP +\f5SH_MONITOR\fP: +Indicates that the monitor option is enabled for job control. +.IP +\f5SH_NOCLOBBER\fP: +The \fB>\fP redirection will fail if the file exists. Each file +created with \fB>\fP will have the \f5O_EXCL\fP bit set as described +in \f5<fcntl.h>\fP +.IP +\f5SH_NOGLOB\fP: +Do not perform pathname expansion. +.IP +\f5SH_NOLOG\fP: +Do not save function definitions in the history file. +.IP +\f5SH_NOTIFY\fP: +Cause a message to be generated as soon as each background job completes. +.IP +\f5SH_NOUNSET\fP: +Cause the shell to fail with an error of an unset variable is +referenced. +.IP +\f5SH_PRIVILEGED\fP: +.IP +\f5SH_VERBOSE\fP: +Cause each line to be echoed as it is read by the parser. +.IP +\f5SH_XTRACE\fP: +Cause each command to be displayed after all expansions, but +before execution. +.IP +\f5SH_VI\fP: +The vi edit mode. +.IP +\f5SH_VIRAW\fP: +Read character at a time rather than line at a time when +in vi edit mode. +.IP +.PP +The \f5sh_trap()\fP function can be used to compile and execute +a string or file. +A value of \f50\fP for \fImode\fP indicates that \fIname\fP +refers to a string. A value of \f51\fP for \fImode\fP +indicates that \fIname\fP is an \f5Sfio_t*\fP to an open stream. +A value of \f52\fP for \fImode\fP indicates that \fIname\fP +points to a parse tree that has been returned by \f5sh_parse()\fP. +The complete file associated with the string or file +is compiled and then executed so that aliases defined +within the string or file will not take effect until +the next command is executed. +.PP +The \f5sh_eval()\fP function executes a string or file +stream \fIsp\fP. +If \fImode\fP is non-zero and the history file has +been created, the stream defined by \fIsp\fP +will be appended to the history file as a command. +.PP +The \f5sh_parse()\fP function takes a pointer to the +shell interpreter \fIshp\fP, a pointer to a string or file stream +\fIsp\fP, and compilation flags, and returns a pointer +to a parse tree of the compiled stream. This pointer can +be used in subsequent calls to \f5sh_trap()\fP. +The compilation flags can be zero or more of the following: +.IP +\f5SH_NL\fP: +Treat new-lines as \fB;\fP. +.IP +\f5SH_EOF\fP: +An end of file causes syntax error. By default it will +be treated as a new-line. +.PP +\f5ksh\fP executes each function defined with the \f5function\fP +reserved word in a separate scope. The \f5Shscope_t\fP type +provides an interface to some of the information that +is available on each scope. The structure contains +the following public members: +.nf + \f5Sh_scope_t *par_scope;\fP + \f5int argc;\fP + \f5char **argv;\fP + \f5char *cmdname;\fP + \f5Dt_t *var_tree;\fP +.fi +The \f5sh_getscope()\fP function can be used to the the +scope information associated with existing scope. +Scopes are numbered from \f50\fP for the global scope +up to the current scope level. The \fIwhence\fP +argument uses the symbolic constants associated with \f5lseek()\fP +to indicate whether the \f5Iscope\fP argument is absolute, +relative to the current scope, or relative to the topmost scope. +The\f5sh_setscope()\fP function can be used to make a +a known scope the current scope. It returns a pointer to the +old current scope. +.PP +The \f5sh_funscope()\fP function can be used to run a function +in a new scope. The arguments \fIargc\fP and \fIargv\fP +are the number of arguments and the list of arguments +respectively. If \fIfn\fP is non-\f5NULL\fP, then +this function is invoked with \fIargc\fP, \fIargv\fP, and \fIarg\fP +as arguments. +.PP +The \f5sh_fun()\fP function can be called within a +discipline function or built-in extension to execute a +discipline function script. +The argument \fIfunnode\fP is a pointer to the shell function +or built-in to execute. +The argument \fIvarnode\fP is a pointer to the name +value pair that has defined this discipline. +The array \fIargv\fP is a \f5NULL\fP terminated list of +arguments that are passed to the function. +.PP +By default, \f5ksh\fP only records but does not act +on signals when running a built-in command. +If a built-in takes a substantial amount of time +to execute, then it should check for interrupts +periodically by calling \f5sh_sigcheck()\fP. +If a signal is pending, \f5sh_sigcheck()\fP will exit +the function you are calling and return to the point +where the most recent built-in was invoked, or where +\f5sh_eval()\fP or \f5sh_trap()\fP was called. +.PP +The \f5sh_delay()\fP function is used to cause the +shell to sleep for a period of time defined by \fIsec\fP. +.PP +The \f5sh_fmtq()\fP function can be used to convert a string +into a string that is quoted so that it can be reinput +to the shell. The quoted string returned by \f5sh_fmtq\fP +may be returned on the current stack, so that it +must be saved or copied. +.PP +The \f5sh_fdnotify()\fP function causes the function \fIfn\fP +to be called whenever the shell duplicates or closes a file. +It is provided for extensions that need to keep track of +file descriptors that could be changed by shell commands. +The function \fIfn\fP is called with two arguments. +The first argument is the original file descriptor. The +second argument is the new file descriptor for duplicating +files, and \f5SH_FDCLOSE\fP when a file has been closed. +The previously installed \f5sh_fdnotify()\fP function pointer +is returned. +.PP +The \f5sh_waitnotify()\fP function causes the function \fIfn\fP +to be called whenever the shell is waiting for input from +a slow device or waiting for a process to complete. +This function can process events and run shell commands +until there is input, the timer is reached or a signal arises. +It is called with three arguments. The first is the file +descriptor from which the shell trying to read or \f5\-1\fP +if the shell is waiting for a process to complete. +The second is a timeout in milliseconds. +A value of \f5\-1\fP for the timeout means that +no timeout should be set. +The third argument is 0 for input file descriptors +and 1 for output file descriptor. +The function needs to return a value \f5>0\fP if there +is input on the file descriptor, and a value \f5<0\fP +if the timeout is reached or a signal has occurred. +A value of \f50\fP indicates +that the function has returned without processing and that the shell +should wait for input or process completion. +The previous installed \f5sh_waitnotify()\fP function pointer is returned. +.PP +The \f5sh_iogetiop()\fP function returns a pointer to the +Sfio stream corresponding to file descriptor number \fIfd\fP +and the given mode \fImode\fP. The mode can be either +\f5SF_READ\fP or \f5SF_WRITE\fP. +The \fIfd\fP argument can the number of an open file descriptor or +one of the following symbolic constants: +.IP +\f5SH_IOCOPROCESS\fP: +The stream corresponding to the most recent co-process. +.IP +\f5SH_IOHISTFILE\fP: +The stream corresponding to the history file. +If no stream exists corresponding to \fIfd\fP or the stream +can not be accessed in the specified mode, \f5NULL\fP is returned. +.PP +.SH SEE ALSO +builtin(1) +cdt(3) +error(3) +nval(3) +sfio(3) +stk(3) +tksh(1) +.PP +.SH AUTHOR +David G. Korn (dgk@research.att.com). + diff --git a/usr/src/lib/libshell/common/tests/alias.sh b/usr/src/lib/libshell/common/tests/alias.sh new file mode 100644 index 0000000000..bf7f17f5f6 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/alias.sh @@ -0,0 +1,83 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +alias foo='print hello' +if [[ $(foo) != hello ]] +then err_exit 'foo, where foo is alias for "print hello" failed' +fi +if [[ $(foo world) != 'hello world' ]] +then err_exit 'foo world, where foo is alias for "print hello" failed' +fi +alias foo='print hello ' +alias bar=world +if [[ $(foo bar) != 'hello world' ]] +then err_exit 'foo bar, where foo is alias for "print hello " failed' +fi +if [[ $(foo \bar) != 'hello bar' ]] +then err_exit 'foo \bar, where foo is alias for "print hello " failed' +fi +alias bar='foo world' +if [[ $(bar) != 'hello world' ]] +then err_exit 'bar, where bar is alias for "foo world" failed' +fi +if [[ $(alias bar) != "bar='foo world'" ]] +then err_exit 'alias bar, where bar is alias for "foo world" failed' +fi +unalias foo || err_exit "unalias foo failed" +alias foo 2> /dev/null && err_exit "alias for non-existent alias foo returns true" +unset bar +alias bar="print foo$bar" +bar=bar +if [[ $(bar) != foo ]] +then err_exit 'alias bar, where bar is alias for "print foo$bar" failed' +fi +unset bar +alias bar='print hello' +if [[ $bar != '' ]] +then err_exit 'alias bar cause variable bar to be set' +fi +alias !!=print +if [[ $(!! hello 2>/dev/null) != hello ]] +then err_exit 'alias for !!=print not working' +fi +alias foo=echo +if [[ $(print "$(foo bar)" ) != bar ]] +then err_exit 'alias in command substitution not working' +fi +( unalias foo) +if [[ $(foo bar 2> /dev/null) != bar ]] +then err_exit 'alias not working after unalias in subshell' +fi +builtin -d rm 2> /dev/null +if whence rm > /dev/null +then [[ ! $(alias -t | grep rm= ) ]] && err_exit 'tracked alias not set' + PATH=$PATH + [[ $(alias -t | grep rm= ) ]] && err_exit 'tracked alias not cleared' +fi +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/append.sh b/usr/src/lib/libshell/common/tests/append.sh new file mode 100644 index 0000000000..7f3cad6021 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/append.sh @@ -0,0 +1,72 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +{ +x=abc +x+=def ;} 2> /dev/null +if [[ $x != abcdef ]] +then err_exit 'abc+def != abcdef' +fi +integer i=3 +{ i+=4;} 2> /dev/null +if (( i != 7 )) +then err_exit '3+4!=7' +fi +iarray=( one two three ) +{ iarray+= (four five six) ;} 2> /dev/null +if [[ ${iarray[@]} != 'one two three four five six' ]] +then err_exit 'index array append fails' +fi +unset iarray +iarray=one +{ iarray+= (four five six) ;} 2> /dev/null +if [[ ${iarray[@]} != 'one four five six' ]] +then err_exit 'index array append to scalar fails' +fi +typeset -A aarray +aarray=( [1]=1 [3]=4 [xyz]=xyz ) +aarray+=( [2]=2 [3]=3 [foo]=bar ) +if [[ ${aarray[3]} != 3 ]] +then err_exit 'associative array append fails' +fi +if [[ ${#aarray[@]} != 5 ]] +then err_exit 'number of elements of associative array append fails' +fi +point=(x=1 y=2) +point+=( y=3 z=4) +if [[ ${point.y} != 3 ]] +then err_exit 'compound append fails' +fi +unset foo +foo=one +foo+=(two) +if [[ ${foo[@]} != 'one two' ]] +then err_exit 'array append to non array variable fails' +fi +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/arith.sh b/usr/src/lib/libshell/common/tests/arith.sh new file mode 100644 index 0000000000..ec3ada5df1 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/arith.sh @@ -0,0 +1,466 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +trap '' FPE # NOTE: osf.alpha requires this (no ieee math) +integer Errors=0 +integer x=1 y=2 z=3 +if (( 2+2 != 4 )) +then err_exit 2+2!=4 +fi +if ((x+y!=z)) +then err_exit x+y!=z +fi +if (($x+$y!=$z)) +then err_exit $x+$y!=$z +fi +if (((x|y)!=z)) +then err_exit "(x|y)!=z" +fi +if ((y >= z)) +then err_exit "y>=z" +fi +if ((y+3 != z+2)) +then err_exit "y+3!=z+2" +fi +if ((y<<2 != 1<<3)) +then err_exit "y<<2!=1<<3" +fi +if ((133%10 != 3)) +then err_exit "133%10!=3" + if (( 2.5 != 2.5 )) + then err_exit 2.5!=2.5 + fi +fi +d=0 +((d || 1)) || err_exit 'd=0; ((d||1))' +if (( d++!=0)) +then err_exit "d++!=0" +fi +if (( --d!=0)) +then err_exit "--d!=0" +fi +if (( (d++,6)!=6 && d!=1)) +then err_exit '(d++,6)!=6 && d!=1' +fi +d=0 +if (( (1?2+1:3*4+d++)!=3 || d!=0)) +then err_exit '(1?2+1:3*4+d++) !=3' +fi +for ((i=0; i < 20; i++)) +do : +done +if (( i != 20)) +then err_exit 'for (( expr)) failed' +fi +for ((i=0; i < 20; i++)); do : ; done +if (( i != 20)) +then err_exit 'for (( expr));... failed' +fi +for ((i=0; i < 20; i++)) do : ; done +if (( i != 20)) +then err_exit 'for (( expr))... failed' +fi +if (( (i?0:1) )) +then err_exit '(( (i?0:1) )) failed' +fi +if (( (1 || 1 && 0) != 1 )) +then err_exit '( (1 || 1 && 0) != 1) failed' +fi +if (( (_=1)+(_x=0)-_ )) +then err_exit '(_=1)+(_x=0)-_ failed' +fi +if (( (3^6) != 5)) +then err_exit '((3^6) != 5) failed' +fi +integer x=1 +if (( (x=-x) != -1 )) +then err_exit '(x=-x) != -1 failed' +fi +i=2 +if (( 1$(($i))3 != 123 )) +then err_exit ' 1$(($i))3 failed' +fi +((pi=4*atan(1.))) +point=( + float x + float y +) +(( point.x = cos(pi/6), point.y = sin(pi/6) )) +if (( point.x*point.x + point.y*point.y > 1.01 )) +then err_exit 'cos*cos +sin*sin > 1.01' +fi +if (( point.x*point.x + point.y*point.y < .99 )) +then err_exit 'cos*cos +sin*sin < .99' +fi +if [[ $((y=x=1.5)) != 1 ]] +then err_exit 'typecast not working in arithmetic evaluation' +fi +typeset -E x=1.5 +( ((x++)) ) 2>/dev/null +if [[ $? == 0 ]] +then err_exit 'postincrement of floating point allowed' +fi +( ((++x)) ) 2>/dev/null +if [[ $? == 0 ]] +then err_exit 'preincrement of floating point allowed' +fi +x=1.5 +( ((x%1.1)) ) 2>/dev/null +if [[ $? == 0 ]] +then err_exit 'floating point allowed with % operator' +fi +x=.125 +if [[ $(( 4 * x/2 )) != 0.25 ]] +then err_exit '(( 4 * x/2 )) is not 0.25, with x=.125' +fi +if [[ $(( pow(2,3) )) != 8 ]] +then err_exit '$(( pow(2,3) )) != 8' +fi +( [[ $(( pow(2,(3)) )) == 8 ]] ) 2> /dev/null +if (( $? )) +then err_exit '$(( pow(2,(3)) )) != 8' +fi +unset x +integer x=1; integer x=1 +if [[ $x != 1 ]] +then err_exit 'two consecutive integer x=1 not working' +fi +unset z +{ z=$(typeset -RZ2 z2; (( z2 = 8 )); print $z2) ;} 2>/dev/null +if [[ $z != "08" ]] +then err_exit "typeset -RZ2 leading 0 decimal not working [z=$z]" +fi +{ z=$(typeset -RZ3 z3; (( z3 = 8 )); print $z3) ;} 2>/dev/null +if [[ $z != "008" ]] +then err_exit "typeset -RZ3 leading 0 decimal not working [z=$z]" +fi +unset z +typeset -Z3 z=010 +(( z=z+1)) +if [[ $z != 011 ]] +then err_exit "leading 0's in -Z not treated as decimal" +fi +unset x +integer x=0 +if [[ $((x+=1)) != 1 ]] || ((x!=1)) +then err_exit "+= not working" + x=1 +fi +x=1 +if [[ $((x*=5)) != 5 ]] || ((x!=5)) +then err_exit "*= not working" + x=5 +fi +if [[ $((x%=4)) != 1 ]] || ((x!=1)) +then err_exit "%= not working" + x=1 +fi +if [[ $((x|=6)) != 7 ]] || ((x!=7)) +then err_exit "|= not working" + x=7 +fi +if [[ $((x&=5)) != 5 ]] || ((x!=5)) +then err_exit "&= not working" + x=5 +fi +function newscope +{ + float x=1.5 + (( x += 1 )) + print -r -- $x +} +if [[ $(newscope) != 2.5 ]] +then err_exit "arithmetic using wrong scope" +fi +unset x +integer y[3]=9 y[4]=2 i=3 +(( x = y[3] + y[4] )) +if [[ $x != 11 ]] +then err_exit "constant index array arithmetic failure" +fi +(( x = $empty y[3] + y[4] )) +if [[ $x != 11 ]] +then err_exit "empty constant index array arithmetic failure" +fi +(( x = y[i] + y[i+1] )) +if [[ $x != 11 ]] +then err_exit "variable subscript index array arithmetic failure" +fi +integer a[5]=3 a[2]=4 +(( x = y[a[5]] + y[a[2]] )) +if [[ $x != 11 ]] +then err_exit "nested subscript index array arithmetic failure" +fi +unset y +typeset -Ai y +y[three]=9 y[four]=2 +three=four +four=three +(( x = y[three] + y[four] )) +if [[ $x != 11 ]] +then err_exit "constant associative array arithmetic failure" +fi +(( x = y[$three] + y[$four] )) +if [[ $x != 11 ]] +then err_exit "variable subscript associative array arithmetic failure" +fi +$SHELL -nc '((a = 1))' 2> /dev/null || err_exit "sh -n fails with arithmetic" +$SHELL -nc '((a.b++))' 2> /dev/null || err_exit "sh -n fails with arithmetic2" +unset z +float z=7.5 +if { (( z%2 != 1));} 2> /dev/null +then err_exit '% not working on floating point' +fi +chr=(a ' ' '=' '\r' '\n' '\\' '\"' '$' "\\'" '[' ']' '(' ')' '<' '\xab' '\040' '`' '{' '}' '*' '\E') +if (('a' == 97)) +then val=(97 32 61 13 10 92 34 36 39 91 93 40 41 60 171 32 96 123 125 42 27) +else val=(129 64 126 13 21 224 127 91 125 173 189 77 93 76 171 32 121 192 208 92 39 21) +fi +q=0 +for ((i=0; i < ${#chr[@]}; i++)) +do if (( '${chr[i]}' != ${val[i]} )) + then err_exit "(( '${chr[i]}' != ${val[i]} ))" + fi + if [[ $(( '${chr[i]}' )) != ${val[i]} ]] + then err_exit "(( '${chr[i]}' )) != ${val[i]}" + fi + if [[ $(( L'${chr[i]}' )) != ${val[i]} ]] + then err_exit "(( '${chr[i]}' )) != ${val[i]}" + fi + if eval '((' "'${chr[i]}'" != ${val[i]} '))' + then err_exit "eval (( '${chr[i]}' != ${val[i]} ))" + fi + if eval '((' "'${chr[i]}'" != ${val[i]} ' + $q ))' + then err_exit "eval (( '${chr[i]}' != ${val[i]} ))" + fi +done +unset x +typeset -ui x=4294967293 +[[ $x != 4294967293 ]] && err_exit "unsigned integers not working" +x=32767 +x=x+1 +[[ $x != 32768 ]] && err_exit "unsigned integer addition not working" +unset x +float x=99999999999999999999999999 +if (( x < 1e20 )) +then err_exit 'large integer constants not working' +fi +unset x y +function foobar +{ + nameref x=$1 + (( x +=1 )) + print $x +} +x=0 y=4 +if [[ $(foobar y) != 5 ]] +then err_exit 'name references in arithmetic statements in functions broken' +fi +if (( 2**3 != pow(2,3) )) +then err_exit '2**3 not working' +fi +if (( 2**3*2 != pow(2,3)*2 )) +then err_exit '2**3*2 not working' +fi +if (( 4**3**2 != pow(4,pow(3,2)) )) +then err_exit '4**3**2 not working' +fi +if (( (4**3)**2 != pow(pow(4,3),2) )) +then err_exit '(4**3)**2 not working' +fi +typeset -Z3 x=11 +typeset -i x +if (( x != 11 )) +then err_exit '-Z3 not treated as decimal' +fi +unset x +typeset -ui x=-1 +(( x >= 0 )) || err_exit 'unsigned integer not working' +(( $x >= 0 )) || err_exit 'unsigned integer not working as $x' +unset x +typeset -ui42 x=50 +if [[ $x != 42#18 ]] +then err_exit 'display of unsigned integers in non-decimal bases wrong' +fi +$SHELL -c 'i=0;(( ofiles[i] != -1 && (ofiles[i] < mins || mins == -1) ));exit 0' 2> /dev/null || err_exit 'lexical error with arithemtic expression' +rm -f core +$SHELL -c '(( +1 == 1))' 2> /dev/null || err_exit 'unary + not working' +typeset -E20 val=123.01234567890 +[[ $val == 123.0123456789 ]] || err_exit "rounding error val=$val" +if [[ $(print x$((10))=foo) != x10=foo ]] +then err_exit 'parsing error with x$((10))=foo' +fi +$SHELL -c 'typeset x$((10))=foo' 2> /dev/null || err_exit 'typeset x$((10)) parse error' +unset x +x=$(( exp(log(2.0)) )) +(( x > 1.999 && x < 2.001 )) || err_exit 'composite functions not working' +unset x y n +typeset -Z8 x=0 y=0 +integer n +for (( n=0; n < 20; n++ )) +do let "x = $x+1" + (( y = $y+1 )) +done +(( x == n )) || err_exit 'let with zero filled fields not working' +(( y == n )) || err_exit '((...)) with zero filled fields not working' +typeset -LZ3 x=10 +[[ $(($x)) == 10 && $((1$x)) == 1010 ]] || err_exit 'zero filled fields not preserving leading zeros' +unset y +[[ $(let y=$x;print $y) == 10 && $(let y=1$x;print $y) == 1010 ]] || err_exit 'zero filled fields not preserving leading zeros with let' +unset i ip ipx +typeset -i hex=( 172 30 18 1) +typeset -iu ip=0 ipx=0 +integer i +for ((i=0; i < 4; i++)) +do (( ip = (ip<<8) | hex[i])) +done +for ((i=0; i < 4; i++)) +do (( ipx = ip % 256 )) + (( ip /= 256 )) + (( ipx != hex[3-i] )) && err_exit "hex digit $((3-i)) not correct" +done +unset x +x=010 +(( x == 8 )) || err_exit 'leading zeros not treated as octal arithmetic' +(( $x == 8 )) || err_exit 'leading zeros not treated as octal arithmetic with $x' +unset x +typeset -Z x=010 +(( x == 10 )) || err_exit 'leading zeros not ignored for arithmetic' +(( $x == 10 )) || err_exit 'leading zeros not ignored for arithmetic with $x' +typeset -i i=x +(( i == 10 )) || err_exit 'leading zeros not ignored for arithmetic assignment' +(( ${x:0:1} == 0 )) || err_exit 'leading zero should not be stripped for x:a:b' +c010=3 +(( c$x == 3 )) || err_exit 'leading zero with variable should not be stripped' +[[ $( ($SHELL -c '((++1))' 2>&1)2>/dev/null ) == *lvalue* ]] || err_exit "((--1)) not generating error message" +i=2 +(( "22" == 22 )) || err_exit "double quoted constants fail" +(( "2$i" == 22 )) || err_exit "double quoted variables fail" +(( "18+$i+2" == 22 )) || err_exit "double quoted expressions fail" +# 04-04-28 bug fix +unset i; typeset -i i=01-2 +(( i == -1 )) || err_exit "01-2 is not -1" + +trap 'rm -f /tmp/script$$ /tmp/data$$.[12]' EXIT +cat > /tmp/script$$ <<-\! +tests=$* +typeset -A blop +function blop.get +{ + .sh.value=777 +} +function mkobj +{ + nameref obj=$1 + obj=() + [[ $tests == *1* ]] && { + (( obj.foo = 1 )) + (( obj.bar = 2 )) + (( obj.baz = obj.foo + obj.bar )) # ok + echo $obj + } + [[ $tests == *2* ]] && { + (( obj.faz = faz = obj.foo + obj.bar )) # ok + echo $obj + } + [[ $tests == *3* ]] && { + # case 3, 'active' variable involved, w/ intermediate variable + (( obj.foz = foz = ${blop[1]} )) # coredump + echo $obj + } + [[ $tests == *4* ]] && { + # case 4, 'active' variable, in two steps + (( foz = ${blop[1]} )) # ok + (( obj.foz = foz )) # ok + echo $obj + } + [[ $tests == *5* ]] && { + # case 5, 'active' variable involved, w/o intermediate variable + (( obj.fuz = ${blop[1]} )) # coredump + echo $obj + } + [[ $tests == *6* ]] && { + echo $(( obj.baz = obj.foo + obj.bar )) # coredump + } + [[ $tests == *7* ]] && { + echo $(( obj.foo + obj.bar )) # coredump + } +} +mkobj bla +! +chmod +x /tmp/script$$ +[[ $(/tmp/script$$ 1) != '( bar=2 baz=3 foo=1 )' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +[[ $(/tmp/script$$ 2) != '( faz=0 )' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +[[ $(/tmp/script$$ 3) != '( foz=777 )' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +[[ $(/tmp/script$$ 4) != '( foz=777 )' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +[[ $(/tmp/script$$ 5) != '( fuz=777 )' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +[[ $(/tmp/script$$ 6) != '0' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +[[ $(/tmp/script$$ 7) != '0' ]] 2>/dev/null && err_exit 'compound var arithmetic failed' +unset foo +typeset -F1 foo=123456789.19 +[[ $foo == 123456789.2 ]] || err_exit 'typeset -F1 not working correctly' + +# divide by zero + +for expr in '1/(1/2)' '8%(1/2)' '8%(1.0/2)' +do [[ $( ( $SHELL -c "( (($expr)) ) || print ok" ) 2>/dev/null ) == ok ]] || err_exit "divide by zero not trapped: $expr" +done + +for expr in '1/(1.0/2)' '1/(1/2.0)' +do [[ $( ( $SHELL -c "( print -r -- \$(($expr)) )" ) 2>/dev/null ) == 2 ]] || err_exit "invalid value for: $expr" +done +[[ $((5||0)) == 1 ]] || err_exit '$((5||0))'" == $((5||0)) should be 1" +$SHELL -c 'integer x=3 y=2; (( (y += x += 2) == 7 && x==5))' 2> /dev/null || err_exit '((y += x += 2)) not working' +$SHELL -c 'b=0; [[ $((b?a=1:b=9)) == 9 ]]' 2> /dev/null || err_exit 'b?a=1:b=9 not working' +unset x +(( x = 4*atan(1.0) )) +[[ $x == "$((x))" ]] || err_exit '$x !- $((x)) when x is pi' +$SHELL -c "[[ ${x//./} == {14,100}(\d) ]]" 2> /dev/null || err_exit 'pi has less than 14 significant places' +if (( Inf+1 == Inf )) +then [[ $(printf "%g\n" $((Inf))) == inf ]] || err_exit 'printf "%g\n" $((Inf)) fails' +# [[ $(printf "%g\n" $((Nan))) == inf ]] || err_exit 'printf "%g\n" $((Nan)) fails' + [[ $(printf "%g\n" Inf) == inf ]] || err_exit 'printf "%g\n" Inf fails' + [[ $(printf "%g\n" NaN) == nan ]] || err_exit 'printf "%g\n" NaN fails' + [[ $(print -- $((Inf))) == inf ]] || err_exit 'print -- $((Inf)) fails' + (( 1.0/0.0 == Inf )) || err_exit '1.0/0.0 != Inf' + [[ $(print -- $((0.0/0.0))) == nan ]] || err_exit '0.0/0.0 != NaN' + (( Inf*Inf == Inf )) || err_exit 'Inf*Inf != Inf' + (( NaN != NaN )) || err_exit 'NaN == NaN' + (( -5*Inf == -Inf )) || err_exit '-5*Inf != -Inf' + [[ $(print -- $((sqrt(-1.0)))) == nan ]]|| err_exit 'sqrt(-1.0) != NaN' + (( pow(1.0,Inf) == 1.0 )) || err_exit 'pow(1.0,Inf) != 1.0' + (( pow(Inf,0.0) == 1.0 )) || err_exit 'pow(Inf,0.0) != 1.0' + [[ $(print -- $((NaN/Inf))) == nan ]] || err_exit 'NaN/Inf != NaN' + (( 4.0/Inf == 0.0 )) || err_exit '4.0/Inf != 0.0' +else err_exit 'Inf and NaN not working' +fi +unset x y +float x=14.555 y +y=$(printf "%a" x) +(( x == y )) || err_exit "output of printf %a not self preserving -- expected $x, got $y" +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/arrays.sh b/usr/src/lib/libshell/common/tests/arrays.sh new file mode 100644 index 0000000000..7dd09fca0f --- /dev/null +++ b/usr/src/lib/libshell/common/tests/arrays.sh @@ -0,0 +1,381 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +function fun +{ + integer i + unset xxx + for i in 0 1 + do xxx[$i]=$i + done +} + +Command=${0##*/} +integer Errors=0 +set -A x zero one two three four 'five six' +if [[ $x != zero ]] +then err_exit '$x is not element 0' +fi +if [[ ${x[0]} != zero ]] +then err_exit '${x[0] is not element 0' +fi +if (( ${#x[0]} != 4 )) +then err_exit "length of ${x[0]} is not 4" +fi +if (( ${#x[@]} != 6 )) +then err_exit 'number of elements of x is not 6' +fi +if [[ ${x[2]} != two ]] +then err_exit ' element two is not 2' +fi +if [[ ${x[@]:2:1} != two ]] +then err_exit ' ${x[@]:2:1} is not two' +fi +set -A y -- ${x[*]} +if [[ $y != zero ]] +then err_exit '$x is not element 0' +fi +if [[ ${y[0]} != zero ]] +then err_exit '${y[0] is not element 0' +fi +if (( ${#y[@]} != 7 )) +then err_exit 'number of elements of y is not 7' +fi +if [[ ${y[2]} != two ]] +then err_exit ' element two is not 2' +fi +set +A y nine ten +if [[ ${y[2]} != two ]] +then err_exit ' element two is not 2' +fi +if [[ ${y[0]} != nine ]] +then err_exit '${y[0] is not nine' +fi +unset y[4] +if (( ${#y[@]} != 6 )) +then err_exit 'number of elements of y is not 6' +fi +if (( ${#y[4]} != 0 )) +then err_exit 'string length of unset element is not 0' +fi +unset foo +if (( ${#foo[@]} != 0 )) +then err_exit 'number of elements of unset variable foo is not 0' +fi +foo='' +if (( ${#foo[0]} != 0 )) +then err_exit 'string length of null element is not 0' +fi +if (( ${#foo[@]} != 1 )) +then err_exit 'number of elements of null variable foo is not 1' +fi +unset foo +foo[0]=foo +foo[3]=bar +unset foo[0] +unset foo[3] +if (( ${#foo[@]} != 0 )) +then err_exit 'number of elements of left in variable foo is not 0' +fi +unset foo +foo[3]=bar +foo[0]=foo +unset foo[3] +unset foo[0] +if (( ${#foo[@]} != 0 )) +then err_exit 'number of elements of left in variable foo again is not 0' +fi +fun +if (( ${#xxx[@]} != 2 )) +then err_exit 'number of elements of left in variable xxx is not 2' +fi +fun +if (( ${#xxx[@]} != 2 )) +then err_exit 'number of elements of left in variable xxx again is not 2' +fi +set -A foo -- "${x[@]}" +if (( ${#foo[@]} != 6 )) +then err_exit 'number of elements of foo is not 6' +fi +if (( ${#PWD[@]} != 1 )) +then err_exit 'number of elements of PWD is not 1' +fi +unset x +x[2]=foo x[4]=bar +if (( ${#x[@]} != 2 )) +then err_exit 'number of elements of x is not 2' +fi +s[1]=1 c[1]=foo +if [[ ${c[s[1]]} != foo ]] +then err_exit 'c[1]=foo s[1]=1; ${c[s[1]]} != foo' +fi +unset s +typeset -Ai s +y=* z=[ +s[$y]=1 +s[$z]=2 +if (( ${#s[@]} != 2 )) +then err_exit 'number of elements of is not 2' +fi +(( s[$z] = s[$z] + ${s[$y]} )) +if [[ ${s[$z]} != 3 ]] +then err_exit '[[ ${s[$z]} != 3 ]]' +fi +if (( s[$z] != 3 )) +then err_exit '(( s[$z] != 3 ))' +fi +(( s[$y] = s[$y] + ${s[$z]} )) +if [[ ${s[$y]} != 4 ]] +then err_exit '[[ ${s[$y]} != 4 ]]' +fi +if (( s[$y] != 4 )) +then err_exit '(( s[$y] != 4 ))' +fi +unset y +set -A y 2 4 6 +typeset -i y +z=${y[@]} +typeset -R12 y +typeset -i y +if [[ ${y[@]} != "$z" ]] +then err_exit 'error in array conversion from int to R12' +fi +if (( ${#y[@]} != 3 )) +then err_exit 'error in count of array conversion from int to R12' +fi +unset abcdefg +: ${abcdefg[1]} +set | grep '^abcdefg$' >/dev/null && err_exit 'empty array variable in set list' +unset x y +x=1 +typeset -i y[$x]=4 +if [[ ${y[1]} != 4 ]] +then err_exit 'arithmetic expressions in typeset not working' +fi +unset foo +typeset foo=bar +typeset -A foo +if [[ ${foo[0]} != bar ]] +then err_exit 'initial value not preserved when typecast to associative' +fi +unset foo +foo=(one two) +typeset -A foo +foo[two]=3 +if [[ ${#foo[*]} != 3 ]] +then err_exit 'conversion of indexed to associative array failed' +fi +set a b c d e f g h i j k l m +if [[ ${#} != 13 ]] +then err_exit '${#} not 13' +fi +unset xxx +xxx=foo +if [[ ${!xxx[@]} != 0 ]] +then err_exit '${!xxx[@]} for scalar not 0' +fi +if [[ ${11} != k ]] +then err_exit '${11} not working' +fi +if [[ ${@:4:1} != d ]] +then err_exit '${@:4:1} not working' +fi +foovar1=abc +foovar2=def +if [[ ${!foovar@} != +(foovar[[:alnum:]]?([ ])) ]] +then err_exit '${!foovar@} does not expand correctly' +fi +if [[ ${!foovar1} != foovar1 ]] +then err_exit '${!foovar1} != foovar1' +fi +unset xxx +: ${xxx[3]} +if [[ ${!xxx[@]} ]] +then err_exit '${!xxx[@]} should be null' +fi +integer i=0 +{ + set -x + xxx[++i]=1 + set +x +} 2> /dev/null +if (( i != 1)) +then err_exit 'execution trace side effects with array subscripts' +fi +unset list +: $(set -A list foo bar) +if (( ${#list[@]} != 0)) +then err_exit '$(set -A list ...) leaves side effects' +fi +unset list +list= (foo bar bam) +( set -A list one two three four) +if [[ ${list[1]} != bar ]] +then err_exit 'array not restored after subshell' +fi +XPATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:.:/sbin:/usr/sbin +xpath=( $( IFS=: ; echo $XPATH ) ) +if [[ $(print -r "${xpath[@]##*/}") != 'bin bin ucb bin . sbin sbin' ]] +then err_exit '${xpath[@]##*/} not applied to each element' +fi +foo=( zero one '' three four '' six) +integer n=-1 +if [[ ${foo[@]:n} != six ]] +then err_exit 'array offset of -1 not working' +fi +if [[ ${foo[@]: -3:1} != four ]] +then err_exit 'array offset of -3:1 not working' +fi +$SHELL -c 'x=(if then else fi)' 2> /dev/null || err_exit 'reserved words in x=() assignment not working' +unset foo +foo=one +foo=( $foo two) +if [[ ${#foo[@]} != 2 ]] +then err_exit 'array getting unset before right hand side evaluation' +fi +foo=(143 3643 38732) +export foo +typeset -i foo +if [[ $($SHELL -c 'print $foo') != 143 ]] +then err_exit 'exporting indexed array not exporting 0-th element' +fi +( $SHELL -c ' + unset foo + typeset -A foo=([0]=143 [1]=3643 [2]=38732) + export foo + typeset -i foo + [[ $($SHELL -c "print $foo") == 143 ]]' +) 2> /dev/null || + err_exit 'exporting associative array not exporting 0-th element' +unset foo +typeset -A foo +foo[$((10))]=ok 2> /dev/null || err_exit 'arithmetic expression as subscript not working' +unset foo +typeset -A foo +integer foo=0 +[[ $foo == 0 ]] || err_exit 'zero element of associative array not being set' +unset foo +typeset -A foo=( [two]=1) +for i in one three four five +do : ${foo[$i]} +done +if [[ ${!foo[@]} != two ]] +then err_exit 'Error in subscript names' +fi +unset x +x=( 1 2 3) +(x[1]=8) +[[ ${x[1]} == 2 ]] || err_exit 'index array produce side effects in subshells' +x=( 1 2 3) +( + x+=(8) + [[ ${#x[@]} == 4 ]] || err_exit 'index array append in subshell error' +) +[[ ${#x[@]} == 3 ]] || err_exit 'index array append in subshell effects parent' +x=( [one]=1 [two]=2 [three]=3) +(x[two]=8) +[[ ${x[two]} == 2 ]] || err_exit 'associative array produce side effects in subshells' +unset x +x=( [one]=1 [two]=2 [three]=3) +( + x+=( [four]=4 ) + [[ ${#x[@]} == 4 ]] || err_exit 'associative array append in subshell error' +) +[[ ${#x[@]} == 3 ]] || err_exit 'associative array append in subshell effects parent' +unset x +integer i +for ((i=0; i < 40; i++)) +do x[i]=$i +done +[[ ${#x[@]} == 40 ]] || err_exit 'index arrays loosing values' +[[ $( ($SHELL -c 'typeset -A var; (IFS=: ; set -A var a:b:c ;print ${var[@]});:' )2>/dev/null) == 'a b c' ]] || err_exit 'change associative to index failed' +unset foo +[[ $(foo=good +for ((i=0; i < 2; i++)) +do [[ ${foo[i]} ]] && print ok +done) == ok ]] || err_exit 'invalid optimization for subscripted variables' +( +x=([foo]=bar) +set +A x bam +) 2> /dev/null && err_exit 'set +A with associative array should be an error' +unset bam foo +foo=0 +typeset -A bam +unset bam[foo] +bam[foo]=value +[[ $bam == value ]] && err_exit 'unset associative array element error' +: only first element of an array can be exported +unset bam +trap 'rm -f /tmp/sharr$$' EXIT +print 'print ${var[0]} ${var[1]}' > /tmp/sharr$$ +chmod +x /tmp/sharr$$ +[[ $($SHELL -c "var=(foo bar);export var;/tmp/sharr$$") == foo ]] || err_exit 'export array not exporting just first element' +unset foo +set -o allexport +foo=one +foo[1]=two +foo[0]=three +[[ $foo == three ]] || err_exit 'export all not working with arrays' +cat > /tmp/sharr$$ <<- \! + typeset -A foo + print foo${foo[abc]} +! +# 04-05-24 bug fix +unset foo +[[ $($SHELL -c "typeset -A foo;/tmp/sharr$$") == foo ]] 2> /dev/null || err_exit 'empty associative arrays not being cleared correctly before scripts' +[[ $($SHELL -c "typeset -A foo;foo[abc]=abc;/tmp/sharr$$") == foo ]] 2> /dev/null || err_exit 'associative arrays not being cleared correctly before scripts' +unset foo +foo=(one two) +[[ ${foo[@]:1} == two ]] || err_exit '${foo[@]:1} == two' +[[ ! ${foo[@]:2} ]] || err_exit '${foo[@]:2} not null' +unset foo +foo=one +[[ ! ${foo[@]:1} ]] || err_exit '${foo[@]:1} not null' +function EMPTY +{ + typeset i + typeset -n ARRAY=$1 + for i in ${!ARRAY[@]} + do unset ARRAY[$i] + done +} +unset foo +typeset -A foo +foo[bar]=bam +foo[x]=y +EMPTY foo +[[ $(typeset | grep foo$) == *associative* ]] || err_exit 'array lost associative attribute' +[[ ! ${foo[@]} ]] || err_exit 'array not empty' +[[ ! ${!foo[@]} ]] || err_exit 'array names not empty' +unset foo +foo=bar +set -- "${foo[@]:1}" +(( $# == 0 )) || err_exit '${foo[@]:1} should not have any values' +unset bar +: ${_foo[bar=4]} +(( bar == 4 )) || err_exit 'subscript of unset variable not evaluated' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/attributes.sh b/usr/src/lib/libshell/common/tests/attributes.sh new file mode 100644 index 0000000000..8be16f1d42 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/attributes.sh @@ -0,0 +1,216 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +r=readonly u=Uppercase l=Lowercase i=22 i8=10 L=abc L5=def uL5=abcdef xi=20 +x=export t=tagged H=hostname LZ5=026 RZ5=026 Z5=123 lR5=ABcdef R5=def n=l +for option in u l i i8 L L5 LZ5 RZ5 Z5 r x H t R5 uL5 lR5 xi n +do typeset -$option $option +done +(r=newval) 2> /dev/null && err_exit readonly attribute fails +i=i+5 +if ((i != 27)) +then err_exit integer attributes fails +fi +if [[ $i8 != 8#12 ]] +then err_exit integer base 8 fails +fi +if [[ $u != UPPERCASE ]] +then err_exit uppercase fails +fi +if [[ $l != lowercase ]] +then err_exit lowercase fails +fi +if [[ $n != lowercase ]] +then err_exit reference variables fail +fi +if [[ t=tagged != $(typeset -t) ]] +then err_exit tagged fails +fi +if [[ t != $(typeset +t) ]] +then err_exit tagged fails +fi +if [[ $Z5 != 00123 ]] +then err_exit zerofill fails +fi +if [[ $RZ5 != 00026 ]] +then err_exit right zerofill fails +fi +L=12345 +if [[ $L != 123 ]] +then err_exit leftjust fails +fi +if [[ $L5 != "def " ]] +then err_exit leftjust fails +fi +if [[ $uL5 != ABCDE ]] +then err_exit leftjust uppercase fails +fi +if [[ $lR5 != bcdef ]] +then err_exit rightjust fails +fi +if [[ $R5 != " def" ]] +then err_exit rightjust fails +fi +if [[ $($SHELL -c 'echo $x') != export ]] +then err_exit export fails +fi +if [[ $($SHELL -c 'xi=xi+4;echo $xi') != 24 ]] +then err_exit export attributes fails +fi +x=$(foo=abc $SHELL <<! + foo=bar + $SHELL -c 'print \$foo' +! +) +if [[ $x != bar ]] +then err_exit 'environment variables require re-export' +fi +(typeset + ) > /dev/null 2>&1 || err_exit 'typeset + not working' +(typeset -L-5 buf="A" 2>/dev/null) +if [[ $? == 0 ]] +then err_exit 'typeset allows negative field for left/right adjust' +fi +a=b +readonly $a=foo +if [[ $b != foo ]] +then err_exit 'readonly $a=b not working' +fi +if [[ $(export | grep '^PATH=') != PATH=* ]] +then err_exit 'export not working' +fi +picture=( + bitmap=/fruit + size=(typeset -E x=2.5) +) +string="$(print $picture)" +if [[ "${string}" != *'size=( typeset -E'* ]] +then err_exit 'print of compound exponential variable not working' +fi +sz=(typeset -E y=2.2) +string="$(print $sz)" +if [[ "${sz}" == *'typeset -E -F'* ]] +then err_exit 'print of exponential shows both -E and -F attributes' +fi +print 'typeset -i m=48/4+1;print -- $m' > /tmp/ksh$$ +chmod +x /tmp/ksh$$ +typeset -Z2 m +if [[ $(/tmp/ksh$$) != 13 ]] +then err_exit 'attributes not cleared for script execution' +fi +typeset -Z LAST=00 +unset -f foo +function foo +{ + if [[ $1 ]] + then LAST=$1 + else ((LAST++)) + fi +} +foo 1 +if (( ${#LAST} != 2 )) +then err_exit 'LAST!=2' +fi +foo +if (( ${#LAST} != 2 )) +then err_exit 'LAST!=2' +fi +rm -rf /tmp/ksh$$ +set -a +unset foo +foo=bar +if [[ $(export | grep ^foo=) != 'foo=bar' ]] +then err_exit 'all export not working' +fi +unset foo +read foo <<! +bar +! +if [[ $(export | grep ^foo=) != 'foo=bar' ]] +then err_exit 'all export not working with read' +fi +if [[ $(typeset | grep PS2) == PS2 ]] +then err_exit 'typeset without arguments outputs names without attributes' +fi +unset a z q x +w1=hello +w2=world +t1="$w1 $w2" +if (( 'a' == 97 )) +then b1=aGVsbG8gd29ybGQ= + b2=aGVsbG8gd29ybGRoZWxsbyB3b3JsZA== +else b1=iIWTk5ZAppaZk4Q= + b2=iIWTk5ZAppaZk4SIhZOTlkCmlpmThA== +fi +z=$b1 +typeset -b x=$b1 +[[ $x == "$z" ]] || print -u2 'binary variable not expanding correctly' +[[ $(printf "%B" x) == $t1 ]] || err_exit 'typeset -b not working' +typeset -b -Z5 a=$b1 +[[ $(printf "%B" a) == $w1 ]] || err_exit 'typeset -b -Z5 not working' +typeset -b q=$x$x +[[ $q == $b2 ]] || err_exit 'typeset -b not working with concatination' +[[ $(printf "%B" q) == $t1$t1 ]] || err_exit 'typeset -b concatination not working' +x+=$b1 +[[ $x == $b2 ]] || err_exit 'typeset -b not working with append' +[[ $(printf "%B" x) == $t1$t1 ]] || err_exit 'typeset -b append not working' +typeset -b -Z20 z=$b1 +(( $(printf "%B" z | wc -c) == 20 )) || err_exit 'typeset -b -Z20 not storing 20 bytes' +{ + typeset -b v1 v2 + read -N11 v1 + read -N22 v2 +} << ! +hello worldhello worldhello world +! +[[ $v1 == "$b1" ]] || err_exit "v1=$v1 should be $b1" +[[ $v2 == "$x" ]] || err_exit "v1=$v2 should be $x" +[[ $(env '!=1' $SHELL -c 'echo ok' 2>/dev/null) == ok ]] || err_exit 'malformed environment terminates shell' +unset var +typeset -b var +printf '12%Z34' | read -r -N 5 var +[[ $var == MTIAMzQ= ]] || err_exit 'binary files with zeros not working' +unset var +if command typeset -usi var=0xfffff 2> /dev/null +then (( $var == 0xffff )) || err_exit 'unsigned short integers not working' +else err_exit 'typeset -usi cannot be used for unsigned short' +fi +[[ $($SHELL -c 'unset foo;typeset -Z2 foo; print ${foo:-3}' 2> /dev/null) == 3 ]] || err_exit '${foo:-3} not 3 when typeset -Z2 field undefined' +[[ $($SHELL -c 'unset foo;typeset -Z2 foo; print ${foo:=3}' 2> /dev/null) == 03 ]] || err_exit '${foo:=-3} not 3 when typeset -Z2 foo undefined' +unset foo bar +unset -f fun +function fun +{ + export foo=hello + typeset -x bar=world + [[ $foo == hello ]] || err_exit 'export scoping problem in function' +} +fun +[[ $(export | grep foo) == 'foo=hello' ]] || err_exit 'export not working in functions' +[[ $(export | grep bar) ]] && err_exit 'typeset -x not local' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/basic.sh b/usr/src/lib/libshell/common/tests/basic.sh new file mode 100644 index 0000000000..28755da226 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/basic.sh @@ -0,0 +1,336 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +# test basic file operations like redirection, pipes, file expansion +Command=${0##*/} +integer Errors=0 +umask u=rwx,go=rx || err_exit "umask u=rws,go=rx failed" +if [[ $(umask -S) != u=rwx,g=rx,o=rx ]] +then err_exit 'umask -S incorrect' +fi +mkdir /tmp/ksh$$ || err_exit "mkdir /tmp/ksh$$ failed" +trap "cd /; rm -rf /tmp/ksh$$" EXIT +pwd=$PWD +[[ $SHELL != /* ]] && SHELL=$pwd/$SHELL +cd /tmp/ksh$$ || err_exit "cd /tmp/ksh$$ failed" +# optimizer bug test +> foobar +for i in 1 2 +do print foobar* + rm -f foobar +done > out$$ +if [[ "$(<out$$)" != "foobar"$'\n'"foobar*" ]] +then print -u2 "optimizer bug with file expansion" +fi +rm -f out$$ foobar +mkdir dir +if [[ $(print */) != dir/ ]] +then err_exit 'file expansion with trailing / not working' +fi +if [[ $(print *) != dir ]] +then err_exit 'file expansion with single file not working' +fi +print hi > .foo +if [[ $(print *) != dir ]] +then err_exit 'file expansion leading . not working' +fi +date > dat1 || err_exit "date > dat1 failed" +test -r dat1 || err_exit "dat1 is not readable" +x=dat1 +cat <$x > dat2 || err_exit "cat < $x > dat2 failed" +cat dat1 dat2 | cat | cat | cat > dat3 || err_exit "cat pipe failed" +cat > dat4 <<! +$(date) +! +cat dat1 dat2 | cat | cat | cat > dat5 & +wait $! +set -- dat* +if (( $# != 5 )) +then err_exit "dat* matches only $# files" +fi +if (command > foo\\abc) 2> /dev/null +then set -- foo* + if [[ $1 != 'foo\abc' ]] + then err_exit 'foo* does not match foo\abc' + fi +fi +if ( : > TT* && : > TTfoo ) 2>/dev/null +then set -- TT* + if (( $# < 2 )) + then err_exit 'TT* not expanding when file TT* exists' + fi +fi +cd ~- || err_exit "cd back failed" +cat > /tmp/ksh$$/script <<- ! + #! $SHELL + print -r -- \$0 +! +chmod 755 /tmp/ksh$$/script +if [[ $(/tmp/ksh$$/script) != "/tmp/ksh$$/script" ]] +then err_exit '$0 not correct for #! script' +fi +rm -r /tmp/ksh$$ || err_exit "rm -r /tmp/ksh$$ failed" +bar=foo +eval foo=\$bar +if [[ $foo != foo ]] +then err_exit 'eval foo=\$bar not working' +fi +bar='foo=foo\ bar' +eval $bar +if [[ $foo != 'foo bar' ]] +then err_exit 'eval foo=\$bar, with bar="foo\ bar" not working' +fi +cd /tmp +cd ../../tmp || err_exit "cd ../../tmp failed" +if [[ $PWD != /tmp ]] +then err_exit 'cd ../../tmp is not /tmp' +fi +( sleep 2; cat <<! +foobar +! +) | cat > /tmp/foobar$$ & +wait $! +foobar=$( < /tmp/foobar$$) +if [[ $foobar != foobar ]] +then err_exit "$foobar is not foobar" +fi +{ + print foo + /bin/echo bar + print bam +} > /tmp/foobar$$ +if [[ $( < /tmp/foobar$$) != $'foo\nbar\nbam' ]] +then err_exit "Output file pointer not shared correctly." +fi +cat > /tmp/foobar$$ <<\! + print foo + /bin/echo bar + print bam +! +chmod +x /tmp/foobar$$ +if [[ $(/tmp/foobar$$) != $'foo\nbar\nbam' ]] +then err_exit "Script not working." +fi +if [[ $(/tmp/foobar$$ | /bin/cat) != $'foo\nbar\nbam' ]] +then err_exit "Script | cat not working." +fi +if [[ $( /tmp/foobar$$) != $'foo\nbar\nbam' ]] +then err_exit "Output file pointer not shared correctly." +fi +rm -f /tmp/foobar$$ +x=$( (print foo) ; (print bar) ) +if [[ $x != $'foo\nbar' ]] +then err_exit " ( (print foo);(print bar ) failed" +fi +x=$( (/bin/echo foo) ; (print bar) ) +if [[ $x != $'foo\nbar' ]] +then err_exit " ( (/bin/echo);(print bar ) failed" +fi +x=$( (/bin/echo foo) ; (/bin/echo bar) ) +if [[ $x != $'foo\nbar' ]] +then err_exit " ( (/bin/echo);(/bin/echo bar ) failed" +fi +cat > /tmp/ksh$$ <<\! +if [[ -p /dev/fd/0 ]] +then builtin cat + cat - > /dev/null + [[ -p /dev/fd/0 ]] && print ok +else print no +fi +! +chmod +x /tmp/ksh$$ +case $( (print) | /tmp/ksh$$;:) in +ok) ;; +no) err_exit "[[ -p /dev/fd/0 ]] fails for standard input pipe" ;; +*) err_exit "builtin replaces standard input pipe" ;; +esac +print 'print $0' > /tmp/ksh$$ +print ". /tmp/ksh$$" > /tmp/ksh$$x +chmod +x /tmp/ksh$$x +if [[ $(/tmp/ksh$$x) != /tmp/ksh$$x ]] +then err_exit '$0 not correct for . script' +fi +rm -r /tmp/ksh$$ /tmp/ksh$$x +mkdir /tmp/ksh$$ || err_exit "mkdir /tmp/ksh$$ failed" +cd /tmp/ksh$$ || err_exit "cd /tmp/ksh$$ failed" +print ./b > ./a; print ./c > b; print ./d > c; print ./e > d; print "echo \"hello there\"" > e +chmod 755 a b c d e +x=$(./a) +if [[ $x != "hello there" ]] +then err_exit "nested scripts failed" +fi +x=$( (./a) | cat) +if [[ $x != "hello there" ]] +then err_exit "scripts in subshells fail" +fi +cd ~- || err_exit "cd back failed" +rm -r /tmp/ksh$$ || err_exit "rm -r /tmp/ksh$$ failed" +x=$( (/bin/echo foo) 2> /dev/null ) +if [[ $x != foo ]] +then err_exit "subshell in command substitution fails" +fi +exec 1>&- +x=$(print hello) +if [[ $x != hello ]] +then err_exit "command subsitution with stdout closed failed" +fi +cd $pwd +x=$(cat <<\! | $SHELL +/bin/echo | /bin/cat +/bin/echo hello +! +) +if [[ $x != $'\n'hello ]] +then err_exit "$SHELL not working when standard input is a pipe" +fi +x=$( (/bin/echo hello) 2> /dev/null ) +if [[ $x != hello ]] +then err_exit "subshell in command substitution with 1 closed fails" +fi +cat > /tmp/ksh$$ <<- \! +read line 2> /dev/null +print done +! +if [[ $($SHELL /tmp/ksh$$ <&-) != done ]] +then err_exit "executing script with 0 closed fails" +fi +trap '' INT +cat > /tmp/ksh$$ <<- \! +trap 'print bad' INT +kill -s INT $$ +print good +! +chmod +x /tmp/ksh$$ +if [[ $($SHELL /tmp/ksh$$) != good ]] +then err_exit "traps ignored by parent not ignored" +fi +trap - INT +cat > /tmp/ksh$$ <<- \! +read line +/bin/cat +! +if [[ $($SHELL /tmp/ksh$$ <<! +one +two +! +) != two ]] +then err_exit "standard input not positioned correctly" +fi +word=$(print $'foo\nbar' | { read line; /bin/cat;}) +if [[ $word != bar ]] +then err_exit "pipe to { read line; /bin/cat;} not working" +fi +word=$(print $'foo\nbar' | ( read line; /bin/cat) ) +if [[ $word != bar ]] +then err_exit "pipe to ( read line; /bin/cat) not working" +fi +if [[ $(print x{a,b}y) != 'xay xby' ]] +then err_exit 'brace expansion not working' +fi +if [[ $(for i in foo bar + do ( tgz=$(print $i) + print $tgz) + done) != $'foo\nbar' ]] +then err_exit 'for loop subshell optimizer bug' +fi +unset a1 +optbug() +{ + set -A a1 foo bar bam + integer i + for ((i=0; i < 3; i++)) + do + (( ${#a1[@]} < 2 )) && return 0 + set -- "${a1[@]}" + shift + set -A a1 -- "$@" + done + return 1 +} +optbug || err_exit 'array size optimzation bug' +wait # not running --pipefile which would interfere with subsequent tests +: $(jobs -p) # required to clear jobs for next jobs -p (interactive side effect) +sleep 20 & +if [[ $(jobs -p) != $! ]] +then err_exit 'jobs -p not reporting a background job' +fi +sleep 20 & +foo() +{ + set -- $(jobs -p) + (( $# == 2 )) || err_exit "$# jobs not reported -- 2 expected" +} +foo +[[ $( (trap 'print alarm' ALRM; sleep 4) & sleep 2; kill -ALRM $!) == alarm ]] || err_exit 'ALRM signal not working' +[[ $($SHELL -c 'trap "" HUP; $SHELL -c "(sleep 2;kill -HUP $$)& sleep 4;print done"') != done ]] && err_exit 'ignored traps not being ignored' +[[ $($SHELL -c 'o=foobar; for x in foo bar; do (o=save);print $o;done' 2> /dev/null ) == $'foobar\nfoobar' ]] || err_exit 'for loop optimization subshell bug' +command exec 3<> /dev/null +if cat /dev/fd/3 >/dev/null 2>&1 +then [[ $($SHELL -c 'cat <(print foo)' 2> /dev/null) == foo ]] || err_exit 'process substitution not working' + [[ $($SHELL -c 'print $(cat <(print foo) )' 2> /dev/null) == foo ]] || err_exit 'process substitution in subshell not working' + [[ $($SHELL -c $'tee >(grep \'1$\' > /tmp/ksh'$$'x) > /dev/null <<- \!!! + line0 + line1 + line2 + !!! + wait + cat /tmp/ksh'$$x 2> /dev/null) == line1 ]] || err_exit '>() process substitution fails' + > /tmp/ksh$$x + [[ $($SHELL -c $' + for i in 1 + do tee >(grep \'1$\' > /tmp/ksh'$$'x) > /dev/null <<- \!!! + line0 + line1 + line2 + !!! + done + wait + cat /tmp/ksh'$$x 2>> /dev/null) == line1 ]] || err_exit '>() process substitution fails in for loop' + [[ $({ $SHELL -c 'cat <(for i in x y z; do print $i; done)';} 2> /dev/null) == $'x\ny\nz' ]] || + err_exit 'process substitution of compound commands not working' +fi +[[ $($SHELL -r 'command -p :' 2>&1) == *restricted* ]] || err_exit 'command -p not restricted' +print cat > /tmp/ksh$$x +chmod +x /tmp/ksh$$x +[[ $($SHELL -c "print foo | /tmp/ksh$$x ;:" 2> /dev/null ) == foo ]] || err_exit 'piping into script fails' +[[ $($SHELL -c 'X=1;print -r -- ${X:=$(expr "a(0)" : '"'a*(\([^)]\))')}'" 2> /dev/null) == 1 ]] || err_exit 'x=1;${x:=$(..."...")} failure' +[[ $($SHELL -c 'print -r -- ${X:=$(expr "a(0)" : '"'a*(\([^)]\))')}'" 2> /dev/null) == 0 ]] || err_exit '${x:=$(..."...")} failure' +if [[ -d /dev/fd && -w /dev/fd/3 ]] +then [[ $(cat <(print hello) ) == hello ]] || err_exit "process substitution not working outside for or while loop" + $SHELL -c '[[ $(for i in 1;do cat <(print hello);done ) == hello ]]' 2> /dev/null|| err_exit "process substitution not working in for or while loop" +fi +exec 3> /dev/null +print 'print foo "$@"' > /tmp/ksh$$x +[[ $( print "(/tmp/ksh$$x bar)" | $SHELL 2>/dev/null) == 'foo bar' ]] || err_exit 'script pipe to shell fails' +print "#! $SHELL" > /tmp/ksh$$x +print 'print -- $0' >> /tmp/ksh$$x +chmod +x /tmp/ksh$$x +[[ $(/tmp/ksh$$x) == /tmp/ksh$$x ]] || err_exit "\$0 is $0 instead of /tmp/ksh$$x" +rm -f /tmp/ksh$$x +exec 3<&- +( typeset -r foo=bar) 2> /dev/null || err_exit 'readonly variables set in a subshell cannot unset' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/bracket.sh b/usr/src/lib/libshell/common/tests/bracket.sh new file mode 100644 index 0000000000..25daf72c32 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/bracket.sh @@ -0,0 +1,233 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +null='' +if [[ ! -z $null ]] +then err_exit "-z: null string should be of zero length" +fi +file=/tmp/regress$$ +if [[ -z $file ]] +then err_exit "-z: $file string should not be of zero length" +fi +trap "rm -f $file" EXIT +rm -f $file +if [[ -a $file ]] +then err_exit "-a: $file shouldn't exist" +fi +> $file +if [[ ! -a $file ]] +then err_exit "-a: $file should exist" +fi +chmod 777 $file +if [[ ! -r $file ]] +then err_exit "-r: $file should be readable" +fi +if [[ ! -w $file ]] +then err_exit "-w: $file should be writable" +fi +if [[ ! -w $file ]] +then err_exit "-x: $file should be executable" +fi +if [[ ! -w $file || ! -r $file ]] +then err_exit "-rw: $file should be readable/writable" +fi +if [[ -s $file ]] +then err_exit "-s: $file should be of zero size" +fi +if [[ ! -f $file ]] +then err_exit "-f: $file should be an ordinary file" +fi +if [[ -d $file ]] +then err_exit "-f: $file should not be a directory file" +fi +if [[ ! -d . ]] +then err_exit "-d: . should not be a directory file" +fi +if [[ -f /dev/null ]] +then err_exit "-f: /dev/null should not be an ordinary file" +fi +chmod 000 $file +if [[ -r $file ]] +then err_exit "-r: $file should not be readable" +fi +if [[ ! -O $file ]] +then err_exit "-r: $file should be owned by me" +fi +if [[ -w $file ]] +then err_exit "-w: $file should not be writable" +fi +if [[ -w $file ]] +then err_exit "-x: $file should not be executable" +fi +if [[ -w $file || -r $file ]] +then err_exit "-rw: $file should not be readable/writable" +fi +if [[ -z x && -z x || ! -z x ]] +then : +else err_exit " wrong precedence" +fi +if [[ -z x && (-z x || ! -z x) ]] +then err_exit " () grouping not working" +fi +if [[ foo < bar ]] +then err_exit "foo comes before bar" +fi +[[ . -ef $(pwd) ]] || err_exit ". is not $PWD" +set -o allexport +[[ -o allexport ]] || err_exit '-o: did not set allexport option' +if [[ -n $null ]] +then err_exit "'$null' has non-zero length" +fi +if [[ ! -r /dev/fd/0 ]] +then err_exit "/dev/fd/0 not open for reading" +fi +if [[ ! -w /dev/fd/2 ]] +then err_exit "/dev/fd/2 not open for writing" +fi +sleep 1 +if [[ ! . -ot $file ]] +then err_exit ". should be older than $file" +fi +if [[ /bin -nt $file ]] +then err_exit "$file should be newer than /bin" +fi +if [[ $file != /tmp/* ]] +then err_exit "$file should match /tmp/*" +fi +if [[ $file = '/tmp/*' ]] +then err_exit "$file should not equal /tmp/*" +fi +[[ ! ( ! -z $null && ! -z x) ]] || err_exit "negation and grouping" +[[ -z '' || -z '' || -z '' ]] || err_exit "three ors not working" +[[ -z '' && -z '' && -z '' ]] || err_exit "three ors not working" +(exit 8) +if [[ $? -ne 8 || $? -ne 8 ]] +then err_exit 'value $? within [[...]]' +fi +x='(x' +if [[ '(x' != '('* ]] +then err_exit " '(x' does not match '('* within [[...]]" +fi +if [[ '(x' != "("* ]] +then err_exit ' "(x" does not match "("* within [[...]]' +fi +if [[ '(x' != \(* ]] +then err_exit ' "(x" does not match \(* within [[...]]' +fi +if [[ 'x(' != *'(' ]] +then err_exit " 'x(' does not match '('* within [[...]]" +fi +if [[ 'x&' != *'&' ]] +then err_exit " 'x&' does not match '&'* within [[...]]" +fi +if [[ 'xy' = *'*' ]] +then err_exit " 'xy' matches *'*' within [[...]]" +fi +if [[ 3 > 4 ]] +then err_exit '3 < 4' +fi +if [[ 4 < 3 ]] +then err_exit '3 > 4' +fi +if [[ 3x > 4x ]] +then err_exit '3x < 4x' +fi +x='bin|dev|?' +cd / +if [[ $(print $x) != "$x" ]] +then err_exit 'extended pattern matching on command arguments' +fi +if [[ dev != $x ]] +then err_exit 'extended pattern matching not working on variables' +fi +if [[ -u $SHELL ]] +then err_exit "setuid on $SHELL" +fi +if [[ -g $SHELL ]] +then err_exit "setgid on $SHELL" +fi +test -d . -a '(' ! -f . ')' || err_exit 'test not working' +if [[ '!' != ! ]] +then err_exit 'quoting unary operator not working' +fi +chmod 600 $file +exec 4> $file +print -u4 foobar +if [[ ! -s $file ]] +then err_exit "-s: $file should be non-zero" +fi +exec 4>&- +if [[ 011 -ne 11 ]] +then err_exit "leading zeros in arithmetic compares not ignored" +fi +{ + set -x + [[ foo > bar ]] +} 2> /dev/null || { set +x; err_exit "foo<bar with -x enabled" ;} +set +x +( + eval "[[ (a) ]]" +) 2> /dev/null || err_exit "[[ (a) ]] not working" +> $file +chmod 4755 "$file" +if test -u $file && test ! -u $file +then err_exit "test ! -u suidfile not working" +fi +for i in '(' ')' '[' ']' +do [[ $i == $i ]] || err_exit "[[ $i != $i ]]" +done +( + [[ aaaa == {4}(a) ]] || err_exit 'aaaa != {4}(a)' + [[ aaaa == {2,5}(a) ]] || err_exit 'aaaa != {2,4}(a)' + [[ abcdcdabcd == {3,6}(ab|cd) ]] || err_exit 'abcdcdabcd == {3,4}(ab|cd)' + [[ abcdcdabcde == {5}(ab|cd)e ]] || err_exit 'abcdcdabcd == {5}(ab|cd)e' +) || err_exit 'Errors with {..}(...) patterns' +[[ D290.2003.02.16.temp == D290.+(2003.02.16).temp* ]] || err_exit 'pattern match bug with +(...)' +rm -rf $file +{ +[[ -N $file ]] && err_exit 'test -N /tmp/*: st_mtime>st_atime after creat' +sleep 2 +print 'hello world' +[[ -N $file ]] || err_exit 'test -N /tmp/*: st_mtime<=st_atime after write' +sleep 2 +read +[[ -N $file ]] && err_exit 'test -N /tmp/*: st_mtime>st_atime after read' +} > $file < $file +if rm -rf "$file" && ln -s / "$file" +then [[ -L "$file" ]] || err_exit '-L not working' + [[ -L "$file"/ ]] && err_exit '-L with file/ not working' +fi +$SHELL -c 't=1234567890; [[ $t == @({10}(\d)) ]]' 2> /dev/null || err_exit ' @({10}(\d)) pattern not working' +$SHELL -c '[[ att_ == ~(E)(att|cus)_.* ]]' 2> /dev/null || err_exit ' ~(E)(att|cus)_* pattern not working' +$SHELL -c '[[ att_ =~ (att|cus)_.* ]]' 2> /dev/null || err_exit ' =~ ere not working' +$SHELL -c '[[ abc =~ a(b)c ]]' 2> /dev/null || err_exit '[[ abc =~ a(b)c ]] fails' +$SHELL -xc '[[ abc =~ \babc\b ]]' 2> /dev/null || err_exit '[[ abc =~ \babc\b ]] fails' +[[ abc == ~(E)\babc\b ]] || err_exit '\b not preserved for ere when not in ()' +[[ abc == ~(iEi)\babc\b ]] || err_exit '\b not preserved for ~(iEi) when not in ()' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/builtins.sh b/usr/src/lib/libshell/common/tests/builtins.sh new file mode 100644 index 0000000000..1d2dfe9157 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/builtins.sh @@ -0,0 +1,451 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +# test shell builtin commands +Command=${0##*/} +integer Errors=0 +builtin getconf +: ${foo=bar} || err_exit ": failed" +[[ $foo = bar ]] || err_exit ": side effects failed" +set -- - foobar +[[ $# = 2 && $1 = - && $2 = foobar ]] || err_exit "set -- - foobar failed" +set -- -x foobar +[[ $# = 2 && $1 = -x && $2 = foobar ]] || err_exit "set -- -x foobar failed" +getopts :x: foo || err_exit "getopts :x: returns false" +[[ $foo = x && $OPTARG = foobar ]] || err_exit "getopts :x: failed" +OPTIND=1 +getopts :r:s var -r +if [[ $var != : || $OPTARG != r ]] +then err_exit "'getopts :r:s var -r' not working" +fi +OPTIND=1 +getopts :d#u var -d 100 +if [[ $var != d || $OPTARG != 100 ]] +then err_exit "'getopts :d#u var -d 100' not working var=$var" +fi +OPTIND=1 +while getopts 'ab' option -a -b +do [[ $OPTIND == $((OPTIND)) ]] || err_exit "OPTIND optimization bug" +done + +USAGE=$'[-][S:server?Operate on the specified \asubservice\a:]:[subservice:=pmserver] + { + [p:pmserver] + [r:repserver] + [11:notifyd] + }' +set pmser p rep r notifyd -11 +while (( $# > 1 )) +do OPTIND=1 + getopts "$USAGE" OPT -S $1 + [[ $OPT == S && $OPTARG == $2 ]] || err_exit "OPT=$OPT OPTARG=$OPTARG -- expected OPT=S OPTARG=$2" + shift 2 +done + +false ${foo=bar} && err_exit "false failed" +read <<! +hello world +! +[[ $REPLY = 'hello world' ]] || err_exit "read builtin failed" +print x:y | IFS=: read a b +if [[ $a != x ]] +then err_exit "IFS=: read ... not working" +fi +read <<! +hello \ +world +! +[[ $REPLY = 'hello world' ]] || err_exit "read continuation failed" +read -d x <<! +hello worldxfoobar +! +[[ $REPLY = 'hello world' ]] || err_exit "read builtin failed" +read <<\! +hello \ + world \ + +! +[[ $REPLY == 'hello world' ]] || err_exit "read continuation2 failed" +print "one\ntwo" | { read line + print $line | /bin/cat > /dev/null + read line +} +read <<\! +\ +a\ +\ +\ +b +! +if [[ $REPLY != ab ]] +then err_exit "read multiple continuation failed" +fi +if [[ $line != two ]] +then err_exit "read from pipeline failed" +fi +line=two +read line < /dev/null +if [[ $line != "" ]] +then err_exit "read from /dev/null failed" +fi +if [[ $(print -R -) != - ]] +then err_exit "print -R not working correctly" +fi +if [[ $(print -- -) != - ]] +then err_exit "print -- not working correctly" +fi +print -f "hello%nbar\n" size > /dev/null +if (( size != 5 )) +then err_exit "%n format of printf not working" +fi +print -n -u2 2>&1- +[[ -w /dev/fd/1 ]] || err_exit "2<&1- with built-ins has side effects" +x=$0 +if [[ $(eval 'print $0') != $x ]] +then err_exit '$0 not correct for eval' +fi +unset x +readonly x +set -- $(readonly) +if [[ " $@ " != *" x "* ]] +then err_exit 'unset readonly variables are not displayed' +fi +if [[ $( for i in foo bar + do print $i + continue 10 + done + ) != $'foo\nbar' ]] +then err_exit 'continue breaks out of loop' +fi +(continue bad 2>/dev/null && err_exit 'continue bad should return an error') +(break bad 2>/dev/null && err_exit 'break bad should return an error') +(continue 0 2>/dev/null && err_exit 'continue 0 should return an error') +(break 0 2>/dev/null && err_exit 'break 0 should return an error') +breakfun() { break;} +continuefun() { continue;} +for fun in break continue +do if [[ $( for i in foo + do ${fun}fun + print $i + done + ) != foo ]] + then err_exit "$fun call in ${fun}fun breaks out of for loop" + fi +done +if [[ $(print -f "%b" "\a\n\v\b\r\f\E\03\\oo") != $'\a\n\v\b\r\f\E\03\\oo' ]] +then err_exit 'print -f "%b" not working' +fi +if [[ $(print -f "%P" "[^x].*b$") != '*[!x]*b' ]] +then err_exit 'print -f "%P" not working' +fi +if [[ $(abc: for i in foo bar;do print $i;break abc;done) != foo ]] +then err_exit 'break labels not working' +fi +if [[ $(command -v if) != if ]] +then err_exit 'command -v not working' +fi +read -r var <<\! + +! +if [[ $var != "" ]] +then err_exit "read -r of blank line not working" +fi +mkdir -p /tmp/ksh$$/a/b/c 2>/dev/null || err_exit "mkdir -p failed" +$SHELL -c "cd /tmp/ksh$$/a/b; cd c" 2>/dev/null || err_exit "initial script relative cd fails" +rm -r /tmp/ksh$$ || err_exit "rm -r /tmp/ksh$$ failed" +trap 'print HUP' HUP +if [[ $(trap) != "trap -- 'print HUP' HUP" ]] +then err_exit '$(trap) not working' +fi +if [[ $(trap -p HUP) != 'print HUP' ]] +then err_exit '$(trap -p HUP) not working' +fi +[[ $($SHELL -c 'trap "print ok" SIGTERM; kill -s SIGTERM $$' 2> /dev/null) == ok + ]] || err_exit 'SIGTERM not recognized' +[[ $($SHELL -c 'trap "print ok" sigterm; kill -s sigterm $$' 2> /dev/null) == ok + ]] || err_exit 'SIGTERM not recognized' +${SHELL} -c 'kill -1 -$$' 2> /dev/null +[[ $(kill -l $?) == HUP ]] || err_exit 'kill -1 -pid not working' +${SHELL} -c 'kill -1 -$$' 2> /dev/null +[[ $(kill -l $?) == HUP ]] || err_exit 'kill -n1 -pid not working' +${SHELL} -c 'kill -s HUP -$$' 2> /dev/null +[[ $(kill -l $?) == HUP ]] || err_exit 'kill -HUP -pid not working' +n=123 +typeset -A base +base[o]=8# +base[x]=16# +base[X]=16# +for i in d i o u x X +do if (( $(( ${base[$i]}$(printf "%$i" $n) )) != n )) + then err_exit "printf %$i not working" + fi +done +if [[ $( trap 'print done' EXIT) != done ]] +then err_exit 'trap on EXIT not working' +fi +if [[ $( trap 'print done' EXIT; trap - EXIT) == done ]] +then err_exit 'trap on EXIT not being cleared' +fi +if [[ $(type test) != 'test is a shell builtin' ]] +then err_exit 'whence -v test not a builtin' +fi +builtin -d test +if [[ $(type test) == *builtin* ]] +then err_exit 'whence -v test after builtin -d incorrect' +fi +typeset -Z3 percent=$(printf '%o\n' "'%'") +forrmat=\\${percent}s +if [[ $(printf "$forrmat") != %s ]] +then err_exit "printf $forrmat not working" +fi +if (( $(printf 'x\0y' | wc -c) != 3 )) +then err_exit 'printf \0 not working' +fi +if [[ $(printf "%bx%s\n" 'f\to\cbar') != $'f\to' ]] +then err_exit 'printf %bx%s\n not working' +fi +alpha=abcdefghijklmnop +if [[ $(printf "%10.*s\n" 5 $alpha) != ' abcde' ]] +then err_exit 'printf %10.%s\n not working' +fi +float x2=.0000625 +if [[ $(printf "%10.5E\n" x2) != 6.25000E-05 ]] +then err_exit 'printf "%10.5E" not normalizing correctly' +fi +x2=.000000001 +if [[ $(printf "%g\n" x2 2>/dev/null) != 1e-09 ]] +then err_exit 'printf "%g" not working correctly' +fi +#FIXME#($SHELL read -s foobar <<\! +#FIXME#testing +#FIXME#! +#FIXME#) 2> /dev/null || err_exit ksh read -s var fails +if [[ $(printf +3 2>/dev/null) != +3 ]] +then err_exit 'printf is not processing formats beginning with + correctly' +fi +if printf "%d %d\n" 123bad 78 >/dev/null 2>/dev/null +then err_exit "printf not exiting non-zero with conversion errors" +fi +if [[ $(trap --version 2> /dev/null;print done) != done ]] +then err_exit 'trap builtin terminating after --version' +fi +if [[ $(set --version 2> /dev/null;print done) != done ]] +then err_exit 'set builtin terminating after --veresion' +fi +unset -f foobar +function foobar +{ + print 'hello world' +} +OPTIND=1 +if [[ $(getopts $'[+?X\ffoobar\fX]' v --man 2>&1) != *'Xhello world'X* ]] +then err_exit '\f...\f not working in getopts usage strings' +fi +if [[ $(printf '%H\n' $'<>"& \'\tabc') != '<>"& '	abc' ]] +then err_exit 'printf %H not working' +fi +if [[ $(printf '%R %R %R %R\n' 'a.b' '*.c' '^' '!(*.*)') != '^a\.b$ \.c$ ^\^$ ^(.*\..*)!$' ]] +then err_exit 'printf %R not working' +fi +if [[ $(printf '%..:c\n' abc) != a:b:c ]] +then err_exit "printf '%..:c' not working" +fi +if [[ $(printf '%..*c\n' : abc) != a:b:c ]] +then err_exit "printf '%..*c' not working" +fi +if [[ $(printf '%..:s\n' abc def ) != abc:def ]] +then err_exit "printf '%..:s' not working" +fi +if [[ $(printf '%..*s\n' : abc def) != abc:def ]] +then err_exit "printf '%..*s' not working" +fi +[[ $(printf '%q\n') == '' ]] || err_exit 'printf "%q" with missing arguments' +# we won't get hit by the one second boundary twice, right? +[[ $(printf '%T\n' now) == "$(date)" ]] || +[[ $(printf '%T\n' now) == "$(date)" ]] || +err_exit 'printf "%T" now' +behead() +{ + read line + left=$(cat) +} +print $'line1\nline2' | behead +if [[ $left != line2 ]] +then err_exit "read reading ahead on a pipe" +fi +read -n1 y <<! +abc +! +if [[ $y != a ]] +then err_exit 'read -n1 not working' +fi +print -n $'{ read -r line;print $line;}\nhello' > /tmp/ksh$$ +chmod 755 /tmp/ksh$$ +trap 'rm -rf /tmp/ksh$$' EXIT +if [[ $($SHELL < /tmp/ksh$$) != hello ]] +then err_exit 'read of incomplete line not working correctly' +fi +set -f +set -- * +if [[ $1 != '*' ]] +then err_exit 'set -f not working' +fi +unset pid1 pid2 +false & +pid1=$! +pid2=$( + wait $pid1 + (( $? == 127 )) || err_exit "job known to subshell" + print $! +) +wait $pid1 +(( $? == 1 )) || err_exit "wait not saving exit value" +wait $pid2 +(( $? == 127 )) || err_exit "subshell job known to parent" +set --noglob +ifs=$IFS +IFS=, +set -- $(getconf LIBPATH) +IFS=$ifs +env= +for v +do IFS=: + set -- $v + IFS=$ifs + eval [[ \$$2 ]] && env="$env $2=\"\$$2\"" +done +set --glob +if [[ $(foo=bar; eval foo=\$foo $env exec -c \$SHELL -c \'print \$foo\') != bar ]] +then err_exit '"name=value exec -c ..." not working' +fi +$SHELL -c 'OPTIND=-1000000; getopts a opt -a' 2> /dev/null +[[ $? == 1 ]] || err_exit 'getopts with negative OPTIND not working' +getopts 'n#num' opt -n 3 +[[ $OPTARG == 3 ]] || err_exit 'getopts with numerical arguments failed' +if [[ $($SHELL -c $'printf \'%2$s %1$s\n\' world hello') != 'hello world' ]] +then err_exit 'printf %2$s %1$s not working' +fi +((n=0)) +((n++)); ARGC[$n]=1 ARGV[$n]="" +((n++)); ARGC[$n]=2 ARGV[$n]="-a" +((n++)); ARGC[$n]=4 ARGV[$n]="-a -v 2" +((n++)); ARGC[$n]=4 ARGV[$n]="-a -v 2 x" +((n++)); ARGC[$n]=4 ARGV[$n]="-a -v 2 x y" +for ((i=1; i<=n; i++)) +do set -- ${ARGV[$i]} + OPTIND=0 + while getopts -a tst "av:" OPT + do : + done + if [[ $OPTIND != ${ARGC[$i]} ]] + then err_exit "\$OPTIND after getopts loop incorrect -- got $OPTIND, expected ${ARGC[$i]}" + fi +done +unset a +{ read -N3 a; read -N1 b;} <<! +abcdefg +! +[[ $a == abc ]] || err_exit 'read -N3 here-document not working' +[[ $b == d ]] || err_exit 'read -N1 here-document not working' +read -n3 a <<! +abcdefg +! +[[ $a == abc ]] || err_exit 'read -n3 here-document not working' +(print -n a;sleep 1; print -n bcde) | { read -N3 a; read -N1 b;} +[[ $a == abc ]] || err_exit 'read -N3 from pipe not working' +[[ $b == d ]] || err_exit 'read -N1 from pipe not working' +(print -n a;sleep 1; print -n bcde) |read -n3 a +[[ $a == a ]] || err_exit 'read -n3 from pipe not working' +rm -f /tmp/fifo$$ +if mkfifo /tmp/fifo$$ 2> /dev/null +then (print -n a; sleep 1;print -n bcde) > /tmp/fifo$$ & + { + read -u5 -n3 -t2 a || err_exit 'read -n3 from fifo timedout' + read -u5 -n1 -t2 b || err_exit 'read -n1 from fifo timedout' + } 5< /tmp/fifo$$ + [[ $a == a ]] || err_exit 'read -n3 from fifo not working' + rm -f /tmp/fifo$$ + mkfifo /tmp/fifo$$ 2> /dev/null + (print -n a; sleep 1;print -n bcde) > /tmp/fifo$$ & + { + read -u5 -N3 -t2 a || err_exit 'read -N3 from fifo timed out' + read -u5 -N1 -t2 b || err_exit 'read -N1 from fifo timedout' + } 5< /tmp/fifo$$ + [[ $a == abc ]] || err_exit 'read -N3 from fifo not working' + [[ $b == d ]] || err_exit 'read -N1 from fifo not working' +fi +rm -f /tmp/fifo$$ +function longline +{ + integer i + for((i=0; i < $1; i++)) + do print argument$i + done +} +# test command -x option +integer sum=0 n=10000 +if ! ${SHELL:-ksh} -c 'print $#' count $(longline $n) > /dev/null 2>&1 +then for i in $(command command -x ${SHELL:-ksh} -c 'print $#;[[ $1 != argument0 ]]' count $(longline $n) 2> /dev/null) + do ((sum += $i)) + done + (( sum == n )) || err_exit "command -x processed only $sum arguments" + command -p command -x ${SHELL:-ksh} -c 'print $#;[[ $1 == argument0 ]]' count $(longline $n) > /dev/null 2>&1 + [[ $? != 1 ]] && err_exit 'incorrect exit status for command -x' +fi +# test command -x option with extra arguments +integer sum=0 n=10000 +if ! ${SHELL:-ksh} -c 'print $#' count $(longline $n) > /dev/null 2>&1 +then for i in $(command command -x ${SHELL:-ksh} -c 'print $#;[[ $1 != argument0 ]]' count $(longline $n) one two three) #2> /dev/null) + do ((sum += $i)) + done + (( sum > n )) || err_exit "command -x processed only $sum arguments" + (( (sum-n)%3==0 )) || err_exit "command -x processed only $sum arguments" + (( sum == n+3)) && err_exit "command -x processed only $sum arguments" + command -p command -x ${SHELL:-ksh} -c 'print $#;[[ $1 == argument0 ]]' count $(longline $n) > /dev/null 2>&1 + [[ $? != 1 ]] && err_exit 'incorrect exit status for command -x' +fi +# test for debug trap +[[ $(typeset -i i=0 + trap 'print $i' DEBUG + while (( i <2)) + do (( i++)) + done) == $'0\n0\n1\n1\n2' ]] || err_exit "DEBUG trap not working" +getconf UNIVERSE - ucb +[[ $($SHELL -c 'echo -3') == -3 ]] || err_exit "echo -3 not working in ucb universe" +typeset -F3 start_x=SECONDS total_t delay=0.02 +typeset reps=50 leeway=5 +sleep $(( 2 * leeway * reps * delay )) | +for (( i=0 ; i < reps ; i++ )) +do read -N1 -t $delay +done +(( total_t = SECONDS - start_x )) +if (( total_t > leeway * reps * delay )) +then err_exit "read -t in pipe taking $total_t secs - $(( reps * delay )) minimum - too long" +elif (( total_t < reps * delay )) +then err_exit "read -t in pipe taking $total_t secs - $(( reps * delay )) minimum - too fast" +fi +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/case.sh b/usr/src/lib/libshell/common/tests/case.sh new file mode 100644 index 0000000000..4090d30103 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/case.sh @@ -0,0 +1,81 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 + +bar=foo2 +bam=foo[3] +for i in foo1 foo2 foo3 foo4 foo5 foo6 +do foo=0 + case $i in + foo1) foo=1;; + $bar) foo=2;; + $bam) foo=3;; + foo[4]) foo=4;; + ${bar%?}5) + foo=5;; + "${bar%?}6") + foo=6;; + esac + if [[ $i != foo$foo ]] + then err_exit "$i not matching correct pattern" + fi +done +f="[ksh92]" +case $f in +\[*\]) ;; +*) err_exit "$f does not match \[*\]";; +esac + +if [[ $($SHELL -c ' + x=$(case abc { + abc) { print yes;};; + *) print no;; + } + ) + print -r -- "$x"' 2> /dev/null) != yes ]] +then err_exit 'case abc {...} not working' +fi +[[ $($SHELL -c 'case a in +a) print -n a > /dev/null ;& +b) print b;; +esac') != b ]] && err_exit 'bug in ;& at end of script' +[[ $(VMDEBUG=1 $SHELL -c ' + tmp=foo + for i in a b + do case $i in + a) : tmp=$tmp tmp.h=$tmp.h;; + b) ( tmp=bar ) + for j in a + do print -r -- $tmp.h + done + ;; + esac + done +') == foo.h ]] || err_exit "optimizer bug" +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/comvar.sh b/usr/src/lib/libshell/common/tests/comvar.sh new file mode 100644 index 0000000000..5dc5c59a1c --- /dev/null +++ b/usr/src/lib/libshell/common/tests/comvar.sh @@ -0,0 +1,197 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +#test for compound variables +Command=${0##*/} +integer Errors=0 +Point=( + float x=1. y=0. +) +eval p="$Point" +if (( (p.x*p.x + p.y*p.y) > 1.01 )) +then err_exit 'compound variable not working' +fi +nameref foo=p +if [[ ${foo.x} != ${Point.x} ]] +then err_exit 'reference to compound object not working' +fi +unset foo +rec=( + name='Joe Blow' + born=( + month=jan + integer day=16 + year=1980 + ) +) +eval newrec="$rec" +if [[ ${newrec.name} != "${rec.name}" ]] +then err_exit 'copying a compound object not working' +fi +if (( newrec.born.day != 16 )) +then err_exit 'copying integer field of compound object not working' +fi +p_t=( + integer z=0 + typeset -A tokens +) +unset x +typeset -A x +x=( [foo]=bar ) +if [[ ${x[@]} != bar ]] +then err_exit 'compound assignemnt of associative arrays not working' +fi +unset -n foo x +unset foo x +foo=( x=3) +nameref x=foo +if [[ ${!x.@} != foo.x ]] +then err_exit 'name references not expanded on prefix matching' +fi +unset x +( + x=() + x.foo.bar=7 + [[ ${x.foo.bar} == 7 ]] || err_exit '[[ ${x.foo.bar} != 7 ]]' + (( x.foo.bar == 7 ))|| err_exit '(( x.foo.bar != 7 ))' + [[ ${x.foo} == *bar=7* ]] || err_exit '[[ ${x.foo} != *bar=7* ]]' +) +foo=(integer x=3) +if [[ ${foo} != *x=3* ]] +then err_exit "compound variable with integer subvariable not working" +fi +$SHELL -c $'x=(foo=bar)\n[[ x == x ]]' 2> /dev/null || + err_exit '[[ ... ]] not working after compound assignment' +unset foo +[[ ${!foo.@} ]] && err_exit 'unset compound variable leaves subvariables' +suitable=( + label="Table Viewer" + langs="ksh" + uselang=ksh + launch=no + groups="default" + default=( + label="Table Viewer Preferences" + entrylist=" \ + vieworigin viewsize viewcolor viewfontname viewfontsize \ + showheader header showfooter footer showtitle title showlegends \ + class_td_lg1_style class_tr_tr1_style \ + class_th_th1_style class_td_td1_style \ + fields fieldorder \ + " + entries=( + vieworigin=( + type=coord var=vieworigin val="0 0" label="Window Position" + ) + viewsize=( + type=coord var=viewsize val="400 400" label="Window Size" + ) + viewcolor=( + type=2colors var=viewcolor val="gray black" + label="Window Colors" + ) + viewfontname=( + type=fontname var=viewfontname val="Times-Roman" + label="Window Font Name" + ) + viewfontsize=( + type=fontsize var=viewfontsize val=14 label="Window Font Size" + ) + + showheader=( + type=yesno var=showheader val=no label="Show Header" + ) + header=( + type=text var=header val="" label="Header" + ) + + showfooter=( + type=yesno var=showfooter val=no label="Show Footer" + ) + footer=( + type=text var=footer val="" label="Footer" + ) + + showtitle=( + type=yesno var=showtitle val=yes label="Show Title" + ) + title=( + type=text var=title val="SWIFTUI - Table View" label="Title" + ) + + showlegends=( + type=yesno var=showlegends val=yes label="Show Legends" + ) + + class_td_lg1_style=( + type=style var=class_td_lg1_style + val="color: black; font-family: Times-Roman; font-size: 14pt" + label="Legend 1 Style" + ) + + class_tr_tr1_style=( + type=style var=class_tr_tr1_style val="background: black" + label="Table Row 1 Style" + ) + + class_th_th1_style=( + type=style var=class_th_th1_style + val="color: black; font-family: Times-Roman; font-size: 14pt; text-align: left" + label="Table Header 1 Style" + ) + + class_td_td1_style=( + type=style var=class_td_td1_style + val="color: black; font-family: Times-Roman; font-size: 14pt; text-align: left" + label="Table Cell 1 Style" + ) + + fields=( + type=text var=fields val= label="List of Fields" + ) + fieldorder=( + type=text var=fieldorder val= label="Order of Fields" + ) + ) + ) +) +[[ "${suitable}" == *entrylist=* ]] || err_exit 'compound variable expansion omitting fields' +foo=( bar=foo barbar=bar) +[[ $foo == *bar=foo* ]] || err_exit 'no prefix elements in compound variable output' +function localvar +{ + typeset point=(typeset -i x=3 y=4) + (( (point.x*point.x + point.y*point.y) == 25 )) || err_exit "local compound variable not working" +} +point=(integer x=6 y=8) +localvar + (( (point.x*point.x + point.y*point.y) == 100 )) || err_exit "global compound variable not preserved" +[[ $($SHELL -c 'foo=();foo.[x]=(y z); print ${foo.x[@]}') == 'y z' ]] 2> /dev/null || err_exit 'foo=( [x]=(y z) not working' +unset z +( [[ ${z.foo.bar:-abc} == abc ]] 2> /dev/null) || err_exit ':- not working with compound variables' +exit $((Errors)) + diff --git a/usr/src/lib/libshell/common/tests/coprocess.sh b/usr/src/lib/libshell/common/tests/coprocess.sh new file mode 100644 index 0000000000..5bbbdf759a --- /dev/null +++ b/usr/src/lib/libshell/common/tests/coprocess.sh @@ -0,0 +1,218 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +# test the behavior of co-processes +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 + +if [[ -d /cygdrive ]] +then err_exit cygwin detected - coprocess tests disabled - enable at the risk of wedging your system + exit $((Errors)) +fi + +function ping # id +{ + integer x=0 + while ((x < 5)) + do read -r + print -r "$1 $REPLY" + done +} + +cat |& +print -p "hello" +read -p line +[[ $line == hello ]] || err_exit 'coprocessing fails' +exec 5>&p 6<&p +print -u5 'hello again' || err_exit 'write on u5 fails' +read -u6 line +[[ $line == 'hello again' ]] || err_exit 'coprocess after moving fds fails' +exec 5<&- 6<&- + +ping three |& +exec 3>&p +ping four |& +exec 4>&p +ping pipe |& + +integer count +for i in three four pipe four pipe four three pipe pipe three pipe +do case $i in + three) to=-u3;; + four) to=-u4;; + pipe) to=-p;; + esac + count=count+1 + print $to $i $count +done + +while ((count > 0)) +do count=count-1 + read -p +# print -r - "$REPLY" + set -- $REPLY + if [[ $1 != $2 ]] + then err_exit "$1 does not match 2" + fi + case $1 in + three);; + four) ;; + pipe) ;; + *) err_exit "unknown message +|$REPLY|+" + esac +done + +file=/tmp/regress$$ +trap "rm -f $file" EXIT +cat > $file <<\! +/bin/cat |& +! +chmod +x $file +$file 2> /dev/null || err_exit "parent coprocess prevents script coprocess" +exec 5<&p 6>&p +exec 5<&- 6>&- +${SHELL-ksh} |& +print -p $'print hello | cat\nprint Done' +read -t 5 -p +read -t 5 -p +if [[ $REPLY != Done ]] +then err_exit "${SHELL-ksh} coprocess not working" +fi +exec 5<&p 6>&p +exec 5<&- 6>&- +count=0 +{ +echo line1 | grep 'line2' +echo line2 | grep 'line1' +} |& +SECONDS=0 +while + read -p -t 10 line +do + ((count = count + 1)) + echo "Line $count: $line" +done +if (( SECONDS > 8 )) +then err_exit 'read -p hanging' +fi +( sleep 3 |& sleep 1 && kill $!; sleep 1; sleep 3 |& sleep 1 && kill $! ) || + err_exit "coprocess cleanup not working correctly" +unset line +( + integer n=0 + while read line + do echo $line |& + if cat <&p + then ((n++)) + wait $! + fi + done > /dev/null 2>&1 <<- ! + line1 + line2 + line3 + line4 + line5 + line6 + line7 + ! + (( n==7 )) && print ok +) | read -t 10 line +if [[ $line != ok ]] +then err_exit 'coprocess timing bug' +fi +( + /bin/cat |& + exec 6>&p + print -u6 ok + exec 6>&- + sleep 1 + kill $! 2> /dev/null +) && err_exit 'coprocess with subshell would hang' +for sig in IOT ABRT +do if ( trap - $sig ) 2> /dev/null + then if [[ $( + cat |& + pid=$! + trap "print TRAP" $sig + ( + sleep 2 + kill -$sig $$ + sleep 2 + kill -$sig $$ + kill $pid + ) 2> /dev/null & + read -p + ) != $'TRAP\nTRAP' ]] + then err_exit 'traps when reading from coprocess not working' + fi + break + fi +done + +trap 'sleep_pid=; kill $pid; err_exit "coprocess 1 hung"' TERM +{ sleep 5; kill $$; } & +sleep_pid=$! +builtin cat +cat |& +pid=$! +exec 5<&p 6>&p +print -u6 hi; read -u5 +[[ $REPLY == hi ]] || err_exit 'REPLY is $REPLY not hi' +exec 6>&- +wait $pid +trap - TERM +[[ $sleep_pid ]] && kill $sleep_pid + +trap 'sleep_pid=; kill $pid; err_exit "coprocess 2 hung"' TERM +{ sleep 5; kill $$; } & +sleep_pid=$! +cat |& +pid=$! +print foo >&p 2> /dev/null || err_exit 'first write of foo to coprocess failed' +print foo >&p 2> /dev/null || err_exit 'second write of foo to coprocess failed' +kill $pid +wait $pid 2> /dev/null +trap - TERM +[[ $sleep_pid ]] && kill $sleep_pid + +trap 'sleep_pid=; kill $pid; err_exit "coprocess 3 hung"' TERM +{ sleep 5; kill $$; } & +sleep_pid=$! +cat |& +pid=$! +print -p foo +print -p bar +read <&p || err_exit 'first read from coprocess failed' +[[ $REPLY == foo ]] || err_exit "first REPLY is $REPLY not foo" +read <&p || err_exit 'second read from coprocess failed' +[[ $REPLY == bar ]] || err_exit "second REPLY is $REPLY not bar" +kill $pid +wait $pid 2> /dev/null +trap - TERM +[[ $sleep_pid ]] && kill $sleep_pid + +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/exit.sh b/usr/src/lib/libshell/common/tests/exit.sh new file mode 100644 index 0000000000..b099ccfecc --- /dev/null +++ b/usr/src/lib/libshell/common/tests/exit.sh @@ -0,0 +1,81 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +function abspath +{ + base=$(basename $SHELL) + cd ${SHELL%/$base} + newdir=$(pwd) + cd ~- + print $newdir/$base +} +#test for proper exit of shell +Command=${0##*/} +integer Errors=0 +builtin getconf +ABSHELL=$(abspath) +mkdir /tmp/ksh$$ || err_exit "mkdir /tmp/ksh$$ failed" +cd /tmp/ksh$$ || err_exit "cd /tmp/ksh$$ failed" +print exit 0 >.profile +${ABSHELL} <<! +HOME=$PWD \ +PATH=$PATH \ +SHELL=$ABSSHELL \ +$( + set --noglob + ifs=$IFS + IFS=, + set -- $(getconf LIBPATH) + IFS=$ifs + for v + do IFS=: + set -- $v + IFS=$ifs + eval [[ \$$2 ]] && eval print -n \" \"\$2=\"\$$2\" + done +) \ +exec -c -a -ksh ${ABSHELL} -c "exit 1" 1>/dev/null 2>&1 +! +status=$(echo $?) +if [[ -o noprivileged && $status != 0 ]] +then err_exit 'exit in .profile is ignored' +elif [[ -o privileged && $status == 0 ]] +then err_exit 'privileged .profile not ignored' +fi +if [[ $(trap 'code=$?; echo $code; trap 0; exit $code' 0; exit 123) != 123 ]] +then err_exit 'exit not setting $?' +fi +cat > run.sh <<- "EOF" + trap 'code=$?; echo $code; trap 0; exit $code' 0 + ( trap 0; exit 123 ) +EOF +if [[ $($SHELL ./run.sh) != 123 ]] +then err_exit 'subshell trap on exit overwrites parent trap' +fi +cd ~- || err_exit "cd back failed" +rm -r /tmp/ksh$$ || err_exit "rm -r /tmp/ksh$$ failed" +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/expand.sh b/usr/src/lib/libshell/common/tests/expand.sh new file mode 100644 index 0000000000..70018b1feb --- /dev/null +++ b/usr/src/lib/libshell/common/tests/expand.sh @@ -0,0 +1,123 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$Line]: "$@" + ((Errors++)) +} + +integer Errors=0 +Command=${0##*/} + +# {...} expansion tests -- ignore if not supported + +[[ $(print a{0,1}z) == "a0z a1z" ]] || exit 0 + +integer Line=$LINENO+1 +set -- \ + 'ff{c,b,a}' 'ffc ffb ffa' \ + 'f{d,e,f}g' 'fdg feg ffg' \ + '{l,n,m}xyz' 'lxyz nxyz mxyz' \ + '{abc\,def}' '{abc,def}' \ + '{abc}' '{abc}' \ + '\{a,b,c,d,e}' '{a,b,c,d,e}' \ + '{x,y,\{a,b,c}}' 'x} y} {a} b} c}' \ + '{x\,y,\{abc\},trie}' 'x,y {abc} trie' \ + '/usr/{ucb/{ex,edit},lib/{ex,how_ex}}' '/usr/ucb/ex /usr/ucb/edit /usr/lib/ex /usr/lib/how_ex' \ + 'XXXX\{a,b,c\}' 'XXXX{a,b,c}' \ + '{}' '{}' \ + '{ }' '{ }' \ + '}' '}' \ + '{' '{' \ + 'abcd{efgh' 'abcd{efgh' \ + 'foo {1,2} bar' 'foo 1 2 bar' \ + '`print -r -- foo {1,2} bar`' 'foo 1 2 bar' \ + '$(print -r -- foo {1,2} bar)' 'foo 1 2 bar' \ + '{1..10}' '1 2 3 4 5 6 7 8 9 10' \ + '{0..10,braces}' '0..10 braces' \ + '{{0..10},braces}' '0 1 2 3 4 5 6 7 8 9 10 braces' \ + 'x{{0..10},braces}y' 'x0y x1y x2y x3y x4y x5y x6y x7y x8y x9y x10y xbracesy' \ + '{3..3}' '3' \ + 'x{3..3}y' 'x3y' \ + '{10..1}' '10 9 8 7 6 5 4 3 2 1' \ + '{10..1}y' '10y 9y 8y 7y 6y 5y 4y 3y 2y 1y' \ + 'x{10..1}y' 'x10y x9y x8y x7y x6y x5y x4y x3y x2y x1y' \ + '{a..f}' 'a b c d e f' \ + '{f..a}' 'f e d c b a' \ + '{a..A}' '{a..A}' \ + '{A..a}' '{A..a}' \ + '{f..f}' 'f' \ + '{1..f}' '{1..f}' \ + '{f..1}' '{f..1}' \ + '0{1..9} {10..20}' '01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20' \ + '{-1..-10}' '-1 -2 -3 -4 -5 -6 -7 -8 -9 -10' \ + '{-19..0}' '-19 -18 -17 -16 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0' \ + '{0..10}' '0 1 2 3 4 5 6 7 8 9 10' \ + '{0..10..1}' '0 1 2 3 4 5 6 7 8 9 10' \ + '{0..10..2}' '0 2 4 6 8 10' \ + '{0..10..3}' '0 3 6 9' \ + '{0..10..0}' '{0..10..0}' \ + '{0..10..-1}' '0' \ + '{10..0}' '10 9 8 7 6 5 4 3 2 1 0' \ + '{10..0..-1}' '10 9 8 7 6 5 4 3 2 1 0' \ + '{10..0..-2}' '10 8 6 4 2 0' \ + '{10..0..-3}' '10 7 4 1' \ + '{10..0..0}' '{10..0..0}' \ + '{10..0..1}' '10' \ + '{a..z..2}' 'a c e g i k m o q s u w y' \ + '{y..b..-3}' 'y v s p m j g d' \ + '{0..0x1000..0x200}' '0 512 1024 1536 2048 2560 3072 3584 4096' \ + '{a,b}{0..2}{z,y}' 'a0z a0y a1z a1y a2z a2y b0z b0y b1z b1y b2z b2y' \ + '{0..0100..8%03o}' '000 010 020 030 040 050 060 070 100' \ + '{0..0100..040%020o}' '00000000000000000000 00000000000000000040 00000000000000000100' \ + '{0..7%03..2u}' '000 001 010 011 100 101 110 111' \ + '{0..10%llu}' '{0..10%llu}' \ + '{0..10%s}' '{0..10%s}' \ + '{0..10%dl}' '{0..10%dl}' \ + '{a,b}{0..3%02..2u}{y,z}' 'a00y a00z a01y a01z a10y a10z a11y a11z b00y b00z b01y b01z b10y b10z b11y b11z' \ + +while (($#>1)) +do ((Line++)) + pattern=$1 + shift + expected=$1 + shift + got=$(eval print -r -- "$pattern") + [[ $got == $expected ]] || err_exit "'$pattern' failed -- expected '$expected' got '$got'" + #print -r -- " '$pattern' '$got' \\" +done + +# ~(N) no expand glob pattern option +set -- ~(N)/dev/null +[[ $# == 1 && $1 == /dev/null ]] || err_exit "~(N)/dev/null not matching /dev/null" +set -- ~(N)/dev/non_existant_file +[[ $# == 0 ]] || err_exit "~(N)/dev/nonexistant not empty" +set -- ""~(N)/dev/non_existant_file +[[ $# == 1 && ! $1 ]] || err_exit '""~(N)/dev/nonexistant not null argument' +set -- ~(N)/dev/non_existant_file"" +[[ $# == 1 && ! $1 ]] || err_exit '~(N)/dev/nonexistent"" not null argument' +for i in ~(N)/dev/non_existent_file +do err_exit "~(N)/dev/non_existent_file in for loop is $i" +done +for i in ""~(N)/dev/non_existent_file +do [[ ! $i ]] || err_exit '""~(N)/dev/non_existent_file not null' +done +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/functions.sh b/usr/src/lib/libshell/common/tests/functions.sh new file mode 100644 index 0000000000..fd45cd1767 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/functions.sh @@ -0,0 +1,758 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +integer Errors=0 +Command=${0##*/} +integer foo=33 +bar=bye +# check for global variables and $0 +function foobar +{ + case $1 in + 1) print -r - "$foo" "$bar";; + 2) print -r - "$0";; + 3) typeset foo=foo + integer bar=10 + print -r - "$foo" "$bar";; + 4) trap 'foo=36' EXIT + typeset foo=20;; + esac +} +function print +{ + command print hi +} +if [[ $(print) != hi ]] +then err_exit "command print not working inside print function" +fi +unset -f print + +if [[ $(foobar 1) != '33 bye' ]] +then err_exit 'global variables not correct' +fi + +if [[ $(foobar 2) != 'foobar' ]] +then err_exit '$0 not correct' +fi + +if [[ $(bar=foo foobar 1) != '33 foo' ]] +then err_exit 'environment override not correct' +fi +if [[ $bar == foo ]] +then err_exit 'scoping error' +fi + +if [[ $(foobar 3) != 'foo 10' ]] +then err_exit non-local variables +fi + +foobar 4 +if [[ $foo != 36 ]] +then err_exit EXIT trap in wrong scope +fi +unset -f foobar || err_exit "cannot unset function foobar" +typeset -f foobar>/dev/null && err_exit "typeset -f has incorrect exit status" + +function foobar +{ + (return 0) +} +> /tmp/shtests$$.1 +{ +foobar +if [ -r /tmp/shtests$$.1 ] +then rm -r /tmp/shtests$$.1 +else err_exit 'return within subshell inside function error' +fi +} +abc() print hi +if [[ $(abc) != hi ]] +then err_exit 'abc() print hi not working' +fi +( unset -f abc ) +if [[ $(abc 2>/dev/null) != hi ]] +then err_exit 'abc() print hi not working after subshell unset' +fi +( + function f + { + exit 1 + } + f + err_exit 'exit from function not working' +) +unset -f foo +function foo +{ + x=2 + ( + x=3 + cd /tmp + print bar + ) + if [[ $x != 2 ]] + then err_exit 'value of x not restored after subshell inside function' + fi +} +x=1 +dir=$PWD +if [[ $(foo) != bar ]] +then err_exit 'cd inside nested subshell not working' +fi +if [[ $PWD != "$dir" ]] +then err_exit 'cd inside nested subshell changes $PWD' +fi +fun() /bin/echo hello +if [[ $(fun) != hello ]] +then err_exit one line functions not working +fi +trap 'rm -f /tmp/script$$ /tmp/data$$.[12]' EXIT +cat > /tmp/script$$ <<-\! + print -r -- "$1" +! +chmod +x /tmp/script$$ +function passargs +{ + /tmp/script$$ "$@" +} +if [[ $(passargs one) != one ]] +then err_exit 'passing args from functions to scripts not working' +fi +cat > /tmp/script$$ <<-\! + trap 'exit 0' EXIT + function foo + { + /tmp > /dev/null 2>&1 + } + foo +! +if ! /tmp/script$$ +then err_exit 'exit trap incorrectly triggered' +fi +if ! $SHELL -c /tmp/script$$ +then err_exit 'exit trap incorrectly triggered when invoked with -c' +fi +$SHELL -c "trap 'rm /tmp/script$$' EXIT" +if [[ -f /tmp/script$$ ]] +then err_exit 'exit trap not triggered when invoked with -c' +fi +cat > /tmp/script$$ <<- \EOF + foobar() + { + return + } + shift + foobar + print -r -- "$1" +EOF +chmod +x /tmp/script$$ +if [[ $( $SHELL /tmp/script$$ arg1 arg2) != arg2 ]] +then err_exit 'arguments not restored by posix functions' +fi +function foo +{ + print hello +} +( + function foo + { + print bar + } + if [[ $(foo) != bar ]] + then err_exit 'function definitions inside subshells not working' + fi +) +if [[ $(foo) != hello ]] +then err_exit 'function definitions inside subshells not restored' +fi +unset -f foo bar +function bar +{ + print "$y" +} + +function foo +{ + typeset x=3 + y=$x bar +} +x=1 +if [[ $(foo) != 3 ]] +then err_exit 'variable assignment list not using parent scope' +fi +unset -f foo$$ +trap "rm -f /tmp/foo$$" EXIT INT +cat > /tmp/foo$$ <<! +function foo$$ +{ + print foo +} +! +chmod +x /tmp/foo$$ +FPATH=/tmp +autoload foo$$ +if [[ $(foo$$ 2>/dev/null) != foo ]] +then err_exit 'autoload not working' +fi +unset -f foobar +function foobar +{ + typeset -r x=3 + return 0 +} +( foobar ) 2> /dev/null || err_exit "cannot unset readonly variable in function" +if $SHELL -n 2> /dev/null <<-! + abc() + ! +then err_exit 'abc() without a function body is not a syntax error' +fi +function winpath +{ + usage='q pathname ...' + typeset var format=s + while getopts "$usage" var + do case $var in + q) format=q;; + esac + done + print done +} +if [[ $( (winpath --man 2>/dev/null); print ok) != ok ]] +then err_exit 'getopts --man in functions not working' +fi +if [[ $( (winpath -z 2>/dev/null); print ok) != ok ]] +then err_exit 'getopts with bad option in functions not working' +fi +unset -f x +function x +{ + print "$@" +} +typeset -ft x +if [[ $(x x=y 2>/dev/null) != x=y ]] +then err_exit 'name=value pair args not passed to traced functions' +fi +function bad +{ + false +} +trap 'val=false' ERR +val=true +bad +if [[ $val != false ]] +then err_exit 'set -e not working for functions' +fi +function bad +{ + false + return 0 +} +val=true +bad +if [[ $val != true ]] +then err_exit 'set -e not disabled for functions' +fi +bad() +{ + false + return 0 +} +val=true +bad +if [[ $val != false ]] +then err_exit 'set -e not inherited for posix functions' +fi +function myexport +{ + nameref var=$1 + if (( $# > 1 )) + then export $1=$2 + fi + if (( $# > 2 )) + then print $(myexport "$1" "$3" ) + return + fi + typeset val + val=$(export | grep "^$1=") + print ${val#"$1="} + +} +export dgk=base +if [[ $(myexport dgk fun) != fun ]] +then err_exit 'export inside function not working' +fi +val=$(export | grep "^dgk=") +if [[ ${val#dgk=} != base ]] +then err_exit 'export not restored after function call' +fi +if [[ $(myexport dgk fun fun2) != fun2 ]] +then err_exit 'export inside function not working with recursive function' +fi +val=$(export | grep "^dgk=") +if [[ ${val#dgk=} != base ]] +then err_exit 'export not restored after recursive function call' +fi +if [[ $(dgk=try3 myexport dgk) != try3 ]] +then err_exit 'name=value not added to export list with function call' +fi +val=$(export | grep "^dgk=") +if [[ ${val#dgk=} != base ]] +then err_exit 'export not restored name=value function call' +fi +unset zzz +if [[ $(myexport zzz fun) != fun ]] +then err_exit 'export inside function not working for zzz' +fi +if [[ $(export | grep "zzz=") ]] +then err_exit 'zzz exported after function call' +fi +unset zzz +typeset -u zzz +function foo +{ + zzz=abc + print $zzz +} +if [[ $(foo)$(foo) != ABCABC ]] +then err_exit 'attributes on unset variables not saved/restored' +fi +function xpd { + typeset i j=$1 + for i + do print i=$i j=$j + [[ $i == a ]] && xpd b + done + } +if [[ $(xpd a c) != $'i=a j=a\ni=b j=b\ni=c j=a' ]] +then err_exit 'for loop function optimization error' +fi + +typeset -A visited +integer level=0 +function closure +{ + (( $# > 5 )) && return 1 + ((level < 2)) && ((level++)) + typeset tmp r=0 + visited[$1]=1 + + for tmp in $level _$level + do + [[ ${visited[$tmp]} == 1 ]] && continue + closure $tmp $* || r=1 + done + return $r +} +closure 0 || err_exit -u2 'for loop function optimization bug2' +mkdir /tmp/ksh$$ || err_exit "mkdir /tmp/ksh$$ failed" +cd /tmp/ksh$$ || err_exit "cd /tmp/ksh$$ failed" +print 'false' > try +chmod +x try +cat > tst <<- EOF + function ignore + { + ./try + return 0 + } + trap "print error; exit 1" ERR + ignore +EOF +if [[ $($SHELL < tst) == error ]] +then err_exit 'ERR trap not cleared' +fi +FPATH=/tmp/ksh$$ +print ': This does nothing' > /tmp/ksh$$/foobar +chmod +x /tmp/ksh$$/foobar +unset -f foobar +{ foobar;} 2> /dev/null +if [[ $? != 126 ]] +then err_exit 'function file without function definition processes wrong error' +fi +print 'set a b c' > dotscript +[[ $(PATH=$PATH: $SHELL -c '. dotscript;print $#') == 3 ]] || err_exit 'positional parameters not preserved with . script without arguments' +cd ~- || err_exit "cd back failed" +cd /; rm -r /tmp/ksh$$ || err_exit "rm -r /tmp/ksh$$ failed" +function errcheck +{ + trap 'print ERR; return 1' ERR + false + print ok +} +err=$(errcheck) +[[ $err == ERR ]] || err_exit 'trap on ERR not working in a function' +x="$( + function foobar + { + print ok + } + typeset -f foobar +)" +eval "$x" || err_exit 'typeset -f generates syntax error' +[[ $(foobar) != ok ]] && err_exit 'typeset -f not generating function' +unset -f a b c +a() +{ + b + b + print ${.sh.fun} +} +b() { : ;} +[[ $(a) == a ]] || err_exit '.sh.fun not set correctly in a function' +print $'a(){\ndate\n}' | $SHELL 2> /dev/null || err_exit 'parser error in a(){;date;}' +cat > /tmp/data$$.1 << '++EOF' + 1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 2 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 4 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 6 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 8 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 10 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 11 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 12 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 13 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 14 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 15 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 16 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 17 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 18 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 19 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 20 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +++EOF +cat > /tmp/script$$ << '++EOF' +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +f() +{ +cat <<\M +++EOF +cat /tmp/data$$.1 >> /tmp/script$$ +printf 'M\n}\n\nf\n\n' >> /tmp/script$$ +$SHELL -c /tmp/script$$ > /tmp/data$$.2 +cmp -s /tmp/data$$.[12] || err_exit 'error with long functions' +rm -f /tmp/script$$ /tmp/data$$.[12] +v=1 +function f +{ + typeset i + for i in 0 1 + do typeset v + v=$i + [[ $v == $i ]] || return 1 + done +} +f || err_exit "typeset optimization bug" +function f +{ + print -r -- "$foo$bar" +} +function g +{ + print -r -- $(bar=bam f) +} +unset foo bar +[[ $(foo=hello g) == hellobam ]] || err_exit 'function exports not passed on' +[[ $(bar=hello g) == bam ]] || err_exit 'function exports not overridden' +unset -f foo +function foo +{ + typeset line=$1 + set +n + while [[ $line ]] + do if [[ ! $varname ]] + then varname=${line%% *} + line=${line##"$varname"?( )} + [[ $line ]] && continue + else print ok + return + fi + varname= + done +} +[[ $(foo 'NUMBERED RECORDSIZE') == ok ]] || err_exit 'optimization error with undefined variable' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/glob.sh b/usr/src/lib/libshell/common/tests/glob.sh new file mode 100644 index 0000000000..4d5fa0b519 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/glob.sh @@ -0,0 +1,303 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + ((errors++)) +} +alias err_exit='err_exit $LINENO' + +integer aware=0 contrary=0 ignorant=0 + +function test_glob +{ + typeset lineno expected drop arg got sep op val add del + if [[ $1 == --* ]] + then del=${1#--} + shift + fi + if [[ $1 == ++* ]] + then add=${1#++} + shift + fi + lineno=$1 expected=$2 + shift 2 + if (( contrary )) + then if [[ $expected == "<Beware> "* ]] + then expected=${expected#"<Beware> "} + expected="$expected <Beware>" + fi + if [[ $expected == *"<aXb> <abd>"* ]] + then expected=${expected/"<aXb> <abd>"/"<abd> <aXb>"} + fi + fi + for arg + do got="$got$sep<$arg>" + sep=" " + done + if (( ignorant && aware )) + then if [[ $del ]] + then got="<$del> $got" + fi + if [[ $add ]] + then expected="<$add> $expected" + fi + fi + if [[ $got != "$expected" ]] + then err_exit $lineno "glob: got '$got' expected '$expected'" + fi +} + +function test_case +{ + typeset lineno expected subject pattern got + lineno=$1 expected=$2 subject=$3 pattern=$4 + eval " + case $subject in + $pattern) got='<match>' ;; + *) got='<nomatch>' ;; + esac + " + if [[ $got != "$expected" ]] + then err_exit $lineno "case $subject in $pattern) got '$got' expected '$expected'" + fi +} + +Command=${0##*/} +tmp=/tmp/ksh$$ +integer errors=0 +unset undefined + +export LC_COLLATE=C + +mkdir $tmp || err_exit $LINENO "mkdir $tmp failed" +trap "cd /; rm -rf $tmp" EXIT +cd $tmp || err_exit $LINENO "cd $tmp failed" +rm -rf * + +touch B b +set -- * +case $* in +'b B') contrary=1 ;; +b|B) ignorant=1 ;; +esac +set -- $(/bin/sh -c 'echo [a-c]') +case $* in +B) aware=1 ;; +esac +rm -rf * + +touch a b c d abc abd abe bb bcd ca cb dd de Beware +mkdir bdir + +test_glob $LINENO '<a> <abc> <abd> <abe> <X*>' a* X* +test_glob $LINENO '<a> <abc> <abd> <abe>' \a* + +if ( set --nullglob ) 2>/dev/null +then + set --nullglob + + test_glob $LINENO '<a> <abc> <abd> <abe>' a* X* + + set --nonullglob +fi + +if ( set --failglob ) 2>/dev/null +then + set --failglob + mkdir tmp + touch tmp/l1 tmp/l2 tmp/l3 + + test_glob $LINENO '' tmp/l[12] tmp/*4 tmp/*3 + test_glob $LINENO '' tmp/l[12] tmp/*4 tmp/*3 + + rm -r tmp + set --nofailglob +fi + +test_glob $LINENO '<bdir/>' b*/ +test_glob $LINENO '<*>' \* +test_glob $LINENO '<a*>' 'a*' +test_glob $LINENO '<a*>' a\* +test_glob $LINENO '<c> <ca> <cb> <a*> <*q*>' c* a\* *q* +test_glob $LINENO '<**>' "*"* +test_glob $LINENO '<**>' \** +test_glob $LINENO '<\.\./*/>' "\.\./*/" +test_glob $LINENO '<s/\..*//>' 's/\..*//' +test_glob $LINENO '</^root:/{s/^[!:]*:[!:]*:\([!:]*\).*$/\1/>' "/^root:/{s/^[!:]*:[!:]*:\([!:]*\).*"'$'"/\1/" +test_glob $LINENO '<abc> <abd> <abe> <bb> <cb>' [a-c]b* +test_glob ++Beware $LINENO '<abd> <abe> <bb> <bcd> <bdir> <ca> <cb> <dd> <de>' [a-y]*[!c] +test_glob $LINENO '<abd> <abe>' a*[!c] + +touch a-b aXb + +test_glob $LINENO '<a-b> <aXb>' a[X-]b + +touch .x .y + +test_glob --Beware $LINENO '<Beware> <d> <dd> <de>' [!a-c]* + +if mkdir a\*b 2>/dev/null +then + touch a\*b/ooo + + test_glob $LINENO '<a*b/ooo>' a\*b/* + test_glob $LINENO '<a*b/ooo>' a\*?/* + test_case $LINENO '<match>' '!7' '*\!*' + test_case $LINENO '<match>' 'r.*' '*.\*' + test_glob $LINENO '<abc>' a[b]c + test_glob $LINENO '<abc>' a["b"]c + test_glob $LINENO '<abc>' a[\b]c + test_glob $LINENO '<abc>' a?c + test_case $LINENO '<match>' 'abc' 'a"b"c' + test_case $LINENO '<match>' 'abc' 'a*c' + test_case $LINENO '<nomatch>' 'abc' '"a?c"' + test_case $LINENO '<nomatch>' 'abc' 'a\*c' + test_case $LINENO '<nomatch>' 'abc' 'a\[b]c' + test_case $LINENO '<match>' '"$undefined"' '""' + test_case $LINENO '<match>' 'abc' 'a["\b"]c' + + rm -rf mkdir a\*b +fi + +mkdir man +mkdir man/man1 +touch man/man1/sh.1 + +test_glob $LINENO '<man/man1/sh.1>' */man*/sh.* +test_glob $LINENO '<man/man1/sh.1>' $(echo */man*/sh.*) +test_glob $LINENO '<man/man1/sh.1>' "$(echo */man*/sh.*)" + +test_case $LINENO '<match>' 'abc' 'a***c' +test_case $LINENO '<match>' 'abc' 'a*****?c' +test_case $LINENO '<match>' 'abc' '?*****??' +test_case $LINENO '<match>' 'abc' '*****??' +test_case $LINENO '<match>' 'abc' '*****??c' +test_case $LINENO '<match>' 'abc' '?*****?c' +test_case $LINENO '<match>' 'abc' '?***?****c' +test_case $LINENO '<match>' 'abc' '?***?****?' +test_case $LINENO '<match>' 'abc' '?***?****' +test_case $LINENO '<match>' 'abc' '*******c' +test_case $LINENO '<match>' 'abc' '*******?' +test_case $LINENO '<match>' 'abcdecdhjk' 'a*cd**?**??k' +test_case $LINENO '<match>' 'abcdecdhjk' 'a**?**cd**?**??k' +test_case $LINENO '<match>' 'abcdecdhjk' 'a**?**cd**?**??k***' +test_case $LINENO '<match>' 'abcdecdhjk' 'a**?**cd**?**??***k' +test_case $LINENO '<match>' 'abcdecdhjk' 'a**?**cd**?**??***k**' +test_case $LINENO '<match>' 'abcdecdhjk' 'a****c**?**??*****' +test_case $LINENO '<match>' "'-'" '[-abc]' +test_case $LINENO '<match>' "'-'" '[abc-]' +test_case $LINENO '<match>' "'\\'" '\\' +test_case $LINENO '<match>' "'\\'" '[\\]' +test_case $LINENO '<match>' "'\\'" "'\\'" +test_case $LINENO '<match>' "'['" '[[]' +test_case $LINENO '<match>' '[' '[[]' +test_case $LINENO '<match>' "'['" '[' +test_case $LINENO '<match>' '[' '[' +test_case $LINENO '<match>' "'[abc'" "'['*" +test_case $LINENO '<nomatch>' "'[abc'" '[*' +test_case $LINENO '<match>' '[abc' "'['*" +test_case $LINENO '<nomatch>' '[abc' '[*' +test_case $LINENO '<match>' 'abd' "a[b/c]d" +test_case $LINENO '<match>' 'a/d' "a[b/c]d" +test_case $LINENO '<match>' 'acd' "a[b/c]d" +test_case $LINENO '<match>' "']'" '[]]' +test_case $LINENO '<match>' "'-'" '[]-]' +test_case $LINENO '<match>' 'p' '[a-\z]' +test_case $LINENO '<match>' '"/tmp"' '[/\\]*' +test_case $LINENO '<nomatch>' 'abc' '??**********?****?' +test_case $LINENO '<nomatch>' 'abc' '??**********?****c' +test_case $LINENO '<nomatch>' 'abc' '?************c****?****' +test_case $LINENO '<nomatch>' 'abc' '*c*?**' +test_case $LINENO '<nomatch>' 'abc' 'a*****c*?**' +test_case $LINENO '<nomatch>' 'abc' 'a********???*******' +test_case $LINENO '<nomatch>' "'a'" '[]' +test_case $LINENO '<nomatch>' 'a' '[]' +test_case $LINENO '<nomatch>' "'['" '[abc' +test_case $LINENO '<nomatch>' '[' '[abc' + +test_glob ++Beware $LINENO '<b> <bb> <bcd> <bdir>' b* +test_glob $LINENO '<Beware> <b> <bb> <bcd> <bdir>' [bB]* + +if ( set --nocaseglob ) 2>/dev/null +then + set --nocaseglob + + test_glob $LINENO '<Beware> <b> <bb> <bcd> <bdir>' b* + test_glob $LINENO '<Beware> <b> <bb> <bcd> <bdir>' [b]* + test_glob $LINENO '<Beware> <b> <bb> <bcd> <bdir>' [bB]* + + set --nonocaseglob +fi + +if ( set -f ) 2>/dev/null +then + set -f + + test_glob $LINENO '<*>' * + + set +f +fi + +if ( set --noglob ) 2>/dev/null +then + set --noglob + + test_glob $LINENO '<*>' * + + set --glob +fi + +FIGNORE='.*|*' +test_glob $LINENO '<*>' * + +FIGNORE='.*|*c|*e|?' +test_glob $LINENO '<a-b> <aXb> <abd> <bb> <bcd> <bdir> <ca> <cb> <dd> <man>' * + +FIGNORE='.*|*b|*d|?' +test_glob $LINENO '<Beware> <abc> <abe> <bdir> <ca> <de> <man>' * + +FIGNORE= +test_glob $LINENO '<man/man1/sh.1>' */man*/sh.* + +unset FIGNORE +test_glob $LINENO '<bb> <ca> <cb> <dd> <de>' ?? +test_glob $LINENO '<man/man1/sh.1>' */man*/sh.* + +GLOBIGNORE='.*:*' +set -- * +if [[ $1 == '*' ]] +then + GLOBIGNORE='.*:*c:*e:?' + test_glob $LINENO '<>' * + + GLOBIGNORE='.*:*b:*d:?' + test_glob $LINENO '<>' * + + unset GLOBIGNORE + test_glob $LINENO '<>' * + test_glob $LINENO '<man/man1/sh.1>' */man*/sh.* + + GLOBIGNORE= + test_glob $LINENO '<man/man1/sh.1>' */man*/sh.* +fi + +exit $errors diff --git a/usr/src/lib/libshell/common/tests/grep.sh b/usr/src/lib/libshell/common/tests/grep.sh new file mode 100644 index 0000000000..db20206dbf --- /dev/null +++ b/usr/src/lib/libshell/common/tests/grep.sh @@ -0,0 +1,102 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 + +function grep +{ + # + # SHELL VERSION OF GREP + # + vflag= xflag= cflag= lflag= nflag= + set -f + while ((1)) # look for grep options + do case "$1" in + -v*) vflag=1;; + -x*) xflag=1;; + -c*) cflag=1;; + -l*) lflag=1;; + -n*) nflag=1;; + -b*) print 'b option not supported';; + -e*) shift;expr="$1";; + -f*) shift;expr=$(< $1);; + -*) print $0: 'unknown flag';return 2;; + *) + if test "$expr" = '' + then expr="$1";shift + fi + test "$xflag" || expr="*${expr}*" + break;; + esac + shift # next argument + done + noprint=$vflag$cflag$lflag # don't print if these flags are set + integer n=0 c=0 tc=0 nargs=$# # initialize counters + for i in "$@" # go thru the files + do if ((nargs<=1)) + then fname='' + else fname="$i": + fi + test "$i" && exec 0< $i # open file if necessary + while read -r line # read in a line + do let n=n+1 + case "$line" in + $expr) # line matches pattern + test "$noprint" || print -r -- "$fname${nflag:+$n:}$line" + let c=c+1 ;; + *) # not a match + if test "$vflag" + then print -r -- "$fname${nflag:+$n:}$line" + fi;; + esac + done + if test "$lflag" && ((c)) + then print -r -- "$i" + fi + let tc=tc+c n=0 c=0 + done + test "$cflag" && print $tc # print count if cflag is set + let tc # set the return value +} + +trap 'rm -f /tmp/grep$$' EXIT +cat > /tmp/grep$$ <<\! +this is a food bar test +to see how many lines find both foo and bar. +Some line contain foo only, +and some lines contain bar only. +However, many lines contain both foo and also bar. +A line containing foobar should also be counted. +There should be six lines with foo and bar. +There are only two line with out foo but with bar. +! + +if (( $(grep -c 'foo*bar' /tmp/grep$$ ) != 6)) +then err_exit +fi +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/heredoc.sh b/usr/src/lib/libshell/common/tests/heredoc.sh new file mode 100644 index 0000000000..53835f2007 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/heredoc.sh @@ -0,0 +1,213 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +f=/tmp/here1$$ +g=/tmp/here2$$ +trap "rm -f $f $g" EXIT +cat > $f <<! +hello world +! +if [[ $(<$f) != 'hello world' ]] +then err_exit "'hello world' here doc not working" +fi +cat > $g <<\! +hello world +! +cmp $f $g 2> /dev/null || err_exit "'hello world' quoted here doc not working" +cat > $g <<- ! + hello world +! +cmp $f $g 2> /dev/null || err_exit "'hello world' tabbed here doc not working" +cat > $g <<- \! + hello world +! +cmp $f $g 2> /dev/null || err_exit "'hello world' quoted tabbed here doc not working" +x=hello +cat > $g <<! +$x world +! +cmp $f $g 2> /dev/null || err_exit "'$x world' here doc not working" +cat > $g <<! +$(print hello) world +! +cmp $f $g 2> /dev/null || err_exit "'$(print hello) world' here doc not working" +cat > $f <<\!! +!@#$%%^^&*()_+~"::~;'`<>?/.,{}[] +!! +if [[ $(<$f) != '!@#$%%^^&*()_+~"::~;'\''`<>?/.,{}[]' ]] +then err_exit "'hello world' here doc not working" +fi +cat > $g <<!! +!@#\$%%^^&*()_+~"::~;'\`<>?/.,{}[] +!! +cmp $f $g 2> /dev/null || err_exit "unquoted here doc not working" +exec 3<<! + foo +! +if [[ $(<&3) != ' foo' ]] +then err_exit "leading tabs stripped with <<!" +fi +$SHELL -c " +eval `echo 'cat <<x'` "|| err_exit "eval `echo 'cat <<x'` core dumps" +cat > /dev/null <<EOF # comments should not cause core dumps +abc +EOF +cat >$g << : +: +: +cmp /dev/null $g 2> /dev/null || err_exit "empty here doc not working" +x=$(print $( cat <<HUP +hello +HUP +) +) +if [[ $x != hello ]] +then err_exit "here doc inside command sub not working" +fi +y=$(cat <<! +${x:+${x}} +! +) +if [[ $y != "${x:+${x}}" ]] +then err_exit '${x:+${x}} not working in here document' +fi +$SHELL -c ' +x=0 +while (( x < 100 )) +do ((x = x+1)) + cat << EOF +EOF +done +' 2> /dev/null || err_exit '100 empty here docs fails' +{ + print 'builtin -d cat + cat <<- EOF' + for ((i=0; i < 100; i++)) + do print XXXXXXXXXXXXXXXXXXXX + done + print ' XXX$(date)XXXX + EOF' +} > $f +chmod +x "$f" +$SHELL "$f" > /dev/null || err_exit "large here-doc with command substitution fails" +x=$(/bin/cat <<! +$0 +! +) +[[ "$x" == "$0" ]] || err_exit '$0 not correct inside here documents' +$SHELL -c 'x=$( +cat << EOF +EOF)' 2> /dev/null || err_exit 'here-doc cannot be terminated by )' +if [[ $( IFS=:;cat <<-! + $IFS$(print hi)$IFS + !) != :hi: ]] +then err_exit '$IFS unset by command substitution in here docs' +fi +if x=$($SHELL -c 'cat <<< "hello world"' 2> /dev/null) +then [[ $x == 'hello world' ]] || err_exit '<<< documents not working' + x=$($SHELL -c 'v="hello world";cat <<< $v' 2> /dev/null) + [[ $x == 'hello world' ]] || err_exit '<<< documents with $x not working' + x=$($SHELL -c 'v="hello world";cat <<< "$v"' 2> /dev/null) + [[ $x == 'hello world' ]] || err_exit '<<< documents with $x not working' +else err_exit '<<< syntax not supported' +fi +if [[ $(cat << EOF #testing +#abc +abc +EOF) != $'#abc\nabc' ]] +then err_exit 'comments not preserved in here-documents' +fi +cat > "$f" <<- '!!!!' + builtin cat + : << EOF + $PWD + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + EOF + command exec 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&- + x=abc + cat << EOF + $x + EOF +!!!! +chmod 755 "$f" +if [[ $($SHELL "$f") != abc ]] +then err_exit 'here document descritor was closed' +fi +cat > "$f" <<- '!!!!' + exec 0<&- + foobar() + { + /bin/cat <<- ! + foobar + ! + } + : << EOF + $PWD + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + EOF + print -r -- "$(foobar)" +!!!! +if [[ $($SHELL "$f") != foobar ]] +then err_exit 'here document with stdin closed failed' +fi +printf $'cat <<# \\!!!\n\thello\n\t\tworld\n!!!' > $f +[[ $($SHELL "$f") == $'hello\n\tworld' ]] || err_exit "<<# not working for quoted here documents" +printf $'w=world;cat <<# !!!\n\thello\n\t\t$w\n!!!' > $f +[[ $($SHELL "$f") == $'hello\n\tworld' ]] || err_exit "<<# not working for non-quoted here documents" +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/io.sh b/usr/src/lib/libshell/common/tests/io.sh new file mode 100644 index 0000000000..649520482a --- /dev/null +++ b/usr/src/lib/libshell/common/tests/io.sh @@ -0,0 +1,251 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +# cut here +function fun +{ + while command exec 3>&1 + do break + done 2> /dev/null + print -u3 good +} +print 'read -r a;print -r -u$1 -- "$a"' > /tmp/mycat$$ +chmod 755 /tmp/mycat$$ +for ((i=3; i < 10; i++)) +do + eval "a=\$(print foo | /tmp/mycat$$" $i $i'>&1 > /dev/null |cat)' 2> /dev/null + [[ $a == foo ]] || err_exit "bad file descriptor $i in comsub script" +done +rm -f /tmp/mycat$$ +exec 3> /dev/null +[[ $(fun) == good ]] || err_exit 'file 3 closed before subshell completes' +exec 3>&- +mkdir /tmp/ksh$$ || err_exit "mkdir /tmp/ksh$$ failed" +trap 'rm -rf /tmp/ksh$$' EXIT +cd /tmp/ksh$$ || err_exit "cd /tmp/ksh$$ failed" +print foo > file1 +print bar >> file1 +if [[ $(<file1) != $'foo\nbar' ]] +then err_exit 'append (>>) not working' +fi +set -o noclobber +exec 3<> file1 +read -u3 line +if [[ $line != foo ]] +then err_exit '<> not working right with read' +fi +if ( 4> file1 ) 2> /dev/null +then err_exit 'noclobber not causing exclusive open' +fi +set +o noclobber +if command exec 4< /dev/fd/3 +then read -u4 line + if [[ $line != bar ]] + then '4< /dev/fd/3 not working correctly' + fi +fi +cat > close0 <<\! +exec 0<&- +echo $(./close1) +! +print "echo abc" > close1 +chmod +x close0 close1 +x=$(./close0) +if [[ $x != "abc" ]] +then err_exit "picked up file descriptor zero for opening script file" +fi +cat > close0 <<\! + for ((i=0; i < 1100; i++)) + do exec 4< /dev/null + read -u4 + done + exit 0 +! +./close0 2> /dev/null || err_exit "multiple exec 4< /dev/null can fail" +$SHELL -c ' + trap "rm -f in$$ out$$" EXIT + for ((i = 0; i < 1000; i++)) + do print -r -- "This is a test" + done > in$$ + > out$$ + exec 1<> out$$ + builtin cat + print -r -- "$(cat in$$)" + cmp -s in$$ out$$' 2> /dev/null +[[ $? == 0 ]] || err_exit 'builtin cat truncates files' +cat >| script <<-\! +print hello +( exec 3<&- 4<&-) +exec 3<&- 4<&- +print world +! +chmod +x script +[[ $( $SHELL ./script) == $'hello\nworld' ]] || err_exit 'closing 3 & 4 causes script to fail' +cd ~- || err_exit "cd back failed" +( exec > '' ) 2> /dev/null && err_exit '> "" does not fail' +unset x +( exec > ${x} ) 2> /dev/null && err_exit '> $x, where x null does not fail' +exec <<! +foo +bar +! +( exec 0< /dev/null) +read line +if [[ $line != foo ]] +then err_exit 'file descriptor not restored after exec in subshell' +fi +exec 3>&- 4>&-; cd /; rm -r /tmp/ksh$$ || err_exit "rm -r /tmp/ksh$$ failed" +[[ $( { + read -r line;print -r -- "$line" + ( + read -r line;print -r -- "$line" + ) & wait + while read -r line + do print -r -- "$line" + done + } << ! +line 1 +line 2 +line 3 +!) == $'line 1\nline 2\nline 3' ]] || err_exit 'read error with subshells' +# 2004-05-11 bug fix +cat > /tmp/io$$.1 <<- \++EOF++ + script=/tmp/io$$.2 + trap 'rm -f $script' EXIT + exec 9> $script + for ((i=3; i<9; i++)) + do eval "while read -u$i; do : ;done $i</dev/null" + print -u9 "exec $i< /dev/null" + done + for ((i=0; i < 60; i++)) + do print -u9 -f "%.80c\n" ' ' + done + print -u9 'print ok' + exec 9<&- + chmod +x $script + $script +++EOF++ +chmod +x /tmp/io$$.1 +[[ $($SHELL /tmp/io$$.1) == ok ]] || err_exit "parent i/o causes child script to fail" +rm -rf /tmp/io$$.[12] +# 2004-11-25 ancient /dev/fd/NN redirection bug fix +x=$( + { + print -n 1 + print -n 2 > /dev/fd/2 + print -n 3 + print -n 4 > /dev/fd/2 + } 2>&1 +) +[[ $x == "1234" ]] || err_exit "/dev/fd/NN redirection fails to dup" +# 2004-12-20 redirction loss bug fix +cat > /tmp/io$$.1 <<- \++EOF++ + function a + { + trap 'print ok' EXIT + : > /dev/null + } + a +++EOF++ +chmod +x /tmp/io$$.1 +[[ $(/tmp/io$$.1) == ok ]] || err_exit "trap on EXIT loses last command redirection" +print > /dev/null {n}> /tmp/io$$.1 +[[ ! -s /tmp/io$$.1 ]] && newio=1 +rm -rf /tmp/io$$.1 +if [[ $newio && $(print hello | while read -u$n; do print $REPLY; done {n}<&0) != hello ]] +then err_exit "{n}<&0 not working with for loop" +fi +[[ $({ read -r;read -u3 3<&0; print -- "$REPLY" ;} <<! +hello +world +!) == world ]] || err_exit 'I/O not synchronized with <&' +trap 'rm -f /tmp/seek$$; exit $((Errors+1))' EXIT +x="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNSPQRSTUVWXYZ1234567890" +for ((i=0; i < 62; i++)) +do printf "%.39c\n" ${x:i:1} +done > /tmp/seek$$ +if command exec 3<> /tmp/seek$$ +then (( $(3<#) == 0 )) || err_exit "not at position 0" + (( $(3<# ((EOF))) == 40*62 )) || err_exit "not at end-of-file" + command exec 3<# ((40*8)) || err_exit "absolute seek fails" + read -u3 + [[ $REPLY == +(i) ]] || err_exit "expecting iiii..." + [[ $(3<#) == $(3<# ((CUR)) ) ]] || err_exit '$(3<#)!=$(3<#((CUR)))' + command exec 3<# ((CUR+80)) + read -u3 + [[ $REPLY == {39}(l) ]] || err_exit "expecting lll..." + command exec 3<# ((EOF-80)) + read -u3 + [[ $REPLY == +(9) ]] || err_exit "expecting 999...; got $REPLY" + command exec 3># ((80)) + print -u3 -f "%.39c\n" @ + command exec 3># ((80)) + read -u3 + [[ $REPLY == +(@) ]] || err_exit "expecting @@@..." + read -u3 + [[ $REPLY == +(d) ]] || err_exit "expecting ddd..." + command exec 3># ((EOF)) + print -u3 -f "%.39c\n" ^ + (( $(3<# ((CUR-0))) == 40*63 )) || err_exit "not at extended end-of-file" + command exec 3<# ((40*62)) + read -u3 + [[ $REPLY == +(^) ]] || err_exit "expecting ddd..." + command exec 3<# ((0)) + command exec 3<# *jjjj* + read -u3 + [[ $REPLY == {39}(j) ]] || err_exit "<# pattern failed" + [[ $(command exec 3<## *llll*) = {39}(k) ]] || err_exit "<## pattern not saving standard output" + read -u3 + [[ $REPLY == {39}(l) ]] || err_exit "<## pattern failed to position" + command exec 3<# *abc* + read -u3 && err_exit "not found pattern not positioning at eof" + cat /tmp/seek$$ | read -r <# *WWW* + [[ $REPLY == *WWWWW* ]] || err_exit '<# not working for pipes' +else err_exit "/tmp/seek$$: cannot open for reading" +fi +trap "" EXIT +rm -f /tmp/seek$$ +$SHELL -ic ' +{ + print -u2 || exit 2 + print -u3 || exit 3 + print -u4 || exit 4 + print -u5 || exit 5 + print -u6 || exit 6 + print -u7 || exit 7 + print -u8 || exit 8 + print -u9 || exit 9 +} 3> /dev/null 4> /dev/null 5> /dev/null 6> /dev/null 7> /dev/null 8> /dev/null 9> /dev/null' > /dev/null 2>&1 +exitval=$? +(( exitval )) && err_exit "print to unit $exitval failed" +trap 'rm -rf /tmp/io.sh$$*' EXIT +$SHELL -c "{ > /tmp/io.sh$$.1 ; date;} >&- 2> /dev/null" > /tmp/io.sh$$.2 +[[ -s /tmp/io.sh$$.1 || -s /tmp/io.sh$$.2 ]] && err_exit 'commands with standard output closed produce output' +$SHELL -c "$SHELL -c ': 3>&1' 1>&- 2>/dev/null" && err_exit 'closed standard output not passed to subshell' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/nameref.sh b/usr/src/lib/libshell/common/tests/nameref.sh new file mode 100644 index 0000000000..a6c52ddaa8 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/nameref.sh @@ -0,0 +1,229 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +function checkref +{ + nameref foo=$1 bar=$2 + if [[ $foo != $bar ]] + then err_exit "foo=$foo != bar=$bar" + fi + foo=hello + if [[ $foo != $bar ]] + then err_exit "foo=$foo != bar=$bar" + fi + foo.child=child + if [[ ${foo.child} != ${bar.child} ]] + then err_exit "foo.child=${foo.child} != bar=${bar.child}" + fi +} + +name=first +checkref name name +name.child=second +checkref name name +.foo=top +.foo.bar=next +checkref .foo.bar .foo.bar +if [[ ${.foo.bar} != hello ]] +then err_exit ".foo.bar=${.foo.bar} != hello" +fi +if [[ ${.foo.bar.child} != child ]] +then err_exit ".foo.bar.child=${.foo.bar.child} != child" +fi +function func1 +{ + nameref color=$1 + func2 color +} + +function func2 +{ + nameref color=$1 + set -s -- ${!color[@]} + print -r -- "$@" +} + +typeset -A color +color[apple]=red +color[grape]=purple +color[banana]=yellow +if [[ $(func1 color) != 'apple banana grape' ]] +then err_exit "nameref or nameref not working" +fi +nameref x=.foo.bar +if [[ ${!x} != .foo.bar ]] +then err_exit "${!x} not working" +fi +typeset +n x $(typeset +n) +unset x +nameref x=.foo.bar +function x.set +{ + [[ ${.sh.value} ]] && print hello +} +if [[ $(.foo.bar.set) != $(x.set) ]] +then err_exit "function references not working" +fi +if [[ $(typeset +n) != x ]] +then err_exit "typeset +n doesn't list names of reference variables" +fi +if [[ $(typeset -n) != x=.foo.bar ]] +then err_exit "typeset +n doesn't list values of reference variables" +fi +file=/tmp/shtest$$ +typeset +n foo bar 2> /dev/null +unset foo bar +export bar=foo +nameref foo=bar +if [[ $foo != foo ]] +then err_exit "value of nameref foo != $foo" +fi +trap "rm -f $file" EXIT INT +cat > $file <<\! +print -r -- $foo +! +chmod +x "$file" +y=$( $file) +if [[ $y != '' ]] +then err_exit "reference variable not cleared" +fi +{ + command nameref xx=yy + command nameref yy=xx +} 2> /dev/null && err_exit "self reference not detected" +typeset +n foo bar +unset foo bar +set foo +nameref bar=$1 +foo=hello +if [[ $bar != hello ]] +then err_exit 'nameref of positional paramters outside of function not working' +fi +unset foo bar +bar=123 +function foobar +{ + typeset -n foo=bar + typeset -n foo=bar +} +foobar 2> /dev/null || err_exit 'nameref not unsetting previous reference' +( + nameref short=verylong + short=( A=a B=b ) + if [[ ${verylong.A} != a ]] + then err_exit 'nameref short to longname compound assignment error' + fi +) 2> /dev/null|| err_exit 'nameref short to longname compound assignment error' +unset x +if [[ $(var1=1 var2=2 + for i in var1 var2 + do nameref x=$i + print $x + done) != $'1\n2' ]] +then err_exit 'for loop nameref optimization error' +fi +if [[ $(typeset -A var1 var2 + var1[sub1]=1 var2[sub2]=1 + for i in var1 var2 + do + typeset -n array=$i + print ${!array[*]} + done) != $'sub1\nsub2' ]] +then err_exit 'for loop nameref optimization test2 error' +fi + +unset -n x foo bar +if [[ $(nameref x=foo;for x in foo bar;do print ${!x};done) != $'foo\nbar' ]] +then err_exit 'for loop optimization with namerefs not working' +fi +if [[ $( + p=(x=(r=3) y=(r=4)) + for i in x y + do nameref x=p.$i + print ${x.r} + done +) != $'3\n4' ]] +then err_exit 'nameref optimization error' +fi +[[ $( +unset x y var +var=(foo=bar) +for i in y var +do typeset -n x=$i + if [[ ${!x.@} ]] + then print ok + fi + typeset +n x +done) != ok ]] && err_exit 'invalid for loop optimization of name references' +function setval # name value +{ + nameref arg=$1 + nameref var=arg.bar + var=$2 +} +foo=( integer bar=0) +setval foo 5 +(( foo.bar == 5)) || err_exit 'nested nameref not working' +function selfref +{ + typeset -n ps=$1 + print -r -- "${ps}" +} +ps=(a=1 b=2) +[[ $(selfref ps) == *a=1* ]] || err_exit 'local nameref cannot reference global variable of the same name' +function subref +{ + typeset -n foo=$1 + print -r -- ${foo.a} +} +[[ $(subref ps) == 1 ]] || err_exit 'local nameref cannot reference global variable child' + +function local +{ + typeset ps=(typeset -i a=3 b=4) + [[ $(subref ps) == 3 ]] || err_exit 'local nameref cannot reference caller compound variable' +} +local +unset -f local +function local +{ + qs=(integer a=3; integer b=4) +} +local 2> /dev/null || err_exit 'function local has non-zero exit status' +[[ ${qs.a} == 3 ]] || err_exit 'function cannot set compound global variable' +unset fun i +foo=(x=hi) +function fun +{ + nameref i=$1 + print -r -- "${i.x}" +} +i=foo +[[ $(fun $i) == hi ]] || err_exit 'nameref for compound variable with in function name of caller fails' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/options.sh b/usr/src/lib/libshell/common/tests/options.sh new file mode 100644 index 0000000000..2520cb2a2e --- /dev/null +++ b/usr/src/lib/libshell/common/tests/options.sh @@ -0,0 +1,313 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +if [[ $( ${SHELL-ksh} -s hello<<-\! + print $1 + ! + ) != hello ]] +then err_exit "${SHELL-ksh} -s not working" +fi +x=$( + set -e + false && print bad + print good +) +if [[ $x != good ]] +then err_exit 'sh -e not workuing' +fi +[[ $($SHELL -D -c 'print hi; print $"hello"') == '"hello"' ]] || err_exit 'ksh -D not working' + +tmp=/tmp/ksh$$ +mkdir $tmp +rc=$tmp/.kshrc +print $'function env_hit\n{\n\tprint OK\n}' > $rc + +export ENV=$rc +if [[ -o privileged ]] +then + [[ $(print env_hit | $SHELL 2>&1) == "OK" ]] && + err_exit 'privileged nointeractive shell reads $ENV file' + [[ $(print env_hit | $SHELL -E 2>&1) == "OK" ]] && + err_exit 'privileged -E reads $ENV file' + [[ $(print env_hit | $SHELL +E 2>&1) == "OK" ]] && + err_exit 'privileged +E reads $ENV file' + [[ $(print env_hit | $SHELL --rc 2>&1) == "OK" ]] && + err_exit 'privileged --rc reads $ENV file' + [[ $(print env_hit | $SHELL --norc 2>&1) == "OK" ]] && + err_exit 'privileged --norc reads $ENV file' +else + [[ $(print env_hit | $SHELL 2>&1) == "OK" ]] && + err_exit 'nointeractive shell reads $ENV file' + [[ $(print env_hit | $SHELL -E 2>&1) == "OK" ]] || + err_exit '-E ignores $ENV file' + [[ $(print env_hit | $SHELL +E 2>&1) == "OK" ]] && + err_exit '+E reads $ENV file' + [[ $(print env_hit | $SHELL --rc 2>&1) == "OK" ]] || + err_exit '--rc ignores $ENV file' + [[ $(print env_hit | $SHELL --norc 2>&1) == "OK" ]] && + err_exit '--norc reads $ENV file' +fi + +export ENV= +if [[ -o privileged ]] +then + [[ $(print env_hit | HOME=$tmp $SHELL 2>&1) == "OK" ]] && + err_exit 'privileged nointeractive shell reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL -E 2>&1) == "OK" ]] && + err_exit 'privileged -E ignores empty $ENV' + [[ $(print env_hit | HOME=$tmp $SHELL +E 2>&1) == "OK" ]] && + err_exit 'privileged +E reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL --rc 2>&1) == "OK" ]] && + err_exit 'privileged --rc ignores empty $ENV' + [[ $(print env_hit | HOME=$tmp $SHELL --norc 2>&1) == "OK" ]] && + err_exit 'privileged --norc reads $HOME/.kshrc file' +else + [[ $(print env_hit | HOME=$tmp $SHELL 2>&1) == "OK" ]] && + err_exit 'nointeractive shell reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL -E 2>&1) == "OK" ]] && + err_exit '-E ignores empty $ENV' + [[ $(print env_hit | HOME=$tmp $SHELL +E 2>&1) == "OK" ]] && + err_exit '+E reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL --rc 2>&1) == "OK" ]] && + err_exit '--rc ignores empty $ENV' + [[ $(print env_hit | HOME=$tmp $SHELL --norc 2>&1) == "OK" ]] && + err_exit '--norc reads $HOME/.kshrc file' +fi + +unset ENV +if [[ -o privileged ]] +then + [[ $(print env_hit | HOME=$tmp $SHELL 2>&1) == "OK" ]] && + err_exit 'privileged nointeractive shell reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL -E 2>&1) == "OK" ]] && + err_exit 'privileged -E reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL +E 2>&1) == "OK" ]] && + err_exit 'privileged +E reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL --rc 2>&1) == "OK" ]] && + err_exit 'privileged --rc reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL --norc 2>&1) == "OK" ]] && + err_exit 'privileged --norc reads $HOME/.kshrc file' +else + [[ $(print env_hit | HOME=$tmp $SHELL 2>&1) == "OK" ]] && + err_exit 'nointeractive shell reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL -E 2>&1) == "OK" ]] || + err_exit '-E ignores $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL +E 2>&1) == "OK" ]] && + err_exit '+E reads $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL --rc 2>&1) == "OK" ]] || + err_exit '--rc ignores $HOME/.kshrc file' + [[ $(print env_hit | HOME=$tmp $SHELL --norc 2>&1) == "OK" ]] && + err_exit '--norc reads $HOME/.kshrc file' +fi + +rm -rf $tmp + +if command set -G 2> /dev/null +then mkdir /tmp/ksh$$ + cd /tmp/ksh$$ + mkdir bar foo + > bar.c > bam.c + > bar/foo.c > bar/bam.c + > foo/bam.c + set -- **.c + expected='bam.c bar.c' + [[ $* == $expected ]] || + err_exit "-G **.c failed -- expected '$expected', got '$*'" + set -- ** + expected='bam.c bar bar.c bar/bam.c bar/foo.c foo foo/bam.c' + [[ $* == $expected ]] || + err_exit "-G ** failed -- expected '$expected', got '$*'" + set -- **/*.c + expected='bam.c bar.c bar/bam.c bar/foo.c foo/bam.c' + [[ $* == $expected ]] || + err_exit "-G **/*.c failed -- expected '$expected', got '$*'" + set -- **/bam.c + expected='bam.c bar/bam.c foo/bam.c' + [[ $* == $expected ]] || + err_exit "-G **/bam.c failed -- expected '$expected', got '$*'" + cd ~- + rm -rf /tmp/ksh$$ +fi + +mkdir /tmp/ksh$$ +cd /tmp/ksh$$ +t="<$$>.profile.<$$>" +echo "echo '$t'" > .profile +cp $SHELL ./-ksh +if [[ -o privileged ]] +then + [[ $(HOME=$PWD $SHELL -l </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged -l reads .profile' + [[ $(HOME=$PWD $SHELL --login </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged --login reads .profile' + [[ $(HOME=$PWD $SHELL --login-shell </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged --login-shell reads .profile' + [[ $(HOME=$PWD $SHELL --login_shell </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged --login_shell reads .profile' + [[ $(HOME=$PWD exec -a -ksh $SHELL </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged exec -a -ksh ksh reads .profile' + [[ $(HOME=$PWD ./-ksh -i </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged ./-ksh reads .profile' + [[ $(HOME=$PWD ./-ksh -ip </dev/null 2>&1) == *$t* ]] && + err_exit 'privileged ./-ksh -p reads .profile' +else + [[ $(HOME=$PWD $SHELL -l </dev/null 2>&1) == *$t* ]] || + err_exit '-l ignores .profile' + [[ $(HOME=$PWD $SHELL --login </dev/null 2>&1) == *$t* ]] || + err_exit '--login ignores .profile' + [[ $(HOME=$PWD $SHELL --login-shell </dev/null 2>&1) == *$t* ]] || + err_exit '--login-shell ignores .profile' + [[ $(HOME=$PWD $SHELL --login_shell </dev/null 2>&1) == *$t* ]] || + err_exit '--login_shell ignores .profile' + [[ $(HOME=$PWD exec -a -ksh $SHELL </dev/null 2>&1) == *$t* ]] || + err_exit 'exec -a -ksh ksh ignores .profile' + [[ $(HOME=$PWD ./-ksh -i </dev/null 2>&1) == *$t* ]] || + err_exit './-ksh ignores .profile' + [[ $(HOME=$PWD ./-ksh -ip </dev/null 2>&1) == *$t* ]] && + err_exit './-ksh -p does not ignore .profile' +fi +cd ~- +rm -rf /tmp/ksh$$ + + +# { exec interactive login_shell restricted xtrace } in the following test + +for opt in \ + allexport all-export all_export \ + bgnice bg-nice bg_nice \ + clobber emacs \ + errexit err-exit err_exit \ + glob \ + globstar glob-star glob_star \ + gmacs \ + ignoreeof ignore-eof ignore_eof \ + keyword log markdirs monitor notify \ + pipefail pipe-fail pipe_fail \ + trackall track-all track_all \ + unset verbose vi \ + viraw vi-raw vi_raw +do old=$opt + if [[ ! -o $opt ]] + then old=no$opt + fi + + set --$opt || err_exit "set --$opt failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + [[ -o no$opt ]] && err_exit "[[ -o no$opt ]] failed" + [[ -o no-$opt ]] && err_exit "[[ -o no-$opt ]] failed" + [[ -o no_$opt ]] && err_exit "[[ -o no_$opt ]] failed" + [[ -o ?$opt ]] || err_exit "[[ -o ?$opt ]] failed" + [[ -o ?no$opt ]] || err_exit "[[ -o ?no$opt ]] failed" + [[ -o ?no-$opt ]] || err_exit "[[ -o ?no-$opt ]] failed" + [[ -o ?no_$opt ]] || err_exit "[[ -o ?no_$opt ]] failed" + + set --no$opt || err_exit "set --no$opt failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + [[ -o $opt ]] && err_exit "[[ -o $opt ]] failed" + + set --no-$opt || err_exit "set --no-$opt failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + [[ -o $opt ]] && err_exit "[[ -o $opt ]] failed" + + set --no_$opt || err_exit "set --no_$opt failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + [[ -o $opt ]] && err_exit "[[ -o $opt ]] failed" + + set -o $opt || err_exit "set -o $opt failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + set -o $opt=1 || err_exit "set -o $opt=1 failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + set -o no$opt=0 || err_exit "set -o no$opt=0 failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + set --$opt=1 || err_exit "set --$opt=1 failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + set --no$opt=0 || err_exit "set --no$opt=0 failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + + set -o no$opt || err_exit "set -o no$opt failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + set -o $opt=0 || err_exit "set -o $opt=0 failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + set -o no$opt=1 || err_exit "set -o no$opt=1 failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + set --$opt=0 || err_exit "set --$opt=0 failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + set --no$opt=1 || err_exit "set --no$opt=1 failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + + set -o no-$opt || err_exit "set -o no-$opt failed" + [[ -o no-$opt ]] || err_exit "[[ -o no-$opt ]] failed" + + set -o no_$opt || err_exit "set -o no_$opt failed" + [[ -o no_$opt ]] || err_exit "[[ -o no_$opt ]] failed" + + set +o $opt || err_exit "set +o $opt failed" + [[ -o no$opt ]] || err_exit "[[ -o no$opt ]] failed" + + set +o no$opt || err_exit "set +o no$opt failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + + set +o no-$opt || err_exit "set +o no-$opt failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + + set +o no_$opt || err_exit "set +o no_$opt failed" + [[ -o $opt ]] || err_exit "[[ -o $opt ]] failed" + + set --$old +done + +for opt in \ + exec interactive login_shell login-shell logi privileged \ + rc restricted xtrace +do [[ -o $opt ]] + y=$? + [[ -o no$opt ]] + n=$? + case $y$n in + 10|01) ;; + *) err_exit "[[ -o $opt ]] == [[ -o no$opt ]]" ;; + esac +done + +for opt in \ + foo foo-bar foo_bar +do if [[ -o ?$opt ]] + then err_exit "[[ -o ?$opt ]] should fail" + fi + if [[ -o ?no$opt ]] + then err_exit "[[ -o ?no$opt ]] should fail" + fi +done +false | true | true || err_exit 'pipe not exiting exit value of last element' +true | true | false && err_exit 'pipe not exiting false' +set -o pipefail +false | true | true && err_exit 'pipe with first not failing with pipefail' +true | false | true && err_exit 'pipe middle not failing with pipefail' +true | true | false && err_exit 'pipe last not failing with pipefail' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/path.sh b/usr/src/lib/libshell/common/tests/path.sh new file mode 100644 index 0000000000..7083aca713 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/path.sh @@ -0,0 +1,186 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +mkdir /tmp/ksh$$ +cd /tmp/ksh$$ +trap "PATH=$PATH; cd /; rm -rf /tmp/ksh$$" EXIT +(PATH="/bin") +[[ $($SHELL -c 'print -r -- "$PATH"') == "$PATH" ]] || err_exit 'export PATH lost in subshell' +cat > bug1 <<- \EOF + print print ok > /tmp/ok$$ + /bin/chmod 755 /tmp/ok$$ + trap 'cd /; rm -f /tmp/ok$$' EXIT + function a + { + typeset -x PATH=/tmp + ok$$ + } + path=$PATH + unset PATH + a + PATH=$path +} +EOF +[[ $($SHELL ./bug1 2> /dev/null) == ok ]] || err_exit "PATH in function not working" +cat > bug1 <<- \EOF + function lock_unlock + { + typeset PATH=/usr/bin + typeset -x PATH='' + } + + PATH=/usr/bin + : $(PATH=/usr/bin getconf PATH) + typeset -ft lock_unlock + lock_unlock +EOF +($SHELL ./bug1) 2> /dev/null || err_exit "path_delete bug" +mkdir tdir$$ +if $SHELL tdir$$ > /dev/null 2>&1 +then err_exit 'not an error to run ksh on a directory' +fi + +print 'print hi' > ls +if [[ $($SHELL ls 2> /dev/null) != hi ]] +then err_exit "$SHELL name not executing version in current directory" +fi +if [[ $(ls -d . 2>/dev/null) == . && $(PATH=/bin:/usr/bin:$PATH ls -d . 2>/dev/null) != . ]] +then err_exit 'PATH export in command substitution not working' +fi +pwd=$PWD +# get rid of leading and trailing : and trailing :. +PATH=${PATH%.} +PATH=${PATH%:} +PATH=${PATH#.} +PATH=${PATH#:} +path=$PATH +var=$(whence date) +dir=$(basename "$var") +for i in 1 2 3 4 5 6 7 8 9 0 +do if ! whence notfound$i 2> /dev/null + then cmd=notfound$i + break + fi +done +print 'print hello' > date +chmod +x date +print 'print notfound' > $cmd +chmod +x "$cmd" +> foo +chmod 755 foo +for PATH in $path :$path $path: .:$path $path: $path:. $PWD::$path $PWD:.:$path $path:$PWD $path:.:$PWD +do +# print path=$PATH $(whence date) +# print path=$PATH $(whence "$cmd") + date + "$cmd" +done > /dev/null 2>&1 +builtin -d date 2> /dev/null +if [[ $(PATH=:/usr/bin; date) != 'hello' ]] +then err_exit "leading : in path not working" +fi +( + PATH=$PWD: + builtin chmod + print 'print cannot execute' > noexec + chmod 644 noexec + if [[ ! -x noexec ]] + then noexec > /dev/null 2>&1 + else exit 126 + fi +) +status=$? +[[ $status == 126 ]] || err_exit "exit status of non-executable is $status -- 126 expected" +builtin -d rm 2> /dev/null +rm=$(whence rm) +d=$(dirname "$rm") +unset FPATH +PATH=/dev/null +if date > /dev/null 2>&1 +then err_exit 'programs in . should not be found' +fi +[[ $(whence ./foo) != "$PWD/"./foo ]] && err_exit 'whence ./foo not working' +[[ $(whence "$PWD/foo") != "$PWD/foo" ]] && err_exit 'whence $PWD/foo not working' +[[ $(whence ./xxxxx) ]] && err_exit 'whence ./xxxx not working' +PATH=$d: +cp "$rm" kshrm$$ +if [[ $(whence kshrm$$) != $PWD/kshrm$$ ]] +then err_exit 'trailing : in pathname not working' +fi +cp "$rm" rm +PATH=:$d +if [[ $(whence rm) != $PWD/rm ]] +then err_exit 'leading : in pathname not working' +fi +PATH=$d: whence rm > /dev/null +if [[ $(whence rm) != $PWD/rm ]] +then err_exit 'pathname not restored after scoping' +fi +mkdir bin +print 'print ok' > bin/tst +chmod +x bin/tst +if [[ $(PATH=$PWD/bin tst 2>/dev/null) != ok ]] +then err_exit '(PATH=$PWD/bin foo) does not find $PWD/bin/foo' +fi +cd / +if whence ls > /dev/null +then PATH= + if [[ $(whence rm) ]] + then err_exit 'setting PATH to Null not working' + fi + unset PATH + if [[ $(whence rm) != /*rm ]] + then err_exit 'unsetting path not working' + fi +fi +PATH=/dev:/tmp/ksh$$ +x=$(whence rm) +typeset foo=$(PATH=/xyz:/abc :) +y=$(whence rm) +[[ $x != "$y" ]] && err_exit 'PATH not restored after command substitution' +whence getconf > /dev/null && err_exit 'getconf should not be found' +builtin /bin/getconf +PATH=/bin +PATH=$(getconf PATH) +x=$(whence ls) +PATH=.:$PWD:${x%/ls} +[[ $(whence ls) == "$x" ]] || err_exit 'PATH search bug when .:$PWD in path' +PATH=$PWD:.:${x%/ls} +[[ $(whence ls) == "$x" ]] || err_exit 'PATH search bug when :$PWD:. in path' +cd "${x%/ls}" +[[ $(whence ls) == /* ]] || err_exit 'whence not generating absolute pathname' +status=$($SHELL -c $'trap \'print $?\' EXIT;/a/b/c/d/e 2> /dev/null') +[[ $status == 127 ]] || err_exit "not found command exit status $status -- expected 127" +status=$($SHELL -c $'trap \'print $?\' EXIT;/dev/null 2> /dev/null') +[[ $status == 126 ]] || err_exit "non executable command exit status $status -- expected 126" +status=$($SHELL -c $'trap \'print $?\' ERR;/a/b/c/d/e 2> /dev/null') +[[ $status == 127 ]] || err_exit "not found command with ERR trap exit status $status -- expected 127" +status=$($SHELL -c $'trap \'print $?\' ERR;/dev/null 2> /dev/null') +[[ $status == 126 ]] || err_exit "non executable command ERR trap exit status $status -- expected 126" +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/quoting.sh b/usr/src/lib/libshell/common/tests/quoting.sh new file mode 100644 index 0000000000..9a9e23bd55 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/quoting.sh @@ -0,0 +1,331 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +if [[ 'hi there' != "hi there" ]] +then err_exit "single quotes not the same as double quotes" +fi +x='hi there' +if [[ $x != 'hi there' ]] +then err_exit "$x not the same as 'hi there'" +fi +if [[ $x != "hi there" ]] +then err_exit "$x not the same as \"hi there \"" +fi +if [[ \a\b\c\*\|\"\ \\ != 'abc*|" \' ]] +then err_exit " \\ differs from '' " +fi +if [[ "ab\'\"\$(" != 'ab\'\''"$(' ]] +then err_exit " \"\" differs from '' " +fi +if [[ $(print -r - 'abc*|" \') != 'abc*|" \' ]] +then err_exit "\$(print -r - '') differs from ''" +fi +if [[ $(print -r - "abc*|\" \\") != 'abc*|" \' ]] +then err_exit "\$(print -r - '') differs from ''" +fi +if [[ "$(print -r - 'abc*|" \')" != 'abc*|" \' ]] +then err_exit "\"\$(print -r - '')\" differs from ''" +fi +if [[ "$(print -r - "abc*|\" \\")" != 'abc*|" \' ]] +then err_exit "\"\$(print -r - "")\" differs from ''" +fi +if [[ $(print -r - $(print -r - 'abc*|" \')) != 'abc*|" \' ]] +then err_exit "nested \$(print -r - '') differs from ''" +fi +if [[ "$(print -r - $(print -r - 'abc*|" \'))" != 'abc*|" \' ]] +then err_exit "\"nested \$(print -r - '')\" differs from ''" +fi +if [[ $(print -r - "$(print -r - 'abc*|" \')") != 'abc*|" \' ]] +then err_exit "nested \"\$(print -r - '')\" differs from ''" +fi +unset x +if [[ ${x-$(print -r - "abc*|\" \\")} != 'abc*|" \' ]] +then err_exit "\${x-\$(print -r - '')} differs from ''" +fi +if [[ ${x-$(print -r - "a}c*|\" \\")} != 'a}c*|" \' ]] +then err_exit "\${x-\$(print -r - '}')} differs from ''" +fi +x=$((echo foo)|(cat)) +if [[ $x != foo ]] +then err_exit "((cmd)|(cmd)) failed" +fi +x=$(print -r -- "\"$HOME\"") +if [[ $x != '"'$HOME'"' ]] +then err_exit "nested double quotes failed" +fi +: ${z="a{b}c"} +if [[ $z != 'a{b}c' ]] +then err_exit '${z="a{b}c"} not correct' +fi +unset z +: "${z="a{b}c"}" +if [[ $z != 'a{b}c' ]] +then err_exit '"${z="a{b}c"}" not correct' +fi +if [[ $(print -r -- "a\*b") != 'a\*b' ]] +then err_exit '$(print -r -- "a\*b") differs from a\*b' +fi +unset x +if [[ $(print -r -- "a\*b$x") != 'a\*b' ]] +then err_exit '$(print -r -- "a\*b$x") differs from a\*b' +fi +x=hello +set -- ${x+foo bar bam} +if (( $# !=3 )) +then err_exit '${x+foo bar bam} does not yield three arguments' +fi +set -- ${x+foo "bar bam"} +if (( $# !=2 )) +then err_exit '${x+foo "bar bam"} does not yield two arguments' +fi +set -- ${x+foo 'bar bam'} +if (( $# !=2 )) +then err_exit '${x+foo '\''bar bam'\''} does not yield two arguments' +fi +set -- ${x+foo $x bam} +if (( $# !=3 )) +then err_exit '${x+foo $x bam} does not yield three arguments' +fi +set -- ${x+foo "$x" bam} +if (( $# !=3 )) +then err_exit '${x+foo "$x" bam} does not yield three arguments' +fi +set -- ${x+"foo $x bam"} +if (( $# !=1 )) +then err_exit '${x+"foo $x bam"} does not yield one argument' +fi +set -- "${x+foo $x bam}" +if (( $# !=1 )) +then err_exit '"${x+foo $x bam}" does not yield one argument' +fi +set -- ${x+foo "$x "bam} +if (( $# !=2 )) +then err_exit '${x+foo "$x "bam} does not yield two arguments' +fi +x="ab$'cd" +if [[ $x != 'ab$'"'cd" ]] +then err_exit '$'"' inside double quotes not working" +fi +x=`print 'ab$'` +if [[ $x != 'ab$' ]] +then err_exit '$'"' inside `` quotes not working" +fi +unset a +x=$(print -r -- "'\ +\ +") +if [[ $x != "'" ]] +then err_exit 'line continuation in double strings not working' +fi +x=$(print -r -- "'\ +$a\ +") +if [[ $x != "'" ]] +then err_exit 'line continuation in expanded double strings not working' +fi +x='\*' +if [[ $(print -r -- $x) != '\*' ]] +then err_exit 'x="\\*";$x != \*' +fi +x=' hello world ' +set -- $x +if (( $# != 2 )) +then err_exit 'field splitting error' +fi +x=$(print -r -- '1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890 \ +1234567890123456789012345678901234567890123456789012345678901234567890') +if (( ${#x} != (15*73-3) )) +then err_exit "length of x, ${#x}, is incorrect should be $((15*73-3))" +fi +x='$hi' +if [[ $x\$ != '$hi$' ]] +then err_exit ' $x\$, with x=$hi, does not expand to $hi$' +fi +if [[ $x$ != '$hi$' ]] +then err_exit ' $x$, with x=$hi, does not expand to $hi$' +fi +set -- $(/bin/echo foo;sleep 1;/bin/echo bar) +if [[ $# != 2 ]] +then err_exit 'word splitting after command substitution not working' +fi +unset q +if [[ "${q:+'}q${q:+'}" != q ]] +then err_exit 'expansion of "{q:+'\''}" not correct when q unset' +fi +q=1 +if [[ "${q:+'}q${q:+'}" != "'q'" ]] +then err_exit 'expansion of "{q:+'\''}" not correct when q set' +fi +x=$'x\' #y' +if [[ $x != "x' #y" ]] +then err_exit "$'x\' #y'" not working +fi +x=$q$'x\' #y' +if [[ $x != "1x' #y" ]] +then err_exit "$q$'x\' #y'" not working +fi +IFS=, +x='a,b\,c,d' +set -- $x +if [[ $2 != 'b\' ]] +then err_exit "field splitting of $x with IFS=$IFS not working" +fi +foo=bar +bar=$(print -r -- ${foo+\\n\ }) +if [[ $bar != '\n ' ]] +then err_exit '${foo+\\n\ } expansion error' +fi +unset bar +bar=$(print -r -- ${foo+\\n\ $bar}) +if [[ $bar != '\n ' ]] +then err_exit '${foo+\\n\ $bar} expansion error with bar unset' +fi +x='\\(..\\)|&\|\|\\&\\|' +if [[ $(print -r -- $x) != "$x" ]] +then err_exit '$x, where x=\\(..\\)|&\|\|\\&\\| not working' +fi +x='\\(' +if [[ $(print -r -- a${x}b) != a"${x}"b ]] +then err_exit 'a${x}b, where x=\\( not working' +fi +x= +if [[ $(print -r -- $x'\\1') != '\\1' ]] +then err_exit 'backreference inside single quotes broken' +fi +set -- '' +set -- "$@" +if (( $# != 1 )) +then err_exit '"$@" not preserving nulls' +fi +x= +if [[ $(print -r s"!\2${x}\1\a!") != 's!\2\1\a!' ]] +then err_exit 'print -r s"!\2${x}\1\a!" not equal s!\2\1\a!' +fi +if [[ $(print -r $'foo\n\n\n') != foo ]] +then err_exit 'trailing newlines on comsubstitution not removed' +fi +unset x +if [[ ${x:='//'} != '//' ]] +then err_exit '${x:='//'} != "//"' +fi +if [[ $(print -r "\"hi$\"") != '"hi$"' ]] +then err_exit '$\ not correct inside ""' +fi +unset x +if [[ "${x-a\}b}" != 'a}b' ]] +then err_exit '"${x-a\}b}" != "a}b"' +fi +if [[ "\}\]$x\*\{\[\\" != '\}\]\*\{\[\' ]] +then err_exit '"\}\]$x\*\{\[\\" != "\}\]\*\{\[\"' +fi +foo=yes +if [[ $(print -r -- {\$foo}) != '{$foo}' ]] +then err_exit '{\$foo}' not expanded correctly +fi +[[ foo == $( +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +########################################################### +print foo) ]] || err_exit "command subsitution with long comments broken" +subject='some/other/words' +re='(?*)/(?*)/(?*)' +[[ ${subject/${re}/\3} != words ]] && err_exit 'string replacement with \3 not working' +[[ ${subject/${re}/'\3'} != '\3' ]] && err_exit 'string replacement with '"'\3'"' not working' +[[ ${subject/${re}/"\\3"} != '\3' ]] && err_exit 'string replacement with "\\3" not working' +[[ ${subject/${re}/"\3"} != '\3' ]] && err_exit 'string replacement with "\3" not working' +string='\3' +[[ ${subject/${re}/${string}} != words ]] && err_exit 'string replacement with $string not working with string=\3' +[[ $(print -r "${subject/${re}/${string}}") != words ]] && err_exit 'string replacement with $string not working with string=\3 using print' +[[ ${subject/${re}/"${string}"} != '\3' ]] && err_exit 'string replacement with "$string" not working with string=\3' +[[ $(print -r "${subject/${re}/"${string}"}") != '\3' ]] && err_exit 'string replacement with "$string" not working with string=\3 using print' +string='\\3' +[[ ${subject/${re}/${string}} != '\3' ]] && err_exit 'string replacement with $string not working with string=\\3' +[[ ${subject/${re}/"${string}"} != '\\3' ]] && err_exit 'string replacement with "$string" not working with string=\\3' +[[ ${subject/${re}/\4} != '\4' ]] && err_exit 'string replacement with \4 not working' +[[ ${subject/${re}/'\4'} != '\4' ]] && err_exit 'string replacement with '\4' not working' +string='\4' +[[ ${subject/${re}/${string}} != '\4' ]] && err_exit 'string replacement with $string not working with string=\4' +[[ ${subject/${re}/"${string}"} != '\4' ]] && err_exit 'string replacement with "$string" not working with string=\4' +string='&foo' +[[ ${subject/${re}/${string}} != '&foo' ]] && err_exit 'string replacement with $string not working with string=&foo' +[[ ${subject/${re}/"${string}"} != '&foo' ]] && err_exit 'string replacement with "$string" not working with string=&foo' +{ +x=x +x=${x:-`id | sed 's/^[^(]*(\([^)]*\)).*/\1/'`} +} 2> /dev/null || err_exit 'skipping over `` failed' +[[ $x == x ]] || err_exit 'assignment ${x:=`...`} failed' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/quoting2.sh b/usr/src/lib/libshell/common/tests/quoting2.sh new file mode 100644 index 0000000000..0532f72d7a --- /dev/null +++ b/usr/src/lib/libshell/common/tests/quoting2.sh @@ -0,0 +1,200 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +set -o noglob +if [[ 'hi there' != "hi there" ]] +then err_exit "single quotes not the same as double quotes" +fi +x='hi there' +if [[ $x != 'hi there' ]] +then err_exit "$x not the same as 'hi there'" +fi +if [[ $x != "hi there" ]] +then err_exit "$x not the same as \"hi there \"" +fi +if [[ \a\b\c\*\|\"\ \\ != 'abc*|" \' ]] +then err_exit " \\ differs from '' " +fi +if [[ "ab\'\"\$(" != 'ab\'\''"$(' ]] +then err_exit " \"\" differs from '' " +fi +if [[ $(print -r - 'abc*|" \') != 'abc*|" \' ]] +then err_exit "\$(print -r - '') differs from ''" +fi +if [[ $(print -r - "abc*|\" \\") != 'abc*|" \' ]] +then err_exit "\$(print -r - '') differs from ''" +fi +if [[ "$(print -r - 'abc*|" \')" != 'abc*|" \' ]] +then err_exit "\"\$(print -r - '')\" differs from ''" +fi +if [[ "$(print -r - "abc*|\" \\")" != 'abc*|" \' ]] +then err_exit "\"\$(print -r - "")\" differs from ''" +fi +if [[ $(print -r - "$(print -r - 'abc*|" \')") != 'abc*|" \' ]] +then err_exit "nested \$(print -r - '') differs from ''" +fi +if [[ "$(print -r - $(print -r - 'abc*|" \'))" != 'abc*|" \' ]] +then err_exit "\"nested \$(print -r - '')\" differs from ''" +fi +if [[ $(print -r - "$(print -r - 'abc*|" \')") != 'abc*|" \' ]] +then err_exit "nested \"\$(print -r - '')\" differs from ''" +fi +unset x +if [[ ${x-$(print -r - "abc*|\" \\")} != 'abc*|" \' ]] +then err_exit "\${x-\$(print -r - '')} differs from ''" +fi +if [[ ${x-$(print -r - "a}c*|\" \\")} != 'a}c*|" \' ]] +then err_exit "\${x-\$(print -r - '}')} differs from ''" +fi +x=$((echo foo)|(cat)) +if [[ $x != foo ]] +then err_exit "((cmd)|(cmd)) failed" +fi +x=$(print -r -- "\"$HOME\"") +if [[ $x != '"'$HOME'"' ]] +then err_exit "nested double quotes failed" +fi +: ${z="a{b}c"} +if [[ $z != 'a{b}c' ]] +then err_exit '${z="a{b}c"} not correct' +fi +unset z +: "${z="a{b}c"}" +if [[ $z != 'a{b}c' ]] +then err_exit '"${z="a{b}c"}" not correct' +fi +if [[ $(print -r -- "a\*b") != 'a\*b' ]] +then err_exit '$(print -r -- "a\*b") differs from a\*b' +fi +unset x +if [[ $(print -r -- "a\*b$x") != 'a\*b' ]] +then err_exit '$(print -r -- "a\*b$x") differs from a\*b' +fi +x=hello +set -- ${x+foo bar bam} +if (( $# !=3 )) +then err_exit '${x+foo bar bam} does not yield three arguments' +fi +set -- ${x+foo "bar bam"} +if (( $# !=2 )) +then err_exit '${x+foo "bar bam"} does not yield two arguments' +fi +set -- ${x+foo 'bar bam'} +if (( $# !=2 )) +then err_exit '${x+foo '\''bar bam'\''} does not yield two arguments' +fi +set -- ${x+foo $x bam} +if (( $# !=3 )) +then err_exit '${x+foo $x bam} does not yield three arguments' +fi +set -- ${x+foo "$x" bam} +if (( $# !=3 )) +then err_exit '${x+foo "$x" bam} does not yield three arguments' +fi +set -- ${x+"foo $x bam"} +if (( $# !=1 )) +then err_exit '${x+"foo $x bam"} does not yield one argument' +fi +set -- "${x+foo $x bam}" +if (( $# !=1 )) +then err_exit '"${x+foo $x bam}" does not yield one argument' +fi +set -- ${x+foo "$x "bam} +if (( $# !=2 )) +then err_exit '${x+foo "$x "bam} does not yield two arguments' +fi +x="ab$'cd" +if [[ $x != 'ab$'"'cd" ]] +then err_exit '$'"' inside double quotes not working" +fi +x=`print 'ab$'` +if [[ $x != 'ab$' ]] +then err_exit '$'"' inside `` quotes not working" +fi +unset a +x=$(print -r -- "'\ +\ +") +if [[ $x != "'" ]] +then err_exit 'line continuation in double strings not working' +fi +x=$(print -r -- "'\ +$a\ +") +if [[ $x != "'" ]] +then err_exit 'line continuation in expanded double strings not working' +fi +x='\*' +if [[ $(print -r -- $x) != '\*' ]] +then err_exit 'x="\\*";$x != \*' +fi +if [[ $(print -r -- "\}" ) != '\}' ]] +then err_exit '(print -r -- "\}"' not working +fi +if [[ $(print -r -- "\{" ) != '\{' ]] +then err_exit 'print -r -- "\{"' not working +fi +# The following caused a syntax error on earlier versions +foo=foo x=- +if [[ `eval print \\${foo$x}` != foo* ]] +then err_exit '`eval print \\${foo$x}`' not working +fi +if [[ "`eval print \\${foo$x}`" != foo* ]] +then err_exit '"`eval print \\${foo$x}`"' not working +fi +if ( [[ $() != '' ]] ) +then err_exit '$() not working' +fi +x=a:b:c +set -- $( IFS=:; print $x) +if (( $# != 3)) +then err_exit 'IFS not working correctly with command substitution' +fi +$SHELL -n 2> /dev/null << \! || err_exit '$(...) bug with ( in comment' +y=$( + # ( this line is a bug fix + print hi +) +! +x= +for j in glob noglob +do for i in 'a\*b' 'a\ b' 'a\bc' 'a\*b' 'a\"b' + do eval [[ '$('print -r -- \'$i\'\$x')' != "'$i'" ]] && err_exit "quoting of $i\$x with $j enabled failed" + eval [[ '$('print -r -- \'$i\'\${x%*}')' != "'$i'" ]] && err_exit "quoting of $i\${x%*} with $j enabled failed" + if [[ $j == noglob ]] + then eval [[ '$('print -r -- \'$i\'\${x:-*}')' != "'$i''*'" ]] && err_exit "quoting of $i\${x:-*} with $j enabled failed" + fi + done + set -f +done +foo=foo +[[ "$" == '$' ]] || err_exit '"$" != $' +[[ "${foo}$" == 'foo$' ]] || err_exit 'foo=foo;"${foo}$" != foo$' +[[ "${foo}${foo}$" == 'foofoo$' ]] || err_exit 'foo=foo;"${foo}${foo}$" != foofoo$' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/return.sh b/usr/src/lib/libshell/common/tests/return.sh new file mode 100644 index 0000000000..b3ee5b11ff --- /dev/null +++ b/usr/src/lib/libshell/common/tests/return.sh @@ -0,0 +1,177 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +# test the behavior of return and exit with functions + +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +foo=NOVAL bar=NOVAL +file=/tmp/shtest$$ +trap "rm -f $file" EXIT INT +function foo +{ + typeset foo=NOEXIT + trap "foo=EXIT;rm -f $file" EXIT + > $file + if (( $1 == 0 )) + then return $2 + elif (( $1 == 1 )) + then exit $2 + else bar "$@" + fi +} + +function bar +{ + typeset bar=NOEXIT + trap 'bar=EXIT' EXIT + if (( $1 == 2 )) + then return $2 + elif (( $1 == 3 )) + then exit $2 + fi +} + +function funcheck +{ + [[ $foo = EXIT ]] || err_exit "foo "$@" : exit trap not set" + if [[ -f $file ]] + then rm -r $file + err_exit "foo $@: doesn't remove $file" + fi + foo=NOVAL bar=NOVAL +} + +(exit 0) || err_exit "exit 0 is not zero" +(return 0) || err_exit "return 0 is not zero" +(exit) || err_exit "default exit value is not zero" +(return) || err_exit "default return value is not zero" +(exit 35) +ret=$? +if (( $ret != 35 )) +then err_exit "exit 35 is $ret not 35" +fi +(return 35) +ret=$? +if (( $ret != 35 )) +then err_exit "return 35 is $ret not 35" +fi + +foo 0 0 || err_exit "foo 0 0: incorrect return" +funcheck 0 0 +foo 0 3 +ret=$? +if (( $ret != 3 )) +then err_exit "foo 0 3: return is $ret not 3" +fi +funcheck 0 3 +foo 2 0 || err_exit "foo 2 0: incorrect return" +[[ $bar = EXIT ]] || err_exit "foo 2 0: bar exit trap not set" +funcheck 2 0 +foo 2 3 +ret=$? +if (( $ret != 3 )) +then err_exit "foo 2 3: return is $ret not 3" +fi +[[ $bar = EXIT ]] || err_exit "foo 2 3: bar exit trap not set" +funcheck 2 3 +(foo 3 3) +ret=$? +if (( $ret != 3 )) +then err_exit "foo 3 3: return is $ret not 3" +fi +foo=EXIT +funcheck 3 3 +cat > $file <<! +return 3 +exit 4 +! +( . $file ) +ret=$? +if (( $ret != 3 )) +then err_exit "return in dot script is $ret should be 3" +fi +chmod 755 $file +( $file ) +ret=$? +if (( $ret != 3 )) +then err_exit "return in script is $ret should be 3" +fi +cat > $file <<! +: line 1 +# next line should fail and cause an exit +: > / +exit 4 +! +( . $file ; exit 5 ) 2> /dev/null +ret=$? +if (( $ret != 1 )) +then err_exit "error in dot script is $ret should be 1" +fi +( $file; exit 5 ) 2> /dev/null +ret=$? +if (( $ret != 5 )) +then err_exit "error in script is $ret should be 5" +fi +cat > $file <<\! +print -r -- "$0" +! +x=$( . $file) +if [[ $x != $0 ]] +then err_exit "\$0 in a dot script is $x. Should be $0" +fi +x=$($SHELL -i 2> /dev/null <<\! +typeset -i x=1/0 +print hello +! +) +if [[ $x != hello ]] +then err_exit "interactive shell terminates with error in bltin" +fi +x=$( set -e + false + print bad + ) +if [[ $x != '' ]] +then err_exit "set -e doesn't terminate script on error" +fi +x=$( set -e + trap 'exit 0' EXIT + false + print bad + ) +if (( $? != 0 )) +then err_exit "exit 0 in trap should doesn't set exit value to 0" +fi +$SHELL <<\! +trap 'exit 8' EXIT +exit 1 +! +if (( $? != 8 )) +then err_exit "exit 8 in trap should set exit value to 8" +fi +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/select.sh b/usr/src/lib/libshell/common/tests/select.sh new file mode 100644 index 0000000000..aec1903fb7 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/select.sh @@ -0,0 +1,63 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +trap "rm -f /tmp/Sh$$*" EXIT +PS3='ABC ' + +cat > /tmp/Sh$$.1 <<\! +1) foo +2) bar +3) bam +! + +select i in foo bar bam +do case $i in + foo) break;; + *) err_exit "select 1 not working" + break;; + esac +done 2> /dev/null <<! +1 +! + +unset i +select i in foo bar bam +do case $i in + foo) err_exit "select foo not working" 2>&3 + break;; + *) if [[ $REPLY != foo ]] + then err_exit "select REPLY not correct" 2>&3 + fi + ( set -u; : $i ) || err_exit "select: i not set to null" 2>&3 + break;; + esac +done 3>&2 2> /tmp/Sh$$.2 <<! +foo +! +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/shtests b/usr/src/lib/libshell/common/tests/shtests new file mode 100644 index 0000000000..a094d8ed20 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/shtests @@ -0,0 +1,63 @@ +# This program runs ksh regression tests +# shtests [ name=value ... ] a.sh b.sh ... + +unset DISPLAY ENV FIGNORE +LANG=C +LC_ALL=C +time=1 +while : +do case $1 in + *=*) n=${1%%=*} + v=${1#*=} + eval $n=\'$v\' + export $n + ;; + -t|--t*)time= + ;; + *) break + ;; + esac + shift +done +export LANG LC_ALL PATH PWD SHELL +PWD=`pwd` +SHELL=${SHELL-ksh} +case $0 in +/*) d=`dirname $0`;; +*/*) d=$PWD/`dirname $0`;; +*) d=$PWD;; +esac +case $SHELL in +/*) ;; +*/*) SHELL=$d/$SHELL;; +*) SHELL=$(whence $SHELL);; +esac +PATH=/bin:/usr/bin +if [[ -d /usr/ucb ]] +then PATH=$PATH:/usr/ucb +fi +PATH=$PATH:$d +if [[ $INSTALLROOT && -r $INSTALLROOT/bin/.paths ]] +then PATH=$INSTALLROOT/bin:$PATH +fi +for i in ${*-*.sh} +do echo test $i begins ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} + t=$(grep -c err_exit $i) + if (( $t )) + then (( t = $t - 1 )) + fi + T=test + if (( $t != 1 )) + then T=${T}s + fi + E=error + if $SHELL $i + then echo test $i passed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} "[ $t $T 0 ${E}s ]" + else e=$? + E=error + if (( $e != 1 )) + then E=${E}s + fi + echo test $i failed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} with exit code $e "[ $t $T $e $E ]" + fi +done diff --git a/usr/src/lib/libshell/common/tests/substring.sh b/usr/src/lib/libshell/common/tests/substring.sh new file mode 100644 index 0000000000..2859d65671 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/substring.sh @@ -0,0 +1,504 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 j=4 +base=/home/dgk/foo//bar +string1=$base/abcabcabc +if [[ ${string1:0} != "$string1" ]] +then err_exit "string1:0" +fi +if [[ ${string1: -1} != "c" ]] +then err_exit "string1: -1" +fi +if [[ ${string1:0:1000} != "$string1" ]] +then err_exit "string1:0" +fi +if [[ ${string1:1} != "${string1#?}" ]] +then err_exit "string1:1" +fi +if [[ ${string1:1:4} != home ]] +then err_exit "string1:1:4" +fi +if [[ ${string1: -5:4} != bcab ]] +then err_exit "string1: -5:4" +fi +if [[ ${string1:1:j} != home ]] +then err_exit "string1:1:j" +fi +if [[ ${string1:(j?1:0):j} != home ]] +then err_exit "string1:(j?1:0):j" +fi +if [[ ${string1%*zzz*} != "$string1" ]] +then err_exit "string1%*zzz*" +fi +if [[ ${string1%%*zzz*} != "$string1" ]] +then err_exit "string1%%*zzz*" +fi +if [[ ${string1#*zzz*} != "$string1" ]] +then err_exit "string1#*zzz*" +fi +if [[ ${string1##*zzz*} != "$string1" ]] +then err_exit "string1##*zzz*" +fi +if [[ ${string1%+(abc)} != "$base/abcabc" ]] +then err_exit "string1%+(abc)" +fi +if [[ ${string1%%+(abc)} != "$base/" ]] +then err_exit "string1%%+(abc)" +fi +if [[ ${string1%/*} != "$base" ]] +then err_exit "string1%/*" +fi +if [[ "${string1%/*}" != "$base" ]] +then err_exit '"string1%/*"' +fi +if [[ ${string1%"/*"} != "$string1" ]] +then err_exit 'string1%"/*"' +fi +if [[ ${string1%%/*} != "" ]] +then err_exit "string1%%/*" +fi +if [[ ${string1#*/bar} != /abcabcabc ]] +then err_exit "string1#*bar" +fi +if [[ ${string1##*/bar} != /abcabcabc ]] +then err_exit "string1#*bar" +fi +if [[ "${string1#@(*/bar|*/foo)}" != //bar/abcabcabc ]] +then err_exit "string1#@(*/bar|*/foo)" +fi +if [[ ${string1##@(*/bar|*/foo)} != /abcabcabc ]] +then err_exit "string1##@(*/bar|*/foo)" +fi +if [[ ${string1##*/@(bar|foo)} != /abcabcabc ]] +then err_exit "string1##*/@(bar|foo)" +fi +foo=abc +if [[ ${foo#a[b*} != abc ]] +then err_exit "abc#a[b*} != abc" +fi +if [[ ${foo//[0-9]/bar} != abc ]] +then err_exit '${foo//[0-9]/bar} not expanding correctly' +fi +foo='(abc)' +if [[ ${foo#'('} != 'abc)' ]] +then err_exit "(abc)#( != abc)" +fi +if [[ ${foo%')'} != '(abc' ]] +then err_exit "(abc)%) != (abc" +fi +foo=a123b456c +if [[ ${foo/[0-9]?/""} != a3b456c ]] +then err_exit '${foo/[0-9]?/""} not expanding correctly' +fi +if [[ ${foo//[0-9]/""} != abc ]] +then err_exit '${foo//[0-9]/""} not expanding correctly' +fi +if [[ ${foo/#a/b} != b123b456c ]] +then err_exit '${foo/#a/b} not expanding correctly' +fi +if [[ ${foo/#?/b} != b123b456c ]] +then err_exit '${foo/#?/b} not expanding correctly' +fi +if [[ ${foo/%c/b} != a123b456b ]] +then err_exit '${foo/%c/b} not expanding correctly' +fi +if [[ ${foo/%?/b} != a123b456b ]] +then err_exit '${foo/%?/b} not expanding correctly' +fi +while read -r pattern string expected +do if (( expected )) + then if [[ $string != $pattern ]] + then err_exit "$pattern does not match $string" + fi + if [[ ${string##$pattern} != "" ]] + then err_exit "\${$string##$pattern} not null" + fi + if [ "${string##$pattern}" != '' ] + then err_exit "\"\${$string##$pattern}\" not null" + fi + if [[ ${string/$pattern} != "" ]] + then err_exit "\${$string/$pattern} not null" + fi + else if [[ $string == $pattern ]] + then err_exit "$pattern matches $string" + fi + fi +done <<- \EOF + +(a)*+(a) aabca 1 + !(*.o) foo.o 0 + !(*.o) foo.c 1 +EOF +xx=a/b/c/d/e +yy=${xx#*/} +if [[ $yy != b/c/d/e ]] +then err_exit '${xx#*/} != a/b/c/d/e when xx=a/b/c/d/e' +fi +if [[ ${xx//\//\\} != 'a\b\c\d\e' ]] +then err_exit '${xx//\//\\} not working' +fi +x=[123]def +if [[ "${x//\[(*)\]/\{\1\}}" != {123}def ]] +then err_exit 'closing brace escape not working' +fi +unset foo +foo=one/two/three +if [[ ${foo//'/'/_} != one_two_three ]] +then err_exit 'single quoting / in replacements failed' +fi +if [[ ${foo//"/"/_} != one_two_three ]] +then err_exit 'double quoting / in replacements failed' +fi +if [[ ${foo//\//_} != one_two_three ]] +then err_exit 'escaping / in replacements failed' +fi +function myexport +{ + nameref var=$1 + if (( $# > 1 )) + then export $1=$2 + fi + if (( $# > 2 )) + then print $(myexport "$1" "$3" ) + return + fi + typeset val + val=$(export | grep "^$1=") + print ${val#"$1="} + +} +export dgk=base +if [[ $(myexport dgk fun) != fun ]] +then err_exit 'export inside function not working' +fi +val=$(export | grep "^dgk=") +if [[ ${val#dgk=} != base ]] +then err_exit 'export not restored after function call' +fi +if [[ $(myexport dgk fun fun2) != fun2 ]] +then err_exit 'export inside function not working with recursive function' +fi +val=$(export | grep "^dgk=") +if [[ ${val#dgk=} != base ]] +then err_exit 'export not restored after recursive function call' +fi +if [[ $(dgk=try3 myexport dgk) != try3 ]] +then err_exit 'name=value not added to export list with function call' +fi +val=$(export | grep "^dgk=") +if [[ ${val#dgk=} != base ]] +then err_exit 'export not restored name=value function call' +fi +unset zzz +if [[ $(myexport zzz fun) != fun ]] +then err_exit 'export inside function not working for zzz' +fi +if [[ $(export | grep "zzz=") ]] +then err_exit 'zzz exported after function call' +fi +set -- foo/bar bam/yes last/file/done +if [[ ${@/*\/@(*)/${.sh.match[1]}} != 'bar yes done' ]] +then err_exit '.sh.match not working with $@' +fi +if [[ ${@/*\/@(*)/\1} != 'bar yes done' ]] +then err_exit '\1 not working with $@' +fi +var=(foo/bar bam/yes last/file/done) +if [[ ${var[@]/*\/@(*)/${.sh.match[1]}} != 'bar yes done' ]] +then err_exit '.sh.match not working with ${var[@]}' +fi +if [[ ${var[@]/*\/@(*)/\1} != 'bar yes done' ]] +then err_exit '\1 not working with ${var[@]}' +fi +var='abc_d2ef.462abc %%' +if [[ ${var/+(\w)/Q} != 'Q.462abc %%' ]] +then err_exit '${var/+(\w)/Q} not workding' +fi +if [[ ${var//+(\w)/Q} != 'Q.Q %%' ]] +then err_exit '${var//+(\w)/Q} not workding' +fi +if [[ ${var//+(\S)/Q} != 'Q Q' ]] +then err_exit '${var//+(\S)/Q} not workding' +fi +if [[ "$(LC_ALL=debug $SHELL <<- \+EOF+ + x=a<2bc><3xyz>g + print ${#x} + +EOF+)" != 4 + ]] +then err_exit '${#x} not working with multibyte locales' +fi +foo='foo+bar+' +[[ $(print -r -- ${foo//+/'|'}) != 'foo|bar|' ]] && err_exit "\${foobar//+/'|'}" +[[ $(print -r -- ${foo//+/"|"}) != 'foo|bar|' ]] && err_exit '${foobar//+/"|"}' +[[ $(print -r -- "${foo//+/'|'}") != 'foo|bar|' ]] && err_exit '"${foobar//+/'"'|'"'}"' +[[ $(print -r -- "${foo//+/"|"}") != 'foo|bar|' ]] && err_exit '"${foobar//+/"|"}"' +unset x +x=abcedfg +: ${x%@(d)f@(g)} +[[ ${.sh.match[0]} == dfg ]] || err_exit '.sh.match[0] not dfg' +[[ ${.sh.match[1]} == d ]] || err_exit '.sh.match[1] not d' +[[ ${.sh.match[2]} == g ]] || err_exit '.sh.match[2] not g' +x=abcedddfg +: ${x%%+(d)f@(g)} +[[ ${.sh.match[1]} == ddd ]] || err_exit '.sh.match[1] not ddd' +unset a b +a='\[abc @(*) def\]' +b='[abc 123 def]' +[[ ${b//$a/\1} == 123 ]] || err_exit "\${var/pattern} not working with \[ in pattern" +unset X +$SHELL -c '[[ ! ${X[@]:0:300} ]]' 2> /dev/null || err_exit '${X[@]:0:300} with X undefined fails' +$SHELL -c '[[ ${@:0:300} == "$0" ]]' 2> /dev/null || err_exit '${@:0:300} with no arguments fails' +i=20030704 +[[ ${i#{6}(?)} == 04 ]] || err_exit '${i#{6}(?)} not working' +[[ ${i#{6,6}(?)} == 04 ]] || err_exit '${i#{6,6}(?)} not working' +LC_ALL=posix +i=" ." +[[ $(printf "<%s>\n" ${i#' '}) == '<.>' ]] || err_exit 'printf "<%s>\n" ${i#' '} failed' +unset x +x=foo +[[ "${x%o}(1)" == "fo(1)" ]] || err_exit 'print ${}() treated as pattern' +unset i pattern string +string=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz +integer i +for((i=0; i < ${#string}; i++)) +do pattern+='@(?)' +done +[[ $(string=$string $SHELL -c ": \${string/$pattern/}; print \${.sh.match[26]}") == Z ]] || err_exit -u2 'sh.match[26] not Z' +: ${string/$pattern/} +(( ${#.sh.match[@]} == 53 )) || err_exit '.sh.match has wrong number of elements' +[[ ${.sh.match[@]:2:4} == 'B C D E' ]] || err_exit '${.sh.match[@]:2:4} incorrect' + +D=$';' E=$'\\\\' Q=$'"' S=$'\'' M='nested pattern substitution failed' + +x='-(-)-' +[[ ${x/*%(())*/\1} == '(-)' ]] || err_exit $M +x='-(-)-)-' +[[ ${x/*%(())*/\1} == '(-)' ]] || err_exit $M +x='-(-()-)-' +[[ ${x/*%(())*/\1} == '()' ]] || err_exit $M +x='-(-\)-)-' +[[ ${x/*%(())*/\1} == '(-\)' ]] || err_exit $M +x='-(-\\)-)-' +[[ ${x/*%(())*/\1} == '(-\\)' ]] || err_exit $M +x='-(-(-)-' +[[ ${x/*%(())*/\1} == '(-)' ]] || err_exit $M +x='-(-(-)-)-' +[[ ${x/*%(())*/\1} == '(-)' ]] || err_exit $M +x='-(-[-]-)-' +[[ ${x/*%(()[])*/\1} == '(-[-]-)' ]] || err_exit $M +x='-[-(-)-]-' +[[ ${x/*%(()[])*/\1} == '(-)' ]] || err_exit $M +x='-(-[-)-]-' +[[ ${x/*%(()[])*/\1} == '-(-[-)-]-' ]] || err_exit $M +x='-(-[-]-)-' +[[ ${x/*%([]())*/\1} == '[-]' ]] || err_exit $M +x='-[-(-)-]-' +[[ ${x/*%([]())*/\1} == '[-(-)-]' ]] || err_exit $M +x='-(-[-)-]-' +[[ ${x/*%([]())*/\1} == '-(-[-)-]-' ]] || err_exit $M + +x='-((-))-' +[[ ${x/*%(())*/\1} == '(-)' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(-g)*%(())*/\1} == '((-))-' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(-g:*)*%(())*/\1} == '(-)' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(+g)*%(())*/\1} == '(-)' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(+g:*)*%(())*/\1} == '(-)' ]] || err_exit $M +x='-((-))-' +[[ ${x/*(?)*%(())*(?)*/:\1:\2:\3:} == ':-(:(-):)-:' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(-g)*(?)*%(())*(?)*/:\1:\2:\3:} == '::((-))::-' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(-g:*(?))*%(())*(?)*/:\1:\2:\3:} == '::(-):)-:' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(+g)*(?)*%(())*(?)*/:\1:\2:\3:} == ':-(:(-):)-:' ]] || err_exit $M +x='-((-))-' +[[ ${x/~(+g:*(?))*%(())*(?)*/:\1:\2:\3:} == ':-(:(-):)-:' ]] || err_exit $M +x='call(a+b,x/(c/d),(0));' +[[ ${x/+([[:alnum:]])*([[:space:]])(*%(()))*/:\1:\2:\3:} == ':call::(a+b,x/(c/d),(0)):' ]] || err_exit $M + +x='-(-;-)-' +[[ ${x/*%(()D${D})*/\1} == '-(-;-)-' ]] || err_exit $M +x='-(-);-' +[[ ${x/*%(()D${D})*/\1} == '(-)' ]] || err_exit $M +x='-(-)\;-' +[[ ${x/*%(()D${D})*/\1} == '(-)' ]] || err_exit $M +x='-(-\;-)-' +[[ ${x/*%(()D${D}E${E})*/\1} == '(-\;-)' ]] || err_exit $M +x='-(-)\;-' +[[ ${x/*%(()D${D}E${E})*/\1} == '(-)' ]] || err_exit $M +x='-(-(-)\;-)-' +[[ ${x/*%(()D${D}E${E})*/\1} == '(-)' ]] || err_exit $M + +x='-(-")"-)-' +[[ ${x/*%(()Q${Q})*/\1} == '(-")"-)' ]] || err_exit $M +x='-(-\")"-)-' +[[ ${x/*%(()Q${Q})*/\1} == '(-\")"-)' ]] || err_exit $M +x='-(-\")\"-)-' +[[ ${x/*%(()Q${Q})*/\1} == '(-\")\"-)' ]] || err_exit $M +x=$'-(-\\\'")\\\'-)-' +[[ ${x/*%(()Q${S}Q${Q})*/\1} == $'(-\\\'")\\\'-)' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()Q${S}Q${Q})*/\1} == $'-(-\\\'")"-)-' ]] || err_exit $M +x=$'-(-\\\'")"\'-)-' +[[ ${x/*%(()Q${S}Q${Q})*/\1} == $'(-\\\'")"\'-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()Q${S}Q${Q})*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\')\\\'\'-)-' +[[ ${x/*%(()Q${S}Q${Q})*/\1} == $'-(-\')\\\'\'-)-' ]] || err_exit $M +x=$'-(-\'")\'-)-' +[[ ${x/*%(()L${S}Q${Q})*/\1} == $'(-\'")\'-)' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()L${S}Q${Q})*/\1} == $'-(-\\\'")"-)-' ]] || err_exit $M +x=$'-(-\\\'")"\'-)-' +[[ ${x/*%(()L${S}Q${Q})*/\1} == $'(-\\\'")"\'-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()L${S}Q${Q})*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\')\\\'\'-)-' +[[ ${x/*%(()L${S}Q${Q})*/\1} == $'-(-\')\\\'\'-)-' ]] || err_exit $M +x='-(-")"-)-' +[[ ${x/*%(()Q${Q})*/\1} == '(-")"-)' ]] || err_exit $M +x='-(-\")"-)-' +[[ ${x/*%(()Q${Q})*/\1} == '(-\")"-)' ]] || err_exit $M +x='-(-\")\"-)-' +[[ ${x/*%(()Q${Q})*/\1} == '(-\")\"-)' ]] || err_exit $M + +x='-(-\)-)-' +[[ ${x/*%(()E${E})*/\1} == '(-\)-)' ]] || err_exit $M +x='-(-\\)-)-' +[[ ${x/*%(()E${E})*/\1} == '(-\\)' ]] || err_exit $M +x='-(-\")"-)-' +[[ ${x/*%(()E${E}Q${Q})*/\1} == '(-\")' ]] || err_exit $M +x='-(-\")\"-)-' +[[ ${x/*%(()E${E}Q${Q})*/\1} == '(-\")' ]] || err_exit $M +x=$'-(-\'")"-)-' +[[ ${x/*%(()E${E}Q${S}Q${Q})*/\1} == $'-(-\'")"-)-' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()E${E}Q${S}Q${Q})*/\1} == $'(-\\\'")"-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()E${E}Q${S}Q${Q})*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()E${E}L${S}Q${Q})*/\1} == $'(-\\\'")"-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()E${E}L${S}Q${Q})*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\\"\')\\\'\\"-)-' +[[ ${x/*%(()E${E}L${S}Q${Q})*/\1} == $'(-\\"\')\\\'\\"-)' ]] || err_exit $M +x=$'-(-\\"\')\\\'\\"\'-)-' +[[ ${x/*%(()E${E}L${S}Q${Q})*/\1} == $'-(-\\"\')\\\'\\"\'-)-' ]] || err_exit $M + +x='-(-;-)-' +[[ ${x/*%(()D\;)*/\1} == '-(-;-)-' ]] || err_exit $M +x='-(-);-' +[[ ${x/*%(()D\;)*/\1} == '(-)' ]] || err_exit $M +x='-(-)\;-' +[[ ${x/*%(()D\;)*/\1} == '(-)' ]] || err_exit $M +x='-(-\;-)-' +[[ ${x/*%(()D\;E\\)*/\1} == '(-\;-)' ]] || err_exit $M +x='-(-);-' +[[ ${x/*%(()D\;E\\)*/\1} == '(-)' ]] || err_exit $M +x='-(-)\;-' +[[ ${x/*%(()D\;E\\)*/\1} == '(-)' ]] || err_exit $M +x='-(-(-)\;-)-' +[[ ${x/*%(()D\;E\\)*/\1} == '(-)' ]] || err_exit $M + +x='-(-")"-)-' +[[ ${x/*%(()Q\")*/\1} == '(-")"-)' ]] || err_exit $M +x='-(-\")"-)-' +[[ ${x/*%(()Q\")*/\1} == '(-\")"-)' ]] || err_exit $M +x='-(-\")\"-)-' +[[ ${x/*%(()Q\")*/\1} == '(-\")\"-)' ]] || err_exit $M +x=$'-(-\\\'")\\\'-)-' +[[ ${x/*%(()Q\'Q\")*/\1} == $'(-\\\'")\\\'-)' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()Q\'Q\")*/\1} == $'-(-\\\'")"-)-' ]] || err_exit $M +x=$'-(-\\\'")"\'-)-' +[[ ${x/*%(()Q\'Q\")*/\1} == $'(-\\\'")"\'-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()Q\'Q\")*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\')\\\'\'-)-' +[[ ${x/*%(()Q\'Q\")*/\1} == $'-(-\')\\\'\'-)-' ]] || err_exit $M +x=$'-(-\'")\'-)-' +[[ ${x/*%(()L\'Q\")*/\1} == $'(-\'")\'-)' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()L\'Q\")*/\1} == $'-(-\\\'")"-)-' ]] || err_exit $M +x=$'-(-\\\'")"\'-)-' +[[ ${x/*%(()L\'Q\")*/\1} == $'(-\\\'")"\'-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()L\'Q\")*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\')\\\'\'-)-' +[[ ${x/*%(()L\'Q\")*/\1} == $'-(-\')\\\'\'-)-' ]] || err_exit $M +x='-(-")"-)-' +[[ ${x/*%(()Q\")*/\1} == '(-")"-)' ]] || err_exit $M +x='-(-\")"-)-' +[[ ${x/*%(()Q\")*/\1} == '(-\")"-)' ]] || err_exit $M +x='-(-\")\"-)-' +[[ ${x/*%(()Q\")*/\1} == '(-\")\"-)' ]] || err_exit $M + +x='-(-\)-)-' +[[ ${x/*%(()E\\)*/\1} == '(-\)-)' ]] || err_exit $M +x='-(-\\)-)-' +[[ ${x/*%(()E\\)*/\1} == '(-\\)' ]] || err_exit $M +x='-(-\")"-)-' +[[ ${x/*%(()E\\Q\")*/\1} == '(-\")' ]] || err_exit $M +x='-(-\")\"-)-' +[[ ${x/*%(()E\\Q\")*/\1} == '(-\")' ]] || err_exit $M +x=$'-(-\'")"-)-' +[[ ${x/*%(()E\\Q\'Q\")*/\1} == $'-(-\'")"-)-' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()E\\Q\'Q\")*/\1} == $'(-\\\'")"-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()E\\Q\'Q\")*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\\\'")"-)-' +[[ ${x/*%(()E\\L\'Q\")*/\1} == $'(-\\\'")"-)' ]] || err_exit $M +x=$'-(-\\"\')\'\\"-)-' +[[ ${x/*%(()E\\L\'Q\")*/\1} == $'(-\\"\')\'\\"-)' ]] || err_exit $M +x=$'-(-\\"\')\\\'\\"-)-' +[[ ${x/*%(()E\\L\'Q\")*/\1} == $'(-\\"\')\\\'\\"-)' ]] || err_exit $M +x=$'-(-\\"\')\\\'\\"\'-)-' +[[ ${x/*%(()E\\L\'Q\")*/\1} == $'-(-\\"\')\\\'\\"\'-)-' ]] || err_exit $M + +pattern=00 +var=100 +[[ $( print $(( ${var%%00} )) ) == 1 ]] || err_exit "arithmetic with embeddded patterns fails" +[[ $( print $(( ${var%%$pattern} )) ) == 1 ]] || err_exit "arithmetic with embeddded pattern variables fails" +if [[ ax == @(a)* ]] && [[ ${.sh.match[1]:0:${#.sh.match[1]}} != a ]] +then err_exit '${.sh.match[1]:1:${#.sh.match[1]}} not expanding correctly' +fi + +string='foo(d:\nt\box\something)bar' +expected='d:\nt\box\something' +[[ ${string/*\(+([!\)])\)*/\1} == "$expected" ]] || err_exit "substring expansion failed '${string/*\(+([!\)])\)*/\1}' returned -- '$expected' expected" +if [[ $($SHELL -c $'export LC_ALL=en_US.UTF-8; print -r "\342\202\254\342\202\254\342\202\254\342\202\254w\342\202\254\342\202\254\342\202\254\342\202\254" | wc -m' 2>/dev/null) == 10 ]] +then LC_ALL=en_US.UTF-8 $SHELL -c b1=$'"\342\202\254\342\202\254\342\202\254\342\202\254w\342\202\254\342\202\254\342\202\254\342\202\254"; [[ ${b1:4:1} == w ]]' || err_exit 'Multibyte ${var:offset:len} not working correctly' +fi +{ $SHELL -c 'unset x;[[ ${SHELL:$x} == $SHELL ]]';} 2> /dev/null || err_exit '${var:$x} fails when x is not set' +{ $SHELL -c 'x=;[[ ${SHELL:$x} == $SHELL ]]';} 2> /dev/null || err_exit '${var:$x} fails when x is null' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/sun_solaris_getconf.sh b/usr/src/lib/libshell/common/tests/sun_solaris_getconf.sh new file mode 100644 index 0000000000..a8eaeb75f6 --- /dev/null +++ b/usr/src/lib/libshell/common/tests/sun_solaris_getconf.sh @@ -0,0 +1,160 @@ +#!/bin/ksh93 + +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +# +# sun_solaris_getconf.sh - test the ksh93 getconf builtin for compatibility +# with /usr/bin/getconf +# + +# test setup +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 + + +# setup +integer mismatch # counts mismatches between builtin and external command +integer getconf_keys # counts tests (paranoid check to make sure the test loop works) +export PATH=/usr/bin:/bin + +# prechecks +[ ! -f "/bin/getconf" ] && err_exit '/bin/getconf not found.' +[ ! -x "/bin/getconf" ] && err_exit '/bin/getconf not executable.' + +# compare builtin getconf output with /usr/bin/getconf +function compare_normal +{ + mismach=0 getconf_keys=0 + /usr/bin/getconf -a | + while read i ; do + let getconf_keys++ + t="${i%:*}" + + a="$(getconf "$t" 2>/dev/null)" + b="$(/usr/bin/getconf "$t" 2>/dev/null)" + + if [ "$a" != "$b" ] ; then + print -u2 "getconf/normal built mismatch: |$t|:|$a| != |$b|" + let mismatch++ + fi + done +} + +# compare builtin getconf output with /usr/bin/getconf while passing a path argument +function compare_path +{ + mismach=0 getconf_keys=0 + /usr/bin/getconf -a | + while read i ; do + let getconf_keys++ + t="${i%:*}" + + a="$(getconf "$t" "/tmp" 2>/dev/null)" + b="$(/usr/bin/getconf "$t" "/tmp" 2>/dev/null)" + + if [ "$a" != "$b" ] ; then + print -u2 "getconf/path built mismatch: |$t|:|$a| != |$b|" + let mismatch++ + fi + done +} + +# future versions of this test should test the following ${PATH}s, too: +# "/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin" \ +#"/usr/xpg4/bin:/bin:/usr/bin" \ +for i in \ + "/bin:/usr/bin" +do + export PATH="${i}" + + ## test whether the getconf builtin is available + if [ "$(builtin | fgrep "/bin/getconf")" = "" ] ; then + err_exit '/bin/getconf not found in the list of builtins.' + fi + + + ## compare "getconf -a" output + if [ "$(getconf -a)" != "$(/usr/bin/getconf -a)" ] ; then + err_exit 'getconf -a output mismatch.' + fi + + + ## check for a key which is only supported by the AST builtin version of getconf: + if [ "$(getconf LIBPREFIX)" != "lib" ] ; then + err_exit 'getconf LIBPREFIX did not return "lib".' + fi + + + ## run normal test + compare_normal + [ ${getconf_keys} -eq 0 ] && err_exit "getconf/normal not working (PATH=${PATH})." + [ ${mismatch} -gt 0 ] && err_exit "getconf/normal test found ${mismatch} differences (PATH=${PATH})." + + # run the same test in a seperate shell + # (we explicitly test this because ast-ksh.2007-01-11 picks up /usr/xpg6/bin/getconf + # if /usr/xpg6/bin/ comes in ${PATH} before /usr/bin (this happens only of ${PATH} + # contains /usr/xpg6/bin before ksh93 is started)). + ${SHELL} -c "integer mismatch ; \ + integer getconf_keys ; \ + $(functions) ; \ + compare_normal ; + [ \${getconf_keys} -eq 0 ] && err_exit \"getconf/normal not working (PATH=\${PATH}).\" ; \ + [ \${mismatch} -gt 0 ] && err_exit \"getconf/normal test found \${mismatch} differences (PATH=\${PATH}).\" ; \ + exit $((Errors))" + let Errors+=$? + + + ## run test with path argument + compare_path + [ ${getconf_keys} -eq 0 ] && err_exit "getconf/path not working." + [ ${mismatch} -gt 0 ] && err_exit "getconf/path test found ${mismatch} differences." + + # run the same test in a seperate shell + # (see comment above) + ${SHELL} -c "integer mismatch ; \ + integer getconf_keys ; \ + $(functions) ; \ + compare_path ; + [ \${getconf_keys} -eq 0 ] && err_exit \"getconf/normal not working (PATH=\${PATH}).\" ; \ + [ \${mismatch} -gt 0 ] && err_exit \"getconf/normal test found \${mismatch} differences (PATH=\${PATH}).\" ; \ + exit $((Errors))" + let Errors+=$? +done + +# test done +exit $((Errors)) + diff --git a/usr/src/lib/libshell/common/tests/tilde.sh b/usr/src/lib/libshell/common/tests/tilde.sh new file mode 100644 index 0000000000..6ebfd8504c --- /dev/null +++ b/usr/src/lib/libshell/common/tests/tilde.sh @@ -0,0 +1,87 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r $Command: "$@" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +function home # id +{ + typeset IFS=: pwd=/etc/passwd + set -o noglob + if [[ -f $pwd ]] && grep -c "^$1:" $pwd > /dev/null + then set -- $(grep "^$1:" $pwd) + print -r -- "$6" + else print . + fi +} + +Command=${0##*/} +integer Errors=0 +OLDPWD=/bin +if [[ ~ != $HOME ]] +then err_exit '~' not $HOME +fi +x=~ +if [[ $x != $HOME ]] +then err_exit x=~ not $HOME +fi +x=x:~ +if [[ $x != x:$HOME ]] +then err_exit x=x:~ not x:$HOME +fi +if [[ ~+ != $PWD ]] +then err_exit '~' not $PWD +fi +x=~+ +if [[ $x != $PWD ]] +then err_exit x=~+ not $PWD +fi +if [[ ~- != $OLDPWD ]] +then err_exit '~' not $PWD +fi +x=~- +if [[ $x != $OLDPWD ]] +then err_exit x=~- not $OLDPWD +fi +for u in root Administrator +do h=$(home $u) + if [[ $h != . ]] + then [[ ~$u -ef $h ]] || err_exit "~$u not $h" + x=~$u + [[ $x -ef $h ]] || "x=~$u not $h" + break + fi +done +x=~%% +if [[ $x != '~%%' ]] +then err_exit 'x='~%%' not '~%% +fi +x=~:~ +if [[ $x != "$HOME:$HOME" ]] +then err_exit x=~:~ not $HOME:$HOME +fi +HOME=/ +[[ ~ == / ]] || err_exit '~ should be /' +[[ ~/foo == /foo ]] || err_exit '~/foo should be /foo when ~==/' +exit $((Errors)) diff --git a/usr/src/lib/libshell/common/tests/variables.sh b/usr/src/lib/libshell/common/tests/variables.sh new file mode 100644 index 0000000000..bc499c890c --- /dev/null +++ b/usr/src/lib/libshell/common/tests/variables.sh @@ -0,0 +1,558 @@ +######################################################################## +# # +# This software is part of the ast package # +# Copyright (c) 1982-2007 AT&T Knowledge Ventures # +# and is licensed under the # +# Common Public License, Version 1.0 # +# by AT&T Knowledge Ventures # +# # +# A copy of the License is available at # +# http://www.opensource.org/licenses/cpl1.0.txt # +# (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) # +# # +# Information and Software Systems Research # +# AT&T Research # +# Florham Park NJ # +# # +# David Korn <dgk@research.att.com> # +# # +######################################################################## +function err_exit +{ + print -u2 -n "\t" + print -u2 -r ${Command}[$1]: "${@:2}" + let Errors+=1 +} +alias err_exit='err_exit $LINENO' + +Command=${0##*/} +integer Errors=0 +# RANDOM +if (( RANDOM==RANDOM || $RANDOM==$RANDOM )) +then err_exit RANDOM variable not working +fi +# SECONDS +sleep 3 +if (( SECONDS < 2 )) +then err_exit SECONDS variable not working +fi +# _ +set abc def +if [[ $_ != def ]] +then err_exit _ variable not working +fi +# ERRNO +#set abc def +#rm -f foobar# +#ERRNO= +#2> /dev/null < foobar# +#if (( ERRNO == 0 )) +#then err_exit ERRNO variable not working +#fi +# PWD +if [[ ! $PWD -ef . ]] +then err_exit PWD variable not working +fi +# PPID +if [[ $($SHELL -c 'print $PPID') != $$ ]] +then err_exit PPID variable not working +fi +# OLDPWD +old=$PWD +cd / +if [[ $OLDPWD != $old ]] +then err_exit OLDPWD variable not working +fi +cd $old || err_exit cd failed +# REPLY +read <<-! + foobar + ! +if [[ $REPLY != foobar ]] +then err_exit REPLY variable not working +fi +integer save=$LINENO +# LINENO +LINENO=10 +# +# These lines intentionally left blank +# +if (( LINENO != 13)) +then err_exit LINENO variable not working +fi +LINENO=save+10 +IFS=: +x=a::b::c +if [[ $x != a::b::c ]] +then err_exit "Word splitting on constants" +fi +set -- $x +if [[ $# != 5 ]] +then err_exit ":: doesn't separate null arguments " +fi +set x +if x$1=0 2> /dev/null +then err_exit "x\$1=value treated as an assignment" +fi +# check for attributes across subshells +typeset -i x=3 +y=1/0 +if ( typeset x=y) 2> /dev/null +then print -u2 "attributes not passed to subshells" +fi +unset x +function x.set +{ + nameref foo=${.sh.name}.save + foo=${.sh.value} + .sh.value=$0 +} +x=bar +if [[ $x != x.set ]] +then err_exit 'x.set does not override assignment' +fi +x.get() +{ + nameref foo=${.sh.name}.save + .sh.value=$foo +} + +if [[ $x != bar ]] +then err_exit 'x.get does not work correctly' +fi +typeset +n foo +unset foo +foo=bar +( + unset foo + set +u + if [[ $foo != '' ]] + then err_exit '$foo not null after unset in subsehll' + fi +) +if [[ $foo != bar ]] +then err_exit 'unset foo in subshell produces side effect ' +fi +unset foo +if [[ $( { : ${foo?hi there} ; } 2>&1) != *'hi there' ]] +then err_exit '${foo?hi there} with foo unset does not print hi there on 2' +fi +x=$0 +set foobar +if [[ ${@:0} != "$x foobar" ]] +then err_exit '${@:0} not expanding correctly' +fi +set -- +if [[ ${*:0:1} != "$0" ]] +then err_exit '${@:0} not expanding correctly' +fi +ACCESS=0 +function COUNT.set +{ + (( ACCESS++ )) +} +COUNT=0 +(( COUNT++ )) +if (( COUNT != 1 || ACCESS!=2 )) +then err_exit " set discipline failure COUNT=$COUNT ACCESS=$ACCESS" +fi +LANG=C > /dev/null 2>&1 +if [[ $LANG != C ]] +then err_exit "C locale not working" +fi +unset RANDOM +unset -n foo +foo=junk +function foo.get +{ + .sh.value=stuff + unset -f foo.get +} +if [[ $foo != stuff ]] +then err_exit "foo.get discipline not working" +fi +if [[ $foo != junk ]] +then err_exit "foo.get discipline not working after unset" +fi +# special variables +set -- 1 2 3 4 5 6 7 8 9 10 +sleep 1000 & +if [[ $(print -r -- ${#10}) != 2 ]] +then err_exit '${#10}, where ${10}=10 not working' +fi +for i in @ '*' ! '#' - '?' '$' +do false + eval foo='$'$i bar='$'{$i} + if [[ ${foo} != "${bar}" ]] + then err_exit "\$$i not equal to \${$i}" + fi + command eval bar='$'{$i%?} 2> /dev/null || err_exit "\${$i%?} gives syntax error" + if [[ $i != [@*] && ${foo%?} != "$bar" ]] + then err_exit "\${$i%?} not correct" + fi + command eval bar='$'{$i#?} 2> /dev/null || err_exit "\${$i#?} gives syntax error" + if [[ $i != [@*] && ${foo#?} != "$bar" ]] + then err_exit "\${$i#?} not correct" + fi + command eval foo='$'{$i} bar='$'{#$i} || err_exit "\${#$i} gives synta +x error" + if [[ $i != @([@*]) && ${#foo} != "$bar" ]] + then err_exit "\${#$i} not correct" + fi +done +kill $! +unset x +CDPATH=/ +x=$(cd tmp) +if [[ $x != /tmp ]] +then err_exit 'CDPATH does not display new directory' +fi +mkdir /tmp/ksh$$ +CDPATH=/: +x=$(cd /tmp;cd ksh$$) +if [[ $x ]] +then err_exit 'CDPATH displays new directory when not used' +fi +x=$(cd tmp/ksh$$) +if [[ $x != /tmp/ksh$$ ]] +then err_exit "CDPATH tmp/ksh$$ does not display new directory" +fi +cd / +rm -rf /tmp/ksh$$ +TMOUT=100 +(TMOUT=20) +if (( TMOUT !=100 )) +then err_exit 'setting TMOUT in subshell affects parent' +fi +unset y +function setdisc # var +{ + eval function $1.get' + { + .sh.value=good + } + ' +} +y=bad +setdisc y +if [[ $y != good ]] +then err_exit 'setdisc function not working' +fi +integer x=$LINENO +: $'\ +' +if (( LINENO != x+3 )) +then err_exit '\<newline> gets linenumber count wrong' +fi +set -- +set -- "${@-}" +if (( $# !=1 )) +then err_exit '"${@-}" not expanding to null string' +fi +for i in : % + / 3b '**' '***' '@@' '{' '[' '}' !! '*a' '@a' '$foo' +do (eval : \${"$i"} 2> /dev/null) && err_exit "\${$i} not an syntax error" +done +unset IFS +( IFS=' ' ; read -r a b c <<-! + x y z + ! + if [[ $b ]] + then err_exit 'IFS=" " not causing adjacent space to be null string' + fi +) +read -r a b c <<-! +x y z +! +if [[ $b != y ]] +then err_exit 'IFS not restored after subshell' +fi + +# The next part generates 3428 IFS set/read tests. + +unset IFS x +function split +{ + i=$1 s=$2 r=$3 + IFS=': ' + set -- $i + IFS=' ' + g="[$#]" + while : + do case $# in + 0) break ;; + esac + g="$g($1)" + shift + done + case "$g" in + "$s") ;; + *) err_exit "IFS=': '; set -- '$i'; expected '$s' got '$g'" ;; + esac + print "$i" | IFS=": " read arg rem; g="($arg)($rem)" + case "$g" in + "$r") ;; + *) err_exit "IFS=': '; read '$i'; expected '$r' got '$g'" ;; + esac +} +for str in \ + '-' \ + 'a' \ + '- -' \ + '- a' \ + 'a -' \ + 'a b' \ + '- - -' \ + '- - a' \ + '- a -' \ + '- a b' \ + 'a - -' \ + 'a - b' \ + 'a b -' \ + 'a b c' +do + IFS=' ' + set x $str + shift + case $# in + 0) continue ;; + esac + f1=$1 + case $f1 in + '-') f1='' ;; + esac + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f1$d1 in + '') split "$d0$f1$d1" "[0]" "()()" ;; + ' ') ;; + *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; + esac + done + done + continue + ;; + esac + f2=$1 + case $f2 in + '-') f2='' ;; + esac + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ' ' ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; + ' ') ;; + *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; + esac + done + done + done + continue + ;; + esac + f3=$1 + case $f3 in + '-') f3='' ;; + esac + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + ' ') continue ;; + esac + case ' ' in + $f2$d2|$d2$f3) continue ;; + esac + for d3 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f3$d3 in + '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; + ' ') ;; + *) x=$f2$d2$f3$d3 + x=${x#' '} + x=${x%' '} + split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" + ;; + esac + done + done + done + done + continue + ;; + esac +done +unset IFS + +if [[ $( (print ${12345:?}) 2>&1) != *12345* ]] +then err_exit 'Incorrect error message with ${12345?}' +fi +unset foobar +if [[ $( (print ${foobar:?}) 2>&1) != *foobar* ]] +then err_exit 'Incorrect error message with ${foobar?}' +fi +unset bar +if [[ $( (print ${bar:?bam}) 2>&1) != *bar*bam* ]] +then err_exit 'Incorrect error message with ${foobar?}' +fi +{ $SHELL -c ' +function foo +{ + typeset SECONDS=0 + sleep 1.5 + print $SECONDS + +} +x=$(foo) +(( x >1 && x < 2 )) +' +} 2> /dev/null || err_exit 'SECONDS not working in function' +trap 'rm -f /tmp/script$$ /tmp/out$$' EXIT +cat > /tmp/script$$ <<-\! + posixfun() + { + unset x + nameref x=$1 + print -r -- "$x" + } + function fun + { + nameref x=$1 + print -r -- "$x" + } + if [[ $1 ]] + then file=${.sh.file} + else print -r -- "${.sh.file}" + fi +! +chmod +x /tmp/script$$ +. /tmp/script$$ 1 +[[ $file == /tmp/script$$ ]] || err_exit ".sh.file not working for dot scripts" +[[ $($SHELL /tmp/script$$) == /tmp/script$$ ]] || err_exit ".sh.file not working for scripts" +[[ $(posixfun .sh.file) == /tmp/script$$ ]] || err_exit ".sh.file not working for posix functions" +[[ $(fun .sh.file) == /tmp/script$$ ]] || err_exit ".sh.file not working for functions" +[[ $(posixfun .sh.fun) == posixfun ]] || err_exit ".sh.fun not working for posix functions" +[[ $(fun .sh.fun) == fun ]] || err_exit ".sh.fun not working for functions" +[[ $(posixfun .sh.subshell) == 1 ]] || err_exit ".sh.subshell not working for posix functions" +[[ $(fun .sh.subshell) == 1 ]] || err_exit ".sh.subshell not working for functions" +( + [[ $(posixfun .sh.subshell) == 2 ]] || err_exit ".sh.subshell not working for posix functions in subshells" + [[ $(fun .sh.subshell) == 2 ]] || err_exit ".sh.subshell not working for functions in subshells" + (( .sh.subshell == 1 )) || err_exit ".sh.subshell not working in a subshell" +) +TIMEFORMAT='this is a test' +[[ $({ { time :;} 2>&1;}) == "$TIMEFORMAT" ]] || err_exit 'TIMEFORMAT not working' +: ${.sh.version} +[[ $(alias integer) == *.sh.* ]] && err_exit '.sh. prefixed to alias name' +: ${.sh.version} +[[ $(whence rm) == *.sh.* ]] && err_exit '.sh. prefixed to tracked alias name' +: ${.sh.version} +[[ $(cd /bin;env | grep PWD) == *.sh.* ]] && err_exit '.sh. prefixed to PWD' +# unset discipline bug fix +dave=dave +function dave.unset +{ + unset dave +} +unset dave +[[ $(typeset +f) == *dave.* ]] && err_exit 'unset discipline not removed' +print 'print ${VAR}' > /tmp/script$$ +VAR=foo /tmp/script$$ > /tmp/out$$ +[[ $(</tmp/out$$) == foo ]] || err_exit 'environment variables not passed to scripts' +( + unset dave + function dave.append + { + .sh.value+=$dave + dave= + } + dave=foo; dave+=bar + [[ $dave == barfoo ]] || exit 2 +) 2> /dev/null +case $? in +0) ;; +1) err_exit 'append discipline not implemented';; +*) err_exit 'append discipline not working';; +esac +.sh.foobar=hello +{ + function .sh.foobar.get + { + .sh.value=world + } +} 2> /dev/null || err_exit "Can't add get discipline to .sh.foobar" +[[ ${.sh.foobar} == world ]] || err_exit 'get discipline for .sh.foobar not working' +x='a|b' +IFS='|' +set -- $x +[[ $2 == b ]] || err_exit '$2 should be b after set' +exec 3>&2 2> /dev/null +set -x +( IFS= ) 2> /dev/null +set +x +exec 2>&3- +set -- $x +[[ $2 == b ]] || err_exit '$2 should be b after subshell' +: & pid=$! +( : & ) +[[ $pid == $! ]] || err_exit '$! value not preserved across subshells' +unset foo +typeset -A foo +function foo.set +{ + case ${.sh.subscript} in + bar) if ((.sh.value > 1 )) + then .sh.value=5 + foo[barrier_hit]=yes + fi + ;; + barrier_hit) + if [[ ${.sh.value} = yes ]] + then foo[barrier_not_hit]=no + else foo[barrier_not_hit]=yes + fi + ;; + esac +} +foo[barrier_hit]=no +foo[bar]=1 +(( foo[bar] == 1 )) || err_exit 'foo[bar] should be 1' +[[ ${foo[barrier_hit]} == no ]] || err_exit 'foo[barrier_hit] should be no' +[[ ${foo[barrier_not_hit]} == yes ]] || err_exit 'foo[barrier_not_hit] should be yes' +foo[barrier_hit]=no +foo[bar]=2 +(( foo[bar] == 5 )) || err_exit 'foo[bar] should be 5' +[[ ${foo[barrier_hit]} == yes ]] || err_exit 'foo[barrier_hit] should be yes' +[[ ${foo[barrier_not_hit]} == no ]] || err_exit 'foo[barrier_not_hit] should be no' +unset x +typeset -i x +function x.set +{ + typeset sub=${.sh.subscript} + (( sub > 0 )) && (( x[sub-1]= x[sub-1] + .sh.value )) +} +x[0]=0 x[1]=1 x[2]=2 x[3]=3 +[[ ${x[@]} == '12 8 5 3' ]] || err_exit 'set discipline for indexed array not working correctly' +((SECONDS=3*4)) +(( SECONDS < 12 || SECONDS > 12.1 )) && err_exit "SECONDS is $SECONDS and should be close to 12" +exit $((Errors)) |