From 6a9f0317b7650e6d19ef04720f246bc991498583 Mon Sep 17 00:00:00 2001 From: Warren Toomey Date: Wed, 22 Jan 2025 14:51:38 +1000 Subject: [PATCH 1/2] Applications/wish: Added the wish shell. --- Applications/Makefile | 6 +- Applications/wish/Ifdefs | 95 +++ Applications/wish/Installation | 54 ++ Applications/wish/LICENSE | 27 + Applications/wish/Makefile.68000 | 1 + Applications/wish/Makefile.common | 23 + Applications/wish/README | 29 + Applications/wish/Wish.1 | 1216 +++++++++++++++++++++++++++++ Applications/wish/Wishrc | 15 + Applications/wish/alias.c | 73 ++ Applications/wish/bind.c | 317 ++++++++ Applications/wish/builtin.c | 275 +++++++ Applications/wish/clebuf.c | 79 ++ Applications/wish/clex.c | 476 +++++++++++ Applications/wish/comlined.c | 775 ++++++++++++++++++ Applications/wish/exec.c | 374 +++++++++ Applications/wish/file.c | 129 +++ Applications/wish/fuzix-wish.pkg | 7 + Applications/wish/global.c | 3 + Applications/wish/header.h | 206 +++++ Applications/wish/hist.c | 151 ++++ Applications/wish/job.c | 530 +++++++++++++ Applications/wish/m_fuzix.h | 26 + Applications/wish/machine.h | 26 + Applications/wish/main.c | 333 ++++++++ Applications/wish/malloc.c | 79 ++ Applications/wish/meta.c | 1073 +++++++++++++++++++++++++ Applications/wish/parse.c | 186 +++++ Applications/wish/posixjob.c | 148 ++++ Applications/wish/prints.c | 353 +++++++++ Applications/wish/proto.h | 227 ++++++ Applications/wish/signal.c | 66 ++ Applications/wish/term.c | 314 ++++++++ Applications/wish/ucbjob.c | 160 ++++ Applications/wish/v7job.c | 162 ++++ Applications/wish/val.c | 134 ++++ Applications/wish/var.c | 201 +++++ Applications/wish/wish | Bin 0 -> 46868 bytes 38 files changed, 8348 insertions(+), 1 deletion(-) create mode 100644 Applications/wish/Ifdefs create mode 100644 Applications/wish/Installation create mode 100644 Applications/wish/LICENSE create mode 100644 Applications/wish/Makefile.68000 create mode 100644 Applications/wish/Makefile.common create mode 100644 Applications/wish/README create mode 100644 Applications/wish/Wish.1 create mode 100644 Applications/wish/Wishrc create mode 100644 Applications/wish/alias.c create mode 100644 Applications/wish/bind.c create mode 100644 Applications/wish/builtin.c create mode 100644 Applications/wish/clebuf.c create mode 100644 Applications/wish/clex.c create mode 100644 Applications/wish/comlined.c create mode 100644 Applications/wish/exec.c create mode 100644 Applications/wish/file.c create mode 100644 Applications/wish/fuzix-wish.pkg create mode 100644 Applications/wish/global.c create mode 100644 Applications/wish/header.h create mode 100644 Applications/wish/hist.c create mode 100644 Applications/wish/job.c create mode 100644 Applications/wish/m_fuzix.h create mode 100644 Applications/wish/machine.h create mode 100644 Applications/wish/main.c create mode 100644 Applications/wish/malloc.c create mode 100644 Applications/wish/meta.c create mode 100644 Applications/wish/parse.c create mode 100644 Applications/wish/posixjob.c create mode 100644 Applications/wish/prints.c create mode 100644 Applications/wish/proto.h create mode 100644 Applications/wish/signal.c create mode 100644 Applications/wish/term.c create mode 100644 Applications/wish/ucbjob.c create mode 100644 Applications/wish/v7job.c create mode 100644 Applications/wish/val.c create mode 100644 Applications/wish/var.c create mode 100644 Applications/wish/wish diff --git a/Applications/Makefile b/Applications/Makefile index 1ec3f9a453..d7f6e1a7c0 100644 --- a/Applications/Makefile +++ b/Applications/Makefile @@ -1,7 +1,7 @@ # APPS = util cmd sh games cave cpm v7games games cursesgames \ as09 ld09 netd MWC flashrom ue cpmfs plato \ - emulators cpnet dw assembler CC cpp ar + emulators cpnet dw assembler CC cpp ar wish .PHONY: $(APPS) @@ -79,6 +79,9 @@ cpp: ar: +(cd ar; $(MAKE) -f Makefile.$(USERCPU)) +wish: + +(cd wish; $(MAKE) -f Makefile.$(USERCPU)) + clean: (cd util; $(MAKE) -f Makefile.$(USERCPU) clean) (cd V7/cmd; $(MAKE) -f Makefile.$(USERCPU) clean) @@ -104,3 +107,4 @@ clean: (cd cpp; $(MAKE) -f Makefile.$(USERCPU) clean) (cd dw; $(MAKE) -f Makefile.$(USERCPU) clean) (cd ar; $(MAKE) -f Makefile.$(USERCPU) clean) + (cd wish; $(MAKE) -f Makefile.$(USERCPU) wish) diff --git a/Applications/wish/Ifdefs b/Applications/wish/Ifdefs new file mode 100644 index 0000000000..b4b38b8683 --- /dev/null +++ b/Applications/wish/Ifdefs @@ -0,0 +1,95 @@ + #defines used in Wish 2.0 + ------------------------- + +$Revision: 41.1 $ $Date: 1995/12/29 02:57:38 $ + +Here are the defines that are used in Wish 2.0. Code that uses specific +library calls and system calls are appropriately ifdef'd. + +DEBUG /* debugging code used in development */ +MALLOCDEBUG /* malloc debugging code used in development */ +MINIX1_5 /* Minix 1.5 specific stuff */ +NOTYET /* code that's not being used, & may never be */ +POSIXJOB /* POSIX style job control */ +PROTO /* Ansi-C function prototypes */ +STDARG /* use */ +UCBJOB /* 4.3BSD style job control */ +USES_DIRECT /* struct direct instead of struct dirent */ +USES_GETWD /* getwd() use instead of getcwd() */ +USES_MORESIG /* sgtty moresig code */ +USES_SGTTY /* bcopy() and sgttyb/TCIOGETP use */ +USES_TCGETA /* termio/TCGETA use */ +USES_TERMIOS /* termios/tcgetattr use */ +V7JOB /* 7th Edition ptrace(1) job control */ +VARARGS /* use */ + +A small amount of code is specific to some systems. The following systems +are defined in the m_*.h files. Only the MINIX1_5 define is used in the +source code. If you are porting Wish to another system, define a name for +the system, but try not to use this when #ifdef'ing code. Use the #define's +above, or create a new define, e.g #define USES_INDEX if using index(). + + +AUX2_01 /* A/UX 2.01 */ +COHERENT /* Coherent */ +FREEBSD_1 /* FreeBSD 1.x */ +FREEBSD_2 /* FreeBSD 2.x */ +GENBSD /* Generic BSD 4.x machine */ +GENSYSV /* Generic SysV machine */ +J386BSD0_1 /* 386BSD-0.1 */ +MINIX1_5 /* Minix 1.5 */ +MINIX1_7 /* Minix 1.7 */ +OSX5_1_ATT /* Pyramid under OSx 5.1a, SysV Universe */ +OSX5_1_BSD /* Pyramid OSx 5.1a, BSD Universe */ +SOLARIS_2 /* Solaris 2.x */ +SUNOS3 /* Sun OS 3.x */ +SUNOS4 /* Sun OS 4.x */ +ULTRIX4_2 /* Ultrix 4.2 */ + +Job Control +----------- + + There are four styles of job control available: 7th Edition Unix +job control (using ptrace), Berkeley job control, POSIX job control and +no job control whatsoever. These are enabled by the following defines, only +one of which should be defined. + +#define POSIXJOB /* POSIX job control */ +#define V7JOB /* 7th Edition Unix job control */ +#define UCBJOB /* Berkeley job control */ + +Argument Passing +---------------- + + The prints() [and friends] functions take a variable number of +arguments. Where possible, argument passing macros should be used. There +are three main types: no argument passing macros, varargs.h style argument +passing and stdarg.h stype argument passing macros. These are enabled by the +following defines, only one of which should be defined. + +#define VARARGS /* varargs.h style argument passing */ +#define STDARG /* stdarg.h stype argument passing */ + + +Miscellaneous +------------- + + There are a few other miscellaneous defines which control things +like types of functions used for directory searches, current working directory +information. + +#define USES_GETWD /* Uses getwd(), not getcwd() */ + +#define PLONG int /* The type of arguments to ptrace(). Minix 1.5 */ + /* uses longs! This is only used when V7JOB is on */ + +#define SIGTYPE void /* Signal handlers return this type. Usually void or */ + /* int, but might also be __sighandler_t */ + +#define USES_DIRECT /* Uses struct direct, not struct dirent */ + +#define PROTO /* The compiler supports Ansi-C prototyped function */ + /* declarations */ + +#define DEBUG /* Debugging code in Wish is #ifdef'd with this. You */ + /* should not need (or want) to turn this on. */ diff --git a/Applications/wish/Installation b/Applications/wish/Installation new file mode 100644 index 0000000000..e8f58e1649 --- /dev/null +++ b/Applications/wish/Installation @@ -0,0 +1,54 @@ + Installation of Wish 2.0 + ------------------------ + +$Revision: 41.4 $ $Date: 2003/04/21 13:13:32 $ + +1) Link the most appropriate file from the list of the m_*.h files to machine.h. + The current machine types are: + + m_386bsd.h 386BSD-0.1 + m_attpyr.h OSx 5.1a, SysV Universe + m_aux.h A/UX 2.01 + m_bsdpyr.h OSx 5.1a, BSD Universe + m_coherent.h Coherent + m_freebsd1.h FreeBSD 1.x + m_freebsd2.h FreeBSD 2.x + m_freebsd4.h FreeBSD 4.x + m_genbsd.h Generic 4.x BSD machine + m_gensysv.h Generic SysV machine + m_minix1.5.h Minix 1.5 + m_minix1.7.h Minix 1.7 + m_solaris.h Solaris + m_sunos3.h Sun OS 3.x + m_sunos4.h Sun OS 4.x + m_ultrix.h Ultrix 4.2 + + Not all of these have been tested recently, only those described in the + `Systems' file. If you get any to work, especially with the changes + required, let me know. + + If there is no m_*.h file for your system, pick one which is close, and + alter it to work: + + POSIX: m_sunos4.h or m_386bsd.h + Old SYSV: m_gensysv.h + 4.3BSD: m_genbsd.h + + Put a unique system #define in the file, and try to #ifdef code with this + define only when absolutely necessary. The file `Ifdefs' has information + about what defines are available in Wish. Please send me a copy of the new + machine file when it works. + +2) Copy the file Std.Makefile to Makefile. Make sure Makefile is writeable. + Edit the Makefile to define how to compile the shell. You should define: + + CFLAGS The compiler flags used when compiling each .c file + LDFLAGS The link flags used when linking the program + CC The name of your compiler + DEFINES Special defines needed during the compilation stage + CLIB Any libraries needed during the link stage + FINAL_TOUCHES Any final touches needed after the linking + + Suggestions for these defines are given in the Makefile. + +3) Run `make' to make the shell. diff --git a/Applications/wish/LICENSE b/Applications/wish/LICENSE new file mode 100644 index 0000000000..1726f18925 --- /dev/null +++ b/Applications/wish/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016, Warren Toomey +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Wish nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Applications/wish/Makefile.68000 b/Applications/wish/Makefile.68000 new file mode 100644 index 0000000000..a1afdf7fe9 --- /dev/null +++ b/Applications/wish/Makefile.68000 @@ -0,0 +1 @@ +include Makefile.common diff --git a/Applications/wish/Makefile.common b/Applications/wish/Makefile.common new file mode 100644 index 0000000000..367928f220 --- /dev/null +++ b/Applications/wish/Makefile.common @@ -0,0 +1,23 @@ +include $(FUZIX_ROOT)/Target/rules.$(USERCPU) + +SRC = alias.c bind.c builtin.c clebuf.c clex.c comlined.c exec.c file.c \ + hist.c job.c main.c malloc.c meta.c parse.c prints.c signal.c \ + term.c val.c var.c + +.SUFFIXES: .c .o + +OBJ = $(SRC:.c=.o) + +all: wish + +.c.o: + $(CC) $(CFLAGS) $(CCOPTS) -c $< + +wish: $(OBJ) + $(LINKER) $(CRT0) $^ -o $@ $(LINKER_OPT) -ltermcap$(USERCPU) $(LINKER_TAIL) + +clean: + rm -f *.o wish core *~ *.asm *.lst *.sym *.map *.noi *.lk *.ihx *.tmp *.bin size.report + +rmbak: + rm -f *~ core diff --git a/Applications/wish/README b/Applications/wish/README new file mode 100644 index 0000000000..35bdb06516 --- /dev/null +++ b/Applications/wish/README @@ -0,0 +1,29 @@ + Wish 2.0 - Warren's Interactive Shell + ------------------------------------- + +Welcome to Wish, a sort-of-tcsh-like shell based largely on the Clam shell +written by Callum Gibson. The aims of wish are: + + + to be as small as possible, so as to compile on small + systems like Minix + + to have some of the interactive features of tcsh, such + as command-line editing, tab and ^D expansion, history, + aliases etc. + + to be portable across as many systems as possible, + + to be fast and have minimal impact on the system. + +I worked on this shell from 1991 to 1996 and then gave up to finish off +my Ph.D. This release of Wish is an alpha-release, and I doubt I will ever +get back to finishing it off. It is known to run on FreeBSD 4.7, but I have +not tried it on any other system since 1996. + +The manual is in nroff form in `Wish.1'. Instructions on how to compile wish +are in the `Installation'. If you are porting wish to another system, read +the file `Ifdefs', `Systems' and the `m_*.h' files. + +Anybody who would like to take over development of the shell should email me, +as I can supply some good documentation about the development up to now, and +also a good description of the shell's internals. + + Warren Toomey wkt@tuhs.org 1996, then a bit in 2003. + diff --git a/Applications/wish/Wish.1 b/Applications/wish/Wish.1 new file mode 100644 index 0000000000..3d36a2f725 --- /dev/null +++ b/Applications/wish/Wish.1 @@ -0,0 +1,1216 @@ +."$Revision: 41.2 $ $Date: 1995/12/29 03:23:32 $ +.TH WISH 1L +.nr LL 6i +.SH NAME +wish \- command interpreter and programming shell +.SH SYNOPSIS +.B wish +[ options ] [ script-file ] +.SH DESCRIPTION +.PP +.I Wish +is a shell (command interpreter) which reads commands from either a +file or the terminal and executes them. It executes system commands as well +as builtin commands (see below for description). Main features include: a +history mechanism, enabling previous commands to be easily redone; a +comprehensive command line editor for ease of use from terminal; word completion +and option listing on the command line; full input and output redirection and +aliases. These features are described in detail below. +.PP +This manual describes internal version 41 of Wish 2.0. +.PP +Wish is derived from the Clam shell, written by Callum Gibson, and is in some +sense a partial rewrite of Clam with some new features added and some old +features removed. Many thanks to Callum for allowing me to use his source, +and for his suggestions and criticisms during Wish's development. +.".SH OPTIONS +."The shell may be invoked with a number of options: +.".TP 4 +.".B \-c +." \- the following arguments are passed to Wish for execution. +.".TP 4 +.".B \-e +." \- exit shell if any command returns a non-zero exit status. +.".TP4 +.".B \-f +." \- fast start up. Do not read the .wishrc file. +.".TP 4 +.".B \-p +." \- parse the commands but do not execute so that checking can be performed +."on shell scripts. +.".TP 4 +.".B \-v +." \- verbose mode. Commands echo back in same form as saved to history +."prior to execution. No metacharacter expansion is done (*,?,{},[],``) +."before the line is echoed. +.".TP 4 +.".B \-x +." \- similar to verbose mode except that commands are echoed back in expanded +."form. +.".TP 4 +.".B \-V +." \- Enter verbose mode even before .wishrc is read. +.".TP 4 +.".B \-X +." \- \-X is to \-x as \-V is to \-v +.PP +.SH Basic Command Line Parsing +.PP +.PP +The command line, whether read from stdin or file, is broken up into +.I words. +Words are sequences of characters which are separated by +word-delimiters (the most obvious example is a +.I space). +The following characters have effects on the words of the +command line and act as word delimiters: +.PP +< +.I filename. +Standard input redirection. Instead of reading input from the +terminal, input is read from the named file (the following word). e.g. +.PP +% prog < inputfile +.PP +> +.I filename. +Standard output redirection. Instead of outputting to the +terminal screen, output is redirected to the named file. e.g. +.PP +% prog > outputfile +.PP +.PP +n> +.I filename. +Other output redirection. Instead of outputting to the +terminal screen, output from the given file descriptor +is redirected to the named file. e.g. +.PP +% prog 5> outputfile +.PP +2> Standard error redirection. The default is the terminal screen, but instead +standard error output is directed to the named file. e.g. +.PP +% program 2> errfile +.PP +[n]>> +.I filename. +As above but the named file is appended to, not erased first. +.PP +| Pipe output of one program to input of another. A series of programs joined +by pipes is called a +.I pipeline. +A pipe joins the standard output +of the first program to the standard input of the second program. e.g. +.PP +% prog1 | prog2 +.PP +& background. When this symbol appears, it runs the previous command in +the background. A number indicating the process id and Wish job number +(job control only) is returned and the prompt is given immediately for the next +command. +.PP +; semicolon is used to separate pipelines. A semicolon can be typed instead +of a newline so that a new command or pipeline can begin. The effect is +exactly the same as a newline and thus it has the highest priority of these +metacharacters. +.".PP +."&& The double ampersand separates two commands (or command pipelines) and +."specifies that the latter should only be executed if the former returns a zero +."status (i.e. terminates successfully). +.".PP +."|| The double pipe is similar to the double ampersand except that it executes +."the latter if the former returns with +.".I non-zero +."status. +.PP +There are valid combinations of these, too, although it is illegal to +redirect output to two places, for example. +Some other characters also have special meaning and these may cause the words +to be interpreted in different ways. Ordinarily the words become the arguments +of an executing program. +." We don't have backslash as yet, sigh. +."To use any of these characters without invoking their +."special meaning, it is necessary to use the backslash character `\\' which +." causes the shell to ignore them and treat them as part of a word. +Most of +these "metacharacters" will also be ignored inside double quotes `"' and +all (except history substitution commands using !) are ignored inside single +quotes `''. (See the section titled "Quoting") +.PP +.SH Job Control +.PP +.PP +If job control is included, Wish +allows the user to have control over their processes or +.I jobs. +Whenever a process is run it is given a job number. This applies to +all background jobs and the current foreground one if it exists. This number +can then be used to identify a process for use with bg or fg. +The list of all currently active jobs can be displayed with the +.I jobs +command. +The +.I bg +and +.I fg +commands allow jobs to be placed into the background (where +the prompt is returned but the command still runs) or the foreground (where +the user is in an interactive state with the process). The stop signal can be +generated by typing `^Z' while a foreground job is running. This has the effect +of stopping that process immediately, which may be restarted with either the bg +or fg command. +." I don't think we have ^Y yet. +." A foreground process may also be stopped by typing `^Y' which +." delays stopping until the input buffer is emptied. +Note that bg and fg always +refer to the current (most recent) job +by default. For further explanation, see the "Builtin Commands" section below. +.PP +Background jobs are allowed to write to the terminal by default but not +read from it. (The shell places a job in the background by disassociating it +with the control terminal's process group. See +.I setpgrp(2)). +.PP + The +.I stty(1) +"tostop" mode can be used if output from background jobs is to be disallowed. +This mode will cause background jobs to stop if a write to the terminal is +attempted. Reads from the background are always disallowed. +The user is notified of a processes status after return is pressed +and before the next prompt appears. This gives information about completion +or stoppage of background jobs. +.PP +Those people using +.B Minix +should use +.I stty(1) +to redefine their `quit' key from ^\\ to ^Z, as there is a primitive +form of job control available when +.I Wish +is compiled with #define V7JOB. This doesn't always work, but most of +the time you can ^Z, bg and fg. +.PP +Job control under Posix systems is still slightly broken; I haven't +had the time to sit down and grok the full implications of the Posix +rationale for job control. +.PP +.SH Command Line Editing (CLE). +.PP +.PP +This feature allows the use of control keys and meta-character key sequences +to perform editing operations upon the current line. This is done by using +"cbreak" terminal mode (see +.I stty(1) +) which causes an interrupt to be generated after each character is typed and +hence available for input, rather than just at the end of a line when return is +typed. Wish also turns echoing off so +that it can position the characters properly +and effect autowrap, control character representation, etc. These two modes +are reset when a foreground job is executed. Other modes are settable by the +user and not changed by Wish. +.PP +European users will be happy to know that the CLE is now 8-bit transparent. +For users with 7-bit keyboards, the CLE now has a command to produce +8-bit keystrokes. When 8-bit characters cannot be displayed on the screen, +turn the Wish variable `MSB' on; 8-bit characters will then be displayed +in bold. +.PP +The Command Line Editor provides many editing commands. The following +table lists the possible commands. The three columns hold: the default +key binding for the command, the internal key value for the command (usually +the same as the key binding), and a brief description of the action. Most +actions are obvious; the complex ones are described below. +.PP +.in +2 +.nf +KEY VALUE ACTION +=== ===== ====== +^@ \\000 save current position +^A \\001 goto start of line +^B \\002 go backwards one character +^C \\003 interrupt - discard line and start again +^D \\004 delete current char, list options, or exit shell +^E \\005 goto end of line +^F \\006 go forward one character +^G \\017 kill whole line +^H \\010 back-delete one character +^I TAB \\011 complete current word +^J LF \\012 finish line (same as return) +^K \\013 kill from cursor to end of line +^L FF \\014 clear screen and redisplay line +^M CR \\015 finish line (same as linefeed) +^N \\016 step forward in history +^O \\017 toggle insert/overwrite mode +^P \\020 step backward in history +^Q \\021 resume tty output +^R \\022 redisplay the line +^S \\023 stop tty output +^T \\024 transpose the current and previous chars +^U \\025 accepts number and key to perform num times +^V \\026 next character to be read literally +^W \\027 delete word backwards +^X \\030 goto saved position +^Y \\031 yank the previous word into a buffer +^Z suspend process (no effect upon CLE) +^[ ESC prefix for meta-commands +^\\ \\034 beep +^] \\035 turn CLE mode bit on (see bindings below) +^^ \\036 turn CLE mode bit off (see bindings below) +^_ \\037 turn the following character's msb on +SPC../ insert that character +0..9 insert the character or read digit (^U) +:..~ insert that character +^? DEL back-delete character (same as ^H) +ESC-^P \\201 match previous partial command +ESC-b +ESC-B \\202 skip backwards one word +ESC-d +ESC-D \\203 delete word forwards +ESC-f +ESC-F \\204 skip forward one word +ESC-h +ESC-H \\205 get help on word +ESC-p +ESC-P \\206 insert buffer onto the line +ESC-y +ESC-Y \\207 yank the next word into a buffer +ESC-/ \\210 search forward for next typed character +ESC-? \\211 search backwards for next typed character +.in -2 +.fi +.PP +Note that some keys which normally generate signals (e.g. ^Z, ^Y, ^\\) +do not do so whilst a line is being entered/edited. Their signal generating +status is returned whenever a foreground job is in progress. +.PP +.SH Complex CLE Functions +.PP +.PP +Some of the CLE commands are complicated. Their full description is given here. +.PP +^D Delete char, list options, leave shell +.PP +This command has three functions. If you use the command at the beginning of +a line, you will exit the shell. If your cursor is on top of a character, +that character will be deleted. Finally, if your cursor is at the end of the +line, a list of `expansions' will be shown. This command will show files, +programs you can run, user's home directories and variables, depending upon +how you use it. +.PP +For example, to see a list of files in your current directory, you can do +.PP +.nf + % word ^D +.fi +.PP +To see all the files starting with `s', do +.PP +.nf + % word word s^D +.fi +.PP +The word can be a relative or absolute path name; to see the files starting +with `std' in the include directory, and the parent directory, do +.PP +.nf + % word word /usr/include/std^D + % word word ../std^D +.fi +.PP +If the word is the first on the line, aliases, builtins and programs in +your PATH are listed. To see all the commands, and all the commands starting +with `cl' do +.PP +.nf + % ^D + % cl^D +.fi +.PP +If the word begins with a `$', the command will give a list of variable names; +to see all the variable names, and all starting with `H', do +.PP +.nf + % word word $^D + % word word $H^D +.fi +.PP +If the word begins with a `~', the command gives a list of users. Examples: +.PP +.nf + % word word ~^D + % word word ~ro^D +.fi +.PP +Words with `/' signs in them will be expanded internally to their pathname +equivalents. The word `~#/' means the list of aliases and builtins. +For example, to list the files in fred's directory, in $HOME, and the +aliases and builtins, do +.PP +.nf + % word word ~fred/^D + % word word $HOME/^D + % word word ~#/^D +.fi +.PP + Filename Completion +.PP +Filename completion is closely associated with the list options function. If +you use instead of ^D, the word on the command line is completed up +to the first duplicate letter. For example, if you have two files 'bigfile.c' +and `bigfile.o' in the current directory, then +.PP +.nf + % ls bi becomes + % ls bigfile. +.fi +.PP +Words with only one match are fully completed. This function understands the +same words as the list options functions, so you can complete variables, +usernames, programs, and files in directories like +.PP +.nf + % ls $HOME/.cl becomes + % ls $HOME/.wishrc +.fi +.PP +Quoting Commands +.PP +The CLE provides commands to quote characters instead of them performing +functions. The first is ^V. For example, to insert a ^A on the line without +^A doing its usual action, do +.PP +.nf + % word word ^V^A +.fi +.PP +The ^_ command works exactly the same, but also turns the character's +most significant bit on, so 8-bit keystrokes can be entered from a 7-bit +keyboard. +.PP +^U Repeat Keystroke +.PP +^U is followed by a decimal number (which isn't displayed), and one character. +That character is repeated number times. +.PP +ESC-^P Match Partial Command +.PP +This command only works for the first word. When the cursor is at the end of +the first word, and this command is done, the last command that matched +the word is added to the line. For example: +.PP +.nf + % ls -l + % wc file + % echo hello + % lESC-^P becomes + % ls -l +.fi +.PP +Word Commands +.PP +The word commands are affected by the Wish variable wordterm. This can be set +to holds the characters that `end' a word. If this variable isn't defined, +the CLE assumes it holds the following characters: space, tab, > < | / ; = +& and `. +.PP +.SH Bindings +.PP +.PP +Wish now distinguishes between the commands available in the CLE and the +keystrokes that cause the commands to occur. This allows Wish to have +different editor bindings. For example, Wish defaults to an emacs-like +binding, as noted above, but can be given vi-like bindings by sourcing +the file Vibind. +.PP +The command characters given in the above table are used by the Wish +.B bind +builtin command, which can bind a keystroke sequence to another keystroke +sequence. With the default emacs-like style, most of the emacs keys are +identical to the internal CLE commands, and only the ESC commands are +internally bound by Wish. Setting up another editor style involves the use +of the `bind' and `unbind' builtins described in the Builtin Commands +section. +.PP +The CLE also offers a boolean `mode'. +When the mode is on, all current key bindings apply. However, when the mode +is off, key bindings marked as `mode only' are disabled. This allows the +vi-style set of bindings to be used. +.PP +The help command calls the system manual command with the word immediately +preceding the cursor as the argument. +.PP +." The next history command (^N) will attempt to predict the user's next command +." if it reaches the end of the history list (i.e. when nothing has been typed +." on the current line, yet) by looking for the most recent occurrence of +." the previous command (not +." including the previous command itself) and then loading the command +." .I after +." that onto the command line. This may be set up to occur automatically by +." setting the TARDIS variable (see Special Variables section). This enables +." the user to set up a loop of commands. A beep sounds if the shell is unable to +." predict what command is next (if the previous command never occurred before, +." for instance). +." .PP +." .nf +." % comm1 +." % comm2 +." % comm3 +." % comm1 +." % ^N --> % comm2 +." .fi +." .PP +.SH Variables. +.PP +.PP +The shell variable facility relies on the following metacharacters: +.PP +$ denoting a variable. Wish will interpret $ as a reference to a +shell variable. +The word immediately adjacent to the `$' is taken +as the variable name. This word is delimited by any of the metacharacters above, +by quotes, by another `$', or by an array subscript. e.g. +.PP +% echo $fred +.PP +This will echo the contents of the variable fred. +." , while this: +." .PP +." % echo $fred[2] +." .PP +." will echo the contents of the third field of fred. Note that array subscripts +." start from zero. Any access to a nonexistent field will return a null word. +.PP += assignment of variables. Variables are assigned simply by stating the name(s) +of the variable(s), followed by the equal `=' sign, and then the value(s). +." It is possible to assign two or more variables simultaneously. e.g. +." .PP +." % a b = x y +." .PP +." This statement assigns 'x' to $a and 'y' to $b. Any left-over words on the +." right hand side of the equals are assigned to the last variable listed on +." the left hand side. Similarly, any left-over variable names (on LHS) are +." assigned null values. Note, however, that this assignment is not truly +." concurrent (at this stage). (i.e. assignment is done left to right, not as +." if it all happened simultaneously. Therefore, variable swapping is not +." possible with only two variables. "a b=$b $a" would set both "a" and "b" to the +." original value of "b". +." .PP +." += appending to a variable. Variables can also be appended to with the `+=' +." operator. This works in the same way as the `=' but does not destroy the old +." value of the variable. To append to a variable, the variable must have existed +." previously. +.PP +Wish is responsible for two separate sets of variables. The examples given +above refer to +.I internal +shell variables. These variables are immediately +accessible to the user and also act as flags for some Wish functions +(e.g. HASH,ignoreeof,UNIV). The other set is known as +.I environment +variables. +Environment variables are passed to any new process by the execve system call. +When the shell is started all environment variables are loaded into the +shell's internal variable storage space. +When this initialisation occurs, +these variables are automatically marked for +.I export +, meaning that when execution of a command +takes place, the variables marked for export form the environment for +the executed command. +It is also possible to mark any variable for export by +using the +.I export +command described below. +.PP +." While there is no +." .I set +." command, variables can be deleted using the +." .I unset +." command. Also +." .I unsetenv +." can be used to delete environment variables. If you +." don't want all of the environment variables cluttering up your internal +." variable list, there is no harm in deleting them but they will not be +." re-exported. To reload the environment variables, simply use the command: +." .PP +." e.g. % PATH=$PATH +." .PP +." since the environment is searched for the variable if it does not exist in the +." internal variable list. +." .PP +." A new feature found only in Wish is the +." .I edit +." command which allows the user +." to edit a variable. This is particularly handy for long variables such as PATH +." and TERMCAP in which only a small component has to be altered. Editing is done +." using the command line editor features described above. +.PP +Wish also has several inbuilt variables: +.PP +.nf + $$ - The process-id of the process + $? - The exit status of the last process + $# - The number of arguments to the process + $n - (where n is a digit) The n'th argument to the process. + $* - All the arguments to the process (except $0) +.fi +.PP +When used interactively, these refer to the Wish itself. However, in +aliases and Wish scripts, these variables refer to the alias or script. +For example, if `prog' is a Wish script, and you type +.PP +% prog foo bar +.PP +then from within "prog", $0 will be "prog", $1 will be "foo" and $2 will be +"bar". Similarly, an alias such as +.PP +% alias l '/bin/ls -lg $*' +.PP +will do '/bin/ls -lg' on all of its arguments. +.PP +.SH History and History Substitution. +.PP +.PP +The history mechanism enables every command typed to be stored and accessed +at a later time. To get a list of previous commands use the +.I history +command. Wish does not enter the +.I history +command into the history, nor does +it store single letter lines since these can be more easily retyped than +by using history substitution (described below). The number of commands +remembered by Wish is set by the internal "history" variable. +.PP +It is possible to recall a previous line(s) from the history by using the +metacharacter `!', followed by the absolute history number, a relative history +number, a string which matches the most recent command starting with the +same string, or another `!' which refers to the previous command. e.g. +.PP +% !12 fred +.PP +This will substitute the history event numbered 12 onto the line at that +position and the argument "fred" will remain as an argument to that command. +.PP +% !-3 +.PP +This will substitute the event from three commands ago. It is still possible +to have an argument or a different substitution on the same line. +.PP +% !fo +.PP +This will substitute the most recent command starting with "fo". +.PP +You can also append characters onto a previous history command rather than +adding a new word. If we issued a command "ls -l" and then used "!la" the +shell would search for a command starting with "la" instead of finding "ls -l" +and appending the "a". So, to achieve this, simply quote the characters that +are to be appended (actually you only need quote the first). Hence, +.br +% !l"a" +.br +will give the desired substitution of "ls -la". Spaces may be included in +the string by using backslash or quotes. +.br +% !"v d" + +." .PP +." Another form of substitution exists which operates only on the previous +." line as an error correction mechanism. Although this is mostly superseded by +." the history pullback (^P) and command line editor operations, it was included +." for completeness. If a line starts with a `^' and has the format: +." .PP +." % ^string1^string2 +." .PP +." then string1 is replaced by string2 in the previous command line. +." .PP +." .B Command Substitution. +." .PP +." The +." .I output +." of a command can be substituted into the command line by using +." .I backquotes +." to surround the command. e.g. +." .PP +." % d=`date` +." .PP +." This command causes the system program +." .I date(1) +." to be executed, and the resulting output is loaded (word by word) into the +." line to be parsed. +." .I Words +." are sequences of non-blank characters. A +." .I blank +." is a space, tab or newline. The "blank" characters are all interpreted as +." spaces when loaded into the line, however, the output of a program in +." backquotes can be preserved as a single argument by surrounding the backquotes +." with double quotes (""). Single quotes would cause the backquotes to remain +." uninterpreted, however. +.PP +.SH Filename Substitution. +.PP +.PP +Rather than type a lot of filenames, sometimes it is possible to specify +a group of files using the "globbing" technique. This means that using the +following metacharacters, you can specify files in any directory using a +sort of shorthand notation. The shell then performs what is called "pattern +matching" to produce the file names wanted. +.PP +* represents any number of characters including zero. +.br +? represents any one character. +.br +[] encloses a list of characters that can match. Two characters may also +have a hyphen separating them meaning "up to". So [A-Za-z] includes the +letters 'A' to 'Z' and 'a' to 'z'. +.PP +e.g. ab* matches all files in current directory starting with "ab". +.br +/usr/bin/lo*.? matches all files in /usr/bin starting with "lo" then +ending with a "." and any other character. +.br +[abc]*.c matches all the C-files in the current directory starting with +an "a", "b", or "c". +.PP +It is also possible to specify +.I subdirectories +using pattern matching e.g. +*/*.c matches all C-files in any subdirectory. +.PP +A further abbreviation method is available for referring to the home +directories of users. This is done by typing a `~' followed by a +.I username +or if no username is given then the home directory of the user running +the shell is used (i.e. ~ refers to your directory). +.br +e.g. "~cgibson" will be replaced by "/u1/hons/cgibson" +.br +"~mike/work" will be replaced by "/u1/staff/mike/work". +.PP +Wish now fully copes with systems that uses yypasswd. +." .PP +." It is also possible to abbreviate by using the {} metacharacters. Actually, +." only the `{' is recognised as a metacharacter. This can be achieved by +." placing a set of strings separated by commas `,' inside the curly braces. +." Note that the shell blindly expands without checking for existence of files +." (no matching done) and thus this command can refer to words other than +." filenames. +." .PP +." e.g. % ls {a,b,c}*.c +." .PP +." This will once again give the listing of C-files in the current directory +." but the difference is that before pattern matching takes place, the line +." is expanded to: "ls a*.c b*.c c*.c" and hence is less efficient since the +." shell must match 3 separate patterns. The following example shows another +." use of the curly brace expansion. +." .PP +." % echo {0,1}{0,1}{0,1}{0,1}{0,1}{0,1}{0,1} +." .PP +." This will echo the representation of the binary numbers from 0 to 127 +." because left to right order is preserved. +." .PP +." So, it is possible to combine any number of these metacharacters *,?,[],{},~ +." to save +." typing, but note that the more complicated the pattern, the longer the shell +." will take to expand it, particularly if many subdirectory levels are involved. +." Also, if many different sets of braces are given in the one word then the +." expansion becomes large and may not even fit on a line. For example, the above +." example using {0,1} has two options in each set of braces and seven sets of +." braces, resulting in two to the power of eight (128) different words. +.PP +.SH Quoting. +.PP +.PP +There are three types of quotes (not including backquotes) which may be used to +stop the above mentioned metacharacters from being interpreted. +." The most +." powerful is the +." .I backslash +." `\\' which only quotes the next character. +." This will quote absolutely anything except +." .I null +." (^@) which may not be quoted since it represents the end of a line +." (null-terminated strings. See +." .I string(3) +." ) and can't be typed, anyway. The next set of quotes, called +Single quotes '' will stop all interpretation except history +substitution (!). +." It is necessary to use the backslash here if the actual +." exclamation mark character is intended. +The last set of quotes is double quotes. +They allow history and variable substitutions but disallow pattern matching. +Note that quotes may be nested but only the outer set have any effect. +." The exception is the use of the +." .I backslash +." when quoting the `!' and when quoting +." the `$' and ``' in double quotes. +.PP +.SH Aliases - User defined Command Sequences +.PP +.PP +It is possible for the user to define their own set of aliases. Unlike +.I Csh(1) +where the first word of any command is checked to see if it's an +alias, and then the alias is substituted onto the line, Wish allows full +shell scripts to be defined which are then executed by the shell after +parsing the line. These aliases are stored internally and executed in +a similar way as a Wish script. Thus it is possible to pass arguments as +$1 $2 etc. or $* for all arguments. In fact, the aliases should behave exactly +as a script stored in the user's directory but without taking up directory +space. Remember that the more aliases that exist, the more slowly commands +will run. +." This problem is greatly reduced in Wish compared with +." .I Csh +." if +." .I hashing +." is on, because the builtins and aliases are hashed from the path along +." with all the other commands and so access to them is still quick via this +." method. The only reason for not using hashing would be if the hash table got +." too full (too many files on your PATH) or process space was needed on the +." system. +." Also using the `~#' metanotation, you can specify explicitly to use a builtin +." or alias if they occur lower down in the path then a system command of the same +." name. e.g. +." .PP +." % ~#/list +." .PP +." This will execute the builtin command +." .I list +." rather than the system command +." .I list(1). +Because of the way that these aliases are implemented, it is easy to +call builtins, system commands, and even other aliases, from within an alias. +You should be careful not to call an alias from within itself since this will +cause an error. Aliases can be created using the builtin command +.I alias +, see below in Builtin Commands section. +.PP +.SH Builtin Commands +.PP +.PP +This section describes the commands that are builtin to the shell. +Builtins are only executed in a subshell if they are at the start or in the +middle of a pipeline or run in the background. +." They exist in a virtual directory +." ~# and can be referred to in this way. See the Aliases section above for more +." information. +.PP +This section is divided up into several subsections, because some +builtins can be left out if they cannot be used or are not needed. +To find out which builtins you have, do +.PP +.nf + % ~#/^D +.fi +.PP +This gives you a list of aliases and builtins. +.PP +.B 1. Standard Builtins +.PP +.TP 4 +." alias [ [ -e -l -s] [ ] ] +alias [ [ -e -s] [ ] ] +When invoked with no argument this command lists the aliases +currently defined. +." If the -e flag is given then an editor (default is .I vi(1)) +." is invoked upon a temporary file and this allows the user to create their +." Wish script which is loaded into the shell as an alias upon completion. This +." is needed if +." multi-lined aliases are to be defined since Wish's command line editor will not +." allow newlines to be displayed and inserted. +The -l flag instructs Wish to load +up the alias from the named file (the alias is given the same name). In the same +way -s causes Wish to save the alias to file. If none +of these flags appear, then the first argument is taken as the alias name, and +the remaining arguments as the alias definition. If there are no other +arguments, that alias's definition is printed. +.TP 4 +bind [[-m] keysequence [newkeysequence]] +This command allows the user to bind a series of keystrokes to a new series +of keystrokes. Note that most ASCII control characters (\\000 to \\037), and +some other control characters (\\201 to \\211) cause the CLE to perform a +function, so most bindings are to one or more of these characters. +With no arguments, +bind +shows the current set of key bindings. With a keysequence argument, +the binding of +the given sequence is shown. With a keysequence and a value, the sequence is +bound to the action. For example: +.PP + % bind '^[[A' ^P +.PP +binds the up-arrow key to the `step backward in history' action. +Bind +also understands C-like octal sequences, so that +.PP + % bind '^[[A' \001hello\005 +.PP +will cause the up-arrow to insert the word `hello' at the beginning of the +line. +.PP +If a keysequence is bound using the `-m' option, then the binding is marked +`mode-only', and is only invoked when the CLE is in MODEON mode. Otherwise +it is ignored. +.PP +Note that the CLE uses the first match found when parsing keyboard input. +For example, if the current bindings are: +.PP + % bind + ^[[A is bound to ^P + ^[[B is bound to ^N + ^[[Aa is bound to ^A + ^[[B is bound to ^E +.PP +then the keyboard input `^[[Aa' will do the command ^P (i.e. go back in +history), and then +add an `a' to the command line; similarly, the input '^[[B' will do the +command ^E (i.e. go to the end of the line). +.PP +Key bindings can be recursive. A large error will occur if you do +.PP + % bind a aa +.PP +as the next time you type `a', it will become an infinite number of a's, +up to the size of the CLE line buffer. +.TP 4 +cd [ ] or chdir [ ] +This command changes the current working directory of the +current shell process. Thus, if cd occurs inside a child process, when that +process dies, the parent will not have changed directory. Cd takes a single +argument which is the name of the directory to change to. The directory name +may be a full path name starting from the root directory or a path name +relative to the current directory. The `~' notation can also be used to refer +to the home directory of users. +.TP 4 +echo [-n] +Echo writes the given arguments out on a single line on standard output. A +newline is appended unless the -n option is given. +." .TP 4 +." edit +." Edit takes one argument which is a variable name that is +." to be edited. The editor used is the command line editor. After completion of +." the editing sequence, press return and the variable is saved. Note variables +." must exist prior to editing them. (See also Command Line Editing section). +." .TP 4 +." exec [ ] +." Replace the current shell with a different program which is +." supplied as arguments. All arguments are passed to +." .I execve(2). +." .TP 4 +." exit [ ] +." Causes the termination of the shell (unless it is the +." login shell). If a numerical argument is given then the shell exits +." with that argument. Default status is zero. +.TP 4 +export [ [ - ] ] +mark a variable for export, or if `-' flag given, unmark +export ability for that variable. When a variable is marked for export, it +means that the internal copy of the variable is transferred to the environment +whenever a program is executed, making the new copy available to the new +process. +.TP 4 +history [ m[-n ] ] +Lists the previously executed commands remembered by the shell. An optional +range can be specified which will list the commands numbered from +.I m +to +.I n. +A single number argument will display the m most recent commands. +.TP 4 +list [ env ] +List the internal variables defined in Wish. With the "env" option, the +environment variables are listed (like +.I printenv(1)). +." .TP 4 +." load +." Load the arguments supplied onto the next command line. Can be used to +." load up alias definitions for example if they need to be slightly altered, +." or to check a history substitution (or other substitution) without automatically +." executing the line. Allows a second carriage return for confirmation. +." .TP 4 +." lock +." Lock the keyboard. User is prompted for a password which must be retyped +." to confirm it. The shell enters "lock" mode and this mode can only be exited by +." once again typing the correct password. +." .TP 4 +." logout +." The command used to logout from the shell and hangup from +." the system. This command only has effect from the lowest shell level (i.e. +." the login shell). +." .TP 4 +." repeat +." This statement causes the command to be executed the number of times specified. +.TP 4 +source [ ] +Cause the Wish script to be executed in the current shell, as if the contents +of the file had been typed from the keyboard. This is useful if the environment +variables are to be set for the shell. (e.g. if you have a script that changes +your TERMCAP or PRINTER variable etc.) This script could also be loaded as an +alias with "alias -l". +.TP 4 +tilde [-l], tilde [[shorthand] [dirname]] +Wish keeps an internal list of shorthand names for directories. Tilde adds +the given shorthand/dirname pair to the list. From then on, you can use +~shorthand to mean the dirname. If just shorthand is given, the expansion +for that shorthand is given. With no arguments, all the internal pairs are +given. The -l argument will also show the username/directory pairs from +the /etc/passwd file. +.TP 4 +unalias ... +Delete the named alias if it exists. If not an error message is returned. +.TP 4 +unbind keysequence +Unbinds the given key sequence, if that sequence has been bound. +.TP 4 +untilde +Removes the shorthand name/directory pair from the internal list of +directory shorthands. +unset ... +Delete the internal variable named. If an environment variable of the same +name exists, it will be deleted. +.TP 4 +unsetenv ... +Delete the environment variable named. This command should be used with +caution since some system programs use the environment variables. The +shell with still have an non-exported copy of the variable. +." .TP 4 +." which [ -o ] [ -O ] +." In its basic form, tells you the full pathname of the file you execute by +." typing . With -o, aliases are ignored and with -O aliases and builtins +." are ignored. +.PP +.B 2. Job Control Builtins +.PP +.TP 4 +bg [ % ] [ ] +This command has the effect of starting the current job (which will be +stopped) in the background if given no arguments. If given arguments it starts +the specified jobs in the background. The arguments are simply the job numbers +which have been assigned by the shell. These can be obtained using the +.I jobs +command. +.TP 4 +fg [ % ] [ ] +Cause the current job to execute in the foreground +whether stopped or executing in the background. If an argument is supplied +it is taken as the job number (as described in +.I bg +and Job Control section). +.TP 4 +jobs +Give a list of current jobs (processes) recognised by +Wish. These jobs are the either running in the background or are stopped. +The shell cannot keep track of processes that result from a +.I fork(2). +In the att universe, job control is severely restricted. (See Job Control +section). +." .TP 4 +." kill [ - ] % [ or ] ... +." Kill the specified job number or process id (pid) with the optionally +." specified +." .I signal. +." Signals are the same as those defined in +." .I sigvec(2) +." (ucb) or +." .I signal(2) +." (att) but with the prefix "SIG" removed. If the signal is unspecified, then +." the SIGTERM signal is sent. If the SIGTERM and SIGHUP signals are sent the +." the shell will send the +." SIGCONT signal as well, which is needed if a stopped job is to receive the +." signal. +." .TP 4 +." stop [ % ... ] +." Stop the current (or named) job that is running in the background. Jobs may be +." restarted using either +." .I bg +." or +." .I fg. +." .TP 4 +." suspend +." Cause this shell to suspend or stop. Control is returned to an outer level +." but the shell will remain in a stopped state (receives a stop signal, like +." typing ^Z). +." .TP 4 +." wait +." Cause the shell to wait for all background processes. The wait can be +." interrupted (^C) at which stage the shell prints a list of all jobs +." currently outstanding. +." .PP +." .B 3. Hashing Builtins +." .PP +." .TP 4 +." hashstat [ x ] +." Display an analysis of the internal hashing table. +." With no arguments, hashstat returns the actual hits and misses whenever the +." shell tries to execute a program and also a hit ratio. With the single argument +." "x", Wish returns the hits and misses encountered when creating the hash +." table, an expected hit ratio, and how full the table is. +." .TP 4 +." rehash +." This causes the files on the user's path to be entered into a hash table. +." If hashing was previously turned off, then it is turned on with this command. +." You will need to do this if a new executable file appears in a directory on +." your path. +." .PP +." .B 4. Non-Minix Builtins +." .PP +." .TP 4 +." help [ ] +." Help gives some additional information about the previous error that occurred, +." or if supplied with an internal error number, explains that particular error +." in more detail. +." .PP +." .B 5. Shell Script Builtins +." .PP +." Currently case, for, if, repeat, shift, and while exist as stub builtins, i.e +." they do nothing. It is planned to give Wish a scripting language as close +." as possible to sh(1). The only written shell script builtin is read. +." .PP +." .TP 4 +." read [ ... ] +." Read from the standard input until an end of line is reached. The characters +." that were read are assigned to the named. If more than one variable +." is to be read, the first word of the input is assigned to the first variable; +." the second to the second respectively; the final variable is assigned any left +." text. If there are more variables than words read, remaining variables are +." assigned null strings. Note this is logically equivalent to variable assignment. +." (see Variables.) +.PP +.SH Special Variables. +.PP +.PP +This section contains a description of the variables which are used by the +shell to indicate certain conditions or which actually affect the running +of the shell in some way. The user may set these variables to suit their own +personal tastes. +.TP 4 +KEEPSTTY +This variable affects the state of the terminal under Wish. If not defined, +you can change the terminal state using stty, and Wish will use the new +state, with the exception of cbreak/cooked modes. with KEEPSTTY, Wish will +always reset the terminal to the initial state. This is useful if you have +a program with sometimes crashes, leaving the terminal in nl state or even +changing the speed. Defining this variable will set the terminal back to a +sane state when you return to Wish. +.TP 4 +MSB +This variable affects the way characters with their msb on are printed. +Wish defaults to printing characters as they are. However, if MSB is set to +any value, characters above 127 are AND-ed back to 0-127, and are printed +in bold. +." HASH +." If HASH exists, hashing for the path will be turned on. If unset then +." hashing is turned off. HASH needs no value. +." .TP 4 +." TARDIS +." Cause the shell to automatically try to predict what command the user wants +." typed every time the prompt is returned. This is the same as typing ^N every +." time the prompt appears. +." .TP 4 +." UNIV +." For dual universe implementations of Unix, such as Pyramid OSx. +." Can be set to either "att" or "ucb" +." and is probably extremely machine dependent. Contact your system administrator +." regarding this. +.TP 4 +beep +This variable can be set to any string of characters +which will be output whenever a bell would normally sound. By default, +this value is ASCII 7 (^G). If set to null, the shell will give no warning +bell. (For terminals with inverse video, "^[[?5h^[[?5l" will produce a +flash). +.TP 4 +cwd +Holds the value of the current working directory. This variable is automatically +updated by the shell whenever a cd or chdir occurs. It generally should not be +changed by the user. +.TP 4 +." history +." The number of commands the shell is to remember for use with the +." .I history +." command. If set to zero, then of course no commands are remembered. The larger +." history's value, the more space is occupied by the shell process since it must +." store all the previous commands. History is set to 25 by default. +." .TP 4 +." ignoreeof +." If set, ignoreeof causes the shell to ignore the ^D key as a means of exiting +." the shell. Ignoreeof needs no value. +." .TP 4 +." nohistdup +." If set, successive identical lines are not saved in the history. +." .TP 4 +prompt +.in -4 +.br +." prompt2 +." .in +4 +." .br +Prompt contains the format description for the user prompt. By default +this is a `%' sign. Prompt2 contains the format description for the second +prompt, given when more information is needed, like in a while, case, or if +statement being executed in interactive mode (i.e. from keyboard). +The format description is simply a string of characters which are displayed, +except that some special attributes may be entered using the `%' metacharacter. +.br +.in +4 +%% a percent `%' sign. +.br +%d current working directory. +.br +%! +.br +%h current history number. +.br +%S start standout mode (see +.I termcap(4)). +.br +%s stop standout mode. +.br +%@ +.br +%t time in 24-hour format with seconds. +.br +.in -4 +If any other character follows the `%' then the `%' and then that character is +printed. Other characters are just printed. e.g. "%d[%h] " could produce +"/u1/staff/mike/work[58]" as the prompt. +This prompt format is similar to that used by +.I tcsh(1). +." .TP 4 +." umask +." Contains an octal number representing the umask for files when created by +." the shell. The default file creation mode can be found by OR-ing this value +." with octal 0777. See +." .I chmod(1) +." and +." .I umask(2). +." .TP 4 +." wid +." Sets the terminal width. Tells the command line editor when to effect a line +." wrap so that the cursor stays in the right place. +.TP 4 +wordterm +This variable holds all the characters that delimit words, and is used in +the word-oriented CLE functions. If undefined, the CLE will use the +characters SPACE, TAB, > < | / ; = & and `. +.SH AUTHORS +Callum Gibson- Honours project 1988. +Warren Toomey. +.SH FILES +.nf +~/.wishrc Read at beginning of execution of Wish. +." ~/.history Saved commands from history list. +." ~/.login Read only by login shell, after .wishrc. +." ~/.logout Read at logout time. +." /etc/passwd source of directories for ~name. +." /usr/local/man help manual called from CLE, defined at +." compile time. +." /usr/local/lib/wish.hlp Text for help builtin command. +.fi +.SH LIMITATIONS +Maximum line length is 2048 (but redefinable). Maximum number of arguments +is 512. Not all of the above features exist, yet, and some new ones may be added +in future versions. +.SH SEE ALSO +chmod(1), csh(1), sh(1), stty(1), tcsh(1), exec(2), fork(2), pipe(2), umask(2), +wait(2), string(3), signal(3), termcap(4), tty(4), environ(7). diff --git a/Applications/wish/Wishrc b/Applications/wish/Wishrc new file mode 100644 index 0000000000..ddd53e63eb --- /dev/null +++ b/Applications/wish/Wishrc @@ -0,0 +1,15 @@ +# Here are some basic aliases and stuff for Wish 2.0 +# +# setenv PATH $HOME/.bin:/usr/local/bin:/usr/etc:/usr/ucb:/bin:/usr/bin +set dir ' ' +set prompt '%d: ' +# +alias , 'make $*' +alias boring 'echo I know' +alias h history +alias j jobs +alias logo exit +#alias ls '/bin/ls -lg $*' +#alias l '/bin/ls -C $*' +alias mn 'nroff -man $* | less' +alias zless 'zcat $* | less' diff --git a/Applications/wish/alias.c b/Applications/wish/alias.c new file mode 100644 index 0000000000..e1ad54c31a --- /dev/null +++ b/Applications/wish/alias.c @@ -0,0 +1,73 @@ +/* This file contains functions relevant to aliases, including alias + * creation in two ways, listing, interpreting. + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" + +#ifndef NO_ALIAS +struct vallist alist= /* The list of aliases */ + { NULL, NULL}; + +static struct val *aline; /* Pointer to one alias */ + +/* Checkalias returns a pointer to the alias named by aname. If + * no alias is found, it returns NULL. It also points aline to the + * alias definition. + */ +struct val *checkalias(aname) + char *aname; +{ + aline= searchval(&alist, aname, TRUE, FALSE); + return(aline); +} + +/* Getaliasline returns the alias one line at a time. + * It depends on having aline initialised correctly. + */ +bool getaliasline(line, nosave) + uchar *line; + int *nosave; + { + + *nosave=0; + if (aline==NULL) return(FALSE); /* No alias to return */ + strcpy(line,aline->val); + aline=NULL; + return(TRUE); + } + +int alias(argc,argv) + int argc; + char *argv[]; +{ + struct val *v; + +#ifdef DEBUG + fprints(2,"In alias with argc %d argv[1] %s\n",argc,argv[1]); +#endif + + if (argc==1) + for (v=alist.head; v; v=v->next) prints("%s\t'%s'\n",v->name, v->val); + if (argc==2) + { + v= searchval(&alist,argv[1],TRUE,FALSE); + prints("%s\t'%s'\n",v->name, v->val); + } + if (argc==3) + setval(argv[1], argv[2], &alist); + return(0); +} + +int unalias(argc,argv) + int argc; + char *argv[]; +{ + int i; + + for (i=1;argv[i];i++) + searchval(&alist,argv[i],FALSE,FALSE); + return(0); +} +#endif diff --git a/Applications/wish/bind.c b/Applications/wish/bind.c new file mode 100644 index 0000000000..e992672454 --- /dev/null +++ b/Applications/wish/bind.c @@ -0,0 +1,317 @@ +/* The key binding routines used by the command line editor + * + * $Revision: 41.3 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" + +/* Wish allows keystrokes to be bound to other keystrokes. The following + * structure is used to hold these bindings. Note well the mode bit; a + * binding with mode!=0 is only used when the CLE has it's mode bit on. + */ + +#ifndef NO_BIND +struct keybind +{ + uchar *key; /* The key sequence we have bound */ + int len; /* The length of the key sequence */ + uchar *cmd; /* The string it is mapped to */ + uchar mode; /* Mode bit. Binding only used when mode on */ + struct keybind *next; +}; + + +static int Keylength = 0; /* The maximum key length */ +static struct keybind *Bindhead = NULL; /* List of bindings */ + +uchar bindbuf[512]; /* Buffer used to expand bindings */ +uchar *bindptr; /* Pointer into bindbuf */ +uchar CLEmode; /* This holds the CLE mode */ + +/* The default key bindings + */ + +static char *defbind[15][3] = { + { NULL, "\033\020", "\201" }, + { NULL, "\033B", "\202" }, + { NULL, "\033b", "\202" }, + { NULL, "\033D", "\203" }, + { NULL, "\033d", "\203" }, + { NULL, "\033F", "\204" }, + { NULL, "\033f", "\204" }, + { NULL, "\033H", "\205" }, + { NULL, "\033h", "\205" }, + { NULL, "\033P", "\206" }, + { NULL, "\033p", "\206" }, + { NULL, "\033Y", "\207" }, + { NULL, "\033y", "\207" }, + { NULL, "\033/", "\210" }, + { NULL, "\033?", "\211" } +}; + +/* Bind is a builtin. With no arguments, it lists the current key bindings. + * With 1 arg, it shows the binding (if any) for argv[1]. With 2 args + * the string argv[1] will be replaced by argv[2]. + */ + +int +Bind(argc, argv) + int argc; + uchar *argv[]; +{ + int s, showall; + uchar *key, *cmd, *ctemp; + struct keybind *temp, *Bindtail; + + if (argc > 4 || argc < 3) + { + fprints(2, "usage: bind [[-m] key [value]]\n"); + return (1); + } + showall = 0; + switch (argc) + { + case 4: + key = argv[2]; + cmd = argv[3]; /* Bind a string to another */ + goto bindit; /* Yuk, a goto */ + case 3: + key = argv[1]; + cmd = argv[2]; /* Bind a string to another */ + bindit: + s = strlen((char *)key); /* Get the key's length */ + if (s == 0) + break; + + temp = (struct keybind *) malloc(sizeof(struct keybind)); + if (!temp) + { + perror("malloc"); + return (1); + } + temp->key = (uchar *) malloc(s + 1); + if (!(temp->key)) + { + perror("malloc"); + return (1); + } + + strcpy((char *)temp->key, (char *)key); /* Copy the key */ + temp->len = s; + temp->cmd = (uchar *) malloc(strlen((char *)cmd) + 1); + if (!(temp->cmd)) + { + perror("malloc"); + return (1); + } + /* Copy the value */ + ctemp = temp->cmd; + while (*cmd != EOS) + { + if (*cmd != '\\') + *(ctemp++) = *(cmd++); + else + { + showall = 0; + cmd++; /* or an octal value */ + while (isdigit(*cmd)) + showall = (showall << 3) + (*(cmd++) - 48); + *(ctemp++) = showall & 0xff; + } + } + *ctemp = EOS; + temp->next = NULL; + if (argc == 4) + temp->mode = 1; + else + temp->mode = 0; + if (s > Keylength) + Keylength = s; + + if (!Bindhead) /* Add to linked list */ + Bindhead = temp; /* Currently this allows duplicates :-( */ + else + { + for (Bindtail = Bindhead; Bindtail->next; Bindtail = Bindtail->next); + Bindtail->next = temp; + } + break; + case 1: + showall = 1; /* Print one or more bindings */ + case 2: + for (temp = Bindhead; temp; temp = temp->next) + if (showall || !strcmp((char *)temp->key, (char *)key)) + { + if (temp->mode) + prints(" * "); + else + prints(" "); + mprint(temp->key, 1); + for (s = Keylength - temp->len; s; s--) + write(1, " ", 1); + prints(" bound to "); + mprint(temp->cmd, 0); + } + } + return (0); +} + +/* Unbind is a builtin which removes argv[1] from the list of key bindings */ + +int +unbind(argc, argv) + int argc; + uchar *argv[]; +{ + int s; + uchar *key; + struct keybind *temp, *t2; + + if (argc != 2) + { + fprints(2, "usage: unbind string, or unbind all\n"); + return (1); + } + key = argv[1]; + if (!strcmp((char *)key,"all")) + { + for (temp = Bindhead, t2 = Bindhead; temp; t2 = temp, temp = temp->next) + { + free(temp->key); + free(temp->cmd); + free(temp); + } + Bindhead = NULL; + return(0); + } + s = strlen((char *)key); /* Get the key's length */ + if (s == 0) + return (1); + + Keylength = 0; + for (temp = Bindhead, t2 = Bindhead; temp; t2 = temp, temp = temp->next) + if (s == temp->len && !strcmp((char *)temp->key, (char *)key)) + { + if (temp == Bindhead) + Bindhead = temp->next; + else + t2->next = temp->next; + free(temp->key); + free(temp->cmd); + free(temp); + } + else if (temp->len > Keylength) + Keylength = temp->len; + return (0); +} + +/* Initialise the default key bindings */ +void initbind() + { + int i; + + for (i = 0; i < 15; i++) + Bind(3, (uchar **) defbind[i]); /* Set default bindings */ + CLEmode = 0; /* and start in mode 1 */ + } + + +/* Exbind: get one or more characters from the user, expanding bindings + * along the way. This routine is recursive & hairy! When called from + * getcomcmd(), inbuf is NULL, indicating we want user keystrokes. + * These are read in, and if there are no partial bind matches, are + * returned to getcomcmd(). If any partial matches, they are buffered + * in bindbuf until either no partials or 1 exact match. Once a match is + * found, we call ourselves with inbuf pointing to the replacement string. + * Thus bindings can recurse, up to the size of bindbuf. Note also that + * after we recurse once, we check to see if there are any leftover chars + * in inbuf, and recurse on them as well. + */ + +#ifdef PROTO +static void expbind(uchar *inbuf) +#else +static void +expbind(inbuf) /* Expand bindings from user's input */ + uchar *inbuf; +#endif +{ + uchar a, *startptr, *exactptr; + int c, currlen, partial, exact; + struct keybind *temp; + + if (inbuf == NULL) + bindptr = bindbuf; + startptr = bindptr; + currlen = 0; + + while (1) /* Look for a keystroke binding */ + { + partial = exact = 0; + if ((inbuf == NULL) || ((a = *(inbuf++)) == EOS)) + c = read(0, (char *)&a, 1); + if (c != -1) /* Decide which uchar to put in the buffer */ + { + *(bindptr++) = a; + *bindptr = EOS; + currlen++; + if ((int) (bindptr - bindbuf) > 510) + return; + } + + for (temp = Bindhead; temp != NULL; temp = temp->next) + { /* Count the # of partial & exact matches */ + /* We exclude mode bindings when CLEmode==0 */ + if (CLEmode == 0 && temp->mode == 1) + continue; + if (currlen > temp->len) + continue; + if (!strcmp((char *)startptr, (char *)temp->key)) + { + exact++; + exactptr = temp->cmd; + } + if (!strncmp((char *)startptr, (char *)temp->key, currlen)) + partial++; + } + if (partial == 0) + break; /* No binding at all */ + if (partial == 1 && exact == 1) /* An exact match, call ourselves */ + { + bindptr = startptr; /* with the matched word */ + expbind(exactptr); + break; + } + } + + if (inbuf != NULL && *inbuf != EOS) /* If any part of our word left over */ + expbind(inbuf); /* check it as well */ +} + +/* Getcomcmd converts a user's keystokes into the commands used by the CLE. + * If there are no chars handy in bindbuf, we call expbind() to get some. + * Then we scan thru the chars and deliver chars or commands to the CLE. + * Hopefully because we use commands>255, the CLE will work with 8-bit + * extended ASCII. + */ + +int +getcomcmd() /* Get either a character or a command from the */ +{ /* user's input */ + int c; + + /* If no chars, get chars from stdin and */ + /* expand bindings */ + while ((c = *(bindptr++)) == 0) + { + expbind(NULL); + bindptr = bindbuf; + } + + /* Default to usual keys */ +#ifdef DEBUG + fprints(2, "Returning %x\n", c); +#endif + return (c); +} +#endif /* NO_BIND */ diff --git a/Applications/wish/builtin.c b/Applications/wish/builtin.c new file mode 100644 index 0000000000..4362ab9ad8 --- /dev/null +++ b/Applications/wish/builtin.c @@ -0,0 +1,275 @@ +/* This file does builtins + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" + +#ifdef PROTO +static int Echo(int argc, char *argv[]), Cd(int argc, char *argv[]); +static int Tilde(int argc, char *argv[]), Untilde(int argc, char *argv[]); +static int Exit(int argc, char *argv[]), Exec(int argc, char *argv[]); +static int Umask(int argc, char *argv[]); +#else +static int Echo(), Cd(); +static int Tilde(), Untilde(); +static int Exit(), Exec(); +static int Umask(); +#endif + +struct builptr buillist[] = { + {"cd", Cd}, + {"echo", Echo}, + {"exit", Exit}, + {"exec", Exec}, + {"umask", Umask}, + {"source", source}, +#ifndef NO_BIND + {"bind", Bind}, + {"unbind", unbind}, +#endif +#ifndef NO_HISTORY + {"history", history}, +#endif +#ifndef NO_VAR + {"export", export}, + {"unexport", unset}, + {"set", set}, + {"unset", unset}, + {"setenv", set}, + {"shift", shift}, +#endif +#ifndef NO_ALIAS + {"alias", alias}, + {"unalias", unalias}, +#endif +#ifndef NO_TILDE + {"tilde", Tilde}, + {"untilde", Untilde}, +#endif +#ifndef NO_JOB +#if defined(UCBJOB) || defined(POSIXJOB) || defined(V7JOB) + {"bg", bg}, + {"fg", fg}, +#endif + {"jobs", joblist}, + {"kill", Kill}, +#endif + {NULL, NULL}}; + + +static int +Exit(argc, argv) + int argc; + char *argv[]; +{ + int how; + + if (argc == 2) + how = atoi(argv[1]); + else + how = 0; + leave_shell(how); + return (1); +} + +static int +Exec(argc, argv) + int argc; + char *argv[]; +{ + argv++; + execvp(argv[0], argv); + fprints(2, "Can't exec %s\n", argv[0]); + return (1); +} + +static int +Echo(argc, argv) + int argc; + char *argv[]; +{ + int doreturn = 1; + int firstarg = 1; + + if (argc > 1 && !strcmp(argv[1], "-n")) + { + doreturn = 0; + firstarg = 2; + } + + for (; firstarg < argc; firstarg++) + { + (void) write(1, argv[firstarg], strlen(argv[firstarg])); + (void) write(1, " ", 1); + } + if (doreturn) + (void) write(1, "\n", 1); + return (0); +} + +/* Here is the global definition for the current directory variable */ +char currdir[128]; + +static int +Cd(argc, argv) + int argc; + char *argv[]; +{ + extern struct vallist vlist; + char *path; + + if (argc > 1) + path = argv[1]; + else if ((path = EVget("HOME")) == NULL) + path = "."; + if (chdir(path) == -1) + { + fprints(2, "%s: bad directory\n", path); + return (1); + } +#ifdef USES_GETWD + if (getwd(currdir)) +#else + if (getcwd(currdir, MAXPL)) +#endif + { + setval("cwd", currdir, &vlist); + return (0); + } + else + { + (void) write(2, "Can't get cwd properly\n", 23); + return (1); + } +} + + +#ifndef NO_TILDE +/* Tilde is a builtin which associates a dir name + * with a shorthand for that dir. + * If called with -l, print out all the passwd file too. + * + * Name holds the shorthand name (starting with a ~). + * Var holds the actual name of the directory. + */ + +struct vallist tlist; /* The tilde list */ + +static int +Tilde(argc, argv) + int argc; + char *argv[]; +{ + struct val *t; + struct passwd *entry; + bool printall = FALSE; + + switch (argc) + { + case 3: + setval(argv[1], argv[2], &tlist); + return (0); + break; + case 2: + if (strcmp(argv[1], "-l")) + { + if ((t = searchval(&tlist, argv[1], TRUE, FALSE)) != NULL) + prints("%s %s\n", t->name, t->val); + break; + } + else + printall = TRUE; + case 1: + for (t = tlist.head; t; t = t->next) + prints("%s %s\n", t->name, t->val); + if (printall) + { + while ((entry = getpwent()) != NULL) + prints("%s %s\n", entry->pw_name, entry->pw_dir); + endpwent(); + } + break; + default: + prints("Usage: tilde [shorthand [dir]] or tilde -l\n"); + return (1); + } + return (0); +} + + +/* Untilde removes a shorthand from the tilde list */ + +static int +Untilde(argc, argv) + int argc; + char *argv[]; +{ + int i; + + if (argc < 2) + { + prints("Usage: %s [short] [short] ...\n", argv[0]); + return (1); + } + for (i = 1; i < argc; i++) + if (!searchval(&tlist, argv[i], FALSE, FALSE)) + prints("No such shorthand: %s\n", argv[i]); + return (0); +} +#endif + + +static int Umask(argc,argv) + int argc; + char *argv[]; + { + int umaskval; + char *cmd; + + switch(argc) + { + case 1: umaskval=umask(0); umask(umaskval); + prints("0%o\n",umaskval); + return(0); + case 2: if (*argv[1]=='0') /* Convert octal */ + { + cmd= argv[1]; umaskval=0; + while (isdigit(*cmd)) + umaskval = (umaskval << 3) + (*(cmd++) - 48); + } + else umaskval=atoi(argv[1]); + umask(umaskval); + return(0); + default: + fprints(2,"Usage: umask [value]\n"); return(1); + } + } + +/* Do builtin. This returns either the positive exit status of the builtin, + * or -1 indicating there was no builtin. We also return 0 for the `pid' + * of the builtin. Note that `fg' is an exception - it returns the new + * fg pid (i.e exit status for fg of 0) OR 0 (i.e exit status for fg of 1). + * So we have to do some mangling with the fg return value only. + */ +int +builtin(argc, argv, rtnpid) + int argc; + char *argv[]; + int *rtnpid; +{ + struct builptr *bptr; + + *rtnpid= 0; /* Usually */ + for (bptr = buillist; bptr->name != NULL; bptr++) + { + if (!strcmp(argv[0], bptr->name)) + if (strcmp(argv[0],"fg")) /* Not fg */ + return ((*(bptr->fptr)) (argc, argv)); + else + { *rtnpid= (*(bptr->fptr)) (argc, argv); + return((*rtnpid)?0:1); + } + } + return (-1); +} diff --git a/Applications/wish/clebuf.c b/Applications/wish/clebuf.c new file mode 100644 index 0000000000..7e0cb157b9 --- /dev/null +++ b/Applications/wish/clebuf.c @@ -0,0 +1,79 @@ +/* Command Line Editor Buffer functions + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" + +/* This version of CLE buffers all characters destined for the terminal + * into a buffer which is flushed just before getcomcmd is called. This + * should hopefully reduce the number of syscalls used in the CLE. The + * following variables hold the buffer & associated pointers. + */ + +#define OUTSIZE 128 /* The size of the buffer */ + +#ifndef NO_COMLINED +extern int curs[2]; +#endif +extern int wid; +extern bool Msb; + +static char outbuf[OUTSIZE]; /* The buffer we use */ +static char *outptr = outbuf; /* Ptr into outbuf for next char */ +static int outcnt = 0; /* Number of chars in outbuf */ + +/* Flushbuf simply flushes the buffer to stdout */ + +void +flushbuf() +{ + if (outcnt == 0) return; + write(1, outbuf, outcnt); + outcnt = 0; + outptr = outbuf; +} + +/* Addbuf takes a string and adds it to the outbuf. If outbuf is + * full, we flush the buffer. + */ + +void +addbuf(str) + char *str; +{ + while (1) + { + for (; *str && outcnt < (OUTSIZE - 1); outcnt++) + *(outptr++) = *(str++); + if (*str == EOS) return; + flushbuf(); + } +} + +/* Mputc prints out a character and updates the cursor position. + * It also handles control chars by preceding them with a caret. + */ + +void +mputc(b) + int b; +{ + extern char *so, *se; + uchar c = (uchar) b; + char d = c; + + if (Msb && c > 0x7f) + { addbuf(so); d &= 0x7f; } + *(outptr++) = d; /* We mimic addbuf for 1 char */ + if (++outcnt == (OUTSIZE - 1)) flushbuf(); + if (Msb && c > 0x7f) addbuf(se); +#ifndef NO_COMLINED + curs[0]++; + if (curs[0] >= wid) + { addbuf("\n"); /* goto start of next line */ + curs[0] = curs[0] % wid; /* hopefully gives zero */ + curs[1]++; + } +#endif +} diff --git a/Applications/wish/clex.c b/Applications/wish/clex.c new file mode 100644 index 0000000000..db218e16ba --- /dev/null +++ b/Applications/wish/clex.c @@ -0,0 +1,476 @@ +/* Here is the declaration of the candidate array. This is used in meta.c + * and probably in parse.c, as well as several million other places. + */ + +#include "header.h" + +struct candidate carray[MAXCAN]; +extern int ncand; +static int maxlen; + +/* Compare is the routine used by qsort to reorder the elements + * in the carray. + */ +int +compare(a, b) +#ifdef BSD43 + CONST struct candidate *a, *b; +#else + CONST void *a, *b; +#endif +{ + struct candidate *c, *d; + + c = (struct candidate *) a; + d = (struct candidate *) b; + return (strcmp(c->name, d->name)); +} + +/* Addcarray adds a new node to the carray. If prev is non-null, then + * prev->next points to the new node. If malc is TRUE, space is malloc'd + * and the word copied. + */ +void +addcarray(word, prev, mode, malc) + char *word; + struct candidate *prev; + int mode; + bool malc; /* Ha ha - a play on clam :-) */ +{ + int j; + struct candidate *here; + + if (ncand == MAXCAN) return; + here = &carray[ncand]; + if (malc && word) + { j = strlen(word); + here->name = (char *) Malloc((unsigned) (j + 2), "addcarray"); + strcpy(here->name, word); + } + else here->name = word; + if (prev >= carray) prev->next = here; + here->next = NULL; + here->mode = mode; + ncand++; +} + + +#ifndef NO_CLEX +/* Print out the maxlen partial match on the word, placing the result at + * pos in the given line. + */ +#ifdef PROTO +static void extend(char *line, int *pos, char *word) +#else +static void +extend(line, pos, word) + char *line; + int *pos; + char *word; +#endif +{ + extern char *wbeep; + extern int beeplength; + int i, j, nostop = 1; + char *newword, *t; + + if (*word == '~' || *word == '$') word++; + t = strrchr(word, '/'); /* Go to the last '/' */ + if (t != NULL) word = ++t; + + switch (ncand) + { + case 0: + Beep; + return; + case 1: + newword = carray[0].name; + default: + if ((newword = (char *) malloc((unsigned) maxlen + 2)) == NULL) + return; + for (i = 0; i < maxlen + 1; i++) /* Clear the new word */ + newword[i] = EOS; + strcpy(newword, word); /* Set up as much as we have */ + + for (i = strlen(word); nostop && i < maxlen; i++) + for (j = 0; j < ncand; j++) + { if (strlen(carray[j].name) <= i) /* Candidate too short, stop */ + { newword[i] = EOS; nostop = 0; break; } + if (newword[i] == 0) /* Copy 1 letter over */ + newword[i] = carray[j].name[i]; + if (newword[i] != carray[j].name[i]) /* Doesn't match the copy */ + { newword[i] = EOS; /* the scrub letter and stop */ + nostop = 0; + break; + } + } + } + for (i = strlen(word); i < strlen(newword); i++) + insert((uchar *) line, (*pos)++, (uchar) newword[i]); + + if (ncand == 1) + { if ((carray[0].mode & S_IFMT) == S_IFDIR) + insert((uchar *) line, (*pos)++, '/'); + else + insert((uchar *) line, (*pos)++, ' '); + } + else + { Beep; free(newword); } +} + + +/* Print out the candidates found in columns. + */ +#ifdef PROTO +static void colprint(void) +#else +static void +colprint() +#endif +{ + extern int wid; + int i, j, collength, numperline, index; + char format[6]; + + maxlen += 2; + numperline = wid / maxlen; + collength = ncand / numperline; + if (ncand % numperline) collength++; + sprints(format, "%%%ds", maxlen); + for (i = 0; i < collength; i++) + { write(1, "\n", 1); + for (j = 0; j < numperline; j++) + { index = i + j * collength; + if (index >= ncand) break; + if ((carray[index].mode & S_IFMT) == S_IFDIR) + strcat(carray[index].name, "/"); +#ifdef S_IFLNK + else if ((carray[index].mode & S_IFMT) == S_IFLNK) + strcat(carray[index].name, "@"); +#endif + else if (carray[index].mode & 0111) + strcat(carray[index].name, "*"); + prints(format, carray[index].name); + } + } + write(1, "\n", 1); +} + + + + +/* Find the name of a file, given a partial word to match against. The + * word may be an absolute path name, or a relative one. Any matches + * against the word are added to the carray. + */ +#ifdef PROTO +static void findfile(char *word) +#else +static void +findfile(word) + char *word; +#endif +{ + extern char currdir[]; + int i, j; + char partdir[MAXWL]; + char *cddir; + char *match; + DIR *dirp; + struct stat statbuf; +#ifdef USES_DIRECT + struct direct *entry; +#else + struct dirent *entry; +#endif + + for (i = 0; i < MAXWL; i++) + partdir[i] = EOS; + + if (word != NULL && *word != EOS) + { + /* Make full pathname */ + if (*word != '/') + { strcpy(partdir, currdir); + strcat(partdir, "/"); + } + strcat(partdir, word); + + /* Find the directory name */ + if ((match = strrchr(partdir, '/')) == NULL) + { prints("Looney! No / in word %s\n", partdir); return; } + + *match = EOS; + match++; /* Match holds the partial file name */ + + i = strlen(match); /* Get the length of the name */ + } + else + { strcpy(partdir, currdir); i = 0; } + + cddir= partdir; + if (*partdir == EOS) /* Can occur when only / is 1st char */ + cddir="/"; + + if ((dirp = opendir(cddir)) == NULL) + { prints("Could not open the directory %s\n", cddir); return; } + + if (chdir(cddir) == 0) + while ((entry = readdir(dirp)) != NULL && ncand < MAXCAN) + { + /* Ignore dot and dot-dot */ + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + /* If we find a partial match add to list */ + if (i == 0 || !strncmp(entry->d_name, match, i)) + { j = strlen(entry->d_name); + if (j > maxlen) maxlen = j; + /* and get the mode as well */ + statbuf.st_mode = 0; + stat(entry->d_name, &statbuf); + addcarray(entry->d_name, NULL, statbuf.st_mode, TRUE); + } + } + closedir(dirp); + chdir(currdir); +} + + +/* Find the name of a variable, or if word has a / in it, get the + * variable's value and treat it as a path. + * Any matches against the word are added to the carray. + */ +#ifdef PROTO +static void finddollar(char *word) +#else +static void +finddollar(word) + char *word; +#endif +{ + char dir[MAXWL]; + extern struct vallist vlist; + struct val *v; + int i, j, mode; + char *a; + + word++; /* Skip over dollar sign */ + i = strlen(word); + if ((a = strchr(word, '/')) != NULL) /* We have to find a file */ + { *(a++) = EOS; /* Get the var name */ + for (v = vlist.head; v; v = v->next) + if (!strncmp(v->name, word, i)) + { sprints(dir, "%s/%s", v->val, a); /* Form the full path name */ + findfile(dir); /* and match against it */ + } + *(--a) = '/'; /* Extend needs to see the / */ + return; + } + + /* Look through the var names */ + for (v = vlist.head; v; v = v->next) + if (!strncmp(v->name, word, i)) + { j = strlen(v->name); + if (*(v->val) == '/') mode = S_IFDIR; /* Guess if it's a dir */ + else mode = 0; + addcarray(v->name, NULL, mode, TRUE); + if (j > maxlen) maxlen = j; + } +} + +/* Find the name of an alias or builtin, given the partial word. + * Any matches against the word are added to the carray. + */ + +#ifdef PROTO +static void findbuilt(char *word) +#else +static void +findbuilt(word) + char *word; +#endif +{ + extern struct builptr buillist[]; +#ifndef NO_ALIAS + extern struct vallist alist; +#endif + int h, i, j; + struct val *v; + + i = strlen(word); + +#ifndef NO_ALIAS + for (v = alist.head; v; v = v->next) + if (!strncmp(v->name, word, i)) + { j = strlen(v->name); + addcarray(v->name, NULL, 0700, TRUE); + if (j > maxlen) maxlen = j; + } +#endif + + for (h = 0; buillist[h].name; h++) /* Then the builtins */ + if (i == 0 || !strncmp(buillist[h].name, word, i)) + { j = strlen(buillist[h].name); + addcarray(buillist[h].name, NULL, 0700, TRUE); + if (j > maxlen) maxlen = j; + } +} + +/* Find the name of a file, or a user, given the partial word. If there + * are no slashes, just go for a user, else get the home dir & call + * findfile. Any matches against the word are added to the carray. + * + * This now uses the tilde list. + */ +#ifdef PROTO +static void findpasswd(char *word) +#else +static void +findpasswd(word) + char *word; +#endif +{ + extern struct vallist tlist; + struct val *t; + int i, j; + char dir[MAXWL]; + + struct passwd *entry; + + if (!strncmp(word, "~#/", 3)) /* If looking for builtins, */ + { findbuilt(&word[3]); + return; + } /* use findbuilt() */ + + if (strchr(word, '/') != NULL)/* We have to find a file */ + { tilde(word, dir); + if (*dir != 0) findfile(dir); + return; + } + + word++; /* Skip over tilde */ + i = strlen(word); + + /* Try our tilde list first */ + for (t = tlist.head; t; t = t->next) + if (!strncmp(t->name, word, i)) + { j = strlen(t->name); + addcarray(t->name, NULL, S_IFDIR, TRUE); + if (j > maxlen) maxlen = j; + } + + while ((entry = getpwent()) != NULL && ncand < MAXCAN) + { + if (i == 0 || !strncmp(entry->pw_name, word, i)) + { j = strlen(entry->pw_name); + addcarray(entry->pw_name, NULL, S_IFDIR, TRUE); + if (j > maxlen) maxlen = j; + } + } + endpwent(); +} + +/* Find the name of a file by looking through $PATH. + * Any matches against the word are added to the carray. + */ +#ifdef PROTO +static void findbin(char *word) +#else +static void +findbin(word) + char *word; +#endif +{ + int i; + char word2[MAXWL]; + char *Path, *thispath, *temp; + + temp = EVget("PATH"); /* Get the PATH value */ + if (temp == NULL) return; + Path = thispath = (char *) malloc((unsigned) strlen(temp) + 4); + if (thispath == NULL) return; + strcpy(thispath, temp); + while (thispath != NULL) + { for (i = 0; i < MAXWL; i++) word2[i] = EOS; + temp = thispath; + while (*temp != EOS && *temp != ' ' && *temp != ':') temp++; + if (*temp == EOS) temp = NULL; + else *(temp++) = EOS; + + strcpy(word2, thispath); + strcat(word2, "/"); + if (word != NULL || *word != EOS) strcat(word2, word); + + findfile(word2); + + while (temp != NULL && *temp != EOS && (*temp == ' ' || *temp == ':')) + temp++; + if (temp == NULL || *temp == EOS) thispath = NULL; + else thispath = temp; + } + free(Path); +} + + +/* Complete subsumes the work of two routines in old Clam, depending on how: + * + * case 0: Print out a columnated list of files that match the word + * at position pos. The line is unchanged. + * case 1: Try to complete as much as possible the word at pos, by + * using the find routines above. Add the completion to the line. + */ +void +complete(line, pos, how) + char *line; + int *pos; + bool how; +{ + extern char *wbeep, yankbuf[]; + extern int beeplength; + extern char *wordterm; + char *b, *yankword = yankbuf; + int startpos; + + ncand = maxlen = 0; + + if (line[(*pos) - 1] == ' ') + { + strcpy(yankbuf, ""); + startpos = *pos - 1; + } /* nothing there to get */ + else + { b = wordterm; /* first get the thing we've got so far */ + wordterm = " "; + startpos = yankprev((uchar *) line, *pos); + wordterm = b; + } + + if (*yankword == '\'' || *yankword == '"' || *yankword == '`') + yankword++; + if (startpos == 0 && *yankword != '.' && *yankword != '/' && *yankword != '~' + && *yankword != '$') + findbin(yankword); + else + switch (*yankword) + { case '~': + findpasswd(yankword); break; + case '$': + finddollar(yankword); break; + default: + findfile(yankword); + } + + if (how == TRUE) extend(line, pos, yankword); + else + { if (ncand == 0) Beep; + else + { qsort((void *) carray, ncand, sizeof(struct candidate), compare); + colprint(); + prprompt(); + show((uchar *) line, TRUE); + } + } + while ((--ncand) >= 0) free(carray[ncand].name); +} +#endif /* NO_CLEX */ diff --git a/Applications/wish/comlined.c b/Applications/wish/comlined.c new file mode 100644 index 0000000000..f6ed3a221d --- /dev/null +++ b/Applications/wish/comlined.c @@ -0,0 +1,775 @@ +/* List of commands defined for the command line editor. + * We only use the ctrl chars, so that people can use + * the editor with 8-bit ascii characters. + * + * $Revision: 41.3 $ $Date: 2003/04/21 13:08:43 $ + */ + +#include "header.h" + +#define MARK 0 /* Save position, make a mark */ +#define START 1 /* Go to start of line */ +#define BAKCH 2 /* Go back one character */ +#define INT 3 /* Interrupt this task */ +#define DELCH 4 /* Delete the current character */ +#define END 5 /* Goto the end of the line */ +#define FORCH 6 /* Go forward one char */ +#define BEEP 7 /* Simply ring the bell */ +#define BKSP 8 /* Backspace over previous character */ +#define COMPLETE 9 /* Complete the current word */ +#define FINISH 10 /* Finish and execute the line */ +#define KILLEOL 11 /* Kill from cursor to end of line */ +#define CLREDISP 12 /* Clear screen & redisplay line */ +#define NL 13 /* Same as FINISH */ +#define NEXTHIST 14 /* Step forward in history */ +#define OVERWRITE 15 /* Toggle insert/overwrite mode */ +#define BACKHIST 16 /* Step backward in history */ +#define XON 17 /* Resume tty output */ +#define REDISP 18 /* Redisplay the line */ +#define XOFF 19 /* Stop tty output */ +#define TRANSPCH 20 /* Transpose current & previous characters */ +#define KILLALL 21 /* Kill the whole line */ +#define QUOTE 22 /* Quote next character literally */ +#define DELWD 23 /* Delete word backwards */ +#define GOMARK 24 /* Goto a mark */ +#define YANKLAST 25 /* Yank the previous word into a buffer */ +#define SUSP 26 /* Suspend process */ + +/* A Gap here for 27 This can be filled as needed */ +/* A Gap here for 28 This can be filled as needed */ + +#define MODEON 29 /* Include bindings with mode bit on */ +#define MODEOFF 30 /* Exclude bindings with mode bit on */ +#define QUOTEUP 31 /* Turn the following char's msb on */ + +#define DEL 127 /* Same as BKSP */ + +#define MATCHPART 129 /* Match a previous partial command */ +#define BAKWD 130 /* Go backwards one word */ +#define DELWDF 131 /* Delete word forwards */ +#define FORWD 132 /* Go forwards one word */ +#define GETHELP 133 /* Get help on a word */ +#define PUT 134 /* Insert buffer on the line */ +#define YANKNEXT 135 /* Yank the next word into the buffer */ +#define SEARCHF 136 /* Search forward for next typed character */ +#define SEARCHB 137 /* Search backwards for next typed character */ + +#define isctrl(x) (((x+1)&0x7f)<33) + +/* Strip takes the line, and removes leading spaces. If the first non-space + * character is a hash, it returns 1. This should also remove trailing + * comments; I might just move the whole thing into meta_1. + */ +#ifdef PROTO +static int strip(uchar *line) +#else +static int +strip(line) + uchar *line; +#endif +{ + int i, nosave = 0; + + for (i = 0; line[i] == ' '; i++); + if (line[i] == '#') + { nosave = 1; i++; } + if (i) strcpy((char *) line, (char *) &line[i]); + return (nosave); +} + +bool Msb; /* Is var Msb defined? */ +#ifndef NO_COMLINED +uchar yankbuf[512]; /* Buffer used when yanking words */ +uchar *wordterm; /* Characters that terminate words */ +extern int wid; /* The width of the screen (minus 1) */ + +/* A quick blurb on the curs[] structure. + * Curs[0] holds the column pos'n of the cursor, curs[1] holds the # of + * lines the cursor is below the prompt line. e.g (0,0) is the first char + * of the prompt, (25,3) is in column 25, 3 lines below the prompt line. + */ +int curs[2]; + + +/* Go moves the cursor to the position (vert,hor), and updates the cursor */ +#ifdef PROTO +static void go(int hor, int vert) +#else +static void +go(hor, vert) + int hor, vert; +#endif +{ + extern char *bs, *nd, *up; + int hdiff, vdiff; + + vdiff = vert - curs[1]; /* vertical difference between */ + /* current and future positions */ + + if (vdiff <= 0) /* if negative go up */ + for (; vdiff; vdiff++) addbuf(up); + else + { /* else go down */ + for (; vdiff; vdiff--) addbuf("\n"); + curs[0] = 0; + } + + hdiff = hor - curs[0]; /* horizontal difference between */ + /* current and future positions */ + curs[0] = hor; + curs[1] = vert; /* a new current pos, hopefully */ + /* assigned here because hor changed */ + /* below and curs needed above */ + + if (hdiff < 0) /* if negative go back */ + { + if (-hdiff <= hor) /* if shorter distance just use ^H */ + for (; hdiff; hdiff++) addbuf(bs); + else + { /* else cr and go forward */ + addbuf("\r"); + for (; hor; hor--) addbuf(nd); + } + } + else + for (; hdiff; hdiff--) /* have to go forward */ + addbuf(nd); +} + +/* Backward: Move the cursor backwards one character */ +#ifdef PROTO +static void backward(void) +#else +static void +backward() +#endif +{ + extern char *bs; + + if (curs[0] == 0) go(wid - 1, curs[1] - 1); + else { addbuf(bs); curs[0]--; } +} + +/* Forward: Move the cursor forwards one character */ +#ifdef PROTO +static void forward(void) +#else +static void +forward() +#endif +{ + extern char *nd; + + curs[0]++; + if (curs[0] >= wid) + { + addbuf("\n"); /* goto start of next line */ + curs[0] = curs[0] % wid; /* hopefully gives zero */ + curs[1]++; + } + else addbuf(nd); +} + +/* Show is a routine that replaces four routines in the old version of Clam. + * The flag variable holds the id of which `routine' to emulate: + * + * case 0: Insert Insert the letter at position pos in the line, + * updating the cursor, and redrawing the line. + * case 1: Overwrite Overwrite the letter at pos in the line, + * updating the cursor, and redrawing the line. + * Note that to toggle ins/ovw, we can have a + * variable ovwflag, and do ovwflag= 1-ovwflag. + * case 2: Show Just redisplay the line. + * case 3: Goend Goto the end of the line. + */ +int +Show(line, pos, let, flag) + uchar *line; + int let, pos, flag; +{ + extern int lenprompt; + int i, horig=0, vorig=0; + uchar letter = (uchar) let, c; + + switch (flag) + { /* Case 0: insert character */ + case 0: + for (i = pos; line[i]; i++); /* goto end of line */ + for (; i != pos; i--) /* copy characters forward */ + line[i] = line[i - 1]; + case 1: + line[pos] = letter; /* Case 1: overwrite char */ + letter &= 0x7f; + if (isctrl(letter)) horig = curs[0] + 2; + else horig = curs[0] + 1; /* Calculate the new cursor */ + vorig = curs[1]; /* position */ + if (horig > wid - 1) + { horig = horig % wid; + vorig++; + } + break; + case 2: + pos = 0; /* Case 2: show the line */ + horig = curs[0]; + vorig = curs[1]; /* save original values */ + curs[0] = lenprompt; + curs[1] = 0; + } /* Case 3: goto end of line */ + for (c = line[pos]; c; c = line[++pos]) /* write out rest of line */ + { + if (isctrl(c)) /* if it's a control char */ + { mputc('^'); /* print out the ^ and */ + mputc(c | 64); /* the equivalent char for it */ + } + else mputc(c); /* else just show it */ + } + if (flag != 3) + go(horig, vorig); /* h/vorig unused for goend */ + return (pos); /* goend only uses this value */ +} + + +/* I'm not exactly sure what copyback() does yet - Warren */ +#ifdef PROTO +static void copyback(uchar *line, int pos, int count) +#else +static void +copyback(line, pos, count) + uchar *line; + int pos, count; +#endif +{ + uchar c; + int i, horig, vorig, wipe; + +#ifdef DEBUG + fprints(2, "pos %d count %d\n", pos, count); +#endif + if ((i = pos + count) < MAXLL) + { + wipe = 0; + for (horig = pos; horig < i; horig++) + if (isctrl(line[horig])) + wipe += 2; /* calculate amount to blank */ + else + wipe++; + for (; line[i] != EOS; ++i) /* copy line back count chars */ + line[i - count] = line[i]; + for (horig = i - count; i > horig && i < MAXLL + 1; i--) + line[i - 1] = EOS; /* end with nulls */ + } + else + { + write(2, "count passed to copyback is too big\n", 35); + return; + } + horig = curs[0]; + vorig = curs[1]; + for (c = line[pos]; c; c = line[++pos]) + if (isctrl(c)) + { + mputc('^'); + mputc(c | 64); + } + else + mputc(c); +#ifdef DEBUG + fprints(2, "wipe %d\n", wipe); +#endif + for (i = 0; i < wipe; i++) + mputc(' '); /* blank old chars */ + go(horig, vorig); +} + +#define delnextword(line,pos) nextword(line,&pos,0) +#define forword(line,pos) nextword(line,pos,1) +#define yanknext(line,pos) nextword(line,&pos,2) + +#define delprevword(line,pos) prevword(line,pos,0) +#define backword(line,pos) prevword(line,pos,1) + +/* The following two routines each replace three separate ones from old Clam */ + +/* Nextword works on the word after/at the cursor poition, depending on flag: + * + * case 0: Delnextword The word after the cursor is removed + * from the line, and the display is updated. + * case 1: Forword The cursor is moved to the start of the + * next word. + * case 2: Yanknext The word after the cursor is put into yankbuf. + * + * Although pos is passed as a pointer, only forword() updates the value. + */ +#ifdef PROTO +static void nextword(uchar *line, int *p, int flag) +#else +static void +nextword(line, p, flag) + uchar *line; + int *p, flag; +#endif +{ + int inword = 0, l = 1, pos = *p, charcount = 0; + uchar c; + + while (l) + { + if ((c = line[pos]) == EOS) + { l = 0; break; } + if (strchr((char *) wordterm, c) != NULL) /* Found end of a word */ + { charcount++; + pos++; + if (inword) l = 0; + } + else + { inword = 1; + charcount++; + pos++; + } + } + +#ifdef DEBUG + fprints(2, "Deleting %d chars\n", charcount); +#endif + switch (flag) + { + case 0: + copyback(line, *p, charcount); + break; + case 1: + for (; l < charcount; l++) forward(); /* move forward */ + *p = pos; + case 2: + for (pos = *p; l < charcount; l++, pos++) + yankbuf[l] = line[pos]; + yankbuf[l] = EOS; + } +} + +/* Prevword works on the word before the cursor poition, depending on flag: + * + * case 0: Delprevword The word before the cursor is removed + * from the line, and the display is updated. + * case 1: Backword The cursor is moved to the start of the + * previous word. + * case 2: Yankprev The word before the cursor is put into yankbuf. + * + * Although pos is passed as a pointer, only delprevword and + * backword update the value. + */ +int +prevword(line, p, flag) + uchar *line; + int *p, flag; +{ + int inword = 0, l = 1, q, pos = *p, charcount = 0; + + while (l) + { + if (pos == 0) + { l = 0; break; } + if (strchr((char *) wordterm, line[pos - 1]) != NULL) + { /* Found end of a word */ + if (inword) l = 0; + else + { charcount++; pos--; } + } + else + { + inword = 1; + charcount++; + pos--; + } + } +#ifdef DEBUG + fprints(2, "Deleting %d chars\n", charcount); +#endif + + switch (flag) + { + case 0: + for (; l < charcount; l++) backward(); /* move back */ + copyback(line, pos, charcount); /* and copy the line on top */ + *p = pos; + break; + case 1: + for (; l < charcount; l++) backward(); /* move back */ + *p = pos; + break; + case 2: + q = pos; + for (; l < charcount; l++, q++) yankbuf[l] = line[q]; + yankbuf[l] = EOS; + } + return (pos); +} + + +/* Clrline: The line from the position pos is cleared */ +#ifdef PROTO +static void clrline(uchar *line, int pos) +#else +static void +clrline(line, pos) + uchar *line; + int pos; +#endif +{ + extern char *cd; + int i, horig=0, vorig=0; + + if (*cd != EOS) /* If there's a special char for clr */ + { /* then use it */ + addbuf(cd); + for (i = pos; line[i]; i++) line[i] = EOS; +#ifdef DEBUG + fprints(2, "cleared ok\n"); +#endif + } + else horig = curs[0]; /* else we gotta use spaces. */ + vorig = curs[1]; /* Orginal values for curs set. */ + for (i = pos; line[i]; i++) /* Wipe out all those chars */ + { /* with spaces (so slow), */ + mputc(' '); + if (isctrl(line[i])) mputc(' '); + line[i] = EOS; + } + go(horig, vorig); /* restore original curs position */ +} + +/* Transpose transposes the characters at pos and pos-1 */ +#ifdef PROTO +static void transpose(uchar *line, int pos) +#else +static void +transpose(line, pos) + uchar *line; + int pos; +#endif +{ + uchar temp; + + if (isctrl(line[pos - 1])) backward(); + backward(); + temp = line[pos]; + line[pos] = line[pos - 1]; + line[pos - 1] = temp; + if (isctrl(line[pos - 1])) + { mputc('^'); + mputc(line[pos - 1] | 64); + } + else mputc(line[pos - 1]); + if (isctrl(line[pos])) + { mputc('^'); + mputc(line[pos] | 64); + } + else mputc(line[pos]); + if (isctrl(line[pos])) backward(); + backward(); +} + + +/* Getuline gets a line from the user, returning a flag if the line + * should be saved. + */ +bool +getuline(line, nosave) + uchar *line; + int *nosave; +{ + extern char *wbeep, *cl, *cd; + extern uchar bindbuf[], *bindptr, CLEmode; + extern int errno, lenprompt, curr_hist; + uchar a; + int c, times = 1, i, pos = 0, hist = curr_hist, + hsave = lenprompt, vsave = 0, possave = 0; + int beeplength = strlen(wbeep); + + memset(line, 0, MAXLL); + wordterm = (uchar *) EVget("Wordterm"); /* Determine the word + terminators */ + if (wordterm == (uchar *) NULL || *wordterm == EOS) + wordterm = (uchar *) " \t><|/;=&`"; + if (EVget("Msb")) Msb = TRUE; + else Msb = FALSE; + bindptr = bindbuf; /* Set up the pointer to the bind buffer */ + *bindptr = EOS; + + prprompt(); /* Print out our prompt */ + setcbreak(); /* and set the terminal into cbreak mode */ + curs[0] = lenprompt; /* lenprompt global set by prprompt or when + prompt set */ + curs[1] = 0; /* start on line 0 */ + while (1) + { + flushbuf(); /* Ensure user can see the current line */ + c = getcomcmd(); /* Get a character or a command */ + for (; times > 0; times--) + switch (c) + { + case EOF: + fprints(2, "%d\n", errno); + perror("comlined"); + exit(1); + case MARK: + hsave = curs[0]; /* save position (make mark) */ + vsave = curs[1]; + possave = pos; + break; + case START: + go(lenprompt, 0); /* goto start of the line */ + pos = 0; + break; + case BAKCH: + if (pos > 0) /* if not at home, go back */ + { if (isctrl(line[pos - 1])) backward(); + backward(); + pos--; + } + else Beep; /* else ring bell */ + break; + case INT: + pos = goend(line, pos); + addbuf("\n"); + flushbuf(); + setcooked(); + return (FALSE); + case DELCH: + if (line[0] == EOS) /* Leave shell if possible */ + { flushbuf(); + if (EVget("ignoreeof")) + fprints(2, "Use `exit' to exit wish\n"); + else leave_shell(0); /* eof */ + setcooked(); + return (FALSE); /* return if no exit */ + } + else if (line[pos] != EOS) + copyback(line, pos, 1); /* delete char */ + else + complete((char *) line, &pos, FALSE); + break; + case END: + pos = goend(line, pos); /* goto end of the line */ + break; + case FORCH: + if (line[pos] != EOS) /* if not at end, go forward */ + { if (isctrl(line[pos])) forward(); + forward(); + pos++; + } + else Beep; /* else ring bell */ + break; + case KILLALL: + go(lenprompt, 0); /* goto start */ + pos = 0; + clrline(line, 0); /* and kill from pos=0 */ + hist = curr_hist; /* reset hist */ + for (pos = 0; pos < MAXLL; pos++) + line[pos] = EOS; + pos = possave = 0; + break; + case DEL: + case BKSP: + if (pos > 0) /* if not at home, delete */ + { if (isctrl(line[pos - 1])) backward(); + backward(); + copyback(line, --pos, 1); /* move line back on to */ + } /* prev char */ + else Beep; /* else ring bell */ + break; + case COMPLETE: + if (line[0] != EOS) /* try to complete word */ + complete((char *) line, &pos, TRUE); + else Beep; + break; + case NL: + case FINISH: + pos = goend(line, pos); + addbuf("\n"); + flushbuf(); + setcooked(); + line[pos++] = EOS; + *nosave = strip(line);/* process it now */ + if (line[0] != EOS) return (TRUE); + else return (FALSE); +#ifndef NO_HISTORY + case NEXTHIST: + if (hist < curr_hist) /* put next hist in line buf */ + { loadhist((char *) line, ++hist); + goto redisp; /* Yuk, a goto */ + } + else Beep; + break; + case BACKHIST: + if (hist == 1) Beep; + else + { if (hist == curr_hist) /* in line buf */ + (void) savehist((char *) line, 0); + loadhist((char *) line, --hist); + } +#endif + case REDISP: + redisp: + go(lenprompt, 0); + addbuf(cd); /* Clear to end of screen */ + show(line, TRUE); /* Show the line typed so far. */ + pos = goend(line, 0); /* goto end of the line */ + break; + case CLREDISP: + addbuf(cl); /* Clear the screen */ + flushbuf(); + prprompt(); /* Reprint the prompt and */ + show(line, TRUE); /* the line typed so far. */ + break; + case KILLEOL: + clrline(line, pos); /* kill line from cursor on */ + break; + case XON: + continue; /* can't use this */ + case XOFF: + continue; /* can't use this */ + case TRANSPCH: + if (pos > 0 && line[pos] != EOS) /* if not home or at end */ + transpose(line, pos); /* swap current and prev char */ + else Beep; /* else ring bell */ + break; + case QUOTE: + if (pos >= MAXLL) + { Beep; + continue; + } + mputc('"'); + backward(); /* literal char */ + flushbuf(); + read(0, (char *) &a, 1); + c = a; + if (c) /* don't allow EOS (null) */ + insert(line, pos++, c); + break; + case DELWD: + if (pos) delprevword(line, &pos); + else Beep; + break; + case GOMARK: + pos = possave; + go(hsave, vsave); + break; + case YANKLAST: + if (pos != 0) /* if not at home */ + yankprev(line, pos);/* yank previous word */ + else Beep; /* else ring bell */ + break; + case SUSP: + continue; + case BAKWD: + if (pos > 0) + backword(line, &pos); + else Beep; + break; + case DELWDF: + if (line[pos] != EOS) + delnextword(line, pos); + else Beep; + break; + case FORWD: + if (line[pos] != EOS) + forword(line, &pos); + else Beep; + break; + case PUT: + if (pos > MAXLL - strlen((char *) yankbuf)) + { Beep; + break; + } + for (i = 0; yankbuf[i]; i++) /* insert yank buffer */ + insert(line, pos++, yankbuf[i]); + break; + case YANKNEXT: + if (line[pos] != EOS) /* if not at end */ + yanknext(line, pos);/* yank next word */ + else Beep; /* else ring bell */ + break; + case SEARCHF: + if (line[pos]) /* search forward */ + { read(0, (char *) &a, 1); + c = a; /* char to search for */ + for (i = pos + 1; line[i] && c != line[i]; i++); + if (line[i]) + while (pos < i) + { pos++; + if (isctrl(line[pos - 1])) forward(); + forward(); + } + else Beep; /* not found */ + } + else Beep; /* at end of line */ + break; + case SEARCHB: + if (pos > 0) /* search backwards */ + { read(0, (char *) &a, 1); + c = a; /* char to search for */ + for (i = pos - 1; i >= 0 && c != line[i]; i--); + if (i >= 0) + while (pos > i) + { pos--; + if (isctrl(line[pos])) backward(); + backward(); + } + else Beep; /* not found */ + } + else Beep; /* at end of line */ + break; + case MODEON: + CLEmode = 1; /* Turn the CLE mode on */ + break; + case MODEOFF: + CLEmode = 0; /* Turn the CLE mode off */ + break; + case BEEP: + Beep; /* Simply beep */ + break; + case QUOTEUP: + if (pos >= MAXLL) /* Turn on next char's msb */ + { Beep; + continue; + } + mputc('"'); + backward(); /* literal char */ + flushbuf(); + read(0, (char *) &a, 1); + c = a; + if (c) /* don't allow EOS (null) */ + { c |= 0x80; + insert(line, pos++, c); + } + break; + default: + if (pos >= MAXLL - 1) + { Beep; + break; + } + insert(line, pos++, c); + break; + } + times = 1; + } +} +#else /* NO_COMLINED */ +bool +getuline(line, nosave) + uchar *line; + int *nosave; +{ + int i; + + prprompt(); /* Print out our prompt */ + for (i=0; inewfd[i]) + { + close(i); /* Close the one we are using */ + /* and dup back the old one */ + if (dup2(d->newfd[i], i) == -1) + { + fprints(2, "Can't undup fd %d\n", i); + } + close(d->newfd[i]); + } + } + /* Now pull the dup object off the stack */ + if (!nd) + duptop = d->prev; + free(d); +} + +/* + * Redirect redirects the file descriptors for fds 0 to 2 for the + * current process; for each fd it also takes a bitmap indicating whether + * to append to the output file, or to open from an input file. + * If an error occurs, we return -1, else 0. We only push a dupstack + * if no error occurs, so we must not call undirect afterwards. + */ +#ifdef PROTO +static int redirect(struct rdrct newfd[3]) +#else +static int +redirect(newfd) + struct rdrct newfd[3]; +#endif +{ + int i, j, appnd; + int mode, flags; + struct dupstack *d; + +#ifdef DEBUG + fprints(2, "Redirect...\n"); +#endif + + + d = (struct dupstack *) Malloc(sizeof(struct dupstack), "redirect"); + + /* Clear all the fields */ + d->newfd[0] = d->newfd[1] = d->newfd[2] = 0; + + for (i = 0; i < 3; i++) /* Do each fd in turn */ + { + /* Not this one */ + if (newfd[i].fd < 3 && newfd[i].file == NULL) + continue; + appnd = newfd[i].how & H_APPEND; + + + j = dup(i); /* Copy that file desc */ + if (j == -1) + { + fprints(2, "Couldn't dup fd %d\n", i); + undirect(d); + return (-1); + } + else + d->newfd[i] = j; + + if (newfd[i].fd > 2) /* Fd is already open */ + { + close(i); + if (dup2(newfd[i].fd, i) == -1) + { + fprints(2, "Could not dup2(%d,%d)\n", newfd[i].fd, i); + undirect(d); + return (-1); + } +#ifdef DEBUG + fprints(2, "Dup'd %d to %d\n", newfd[i].fd, i); +#endif + close(newfd[i].fd); + } + /* Open this file */ + else + { + close(i); + if (newfd[i].how & H_FROMFILE) /* for reading */ + { + flags = O_RDONLY; + if (open(newfd[i].file, flags) == -1) + { + fprints(2, "Can't open %s\n", newfd[i].file); + undirect(d); + return (-1); + } + } + else + /* for writing */ + { + flags = O_WRONLY | O_CREAT; + if (!appnd) + flags |= O_TRUNC; + else + flags |= O_APPEND; + mode = 0666; + if (open(newfd[i].file, flags, mode) == -1) + { + fprints(2, "Can't open %s\n", newfd[i].file); + undirect(d); + return (-1); + } +#ifdef DEBUG + fprints(2, "Opened %s as %d\n", newfd[i].file, i); +#endif + } + } + } + /* Now put the old fd's on the stack */ + d->prev = duptop; + duptop = d; + return (0); +} + +#ifndef NO_ALIAS +#define MAXAL 16 +static char *curr_alias[MAXAL]; /* The list of aliases we have done */ +static int adepth = 0; /* Our current depth */ + +/* Runalias checks to see if there is an alias, and then runs it. + * It either returns the exit status of the alias, or -1 if there + * was no alias. The only bit in how we are interested in is + * H_BCKGND. This gets passed onto doline to indicate how to waitfor() + * the child. + */ +#ifdef PROTO +static int runalias(int argc, char *argv[], int how) +#else +static int +runalias(argc, argv, how) + int argc; + char *argv[]; + int how; +#endif +{ + extern int Argc, saveh, Exitstatus; + extern char **Argv; +#ifdef PROTO + extern bool(*getaline) (uchar *line , int *nosave ); + bool(*oldgetline) (uchar *line , int *nosave ); +#else + extern bool(*getaline) (); + bool(*oldgetline) (); +#endif + + int i, oldsaveh, oldargc; + char **oldargv; + + /* Check for already used alias */ + for (i = 0; i < adepth; i++) + if (!strcmp(curr_alias[i], argv[0])) + return (-1); + + if (checkalias(argv[0]) != NULL) /* Find an alias */ + { + oldargc = Argc; + oldargv = Argv; + Argc = argc; + Argv = argv; + oldsaveh = saveh; + oldgetline = getaline; + if (adepth == MAXAL) + { + fprints(2, "Aliases nested too deep\n"); + /* exit(1); */ + return (-1); + } + curr_alias[adepth++] = argv[0]; +#ifdef DEBUG + fprints(11, "About to send the alias through doline()\n"); +#endif + saveh = FALSE; + how= (how & H_BCKGND) | TRUE; + getaline = getaliasline; + doline(how); + adepth--; + Argc = oldargc; + Argv = oldargv; + getaline = oldgetline; + saveh = oldsaveh; + return (Exitstatus); + } + else + return (-1); +} +#endif /* NO_ALIAS */ + + +static int bgpgrp; /* Background process group id */ + +/* Invoke simple command. This takes the args to pass to the executed + * process/builtin, the list of new file descriptors, and a bitmap + * indicating how to run the process. We only use the background bits + * at the moment. + */ + +int +invoke(argc, argv, newfd, how, anydups) + int argc, how; + char *argv[]; +struct rdrct newfd[]; +int anydups; + +{ + extern int saveh, Exitstatus; + int pid, i; + +#ifdef DEBUG + if (argv[0]) + fprints(2, "Invoking %s\n", argv[0]); +#endif + /* Firstly redirect the input/output */ + if (anydups) + { + i = redirect(newfd); + if (i == -1) + return (0); + } + + if (!(how & H_BCKGND)) /* If a foreground process */ + { + bgpgrp=0; /* Reset for next bg process */ + /* Try builtins first */ + if (argc == 0 || ((i = builtin(argc, argv, &pid)) != -1)) + { + Exitstatus = i; + if (anydups) + undirect(NULL); + return (pid); + } + +#ifndef NO_ALIAS + if ((i = runalias(argc, argv, how)) >= 0) + { Exitstatus= i; + if (anydups) + undirect(NULL); + return (0); + } +#endif + } + + /* Else fork/exec the process */ + switch (pid = fork()) + { + case -1: + fprints(2, "Can't create new process\n"); + if (anydups) + undirect(NULL); + return (0); + /* Restore signals to normal if fg */ + case 0: + saveh = FALSE; + dflsig(); +#ifdef V7JOB + ptrace(0,0,(PLONG)0,(PLONG)0); /* Enable tracing on the child */ +#endif + if (how & H_BCKGND) + { /* Move process to new proc-grp if bg */ +#if defined(UCBJOB) || defined(POSIXJOB) +#ifdef POSIXJOB + /* First get a pgrp-id if none */ + if (bgpgrp==0) bgpgrp=getpid(); + i = setpgid(0, bgpgrp); + if (i == -1) + fprints(2, "I was -1\n"); +#else + i = setpgrp(0, getpid()); + if (i == -1) + fprints(2, "I was -1\n"); +#endif /* POSIXJOB */ +#endif /* UCBJOB || POSIXJOB */ + } +#ifndef NO_VAR + if (!EVupdate()) + fatal("Can't update environment"); +#endif + /* The fork only wants fds 0,1,2 */ +#ifdef DEBUG + fprints(2, "The child is closing 3 to 20\n"); +#endif + for (i = 3; i < 20; i++) + close(i); + + /* Try builtins first */ + if (argc == 0 || ((i = builtin(argc, argv, &pid)) != -1)) + { + exit(i); + } + +#ifndef NO_ALIAS + if ((i = runalias(argc, argv, how)) >= 0) + { + if (anydups) + undirect(NULL); + exit(i); + } +#endif + /* Finally exec() the beast */ + + execvp(argv[0], argv); + /* Failed, exit the child */ + fprints(2, "Can't execute %s\n", argv[0]); + exit(1); + + default: +#ifdef DEBUG + fprints(2, "Just forked %s, pid %d\n", argv[0], pid); +#endif + /* and return the new pid */ +#ifndef NO_JOB + addjob(pid, argv[0], how & H_BCKGND); +#endif +#ifdef V7JOB +/* We can't just leave a backgrounded process as is, because it will + * stop on the exec(), and because we won't waitfor() it, it will stay + * like that. So we wake it up here. + */ + if (how & H_BCKGND) + ptrace(7,pid,(PLONG)1,(PLONG)0); +#endif + if (anydups) + undirect(NULL); + return (pid); + } +} diff --git a/Applications/wish/file.c b/Applications/wish/file.c new file mode 100644 index 0000000000..8b6aa9db5f --- /dev/null +++ b/Applications/wish/file.c @@ -0,0 +1,129 @@ +/* File does the work for opening & reading lines from files. We keep a + * stack of open fds and buffers for each file. This is to minimise the + * overhead or read()ing, but avoiding the use of stdio. + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#include "header.h" + +static int currfile = 0; +static int filedesc[10], bufbytes[10]; +static char *bufptr[10], *filebuf[10]; + + +/* Fileopen opens the named file read-only, and sets the number of bytes + * buffered in yankbuf to zero. NOTE if filename==NULL, we assume that + * fd has already been open. + */ +bool +fileopen(filename, fd) + char *filename; + int fd; +{ + if (currfile == 9) return (FALSE); /* No space on stack left */ + currfile++; + if (filename) + { if ((filedesc[currfile] = open(filename, O_RDONLY, 0)) == -1) + { currfile--; fprints(2, "Can't open %s\n", filename); return (FALSE); } + } + else + filedesc[currfile] = fd; + + filebuf[currfile] = (char *) malloc(MAXLL); + if (filebuf[currfile] == NULL) + { close(filedesc[currfile]); currfile--; + fprints(2, "Can't malloc file buffer\n"); return (FALSE); + } + bufptr[currfile] = filebuf[currfile]; + bufbytes[currfile] = 0; + return (TRUE); +} + +void +fileclose() +{ + free(filebuf[currfile]); + close(filedesc[currfile]); + currfile--; +} + +/* Getfileline obtains a line from the opened file. + */ +bool +getfileline(line, nosave) + uchar *line; + int *nosave; +{ + int in_line = 0; + + *nosave = 0; + while (1) /* Until we have a line, copy stuff */ + { /* We've already got some to copy */ + if (!in_line) + { + for (; bufbytes[currfile] && (*bufptr[currfile] == ' ' || + *bufptr[currfile] == '\t'); bufbytes[currfile]--, bufptr[currfile]++); + in_line = 0; + } + if (bufbytes[currfile] == 0) + { in_line = 1; goto doread; } /* Yuk, a goto */ + + if (*bufptr[currfile] == '#') *nosave = 1; + for (; bufbytes[currfile] && *bufptr[currfile] != '\n'; bufbytes[currfile]--) + *line++ = *bufptr[currfile]++; + + if (bufbytes[currfile]) /* We hit a \n, so return */ + { *line = EOS; bufptr[currfile]++; + bufbytes[currfile]--; return (TRUE); + } + +doread: + bufptr[currfile] = filebuf[currfile]; + /* Get more characters */ + bufbytes[currfile] = read(filedesc[currfile], filebuf[currfile], MAXLL); + if (bufbytes[currfile] < 1) return (FALSE); /* End of file */ + } +} + +int +source(argc, argv) + int argc; + char *argv[]; +{ + extern int saveh; +#ifdef PROTO + extern bool(*getaline) (uchar *line , int *nosave ); + bool(*oldgetline) (uchar *line , int *nosave ); +#else + extern bool(*getaline) (); + bool(*oldgetline) (); +#endif + + int oldsaveh; + + if (argc != 2) + { prints("Usage: source file\n"); return (1); } + +#ifdef DEBUG + prints("About to fileopen %s\n", argv[1]); +#endif + + if (fileopen(argv[1], 0) == FALSE) + { prints("Unable to source file %s\n", argv[1]); return (1); } + else + { +#ifdef DEBUG + prints("About to send the file through doline()\n"); +#endif + oldsaveh = saveh; + saveh = FALSE; + oldgetline = getaline; + getaline = getfileline; + doline(FALSE); + getaline = oldgetline; + saveh = oldsaveh; + } + fileclose(); + return (0); +} diff --git a/Applications/wish/fuzix-wish.pkg b/Applications/wish/fuzix-wish.pkg new file mode 100644 index 0000000000..f5561bcd8e --- /dev/null +++ b/Applications/wish/fuzix-wish.pkg @@ -0,0 +1,7 @@ +package wish +if-file wish + +f 0755 /usr/bin/wish wish +f 0655 /usr/man/man1/wish.1 Wish.1 +f 0644 /root/.wishrc Wishrc + diff --git a/Applications/wish/global.c b/Applications/wish/global.c new file mode 100644 index 0000000000..2b42c0960f --- /dev/null +++ b/Applications/wish/global.c @@ -0,0 +1,3 @@ +#include "header.h" + +char currdir[128]; diff --git a/Applications/wish/header.h b/Applications/wish/header.h new file mode 100644 index 0000000000..2739cc7938 --- /dev/null +++ b/Applications/wish/header.h @@ -0,0 +1,206 @@ +/* Definitions of all the various names and macros that we need to + * compile Wish. + * + * $Revision: 41.3 $ $Date: 2003/04/21 13:08:43 $ + */ + +#include "machine.h" + +/* These defines turn off some of the capabilities + * of the shell. They are mainly used for finding + * the location of bugs. + */ +#undef NO_ALIAS /* No alias functions */ +#undef NO_JOB /* No job functions */ +#undef NO_HISTORY /* No history stuff */ +#undef NO_TILDE /* No tilde stuff */ +#undef NO_VAR /* No variables */ +#undef NO_COMLINED /* No command line editor */ +#ifdef NO_COMLINED +# define NO_CLEX /* No command line extensions */ +# define NO_BIND /* No key bindings */ +#endif + +#if defined(__STDC__) && __STDC__ && !defined(MINIX1_5) +# define PROTO +# define CONST const +# ifndef __GNUC__ +# include +# endif +#else +# define CONST +#endif + +#ifndef NULL /* Generic null pointer. May need */ +# define NULL 0 /* changing on some machines to */ +#endif /* (char *)0 or (void *)0 */ + +#ifndef SIGTYPE /* Signal handlers return this */ +# define SIGTYPE void +#endif + +#undef EOF /* Minix defines this in stdio.h */ +#define EOF -1 + +#ifndef MAXSIG +# define MAXSIG 27 /* Maximum signals known to Wish */ +#endif + +#define UNDEF -1 /* Used when setting cbreak etc. */ +#define EOS '\0' /* End of string terminator */ +#define BADFD -2 +#define MAXARG 300 /* Max # args passed to processes */ +#define MAXWORD 200 /* Only used in parse, try to lose */ +#define MAXFNAME 200 /* Only used in parse, try to lose */ +#define MAXPL 512 /* Path length */ +#define MAXLL 2048 /* Used in comlined & hist */ +#define MAXWL 512 /* Used in clex & meta */ +#define MAXCAN 1000 /* maximum number of candidates */ + + +/* The following used to be enums, + * but they get used an awful lot + * with ints. + */ +#undef TRUE +#undef FALSE +#define TRUE 1 +#define FALSE 0 +#if (!defined(FREEBSD_4) && !defined(FREEBSD_5)) +typedef int bool; /* Must be int as we pass bools as f'n args */ +#endif +typedef unsigned char uchar; /* CLE uses unsigned chars throughout */ + +/* We use the following when debugging malloc/free */ +#ifdef MALLOCDEBUG +# define free myFree +# define malloc myMalloc +#endif + +#define fatal(mess) { fprints(2,"%s\n",mess); exit(1); } + + +/* + * These structures are used to store the definitions for aliases, history, + * the tilde lists, variables. + */ + +struct val { + char *name; /* The name of the var/history/tilde/alias */ + char *val; /* The value */ + int hnum; /* History only: the history number */ + bool exported; /* Variable only: is this exported? */ + struct val *next; +}; + +struct vallist { + struct val *head; /* Singly-linked list with head/tail */ + struct val *tail; +}; + +/* Clex and Meta + * + * The following structure is used by both clex.c and meta.c. Meta + * uses it to hold candidates files that matched a ^D or expression. + * Here the mode holds the mode of each file as obtained by stat(). + * Clex uses the struct to build a linked list of words that will + * eventually be parsed by the parser. Here, the mode is a complex bitfield. + * For normal words,the mode is used as a bitfield to indicate: + * - if the name has been malloc'd (mode&TRUE) + * - if the name has an invisible space at the end (mode&C_SPACE) + * - if the name is in single quotes (mode&C_QUOTE) + * - if the name is in backquotes (mode&C_BACKQUOTE) + * - if the name is in curly brackets (mode&C_CURLY) + * - if the name starts with a $ sign (mode&C_DOLLAR) + * Often there will be nodes in the list with mode==0 & name==NULL, these + * indicate words removed during meta and should be skipped by the parser. + */ + +#define C_SPACE 002 /* Word has a space on the end */ +#define C_DOLLAR 004 /* Word starts with a $ sign */ +#define C_QUOTE 010 /* Word in single quotes */ +/* #define C_DBLQUOTE 020 * Word in double quotes */ +#define C_CURLY 020 /* Word in curly braces {} */ +#define C_BACKQUOTE 040 /* Word in backquotes */ +/* #define C_QUOTEBITS 060 * Dbl and back quote bits */ + +/* Some words have name==NULL & mode!=0; these are special words for the + * parser. They are separate, non-malloc'd, non-quoted words, and the bits + * used above can be reused. The bits must be in C_WORDMASK + */ +#define C_WORDMASK 01700 /* Bits must fall in here */ + +#define C_SEMI 0100 /* The word is a semicolon */ +#define C_DOUBLE C_SEMI /* Add this to `double' the symbol */ +#define C_PIPE 0200 /* The word is a pipe */ +#define C_DBLPIPE 0300 /* The word is a double pipe */ +#define C_AMP 0400 /* The word is an ampersand */ +#define C_DBLAMP 0500 /* The word is a double ampersand */ +#define C_LT 0600 /* The word is a less-than. */ +#define C_LTLT 0700 /* The word is two less-thans. */ +#define C_FD 03 /* File descriptor bits */ +#define C_GT 01000 /* The word is a greater-than. Bits */ + /* in the mask C_FD hold the fd (1-2) */ +#define C_GTGT 01100 /* The word is two greater-thans.Bits */ + /* in the mask C_FD hold the fd (0-9) */ +struct candidate + { char *name; /* The file's name */ + struct candidate *next; /* Next field in linked list */ + int mode; /* File's mode, or malloc'd bool */ + }; + +/* Execution + * + * The how parameter to execute() indicates how the process should be + * executed. + */ + +#define H_BCKGND 002 /* Process running in background */ +#define H_FROMFILE 004 /* Process has input from file */ +#define H_APPEND 010 /* Process is appending output */ + +/* Redirection + * + * The rdrct structure holds the new file descriptors for the process, + * or their file names (if any). Redirect() gets 3 of these, and + * for (i=0 to 2) { if name not null, open(name); else if fd not 0, + * dup2(fd,i); } + * The how field can have the H_ bits described above. + */ + +struct rdrct { + int fd; /* The input file descriptor */ + int how; /* How to open up the file */ + char *file; /* Input file's name */ + }; + +/* Command Line Editing + * + * Several old routines have been subsumed by one routine, Show(). + * Unfortunately, these are used in comlined, clex and hist, so the + * defines that map the old onto the new have to be out here. + */ + +#define insert(a,b,c) (void)Show(a,b,c,0) +#define show(a,c) (void)Show(a,0,0,2) +#define goend(a,b) Show(a,b,0,3) +#define yankprev(line,pos) prevword(line,&pos,2) +#define Beep write(1,wbeep,beeplength) + + +/* Builtins + * + * The following structure holds the builtins. This is only used by + * builtin.c and clex.c. Pity we can't leave it in builtin.c + */ + +struct builptr { + char *name; +#ifdef PROTO + int (*fptr)(int argc, char *argv[]); +#else + int (*fptr)(); +#endif + }; + +#include "proto.h" diff --git a/Applications/wish/hist.c b/Applications/wish/hist.c new file mode 100644 index 0000000000..56efd069f5 --- /dev/null +++ b/Applications/wish/hist.c @@ -0,0 +1,151 @@ +/* The command history is stored as a singly linked list, each node holding + * a command, its history number, and a pointer to the next node. + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" + +#ifndef NO_HISTORY +static struct vallist hlist = /* The history list */ + { NULL, NULL}; +int curr_hist = 1; /* The current history number */ +static int maxhist = 25; /* Maximum # of saved lines */ +static int curr_saved = 0; /* How many we have saved currently */ +static bool nohistdup = TRUE; /* Do we omit duplicate lines? */ + + +/* Savehist saves the given line into the history with the given history + * number, and ensuring that there are no more than max histories. + * It returns 1 if saved, or 0 if a duplicate or other errors. + */ +int +savehist(line, andadd) + char *line; + bool andadd; +{ + struct val *v; + char *mh; + + mh = EVget("history"); /* Set the current history */ + if (mh) maxhist = atoi(mh); + + /* Adjust the number saved if necessary */ + while (curr_saved > maxhist) { pullval(&hlist); curr_saved--; } + + /* If we don't care about duplicates, or there */ + /* isn't a duplicate, save the history */ + if (!nohistdup || ((v = searchval(&hlist, line, TRUE, FALSE)) == NULL)) + { + v = (struct val *) Malloc(sizeof(struct val), "savehist malloc"); + v->name = Malloc(strlen(line) + 1, "savehist malloc"); + strcpy(v->name, line); v->hnum = curr_hist++; appendval(&hlist, v); + if (++curr_saved > maxhist) { pullval(&hlist); curr_saved--; } + } + return (1); +} + +/* Loadhist finds the command line with the given histnum, and loads it + * at the given pos into the command line. + */ +void +loadhist(line, histnum) + char *line; + int histnum; +{ + struct val *ptr; + + /* Find either the last hist or the histnum one */ + for (ptr = hlist.head; ptr && ptr->hnum != histnum; ptr = ptr->next); + if (ptr) strcpy(line, ptr->name); /* copy into the command line */ + /* else we can't find that number so load */ + /* nothing. Maybe it should beep here */ +} + +/* The history builtin prints out the current history list. + * None of the arguments are used. + */ +int +history(argc, argv) + int argc; + char *argv[]; +{ + struct val *ptr; + + for (ptr = hlist.head; ptr && ptr->hnum < curr_hist; ptr = ptr->next) + if (ptr->hnum >= curr_hist - maxhist) + { prints("%d ", ptr->hnum); + mprint((uchar *) ptr->name, 0); /* this routine intercepts the ^ chars */ + } + return (0); +} + +/* Getnumhist returns the command line with the given history number. + * If none exists, NULL is returned. + */ +#ifdef PROTO +static char * getnumhist(int histnum) +#else +static char * +getnumhist(histnum) + int histnum; +#endif +{ + struct val *ptr; + + for (ptr = hlist.head; ptr; ptr = ptr->next) + if (ptr->hnum == histnum) return (ptr->name); + return (NULL); +} + +/* Gethist takes a event string, and returns a match from + * the history. The event can be one of the following: + * Value Return value + * an integer The command line with the given histnum + * "!" The last command line + * -integer The line integer histories ago + * word The last line that strncmps the word + * + * If no event is found, NULL is returned. + */ +char * +gethist(event) + char *event; +{ + struct val *ptr, *this; + int histnum, len; + + if ((*event >= '0' && *event <= '9') || (*event == '-') || (*event == '!')) + { + switch (*event) + { + case '-': + histnum = curr_hist - atoi(event + 1); + break; + case '!': + histnum = curr_hist - 1; + break; + default: + histnum = atoi(event); + } +#ifdef DEBUG + prints("Histnum is %d\n", histnum); +#endif + return (getnumhist(histnum)); + } + else + { + /* + If the history matches the requested event then that history line is + copied into the event pointer. Note that the first histnum characters of + event will not change and thus further searches for more recently + matching histories will still be valid. + */ + len= strlen(event); + for (ptr=NULL, this = hlist.head; this; this = this->next) + if (!strncmp(this->name, event, len)) ptr=this; + + if (ptr) return (ptr->name); else return(NULL); + } +} +#endif /* NO_HISTORY */ diff --git a/Applications/wish/job.c b/Applications/wish/job.c new file mode 100644 index 0000000000..b97007a5d2 --- /dev/null +++ b/Applications/wish/job.c @@ -0,0 +1,530 @@ +/* Job control. If UCBJOB is defined, we use BSD4.x job control. If POSIXJOB + * is defined, we use POSIX job control. IF V7JOB is defined, we provide + * job control using ptrace(). If none are defined, we simulate job control + * as best we can. + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#include "header.h" + +/* We define the wait return status macros for SysV. We also define + * WIFCORE, which evaluates as true if WIFSIGNALED is TRUE and + * a core has been dumped. This only works under SysV and BSD. + * Also defined are WRUNFG and WRUNBG, which are true is the + * process is running in the fore or background. + */ +#define RUNFG -1 /* Status if running in fg */ +#define RUNBG -2 /* Status if running in bg */ + +#define WAIT_T int +#define STATUS status +#ifdef UCBJOB +# undef WAIT_T +# undef STATUS +# define WAIT_T union wait +# define STATUS status.w_status +#endif + + +/* The job structure hold the information needed to manipulate the + * jobs, using job numbers instead of pids. Note that the linked + * list used by Wish is ordered by jobnumber. + */ +struct job +{ + int jobnumber; /* The job number */ + int pid; /* The pid of the job */ + char *name; /* Job's argv[0]; */ + char *dir; /* The job's working directory */ + WAIT_T status; /* Job's status */ + bool changed; /* Changed since last CLE? */ + struct job *next; /* Pointer to next job */ +}; + +static struct job *jtop = NULL, /* The head of the linked list */ + *currentjob = NULL; /* Pointer to current job structure */ + +int Exitstatus = 0; /* The exit status of the last child */ +static bool donepipe = TRUE; /* Addjob makes a new currentjob if this is */ + /* TRUE, then sets it FALSE. Joblist sets */ + /* it TRUE again. */ + +#ifdef PROTO +# define P(s) s +#else +# define P(s) () +#endif + +#if defined(UCBJOB) || defined(POSIXJOB) || defined(V7JOB) +static struct job *findjob P((int pid )); +static void rmjob P((struct job *ptr )); +#endif +#undef P + +#ifdef POSIXJOB +# include "posixjob.c" +#endif + +#ifdef UCBJOB +# include "ucbjob.c" +#endif + +#ifdef V7JOB +# include "v7job.c" +#endif + +#if defined(UCBJOB) || defined(POSIXJOB) || defined(V7JOB) +/* Given a process id, return a pointer to it's job structure */ +static struct job * +findjob(pid) + int pid; +{ + struct job *ptr; + + for (ptr = jtop; ptr; ptr = ptr->next) + if (ptr->pid == pid) + return (ptr); + return (NULL); +} +#endif + + +/* Given a job number, return it's process id */ +#ifdef PROTO +static int pidfromjob(int jobno) +#else +static int +pidfromjob(jobno) + int jobno; +#endif +{ + struct job *ptr; + + for (ptr = jtop; ptr; ptr = ptr->next) + if (ptr->jobnumber == jobno) + return (ptr->pid); + return (-1); +} + + +/* Add the pid and it's argv[0] to the job list. Return the allocated + * job number. + */ +int +addjob(pid, name, isbg) + int pid, isbg; + char *name; +{ + extern char currdir[]; + int jobno, last = 0; + struct job *ptr, *old, *new; + +#ifndef V7JOB + if (currentjob && donepipe == FALSE) /* We already have a current job */ + { +#ifdef DEBUG + fprints(2, "I already got one, you sons of a donkey\n"); +#endif + return (0); + } +#endif + /* Build the structure */ + ptr = old = (struct job *) malloc((unsigned) sizeof(struct job)); + if (ptr == NULL) + { + perror("addjob"); + return (0); + } + ptr->pid = pid; + ptr->name = (char *) malloc((unsigned) (strlen(name) + 1)); + if (ptr->name) + (void) strcpy(ptr->name, name); + else + { + perror("addjob"); + return (0); + } + /* We need some way of indicating the child */ + /* is still running. I am using RUNxx for now. */ + if (isbg) + ptr->STATUS = RUNBG; + else + ptr->STATUS = RUNFG; + + ptr->dir = (char *) malloc((unsigned) (strlen(currdir) + 1)); + if (ptr->dir) + (void) strcpy(ptr->dir, currdir); + else + { + perror("addjob"); + return (0); + } + ptr->changed = FALSE; + + /* Find the end of list */ + if (jtop == NULL) + { + jobno = ptr->jobnumber = 1; + jtop = ptr; ptr->next = NULL; + } + else + { /* Find a gap to put the node in */ + for (last = 0, old = new = jtop; + new != NULL && new->jobnumber - last == 1; + old = new, last = new->jobnumber, new = new->next); + last++; + if (new == NULL) + { + old->next = ptr; + ptr->next = NULL; + } + else + { + ptr->next = new; + old->next = ptr; + } + jobno = ptr->jobnumber = last; + } + + currentjob = ptr; /* a new current job */ + donepipe = FALSE; +#ifdef DEBUG + fprints(2, "added jobno %d pid %d\n", jobno, pid); +#endif + return (jobno); +} + +#if defined(UCBJOB) || defined(POSIXJOB) || defined(V7JOB) +/* Remove the job from the job list */ +static void +rmjob(ptr) + struct job *ptr; +{ + struct job *prev; + + if (ptr==NULL) return; +#ifdef DEBUG + fprints(2, "removing pid %d from list\n", ptr->pid); +#endif + if (ptr==jtop) jtop=jtop->next; + else + { + for (prev=jtop;prev && prev->next!=ptr; prev=prev->next) + ; + if (prev==NULL) return; + prev->next= ptr->next; + } + free(ptr->name); + if (ptr == currentjob) + { + currentjob = NULL; + Exitstatus = (WIFEXITED(ptr->status)) ? WEXITSTATUS(ptr->status) : 1; + } + free(ptr); +} +#endif + +/* Print out the list of current jobs and their status. + * Note although this is a builtin, it is called from + * main with an argc value of 0, to show new jobs only. + */ +int +joblist(argc, argv) + int argc; + char *argv[]; +{ +#ifndef FUZIX + struct job *ptr, *next; + bool exitzero; + + if (argc > 1) + { + prints("Usage: jobs\n"); + return (1); + } + +#if defined(UCBJOB) || defined(POSIXJOB) + waitfor(0); /* Collect any jobs doline() might have missed */ +#endif + + for (ptr = jtop; ptr; ptr = ptr->next) + { +/* Printing out conditions are tricky. We print if we're being called as + * a builtin (when argc!=0); otherwise only if the status has changed, + * and it wasn't the current job or exited with an exit status of 0. + * Also don't print if it's RUNFG or RUNBG. + */ + exitzero = (WIFEXITED(ptr->status) && (WEXITSTATUS(ptr->status) == 0)); + if (argc || (ptr->changed && !WRUNFG(ptr->status) && !WRUNBG(ptr->status) + && ptr != currentjob && !exitzero)) + { + if (currentjob && currentjob->pid == ptr->pid) + prints(" + [%d] %d", ptr->jobnumber, ptr->pid); + else + prints(" [%d] %d", ptr->jobnumber, ptr->pid); + prints(" %s ", ptr->name); + /* Still running */ + if (WRUNFG(ptr->status) || WRUNBG(ptr->status)) + { + prints("\n"); + continue; + } + if (WIFEXITED(ptr->status)) + { + prints(" Exited %d\n", WEXITSTATUS(ptr->status)); + continue; + } + if (WIFSTOPPED(ptr->status)) + { + prints(" Stopped %s\n", siglist[WSTOPSIG(ptr->status)]); + continue; + } + if (WIFSIGNALED(ptr->status)) + { + prints(" %s", siglist[WTERMSIG(ptr->status)]); + if (WIFCORE(ptr->status)) + prints(" (core dumped)\n"); + else + prints("\n"); + continue; + } + } + } + for (ptr = jtop; ptr; ptr = next) + { + next= ptr->next; + if (WRUNFG(ptr->status) || WRUNBG(ptr->status)|| WIFSTOPPED(ptr->status)) + ptr->changed = FALSE; + else + rmjob(ptr); + } + donepipe = TRUE; /* It must be, if doline() is calling us */ +#endif + return (0); +} + +int +Kill(argc, argv) + int argc; + char *argv[]; + +{ + int pid, sig = 0; + char *jobname, *sigwd; +#ifdef V7JOB + struct job *job; +#endif + + switch (argc) + { + case 2: + sig = 9; + jobname = argv[1]; + goto killit; /* Yuk, a goto */ + case 3: + jobname = argv[2]; + sigwd = argv[1]; + if (*sigwd == '-') + sigwd++; + sig = atoi(sigwd); +#ifdef FUZIX + goto killit; /* Yuk, a goto */ +#else + if (sig == 0) + for (i = 1; signame[i]; i++) + if (!strcmp(sigwd, signame[i])) + { + sig = i; + break; + } +#endif + killit: + if (sig == 0) + { + fprints(2,"Bad signal argument\n"); + return (1); + } + if (*jobname == '%') + pid = pidfromjob(atoi(++jobname)); + else + pid = atoi(jobname); + if (pid < 1) + { + fprints(2, "No such job number: %s\n", jobname); + return (1); + } +#ifdef V7JOB +/* A stopped job can't be killed until the parent (i.e us) restarts it. + * So if the job is stopped, we restart it so we can kill it :-) + */ + job= findjob(pid); + if (job && WIFSTOPPED(job->status)) + { +# ifdef DEBUG + fprints(2,"About to restart ptrace the job\n"); +# endif + ptrace(7,pid,(PLONG)1,(PLONG)0); /* Start it up again */ + } +#endif + kill(pid, sig); + return (0); + default: +#ifdef FUZIX + prints("Usage: kill [-]number job|pid\n"); +#else + prints("Usage: kill [-]signame|number job|pid\n"); + prints(" Signal names are:\n"); + for (i = 1; signame[i]; i++) + { + if (i % 6 == 1) + prints(" "); + prints("%s ", signame[i]); + if (i % 6 == 0) + prints("\n"); + } + prints("\n"); +#endif + return (1); + } +} + +#ifdef FUZIX +void +waitfor(pid) + int pid; +{ + int status; + int wpid; + do { + wpid = wait(&status); + } while (wpid != pid); +} +#endif + + +#if defined(UCBJOB) || defined(POSIXJOB) || defined(V7JOB) +/* Builtins */ +int +bg(argc, argv) + int argc; + char *argv[]; + +{ + int pid=0; + char *c=NULL; + + if (argc > 2) + { + prints("usage: bg [pid]\n"); + return (1); + } + if (argc == 1) + { +#ifdef DEBUG + fprints(2,"Trying to use current job pointer\n"); +#endif + if (currentjob) + { + pid = currentjob->pid; + } + } + else + { + c = argv[1]; + if (*c == '%') + pid = pidfromjob(atoi(++c)); + else + pid = atoi(c); + } + if (pid < 1) + { + fprints(2, "No such job number: %s\n", c); + return (1); + } + bgstuff(pid); + + return (0); +} + + +/* Fg is a special builtin. Instead of returning an exitstatus, it returns + * the pid of the fg'd process. Builtin() knows about this. + */ +int +fg(argc, argv) + int argc; + char *argv[]; + +{ + extern char currdir[]; + extern struct vallist vlist; + int pid; + struct job *ptr; + char *c; + + if (argc > 2) + { + prints("usage: fg [pid]\n"); + return (0); + } + if (argc == 1) + { +#ifdef DEBUG + fprints(2,"Trying to use current job pointer\n"); +#endif + if (currentjob) + { + pid = currentjob->pid; + } + } + else + { + c = argv[1]; + if (*c == '%') + pid = pidfromjob(atoi(++c)); + else + pid = atoi(c); + } + if (pid < 1) + { + fprints(2, "No such job number: %s\n", c); + return (0); + } + + if (fgstuff(pid)) return(0); + + ptr = findjob(pid); + if (ptr) + { + if (strcmp(ptr->dir, currdir)) /* If directory has changed */ + { + fprints(2, "[%d] %d %s (wd now: %s)\n", ptr->jobnumber, ptr->pid, + ptr->name, ptr->dir); + + chdir(ptr->dir); /* Reset our current directory */ + strcpy(currdir,ptr->dir); + setval("cwd", currdir, &vlist); + } + else + fprints(2, "[%d] %d %s\n", ptr->jobnumber, ptr->pid, ptr->name); + } + else + { + fprints(2, "Couldn't find job ptr in fg\n"); + return (0); + } + /* and finally wake them up */ + +#ifndef V7JOB +# ifdef DEBUG + fprints(2,"About to SIGCONT %d\n", pid); +# endif + kill(pid, SIGCONT); +#endif + + ptr->STATUS = RUNFG; + currentjob = ptr; + return (pid); +} +#endif /* defined(UCBJOB) || defined(POSIXJOB) || defined(V7JOB) */ diff --git a/Applications/wish/m_fuzix.h b/Applications/wish/m_fuzix.h new file mode 100644 index 0000000000..17edaa1e6c --- /dev/null +++ b/Applications/wish/m_fuzix.h @@ -0,0 +1,26 @@ +/* FUZIX specific includes and defines + * + */ +#define FUZIX0_4 /* FUZIX 0.4 */ +#define FUZIX + +#define USES_TERMIOS +#define PROTO +#define STDARG + +#include +#include +#include /* Only for perror */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/Applications/wish/machine.h b/Applications/wish/machine.h new file mode 100644 index 0000000000..17edaa1e6c --- /dev/null +++ b/Applications/wish/machine.h @@ -0,0 +1,26 @@ +/* FUZIX specific includes and defines + * + */ +#define FUZIX0_4 /* FUZIX 0.4 */ +#define FUZIX + +#define USES_TERMIOS +#define PROTO +#define STDARG + +#include +#include +#include /* Only for perror */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/Applications/wish/main.c b/Applications/wish/main.c new file mode 100644 index 0000000000..f461c4da91 --- /dev/null +++ b/Applications/wish/main.c @@ -0,0 +1,333 @@ +/* The main loop of the shell + */ + +#include "header.h" +char x,y,z; + +int Argc; /* The number of arguments for Wish */ +char **Argv; /* The argument list for Wish */ +static char *prompt; /* A pointer to the prompt string */ +int lenprompt; /* and the length of the prompt */ +int saveh; /* Should we save history? */ +static bool Loginshell=FALSE; /* Indicates if we're a login shell */ +static struct candidate *next_word; + +#ifdef PROTO +bool(*getaline) (uchar *line,int *nosave) = getuline; +#else +bool(*getaline) () = getuline; /* Our input routine defaults to getuline() */ +#endif + + +/* Lengthint is used by prprompt to determine the number + * of chars in the string version of an integer; this is + * needed so the prompt length is calculated properly. + */ +#ifdef PROTO +static int lengthint(int num) +#else +static int lengthint(num) + int num; +#endif +{ + int i; + + for (i = 0; num; num = num / 10, i++); + return (i); +} + +/* Print out the current time in a set length string */ +#ifdef PROTO +static void printime(void) +#else +static void +printime() +#endif +{ + long clock; + struct tm *t; + + time(&clock); + t = localtime(&clock); + prints("%2d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec); +} + +/* Prprompt prints out the prompt. It parses all the special options + * that the prompt can take, and determines the effective length of + * the prompt as it appears on the screen. If there is no $prompt, + * it defaults to '% '. + */ +void +prprompt() +{ + extern int curr_hist; + extern char *so, *se; + char c; + int i, len; + + lenprompt = 0; + prompt = EVget("prompt"); + if (prompt == NULL) + prompt = "% "; + for (i = 0; prompt[i]; i++) + if (prompt[i] == '%') + switch (prompt[++i]) + { + case EOS: + case '%': + write(1, "%", 1); + lenprompt++; + break; + case '!': +#ifndef NO_HISTORY + case 'h': + lenprompt += lengthint(curr_hist); + prints("%d", curr_hist); + break; +#endif + case 'd': + len = strlen(EVget("cwd")); + lenprompt += len; + write(1, EVget("cwd"), len); + break; + case 'S': + prints("%s", so); + break; + case 's': + prints("%s", se); /* temp stop standout */ + break; + case '@': + case 't': + lenprompt += 8; + printime(); + break; + default: + write(1, "%", 1); + lenprompt += 2; + if (prompt[i] < 32 || prompt[i] == 127) + { + write(1, "^", 1); + c = prompt[i] + 64; + write(1, &c, 1); + lenprompt++; + } + else + write(1, &prompt[i], 1); + } + else + { + if (prompt[i] < 32 || prompt[i] == 127) + { + write(1, "^", 1); + c = prompt[i] + 64; + write(1, &c, 1); + lenprompt++; + } + else + write(1, &prompt[i], 1); + lenprompt++; + } +} + +/* Set the terminal back to normal + * before leaving the shell + */ +void +leave_shell(how) + int how; +{ + char *argv[2]; + + setcooked(); + if (Loginshell) /* If we're a login shell */ + { + argv[0] = "source"; + argv[1] = ".klogout"; /* Source .klogout */ + source(2, argv); + } + write(1, "\n", 1); + exit(how); +} + +/* Setup does most of the initialisation of the shell. Firstly the default + * variables & the environ variables are set up. Then the termcap strings + * are found, and then any other misc. things are done. + */ +#ifdef PROTO +static void setup(void) +#else +static void +setup() +#endif +{ + extern char currdir[]; + extern struct vallist vlist; + char *argv[2]; + char *home; + + /* Initialise the environment */ + if (!EVinit()) + fatal("Can't initialise environment"); +#ifdef USES_GETWD + if (getwd(currdir)) /* Get the current directory */ +#else + if (getcwd(currdir, MAXPL)) +#endif + setval("cwd", currdir, &vlist); + else + write(2, "Can't get cwd properly\n", 23); + setval("prompt", "% ", &vlist); /* Set the prompt to % for now */ + setval("Version", "2.0.41", &vlist); + catchsig(); /* Catch signals */ + getstty(); /* Set up the stty for Wish */ + terminal(); /* Get the termcap strings */ + +#ifndef NO_BIND + initbind(); /* Set the default key bindings */ +#endif + + argv[0] = "source"; + home= EVget("HOME"); + if (home) + { argv[1]= Malloc(strlen(home) + strlen(".wishrc")+3, "setup malloc"); + sprints(argv[1],"%s/.wishrc", home); + } + else argv[1] = ".wishrc"; /* Source .wishrc */ + source(2, argv); + free(argv[1]); + if (*Argv[0]=='-') /* If we're a login shell */ + { + Loginshell= TRUE; /* Make sure we know */ + if (home) + { argv[1]= Malloc(strlen(home) + strlen(".wishlogin")+3, "setup malloc"); + sprints(argv[1],"%s/.wishlogin", home); + } + else argv[1] = ".wishlogin"; /* Source .wishlogin */ + source(2, argv); + free(argv[1]); + } + saveh = TRUE; +} + + +/* Doline is the loop which gets a line from the user, runs it through + * the metachar expansion, and finally calls command() to execute it. + * It returns when there are no more lines available - so it really + * should be called dolines()! + * + * If isalias is FALSE, we are not expanding aliases. Otherwise we are, + * so don't clear the carray, our args are in there. Also, how will + * then hold H_BCKGND if we are a subshell. + */ + +void +doline(isalias) + int isalias; +{ + extern struct candidate carray[], *wordlist; /* Wordlist is list or words */ + extern int Exitstatus, ncand; + int i; + +/* Nextword holds the word for the next pipeline, + * termword holds the token that terminated the pipeline. + */ + struct candidate *termword; + char *linebuf; + int pid, q, oldncand; + int term; + +#ifdef DEBUG + i = (int) sbrk(0); + prints("In doline, Brk value is %x\n", i); + prints("Getaline f'n ptr is %x\n", getaline); +#endif + linebuf = (char *) malloc(MAXLL); + if (linebuf == NULL) + fatal("Couldn't malloc linebuf\n"); + + while ((*getaline) (linebuf, &q) == TRUE) /* Get a line from user */ + { + if (q || *linebuf == EOS) + continue; + +#ifdef DEBUG + fprints(2, "In doline, line is %s\n", linebuf); +#endif + if (isalias == FALSE) + ncand = 0; /* Initialise the carray */ + next_word = &carray[ncand]; /* list for meta.c */ + oldncand = ncand; + + if (meta_1(linebuf, FALSE) == FALSE) + continue; /* Expand ! */ +#ifndef NO_HISTORY + if (saveh) + savehist(expline(carray), 1); /* Save the line */ +#endif + +/* At this point, we need to extract pipelines + * from the parsed line, so that we expand + * metachars properly. This means we need yet + * another loop. Oh well. + */ + for (wordlist = next_word; wordlist != NULL; wordlist = next_word) + { + /* Find pipeline end */ + while (next_word && (i = next_word->mode & C_WORDMASK) != C_SEMI && i != C_DBLAMP + && i != C_DBLPIPE) + next_word = next_word->next; + /* If we found one */ + if (next_word) + { + termword = next_word; /* Save it */ + next_word = next_word->next; /* Nextword is next pipeline */ + termword->next = NULL; /* Terminate our pipeline */ + } + meta_2(); /* Expand metacharacters */ + term = command(&pid, FALSE, NULL, FALSE); /* Actually run it here */ + + if (isalias & H_BCKGND) pid= -pid; /* Wait ALWAYS if subshell */ + if (term != C_AMP && pid) + waitfor(pid); /* Wait for it to finish */ + if (term == C_DBLAMP && Exitstatus != 0) + break; + if (term == C_DBLPIPE && Exitstatus == 0) + break; +#ifndef NO_JOB + if (saveh) + joblist(0, NULL); /* print the list of jobs */ +#endif + + } + + for (i = oldncand; i < ncand; i++) /* Free the command words */ + if (carray[i].mode == TRUE) + { + carray[i].mode = FALSE; + free(carray[i].name); + } + ncand = oldncand; + + } + free(linebuf); +} + +/* Main. This is the bit that starts the whole ball rolling. + */ +int +main(argc, argv) + int argc; + char *argv[]; + +{ + + Argc = argc; /* Set up arg variables */ + Argv = argv; +#ifdef MALLOCDEBUG + initmall(); /* Initialise the malloc arrays */ +#endif + setup(); /* Set up the vars & the termcap stuff */ + + while (1) + doline(FALSE); /* We only exit when we shutdown() */ +} diff --git a/Applications/wish/malloc.c b/Applications/wish/malloc.c new file mode 100644 index 0000000000..e42969b1be --- /dev/null +++ b/Applications/wish/malloc.c @@ -0,0 +1,79 @@ +/* This is a version of malloc which will always + * return a valid pointer, or exit(1) with a + * message. + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#include "header.h" + +#ifdef MALLOCDEBUG +static void *StartSbrk; +static void *mallptr[1024]; +static int mallwho[1024]; +static int mallsize[1024]; +#endif + +char *Malloc(size, mesg) + unsigned int size; + char *mesg; + { + char *a; + + if ((a=(char *)malloc(size))==NULL) + { fprints(2,"Malloc error: %s\n",mesg); exit(1); } + return(a); + } + + +/* The following routines are non-portable, and are used solely for + * debugging malloc/free calls. + */ + +#ifdef MALLOCDEBUG +# undef free +# undef malloc + +void initmall() + { + int i; + + for (i=0;i<1024;i++) mallptr[i]=NULL; + StartSbrk= sbrk(0); + } + +void *myMalloc(size) + unsigned int size; + { + void *a; + int i, *b; + + b= (int *)&size; b--; + a= (void *)malloc(size); + if (a) + for (i=0;i<1024;i++) + if (mallptr[i]==NULL) + { mallptr[i]=a; mallwho[i]= *b; mallsize[i]=size; break; } + fprints(2,"0x%x malloc'd 0x%x, size %d\n",*b,a,size); + return(a); + } + +void myFree(ptr) + void *ptr; + { + int *b; + int i; + + b= (int *)&ptr; b--; + fprints(2,"0x%x ",*b); + if (ptr<= StartSbrk) + { fprints(2,"Trying to free a bad pointer, 0x%x\n",ptr); exit(1); } + else + { + for (i=0;i<1024;i++) if (mallptr[i]==ptr) { mallptr[i]=NULL; break; } + if (i==1024) { fprints(2,"Unmalloc'd ptr 0x%x\n",ptr); exit(1); } + fprints(2,"%d Freeing 0x%x\n",i,ptr); + free(ptr); + } + } +#endif /* MALLOCDEBUG */ diff --git a/Applications/wish/meta.c b/Applications/wish/meta.c new file mode 100644 index 0000000000..dcd1fc3e88 --- /dev/null +++ b/Applications/wish/meta.c @@ -0,0 +1,1073 @@ +/* Metacharacter expansion. This file holds the routines that do the first + * parsing of the command line, and the expansion of the metacharacters + * ' " ` \ $ ~ * ? and [ + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" +#define MAXMATCH 512 +#define OK 0 +#define NX_ERR -1 +#define BR_ERR -2 + + +extern struct candidate carray[]; /* The matched files */ +extern struct candidate *next_word; +int ncand; +struct candidate *wordlist; /* The list of words for the parser */ +static int Mode; /* Used by star */ +static char *qline; /* ``'d line from backquot */ + +#ifdef PROTO +static int matchdir(char *directory, char *pattern); +#else +static int matchdir(); +#endif +#ifdef DEBUG +static void listcarray(); +#endif + +/* Match takes a string, and a pattern, which can contain *, ? , \ and [], + * and returns 0 if the strings matched the pattern, otherwise a negative + * number. If the pattern ends in a '/', matchdir is called with the + * arg accumdir/string to find further matches. Match also adds each match + * into the matches array given above. + */ +#ifdef PROTO +static int match(char *string, char *pattern, char *accumdir) +#else +static int +match(string, pattern, accumdir) + char *string, *pattern, *accumdir; +#endif +{ + char c, *findex, *pindex, *fmatch, *pmatch, rempat[MAXWL], where[MAXPL]; + int i, mismatch, found, star; + + findex = string; /* Initialise our vars */ + pindex = pattern; + if ((*findex == '.') && (*pindex != '.')) /* Don't match . unless asked */ + return (NX_ERR); + mismatch = star = 0; + while (*findex && !mismatch) /* While no mismatch */ + { + switch (*pindex) /* Match the pattern's char */ + { + case '*': + pindex++; /* Skip the '*' */ + pmatch = pindex; /* We must match from here on */ + fmatch = findex; + star = 1; /* Found a star */ + break; + case '?': + pindex++; /* just increment, letter automatically + matches. */ + findex++; + break; + case '[': + found = 0; /* For all chars in [] */ + for (; (*pindex) && (*pindex != ']'); pindex++) + { + switch (*pindex) + { + case '\\': + if (*(pindex + 1) == *findex) /* Try match on next char */ + found = 1; + break; + case '-': + if ((*(pindex + 1) == ']') || (*(pindex - 1) == '[')) + if (*pindex == *findex) + { + found = 1; /* Try a match on '-' */ + break; + } /* or try all chars in the range */ + for (c = *(pindex - 1) + 1; c <= *(pindex + 1) && !found && c != ']'; c++) + if (c == *findex) + found = 1; + break; + default: /* Default: just match that char */ + if (*pindex == *findex) + found = 1; + break; + } + } +/* + * We could exit the loop as soon as found is set to 1 but we have to + * keep going to the end of the list so we can recommence matching + * after the ']'. This also allows us to test for the unmatched + * bracket. + */ + if (*pindex == EOS) + { + fprints(2, "Unmatched bracket '['\n"); + return (BR_ERR); + } + if (found) + { + pindex++; /* move past the ']' */ + findex++; /* and on to the next letter */ + break; + } + if (!star) + mismatch = 1; /* failure for this letter of file */ + else + { + pindex = pmatch; + findex = ++fmatch; + } + break; + case '\\': + pindex++; /* go on to next symbol */ + + default: + if (*findex != *pindex) /* No match and not a star */ + if (!star) /* gives an error */ + mismatch = 1; + else + { + pindex = pmatch; /* Move back to the char we must */ + findex = ++fmatch; /* match, & move along string */ + } + else + { + pindex++; /* Yes, a match, move up */ + findex++; + } + break; + } + } + if (mismatch) + return (NX_ERR); /* it didn't match, go on to next one */ + for (; *pindex == '*'; pindex++); /* get rid of trailing stars in pattern */ + if (*findex == *pindex) + { + /* add it to the candidate list */ + where[0] = EOS; + if (*accumdir) + { + strcpy(where, accumdir); + strcat(where, "/"); + } + strcat(where, string); + if (ncand == MAXCAN) + return (OK); + carray[ncand].name = (char *) malloc((unsigned) (strlen(where) + 4)); + if (carray[ncand].name == NULL) + return (OK); + strcpy(carray[ncand].name, where); + carray[ncand++].mode = Mode | TRUE | C_SPACE; /* Was malloc'd */ + return (OK); + } + else if (*pindex == '/') /* test if file is a directory */ + { + for (i = 0; *pindex; i++, pindex++) + rempat[i] = *(pindex + 1);/* even copies null across */ + strcpy(where, accumdir); + if (where[0] != EOS) + { + strcat(where, "/"); + strcat(where, string); + } + else + strcpy(where, string); + return (matchdir(where, rempat)); /* recursively call this pair again */ + } + else /* it just doesn't match */ + return (NX_ERR); +} + +/* Matchdir takes a directory name and a pattern, and returns 0 if any + * files in the directory match the pattern. If none, it returns a negative + * number. Note that this may be recursive, as it calls match(), which calls + * matchdir(). + */ +#ifdef PROTO +static int matchdir(char *directory, char *pattern) +#else +static int +matchdir(directory, pattern) + char *directory, *pattern; +#endif +{ + DIR *dirp; + +#ifdef USES_DIRECT + struct direct *entry; +#else + struct dirent *entry; +#endif + int foundany = 0; + + if (*directory != EOS) + { + if ((dirp = opendir(directory)) == NULL) + return (NX_ERR); + } + else + { + if ((dirp = opendir(".")) == NULL) + return (NX_ERR); + } + while ((entry = readdir(dirp)) != NULL) + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..") && + !match(entry->d_name, pattern, directory)) + foundany++; + closedir(dirp); + if (foundany) + return (OK); + else + return (NX_ERR); +} + +#ifdef PROTO +static void finddir(char *word, char *dir) +#else +static +void +finddir(word, dir) + char *word, *dir; +#endif +{ + char c; + int i = 0, j, l = 1; + +/* This function finds the directory to start the matching from. */ + while (l) + switch (c = word[i]) + { + case '*': + case '?': + case '[': + for (; dir[i] != '/' && i; i--); /* go back to end of previous + component */ + if (i) + dir[i++] = EOS; + else if (dir[0] == '/') + dir[++i] = EOS; + else + dir[0] = EOS; + l = 0; + break; + default: + dir[i++] = c; + } + for (l = i, j = 0; word[l]; l++, j++) + word[j] = word[l]; + word[j] = EOS; +} + + +#ifndef NO_TILDE +/* Tilde takes the word beginning with a ~, and returns the word with the + * first part replaced by the directory name. If ~/, we use $HOME + * e.g ~fred -> /u1/staff/fred + * ~jim/Dir/a*.c -> /usr/tmp/jim/Dir/a*.c + * ~/Makefile -> /u1/staff/warren/Makefile + * If it cannot expand, it returns dir as a zero-terminated string + * + * It also uses the tilde list now as well. + */ +void +tilde(word, dir) + char *word, *dir; +{ + extern struct vallist tlist; + + struct passwd *entry; + struct val *t; + char *a; + + word++; + *dir = EOS; + if (*word == '/') /* use $HOME if ~/ */ + { + a = word; + entry = getpwuid(getuid()); + strcpy(dir, entry->pw_dir); + } + else + { + a = strchr(word, '/'); /* Get the word to parse */ + if (a != NULL) + *a = EOS; + for (t = tlist.head; t; t = t->next) /* Try the tilde list next */ + { + if (!strcmp(word, t->name)) + { + strcpy(dir, t->val); + break; + } + } + if (*dir == EOS) /* Finally, consult the passwd file */ + { + entry = getpwnam(word); /* Get the directory's name */ + if (entry) + strcpy(dir, entry->pw_dir); + } + } + endpwent(); + if (*dir == EOS) + return; + /* Form the real partial name */ + if (a != NULL) + { + *a = '/'; + strcat(dir, a); + } +} +#endif + +/* Dollar expands a variable. It takes a pointer to a candidiate, + * and replaces it with the variable's value. + */ +#ifdef PROTO +static void dollar(struct candidate *cand) +#else +static void +dollar(cand) + struct candidate *cand; +#endif +{ + extern int Exitstatus, Argc; + extern char **Argv; + struct candidate *next; + char *end, *value; + char c = EOS; + char *a, *doll; + unsigned int length, base, mall = 0; /* Mall is 1 if we've malloc'd */ + + + doll = cand->name; + /* If a special variable */ + if (strlen(doll) == 1 || doll[1] == '=') + { + switch (*doll) + { + case '$': /* Replace with pid */ + value = (char *) malloc(10); + length = getpid(); + if (value) + { + sprints(value, "%d", length); + mall = 1; + } + break; + case '#': /* Replace with # of args */ + value = (char *) malloc(10); + if (value) + { + sprints(value, "%d", Argc - 1); + mall = 1; + } + break; + case '?': /* Replace with exist status */ + value = (char *) malloc(10); + if (value) + { + sprints(value, "%d", Exitstatus); + mall = 1; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': /* Replace with Argv[n] */ + case '5': + case '6': + case '7': + case '8': + case '9': + length = *doll - '0'; + value = (length < Argc ? Argv[length] : ""); + break; + case '*': /* Replace with all args */ + if (Argc > 1) + { + base = ncand; + ncand += (Argc - 1); + carray[ncand - 1].next = cand->next; + carray[ncand - 1].name = Argv[Argc - 1]; + carray[ncand - 1].mode = C_SPACE; + cand->next = &carray[base]; + for (length = 1; base < ncand - 1; base++, length++) + { + carray[base].name = Argv[length]; + carray[base].mode = C_SPACE; + carray[base].next = &carray[base + 1]; + } + } + if (cand->mode & TRUE) + { + free(cand->name); + cand->mode = FALSE; + } + cand->name = NULL; + return; + default: + fprints(2, "Unknown variable $%c\n", *doll); + return; + } + if (doll[1] == '=') /* An assignment */ + { + end = doll + 1; + c = '='; + } + } + else + { /* A normal variable */ + /* Find the last char of the var */ + for (end = doll; end != EOS && isalnum(*end); end++); + c = *end; + *end = EOS; /* Terminate the name */ + + value = EVget(doll); /* Get the value */ + if (!value) + { + fprints(2, "Unknown variable $%s\n", doll); + return; + } + else if (c == '[') /* or a field of it */ + { + length = atoi(++end); /* Get the field number */ + /* Skip the number */ + while (*end != ']' && *end != EOS) + end++; + if (*end == ']') + end++; + c = *end; + *end = EOS; + for (a = value; length; length--) + { /* Find a gap */ + if (!(a = strpbrk(a, " \t"))) + { + value = NULL; + break; + } + while (*a == ' ' || *a == '\t') + a++; /* Skip the gap */ + if (*a == EOS) + { + value = NULL; + break; + } + } + if (value) /* If a valid field */ + { + length = strcspn(a, " \t"); /* Get the length of the field */ + value = (char *) malloc(length + 2); + if (value) + { + strncpy(value, a, length); /* Make a copy - can't change var */ + value[length] = EOS; + mall = TRUE; + } + } + } + } + + cand->mode &= ~C_DOLLAR; + if (c) /* If there's more to the value */ + { + *end = c; /* Insert the rest in the list */ + next = cand->next; + addcarray(end, cand, cand->mode | TRUE, TRUE); + carray[ncand - 1].next = next; + cand->mode &= ~C_SPACE; + } + if (cand->mode & TRUE) + free(cand->name); /* we can replace totally */ + cand->name = value; + if (mall) + cand->mode |= TRUE; + else + cand->mode &= ~TRUE; +#ifdef DEBUG + fprints(2, " In dollar "); + listcarray(); +#endif + return; +} + + +/* Expline takes a linked list of words, and converts them into a normal + * string. This is used to save history. No double quotes now. + */ +char * +expline(list) + struct candidate *list; +{ + struct candidate *q; + char *out, *a; + int i, mode = 0; + bool lastspace = FALSE; + + /* Find the amount to malloc */ + for (i = 0, q = list; q != NULL; q = q->next) + if (q->name) + i += strlen(q->name) + 6; /* Just in case */ + + out = (char *) malloc((unsigned) i + 4); + if (out == NULL) + return (NULL); + + for (a = out, q = list; q; q = q->next) + { + mode = q->mode; + if (q->name) /* It's a word */ + { + if (lastspace) + *(a++) = ' '; + if (mode & C_QUOTE) + *(a++) = '\''; + if (mode & C_CURLY) + *(a++) = '{'; + if (mode & C_BACKQUOTE) + *(a++) = '`'; + if (mode & C_DOLLAR) + *(a++) = '$'; /* Add $ to vars */ + strcpy(a, q->name); + a += strlen(q->name); /* Add the word!! */ + if (mode & C_QUOTE) + *(a++) = '\''; + if (mode & C_CURLY) + *(a++) = '}'; + if (mode & C_BACKQUOTE) + *(a++) = '`'; + lastspace = mode & C_SPACE; + } + else + { + if (lastspace) + *(a++) = ' '; + switch (mode & C_WORDMASK) + { + case C_SEMI: + strcpy(a, "; "); + a += 2; + break; + case C_PIPE: + strcpy(a, "| "); + a += 2; + break; + case C_DBLPIPE: + strcpy(a, "|| "); + a += 3; + break; + case C_AMP: + strcpy(a, "& "); + a += 2; + break; + case C_DBLAMP: + strcpy(a, "&& "); + a += 3; + break; + case C_LT: + strcpy(a, "< "); + a += 2; + break; + case C_LTLT: + strcpy(a, "<< "); + a += 3; + break; + case C_GT: + if ((mode & C_FD) == 2) + { + strcpy(a, ">& "); + a += 3; + } + else + { + strcpy(a, "> "); + a += 2; + } + break; + case C_GTGT: + strcpy(a, ">> "); + a += 3; + break; + } + lastspace = FALSE; + } + } + return (out); +} + +#ifdef DEBUG +/* Wordlist is used for debugging to print out the carray linked list */ +static void +listcarray() +{ + struct candidate *curr; + + fprints(2, "Here's the wordlist:\n"); + for (curr = wordlist; curr != NULL; curr = curr->next) + { + fprints(2, "--> %x ", curr); + if (curr->name) + fprints(2, "%s", curr->name); + if (curr->mode & C_WORDMASK) + switch (curr->mode & C_WORDMASK) + { + case C_SEMI: + fprints(2, "; C_SEMI"); + break; + case C_PIPE: + fprints(2, "| C_PIPE"); + break; + case C_DBLPIPE: + fprints(2, "|| C_DBLPIPE"); + break; + case C_AMP: + fprints(2, "& C_AMP"); + break; + case C_DBLAMP: + fprints(2, "&& C_DBLAMP"); + break; + case C_LT: + fprints(2, "< C_LT"); + break; + case C_LTLT: + fprints(2, "<< C_LTLT"); + break; + case C_GT: + fprints(2, "%d> C_GT", curr->mode & C_FD); + break; + case C_GTGT: + fprints(2, "%d>> C_GTGT", curr->mode & C_FD); + break; + default: + fprints(2, "%o C_UNKNOWN", curr->mode); + } + else + { + if (curr->mode & C_SPACE) + fprints(2, " C_SPACE"); + if (curr->mode & C_DOLLAR) + fprints(2, " C_DOLLAR"); + if (curr->mode & C_QUOTE) + fprints(2, " C_QUOTE"); + if (curr->mode & C_CURLY) + fprints(2, " C_DBLQUOTE"); + if (curr->mode & C_BACKQUOTE) + fprints(2, " C_BACKQUOTE"); + if (curr->mode & TRUE) + fprints(2, " (malloc'd)"); + } + fprints(2, " %x\n", curr->next); + } +} + +#endif + +/* Addword is used by Meta_1 to add a word into the carray list. + * This is where history is expanded. + */ +#ifdef PROTO +static void addword(char *string, int mode) +#else +static void +addword(string, mode) + char *string; + int mode; +#endif +{ + char *b; + + if (string != NULL) /* It's a word, check it out */ + { + if (*string == EOS) + return; + +#ifndef NO_HISTORY + /* Find any history and retrieve it */ + if ((mode & (C_QUOTE | C_BACKQUOTE | C_DOLLAR | C_CURLY)) == 0) + if ( *string == '!' && (b = gethist(++string)) != NULL) + { + meta_1(b, FALSE); /* Expand it too */ + return; + } +#endif + /* mode &= ~FALSE; I have NO idea what this does! */ + } + addcarray(string, &carray[ncand - 1], mode, mode & TRUE); +} + + +/* Star expands * ? and [] in the carray */ +#ifdef PROTO +static void star(struct candidate *curr) +#else +static void +star(curr) + struct candidate *curr; +#endif +{ + char dir[MAXWL]; + struct candidate *a; + int base; + + Mode = curr->mode & ~(TRUE | C_SPACE); + base = ncand; /* Save start of expansion */ + dir[0] = EOS; + finddir(curr->name, dir); /* expand them */ + a = curr->next; + if (!matchdir(dir, curr->name)) + { + qsort((void *) &carray[base], ncand - base, sizeof(struct candidate), compare); + /* Now insert into list */ + carray[ncand - 1].next = a; + curr->next = &carray[base]; + for (; base < ncand - 1; base++) + carray[base].next = &carray[base + 1]; + } + if (curr->mode & TRUE) + { + free(curr->name); + curr->mode = FALSE; + } + curr->name = NULL; +#ifdef DEBUG + fprints(2, " In star "); + listcarray(); +#endif +} + +/* Getbackqline is called when backquot forks to run a `command`. + */ +#ifdef PROTO +static bool getbackqline( uchar *line, int *nosave) +#else +static bool +getbackqline(line, nosave) + uchar *line; + int *nosave; +#endif +{ + *nosave = 0; + if (qline) + { + strcpy(line, qline); + qline = NULL; + return (TRUE); + } + else + return (FALSE); +} + +/* Backquot expands backquotes */ +#ifdef PROTO +static void backquot(struct candidate *curr) +#else +static void +backquot(curr) + struct candidate *curr; +#endif +{ +#ifdef PROTO + extern bool(*getaline) (uchar *line , int *nosave ); +#else + extern bool(*getaline) (); +#endif + extern int saveh; + struct candidate *first; + int term, base; + int pfd[2]; + char *line; + + term = pipe(pfd); + if (term == -1) + { + fprints(2, "Cannot open pipe in backquot\n"); + return; + } + switch (fork()) + { + case -1: + fatal("Bad fork in meta_4\n"); + + case 0: + /* Redirect our output */ + close(pfd[0]); + close(1); + dup(pfd[1]); + close(pfd[1]); + qline = curr->name; + getaline = getbackqline; /* Pass line to doline */ + saveh = FALSE; + doline(FALSE); + exit(0); /* and die */ + default: + close(pfd[1]); + base = ncand; /* Get start of expansion */ + first = curr; + curr = curr->next; /* Move curr up now */ + line = (char *) malloc(MAXLL); + if (line == NULL) + { + fprints(2, "Can't malloc line in backquot\n"); + return; + } + fileopen(NULL, pfd[0]); + while (getfileline(line, &term)) + meta_1(line, C_SPACE | TRUE); /* Add words to the carray */ + fileclose(); + free(line); + /* Delete first node */ + if (first->mode & TRUE) + free(first->name); + first->name = NULL; + first->mode = 0; + /* Insert words in list */ + if (ncand > base) + { + carray[base - 1].next = NULL; /* Undo addcarray */ + first->next = &carray[base]; + carray[ncand - 1].next = curr; + } + } +#ifdef DEBUG + fprints(2, " In backquot "); + listcarray(); +#endif +} + +/* Meta_1 takes the user's input line (old), and builds a linked list of words + * in the carray. It also expands history. Meta_1 returns TRUE if + * successful; if any errors occur, it returns FALSE. + * + * If mustmalc is TRUE, meta_1 assumes the word must be strcpy'd. + * As of version 38, support for double quotes is gone, and backquotes are + * treated in the same way as single quotes. + */ +bool +meta_1(old, mustmalc) + char *old; + bool mustmalc; +{ + char bq[3]; /* Used to find backslashes in quotes */ + char *a, *old2; + char c; + int mode; + int lastdollar = 0; /* Was last word a variable? */ + + bq[0]= '\\'; bq[2]= EOS; + while (1) /* Parse each word */ + { + a = strpbrk(old, " \t\n\\'`{;|<>&$"); + /* why `&& *old' below - Not all strpbrk()s give NULL when *old=EOS */ + if (a != NULL && *old) /* We found a word */ + { + c = *a; + *a = EOS; + mode = (c == ' ' || c == '\t') ? C_SPACE : 0; + /* Add the word to the list */ + if (a > old) + addword(old, mode | lastdollar | mustmalc); + + mode = 0; + switch (c) /* Now deal with the `token' */ + { + case '{': + mode = C_CURLY; + c = '}'; + goto quotes; /* Yuk, a goto */ + case '`': + mode = C_BACKQUOTE; + goto quotes; /* Yuk, a goto */ + case '\'': + mode = C_QUOTE; + quotes: + old2= old = ++a; bq[1]= c; + quotes2: + a = strpbrk(old2,bq); /* Find either slosh or matching quote */ + if (a == NULL) + { + fprints(2, "No matching %c\n", c); + return (FALSE); + } + if (*a == '\\') + { + if (a>old) /* there is something before the slosh */ + { + *a= EOS; + addword(old, mode | lastdollar | mustmalc); + } + old= ++a; old2= old+1; + goto quotes2; /* Yuk, another goto */ + } + *(a++) = EOS; /* Terminate the word */ + if (*a == ' ' || *a == '\t') /* Check for trailing spaces */ + { + a++; + mode |= C_SPACE; + } + addword(old, mode | lastdollar | mustmalc); + break; + case '\\': + old = (char *) malloc(2); + if (old) + { + old[0] = *(a + 1); + old[1] = EOS; + addword(old, mode | lastdollar | TRUE); + } + a += 2; + break; + case '|': + mode = C_PIPE; + goto doubles; /* Yuk, a goto */ + case '&': + mode = C_AMP; + goto doubles; /* Yuk, a goto */ + case '<': + mode = C_LT; + doubles: + a++; + if (*a == c) + { + a++; + mode += C_DOUBLE; + } + addword(NULL, mode); + break; + case '>': + mode = 1 | C_GT; /* Default to stdout */ + switch (*(a + 1)) + { + case '>': + mode = 1 | C_GTGT; + a++; + break; + case '&': + mode = 2 | C_GT; + a++; + } + addword(NULL, mode); + a++; + break; + case ';': + addword(NULL, C_SEMI); + case '$': + if (lastdollar && (a==old)) /* We found a $$ */ + { + a++; mode= C_DOLLAR | TRUE ; + mode|= ((*a==' ')||(*a=='\t')) ? C_SPACE : FALSE; + addword("$",mode); a--; + c=' '; /* Don't recognise 2nd $ below */ + } + case ' ': + case '\t': + a++; + case '\n': + break; + default: + fprints(2, "Unknown token %c in meta_1", c); + return (FALSE); + } + lastdollar = (c == '$') ? C_DOLLAR : 0; /* Mark variables */ + + /* Ok, skip intermediate spaces */ + for (old = a; *old && (*old == ' ' || *old == '\t'); old++); + } + else + { + addword(old, lastdollar | mustmalc); + break; + } + } +#ifdef DEBUG + fprints(2, " In meta_1 "); + wordlist = next_word; + listcarray(); +#endif + return (TRUE); +} + + +/* Joinup concatenates two carray elements together. The following pointers + * must be non-null: curr, curr->next, curr->name, curr->next->name. + */ +#ifdef PROTO +static void joinup(struct candidate *curr) +#else +static void +joinup(curr) + struct candidate *curr; +#endif +{ + char *a; + + a = (char *) malloc((unsigned) (strlen(curr->name) + strlen(curr->next->name) + 1)); + if (!a) + return; + strcpy(a, curr->name); + strcat(a, curr->next->name); + if (curr->mode & TRUE) + free(curr->name); + curr->mode = curr->next->mode | TRUE; + curr->next = curr->next->next; + curr->name = a; +} + +/* Meta_2 expands $ and ~ in the carray */ +void +meta_2() +{ + struct candidate *curr; + char *a, tildir[MAXWL]; + + for (curr = wordlist; curr != NULL; curr = curr->next) + { + a = curr->name; + if (a == NULL || curr->mode & C_QUOTE) + continue; + +#ifndef NO_TILDE + if (*a == '~') /* If a ~ */ + { + tilde(a, tildir); /* expand it too */ +#ifdef DEBUG + fprints(2, "tildir is %s\n", tildir); +#endif + if (curr->mode & TRUE) + free(curr->name); + curr->name = (char *) malloc((unsigned) strlen(tildir) + 4); + if (curr->name != NULL) + { + strcpy(curr->name, tildir); + curr->mode = TRUE | C_SPACE; + } + } +#endif + + if (curr->mode & C_DOLLAR) /* If it has a $ */ + dollar(curr); /* expand it */ + + } + for (curr = wordlist; curr != NULL; curr = curr->next) + { + while (curr->name && curr->next && curr->next->name && !(curr->mode & C_SPACE)) + joinup(curr); /* No space, we'd better join em */ + +#ifdef NOTYET + if (curr->mode & C_CURLY) /* If it is in {} */ + curly(curr); /* expand it */ +#endif + + if (curr->mode & C_BACKQUOTE) /* Ha! Found a backquote word */ + backquot(curr); + + if (!(curr->mode & C_QUOTE) && curr->name && strpbrk(curr->name, "*?[")) + star(curr); + + while (curr->name && curr->next && curr->next->name + && !(curr->mode & ~TRUE) && !(curr->next->mode & ~(TRUE|C_SPACE))) + joinup(curr); /* No space, we'd better join em */ + } +#ifdef DEBUG + fprints(2, " In meta_2 "); + listcarray(); +#endif +} diff --git a/Applications/wish/parse.c b/Applications/wish/parse.c new file mode 100644 index 0000000000..5afa3b14c5 --- /dev/null +++ b/Applications/wish/parse.c @@ -0,0 +1,186 @@ +/* Parsing the input file + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#include "header.h" + +/* The parser uses the symbols parsed by meta.c, but it needs a few more + * definitions for proper parsing. These are below. + */ + +#define C_WORD C_SPACE /* This indicates the returned word is a */ + /* usual word */ +#define C_EOF FALSE /* Returned when no more tokens left */ + +extern struct candidate *wordlist; /* The list of words to parse */ +static struct candidate *pcurr; /* A ptr to the word we are parsing */ + +/* Gettoken returns the symbol which stands for the current word, or 0. + * If 0, word points to a string that holds a normal word. This is guaranteed + * not to be clobbered until we get back out to main(). + */ +#ifdef PROTO +static int gettoken(char **word, int *fd) +#else +static int +gettoken(word, fd) + char **word; + int *fd; +#endif +{ + int mode; + + /* Skip any null words */ + while (1) + { + /* Return EOF on end of list */ + if (pcurr == NULL) + return (C_EOF); + mode = pcurr->mode; + + if (mode & C_WORDMASK) /* We've found a token, return */ + { + *word = NULL; + *fd = mode & C_FD; + mode &= C_WORDMASK; /* stripping any fd */ + pcurr = pcurr->next; + return (mode); + } + + if (pcurr->name && *(pcurr->name) != EOS) + { + *word = pcurr->name; /* just return it */ + pcurr = pcurr->next; + return (C_WORD); + } + pcurr = pcurr->next; /* Not a valid word or symbol, skip */ + } +} + +/* Command parses the input and calls invoke() to redirect and execute + * each simple command in the parsebuf. It returns the pid to wait on + * in waitpid, or 0 if no pid to wait on, as well as the token that ended + * the pasebuf. This routine is recursive, and when passed makepipe=TRUE, + * makes a pipe, and returns the fd of the writing end in pipefdp. + */ +int +command(waitpid, makepipe, pipefdp, anydups) /* Do simple command */ + int *waitpid, *pipefdp; + int makepipe, anydups; +{ + int token, term; + int argc, pid, pfd[2]; + char *argv[MAXARG + 1]; + char *word; + int i, fd, how = 0; + struct rdrct newfd[3]; + + argc = 0; + argv[0] = NULL; + for (i = 0; i < 3; i++) + { + newfd[i].fd = 0; + newfd[i].how= 0; + newfd[i].file = NULL; + } + if (makepipe == FALSE) + pcurr = wordlist; /* Start parsing the wordlist */ + + while (1) + { + token = gettoken(&word, &fd); +#ifdef DEBUG + prints("Token is %o", token); + if (word != NULL) + prints(" word is %s", word); + write(1, "\n", 1); +#endif + switch (token) + { + case C_WORD: + if (argc == MAXARG) + { + fprints(2, "Too many args\n"); + break; + } + argv[argc++] = word; + continue; + case C_LT: + if (makepipe) + { + fprints(2, "Extra <\n"); + break; + } + if (gettoken(&(newfd[fd].file), &i) != C_WORD) + { + fprints(2, "Illegal <\n"); + break; + } + newfd[fd].how = H_FROMFILE; + anydups= TRUE; + continue; + case C_GT: + case C_GTGT: + if (newfd[fd].fd != 0 || newfd[fd].file != NULL) + { + fprints(2, "Extra > or >>\n"); + break; + } + if (gettoken(&(newfd[fd].file), &i) != C_WORD) + { + fprints(2, "Illegal > or >>\n"); + break; + } + if (token == C_GTGT) + newfd[fd].how = H_APPEND; + anydups= TRUE; + continue; + case C_AMP: /* If a pipe, call ourselves to get */ + case C_EOF: /* the write file descriptor */ + case C_SEMI: + case C_DBLAMP: + case C_DBLPIPE: + case C_PIPE: + argv[argc] = NULL; + if (token == C_PIPE) + { + anydups= TRUE; + if (newfd[1].fd != 0 || newfd[1].file != NULL) + { + fprints(2, "> or >> conflicts with |\n"); + break; + } + term = command(waitpid, TRUE, &(newfd[1].fd),anydups); + } /* and set up the terminal token */ + else + term = token; + /* If called by us, make the needed pipe */ + if (makepipe) + { + if (pipe(pfd) == -1) + { + perror("pipe"); + break; + } + /* and return the write file descriptor */ +#ifdef DEBUG + fprints(2, "Opened pipe fds %d and %d\n", pfd[0], pfd[1]); +#endif + *pipefdp = pfd[1]; + newfd[0].fd = pfd[0]; + } + if (term == C_AMP) + how = H_BCKGND; + pid = invoke(argc, argv, newfd, how, anydups); + /* End of command line, return pid to wait */ + if (token != C_PIPE) + *waitpid = pid; + if (argc == 0 && token != C_EOF) + fprints(2, "Missing command\n"); + return (term); + default: + prints("Unknown token %o in command()\n", token); + } + } +} diff --git a/Applications/wish/posixjob.c b/Applications/wish/posixjob.c new file mode 100644 index 0000000000..98d5e97e9f --- /dev/null +++ b/Applications/wish/posixjob.c @@ -0,0 +1,148 @@ +/* POSIX Job Control functions + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + * + */ + +#define WIFCORE(x) (FALSE) /* We can't tell */ +#define WRUNFG(x) ((x) == RUNFG) +#define WRUNBG(x) ((x) == RUNBG) + +/* The following tables are all VERY OS-dependent, especially on POSIX + * systems. If you find the shell giving you weird signal messages, you + * should change the names of the signals below. + */ +static char *siglist[] = { + "", "Hangup", "Interrupt", "Quit", "Illegal Instruction", + "Trace/BPT Trap", "IOT Trap", "EMT Trap", "Floating Point Exception", + "Killed", "Bus Error", "Segmentation Violation", "Bad System Call", + "Broken Pipe", "Alarm", "Terminated" ,"Urgent Socket Condition", + "Stopped (signal)", "Stopped", "Continue", "Child Status Change", + "(tty input)", "(tty output)", "I/O", "Cpu Time Limit", + "File Size Limit", "Virtual Time Alarm", "Profile Alarm", + "Window Change", "Resource Lost", "User Signal 1", "User Signal 2" +}; + +char *signame[] = { + "", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", + "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM" ,"URG", "STOP", "TSTP", + "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU", "XFSZ", "VTALRM", "PROF", + "WINCH", "LIST", "USR1", "USR2", NULL +}; + +/* Waitfor is called to wait for a requested job to stop/die. It receives the + * new status of any children. If it's the requested job, we return. Pid holds + * the process-id of the job we are after; if it is 0, we return when no + * processes are left waiting to report a change in state. + * + * If pid is negative, we are a subshell and _must_ return only when the pid + * dies. We set the subshell flag and make pid positive again. Then, if we + * find out that the job was stopped, we wake it up. This works because we + * are put to sleep at the same time as the child, and the original shell + * fg()s _us_. + */ +void +waitfor(pid) + int pid; +{ + struct job *thisjob; + int wpid; + bool subshell=FALSE; + int status; + + int waitflags; + waitflags = (pid == 0) ? WNOHANG | WUNTRACED : WUNTRACED; + +#ifdef DEBUG + fprints(2,"In waitfor\n"); +#endif + + if (pid<0) { pid= -pid; subshell= TRUE; } + while (1) + { + wpid = waitpid(-1, &status, waitflags); + +#ifdef DEBUG + fprints(2,"waitxx() returned\n"); +#endif + if (wpid == -1 || wpid == 0) + break; + + if (subshell && WIFSTOPPED(status)) /* We can't return yet */ + { + kill(wpid, SIGCONT); /* Wake the child up first - waah! */ + continue; + } +#ifndef NO_JOB + thisjob = findjob(wpid); + if (thisjob == NULL) + continue; + thisjob->status = status; + thisjob->changed = TRUE; +#endif + if (pid == wpid) + return; + } +} + +/* Stopjob is only called when we received a SIGTSTP. If we are wait3()ing + * a pid, we send a SIGSTOP to that pid. This should then cause wait3() + * to exit, and thus waitfor() will return. + */ +SIGTYPE +stopjob(a) + int a; +{ + signal(SIGTSTP, stopjob); +#ifdef DEBUG + fprints(2,"In stopjobs\n"); +#endif + if (currentjob) + { +#ifdef DEBUG + fprints(2,"About to stop pid %d\n", currentjob->pid); +#endif + kill(currentjob->pid, SIGSTOP); + } +} + + +static void +bgstuff(pid) + int pid; +{ + struct job *ptr; + + setpgid(pid, pid); +#ifdef DEBUG + fprints(2,"About to SIGCONT %d\n", pid); +#endif + kill(pid, SIGCONT); +#ifndef NO_JOB + ptr = findjob(pid); + if (ptr) { ptr->STATUS = RUNBG; currentjob= ptr; } +#endif +} + + +static int +fgstuff(pid) + int pid; +{ + +/* Under UCBJOB and POSIXJOB, we don't try & bring the pid into our + * pgrp, that doesn't work. Instead, we move the terminal + * over to that pgrp, and move ourselves to that pgrp as well. + */ + if (tcsetpgrp(0, pid) == -1) /* Move the terminal to that pgrp */ + { + perror("fg tcsetpgrp"); + return (1); + } + if (setpgid(0, pid) == -1) /* Set shell's process group to the pid's */ + { + perror("fg setpgid"); + return (1); + } + return(0); +} diff --git a/Applications/wish/prints.c b/Applications/wish/prints.c new file mode 100644 index 0000000000..b4030cf4d2 --- /dev/null +++ b/Applications/wish/prints.c @@ -0,0 +1,353 @@ +/* This file contains functions that replace printf, fprintf and sprintf, + * which are smaller and do not use stdio buffering. They can only handle + * %c %s %o %d and %x as format types, although %2x and %02x can be done as + * well. Note that fprintf takes as its first argument a file descriptor, + * not a FILE pointer. + * + * These routines were derived from the prints routine in Minix 1.5. + * + * $Revision: 41.2 $ $Date: 2003/04/21 13:09:53 $ + */ +#define NO_PRINTS_DEFN +#include "header.h" + +/* If your compiler/system supports one of the following, + * you should use it, preferably stdarg.h. + */ +#ifdef STDARG +#include +#endif +#ifdef VARARGS +#ifdef va_start +# undef va_start +#endif +#include +#endif + +#if !defined(STDARG) && !defined(VARARGS) + +/* Otherwise we must make do with these */ +#define va_list char ** +#define va_arg(x,t) (t)*(x++) +#endif + +#define MAXDIG 11 /* 32 bits in radix 8 */ +#define TRUNC_SIZE 1024 + +static char Buf[TRUNC_SIZE], *Bufp; +static char Intbuf[MAXDIG]; +static int Out; +static char *Dest; + +#ifdef PROTO +static void __itoa(char *p, unsigned int num, int radix) +#else +static void +_itoa(p, num, radix) + char *p; + unsigned int num; + int radix; +#endif +{ + int i; + char *q; + + q = p + MAXDIG; + do + { + i = (int) (num % radix); + i += '0'; + if (i > '9') + i += 'A' - '0' - 10; + *--q = i; + } while ((num = (num / radix))); + i = p + MAXDIG - q; + do + *p++ = *q++; + while (--i); + *p = 0; +} + +#ifdef PROTO +static void _put(char c) +#else +static void +_put(c) + char c; +#endif +{ + if (Bufp < &Buf[TRUNC_SIZE]) + *Bufp++ = c; +} + +#ifdef PROTO +static void printvoid(char *str, va_list ap) +#else +static void +printvoid(str, ap) + char *str; + va_list ap; +#endif +{ + int w; + int k, x, radix; + char *p, *p1, c, fillchar; + + Bufp = Buf; + while (*str != '\0') + { + if (*str != '%') + { + _put(*str++); + continue; + } + w = 0; + fillchar = ' '; + str++; + while (*str >= '0' && *str <= '9') + { + if (*str == '0' && !w) + fillchar = '0'; + w = 10 * w + (*str - '0'); + str++; + } + + switch (*str) + { + case 'c': + k = va_arg(ap, int); + _put(k); + str++; + continue; + case 's': + p = va_arg(ap, char *); + p1 = p; + while ((c = *p++)) + _put(c); + for (x = strlen(p1); w > x; w--) + _put(fillchar); + str++; + continue; + case 'x': + radix = 16; + goto printnum; + case 'd': + radix = 10; + goto printnum; + case 'o': + radix = 8; + printnum: + x = va_arg(ap, int); + str++; + __itoa(Intbuf, x, radix); + p = Intbuf; + for (x = strlen(p); w > x; w--) + _put(fillchar); + while ((c = *p++)) + _put(c); + continue; + default: + _put(*str++); + continue; + } + + } + /* write everything in one blow. */ + if (Out == -1) + { + *Bufp++ = 0; +#ifdef USES_BCOPY + bcopy(Buf, Dest, (int) (Bufp - Buf)); +#else + memcpy(Dest, Buf, (int) (Bufp - Buf)); +#endif + } + else + write(Out, Buf, (int) (Bufp - Buf)); +} + +#ifdef VARARGS +/* For systems that have varargs.h, use the following three routines + */ +/* VARARGS */ +void +fprints(va_alist) +va_dcl +{ + va_list argptr; + char *str; + + va_start(argptr); + Out = va_arg(argptr, int); + str = va_arg(argptr, char *); + printvoid(str, argptr); + va_end(argptr); +} +/* VARARGS */ +void +sprints(va_alist) +va_dcl +{ + va_list argptr; + char *str; + + va_start(argptr); + Dest = va_arg(argptr, char *); + Out = -1; + str = va_arg(argptr, char *); + printvoid(str, argptr); + va_end(argptr); +} +/* VARARGS */ +void +prints(va_alist) +va_dcl +{ + va_list argptr; + char *str; + + va_start(argptr); + Out = 1; + str = va_arg(argptr, char *); + printvoid(str, argptr); + va_end(argptr); +} + +#endif + +#ifdef STDARG +/* For systems that have stdarg.h, use the following three routines + */ +/* VARARGS */ +#ifdef PROTO +void fprints(int fd, char *str, ...) +#else +void fprints(fd, str) + int fd; + char *str; +#endif +{ + va_list argptr; + + va_start(argptr, str); + Out = fd; + printvoid(str, argptr); + va_end(argptr); +} +/* VARARGS */ +#ifdef PROTO +void sprints(char *out, char *str, ...) +#else +void sprints(out, str) + char *out; + char *str; +#endif +{ + va_list argptr; + + va_start(argptr, str); + Dest = out; + Out = -1; + printvoid(str, argptr); + va_end(argptr); +} +/* VARARGS */ +#ifdef PROTO +void prints(char *str, ...) +#else +void prints(str) + char *str; +#endif +{ + va_list argptr; + + va_start(argptr, str); + Out = 1; + printvoid(str, argptr); + va_end(argptr); +} + +#endif + +#if !defined(STDARG) && !defined(VARARGS) +/* For systems that have neither, we use these. + */ +/* VARARGS */ +void +fprints(fd, str, argptr) + int fd; + char *str; + char *argptr; +{ + Out = fd; + printvoid(str, &argptr); +} +/* VARARGS */ +void +sprints(out, str, argptr) + char *out; + char *str; + char *argptr; +{ + Dest = out; + Out = -1; + printvoid(str, &argptr); +} +/* VARARGS */ +void +prints(str, argptr) + char *str; + char *argptr; +{ + Out = 1; + printvoid(str, &argptr); +} + +#endif + +/* Mprint is a small utility routine that prints out a line with control + * characters converted to Ascii, e.g ^A etc. If nocr is true, no \n is + * appended. Mprint now uses the MSB variable to determine how to print + * out msb-on chars. If MSB, use standout mode. + */ +void +mprint(line, nocr) + uchar *line; + int nocr; +{ + extern char *so, *se; + char c, low, buf[MAXLL]; + bool msb; + int i, j, k; + + if (EVget("Msb")) + msb = TRUE; + else + msb = FALSE; + for (j = 0, i = 0; line[i]; i++) + { + c = line[i]; + low = c & 0x7f; /* Turn the msb off */ + + if (low < 32 || low == 127) /* If a ctrl-char, insert ^ */ + { + buf[j++] = '^'; + c |= 64; + low |= 64; /* and make it a capital */ + } + if (msb && low != c) /* Bold msb-on char if wanted */ + { + for (k = 0; so[k]; j++, k++) + buf[j] = so[k]; + buf[j++] = low; + for (k = 0; se[k]; j++, k++) + buf[j] = se[k]; + } + else + buf[j++] = c; + } + buf[j] = EOS; + write(1, buf, j); + if (!nocr) + write(1, "\n", 1); +} diff --git a/Applications/wish/proto.h b/Applications/wish/proto.h new file mode 100644 index 0000000000..069c18d9da --- /dev/null +++ b/Applications/wish/proto.h @@ -0,0 +1,227 @@ +/* Prototypes for Wish. This file contains the K&R and Ansi prototypes for + * all the non-static routines in the shell, plus prototypes for all the + * external routines that the shell uses. Expect to comment some of these + * out depending upon what is already defined in your system's header files. + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#ifdef PROTO +# define P(s) s +#else +# define P(s) () +#endif + + +/* Trying to use the intuitive prototypes below AND + * allow the use of stdargs or varargs is basically + * impossible. + * + */ +#ifndef NO_PRINTS_DEFN +void fprints P((int fd, CONST char *fmt, ...)); +void sprints P((char *out, CONST char *fmt, ...)); +void prints P((CONST char *fmt, ...)); +#endif + +void mprint P((uchar *line , int nocr )); + +/* alias.c */ +struct val *checkalias P((char *aname )); +bool getaliasline P((uchar *line , int *nosave )); +int alias P((int argc , char *argv [])); +int unalias P((int argc , char *argv [])); + +/* bind.c */ +int Bind P((int argc , uchar *argv [])); +int unbind P((int argc , uchar *argv [])); +void initbind P((void )); +int getcomcmd P((void )); + +/* builtin.c */ +int builtin P((int argc , char *argv [], int *rtnpid )); + +/* clebuf.c */ +void flushbuf P((void )); +void addbuf P((char *str )); +void mputc P((int b )); + +/* clex.c */ +int compare P((CONST void *a , CONST void *b )); +void addcarray P((char *word , struct candidate *prev , int mode , bool malc )); +void complete P((char *line , int *pos , bool how )); + +/* comlined.c */ +int Show P((uchar *line , int pos , int let , int flag )); +int prevword P((uchar *line , int *p , int flag )); +bool getuline P((uchar *line , int *nosave )); + +/* exec.c */ +int invoke P((int argc , char *argv [], struct rdrct newfd [], int how , int anydups )); + +/* file.c */ +bool fileopen P((char *filename , int fd )); +void fileclose P((void )); +bool getfileline P((uchar *line , int *nosave )); +int source P((int argc , char *argv [])); + +/* hist.c */ +int savehist P((char *line , bool andadd )); +void loadhist P((char *line , int histnum )); +int history P((int argc , char *argv [])); +char *gethist P((char *event )); + +/* job.c */ +int addjob P((int pid , char *name , int isbg )); +int joblist P((int argc , char *argv [])); +int Kill P((int argc , char *argv [])); +int bg P((int argc , char *argv [])); +int fg P((int argc , char *argv [])); + +/* posixjob.c */ +void waitfor P((int pid )); +SIGTYPE stopjob P((int a)); + +/* main.c */ +void prprompt P((void )); +void leave_shell P((int how )); +void doline P((int isalias )); +int main P((int argc , char *argv [])); + +/* malloc.c */ +char *Malloc P((unsigned int size , char *mesg )); +void initmall P((void )); +void *myMalloc P((unsigned int size )); +void myFree P((void *ptr )); + +/* meta.c */ +void tilde P((char *word , char *dir )); +char *expline P((struct candidate *list )); +bool meta_1 P((char *old , bool mustmalc )); +void meta_2 P((void )); + +/* parse.c */ +int command P((int *waitpid , int makepipe , int *pipefdp , int anydups )); + +/* signal.c */ +void catchsig P((void )); +void dflsig P((void )); + +/* term.c */ +void getstty P((void )); +void setcbreak P((void )); +void setcooked P((void )); +void getstty P((void )); +void setcbreak P((void )); +void setcooked P((void )); +void getstty P((void )); +void setcbreak P((void )); +void setcooked P((void )); +void terminal P((void )); + +/* val.c */ +void appendval P((struct vallist *l , struct val *v )); +struct val *searchval P((struct vallist *l , char *name , int mode , bool sub )); +void saveval P((struct vallist *l , struct val *v )); +struct val *pullval P((struct vallist *l )); +void setval P((char *name , char *val , struct vallist *l )); + +/* var.c */ +char *EVget P((char *name )); +bool EVinit P((void )); +bool EVupdate P((void )); +int export P((int argc , char *argv [])); +int shift P((int argc , char *argv [])); +int unset P((int argc , char *argv [])); +int set P((int argc , char *argv [])); + + + +/* The following are defined so that gcc -Wall won't complain about + * missing prototypes - you may need to alter or comment them out + * for your own machine. Some of these were borrowed from Minix. + */ + + +#ifdef UNUSED +/* Ansi C routines */ + +int atoi P((CONST char *nptr)); +void free P((void *ptr)); +void *malloc P((size_t size)); +void *memcpy P((void *s1, CONST void *s2, size_t n)); +void *memset P((void *s, int c, size_t n)); +void qsort P((void *base, size_t nmemb, size_t size, + int (*compare)(CONST void *a, CONST void *b))); +void *realloc P((void *ptr, size_t size)); +char *strcat P((char *s1, CONST char *s2)); +char *strchr P((CONST char *s, int c)); +int strcmp P((CONST char *s1, CONST char *s2)); +char *strcpy P((char *s1, CONST char *s2)); +size_t strlen P((CONST char *s)); +int strncmp P((CONST char *s1, CONST char *s2, size_t n)); +char *strncpy P((char *s1, CONST char *s2, size_t n)); +char *strpbrk P((CONST char *s1, CONST char *s2)); +char *strrchr P((CONST char *s, int c)); + + +/* POSIX routines */ + +int chdir P((CONST char *path)); +int closedir P((DIR *dirp)); +int close P((int fd)); +int dup P((int fd)); +int dup2 P((int fd, int fd2)); +int execvp P((CONST char *file, char *argv[])); +void exit P((int status)); +pid_t fork P((void)); +char *getcwd P((char *buf, int size)); +pid_t getpid P((void)); +uid_t getuid P((void)); +struct passwd *getpwent P((void)); +struct passwd *getpwnam P((CONST char *name)); +struct passwd *getpwuid P((int uid)); +void endpwent P((void)); +int kill P((pid_t pid, int sig)); +struct tm *localtime P((CONST time_t *timer)); +off_t lseek P((int fd, off_t offset, int whence)); +int open P((CONST char *path, int oflag, mode_t modes)); +void perror P((CONST char *s)); +int pipe P((int fildes[2])); +int read P((int fd, char *buf, unsigned int n)); +int setpgid P((int pid, int pgid)); +int getpgrp P((int pid)); +SIGTYPE (*signal)(); +int stat P((CONST char *filename, struct stat *s)); +int tcgetattr P((int fd, struct termios *tptr)); +int tcsetattr P((int fd, int opt_actions, struct termios *tptr)); +int tcsetpgrp P((int fd, int pgrp)); +time_t time P((time_t *timeptr)); +pid_t wait P((int *stat_loc)); +pid_t waitpid P((pid_t pid, int *stat_loc, int options)); +int write P((int fd, CONST char *buf, unsigned int n)); +DIR *opendir P((CONST char *dirname)); +int closedir P((DIR *dirp)); +#ifdef USES_DIRECT +struct direct *readdir P((DIR *dirp)); +#else +struct dirent *readdir P((DIR *dirp)); +#endif + + +/* Other routines */ + +char *getcwd P((char *buf)); +int ioctl P((int fd, int request, ... )); +int setpgrp P((int pid, int pgid)); +int tgetent P((char *bp, CONST char *name)); +int tgetflag P((CONST char *id)); +int tgetnum P((CONST char *id)); +char *tgetstr P((CONST char *id, char **area)); +int wait3 P((union wait *status, int options, struct rusage *rusage)); +void bzero P((char *b1, int length)); +void bcopy P((CONST char *b1, char *b2, int length)); + +#endif /* UNUSED */ + +#undef P diff --git a/Applications/wish/signal.c b/Applications/wish/signal.c new file mode 100644 index 0000000000..83ea5d8504 --- /dev/null +++ b/Applications/wish/signal.c @@ -0,0 +1,66 @@ +/* Most of the functions that handle signals are kept in this file. + * Exceptions are some signal() calls done in job.c + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#include "header.h" + +/* Graceful is set to catch most of the unused signals in Wish. All it + * does is print out an error message and exit. + */ +#ifdef PROTO +static SIGTYPE graceful(int sig) +#else +static SIGTYPE +graceful(sig) + int sig; +#endif +{ + extern char *signame[]; + + setcooked(); + fprints(2, "Received SIG%s signal, # %d\n", signame[sig],sig); + exit(1); +} + + +/* Catchsig is called once at Wish startup, and it sets graceful to + * catch most of the signals unused by Wish. + */ +void +catchsig() +{ +#ifdef DEBUG + int i; + + for (i = 1; i <= MAXSIG; i++) + { + if (i != SIGKILL && i != SIGCONT) /* SIGKILL cannot be caught or ignored */ + signal(i, graceful); + } +#endif + + signal(SIGINT, SIG_IGN); /* Sometimes taken out for debugging */ + signal(SIGQUIT, SIG_IGN); + +#if defined(UCBJOB) || defined(POSIXJOB) + /* Also catch these for job control */ + signal(SIGTSTP, stopjob); + signal(SIGCHLD, SIG_IGN); +#endif +} + +/* Dflsig sets all the signals to their default handlers, so that exec'd + * programs will have a standard signal environment. + */ +void +dflsig() +{ + int i; + +#ifdef DEBUG + fprints(2,"%d signals defined\n",MAXSIG); +#endif + for (i = 1; i <= MAXSIG; i++) signal(i, SIG_DFL); +} diff --git a/Applications/wish/term.c b/Applications/wish/term.c new file mode 100644 index 0000000000..ad05f3eeef --- /dev/null +++ b/Applications/wish/term.c @@ -0,0 +1,314 @@ +/* This file contains routines for setting the terminal characteristics, + * and for getting the termcap strings. + * + * $Revision: 41.3 $ $Date: 2003/04/21 13:08:43 $ + */ + +#include "header.h" + +int beeplength; /* Strlen of beep */ +int wid; /* The width of the screen (minus 1) */ + + /* The termcap strings Wish uses */ +char *bs, *nd, *cl, *cd, *up, *so, *se, *wbeep; + +static char *termcapbuf; /* Buffer to get termcap strings */ + +/* Wish keeps the ``normal'' terminal characteristics (i.e the ones Wish's + * children see) in a structure, and restores the characteristics each + * time it forks. The structure depends upon the OS we are being compiled + * under. + */ + +#ifdef USES_TCGETA +static struct termio tbuf, tbuf2; + +#endif + +#ifdef USES_SGTTY +static struct sgttyb tbuf, tbuf2; +static struct tchars sbuf, sbuf2; + +# ifdef USES_MORESIG +static struct ltchars moresigc, moresigc2; +# endif +#endif + +#ifdef USES_TERMIOS +static struct termios tbuf, tbuf2; + +#endif + + +#ifdef DEBUG +/* Printctrl prints out a terminal sequence that Wish got + * from termcap. Only used when debugging the ensure Wish + * is actually getting the strings, and the right ones. + */ +static void +printctrl(name, str) + char *name, *str; +{ + int i; + + prints("%s: ", name); + for (i = 0; str[i]; i++) + if (str[i] > 31) + prints("%c",str[i]); + else + { + prints("%c",'^'); + if (str[i] > 26) + prints("%c",str[i] + 64); + else + prints("%c",str[i] + 96); + } + prints('\n'); +} + +#endif + + +/* There are 3 versions of getstty, setcooked and setcbreak, + * defined by USES_TCGETA, USES_TERMIOS and USES_SGTTY + */ + +#ifdef USES_TCGETA +/* Getstty: Get the current terminal structures for Wish */ +void +getstty() +{ + if (ioctl(0, TCGETA, &tbuf)) + perror("ioctl in getstty"); + memcpy(&tbuf2, &tbuf, sizeof(tbuf)); +} + +/* Setcbreak: Set terminal to cbreak mode */ +void +setcbreak() +{ + bool keepstty; + + if (EVget("Keepstty")) + keepstty = TRUE; + else + keepstty = FALSE; + + if (!keepstty) + getstty(); + tbuf2.c_lflag &= ~(ICANON | ECHO | ISIG); /* Turn off canonical input and echo */ + tbuf2.c_cc[4] = 1; /* read 1 char before returning like CBREAK */ + if (ioctl(0, TCSETA, &tbuf2)) + perror("ioctl in setcbreak"); +} + +/* Setcooked: Set terminal to cooked mode */ +void +setcooked() +{ + if (ioctl(0, TCSETA, &tbuf)) + perror("ioctl in setcooked"); +} + +#endif /* USES_TCGETA */ + + +#ifdef USES_TERMIOS +/* Getstty: Get the current terminal structures for Wish */ +void +getstty() +{ + if (tcgetattr(0, &tbuf)) + perror("tcgetattr in getstty"); + memcpy(&tbuf2, &tbuf, sizeof(tbuf)); +} + +/* Setcbreak: Set terminal to cbreak mode */ +void +setcbreak() +{ + int i; + bool keepstty; + + if (EVget("Keepstty")) + keepstty = TRUE; + else + keepstty = FALSE; + + if (!keepstty) + getstty(); + /* Turn off canonical input and echo */ + tbuf2.c_lflag = tbuf2.c_lflag & (~ICANON) & (~ECHO); + for (i=0; i< NCCS; i++) tbuf2.c_cc[i]=0; + tbuf2.c_cc[VMIN] = 1; /* read 1 char before returning like CBREAK */ + if (tcsetattr(0, TCSANOW, &tbuf2)) + perror("cbreak tcsetattr"); +} + +/* Setcooked: Set terminal to cooked mode */ +void +setcooked() +{ + if (tcsetattr(0, TCSANOW, &tbuf)) + perror("cooked tcsetattr"); +} + +#endif /* USES_TERMIOS */ + + +#ifdef USES_SGTTY +/* Getstty: Get the current terminal structures for Wish */ +void +getstty() +{ + if (ioctl(0, TIOCGETP, &tbuf))/* get the sgttyb struct */ + perror("ioctl2 in getstty"); + if (ioctl(0, TIOCGETC, &sbuf))/* get the tchars struct */ + perror("ioctl3 in getstty"); +#ifdef USES_MORESIG + bcopy(&sbuf, &sbuf2, sizeof(sbuf)); /* and copy them so we can change */ + bcopy(&tbuf, &tbuf2, sizeof(tbuf)); + if (ioctl(0, TIOCGLTC, &moresigc)) /* get the ltchars struct */ + perror("ioctl4 in getstty"); + bcopy(&moresigc, &moresigc2, sizeof(moresigc)); +#else + memcpy(&sbuf2, &sbuf, sizeof(sbuf)); /* and copy them so we can change */ + memcpy(&tbuf2, &tbuf, sizeof(tbuf)); +#endif +} + +/* Setcbreak: Set terminal to cbreak mode */ +void +setcbreak() +{ + bool keepstty; + + if (EVget("Keepstty")) + keepstty = TRUE; + else + keepstty = FALSE; + + if (!keepstty) + getstty(); + /* setup terminal with ioctl calls */ + + tbuf2.sg_flags |= CBREAK; /* cbreak mode to get each char */ + tbuf2.sg_flags &= (~ECHO); /* do not echo chars to screen */ + if (ioctl(0, TIOCSETP, &tbuf2)) /* put it back, modified */ + perror("ioctl1 su"); + sbuf2.t_intrc = (UNDEF); /* no interrupt or quitting */ + /* sbuf2.t_quitc=(UNDEF); Allow quit while debugging */ + sbuf2.t_eofc = (UNDEF); /* or eof signalling */ + if (ioctl(0, TIOCSETC, &sbuf2)) /* put it back, modified */ + perror("ioctl2 scb"); +#ifdef USES_MORESIG + moresigc2.t_suspc = (UNDEF); /* no stopping */ + moresigc2.t_dsuspc = (UNDEF); /* or delayed stopping */ + moresigc2.t_rprntc = (UNDEF); /* or reprinting */ + moresigc2.t_flushc = (UNDEF); /* or flushing */ + moresigc2.t_werasc = (UNDEF); /* or word erasing */ + moresigc2.t_lnextc = (UNDEF); /* or literal quoting */ + if (ioctl(0, TIOCSLTC, &moresigc2)) /* put it back, modified */ + perror("ioctl3"); +#endif +} + + +/* Setcooked: Set terminal to cooked mode */ +void +setcooked() +{ + if (ioctl(0, TIOCSETP, &tbuf)) + perror("ioctl1 sd"); + if (ioctl(0, TIOCSETC, &sbuf)) + perror("ioctl2 sd"); +#ifdef USES_MORESIG + if (ioctl(0, TIOCSLTC, &moresigc)) /* set ltchars struct to be default */ + perror("ioctl3 in setcooked"); +#endif +} + +#endif /* USES_SGTTY */ + + +/* gettstring: Given the name of a termcap string, gets the string + * and places it into loc. Returns 1 if ok, 0 if no string. + * If no string, loc[0] is set to EOS. + */ +#ifdef PROTO +static bool gettstring(char *name, char **loc) +#else +static bool +gettstring(name, loc) + char *name, **loc; +#endif +{ + char bp[50], *area = bp; + + if (tgetstr(name, &area) != NULL) + { + area = bp; + while (isdigit(*area)) + area++; /* Skip time delay chars */ + *loc = (char *) malloc((unsigned) strlen(area) + 2); + if (!(*loc)) + return (FALSE); + strcpy(*loc, area); + return (TRUE); + } + else + *loc = ""; + return (FALSE); +} + + +/* Terminal is called at the beginning of Wish to get the termcap + * strings needed by the Command Line Editor. If they are not got, + * or the wrong ones are found, the CLE will act strangely. Wish + * should at this stage default to a dumber CLE. + */ +void +terminal() +{ + extern int beeplength; + char *t, term[20]; + +/* set up cursor control sequences from termcap */ + + t=EVget("TERM"); + if (!t) fatal("No termcap entry available"); + strncpy(term, t, 10); + termcapbuf = (char *) malloc(2048); + if (!termcapbuf) + return; + tgetent(termcapbuf, term); + if (tgetflag("bs") == 1) + bs = "\b"; + else + gettstring("bc", &bs); + if ((wid = tgetnum("co")) == -1) + wid = 80; + wid--; /* this is to eliminate unwanted auto newlines */ + gettstring("cl", &cl); + gettstring("cd", &cd); + gettstring("nd", &nd); + gettstring("up", &up); + gettstring("so", &so); + gettstring("se", &se); + gettstring("bl", &wbeep); + if (*wbeep == EOS) + + wbeep = "\007"; + beeplength = strlen(wbeep); +#ifdef DEBUG + printctrl("bs", bs); + printctrl("cl", cl); + printctrl("cd", cd); + printctrl("nd", nd); + printctrl("up", up); + printctrl("so", so); + printctrl("se", se); + printctrl("beep", wbeep); +#endif + free(termcapbuf); +} diff --git a/Applications/wish/ucbjob.c b/Applications/wish/ucbjob.c new file mode 100644 index 0000000000..81f419a446 --- /dev/null +++ b/Applications/wish/ucbjob.c @@ -0,0 +1,160 @@ +/* 4.3BSD Job Control functions + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + * + */ + +#define WIFCORE(x) ((x).w_coredump) +#ifndef WEXITSTATUS +# define WEXITSTATUS(x) ((x).w_retcode) +#endif +#ifndef WTERMSIG +# define WTERMSIG(x) ((x).w_termsig) +#endif +#ifndef WSTOPSIG +# define WSTOPSIG(x) ((x).w_stopsig) +#endif +#define WRUNFG(x) ((x).w_status == RUNFG) +#define WRUNBG(x) ((x).w_status == RUNBG) + +/* The following tables are all VERY OS-dependent, especially on POSIX + * systems. If you find the shell giving you weird signal messages, you + * should change the names of the signals below. + */ + +static char *siglist[] = { + "", "Hangup", "Interrupt", "Quit", "Illegal Instruction", + "Trace/BPT Trap", "IOT Trap", "EMT Trap", "Floating Point Exception", + "Killed", "Bus Error", "Segmentation Violation", "Bad System Call", + "Broken Pipe", "Alarm", "Terminated" ,"Urgent Socket Condition", + "Stopped (signal)", "Stopped", "Continue", "Child Status Change", + "(tty input)", "(tty output)", "I/O", "Cpu Time Limit", + "File Size Limit", "Virtual Time Alarm", "Profile Alarm", + "Window Change", "Resource Lost", "User Signal 1", "User Signal 2" +}; + +char *signame[] = { + "", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", + "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM" ,"URG", "STOP", "TSTP", + "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU", "XFSZ", "VTALRM", "PROF", + "WINCH", "LIST", "USR1", "USR2", NULL +}; + +/* Waitfor is called to wait for a requested job to stop/die. It receives the + * new status of any children. If it's the requested job, we return. Pid holds + * the process-id of the job we are after; if it is 0, we return when no + * processes are left waiting to report a change in state. + * + * If pid is negative, we are a subshell and _must_ return only when the pid + * dies. We set the subshell flag and make pid positive again. Then, if we + * find out that the job was stopped, we wake it up. This works because we + * are put to sleep at the same time as the child, and the original shell + * fg()s _us_. + */ +void +waitfor(pid) + int pid; +{ + struct job *thisjob; + int wpid; + bool subshell=FALSE; + + union wait status; + + int waitflags; + waitflags = (pid == 0) ? WNOHANG | WUNTRACED : WUNTRACED; + +#ifdef DEBUG + fprints(2,"In waitfor\n"); +#endif + + if (pid<0) { pid= -pid; subshell= TRUE; } + while (1) + { + wpid = wait3(&status, waitflags, NULL); + +#ifdef DEBUG + fprints(2,"waitxx() returned\n"); +#endif + if (wpid == -1 || wpid == 0) + break; + + if (subshell && WIFSTOPPED(status)) /* We can't return yet */ + { + kill(wpid, SIGCONT); /* Wake the child up first - waah! */ + continue; + } + thisjob = findjob(wpid); + if (thisjob == NULL) + continue; + thisjob->status = status; + thisjob->changed = TRUE; + if (pid == wpid) + return; + } +} + +/* Stopjob is only called when we received a SIGTSTP. If we are wait3()ing + * a pid, we send a SIGSTOP to that pid. This should then cause wait3() + * to exit, and thus waitfor() will return. + */ +SIGTYPE +stopjob() +{ + signal(SIGTSTP, stopjob); +#ifdef DEBUG + fprints(2,"In stopjobs\n"); +#endif + if (currentjob) + { +#ifdef DEBUG + fprints(2,"About to stop pid %d\n", currentjob->pid); +#endif + kill(currentjob->pid, SIGSTOP); + } +} + + +static void +bgstuff(pid) + int pid; +{ + struct job *ptr; + + setpgrp(pid, pid); +# ifdef DEBUG + fprints(2,"About to SIGCONT %d\n", pid); +# endif + kill(pid, SIGCONT); + ptr = findjob(pid); + if (ptr) + { ptr->STATUS = RUNBG; currentjob= ptr; } +} + + +static int +fgstuff(pid) + int pid; +{ + int pgrp; + +/* Under UCBJOB and POSIXJOB, we don't try & bring the pid into our + * pgrp, that doesn't work. Instead, we move the terminal + * over to that pgrp, and move ourselves to that pgrp as well. + */ + pgrp = getpgrp(pid); /* Determine if job's pgrp != ours */ + if (pgrp == pid) + { + if (ioctl(0, TIOCSPGRP, &pgrp) == -1) /* Move the terminal to that pgrp */ + { + perror("fg setpgrp ioctl"); + return (1); + } + if (setpgrp(getpid(), pgrp) == -1) /* Set shell's process group to the pid's */ + { + perror("fg setpgrp"); + return (1); + } + } + return(0); +} diff --git a/Applications/wish/v7job.c b/Applications/wish/v7job.c new file mode 100644 index 0000000000..635f49e18a --- /dev/null +++ b/Applications/wish/v7job.c @@ -0,0 +1,162 @@ +/* 7th Edition Job Control functions + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + * + */ +#ifndef WIFSTOPPED +#define WIFSTOPPED(x) (((x) & 0xFF) == 0x7F) /* As per my Version7 manual */ +#define WIFSIGNALED(x) ((x) & 0xFF) +#define WIFEXITED(x) (((x) & 0xFF) == 0) +#define WEXITSTATUS(x) ((x) >> 8) +#define WTERMSIG(x) ((x) & 0xFF) +#define WSTOPSIG(x) ((x) >> 8) +#endif +#define WIFCORE(x) ((x) & 0x80) +#define WRUNFG(x) ((x) == RUNFG) +#define WRUNBG(x) ((x) == RUNBG) + +/* The following tables are all VERY OS-dependent, especially on POSIX + * systems. If you find the shell giving you weird signal messages, you + * should change the names of the signals below. + */ +static char *siglist[] = { + "", "Hangup", "Interrupt", "Quit", "Illegal Instruction", + "Trace/BPT Trap", "IOT Trap", "EMT Trap", "Floating Point Exception", + "Killed", "Bus Error", "Segmentation Violation", "Bad System Call", + "Broken Pipe", "Alarm", "Terminated", "User Signal 1", "User Signal 2", + "Child Death", "Power Failure" +}; + +char *signame[] = { + "", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", + "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM", "USR1", "USR2", "CLD", + "PWR", NULL +}; + + +/* Waitfor is called to wait for a requested job to stop/die. It receives the + * new status of any children. If it's the requested job, we return. Pid holds + * the process-id of the job we are after; if it is 0, we return when no + * processes are left waiting to report a change in state. + * + * If pid is negative, we are a subshell and _must_ return only when the pid + * dies. We set the subshell flag and make pid positive again. Then, if we + * find out that the job was stopped, we wake it up. This works because we + * are put to sleep at the same time as the child, and the original shell + * fg()s _us_. + */ +void +waitfor(pid) + int pid; +{ + struct job *thisjob; + int wpid; + bool subshell=FALSE; + int stopsig; + + int status; + +#ifdef DEBUG + fprints(2,"In waitfor\n"); +#endif + + if (pid<0) { pid= -pid; subshell= TRUE; } + while (1) + { + if (pid == 0) + return; + wpid = wait(&status); + +#ifdef DEBUG + fprints(2,"waitxx() returned with %x\n",status); +#endif + if (wpid == -1 || wpid == 0) + break; + + thisjob = findjob(wpid); + if (thisjob == NULL) + continue; + +/* Version 7 job control is quite interesting. Since we don't have a ^Z + * key, we use the ^\ key instead. Luckily this is caught by the parent + * and stops the process. When any signal is caught by the child, it + * stops. We must arrange to stop it on SIGQUIT (^\), and restart it on + * any other signal. + */ + if (WIFSTOPPED(status)) + { + stopsig= WSTOPSIG(status); +#ifdef DEBUG + fprints(2,"Stopped status %x, stopsig %d (%s)\n", + status,stopsig,signame[stopsig]); +#endif + if (stopsig!=SIGQUIT) + { + if (stopsig== SIGTRAP) + { +#ifdef DEBUG + fprints(2,"About to ptrace(7,%d,1,0)\n",wpid); +#endif + ptrace(7,wpid,(PLONG)1,(PLONG)0); /* Keep it going after exec */ + } + else + { +#ifdef DEBUG + fprints(2,"About to ptrace(7,%d,1,stopsig)\n",wpid); +#endif + ptrace(7,wpid,(PLONG)1,(PLONG)stopsig); /* Deliver the signal */ + } + continue; + } + } /* SIGQUIT falls out to below where job is marked stopped */ + + thisjob->status = status; + thisjob->changed = TRUE; + if (pid == wpid) + return; + } +} + +#ifdef PROTO +static void bgstuff(int pid) +#else +static void +bgstuff(pid) + int pid; +#endif +{ + struct job *ptr; + +#ifdef DEBUG + fprints(2,"About to ptrace(7,%d,1,0)\n", pid); +#endif + ptrace(7,pid,(PLONG)1,(PLONG)0); /* Start it up again */ + if (ptr) + { ptr->STATUS = RUNBG; currentjob= ptr; } +} + +#ifdef PROTO +static int fgstuff(int pid) +#else +static int +fgstuff(pid) + int pid; +#endif +{ + struct job *ptr; + + ptr=findjob(pid); + +/* We need to wake up stopped jobs before we can wait on them */ + if ((ptr) && WIFSTOPPED(ptr->status)) + { +#ifdef DEBUG + fprints(2,"About to ptrace(7,%d,1,0)\n", pid); +#endif + ptrace(7,pid,(PLONG)1,(PLONG)0); /* Start it up again */ + } + + ptr->STATUS = RUNFG; + currentjob = ptr; + return (0); +} diff --git a/Applications/wish/val.c b/Applications/wish/val.c new file mode 100644 index 0000000000..ca55f1baad --- /dev/null +++ b/Applications/wish/val.c @@ -0,0 +1,134 @@ +/* Functions dealing with lists of values + * + * $Revision: 41.1 $ $Date: 1995/12/29 02:10:46 $ + */ + +#include "header.h" + +void +appendval(l, v) + struct vallist *l; + struct val *v; +{ + v->next = NULL; + if (l->head == NULL) l->head = l->tail = v; + else + { l->tail->next = v; l->tail = v; } +} + + + +/* Searchval searches through a val list for a given val. + * When a position is found, searchval operates according + * to the mode value: if true, return the val, else delete + * the val. If sub is true, substring matches are used. + */ + +struct val * +searchval(l, name, mode, sub) + struct vallist *l; + char *name; + int mode; + bool sub; +{ + struct val *last, *this; + int len; + + switch (sub) + { + case TRUE: + len = strlen(name); + for (last = this = l->head; this; last = this, this = this->next) + if (!strncmp(this->name, name, len)) + break; + break; + case FALSE: + for (last = this = l->head; this; last = this, this = this->next) + if (!strcmp(this->name, name)) + break; + break; + default: + fatal("Bad sub value in searchval"); + } + if (this == NULL) return (NULL); + + switch (mode) + { + case FALSE: + if (l->head == l->tail) + { l->head = l->tail = NULL; return (this); } + + if (this == l->head) l->head = this->next; + else last->next = this->next; + + if (this == l->tail) l->tail = last; + this->next = NULL; + case TRUE: + return (this); + default: + fatal("Bad mode value in searchval"); + } +} + +/* Save the value into the list in the correct order + */ +void +saveval(l, v) + struct vallist *l; + struct val *v; +{ + struct val *last, *this; + + if (l->head == NULL) + { l->head = l->tail = v; v->next = NULL; return; } + + for (last = this = l->head; this; last = this, this = this->next) + { if (!strcmp(this->name, v->name)) + { free(this->val); free(v->name); /* Overwrite the old val */ + this->val = v->val; this->exported = v->exported; + free(v); return; + } + if (strcmp(this->name, v->name) > 0) break; + } + + if (this == NULL) + { l->tail->next = v; v->next = NULL; + l->tail = v; return; + } + + if (this == last) + { v->next = l->head; l->head = v; return; } + + v->next = this; last->next = v; +} + +struct val * +pullval(l) + struct vallist *l; +{ + struct val *temp; + + if (l->head == NULL) return (NULL); + + temp = l->head; + if (l->head == l->tail) + l->head = l->tail = NULL; + else + l->head = l->head->next; + temp->next = NULL; + return (temp); +} + +void +setval(name, val, l) + char *name, *val; + struct vallist *l; +{ + struct val *v; + + v = (struct val *) Malloc(sizeof(struct val), "setval val malloc"); + v->name = Malloc(strlen(name) + 1, "setval name malloc"); + v->val = Malloc(strlen(val) + 1, "setval val malloc"); + strcpy(v->name, name); strcpy(v->val, val); + v->exported = FALSE; saveval(l, v); +} diff --git a/Applications/wish/var.c b/Applications/wish/var.c new file mode 100644 index 0000000000..142619d94a --- /dev/null +++ b/Applications/wish/var.c @@ -0,0 +1,201 @@ +/* Variables + * + * $Revision: 41.2 $ $Date: 1996/06/14 06:24:54 $ + */ + +#include "header.h" + +struct vallist vlist= /* The linked list of vars */ + { NULL, NULL}; + +char * +EVget(name) /* Get value of variable */ + char *name; +{ + struct val *v; + + if ((v = searchval(&vlist, name, TRUE, FALSE)) == NULL || v->name == NULL) + return (NULL); + return (v->val); +} + + +bool +EVinit() /* Initialise symtable from environment */ +{ + extern char **environ; + int i; + char c, *name, *val; + struct val *v; + + for (i = 0; environ[i] != NULL; i++) + { + name = environ[i]; + val = strchr(name, '='); + c = *val; + *(val++) = '\0'; + v= (struct val *)Malloc(sizeof(struct val), "EVinit val malloc"); + v->name= Malloc(strlen(name)+1, "EVinit name malloc"); + v->val= Malloc(strlen(val)+1, "EVinit val malloc"); + strcpy(v->name, name); strcpy(v->val, val); + v->exported=TRUE; + saveval(&vlist, v); + *(--val) = c; + } + return (TRUE); +} + +#ifndef NO_VAR +bool +EVupdate() /* Build envp from symbol table */ +{ + extern char **environ; + int i, envi, nvlen; + struct val *v; + static bool updated = FALSE; + + for (i = 0, v = vlist.head; v != NULL; v = v->next) + i += v->exported; + + if (!updated) + if ((environ = (char **) malloc((i + 1) * sizeof(char *))) == NULL) + return (FALSE); + envi = 0; + for (v = vlist.head; v != NULL; v = v->next) + { + if (v->name == NULL || !v->exported) + continue; + nvlen = strlen(v->name) + strlen(v->val) + 2; + if (!updated) + { + if ((environ[envi] = (char *) malloc(nvlen)) == NULL) + return (FALSE); + } + else if ((environ[envi] = (char *) realloc(environ[envi], nvlen)) == NULL) + return (FALSE); + sprints(environ[envi], "%s=%s", v->name, v->val); + envi++; + } + environ[envi] = NULL; + updated = TRUE; + return (TRUE); +} + +int +export(argc, argv) /* Export command */ + int argc; + char *argv[]; +{ + int i; + struct val *v; + + if (argc < 2) + { + prints("Usage: export var [var] ...\n"); + return (1); + } + for (i = 1; i < argc; i++) + { + v = searchval(&vlist, argv[i], TRUE, FALSE); /* Setenv */ + if (v == NULL) + prints("No such variable: %s\n", argv[i]); + else + v->exported = TRUE; + } + return (0); +} + +int +shift(argc, argv) + int argc; + char *argv[]; +{ + extern int Argc; + extern char **Argv; + int i = 1; + + if (argc > 2) + { + prints("Usage: shift [val]\n"); + return (1); + } + if (argc == 2) + i = atoi(argv[1]); + if (i > Argc) + { + prints("Not enough vars to shift\n"); + return (1); + } + Argv += i; + Argc -= i; + return (0); +} + +int +unset(argc, argv) + int argc; + char *argv[]; +{ + int i, j; + struct val *v; + + j = strcmp(argv[0], "unexport"); + if (argc < 2) + { + prints("Usage: %s [var] [var] ...\n", argv[0]); + return (1); + } + + for (i = 1; i < argc; i++) + switch (j) + { + case 0: + v = searchval(&vlist, argv[i], TRUE, FALSE); /* Unsetenv */ + if (v == NULL) + prints("No such variable: %s\n", argv[i]); + else + v->exported = FALSE; + break; + default: + if (!searchval(&vlist, argv[i], FALSE, FALSE)) + prints("No such variable: %s\n", argv[i]); + } + return (0); +} + +int +set(argc, argv) + int argc; + char *argv[]; +{ + struct val *v; + + if ((argc !=1) && (argc!=3)) + { prints("Usage: %s variable value, or %s\n", argv[0], argv[0]); return (1); } + + if (argc==3) + { setval(argv[1], argv[2], &vlist); + if (!strcmp(argv[0], "setenv")) + { + v = searchval(&vlist, argv[1], TRUE, FALSE); /* Setenv */ + if (v != NULL) v->exported = TRUE; + } + } + else + { + if (!strcmp(argv[0], "setenv")) + { + for (v = vlist.head; v != NULL; v = v->next) + if (v->name != NULL && v->exported) + prints("%s=%s\n", v->name, v->val); + } + else + { + for (v = vlist.head; v != NULL; v = v->next) + if (v->name != NULL) + prints("%s=%s\n", v->name, v->val); + } + } + return (0); +} +#endif /* NO_VAR */ diff --git a/Applications/wish/wish b/Applications/wish/wish new file mode 100644 index 0000000000000000000000000000000000000000..afb8aa402583f2aabdf70ad9efa7a51d68eb5196 GIT binary patch literal 46868 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�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

!@3&DgscbGMEyV9c+pQHLh5 z{p(H;`)fCRAokY-7{m_j!CsKZ*N1ToC-6Pa;5;tkDz0M$cW@8)@fc6>94~q9Odt}` zh($aSk&IL*NJkcOkdHzvMhVJLfojyD0ZnK@8#>{D6K?pRqYnevib3qaPVB*69KazQ l!x@~%MO?>CjNlIL;yGUOUT=X&L_pFz+V6W literal 0 HcmV?d00001 From 763a6eef800873f962ca138fea16937d335898dc Mon Sep 17 00:00:00 2001 From: Warren Toomey Date: Wed, 22 Jan 2025 14:52:23 +1000 Subject: [PATCH 2/2] Applications/wish/wish: Oops, removed the binary. --- Applications/wish/wish | Bin 46868 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Applications/wish/wish diff --git a/Applications/wish/wish b/Applications/wish/wish deleted file mode 100644 index afb8aa402583f2aabdf70ad9efa7a51d68eb5196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46868 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�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

!@3&DgscbGMEyV9c+pQHLh5 z{p(H;`)fCRAokY-7{m_j!CsKZ*N1ToC-6Pa;5;tkDz0M$cW@8)@fc6>94~q9Odt}` zh($aSk&IL*NJkcOkdHzvMhVJLfojyD0ZnK@8#>{D6K?pRqYnevib3qaPVB*69KazQ l!x@~%MO?>CjNlIL;yGUOUT=X&L_pFz+V6W