commit 6bb38f6a01dbf4bbd584e038ceace0a00faa27bb
Author: Byron Rakitzis <byron@archone.tamu.edu>
Date: Wed, 27 May 1992 00:00:01 +0100
release: rc-1.4
Diffstat:
A | .gitignore | | | 0 | |
A | CHANGES | | | 171 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | COPYRIGHT | | | 26 | ++++++++++++++++++++++++++ |
A | EXAMPLES | | | 749 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | Makefile | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README | | | 100 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | addon.c | | | 22 | ++++++++++++++++++++++ |
A | addon.h | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
A | builtins.c | | | 532 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.h-dist | | | 204 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cpp | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
A | except.c | | | 140 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | exec.c | | | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | execve.c | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | fn.c | | | 266 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | footobar.c | | | 368 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | getopt.c | | | 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | glob.c | | | 240 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | glom.c | | | 420 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | hash.c | | | 302 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | heredoc.c | | | 156 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | history/Makefile | | | 6 | ++++++ |
A | history/history.1 | | | 234 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | history/history.c | | | 333 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | input.c | | | 373 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | jbwrap.h | | | 10 | ++++++++++ |
A | lex.c | | | 398 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | list.c | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 115 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | match.c | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | mksignal | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | nalloc.c | | | 137 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | open.c | | | 29 | +++++++++++++++++++++++++++++ |
A | parse.y | | | 174 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | print.c | | | 456 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | proto.h | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | rc.1 | | | 1880 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | rc.h | | | 380 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | redir.c | | | 77 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | signal.c | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | status.c | | | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | tree.c | | | 172 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | trip.rc | | | 0 | |
A | utils.c | | | 125 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | var.c | | | 226 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | version.c | | | 1 | + |
A | wait.c | | | 124 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | walk.c | | | 344 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | which.c | | | 114 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
49 files changed, 10301 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
diff --git a/CHANGES b/CHANGES
@@ -0,0 +1,171 @@
+Changes since 1.2: (Too many to count!)
+
+A memory stomping bug was fixed (provoked by assigning a variable
+to its old value plus something else).
+
+Better signal handling; a real signal handler which manages a queue
+of pending signals was added.
+
+rc now ignores SIGQUIT and traps SIGINT even in non-interactive
+mode. Thus,
+
+ rc ed.sh
+
+will not do mysterious things if the shell script "ed.sh" calls a
+command like "ed" and then you hit ^C.
+
+rc now opens 0, 1, 2 on /dev/null if they are inherited closed.
+rc -o prevents this.
+
+A couple of stupid O(n^2) string appends were replaced with O(n)
+loops. This should make foo=`{cat /etc/termcap} bar=$^foo a little
+faster :-)
+
+Returning a list of signals from a function is now legal, so "return
+$status" should always work.
+
+The code has been revised, new printing routines have been added.
+
+rc no longer uses redundant braces when exporting functions.
+
+A first stab at a verification suite has been added (trip.rc).
+(someone, please help me make this comprehensive!)
+
+rc -p now does not initialize functions from the environment. This
+should make it easier to write shell scripts that don't need to
+assume anything about the environment.
+
+Inherited ignored signals are now ignored in the current shell and
+passed on ignored to the child processes. whatis -s also reflects
+this information.
+
+A file descriptor leak in the /dev/fd implementation of >{} was
+fixed.
+
+A variable set to '' was not imported from the environment; this
+has been fixed.
+
+Changes since 1.3beta:
+
+New Makefile/config.h setup.
+
+builtin echo may now be conditionally included out, to use a Goldwynism.
+
+builtin exit takes any legal exit status. If the status is not all zeros,
+rc exits with 1. (having "exit sigiot" produce a core dump would be going
+a little far, I think.)
+
+limit does not append a unit after a zero limit; 0g was too confusing.
+
+exec > /nonexistentfile does not cause rc to exit any more.
+
+If a noninteractive rc is started with sigint ignored, rc does not install
+its own signal handler.
+
+error messages produced by rc in a subshell were cleaned up. (rc erroneously
+reset the 'interactive' flag after a fork)
+
+print.c was cleaned up a little; no functionality was changed, but should
+be more portable now.
+
+a bug in rc-1.3beta (not previous versions) was fixed: setting the first
+element of $path to '' caused PATH to be exported as '':etc..
+
+getopt's "illegal option" message was gratuitously changed to something
+less abrupt.
+
+some dead code was removed from input.c
+
+%term was changed to %token in parse.y; apparently newer yacc's don't grok
+%term any more.
+
+a race condition in the signal handler was fixed.
+
+the variable in for() was getting evaluated each time through the loop
+(e.g., for (`{echo i;date>[1=2]} in 1 2 3)echo $i would print the date
+three times). This was cleaned up.
+
+a redundant fork() was removed from walk.c; this showed up when running
+a braced command with a redirection in the background. e.g., {a;b}>c&
+
+man pages for history and rc were cleaned up by david (thanks).
+
+rc set SIGQUIT and SIGTERM to SIG_DFL on background jobs---even when
+trying to do old-style backgrounding (i.e., don't use process groups,
+just ignore SIGINT & SIGQUIT & SIGTERM).
+
+$0 is now changed to the name of the signal when entering a signal
+handler. Thus it's possible to write code like
+
+ fn sigint sigterm sigquit {
+ switch ($0) {
+ case sigint
+ ...
+ case sigterm
+ ...
+
+wait with no arguments now prints the pid of any and all children
+that died with a signal. e.g.,
+
+ ; wait
+ 25321: terminated
+ 25325: terminated
+
+as opposed to
+
+ ; wait
+ terminated
+
+An error saving/restoring state in the input stream code would
+cause rc to exit with the (erroneous) command:
+
+ eval '|[a'
+
+FIFO's were not removed in a backgrounded command, e.g.,
+
+ cat <{echo hi}&
+
+Changes since rc-1.4beta:
+
+getopt was renamed to rc_getopt to avoid libc collisions.
+
+$cdpath with a / in it caused a cd to sometimes have two //'s at the
+front of the path. This is reserved by POSIX, so I changed it to skip
+one of the /'s.
+
+signal handling now emulates sh in the way I described in a previous
+message: the race condition present in older rc's whereby some SIGINTs
+got lost is now gone; any SIGINT received during a wait() is acted upon
+at the end of the wait(), unless of course SIGINT is being deliberately
+ignored.
+
+getopt was renamed to avoid naming conflicts with libc. Also a sound
+move since rc_getopt is no longer quite libc-getopt compatible; I had
+to add in a mechanism for resetting getopt.
+
+signal handler code in fn.c was cleaned up; there were several bugs
+in rc-1.4beta, notably the shell sometimes spawned background jobs
+with SIGTERM ignored. I took the opportunity to make things a little
+cleaner here.
+
+a quasi-memory leak in the code for while() was fixed: a long-running
+while that had rc commands allocating memory in it could cause the
+shell to grow without bounds. I fixed this by placing the while loop
+(*and* test!) inside a new allocation arena each time through the loop.
+
+A new configuration parameter, NOJOB, was added to allow you to force
+v7-style backgrounding (no setpgrp, ignore SIGINT and SIGTERM).
+
+The FIFO code was reworked a little. It should be more robust now---
+FIFOs get removed at the end of the command of the argument list
+that they were on:
+
+ fn foo {echo $*; cat $*}
+ foo<{echo hi}
+
+now works as expected. Also FIFO names are pushed onto the exception
+stack so that their removal occurs in the face of exceptions and so
+on.
+
+A memory leak in treefree() was plugged up --- the root node of a
+function was not getting freed.
diff --git a/COPYRIGHT b/COPYRIGHT
@@ -0,0 +1,26 @@
+/*
+ * Copyright 1991 Byron Rakitzis. All rights reserved.
+ *
+ * This software is not subject to any license of the American Telephone
+ * and Telegraph Company or of the Regents of the University of California.
+ *
+ * Permission is granted to anyone to use this software for any purpose on
+ * any computer system, and to alter it and redistribute it freely, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ * software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ * explicit claim or by omission. Since few users ever read sources,
+ * credits must appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software. Since few users
+ * ever read sources, credits must appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ *
+ * [this copyright notice is adapted from Henry Spencer's
+ * "awf" copyright notice.]
+ */
diff --git a/EXAMPLES b/EXAMPLES
@@ -0,0 +1,749 @@
+There is no repository for useful rc code snippets as yet, so I'm including
+a (short) file in the distribution with some helpful/intriguing pieces of
+rc code.
+
+A sample .rcrc
+--------------
+Here is the .rcrc I use on archone:
+
+umask 022
+path=(/bin /usr/bin /usr/ucb)
+ht=`/usr/arch/bin/hosttype
+h=$home
+history=$h/.history
+bin=$h/bin/$ht
+lib=$h/lib/$ht
+sh=$h/bin/sh
+include=$h/lib/include
+
+switch ($ht) {
+case sun*
+ OBERON='. '$h/other/oberon
+ p=/usr/ucb
+ compiler='gcc -Wall -O -g'
+ MANPATH=$h/man:/usr/arch/man:/usr/man
+ if (! ~ $TERM ()) {
+ stty dec
+ /usr/arch/bin/msgs -q
+ }
+case next
+ p=(/usr/ucb /usr/bin /NextApps)
+ compiler='cc -Wall -O -g -DNODIRENT'
+ MANPATH=$h/man:/usr/arch/man:/usr/man
+ if (! ~ $TERM ())
+ stty dec
+case sgi
+ p=(/usr/ucb /usr/sbin /usr/bin)
+ compiler='gcc -Wall -O -g -DNOSIGCLD'
+ MANPATH=$h/man:/usr/arch/man:/usr/catman
+ if (!{~ $TERM () || ~ $TERM *iris*})
+ stty line 1 intr '' erase '' kill ''
+case *
+ echo .rcrc not configured for this machine
+}
+
+path=(. $sh $bin /usr/arch/bin $p /bin /usr/bin/X11 /etc /usr/etc)
+cdpath=(. .. $h $h/src $h/misc $h/other $h/adm)
+RNINIT=-d$h' -t -M -2400-h -2400+hfrom'; DOTDIR=$h/misc/news
+PRINTER=lw
+
+fn s {
+ echo $status
+}
+fn cd {
+ builtin cd $1 && \
+ switch ($1) {
+ case ()
+ dir=$home
+ case *
+ dir=()
+ }
+}
+fn pwd {
+ if (~ $dir ())
+ dir=`/bin/pwd
+ echo $dir
+}
+fn x {
+ if (~ `tty /dev/console)
+ clear_colormap
+ clear
+ exit
+}
+fn p {
+ if (~ $history ()) {
+ echo '$history not set' >[1=2]
+ return 1
+ }
+
+ if (! ~ $#* 0 1 2) {
+ echo usage: $0 '[egrep pattern] [sed command]' >[1=2]
+ return 1
+ }
+
+ command=`{
+ egrep -v '^[ ]*p([ ]+|$)' $history | switch ($#*) {
+ case 0
+ cat
+ case 1
+ egrep $1
+ case 2
+ egrep $1 | sed $2
+ } | tail -1
+ }
+
+ echo $command
+ eval $command
+}
+
+if (~ $TERM dialup network) {
+ TERM=vt100
+ biff y
+}
+
+A front-end to NeXT's "openfile"
+--------------------------------
+
+Named after the sam "B" command for opening a file, this script was written
+by Paul Haahr. (Assumes the "pick" command from Kernighan and Pike is also
+in your path.)
+
+#!/bin/rc
+if (~ $#* 0)
+ exec openfile
+create = ()
+files = ()
+for (i in $*)
+ if (test -f $i) {
+ files = ($files $i)
+ } else {
+ create = ($create $i)
+ }
+create = `{ pick $create }
+files = ($files $create)
+for (i in $create)
+ > $i
+if (! ~ $#files 0)
+ openfile $files
+
+A read function
+---------------
+
+Unlike sh, rc doesn't have a read. This clever alternative returns an
+exit status as well as fetch a variable. Use as
+
+ read foo
+
+to set $foo to a single line from the terminal.
+
+(due to John Mackin <john@syd.dit.csiro.au>)
+
+fn read {
+ x=() {
+ x = `` ($nl) { awk '{print; print 0; exit}' ^ $nl ^ \
+ 'END {print 1; print 1}' }
+ $1 = $x(1)
+ return $x(2)
+ }
+}
+
+From cs.wisc.edu!dws Fri Aug 2 18:16:14 1991
+
+#-------
+# ls front end
+#-------
+fn ls \
+{
+ test -t 1 && * = (-FCb $*)
+ builtin ls $*
+}
+#-------
+# nl - holds a newline, useful in certain command substitutions
+#-------
+nl='
+'
+#-------
+# show - tell me about a name
+#
+# Runs possibly dangerous things through cat -v in order to protect
+# me from the effects of control characters I might have in the
+# environment.
+#-------
+fn show \
+{
+ * = `` $nl {whatis -- $*}
+ for(itis)
+ {
+ switch($^itis)
+ {
+ case 'fn '* ; echo $itis | cat -v -t
+ case builtin* ; echo $itis
+ case /* ; file $itis; ls -ld $itis
+ case *'='* ; echo $itis | cat -v -t
+ case * ; echo $itis: UNKNOWN: update show
+ }
+ }
+ itis = ()
+}
+#-------
+# Tell me automatically when a command has a nonzero status.
+#-------
+fn prompt \
+{
+ Status = $status
+ ~ $Status 0 || echo '[status '$Status']'
+}
+
+#-------
+# chop - echo the given list, less its final member
+#
+# e.g. chop (a b c) -> (a b)
+#-------
+fn chop {
+ ~ $#* 0 1 && return 0
+ ans = '' { # local variable
+ ans = ()
+ while(! ~ $#* 1)
+ {
+ ans = ($ans $1)
+ shift
+ }
+ echo $ans
+ }
+}
+
+From arnold@audiofax.com Thu May 30 08:49:51 1991
+
+# cd.rc --- souped up version of cd
+
+# this is designed to emulate the fancy version of cd in ksh,
+# so if you're a purist, feel free to gag
+
+_cwd=$home
+_oldcwd=$home
+
+fn cd {
+ if (~ $#* 0) {
+ if (~ $_cwd $home) { # do nothing
+ } else {
+ builtin cd && { _oldcwd=$_cwd ; _cwd=$home }
+ }
+ } else if (~ $#* 1) {
+ if (~ $1 -) {
+ _t=$_cwd
+ builtin cd $_oldcwd && {
+ _cwd=$_oldcwd
+ _oldcwd=$_t
+ echo $_cwd
+ }
+ _t=()
+ } else {
+ # if a cd happens through the cdpath, rc echos
+ # the directory on its own. all we have to do
+ # is track where we end up
+ _dopwd = 1
+ { ~ $1 /* } && _dopwd = 0 # absolute path
+ builtin cd $1 && {
+ _oldcwd=$_cwd
+ _cwd=$1
+ { ~ $_dopwd 1 } && _cwd=`/bin/pwd
+ }
+ _dopwd=()
+ }
+ } else if (~ $#* 2) {
+ _t=`{ echo $_cwd | sed 's<'$1'<'$2'<' }
+ builtin cd $_t && {
+ _oldcwd=$_cwd
+ _cwd=$_t
+ echo $_cwd
+ }
+ _t=()
+ } else {
+ echo cd: takes 0, 1, or 2 arguments >[1=2]
+ builtin cd $1 && { _oldcwd=$_cwd ; _cwd=`/bin/pwd ; echo $_cwd }
+ }
+}
+
+fn pwd { echo $_cwd }
+
+From vlsi.cs.caltech.edu!drazen Tue Jan 21 16:03:14 1992
+
+# A kill builtin.
+
+#ifdef B_KILL
+#include <ctype.h>
+static void b_kill(char **av)
+{
+ int signal = SIGTERM;
+ int n = 1;
+ pid_t pid;
+ boolean res;
+
+ if (!av[1]) {
+ set(TRUE);
+ return;
+ }
+#undef STRCMP
+#define STRCMP strcmp
+ if ( '-' == av[1][0]) {
+ char *p = 1+av[1];
+ if (0 == strcmp(av[1], "-l")){
+ int r; const int nsig = NUMOFSIGNALS;
+ const int C = 4, R = 1 + (int)((nsig-2)/C);
+ for (r=1; r<=R; r++){
+ int j;
+ for (j=r; j<nsig; j+=R){
+ fprint(1, "%s%d. %s\t", j<10?" ":"", j, signals[j][0]);
+ }
+ fprint(1,"\n");
+ }
+ set(TRUE);
+ return;
+ }
+ n++;
+ if ( (signal=a2u(p)) < 0){
+ int i;
+ for (i = 1; i < NUMOFSIGNALS; i++){
+ char UPPER[31], *u=UPPER, *q;
+ for (q=signals[i][0]; *q; q++, u++) *u = toupper(*q);
+ *u = '\0';
+
+ if (*signals[i][0] &&
+ (!STRCMP(signals[i][0], p) || !STRCMP(3+signals[i][0],p)
+ || !STRCMP(UPPER, p) || !STRCMP(3+UPPER, p) ) )
+ {
+ signal = i;
+ break;
+ }
+ }
+ if (signal < 0){
+ fprint(2,"kill: bad signal %s\n", av[1]);
+ set(FALSE);
+ return;
+ }
+ }
+ }
+#undef STRCMP
+
+ for (res=TRUE; av[n]; n++){
+ if( (pid = (pid_t) a2u(av[n])) < 0){
+ fprint(2, "kill: bad process number %s\n", av[n]);
+ res = FALSE;
+ continue;
+ }
+ if (kill(pid,signal)){
+ uerror("kill");
+ res = FALSE;
+ continue;
+ }
+ }
+ set(res);
+}
+#endif
+From acc.stolaf.edu!quanstro Thu Apr 2 02:51:10 1992
+Received: from thor.acc.stolaf.edu ([130.71.192.1]) by archone.tamu.edu with SMTP id <45339>; Thu, 2 Apr 1992 02:50:56 -0600
+Received: by thor.acc.stolaf.edu; Thu, 2 Apr 92 02:49:31 -0600
+Date: Thu, 2 Apr 1992 02:49:31 -0600
+From: quanstro@acc.stolaf.edu
+Message-Id: <9204020849.AA26566@thor.acc.stolaf.edu>
+To: byron@archone.tamu.edu
+Subject: EXAMPLES in 1.4beta
+Status: RO
+
+
+I have a little bit of code which might be a little more general than
+the souped-up version that is already there. Here it is, if you are
+interested.
+
+# directory functions ###################################################
+fn pwd { echo $PWD; }
+
+fn pushd {
+ dirs = ($PWD $dirs);
+ builtin cd $*;
+ PWD = `{builtin pwd};
+}
+
+fn popd {
+ switch ($#*)
+ {
+ case 0
+ ;
+ case 1
+ echo 'popd: argument '^$1^' ignored.' >[1=2];
+ case *
+ echo 'popd: usage: popd [n].';
+ }
+
+ if (! ~ $dirs ())
+ {
+ builtin cd $dirs(1);
+ PWD = $dirs(1);
+ echo $PWD;
+ * = $dirs;
+ shift
+ dirs = $*;
+ }
+}
+
+fn cd {
+ ~ $* () && * = $home;
+ !~ $#* 1 && echo 'cd: too many arguments' >[1=2] && return 1;
+
+ if (test -r $* ) {
+ pushd $*;
+ } else {
+ echo cd: $* does not exist. >[1=2]
+ return 1;
+ }
+}
+
+fn back { popd $*; }
+
+fn Back {
+ cd $home;
+ PWD = $home;
+ dirs = ();
+}
+
+fn dirs {
+ echo $dirs;
+}
+
+PWD = `{builtin pwd} ; dirs = $PWD # kickstart
+
+
+
+
+
+From acc.stolaf.edu!quanstro Thu Apr 2 02:53:40 1992
+Received: from thor.acc.stolaf.edu ([130.71.192.1]) by archone.tamu.edu with SMTP id <45339>; Thu, 2 Apr 1992 02:53:38 -0600
+Received: by thor.acc.stolaf.edu; Thu, 2 Apr 92 02:51:46 -0600
+Date: Thu, 2 Apr 1992 02:51:46 -0600
+From: quanstro@acc.stolaf.edu
+Message-Id: <9204020851.AA26573@thor.acc.stolaf.edu>
+To: byron@archone.tamu.edu
+Subject: EXAMPLES
+Status: RO
+
+
+Little yp hack which act's like ~ w/o syntatic sugar (for those who do
+not have the luxury of seting up symbolic links to all user's homes
+
+# user function #########################################################
+fn u user {
+ info = ()
+ info = `` (':') {ypmatch $1 passwd >[2] /dev/null }
+
+ if (~ $info ()) {
+ echo user $1 unknown >[1=2];
+ return 1;
+ } else {
+ echo $info(6)
+ if (~ $0 user)
+ cd $info(6)
+ }
+}
+
+
+From stolaf.edu!quanstro Sun Apr 5 04:53:34 1992
+Date: Sun, 5 Apr 1992 04:53:08 -0500
+From: quanstro@stolaf.edu (Erik Quanstrom)
+To: byron@archone.tamu.edu
+Subject: man written in rc
+Status: RO
+
+I whipped this up because the NeXTs here insist on using MANPAGER
+instead of PAGER and otherwise being obnoxious . . .
+
+Anyway ... I hope you approve
+
+#!/bin/rc
+#########################################################################
+# file: man #
+# #
+# object: display man pages #
+# #
+# bugs: * slow #
+# * does not know about fmt files #
+# #
+# Erik Quanstrom #
+# 11. Februar 1992 #
+#########################################################################
+PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:$PATH ;
+TROFF = (nroff -hq -Tcrt);
+macros=an;
+sections=(cat1 cat2 cat3 cat4 cat5 cat6 cat7 cat8 catl man1 man2 man3 man4 \
+ man5 man6 man7 man8 manl)
+$nl='
+'
+fn sigint sighup sigquit sigalrm sigterm { rm -f $Bat; exit 2;}
+
+fn usage {
+ echo usage: $0 [-] [-t] [-M path] [-T macros] [[section] title] ...>[1=2];
+ exit 1;
+}
+
+n=();
+fn shiftn {
+ n=($n 1)
+}
+
+~ $PAGER () && test -t 1 && PAGER=more; #default pager
+
+while (~ $1 -*)
+ {
+ switch ($1)
+ {
+ case -
+ if (~ $PAGER troff)
+ echo bad combination of flags >[1=2] && usage;
+ PAGER=cat;
+ case -t
+ ~ TROFF () && TROFF = (troff -t);
+ ~ TCAT () && PAGER=(lpr -t);
+ case -M
+ shift;
+ ~ $1 () && usage;
+
+ MANPATH=$1;
+ case -T
+ shift;
+ ~ $1 () && usage;
+ macros=$1;
+ case -k
+ fflag=(); kflag=1;
+ shift;
+ break;
+ case -f
+ # locate related too filenames
+ kflag=(); fflag=1;
+ shift;
+ break;
+ case -*
+ echo bad flag '`'^$1^'''' >[1=2];
+ usage;
+ }
+ shift;
+ }
+
+if (!~ $#* 1) {
+ ~ $1 [l1-8] && { sname=$1 ; sections=(cat man)^$1 ; shift }
+ #hack for sun-style man pages
+ ~ $1 [l1-8]? && { sname=$1 ; sections=(cat man)^`{echo $1| cut -c1} ; shift }
+}
+
+if (~ 1 $fflag $kflag) {
+ dirlist=();
+ for (d in ``(:$nl) {echo $MANPATH})
+ test -s $d^/whatis && dirlist=($dirlist $d^/whatis);
+
+ ~ $1 () && usage;
+
+ if (~ $fflag 1) {
+ while (!~ $1 ()) {
+ cmd=`{echo $1 | sed 's/^.*\///g'}
+ egrep -h '^'^$cmd' ' $dirlist;
+ shift;
+ }
+ } else {
+ while (!~ $1 ()) {
+ grep -h $1 $dirlist;
+ shift;
+ }
+ }
+ exit 0;
+}
+
+s=0;
+while (!~ $1 ()) {
+ for (dir in ``(:$nl) {echo $MANPATH}) {
+ filelist=($filelist `{echo $dir/^$sections^/$1^.* |\
+ tr ' ' '\12' | grep -v '*'})
+
+ # coment this out if you don't care about speed, but
+ # would rather find all the pages.
+ ~ $filelist () || break;
+ }
+
+ if (~ $filelist ()) {
+ if (~ $#sections 2) {
+ echo no entry for $1 in section '`'$sname'''' of the manual >[1=2];
+ } else {
+ echo no entry for '`'$1'''' found. >[1=2];
+ }
+ s=1;
+ } else {
+
+ echo $filelist '(' $#filelist ')' >[1=2];
+
+ for (file in $filelist) {
+ if (~ $file */cat[1-8l]/*) {
+ Cat = ($Cat $file);
+ } else {
+ # search for dups
+ dont=()
+ for (x in $Cat) {
+ if (~ `{echo $x | sed 's/\/[mc]a[nt][1-8l]//'} \
+ `{echo $file | sed 's/\/[mc]a[nt][1-8l]//'}) {
+ dont=1;
+ break;
+ }
+ }
+
+ if (~ $dont ()) {
+ cd `{echo $file | sed 's/man[1-8].*//'}
+ echo -n Formatting ...
+ $TROFF -m^$macros $file > /tmp/man^$pid^$#n && \
+ Bat = ($Bat /tmp/man^$pid^$#n)
+
+ shiftn;
+ echo ' 'done.
+ }
+ }
+ }
+ }
+ shift;
+}
+
+{ !~ () $Cat || !~ () $Bat } && $PAGER $Cat $Bat;
+
+rm -f $Bat;
+~ $s () || exit $s;
+
+exit 0;
+
+
+
+From osf.org!rsalz Thu Apr 23 16:22:32 1992
+Date: Thu, 23 Apr 1992 16:22:07 -0500
+From: rsalz@osf.org
+To: byron@archone.tamu.edu
+Subject: One for your EXAMPLES file
+Status: RO
+
+Use
+ trimhist [-#lines]
+trims your history file back; useful for folks with dis quota's :-)
+fn trimhist { p1=-100 {
+ cp $history $history^'~'
+ ~ $#* 1 && p1=$1
+ tail $p1 <$history^'~' >$history
+ rm $history^'~'
+} }
+
+From Pa.dec.com!uucp Mon Apr 27 12:25:02 1992
+Date: Mon, 27 Apr 1992 12:15:18 -0500
+From: haahr@adobe.com
+To: Byron Rakitzis <byron@archone.tamu.edu>
+Subject: a neat little rc script
+
+what you have to know to understand this:
+ $md for me is usually obj.$machine
+ my mkfiles build *.o, *.a, and the a.outs in $md
+ this is my acc script, which i use for compiling adobe routines
+---
+#! /user/haahr/bin/next/rc
+
+cc = cc
+proto = '-DPROTOTYPES=1'
+
+switch ($md) {
+case noproto.$machine; proto = '-DPROTOTYPES=0'
+case gprof.$machine; cc = ($cc -pg)
+case prof.$machine; cc = ($cc -p)
+case lcomp.$machine; cc = lcomp
+}
+exec $cc $proto '-DPACKAGE_SPECS="package.h"' '-DISP=isp_mc68020' '-DOS=os_mach' $*
+
+From rc-owner Tue May 12 14:54:10 1992
+Received: from postman.osf.org ([130.105.1.152]) by archone.tamu.edu with SMTP id <45337>; Tue, 12 May 1992 14:38:16 -0500
+Received: from earth.osf.org by postman.osf.org (5.64+/OSF 1.0)
+ id AA14480; Tue, 12 May 92 13:25:03 -0400
+Received: by earth.osf.org (5.64/4.7) id AA03499; Tue, 12 May 92 13:25:02 -0400
+Date: Tue, 12 May 1992 12:25:02 -0500
+From: rsalz@osf.org
+Message-Id: <9205121725.AA03499@earth.osf.org>
+To: rc@archone.tamu.edu
+Subject: Useful function
+Status: R
+
+It looks like line noise, but it turns things like
+ /home/rsalz/foo/bar
+into
+ ~/foo/bar
+
+Useful for when you put your current directory up in your icon title.
+By duplicating the $home section you can make things like
+ /project/dce/build/dce1.0.1/src/rpc
+become
+ $MYBT/src/rpc
+
+## If a pathname starts with $home, turn $home into ~. Uses all built-ins.
+fn tildepath { p1=() i=() {
+ p1=$1
+ switch ($p1) {
+ case $home $home/*
+ # Split arg into components
+ *=`` (/) { echo -n $p1 }
+ # Shift down by number of components in $home
+ for (i in `` (/) { echo -n $home } ) shift
+ # Glue back together
+ p1='~'
+ for (i) p1=$p1 ^ '/' ^ $i
+ echo $p1
+ case *
+ echo $p1
+ }
+ return 0
+} }
+
+From osf.org!rsalz Tue May 12 15:47:12 1992
+Received: from postman.osf.org ([130.105.1.152]) by archone.tamu.edu with SMTP id <45316>; Tue, 12 May 1992 15:47:06 -0500
+Received: from earth.osf.org by postman.osf.org (5.64+/OSF 1.0)
+ id AA21070; Tue, 12 May 92 16:46:58 -0400
+Received: by earth.osf.org (5.64/4.7) id AA09396; Tue, 12 May 92 16:46:56 -0400
+Date: Tue, 12 May 1992 15:46:56 -0500
+From: rsalz@osf.org
+Message-Id: <9205122046.AA09396@earth.osf.org>
+To: byron@archone.tamu.edu
+Subject: Re: Useful function
+Status: R
+
+>wow. thanks, i'll add it to EXAMPLES.
+Glad you like. Getting something added to EXAMPLES has been a goal of mine...
+
+I've been thinking, a bit, about a more general way of doing it. I want
+a "prefix-sub" function, like this
+ prefix $some_path var1 var2 var3 var4 var5
+That would take some_path and replace any leading $var1 (etc) values
+with the variable name. Return on the first match.
+
+Hmm -- come to think of it, that's very easy to do:
+
+# Use pathprefix filename var1 var2 var3
+# returns filename, with leading prefixes (in $var1...) turned into the
+# string $var1...
+fn pathprefix { p1=() i=() j=() {
+ p1=$1 ; shift
+ for (i) {
+ ~ $p1 $$i $$i^/* && {
+ *=`` (/) { echo -n $p1 }
+ for (j in `` (/) { echo -n $$i } ) shift
+ p1='$'^$i
+ for (j) p1=$p1 ^ '/' ^ $j
+ echo $p1
+ return 0
+ }
+ }
+ echo $p1
+ return 0
+} }
+
+home=/user/users/rsalz
+z=/usr/users
+pathprefix /usr/users/rsalz home usr # --> $home
+pathprefix /usr/users/rsalz z # --> $z/rsalz
+pathprefix /usr/users/rsalz/foo z home # --> $z/rsalz/foo
+pathprefix /usr/users/rsalz/foo home # --> $home/foo
+
diff --git a/Makefile b/Makefile
@@ -0,0 +1,65 @@
+# Makefile for rc.
+
+# Please check the configuration parameters in config.h (and if you want
+# to make sure, the definitions in proto.h) to make sure they are correct
+# for your system.
+
+SHELL=/bin/sh
+
+# Uncomment this line if you have defined the NOEXECVE macro in config.h
+#EXECVE=execve.o
+
+# Define this macro if you wish to extend rc via locally-defined builtins.
+# An interface is provided in addon.[ch]. Note that the author does not
+# endorse any such extensions, rather hopes that this way rc will become
+# useful to more people.
+#ADDON=addon.o
+
+# Use an ANSI compiler (or at least one that groks prototypes and void *):
+CC=gcc -g -O
+CFLAGS=
+LDFLAGS=
+
+# You may substitute "bison -y" for yacc. (You want to choose the one that
+# makes a smaller y.tab.c.)
+YACC=yacc
+
+OBJS=$(ADDON) builtins.o except.o exec.o $(EXECVE) fn.o footobar.o getopt.o \
+ glob.o glom.o hash.o heredoc.o input.o lex.o list.o main.o match.o \
+ nalloc.o open.o print.o redir.o sigmsgs.o signal.o status.o tree.o \
+ utils.o var.o version.o wait.o walk.o which.o y.tab.o
+
+# If rc is compiled with READLINE defined, you must supply the correct
+# arguments to ld on this line. Typically this would be something like:
+#
+# $(CC) -o $@ $(OBJS) -lreadline -ltermcap
+
+rc: $(OBJS)
+ $(CC) -o $@ $(OBJS) $(LDFLAGS)
+
+sigmsgs.c: mksignal
+ sh mksignal /usr/include/sys/signal.h
+
+y.tab.c: parse.y
+ $(YACC) -d parse.y
+
+config.h: config.h-dist
+ cp config.h-dist config.h
+
+trip: rc
+ ./rc -p < trip.rc
+
+clean: force
+ rm -f *.o *.tab.* sigmsgs.*
+
+history: force
+ cd history; make CC="$(CC)" $(HISTORYMAKEFLAGS)
+
+force:
+
+# dependencies:
+
+$(OBJS): config.h
+sigmsgs.h: sigmsgs.c
+lex.o y.tab.o: y.tab.c
+builtins.c fn.c status.c hash.c: sigmsgs.h
diff --git a/README b/README
@@ -0,0 +1,100 @@
+This is release 1.4 of rc.
+
+Read COPYRIGHT for copying information. All files are
+
+Copyright 1991, Byron Rakitzis.
+
+COMPILING
+
+rc was written in portable ANSI C. If you don't have an ANSI compiler
+like gcc or something close (e.g., sgi's cc) read further down on
+how to convert rc's source to old C.
+
+Please read the Makefile, and copy config.h-dist to config.h and
+examine the parameters in there; they let you customize rc to your
+Unix. For example, some Unices support /dev/fd, or some have FIFOs.
+If you do not perform this step then the Makefile will automatically
+copy config.h-dist to config.h and proceed assuming that everything
+is ok. Note that config.h-dist supplies default parameter configurations
+for SunOS, NeXT-OS, Irix, Ultrix and some others. Finally, if you're
+having trouble you may want to look at proto.h and see if everything
+in there jibes with your system.
+
+After you've built rc, you may wish to run it through a test script
+to see that everything is ok. Type "make trip" for this. This will
+produce some output, and end with "trip is complete". If the trip
+ends with "trip took a wrong turn..." then drop me a line.
+
+To compile the history program, go into the history subdirectory
+and type "make". This will create a binary called "history". However,
+in order for it to work as advertised it must be installed into
+your bin as four files named -, --, -p and --p. (these can be soft
+or hard links to the same file)
+
+rc may also be linked with either GNU readline (10,000+ lines of
+code!) or a supplied readline-like system by Simmule Turner (1,000+
+lines of code). See the Makefile on how to do this.
+
+BUGS
+
+Send bug reports to byron@archone.tamu.edu. If a core dump is
+generated, sending me a backtrace will help me out a great deal. You
+can get a backtrace like this:
+
+ ; gdb rc core
+ (gdb) where
+ <<<BACKTRACE INFO>>>
+ (gdb)
+
+Also, always report the machine, compiler and OS used to make rc. It's
+possible I may have access to a machine of that type, in which case it
+becomes much easier for me to track the bug down.
+
+If you are using gcc, please make sure that you have a recent version of
+the compiler (1.39 and up) before you send me a note; I have found that
+older versions of gcc choke over rc and generate bad code on several
+architectures. (this is especially relevant for the the MIPS architecture)
+
+FEEPING CREATURISM
+
+See the end of the man page, under "INCOMPATABILITIES" for (known?)
+differences from the "real" rc. Most of these changes were necessary
+to get rc to work in a reasonable fashion on a real (i.e., commercial,
+non-Labs) UNIX system; a few were changes motivated by concern
+about some inadequacies in the original design.
+
+OLD C
+
+If you need to convert rc's source into K&R C, you need to run the
+source through a filter called "unproto", posted in comp.sources.misc.
+A sample "cpp" shell script that I used to run unproto under SunOS
+is supplied with rc.
+
+CREDITS
+
+This shell was written by me, Byron Rakitzis, but kudos go to Paul
+Haahr for letting me know what a shell should do and for contributing
+certain bits and pieces to rc (notably the limits code, print.c,
+most of which.c and the backquote redirection code), and to Hugh
+Redelmeier for running rc through his fussy ANSI compiler and
+thereby provoking interesting discussions about portability, and
+also for providing many valuable suggestions for improving rc's
+code in general. Finally, many thanks go to David Sanderson, for
+reworking the man page to format well with troff, and for providing
+many suggestions both for rc and its man page.
+
+Thanks to Boyd Roberts for the original history.c, and to Hugh
+again for re-working parts of that code.
+
+Of course, without Tom Duff's design of the original rc, I could
+not have written this shell (though I probably would have written
+*a* shell). Almost of all of the features, with minor exceptions,
+have been implemented as described in the Unix v10 manuals. Hats
+off to td for designing a C-like, minimal but very useful shell.
+
+Tom Duff has kindly given permission for the paper he wrote for
+UKUUG to be distributed with this version of rc (called "plan9.ps"
+in the same ftp directory as the shell). Please read this paper
+bearing in mind that it describes a program that was written at
+AT&T and that the version of rc presented here differs in some
+respects.
diff --git a/addon.c b/addon.c
@@ -0,0 +1,22 @@
+/*
+ This file contains the implementations of any locally defined
+ builtins.
+*/
+
+#ifdef DWS
+
+/*
+ This is what DaviD Sanderson (dws@cs.wisc.edu) uses.
+*/
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include "rc.h" /* for bool TRUE, FALSE */
+#include "addon.h"
+
+#include "addon/access.c"
+#include "addon/test.c"
+
+#endif
diff --git a/addon.h b/addon.h
@@ -0,0 +1,38 @@
+/*
+ This file is the interface to the rest of rc for any locally
+ defined addon builtins. By default there are none.
+ The interface consists of the following macro.
+
+ ADDONS A comma-separated list of pairs of function pointers
+ and string literals.
+
+ The addon functions must also have proper prototypes in this file.
+ The builtins all have the form:
+
+ void b_NAME(char **av);
+
+ Builtins report their exit status using set(TRUE) or set(FALSE).
+
+ Example:
+
+ #define ADDONS { b_test, "test" },
+ extern void b_test(char **av);
+*/
+
+#define ADDONS /* no addons by default */
+
+#ifdef DWS
+
+/*
+ This is what DaviD Sanderson (dws@cs.wisc.edu) uses.
+*/
+
+#undef ADDONS
+#define ADDONS { b_access, "access" },\
+ { b_test, "test" },\
+ { b_test, "[" },
+
+extern void b_access(char **av);
+extern void b_test(char **av);
+
+#endif
diff --git a/builtins.c b/builtins.c
@@ -0,0 +1,532 @@
+/* builtins.c: the collection of rc's builtin commands */
+
+/*
+ NOTE: rc's builtins do not call "rc_error" because they are
+ commands, and rc errors usually arise from syntax errors. e.g.,
+ you probably don't want interpretation of a shell script to stop
+ because of a bad umask.
+*/
+
+#include <sys/ioctl.h>
+#include <setjmp.h>
+#include <errno.h>
+#include "rc.h"
+#include "jbwrap.h"
+#ifndef NOLIMITS
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+#include "addon.h"
+
+extern int umask(int);
+
+static void b_break(char **), b_cd(char **), b_eval(char **), b_exit(char **),
+ b_newpgrp(char **), b_return(char **), b_shift(char **), b_umask(char **),
+ b_wait(char **), b_whatis(char **);
+
+#ifndef NOLIMITS
+static void b_limit(char **);
+#endif
+#ifndef NOECHO
+static void b_echo(char **);
+#endif
+
+static struct {
+ builtin_t *p;
+ char *name;
+} builtins[] = {
+ { b_break, "break" },
+ { b_builtin, "builtin" },
+ { b_cd, "cd" },
+#ifndef NOECHO
+ { b_echo, "echo" },
+#endif
+ { b_eval, "eval" },
+ { b_exec, "exec" },
+ { b_exit, "exit" },
+#ifndef NOLIMITS
+ { b_limit, "limit" },
+#endif
+ { b_newpgrp, "newpgrp" },
+ { b_return, "return" },
+ { b_shift, "shift" },
+ { b_umask, "umask" },
+ { b_wait, "wait" },
+ { b_whatis, "whatis" },
+ { b_dot, "." },
+ ADDONS
+};
+
+extern builtin_t *isbuiltin(char *s) {
+ int i;
+ for (i = 0; i < arraysize(builtins); i++)
+ if (streq(builtins[i].name, s))
+ return builtins[i].p;
+ return NULL;
+}
+
+/* funcall() is the wrapper used to invoke shell functions. pushes $*, and "return" returns here. */
+
+extern void funcall(char **av) {
+ Jbwrap j;
+ Estack e1, e2;
+ Edata jreturn, star;
+ if (setjmp(j.j))
+ return;
+ starassign(*av, av+1, TRUE);
+ jreturn.jb = &j;
+ star.name = "*";
+ except(eReturn, jreturn, &e1);
+ except(eVarstack, star, &e2);
+ walk(treecpy(fnlookup(*av), nalloc), TRUE);
+ varrm("*", TRUE);
+ unexcept(); /* eVarstack */
+ unexcept(); /* eReturn */
+}
+
+static void arg_count(char *name) {
+ fprint(2, "too many arguments to %s\n", name);
+ set(FALSE);
+}
+
+static void badnum(char *num) {
+ fprint(2, "%s is a bad number\n", num);
+ set(FALSE);
+}
+
+/* a dummy command. (exec() performs "exec" simply by not forking) */
+
+extern void b_exec(char **av) {
+}
+
+#ifndef NOECHO
+/* echo -n omits a newline. echo -- -n echos '-n' */
+
+static void b_echo(char **av) {
+ char *format = "%A\n";
+ if (*++av != NULL) {
+ if (streq(*av, "-n"))
+ format = "%A", av++;
+ else if (streq(*av, "--"))
+ av++;
+ }
+ fprint(1, format, av);
+ set(TRUE);
+}
+#endif
+
+/* cd. traverse $cdpath if the directory given is not an absolute pathname */
+
+static void b_cd(char **av) {
+ List *s, nil;
+ char *path = NULL;
+ SIZE_T t, pathlen = 0;
+ if (*++av == NULL) {
+ s = varlookup("home");
+ *av = (s == NULL) ? "/" : s->w;
+ } else if (av[1] != NULL) {
+ arg_count("cd");
+ return;
+ }
+ if (isabsolute(*av) || streq(*av, ".") || streq(*av, "..")) { /* absolute pathname? */
+ if (chdir(*av) < 0) {
+ set(FALSE);
+ uerror(*av);
+ } else
+ set(TRUE);
+ } else {
+ s = varlookup("cdpath");
+ if (s == NULL) {
+ s = &nil;
+ nil.w = "";
+ nil.n = NULL;
+ }
+ do {
+ if (s != &nil && *s->w != '\0') {
+ t = strlen(*av) + strlen(s->w) + 2;
+ if (t > pathlen)
+ path = nalloc(pathlen = t);
+ strcpy(path, s->w);
+ if (!streq(s->w, "/")) /* "//" is special to POSIX */
+ strcat(path, "/");
+ strcat(path, *av);
+ } else {
+ pathlen = 0;
+ path = *av;
+ }
+ if (chdir(path) >= 0) {
+ set(TRUE);
+ if (interactive && *s->w != '\0' && !streq(s->w, "."))
+ fprint(1, "%s\n", path);
+ return;
+ }
+ s = s->n;
+ } while (s != NULL);
+ fprint(2, "couldn't cd to %s\n", *av);
+ set(FALSE);
+ }
+}
+
+static void b_umask(char **av) {
+ int i;
+ if (*++av == NULL) {
+ set(TRUE);
+ i = umask(0);
+ umask(i);
+ fprint(1, "0%o\n", i);
+ } else if (av[1] == NULL) {
+ i = o2u(*av);
+ if ((unsigned int) i > 0777) {
+ fprint(2, "bad umask\n");
+ set(FALSE);
+ } else {
+ umask(i);
+ set(TRUE);
+ }
+ } else {
+ arg_count("umask");
+ return;
+ }
+}
+
+static void b_exit(char **av) {
+ if (*++av != NULL)
+ ssetstatus(av);
+ rc_exit(getstatus());
+}
+
+/* raise a "return" exception, i.e., return from a function. if an integer argument is present, set $status to it */
+
+static void b_return(char **av) {
+ if (*++av != NULL)
+ ssetstatus(av);
+ rc_raise(eReturn);
+}
+
+/* raise a "break" exception for breaking out of for and while loops */
+
+static void b_break(char **av) {
+ if (av[1] != NULL) {
+ arg_count("break");
+ return;
+ }
+ rc_raise(eBreak);
+}
+
+/* shift $* n places (default 1) */
+
+static void b_shift(char **av) {
+ int shift = (av[1] == NULL ? 1 : a2u(av[1]));
+ List *s, *dollarzero;
+ if (av[1] != NULL && av[2] != NULL) {
+ arg_count("shift");
+ return;
+ }
+ if (shift < 0) {
+ badnum(av[1]);
+ return;
+ }
+ s = varlookup("*")->n;
+ dollarzero = varlookup("0");
+ while (s != NULL && shift != 0) {
+ s = s->n;
+ --shift;
+ }
+ if (s == NULL && shift != 0) {
+ fprint(2, "cannot shift\n");
+ set(FALSE);
+ } else {
+ varassign("*", append(dollarzero, s), FALSE);
+ set(TRUE);
+ }
+}
+
+/* dud function */
+
+extern void b_builtin(char **av) {
+}
+
+/* wait for a given process, or all outstanding processes */
+
+static void b_wait(char **av) {
+ int stat, pid;
+ if (av[1] == NULL) {
+ waitforall();
+ return;
+ }
+ if (av[2] != NULL) {
+ arg_count("wait");
+ return;
+ }
+ if ((pid = a2u(av[1])) < 0) {
+ badnum(av[1]);
+ return;
+ }
+ if (rc_wait4(pid, &stat, FALSE) > 0)
+ setstatus(pid, stat);
+ else
+ set(FALSE);
+ SIGCHK;
+}
+
+/*
+ whatis without arguments prints all variables and functions. Otherwise, check to see if a name
+ is defined as a variable, function or pathname.
+*/
+
+static void b_whatis(char **av) {
+ bool f, found;
+ bool ess = FALSE;
+ int i, ac, c;
+ List *s;
+ Node *n;
+ char *e;
+ for (rc_optind = ac = 0; av[ac] != NULL; ac++)
+ ; /* count the arguments for getopt */
+ while ((c = rc_getopt(ac, av, "s")) == 's')
+ ess = TRUE;
+ if (c != -1) {
+ set(FALSE);
+ return;
+ }
+ av += rc_optind;
+ if (*av == NULL && !ess) {
+ whatare_all_vars();
+ set(TRUE);
+ return;
+ }
+ if (ess)
+ whatare_all_signals();
+ found = TRUE;
+ for (i = 0; av[i] != NULL; i++) {
+ f = FALSE;
+ errno = ENOENT;
+ if ((s = varlookup(av[i])) != NULL) {
+ f = TRUE;
+ prettyprint_var(1, av[i], s);
+ }
+ if ((n = fnlookup(av[i])) != NULL) {
+ f = TRUE;
+ prettyprint_fn(1, av[i], n);
+ } else if (isbuiltin(av[i]) != NULL) {
+ f = TRUE;
+ fprint(1, "builtin %s\n", av[i]);
+ } else if ((e = which(av[i], FALSE)) != NULL) {
+ f = TRUE;
+ fprint(1, "%s\n", e);
+ }
+ if (!f) {
+ found = FALSE;
+ if (errno != ENOENT)
+ uerror(av[i]);
+ else
+ fprint(2, "%s not found\n", av[i]);
+ }
+ }
+ set(found);
+}
+
+/* push a string to be eval'ed onto the input stack. evaluate it */
+
+static void b_eval(char **av) {
+ bool i = interactive;
+ if (av[1] == NULL)
+ return;
+ interactive = FALSE;
+ pushstring(av + 1, i); /* don't reset line numbers on noninteractive eval */
+ doit(TRUE);
+ interactive = i;
+}
+
+/*
+ push a file to be interpreted onto the input stack. with "-i" treat this as an interactive
+ input source.
+*/
+
+extern void b_dot(char **av) {
+ int fd;
+ bool old_i = interactive, i = FALSE;
+ Estack e;
+ Edata star;
+ av++;
+ if (*av == NULL)
+ return;
+ if (streq(*av, "-i")) {
+ av++;
+ i = TRUE;
+ }
+ if (dasheye) { /* rc -i file has to do the right thing. reset the dasheye state to FALSE, though. */
+ dasheye = FALSE;
+ i = TRUE;
+ }
+ if (*av == NULL)
+ return;
+ fd = rc_open(*av, rFrom);
+ if (fd < 0) {
+ if (rcrc) /* on rc -l, don't flag nonexistence of .rcrc */
+ rcrc = FALSE;
+ else {
+ uerror(*av);
+ set(FALSE);
+ }
+ return;
+ }
+ rcrc = FALSE;
+ starassign(*av, av+1, TRUE);
+ pushfd(fd);
+ interactive = i;
+ star.name = "*";
+ except(eVarstack, star, &e);
+ doit(TRUE);
+ varrm("*", TRUE);
+ unexcept(); /* eVarstack */
+ interactive = old_i;
+}
+
+/* put rc into a new pgrp. Used on the NeXT where the Terminal program is broken (sigh) */
+
+static void b_newpgrp(char **av) {
+ if (av[1] != NULL) {
+ arg_count("newpgrp");
+ return;
+ }
+ setpgrp(rc_pid, rc_pid);
+#ifdef TIOCSPGRP
+ ioctl(2, TIOCSPGRP, &rc_pid);
+#endif
+}
+
+/* Berkeley limit support was cleaned up by Paul Haahr. */
+
+#ifndef NOLIMITS
+typedef struct Suffix Suffix;
+struct Suffix {
+ const Suffix *next;
+ long amount;
+ char *name;
+};
+
+static const Suffix
+ kbsuf = { NULL, 1024, "k" },
+ mbsuf = { &kbsuf, 1024*1024, "m" },
+ gbsuf = { &mbsuf, 1024*1024*1024, "g" },
+ stsuf = { NULL, 1, "s" },
+ mtsuf = { &stsuf, 60, "m" },
+ htsuf = { &mtsuf, 60*60, "h" };
+#define SIZESUF &gbsuf
+#define TIMESUF &htsuf
+#define NOSUF ((Suffix *) NULL) /* for RLIMIT_NOFILE on SunOS 4.1 */
+
+typedef struct {
+ char *name;
+ int flag;
+ const Suffix *suffix;
+} Limit;
+static const Limit limits[] = {
+ { "cputime", RLIMIT_CPU, TIMESUF },
+ { "filesize", RLIMIT_FSIZE, SIZESUF },
+ { "datasize", RLIMIT_DATA, SIZESUF },
+ { "stacksize", RLIMIT_STACK, SIZESUF },
+ { "coredumpsize", RLIMIT_CORE, SIZESUF },
+#ifdef RLIMIT_RSS /* SysVr4 does not have this */
+ { "memoryuse", RLIMIT_RSS, SIZESUF },
+#endif
+#ifdef RLIMIT_VMEM /* instead, they have this! */
+ { "vmemory", RLIMIT_VMEM, SIZESUF },
+#endif
+#ifdef RLIMIT_NOFILE /* SunOS 4.1 adds a limit on file descriptors */
+ { "descriptors", RLIMIT_NOFILE, NOSUF },
+#endif
+ { NULL, 0, NULL }
+};
+
+extern int getrlimit(int, struct rlimit *);
+extern int setrlimit(int, struct rlimit *);
+
+static void printlimit(const Limit *limit, bool hard) {
+ struct rlimit rlim;
+ long lim;
+ getrlimit(limit->flag, &rlim);
+ if (hard)
+ lim = rlim.rlim_max;
+ else
+ lim = rlim.rlim_cur;
+ if (lim == RLIM_INFINITY)
+ fprint(1, "%s \tunlimited\n", limit->name);
+ else {
+ const Suffix *suf;
+ for (suf = limit->suffix; suf != NULL; suf = suf->next)
+ if (lim % suf->amount == 0 && (lim != 0 || suf->amount > 1)) {
+ lim /= suf->amount;
+ break;
+ }
+ fprint(1, "%s \t%d%s\n", limit->name, lim, (suf == NULL || lim == 0) ? "" : suf->name);
+ }
+}
+
+static long parselimit(const Limit *limit, char *s) {
+ char *t;
+ int len = strlen(s);
+ long lim = 1;
+ const Suffix *suf = limit->suffix;
+ if (streq(s, "unlimited"))
+ return RLIM_INFINITY;
+ if (suf == TIMESUF && (t = strchr(s, ':')) != NULL) {
+ *t++ = '\0';
+ lim = 60 * a2u(s) + a2u(t);
+ } else {
+ for (; suf != NULL; suf = suf->next)
+ if (streq(suf->name, s + len - strlen(suf->name))) {
+ s[len - strlen(suf->name)] = '\0';
+ lim *= suf->amount;
+ break;
+ }
+ lim *= a2u(s);
+ }
+ return lim;
+}
+
+static void b_limit(char **av) {
+ const Limit *lp = limits;
+ bool hard = FALSE;
+ if (*++av != NULL && streq(*av, "-h")) {
+ av++;
+ hard = TRUE;
+ }
+ if (*av == NULL) {
+ for (; lp->name != NULL; lp++)
+ printlimit(lp, hard);
+ return;
+ }
+ for (;; lp++) {
+ if (lp->name == NULL) {
+ fprint(2, "no such limit\n");
+ set(FALSE);
+ return;
+ }
+ if (streq(*av, lp->name))
+ break;
+ }
+ if (*++av == NULL)
+ printlimit(lp, hard);
+ else {
+ struct rlimit rlim;
+ long pl;
+ getrlimit(lp->flag, &rlim);
+ if ((pl = parselimit(lp, *av)) < 0) {
+ fprint(2, "bad limit\n");
+ set(FALSE);
+ return;
+ }
+ if (hard)
+ rlim.rlim_max = pl;
+ else
+ rlim.rlim_cur = pl;
+ if (setrlimit(lp->flag, &rlim) == -1) {
+ uerror("setrlimit");
+ set(FALSE);
+ } else
+ set(TRUE);
+ }
+}
+#endif
diff --git a/config.h-dist b/config.h-dist
@@ -0,0 +1,204 @@
+/* Copy config.h-dist to config.h and edit config.h, don't edit this file */
+
+/*
+ * Configuration parameters for rc. Suggested defaults are at the bottom
+ * of this file (you should probably look at those first to see if your
+ * system matches one of them; you can search for the beginning of the
+ * defaults section by looking for the string "#ifndef CUSTOM"). If you
+ * want to override the suggested defaults, define the macro CUSTOM.
+#define CUSTOM
+ */
+
+/*
+ * (Note that certain default settings redefine this macro)
+ * DEFAULTPATH the default search path that rc uses when it is started
+ * without either a $PATH or $path environment variable. You must pick
+ * something sensible for your system if you don't like the path shown
+ * below.
+ */
+#define DEFAULTPATH "/usr/ucb", "/usr/bin", "/bin", "."
+
+/*
+ * Define the macro NODIRENT if your system has <sys/dir.h> but not
+ * <dirent.h>. (e.g., NeXT-OS and RISCos)
+#define NODIRENT
+ */
+
+/*
+ * Define the macro SVSIGS if your system has System V signal semantics,
+ * i.e., if "slow" system calls are interrupted rather than resumed
+ * after returning from an interrupt handler. (If you are not sure what
+ * this means, see the man page for signal(2). In any case, it is probably
+ * safe to leave this macro undefined.)
+#define SVSIGS
+ */
+
+/*
+ * Define the macro NOCMDARG if you do not have /dev/fd or fifos on your
+ * system. You may also want to define this if you have broken fifos.
+#define NOCMDARG
+ */
+
+/*
+ * Define TMPDIR if you need to have rc create its fifos in a directory
+ * other than /tmp. For example, if you have a Sun with /tmp mounted
+ * as a ramdisk (type "tmpfs") then you cannot use fifos in /tmp (sigh).
+#define TMPDIR "/var/tmp"
+ */
+
+/*
+ * Define the macro DEVFD if your system supports /dev/fd.
+#define DEVFD
+ */
+
+/*
+ * Define the macro NOLIMITS if your system does not support Berkeley
+ * limits.
+#define NOLIMITS
+ */
+
+/*
+ * Define the macro NOSIGCLD if your system uses SIGCLD in the System
+ * V way. (e.g., sgi's Irix)
+#define NOSIGCLD
+ */
+
+/*
+ * Define the macro READLINE if you want rc to call GNU readline
+ * instead of read(2) on interactive shells.
+#define READLINE
+ */
+
+/*
+ * Define the macro NOEXECVE if your Unix does not interpret #! in the
+ * kernel, and uncomment the EXECVE variable in the Makefile.
+#define NOEXECVE
+ */
+
+/*
+ * If you want rc to default to some interpreter for files which don't
+ * have a legal #! on the first line, define the macro DEFAULTINTERP.
+#define DEFAULTINTERP "/bin/sh"
+ */
+
+/*
+ * If your /bin/sh (or another program you care about) rejects environment
+ * variables with special characters in them (such as ':' or '-'), rc can
+ * put out ugly variable names using [_0-9a-zA-Z] that encode the real name;
+ * define PROTECT_ENV for this hack. (Known offenders: every sh I have tried;
+ * SunOS (silently discards), NeXT (aborts with error), SGI (aborts with
+ * error), Ultrix (sh seems to work, sh5 aborts with error))
+#define PROTECT_ENV
+ */
+
+/*
+ * Define the macro NOECHO if you wish to omit rc's echo builtin from the
+ * compile.
+#define NOECHO
+ */
+
+/*
+ * Define the NOJOB if you do *not* wish rc to perform backgrounding
+ * as if it were a job-control shell; that is, if you do *not* wish
+ * it to put a command spawned in the background into a new process
+ * group. Since most systems support job control and since there are
+ * many broken programs that do not behave correctly when backgrounded
+ * in a v7 non-job-control fashion, rc by default performs a job-
+ * control-like backgrounding.
+#define NOJOB
+ */
+
+/* Beginning of defaults section: */
+
+#ifndef CUSTOM
+
+/*
+ * Suggested settings for Sun, NeXT and sgi (machines here at TAMU):
+ */
+
+#ifdef NeXT /* Used on NextOS 2.1 */
+#define NODIRENT
+#define PROTECT_ENV
+#define NOCMDARG
+#endif
+
+#ifdef sgi /* Used on Irix 3.3.[12] */
+#define SVSIGS
+#define NOSIGCLD
+#define PROTECT_ENV
+#undef DEFAULTPATH
+#define DEFAULTPATH "/usr/bsd", "/usr/sbin", "/usr/bin", "/bin", "."
+#endif
+
+#ifdef sun /* Used on SunOS 4.1.1 */
+#define PROTECT_ENV
+#undef DEFAULTPATH
+#define DEFAULTPATH "/usr/ucb", "/usr/bin", "."
+#endif
+
+/*
+ * Suggested settings for HP300 running 4.3BSD-utah (DWS):
+ */
+
+#if defined(hp300) && !defined(hpux)
+#define NODIRENT
+#define NOCMDARG
+#define DEFAULTINTERP "/bin/sh"
+#define PROTECT_ENV
+#endif
+
+/*
+ * Suggested settings for Ultrix
+ */
+
+#ifdef ultrix
+#define PROTECT_ENV
+#define DEFAULTINTERP "/bin/sh" /* so /bin/true can work */
+#endif
+
+/*
+ * Suggested settings for RISCos 4.52
+ */
+
+/*
+ This doesn't work without interfering with other MIPS-based
+ systems' configuration. Please do it by hand.
+*/
+
+#if defined(host_mips) && defined(MIPSEB) && defined(SYSTYPE_BSD43)
+#define NODIRENT
+#define PROTECT_ENV
+#endif
+
+/*
+ * Suggested settings for AIX
+ */
+
+#ifdef _AIX
+#define PROTECT_ENV
+#endif
+
+/*
+ * Suggested settings for OSF/1 1.0
+ */
+
+#ifdef OSF1
+#define PROTECT_ENV
+#endif
+
+/*
+ * Suggested settings for Unicos XXX
+ */
+
+#ifdef cray
+#define PROTECT_ENV
+#define NOLIMITS
+#define word _word
+#define DEFAULTINTERP "/bin/sh"
+#endif
+
+#endif /* CUSTOM */
+
+#ifndef TMPDIR
+#define TMPDIR "/tmp"
+#endif
diff --git a/cpp b/cpp
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# @(#) cpp.sh 1.3 92/01/15 21:53:22
+
+# Unprototypeing preprocessor for pre-ANSI C compilers. On some systems,
+# this script can be as simple as:
+#
+# /lib/cpp "$@" | unproto
+#
+# However, some cc(1) drivers specify output file names on the
+# preprocessor command line, so this shell script must be prepared to
+# intercept them. Depending on the driver program, the cpp options may
+# even go before or after the file name argument(s). The script below
+# tries to tackle all these cases.
+#
+# You may want to add -Ipath_to_stdarg.h_file, -Dvoid=, -Dvolatile=,
+# and even -D__STDC__.
+
+## (This is what I used while testing with the SunOS C compiler.
+## Also, I added "-Qpath ." to CFLAGS so that cpp would be
+## run out of the current directory. --- Byron)
+cpp_args="-I/u/byron/lib/sun4 -Dconst= -Dvolatile="
+
+while :
+do
+ case $1 in
+ "") break;;
+ -*) cpp_args="$cpp_args $1";;
+ *) cpp_args="$cpp_args $1"
+ case $2 in
+ ""|-*) ;;
+ *) exec 1> $2 || exit 1; shift;;
+ esac;;
+ esac
+ shift
+done
+
+/lib/cpp $cpp_args | unproto
diff --git a/except.c b/except.c
@@ -0,0 +1,140 @@
+#include <setjmp.h>
+#include <signal.h>
+#include "rc.h"
+#include "jbwrap.h"
+
+/*
+ A return goes back stack frames to the last return. A break does
+ not. A signal goes to the last interactive level. (see below)
+*/
+
+bool nl_on_intr = TRUE;
+
+static Estack *estack;
+
+/* add an exception to the input stack. */
+
+extern void except(ecodes e, Edata data, Estack *ex) {
+ ex->prev = estack;
+ estack = ex;
+ estack->e = e;
+ estack->data = data;
+ if (e == eError || e == eBreak || e == eReturn)
+ estack->interactive = interactive;
+}
+
+/* remove an exception, restore last interactive value */
+
+extern void unexcept() {
+ switch (estack->e) {
+ default:
+ break;
+ case eError:
+ interactive = estack->interactive;
+ break;
+ case eArena:
+ restoreblock(estack->data.b);
+ break;
+ case eFifo:
+ unlink(estack->data.name);
+ break;
+ case eFd:
+ close(estack->data.fd);
+ break;
+ }
+ estack = estack->prev;
+}
+
+/*
+ Raise an exception. The rules are pretty complicated: you can return
+ from a loop inside a function, but you can't break from a function
+ inside of a loop. On errors, rc_raise() goes back to the LAST
+ INTERACTIVE stack frame. If no such frame exists, then rc_raise()
+ exits the shell. This is what happens, say, when there is a syntax
+ error in a noninteractive shell script. While traversing the
+ exception stack backwards, rc_raise() also removes input sources
+ (closing file-descriptors, etc.) and pops instances of variables
+ that have been pushed onto the variable stack (e.g., for a function
+ call (for $*) or a local assignment).
+*/
+
+extern void rc_raise(ecodes e) {
+ if (e == eError && rc_pid != getpid())
+ exit(1); /* child processes exit on an error/signal */
+ for (; estack != NULL; estack = estack->prev)
+ if (estack->e != e) {
+ if (e == eBreak && estack->e != eArena)
+ rc_error("break outside of loop");
+ else if (e == eReturn && estack->e == eError) /* can return from loops inside functions */
+ rc_error("return outside of function");
+ switch (estack->e) {
+ default:
+ break;
+ case eVarstack:
+ varrm(estack->data.name, TRUE);
+ break;
+ case eArena:
+ restoreblock(estack->data.b);
+ break;
+ case eFifo:
+ unlink(estack->data.name);
+ break;
+ case eFd:
+ close(estack->data.fd);
+ break;
+ }
+ } else {
+ if (e == eError && !estack->interactive) {
+ popinput();
+ } else {
+ Jbwrap *j = estack->data.jb;
+
+ interactive = estack->interactive;
+ estack = estack->prev;
+ longjmp(j->j, 1);
+ }
+ }
+ rc_exit(1); /* top of exception stack */
+}
+
+extern bool outstanding_cmdarg() {
+ return estack->e == eFifo || estack->e == eFd;
+}
+
+extern void pop_cmdarg(bool remove) {
+ for (; estack != NULL; estack = estack->prev)
+ switch (estack->e) {
+ case eFifo:
+ if (remove)
+ unlink(estack->data.name);
+ break;
+ case eFd:
+ if (remove)
+ close(estack->data.fd);
+ break;
+ default:
+ return;
+ }
+}
+
+/* exception handlers */
+
+extern void rc_error(char *s) {
+ pr_error(s);
+ set(FALSE);
+ redirq = NULL;
+ cond = FALSE; /* no longer inside conditional */
+ rc_raise(eError);
+}
+
+extern void sigint(int s) {
+ if (s != SIGINT)
+ panic("s != SIGINT in sigint catcher");
+ /* this is the newline you see when you hit ^C while typing a command */
+ if (nl_on_intr)
+ fprint(2, "\n");
+ nl_on_intr = TRUE;
+ redirq = NULL;
+ cond = FALSE;
+ rc_raise(eError);
+}
diff --git a/exec.c b/exec.c
@@ -0,0 +1,119 @@
+/* exec.c */
+#include <signal.h>
+#include <errno.h>
+#include <setjmp.h>
+#include "rc.h"
+#include "jbwrap.h"
+
+/*
+ Takes an argument list and does the appropriate thing (calls a
+ builtin, calls a function, etc.)
+*/
+
+extern void exec(List *s, bool parent) {
+ char **av, **ev = NULL;
+ int pid, stat;
+ builtin_t *b;
+ char *path = NULL;
+ bool didfork, returning, saw_exec, saw_builtin;
+ av = list2array(s, dashex);
+ saw_builtin = saw_exec = FALSE;
+ do {
+ if (*av == NULL || isabsolute(*av))
+ b = NULL;
+ else if (!saw_builtin && fnlookup(*av) != NULL)
+ b = funcall;
+ else
+ b = isbuiltin(*av);
+
+ /*
+ a builtin applies only to the immmediately following
+ command, e.g., builtin exec echo hi
+ */
+ saw_builtin = FALSE;
+
+ if (b == b_exec) {
+ av++;
+ saw_exec = TRUE;
+ parent = FALSE;
+ } else if (b == b_builtin) {
+ av++;
+ saw_builtin = TRUE;
+ }
+ } while (b == b_exec || b == b_builtin);
+ if (*av == NULL && saw_exec) { /* do redirs and return on a null exec */
+ doredirs();
+ return;
+ }
+ /* force an exit on exec with any rc_error, but not for null commands as above */
+ if (saw_exec)
+ rc_pid = -1;
+ if (b == NULL) {
+ path = which(*av, TRUE);
+ if (path == NULL && *av != NULL) { /* perform null commands for redirections */
+ set(FALSE);
+ redirq = NULL;
+ if (parent)
+ return;
+ rc_exit(1);
+ }
+ ev = makeenv(); /* environment only needs to be built for execve() */
+ }
+ /*
+ If parent & the redirq is nonnull, builtin or not it has to fork.
+ If the fifoq is nonnull, then it must be emptied at the end so we
+ must fork no matter what.
+ */
+ if ((parent && (b == NULL || redirq != NULL)) || outstanding_cmdarg()) {
+ pid = rc_fork();
+ didfork = TRUE;
+ } else {
+ pid = 0;
+ didfork = FALSE;
+ }
+ returning = (!didfork && parent);
+ switch (pid) {
+ case -1:
+ uerror("fork");
+ rc_error(NULL);
+ /* NOTREACHED */
+ case 0:
+ if (!returning)
+ setsigdefaults(FALSE);
+ pop_cmdarg(FALSE);
+ doredirs();
+
+ /* null commands performed for redirections */
+ if (*av == NULL || b != NULL) {
+ if (b != NULL)
+ (*b)(av);
+ if (returning)
+ return;
+ rc_exit(getstatus());
+ }
+#ifdef NOEXECVE
+ my_execve(path, (const char **) av, (const char **) ev); /* bogus, huh? */
+#else
+ execve(path, (const char **) av, (const char **) ev);
+#endif
+#ifdef DEFAULTINTERP
+ if (errno == ENOEXEC) {
+ *av = path;
+ *--av = DEFAULTINTERP;
+ execve(*av, (const char **) av, (const char **) ev);
+ }
+#endif
+ uerror(*av);
+ rc_exit(1);
+ /* NOTREACHED */
+ default:
+ redirq = NULL;
+ rc_wait4(pid, &stat, TRUE);
+ setstatus(-1, stat);
+ if ((stat & 0xff) == 0)
+ nl_on_intr = FALSE;
+ SIGCHK;
+ nl_on_intr = TRUE;
+ pop_cmdarg(TRUE);
+ }
+}
diff --git a/execve.c b/execve.c
@@ -0,0 +1,61 @@
+/* execve.c: an execve() for geriatric unices without #! */
+
+/*
+ NOTE: this file depends on a hack in footobar.c which places two free spots before
+ av[][] so that execve does not have to call malloc.
+*/
+
+#include <errno.h>
+#include "rc.h"
+
+#define giveupif(x) { if (x) goto fail; }
+
+extern int my_execve(const char *path, const char **av, const char **ev) {
+ int fd, len, fst, snd, end;
+ bool noarg;
+ char pb[256]; /* arbitrary but generous limit */
+ execve(path, av, ev);
+ if (errno != ENOEXEC)
+ return -1;
+ fd = rc_open(path, rFrom);
+ giveupif(fd < 0);
+ len = read(fd, pb, sizeof pb);
+ close(fd);
+ /* reject scripts which don't begin with #! */
+ giveupif(len <= 0 || pb[0] != '#' || pb[1] != '!');
+ for (fst = 2; fst < len && (pb[fst] == ' ' || pb[fst] == '\t'); fst++)
+ ; /* skip leading whitespace */
+ giveupif(fst == len);
+ for (snd = fst; snd < len && pb[snd] != ' ' && pb[snd] != '\t' && pb[snd] != '\n'; snd++)
+ ; /* skip first arg */
+ giveupif(snd == len);
+ noarg = (pb[snd] == '\n');
+ pb[snd++] = '\0'; /* null terminate the first arg */
+ if (!noarg) {
+ while (snd < len && (pb[snd] == ' ' || pb[snd] == '\t'))
+ snd++; /* skip whitespace to second arg */
+ giveupif(snd == len);
+ noarg = (pb[snd] == '\n'); /* could have trailing whitespace after only one arg */
+ if (!noarg) {
+ for (end = snd; end < len && pb[end] != ' ' && pb[end] != '\t' && pb[end] != '\n'; end++)
+ ; /* skip to the end of the second arg */
+ giveupif(end == len);
+ if (pb[end] == '\n') {
+ pb[end] = '\0'; /* null terminate the first arg */
+ } else { /* else check for a spurious third arg */
+ pb[end++] = '\0';
+ while (end < len && (pb[end] == ' ' || pb[end] == '\t'))
+ end++;
+ giveupif(end == len || pb[end] != '\n');
+ }
+ }
+ }
+ *av = path;
+ if (!noarg)
+ *--av = pb + snd;
+ *--av = pb + fst;
+ execve(*av, av, ev);
+ return -1;
+fail: errno = ENOEXEC;
+ return -1;
+}
diff --git a/fn.c b/fn.c
@@ -0,0 +1,266 @@
+/*
+ fn.c: functions for adding and deleting functions from the symbol table.
+ Support for signal handlers is also found here.
+*/
+
+#include <signal.h>
+#include <errno.h>
+#include "rc.h"
+#include "sigmsgs.h"
+
+static void fn_handler(int), dud_handler(int);
+
+static bool runexit = FALSE;
+static Node *handlers[NUMOFSIGNALS], null;
+static void (*def_sigint)(int) = SIG_DFL,
+ (*def_sigquit)(int) = SIG_DFL,
+ (*def_sigterm)(int) = SIG_DFL;
+
+/*
+ Set signals to default values for rc. This means that interactive
+ shells ignore SIGTERM, etc.
+*/
+
+extern void inithandler() {
+ int i;
+ null.type = nBody;
+ null.u[0].p = null.u[1].p = NULL;
+ for (i = 1; i < NUMOFSIGNALS; i++)
+#ifdef NOSIGCLD
+ if (i != SIGCLD)
+#endif
+ if (sighandlers[i] == SIG_IGN)
+ fnassign(signals[i].name, NULL); /* ignore incoming ignored signals */
+ if (interactive || sighandlers[SIGINT] != SIG_IGN) {
+ def_sigint = sigint;
+ fnrm("sigint"); /* installs SIGINT catcher if not inherited ignored */
+ }
+ if (!dashdee) {
+ if (interactive || sighandlers[SIGQUIT] != SIG_IGN) {
+ def_sigquit = dud_handler;
+ fnrm("sigquit"); /* "ignores" SIGQUIT unless inherited ignored */
+ }
+ if (interactive) {
+ def_sigterm = dud_handler;
+ fnrm("sigterm"); /* ditto for SIGTERM */
+ }
+ }
+}
+
+/* only run this in a child process! resets signals to their default values */
+
+extern void setsigdefaults(bool sysvbackground) {
+ int i;
+ /*
+ General housekeeping: setsigdefaults happens after fork(),
+ so it's a convenient place to clean up open file descriptors.
+ (history file, scripts, etc.)
+ */
+ closefds();
+ /*
+ Restore signals to SIG_DFL, paying close attention to
+ a few quirks: SIGINT, SIGQUIT and are treated specially
+ depending on whether we are doing v7-style backgrounding
+ or not; the default action for SIGINT, SIGQUIT and SIGTERM
+ must be set to the appropriate action; finally, care must
+ be taken not to set to SIG_DFL any signals which are being
+ ignored.
+ */
+ for (i = 1; i < NUMOFSIGNALS; i++)
+ if (sighandlers[i] != SIG_IGN) {
+ handlers[i] = NULL;
+ switch (i) {
+ case SIGINT:
+ if (sysvbackground) {
+ def_sigint = SIG_IGN;
+ fnassign("sigint", NULL); /* ignore */
+ } else {
+ def_sigint = SIG_DFL;
+ goto sigcommon;
+ }
+ break;
+ case SIGQUIT:
+ if (sysvbackground) {
+ def_sigquit = SIG_IGN;
+ fnassign("sigquit", NULL); /* ignore */
+ } else {
+ def_sigquit = SIG_DFL;
+ goto sigcommon;
+ }
+ break;
+ case SIGTERM:
+ def_sigterm = SIG_DFL;
+ /* FALLTHROUGH */
+ sigcommon:
+ default:
+ if (sighandlers[i] != SIG_DFL) {
+ rc_signal(i, SIG_DFL);
+ delete_fn(signals[i].name);
+ }
+ }
+ }
+ delete_fn("sigexit");
+ runexit = FALSE; /* No sigexit on subshells */
+}
+
+/* rc's exit. if runexit is set, run the sigexit function. */
+
+extern void rc_exit(int stat) {
+ static char *sigexit[2] = {
+ "sigexit",
+ NULL
+ };
+ if (runexit) {
+ runexit = FALSE;
+ funcall(sigexit);
+ stat = getstatus();
+ }
+ exit(stat);
+}
+
+/* The signal handler for all functions. calls walk() */
+
+static void fn_handler(int s) {
+ List *dollarzero;
+ Estack e;
+ Edata star;
+ int olderrno;
+ if (s < 1 || s >= NUMOFSIGNALS)
+ panic("unknown signal");
+ olderrno = errno;
+ dollarzero = nnew(List);
+ dollarzero->w = signals[s].name;
+ dollarzero->n = NULL;
+ varassign("*", dollarzero, TRUE);
+ star.name = "*";
+ except(eVarstack, star, &e);
+ walk(handlers[s], TRUE);
+ varrm("*", TRUE);
+ unexcept(); /* eVarstack */
+ errno = olderrno;
+}
+
+/* A dud signal handler for SIGQUIT and SIGTERM */
+
+static void dud_handler(int s) {
+}
+
+/*
+ Assign a function in Node form. Check to see if the function is also
+ a signal, and set the signal vectors appropriately.
+*/
+
+extern void fnassign(char *name, Node *def) {
+ Node *newdef = treecpy(def == NULL ? &null : def, ealloc); /* important to do the treecopy first */
+ Function *new = get_fn_place(name);
+ int i;
+ new->def = newdef;
+ new->extdef = NULL;
+ if (strncmp(name, "sig", conststrlen("sig")) == 0) { /* slight optimization */
+#ifdef NOSIGCLD /* System V machines treat SIGCLD very specially */
+ if (streq(name, "sigcld"))
+ rc_error("can't trap SIGCLD");
+#endif
+ if (streq(name, "sigexit"))
+ runexit = TRUE;
+ for (i = 1; i < NUMOFSIGNALS; i++) /* zero is a bogus signal */
+ if (streq(signals[i].name, name)) {
+ handlers[i] = newdef;
+ if (def == NULL)
+ rc_signal(i, SIG_IGN);
+ else
+ rc_signal(i, fn_handler);
+ break;
+ }
+ }
+}
+
+/* Assign a function from the environment. Store just the external representation */
+
+extern void fnassign_string(char *extdef) {
+ char *name = get_name(extdef+3); /* +3 to skip over "fn_" */
+ Function *new;
+ if (name == NULL)
+ return;
+ new = get_fn_place(name);
+ new->def = NULL;
+ new->extdef = ecpy(extdef);
+}
+
+/* Return a function in Node form, evaluating an entry from the environment if necessary */
+
+extern Node *fnlookup(char *name) {
+ Function *look = lookup_fn(name);
+ Node *ret;
+ if (look == NULL)
+ return NULL; /* not found */
+ if (look->def != NULL)
+ return look->def;
+ if (look->extdef == NULL) /* function was set to null, e.g., fn foo {} */
+ return &null;
+ ret = parse_fn(name, look->extdef);
+ if (ret == NULL) {
+ efree(look->extdef);
+ look->extdef = NULL;
+ return &null;
+ } else {
+ return look->def = treecpy(ret, ealloc); /* Need to take it out of nalloc space */
+ }
+}
+
+/* Return a function in string form (used by makeenv) */
+
+extern char *fnlookup_string(char *name) {
+ Function *look = lookup_fn(name);
+
+ if (look == NULL)
+ return NULL;
+ if (look->extdef != NULL)
+ return look->extdef;
+ return look->extdef = fun2str(name, look->def);
+}
+
+/*
+ Remove a function from the symbol table. If it also defines a signal
+ handler, restore the signal handler to its default value.
+*/
+
+extern void fnrm(char *name) {
+ int i;
+ for (i = 1; i < NUMOFSIGNALS; i++)
+ if (streq(signals[i].name, name)) {
+ handlers[i] = NULL;
+ switch (i) {
+ case SIGINT:
+ rc_signal(i, def_sigint);
+ break;
+ case SIGQUIT:
+ rc_signal(i, def_sigquit);
+ break;
+ case SIGTERM:
+ rc_signal(i, def_sigterm);
+ break;
+ default:
+ rc_signal(i, SIG_DFL);
+ }
+ }
+ if (streq(name, "sigexit"))
+ runexit = FALSE;
+ delete_fn(name);
+}
+
+extern void whatare_all_signals() {
+ int i;
+ for (i = 1; i < NUMOFSIGNALS; i++)
+ if (*signals[i].name != '\0')
+ if (sighandlers[i] == SIG_IGN)
+ fprint(1, "fn %s {}\n", signals[i].name);
+ else if (sighandlers[i] == fn_handler)
+ fprint(1, "fn %S {%T}\n", signals[i].name, handlers[i]);
+ else
+ fprint(1, "fn %s\n", signals[i].name);
+}
+
+extern void prettyprint_fn(int fd, char *name, Node *n) {
+ fprint(fd, "fn %S {%T}\n", name, n);
+}
diff --git a/footobar.c b/footobar.c
@@ -0,0 +1,368 @@
+/*
+ footobar.c: a collection of functions to convert internal representations of
+ variables and functions to external representations, and vice versa
+*/
+
+#include "rc.h"
+
+#define FSCHAR '\1'
+#define FSSTRING "\1"
+
+static char *getenvw(char *, bool);
+
+#ifdef PROTECT_ENV
+static bool Fconv(Format *f, int ignore) { /* protect an exported name from brain-dead shells */
+ int c;
+ unsigned const char *s = va_arg(f->args, unsigned const char *);
+
+ while ((c = *s++) != '\0')
+ if (dnw[c] || c == '*' || (c == '_' && *s == '_'))
+ fmtprint(f, "__%02x", c);
+ else
+ fmtputc(f, c);
+ return FALSE;
+}
+#endif
+
+/* used to turn a function in Node * form into something we can export to the environment */
+
+extern char *fun2str(char *name, Node *n) {
+ return mprint("fn_%F={%T}", name, n);
+}
+
+/* convert a redirection to a printable form */
+
+static bool Dconv(Format *f, int ignore) {
+ const char *name = "?";
+ int n = va_arg(f->args, int);
+ switch (n) {
+ case rCreate: name = ">"; break;
+ case rAppend: name = ">>"; break;
+ case rFrom: name = "<"; break;
+ case rHeredoc: name = "<<"; break;
+ case rHerestring: name = "<<<"; break;
+ }
+ fmtcat(f, name);
+ return FALSE;
+}
+
+/* defaultfd -- return the default fd for a given redirection operation */
+
+extern int defaultfd(int op) {
+ return (op == rCreate || op == rAppend) ? 1 : 0;
+}
+
+/* convert a function in Node * form into something rc can parse (and humans can read?) */
+
+static bool Tconv(Format *f, int ignore) {
+ Node *n = va_arg(f->args, Node *);
+ if (n == NULL) {
+ fmtprint(f, "()");
+ return FALSE;
+ }
+ switch (n->type) {
+ case nWord: fmtprint(f, "%S", n->u[0].s); break;
+ case nQword: fmtprint(f, "%#S", n->u[0].s); break;
+ case nBang: fmtprint(f, "! %T", n->u[0].p); break;
+ case nCase: fmtprint(f, "case %T", n->u[0].p); break;
+ case nNowait: fmtprint(f, "%T&", n->u[0].p); break;
+ case nCount: fmtprint(f, "$#%T", n->u[0].p); break;
+ case nFlat: fmtprint(f, "$^%T", n->u[0].p); break;
+ case nRmfn: fmtprint(f, "fn %T", n->u[0].p); break;
+ case nSubshell: fmtprint(f, "@ %T", n->u[0].p); break;
+ case nVar: fmtprint(f, "$%T", n->u[0].p); break;
+ case nAndalso: fmtprint(f, "%T&&%T", n->u[0].p, n->u[1].p); break;
+ case nAssign: fmtprint(f, "%T=%T", n->u[0].p, n->u[1].p); break;
+ case nConcat: fmtprint(f, "%T^%T", n->u[0].p, n->u[1].p); break;
+ case nElse: fmtprint(f, "{%T}else %T", n->u[0].p, n->u[1].p); break;
+ case nNewfn: fmtprint(f, "fn %T {%T}", n->u[0].p, n->u[1].p); break;
+ case nIf: fmtprint(f, "if(%T)%T", n->u[0].p, n->u[1].p); break;
+ case nOrelse: fmtprint(f, "%T||%T", n->u[0].p, n->u[1].p); break;
+ case nArgs: fmtprint(f, "%T %T", n->u[0].p, n->u[1].p); break;
+ case nSwitch: fmtprint(f, "switch(%T){%T}", n->u[0].p, n->u[1].p); break;
+ case nMatch: fmtprint(f, "~ %T %T", n->u[0].p, n->u[1].p); break;
+ case nVarsub: fmtprint(f, "$%T(%T)", n->u[0].p, n->u[1].p); break;
+ case nWhile: fmtprint(f, "while(%T)%T", n->u[0].p, n->u[1].p); break;
+ case nLappend: fmtprint(f, "(%T %T)", n->u[0].p, n->u[1].p); break;
+ case nForin: fmtprint(f, "for(%T in %T)%T", n->u[0].p, n->u[1].p, n->u[2].p); break;
+ case nDup:
+ if (n->u[2].i != -1)
+ fmtprint(f, "%D[%d=%d]", n->u[0].i, n->u[1].i, n->u[2].i);
+ else
+ fmtprint(f, "%D[%d=]", n->u[0].i, n->u[1].i);
+ break;
+ case nBackq: {
+ Node *n0 = n->u[0].p, *n00;
+ if (n0 != NULL && n0->type == nVar
+ && (n00 = n0->u[0].p) != NULL && n00->type == nWord && streq(n00->u[0].s, "ifs"))
+ fmtprint(f, "`");
+ else
+ fmtprint(f, "``%T", n0);
+ fmtprint(f, "{%T}", n->u[1].p);
+ break;
+ }
+ case nCbody:
+ case nBody: {
+ Node *n0 = n->u[0].p;
+ if (n0 != NULL)
+ fmtprint(f, "%T", n->u[0].p);
+ if (n->u[1].p != NULL) {
+ if (n0 != NULL && n0->type != nNowait)
+ fmtprint(f, ";");
+ fmtprint(f, "%T", n->u[1].p);
+ }
+ break;
+ }
+ case nBrace:
+ fmtprint(f, "{%T}", n->u[0].p);
+ if (n->u[1].p != NULL)
+ fmtprint(f, "%T", n->u[1].p);
+ break;
+ case nEpilog:
+ case nPre:
+ fmtprint(f, "%T", n->u[0].p);
+ if (n->u[1].p != NULL)
+ fmtprint(f, " %T", n->u[1].p);
+ break;
+ case nPipe: {
+ int ofd = n->u[0].i, ifd = n->u[1].i;
+ fmtprint(f, "%T|", n->u[2].p);
+ if (ifd != 0)
+ fmtprint(f, "[%d=%d]", ofd, ifd);
+ else if (ofd != 1)
+ fmtprint(f, "[%d]", ofd);
+ fmtprint(f, "%T", n->u[3].p);
+ break;
+ }
+ case nRedir: {
+ int op = n->u[0].i;
+ fmtprint(f, "%D", op);
+ if (n->u[1].i != defaultfd(op))
+ fmtprint(f, "[%d]", n->u[1].i);
+ fmtprint(f, "%T", n->u[2].p);
+ break;
+ }
+ case nNmpipe: {
+ int op = n->u[0].i;
+ fmtprint(f, "%D", op);
+ if (n->u[1].i != defaultfd(op))
+ fmtprint(f, "[%d]", n->u[1].i);
+ fmtprint(f, "{%T}", n->u[2].p);
+ break;
+ }
+ }
+ return FALSE;
+}
+
+/* convert a List to a string, separating it with ^A characters. Used for exporting variables to the environment */
+
+extern char *list2str(char *name, List *s) {
+ SIZE_T size, step;
+ List *t;
+ char *w, *x;
+ name = nprint("%F", name);
+ size = strlen(name) + listlen(s);
+ w = ealloc(size + 2);
+ t = s;
+ x = w;
+ strcpy(x, name);
+ strcpy(x += strlen(name), "=");
+ strcpy(x += conststrlen("="), t->w);
+ for (x += strlen(t->w), s = s->n; s != NULL; s = s->n) {
+ memcpy(x, FSSTRING, step = conststrlen(FSSTRING));
+ x += step;
+ memcpy(x, s->w, step = strlen(s->w));
+ x += step;
+ }
+ *x = '\0';
+ return w;
+}
+
+/* convert a List to an array, for execve() */
+
+extern char **list2array(List *s, bool print) {
+ char **av;
+ int i;
+
+ /* 4 == 1 for the null terminator + 2 for the fake execve() + 1 for defaulting to sh */
+ av = nalloc((listnel(s) + 4) * sizeof (char *));
+ av += 3; /* hide the two free spots from rc (two for #! emulation, one for defaulting to sh) */
+ if (print)
+ fprint(2, "%L\n", s, " ");
+ for (i = 0; s != NULL; i++) {
+ av[i] = s->w;
+ s = s->n;
+ }
+ av[i] = NULL;
+ return av;
+}
+
+/* figure out the name of a variable given an environment string. copy this into malloc space */
+
+extern char *get_name(char *s) {
+ int c;
+ SIZE_T i;
+ char *r, *namebuf;
+ for (i = 0; s[i] != '\0' && s[i] != '='; i++)
+ ;
+ if (s[i] == '\0')
+ return NULL;
+ r = namebuf = ealloc(i + 1);
+ while (1)
+ switch (c = *s++) {
+ case '=':
+ *r++ = '\0';
+ return namebuf;
+#ifdef PROTECT_ENV
+ case '_':
+ if (*s == '_') {
+ static const char hexchar[] = "0123456789abcdef";
+ char *h1 = strchr(hexchar, s[1]);
+ char *h2 = strchr(hexchar, s[2]);
+ if (h1 != NULL && h2 != NULL) {
+ *r++ = ((h1 - hexchar) << 4) | (h2 - hexchar);
+ s += 3;
+ break;
+ }
+ }
+ /* FALLTHROUGH */
+#endif
+ default:
+ *r++ = c;
+ break;
+ }
+}
+
+/* get the next word from a variable's value as represented in the environment. */
+
+static char *getenvw(char *s, bool saw_alpha) {
+ SIZE_T i;
+ char *r;
+ for (i = 0; s[i] != '\0' && s[i] != FSCHAR; i++)
+ ;
+ if (i == 0) {
+ if (s[i] == '\0' && !saw_alpha)
+ return NULL;
+ else
+ return clear(enew(char), (SIZE_T) 1);
+ }
+ r = strncpy(ealloc(i + 1), s, i);
+ r[i] = '\0';
+ return r;
+}
+
+/* take an environment entry for a variable (elements ^A separated) and turn it into a List */
+
+extern List *parse_var(char *name, char *extdef) {
+ List *r, *top;
+ char *f;
+ bool saw_alpha;
+ top = r = enew(List);
+ extdef = strchr(extdef, '=') + 1;
+ if ((f = getenvw(extdef, FALSE)) == NULL) {
+ r->w = "";
+ r->m = NULL;
+ r->n = NULL;
+ } else {
+ while (1) {
+ r->w = f;
+ r->m = NULL;
+ extdef += strlen(f);
+ if (*extdef == FSCHAR) {
+ extdef++;
+ saw_alpha = TRUE;
+ } else {
+ saw_alpha = FALSE;
+ }
+ if ((f = getenvw(extdef, saw_alpha)) == NULL) {
+ r->n = NULL;
+ break;
+ }
+ r = r->n = enew(List);
+ }
+ }
+ return top;
+}
+
+/* get an environment entry for a function and have rc parse it. */
+
+#define PREFIX "fn x"
+#define PRELEN conststrlen(PREFIX)
+extern Node *parse_fn(char *name, char *extdef) {
+ Node *def;
+ char *s, old[PRELEN];
+ if ((s = strchr(extdef, '=')) == NULL)
+ return NULL;
+ memcpy(old, s -= (PRELEN-1), PRELEN);
+ memcpy(s, PREFIX, PRELEN);
+ def = parseline(s);
+ memcpy(s, old, PRELEN);
+ return (def == NULL || def->type != nNewfn) ? NULL : def->u[1].p;
+}
+
+static bool Aconv(Format *f, int c) {
+ char **a = va_arg(f->args, char **);
+ if (*a != NULL) {
+ fmtcat(f, *a);
+ while (*++a != NULL)
+ fmtprint(f, " %s", *a);
+ }
+ return FALSE;
+}
+
+static bool Lconv(Format *f, int c) {
+ List *l = va_arg(f->args, List *);
+ char *sep = va_arg(f->args, char *);
+ char *fmt = (f->flags & FMT_leftside) ? "%s%s" : "%-S%s";
+ if (l == NULL && (f->flags & FMT_leftside) == 0)
+ fmtprint(f, "()");
+ else {
+ List *s;
+ for (s = l; s != NULL; s = s->n)
+ fmtprint(f, fmt, s->w, s->n == NULL ? "" : sep);
+ }
+ return FALSE;
+}
+
+#define ISMETA(c) (c == '*' || c == '?' || c == '[')
+
+static bool Sconv(Format *f, int ignore) {
+ int c;
+ unsigned char *s = va_arg(f->args, unsigned char *), *t = s;
+ bool quoted = (f->flags & FMT_altform) != 0; /* '#' */
+ bool metaquote = (f->flags & FMT_leftside) != 0; /* '-' */
+ if (*s == '\0') {
+ fmtprint(f, "''");
+ return FALSE;
+ }
+ if (!quoted) {
+ while ((c = *t++) != '\0')
+ if (nw[c] == 1 || (metaquote && ISMETA(c)))
+ goto quoteit;
+ fmtprint(f, "%s", s);
+ return FALSE;
+ }
+quoteit:
+ fmtputc(f, '\'');
+ while ((c = *s++) != '\0') {
+ fmtputc(f, c);
+ if (c == '\'')
+ fmtputc(f, '\'');
+
+ }
+ fmtputc(f, '\'');
+ return FALSE;
+}
+
+void initprint(void) {
+ fmtinstall('A', Aconv);
+ fmtinstall('L', Lconv);
+ fmtinstall('S', Sconv);
+ fmtinstall('T', Tconv);
+ fmtinstall('D', Dconv);
+#ifdef PROTECT_ENV
+ fmtinstall('F', Fconv);
+#else
+ fmtinstall('F', fmtinstall('s', NULL));
+#endif
+}
diff --git a/getopt.c b/getopt.c
@@ -0,0 +1,50 @@
+#include "rc.h"
+
+int rc_opterr = 1;
+int rc_optind = 1;
+int rc_optopt;
+char *rc_optarg;
+
+/* getopt routine courtesy of David Sanderson */
+
+extern int rc_getopt(int argc, char **argv, char *opts) {
+ static int sp = 1;
+ int c;
+ char *cp;
+ if (rc_optind == 0) /* reset rc_getopt() */
+ rc_optind = sp = 1;
+ if (sp == 1)
+ if (rc_optind >= argc || argv[rc_optind][0] != '-' || argv[rc_optind][1] == '\0') {
+ return -1;
+ } else if (strcmp(argv[rc_optind], "--") == 0) {
+ rc_optind++;
+ return -1;
+ }
+ rc_optopt = c = argv[rc_optind][sp];
+ if (c == ':' || (cp=strchr(opts, c)) == 0) {
+ fprint(2, "%s: bad option: -%c\n", argv[0], c);
+ if (argv[rc_optind][++sp] == '\0') {
+ rc_optind++;
+ sp = 1;
+ }
+ return '?';
+ }
+ if (*++cp == ':') {
+ if (argv[rc_optind][sp+1] != '\0') {
+ rc_optarg = &argv[rc_optind++][sp+1];
+ } else if (++rc_optind >= argc) {
+ fprint(2, "%s: option requires an argument -- %c\n", argv[0], c);
+ sp = 1;
+ return '?';
+ } else
+ rc_optarg = argv[rc_optind++];
+ sp = 1;
+ } else {
+ if (argv[rc_optind][++sp] == '\0') {
+ sp = 1;
+ rc_optind++;
+ }
+ rc_optarg = NULL;
+ }
+ return c;
+}
diff --git a/glob.c b/glob.c
@@ -0,0 +1,240 @@
+/* glob.c: rc's (ugly) globber. This code is not elegant, but it works */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "rc.h"
+#ifdef NODIRENT
+#include <sys/dir.h>
+#define dirent direct /* need to get the struct declaraction right */
+#else
+#include <dirent.h>
+#endif
+
+static List *dmatch(char *, char *, char *);
+static List *doglob(char *, char *);
+static List *lglob(List *, char *, char *, SIZE_T);
+static List *sort(List *);
+
+/*
+ Matches a list of words s against a list of patterns p. Returns true iff
+ a pattern in p matches a word in s. () matches (), but otherwise null
+ patterns match nothing.
+*/
+
+extern bool lmatch(List *s, List *p) {
+ List *q;
+ int i;
+ bool okay;
+ if (s == NULL) {
+ if (p == NULL) /* null matches null */
+ return TRUE;
+ for (; p != NULL; p = p->n) { /* one or more stars match null */
+ if (*p->w != '\0') { /* the null string is a special case; it does *not* match () */
+ okay = TRUE;
+ for (i = 0; p->w[i] != '\0'; i++)
+ if (p->w[i] != '*' || p->m[i] != 1) {
+ okay = FALSE;
+ break;
+ }
+ if (okay)
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+ for (; s != NULL; s = s->n)
+ for (q = p; q != NULL; q = q->n)
+ if (match(q->w, q->m, s->w))
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ Globs a list; checks to see if each element in the list has a metacharacter. If it
+ does, it is globbed, and the output is sorted.
+*/
+
+extern List *glob(List *s) {
+ List *top, *r;
+ bool meta;
+ for (r = s, meta = FALSE; r != NULL; r = r->n)
+ if (r->m != NULL)
+ meta = TRUE;
+ if (!meta)
+ return s; /* don't copy lists with no metacharacters in them */
+ for (top = r = NULL; s != NULL; s = s->n) {
+ if (s->m == NULL) { /* no metacharacters; just tack on to the return list */
+ if (top == NULL)
+ top = r = nnew(List);
+ else
+ r = r->n = nnew(List);
+ r->w = s->w;
+ } else {
+ if (top == NULL)
+ top = r = sort(doglob(s->w, s->m));
+ else
+ r->n = sort(doglob(s->w, s->m));
+ while (r->n != NULL)
+ r = r->n;
+ }
+ }
+ r->n = NULL;
+ return top;
+}
+
+/* Matches a pattern p against the contents of directory d */
+
+static List *dmatch(char *d, char *p, char *m) {
+ bool matched = FALSE;
+ List *top, *r;
+ static DIR *dirp;
+ static struct dirent *dp;
+ static struct stat s;
+ /* prototypes for XXXdir functions. comment out if necessary */
+ extern DIR *opendir(const char *);
+ extern struct dirent *readdir(DIR *);
+ /*extern int closedir(DIR *);*/
+
+ top = r = NULL;
+ /* opendir succeeds on regular files on some systems, so the stat() call is necessary (sigh) */
+ if (stat(d, &s) < 0 || (s.st_mode & S_IFMT) != S_IFDIR || (dirp = opendir(d)) == NULL)
+ return NULL;
+ while ((dp = readdir(dirp)) != NULL)
+ if ((*dp->d_name != '.' || *p == '.') && match(p, m, dp->d_name)) { /* match ^. explicitly */
+ matched = TRUE;
+ if (top == NULL)
+ top = r = nnew(List);
+ else
+ r = r->n = nnew(List);
+ r->w = ncpy(dp->d_name);
+ r->m = NULL;
+ }
+ closedir(dirp);
+ if (!matched)
+ return NULL;
+ r->n = NULL;
+ return top;
+}
+
+/*
+ lglob() globs a pattern agains a list of directory roots. e.g., (/tmp /usr /bin) "*"
+ will return a list with all the files in /tmp, /usr, and /bin. NULL on no match.
+ slashcount indicates the number of slashes to stick between the directory and the
+ matched name. e.g., for matching ////tmp/////foo*
+*/
+
+static List *lglob(List *s, char *p, char *m, SIZE_T slashcount) {
+ List *q, *r, *top, foo;
+ static List slash;
+ static SIZE_T slashsize = 0;
+ if (slashcount + 1 > slashsize) {
+ slash.w = ealloc(slashcount + 1);
+ slashsize = slashcount;
+ }
+ slash.w[slashcount] = '\0';
+ while (slashcount > 0)
+ slash.w[--slashcount] = '/';
+ for (top = r = NULL; s != NULL; s = s->n) {
+ q = dmatch(s->w, p, m);
+ if (q != NULL) {
+ foo.w = s->w;
+ foo.m = NULL;
+ foo.n = NULL;
+ if (!(s->w[0] == '/' && s->w[1] == '\0')) /* need to separate */
+ q = concat(&slash, q); /* dir/name with slash */
+ q = concat(&foo, q);
+ if (r == NULL)
+ top = r = q;
+ else
+ r->n = q;
+ while (r->n != NULL)
+ r = r->n;
+ }
+ }
+ return top;
+}
+
+/*
+ Doglob globs a pathname in pattern form against a unix path. Returns the original
+ pattern (cleaned of metacharacters) on failure, or the globbed string(s).
+*/
+
+static List *doglob(char *w, char *m) {
+ static char *dir = NULL, *pattern = NULL, *metadir = NULL, *metapattern = NULL;
+ static SIZE_T dsize = 0;
+ char *d, *p, *md, *mp;
+ SIZE_T psize;
+ char *s = w;
+ List firstdir;
+ List *matched;
+ if ((psize = strlen(w) + 1) > dsize || dir == NULL) {
+ efree(dir); efree(pattern); efree(metadir); efree(metapattern);
+ dir = ealloc(psize);
+ pattern = ealloc(psize);
+ metadir = ealloc(psize);
+ metapattern = ealloc(psize);
+ dsize = psize;
+ }
+ d = dir;
+ p = pattern;
+ md = metadir;
+ mp = metapattern;
+ if (*s == '/')
+ while (*s == '/')
+ *d++ = *s++, *md++ = *m++;
+ else
+ while (*s != '/' && *s != '\0')
+ *d++ = *s++, *md++ = *m++; /* get first directory component */
+ *d = '\0';
+ /*
+ Special case: no slashes in the pattern, i.e., open the current directory.
+ Remember that w cannot consist of slashes alone (the other way *s could be
+ zero) since doglob gets called iff there's a metacharacter to be matched
+ */
+ if (*s == '\0') {
+ matched = dmatch(".", dir, metadir);
+ goto end;
+ }
+ if (*w == '/') {
+ firstdir.w = dir;
+ firstdir.m = metadir;
+ firstdir.n = NULL;
+ matched = &firstdir;
+ } else {
+ /*
+ we must glob against current directory,
+ since the first character is not a slash.
+ */
+ matched = dmatch(".", dir, metadir);
+ }
+ do {
+ SIZE_T slashcount;
+ SIGCHK;
+ for (slashcount = 0; *s == '/'; s++, m++)
+ slashcount++; /* skip slashes */
+ while (*s != '/' && *s != '\0')
+ *p++ = *s++, *mp++ = *m++; /* get pattern */
+ *p = '\0';
+ matched = lglob(matched, pattern, metapattern, slashcount);
+ p = pattern, mp = metapattern;
+ } while (*s != '\0');
+end: if (matched == NULL) {
+ matched = nnew(List);
+ matched->w = w;
+ matched->m = NULL;
+ matched->n = NULL;
+ }
+ return matched;
+}
+
+static List *sort(List *s) {
+ SIZE_T nel = listnel(s);
+ if (nel > 1) {
+ char **a;
+ List *t;
+ qsort(a = list2array(s, FALSE), nel, sizeof(char *), starstrcmp);
+ for (t = s; t != NULL; t = t->n)
+ t->w = *a++;
+ }
+ return s;
+}
diff --git a/glom.c b/glom.c
@@ -0,0 +1,420 @@
+/* glom.c: builds an argument list out of words, variables, etc. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include "rc.h"
+#if !defined(S_IFIFO) && !defined(DEVFD)
+#define NOCMDARG
+#endif
+
+static List *backq(Node *, Node *);
+static List *bqinput(List *, int);
+static List *count(List *);
+static List *mkcmdarg(Node *);
+
+Rq *redirq = NULL;
+
+extern List *word(char *w, char *m) {
+ List *s = NULL;
+ if (w != NULL) {
+ s = nnew(List);
+ s->w = w;
+ s->m = m;
+ s->n = NULL;
+ }
+ return s;
+}
+
+/*
+ Append list s2 to list s1 by copying s1 and making the new copy
+ point at s2.
+*/
+
+extern List *append(List *s1, List *s2) {
+ List *r, *top;
+ if (s1 == NULL)
+ return s2;
+ if (s2 == NULL)
+ return s1;
+ for (r = top = nnew(List); 1; r = r->n = nnew(List)) {
+ r->w = s1->w;
+ r->m = s1->m;
+ if ((s1 = s1->n) == NULL)
+ break;
+ }
+ r->n = s2;
+ return top;
+}
+
+extern List *concat(List *s1, List *s2) {
+ int n1, n2;
+ List *r, *top;
+ if (s1 == NULL)
+ return s2;
+ if (s2 == NULL)
+ return s1;
+ if ((n1 = listnel(s1)) != (n2 = listnel(s2)) && n1 != 1 && n2 != 1)
+ rc_error("bad concatenation");
+ for (r = top = nnew(List); 1; r = r->n = nnew(List)) {
+ SIZE_T x = strlen(s1->w);
+ SIZE_T y = strlen(s2->w);
+ SIZE_T z = x + y + 1;
+ r->w = nalloc(z);
+ strcpy(r->w, s1->w);
+ strcat(r->w, s2->w);
+ if (s1->m == NULL && s2->m == NULL) {
+ r->m = NULL;
+ } else {
+ r->m = nalloc(z);
+ if (s1->m == NULL)
+ clear(r->m, x);
+ else
+ memcpy(r->m, s1->m, x);
+ if (s2->m == NULL)
+ clear(&r->m[x], y);
+ else
+ memcpy(&r->m[x], s2->m, y);
+ r->m[z] = 0;
+ }
+ if (n1 > 1)
+ s1 = s1->n;
+ if (n2 > 1)
+ s2 = s2->n;
+ if (s1 == NULL || s2 == NULL || (n1 == 1 && n2 == 1))
+ break;
+ }
+ r->n = NULL;
+ return top;
+}
+
+extern List *varsub(List *var, List *subs) {
+ List *r, *top;
+ int n = listnel(var);
+ for (top = r = NULL; subs != NULL; subs = subs->n) {
+ int i = a2u(subs->w);
+ if (i < 1)
+ rc_error("bad subscript");
+ if (i <= n) {
+ List *sub = var;
+ while (--i)
+ sub = sub->n; /* loop until sub == var(i) */
+ if (top == NULL)
+ top = r = nnew(List);
+ else
+ r = r->n = nnew(List);
+ r->w = sub->w;
+ r->m = sub->m;
+ }
+ }
+ if (top != NULL)
+ r->n = NULL;
+ return top;
+}
+
+extern List *flatten(List *s) {
+ List *r;
+ SIZE_T step;
+ char *f;
+ if (s == NULL || s->n == NULL)
+ return s;
+ r = nnew(List);
+ f = r->w = nalloc(listlen(s) + 1);
+ r->m = NULL; /* flattened lists come from variables, so no meta */
+ r->n = NULL;
+ strcpy(f, s->w);
+ f += strlen(s->w);
+ do {
+ *f++ = ' ';
+ s = s->n;
+ step = strlen(s->w);
+ memcpy(f, s->w, step);
+ f += step;
+ } while (s->n != NULL);
+ *f = '\0';
+ return r;
+}
+
+static List *count(List *l) {
+ List *s = nnew(List);
+ s->w = nprint("%d", listnel(l));
+ s->n = NULL;
+ s->m = NULL;
+ return s;
+}
+
+extern void assign(List *s1, List *s2, bool stack) {
+ List *val = s2;
+ if (s1 == NULL)
+ rc_error("null variable name");
+ if (s1->n != NULL)
+ rc_error("multi-word variable name");
+ if (*s1->w == '\0')
+ rc_error("zero-length variable name");
+ if (a2u(s1->w) != -1)
+ rc_error("numeric variable name");
+ if (strchr(s1->w, '=') != NULL)
+ rc_error("'=' in variable name");
+ if (*s1->w == '*' && s1->w[1] == '\0')
+ val = append(varlookup("0"), s2); /* preserve $0 when * is assigned explicitly */
+ if (s2 != NULL || stack) {
+ if (dashex)
+ prettyprint_var(2, s1->w, val);
+ varassign(s1->w, val, stack);
+ alias(s1->w, varlookup(s1->w), stack);
+ } else {
+ if (dashex)
+ prettyprint_var(2, s1->w, NULL);
+ varrm(s1->w, stack);
+ }
+}
+
+/*
+ The following two functions are by the courtesy of Paul Haahr,
+ who could not stand the incompetence of my own backquote implementation.
+*/
+
+#define BUFSIZE ((SIZE_T) 1000)
+
+static List *bqinput(List *ifs, int fd) {
+ char *end, *bufend, *s;
+ List *r, *top, *prev;
+ SIZE_T remain, bufsize;
+ char isifs[256];
+ int n, state; /* a simple FSA is used to read in data */
+
+ clear(isifs, sizeof isifs);
+ for (isifs['\0'] = TRUE; ifs != NULL; ifs = ifs->n)
+ for (s = ifs->w; *s != '\0'; s++)
+ isifs[*(unsigned char *)s] = TRUE;
+ remain = bufsize = BUFSIZE;
+ top = r = nnew(List);
+ r->w = end = nalloc(bufsize + 1);
+ r->m = NULL;
+ state = 0;
+ prev = NULL;
+
+ while (1) {
+ if (remain == 0) { /* is the string bigger than the buffer? */
+ SIZE_T m = end - r->w;
+ char *buf;
+ while (bufsize < m + BUFSIZE)
+ bufsize *= 2;
+ buf = nalloc(bufsize + 1);
+ memcpy(buf, r->w, m);
+ r->w = buf;
+ end = &buf[m];
+ remain = bufsize - m;
+ }
+ if ((n = rc_read(fd, end, remain)) <= 0) {
+ if (n == 0)
+ /* break */ break;
+ uerror("backquote read");
+ rc_error(NULL);
+ }
+ remain -= n;
+ for (bufend = &end[n]; end < bufend; end++)
+ if (state == 0) {
+ if (!isifs[*(unsigned char *)end]) {
+ state = 1;
+ r->w = end;
+ }
+ } else {
+ if (isifs[*(unsigned char *)end]) {
+ state = 0;
+ *end = '\0';
+ prev = r;
+ r = r->n = nnew(List);
+ r->w = end+1;
+ r->m = NULL;
+ }
+ }
+ }
+ if (state == 1) { /* terminate last string */
+ *end = '\0';
+ r->n = NULL;
+ } else {
+ if (prev == NULL) /* no input at all? */
+ top = NULL;
+ else
+ prev->n = NULL; /* else terminate list */
+ }
+ return top;
+}
+
+static List *backq(Node *ifs, Node *n) {
+ int p[2], pid, sp;
+ List *bq;
+ if (n == NULL)
+ return NULL;
+ if (pipe(p) < 0) {
+ uerror("pipe");
+ rc_error(NULL);
+ }
+ if ((pid = rc_fork()) == 0) {
+ setsigdefaults(FALSE);
+ mvfd(p[1], 1);
+ close(p[0]);
+ redirq = NULL;
+ walk(n, FALSE);
+ exit(getstatus());
+ }
+ close(p[1]);
+ bq = bqinput(glom(ifs), p[0]);
+ close(p[0]);
+ rc_wait4(pid, &sp, TRUE);
+ statprint(-1, sp);
+ SIGCHK;
+ return bq;
+}
+
+extern void qredir(Node *n) {
+ Rq *next;
+ if (redirq == NULL) {
+ next = redirq = nnew(Rq);
+ } else {
+ for (next = redirq; next->n != NULL; next = next->n)
+ ;
+ next->n = nnew(Rq);
+ next = next->n;
+ }
+ next->r = n;
+ next->n = NULL;
+}
+
+#ifdef NOCMDARG
+static List *mkcmdarg(Node *n) {
+ rc_error("named pipes are not supported");
+ return NULL;
+}
+#else
+#ifndef DEVFD
+static List *mkcmdarg(Node *n) {
+ int fd;
+ char *name;
+ Edata efifo;
+ Estack *e = enew(Estack);
+ List *ret = nnew(List);
+ static int fifonumber = 0;
+ name = nprint("%s/rc%d.%d", TMPDIR, getpid(), fifonumber++);
+ if (mknod(name, S_IFIFO | 0666, 0) < 0) {
+ uerror("mknod");
+ return NULL;
+ }
+ if (rc_fork() == 0) {
+ setsigdefaults(FALSE);
+ fd = rc_open(name, (n->u[0].i != rFrom) ? rFrom : rCreate); /* stupid hack */
+ if (fd < 0) {
+ uerror("open");
+ exit(1);
+ }
+ if (mvfd(fd, (n->u[0].i == rFrom)) < 0) /* same stupid hack */
+ exit(1);
+ redirq = NULL;
+ walk(n->u[2].p, FALSE);
+ exit(getstatus());
+ }
+ efifo.name = name;
+ except(eFifo, efifo, e);
+ ret->w = name;
+ ret->m = NULL;
+ ret->n = NULL;
+ return ret;
+}
+#else
+static List *mkcmdarg(Node *n) {
+ char *name;
+ List *ret = nnew(List);
+ Estack *e = nnew(Estack);
+ Edata efd;
+ int p[2];
+ if (pipe(p) < 0) {
+ uerror("pipe");
+ return NULL;
+ }
+ if (rc_fork() == 0) {
+ setsigdefaults(FALSE);
+ if (mvfd(p[n->u[0].i == rFrom], n->u[0].i == rFrom) < 0) /* stupid hack */
+ exit(1);
+ close(p[n->u[0].i != rFrom]);
+ redirq = NULL;
+ walk(n->u[2].p, FALSE);
+ exit(getstatus());
+ }
+ name = nprint("/dev/fd/%d", p[n->u[0].i != rFrom]);
+ efd.fd = p[n->u[0].i != rFrom];
+ except(eFd, efd, e);
+ close(p[n->u[0].i == rFrom]);
+ ret->w = name;
+ ret->m = NULL;
+ ret->n = NULL;
+ return ret;
+}
+#endif /* DEVFD */
+#endif /* !NOCMDARG */
+
+extern List *glom(Node *n) {
+ List *v, *head, *tail;
+ Node *words;
+ if (n == NULL)
+ return NULL;
+ switch (n->type) {
+ case nArgs:
+ case nLappend:
+ words = n->u[0].p;
+ tail = NULL;
+ while (words != NULL && (words->type == nArgs || words->type == nLappend)) {
+ if (words->u[1].p != NULL && words->u[1].p->type != nWord && words->u[1].p->type != nQword)
+ break;
+ head = glom(words->u[1].p);
+ if (head != NULL) {
+ head->n = tail;
+ tail = head;
+ }
+ words = words->u[0].p;
+ }
+ v = append(glom(words), tail); /* force left to right evaluation */
+ return append(v, glom(n->u[1].p));
+ case nBackq:
+ return backq(n->u[0].p, n->u[1].p);
+ case nConcat:
+ head = glom(n->u[0].p); /* force left-to-right evaluation */
+ return concat(head, glom(n->u[1].p));
+ case nDup:
+ case nRedir:
+ qredir(n);
+ return NULL;
+ case nWord:
+ case nQword:
+ return word(n->u[0].s, n->u[1].s);
+ case nNmpipe:
+ return mkcmdarg(n);
+ default:
+ /*
+ The next four operations depend on the left-child of glom
+ to be a variable name. Therefore the variable is looked up
+ here.
+ */
+ if ((v = glom(n->u[0].p)) == NULL)
+ rc_error("null variable name");
+ if (v->n != NULL)
+ rc_error("multi-word variable name");
+ if (*v->w == '\0')
+ rc_error("zero-length variable name");
+ v = (*v->w == '*' && v->w[1] == '\0') ? varlookup(v->w)->n : varlookup(v->w);
+ switch (n->type) {
+ default:
+ panic("unexpected node in glom");
+ exit(1);
+ /* NOTREACHED */
+ case nCount:
+ return count(v);
+ case nFlat:
+ return flatten(v);
+ case nVar:
+ return v;
+ case nVarsub:
+ return varsub(v, glom(n->u[1].p));
+ }
+ }
+}
diff --git a/hash.c b/hash.c
@@ -0,0 +1,302 @@
+/* hash.c: hash table support for functions and variables. */
+
+/*
+ Functions and variables are cached in both internal and external
+ form for performance. Thus a variable which is never "dereferenced"
+ with a $ is passed on to rc's children untouched. This is not so
+ important for variables, but is a big win for functions, where a call
+ to yyparse() is involved.
+*/
+
+#include "rc.h"
+#include "sigmsgs.h"
+
+static bool var_exportable(char *);
+static bool fn_exportable(char *);
+static int hash(char *, int);
+static int find(char *, Htab *, int);
+static void free_fn(Function *);
+
+Htab *fp;
+Htab *vp;
+static int fused, fsize, vused, vsize;
+static char **env;
+static int bozosize;
+static int envsize;
+static bool env_dirty = TRUE;
+static char *dead = "";
+
+#define HASHSIZE 64 /* rc was debugged with HASHSIZE == 2; 64 is about right for normal use */
+
+extern void inithash() {
+ Htab *fpp, *vpp;
+ int i;
+ fp = ealloc(sizeof(Htab) * HASHSIZE);
+ vp = ealloc(sizeof(Htab) * HASHSIZE);
+ fused = vused = 0;
+ fsize = vsize = HASHSIZE;
+ for (vpp = vp, fpp = fp, i = 0; i < HASHSIZE; i++, vpp++, fpp++)
+ vpp->name = fpp->name = NULL;
+}
+
+#define ADV() {if ((c = *s++) == '\0') break;}
+
+/* hash function courtesy of paul haahr */
+
+static int hash(char *s, int size) {
+ int c, n = 0;
+ while (1) {
+ ADV();
+ n += (c << 17) ^ (c << 11) ^ (c << 5) ^ (c >> 1);
+ ADV();
+ n ^= (c << 14) + (c << 7) + (c << 4) + c;
+ ADV();
+ n ^= (~c << 11) | ((c << 3) ^ (c >> 1));
+ ADV();
+ n -= (c << 16) | (c << 9) | (c << 2) | (c & 3);
+ }
+ if (n < 0)
+ n = ~n;
+ return n & (size - 1); /* need power of 2 size */
+}
+
+static bool rehash(Htab *ht) {
+ int i, j, size;
+ int newsize, newused;
+ Htab *newhtab;
+ if (ht == fp) {
+ if (fsize > 2 * fused)
+ return FALSE;
+ size = fsize;
+ } else {
+ if (vsize > 2 * vused)
+ return FALSE;
+ size = vsize;
+ }
+ newsize = 2 * size;
+ newhtab = ealloc(newsize * sizeof(Htab));
+ for (i = 0; i < newsize; i++)
+ newhtab[i].name = NULL;
+ for (i = newused = 0; i < size; i++)
+ if (ht[i].name != NULL && ht[i].name != dead) {
+ newused++;
+ j = hash(ht[i].name, newsize);
+ while (newhtab[j].name != NULL) {
+ j++;
+ j &= (newsize - 1);
+ }
+ newhtab[j].name = ht[i].name;
+ newhtab[j].p = ht[i].p;
+ }
+ if (ht == fp) {
+ fused = newused;
+ fp = newhtab;
+ fsize = newsize;
+ } else {
+ vused = newused;
+ vp = newhtab;
+ vsize = newsize;
+ }
+ efree(ht);
+ return TRUE;
+}
+
+#define varfind(s) find(s, vp, vsize)
+#define fnfind(s) find(s, fp, fsize)
+
+static int find(char *s, Htab *ht, int size) {
+ int h = hash(s, size);
+ while (ht[h].name != NULL && !streq(ht[h].name, s)) {
+ h++;
+ h &= size - 1;
+ }
+ return h;
+}
+
+extern void *lookup(char *s, Htab *ht) {
+ int h = find(s, ht, ht == fp ? fsize : vsize);
+ return (ht[h].name == NULL) ? NULL : ht[h].p;
+}
+
+extern Function *get_fn_place(char *s) {
+ int h = fnfind(s);
+ env_dirty = TRUE;
+ if (fp[h].name == NULL) {
+ if (rehash(fp))
+ h = fnfind(s);
+ fused++;
+ fp[h].name = ecpy(s);
+ fp[h].p = enew(Function);
+ } else
+ free_fn(fp[h].p);
+ return fp[h].p;
+}
+
+extern Variable *get_var_place(char *s, bool stack) {
+ Variable *new;
+ int h = varfind(s);
+
+ env_dirty = TRUE;
+
+ if (vp[h].name == NULL) {
+ if (rehash(vp))
+ h = varfind(s);
+ vused++;
+ vp[h].name = ecpy(s);
+ vp[h].p = enew(Variable);
+ ((Variable *)vp[h].p)->n = NULL;
+ return vp[h].p;
+ } else {
+ if (stack) { /* increase the stack by 1 */
+ new = enew(Variable);
+ new->n = vp[h].p;
+ return vp[h].p = new;
+ } else { /* trample the top of the stack */
+ new = vp[h].p;
+ efree(new->extdef);
+ listfree(new->def);
+ return new;
+ }
+ }
+}
+
+extern void delete_fn(char *s) {
+ int h = fnfind(s);
+ if (fp[h].name == NULL)
+ return; /* not found */
+ env_dirty = TRUE;
+ free_fn(fp[h].p);
+ efree(fp[h].p);
+ efree(fp[h].name);
+ if (fp[(h+1)&(fsize-1)].name == NULL) {
+ --fused;
+ fp[h].name = NULL;
+ } else {
+ fp[h].name = dead;
+ }
+}
+
+extern void delete_var(char *s, bool stack) {
+ int h = varfind(s);
+ Variable *v;
+ if (vp[h].name == NULL)
+ return; /* not found */
+ env_dirty = TRUE;
+ v = vp[h].p;
+ efree(v->extdef);
+ listfree(v->def);
+ if (v->n != NULL) { /* This is the top of a stack */
+ if (stack) { /* pop */
+ vp[h].p = v->n;
+ efree(v);
+ } else { /* else just empty */
+ v->extdef = NULL;
+ v->def = NULL;
+ }
+ } else { /* needs to be removed from the hash table */
+ efree(v);
+ efree(vp[h].name);
+ if (vp[(h+1)&(vsize-1)].name == NULL) {
+ --vused;
+ vp[h].name = NULL;
+ } else {
+ vp[h].name = dead;
+ }
+ }
+}
+
+static void free_fn(Function *f) {
+ treefree(f->def);
+ efree(f->extdef);
+}
+
+extern void initenv(char **envp) {
+ int n;
+ for (n = 0; envp[n] != NULL; n++)
+ ;
+ n++; /* one for the null terminator */
+ if (n < HASHSIZE)
+ n = HASHSIZE;
+ env = ealloc((envsize = 2 * n) * sizeof (char *));
+ for (; *envp != NULL; envp++)
+ if (strncmp(*envp, "fn_", conststrlen("fn_")) == 0) {
+ if (!dashpee)
+ fnassign_string(*envp);
+ } else {
+ if (!varassign_string(*envp)) /* add to bozo env */
+ env[bozosize++] = *envp;
+ }
+}
+
+static bool var_exportable(char *s) {
+ static char *notforexport[] = {
+ "apid", "pid", "apids", "*", "ifs"
+ };
+ int i;
+ for (i = 0; i < arraysize(notforexport); i++)
+ if (streq(s, notforexport[i]))
+ return FALSE;
+ return TRUE;
+}
+
+static bool fn_exportable(char *s) {
+ int i;
+ if (strncmp(s, "sig", conststrlen("sig")) == 0) { /* small speed hack */
+ for (i = 0; i < NUMOFSIGNALS; i++)
+ if (streq(s, signals[i].name))
+ return FALSE;
+ if (streq(s, "sigexit"))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+extern char **makeenv() {
+ int ep, i;
+ char *v;
+ if (!env_dirty)
+ return env;
+ env_dirty = FALSE;
+ ep = bozosize;
+ if (vsize + fsize + 1 + bozosize > envsize) {
+ envsize = 2 * (bozosize + vsize + fsize + 1);
+ env = erealloc(env, envsize * sizeof(char *));
+ }
+ for (i = 0; i < vsize; i++) {
+ if (vp[i].name == NULL || vp[i].name == dead || !var_exportable(vp[i].name))
+ continue;
+ v = varlookup_string(vp[i].name);
+ if (v != NULL)
+ env[ep++] = v;
+ }
+ for (i = 0; i < fsize; i++) {
+ if (fp[i].name == NULL || fp[i].name == dead || !fn_exportable(fp[i].name))
+ continue;
+ env[ep++] = fnlookup_string(fp[i].name);
+ }
+ env[ep] = NULL;
+ qsort(env, (SIZE_T) ep, sizeof(char *), starstrcmp);
+ return env;
+}
+
+extern void whatare_all_vars() {
+ int i;
+ List *s;
+ for (i = 0; i < vsize; i++)
+ if (vp[i].name != NULL && (s = varlookup(vp[i].name)) != NULL)
+ prettyprint_var(1, vp[i].name, s);
+ for (i = 0; i < fsize; i++)
+ if (fp[i].name != NULL && fp[i].name != dead)
+ prettyprint_fn(1, fp[i].name, fnlookup(fp[i].name));
+}
+
+/* fake getenv() for readline() follows: */
+
+#ifdef READLINE
+extern char *getenv(const char *name) {
+ List *s;
+ if (name == NULL || vp == NULL || (s = varlookup((char *) name)) == NULL)
+ return NULL;
+ return s->w;
+}
+#endif
diff --git a/heredoc.c b/heredoc.c
@@ -0,0 +1,156 @@
+/* heredoc.c: heredoc slurping is done here */
+
+#include "rc.h"
+
+struct Hq {
+ Node *doc;
+ char *name;
+ Hq *n;
+ bool quoted;
+} *hq;
+
+static bool dead = FALSE;
+
+/*
+ * read in a heredocument. A clever trick: skip over any partially matched end-of-file
+ * marker storing only the number of characters matched. If the whole marker is matched,
+ * return from readheredoc(). If only part of the marker is matched, copy that part into
+ * the heredocument.
+ *
+ * BUG: if the eof string contains a newline, the state can get confused, and the
+ * heredoc may continue past where it should. on the other hand, /bin/sh seems to
+ * never get out of its readheredoc() when the heredoc string contains a newline
+ */
+
+static char *readheredoc(char *eof) {
+ int c;
+ char *t, *buf, *bufend;
+ unsigned char *s;
+ SIZE_T bufsize;
+ t = buf = nalloc(bufsize = 512);
+ bufend = &buf[bufsize];
+ dead = FALSE;
+#define RESIZE(extra) { \
+ char *nbuf; \
+ bufsize = bufsize * 2 + extra; \
+ nbuf = nalloc(bufsize); \
+ memcpy(nbuf, buf, (SIZE_T) (t - buf)); \
+ t = nbuf + (t - buf); \
+ buf = nbuf; \
+ bufend = &buf[bufsize]; \
+ }
+ for (;;) {
+ print_prompt2();
+ for (s = (unsigned char *) eof; (c = gchar()) == *s; s++)
+ ;
+ if (*s == '\0' && (c == '\n' || c == EOF)) {
+ *t++ = '\0';
+ return buf;
+ }
+ if (s != (unsigned char *) eof) {
+ SIZE_T len = s - (unsigned char *) eof;
+ if (t + len >= bufend)
+ RESIZE(len);
+ memcpy(t, eof, len);
+ t += len;
+ }
+ for (;; c = gchar()) {
+ if (c == EOF) {
+ yyerror("heredoc incomplete");
+ dead = TRUE;
+ return NULL;
+ }
+ if (t + 1 >= bufend)
+ RESIZE(0);
+ *t++ = c;
+ if (c == '\n')
+ break;
+ }
+ }
+}
+
+/* parseheredoc -- turn a heredoc with variable references into a node chain */
+
+static Node *parseheredoc(char *s) {
+ int c = *s;
+ Node *result = NULL;
+ while (TRUE) {
+ Node *node;
+ switch (c) {
+ default: {
+ char *begin = s;
+ while ((c = *s++) != '\0' && c != '$')
+ ;
+ *--s = '\0';
+ node = mk(nQword, begin, NULL);
+ break;
+ }
+ case '$': {
+ char *begin = ++s, *var;
+ c = *s++;
+ if (c == '$') {
+ node = mk(nQword, "$", NULL);
+ c = *s;
+ } else {
+ SIZE_T len = 0;
+ do
+ len++;
+ while (!dnw[c = *(unsigned char *) s++]);
+ if (c == '^')
+ c = *s;
+ else
+ s--;
+ var = nalloc(len + 1);
+ var[len] = '\0';
+ memcpy(var, begin, len);
+ node = mk(nFlat, mk(nWord, var, NULL));
+ }
+ break;
+ }
+ case '\0':
+ return result;
+ }
+ result = (result == NULL) ? node : mk(nConcat, result, node);
+ }
+}
+
+/* read in heredocs when yyparse hits a newline. called from yyparse */
+
+extern int heredoc(int end) {
+ Hq *here;
+ if ((here = hq) != NULL) {
+ hq = NULL;
+ if (end) {
+ yyerror("heredoc incomplete");
+ return FALSE;
+ }
+ do {
+ Node *n = here->doc;
+ char *s = readheredoc(here->name);
+ if (dead)
+ return FALSE;
+ n->u[2].p = here->quoted ? mk(nQword, s, NULL) : parseheredoc(s);
+ n->u[0].i = rHerestring;
+ } while ((here = here->n) != NULL);
+ }
+ return TRUE;
+}
+
+/* queue pending heredocs into a queue. called from yyparse */
+
+extern int qdoc(Node *name, Node *n) {
+ Hq *new, **prev;
+ if (name->type != nWord && name->type != nQword) {
+ yyerror("eof-marker not a single literal word");
+ flushu();
+ return FALSE;
+ }
+ for (prev = &hq; (new = *prev) != NULL; prev = &new->n)
+ ;
+ *prev = new = nnew(Hq);
+ new->name = name->u[0].s;
+ new->quoted = (name->type == nQword);
+ new->doc = n;
+ new->n = NULL;
+ return TRUE;
+}
diff --git a/history/Makefile b/history/Makefile
@@ -0,0 +1,6 @@
+CC=gcc -g -O
+
+history: history.c
+ $(CC) -o history history.c
+ @echo 'Now install "history" as -, --, -p and --p in your bin directory'
+ @echo '(for example, by making them links to the same file)'
diff --git a/history/history.1 b/history/history.1
@@ -0,0 +1,234 @@
+.\" history.1
+.\"-------
+.\" See rc.1 for man page portability notes.
+.\"-------
+.\" Dd distance to space vertically before a "display"
+.\" These are what n/troff use for interparagraph distance
+.\"-------
+.if t .nr Dd .4v
+.if n .nr Dd 1v
+.\"-------
+.\" Ds begin a display, indented .5 inches from the surrounding text.
+.\"
+.\" Note that uses of Ds and De may NOT be nested.
+.\"-------
+.de Ds
+.\" .RS \\$1
+.sp \\n(Ddu
+.in +0.5i
+.nf
+..
+.\"-------
+.\" De end a display (no trailing vertical spacing)
+.\"-------
+.de De
+.fi
+.in
+.\" .RE
+..
+.\"-------
+.\" I stole the Xf macro from the -man macros on my machine (originally
+.\" "}S", I renamed it so that it won't conflict).
+.\"-------
+.\" Set Cf to the name of the constant width font.
+.\" It will be "C" or "(CW", typically.
+.\" NOTEZ BIEN the lines defining Cf must have no trailing white space:
+.\"-------
+.if t .ds Cf C
+.if n .ds Cf R
+.\"-------
+.\" Rc - Alternate Roman and Courier
+.\"-------
+.de Rc
+.Xf R \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Ic - Alternate Italic and Courier
+.\"-------
+.de Ic
+.Xf I \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Bc - Alternate Bold and Courier
+.\"-------
+.de Bc
+.Xf B \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Cr - Alternate Courier and Roman
+.\"-------
+.de Cr
+.Xf \\*(Cf R \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Ci - Alternate Courier and Italic
+.\"-------
+.de Ci
+.Xf \\*(Cf I \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Cb - Alternate Courier and Bold
+.\"-------
+.de Cb
+.Xf \\*(Cf B \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Xf - Alternate fonts
+.\"
+.\" \$1 - first font
+.\" \$2 - second font
+.\" \$3 - desired word with embedded font changes, built up by recursion
+.\" \$4 - text for first font
+.\" \$5 - \$9 - remaining args
+.\"
+.\" Every time we are called:
+.\"
+.\" If there is something in \$4
+.\" then Call ourself with the fonts switched,
+.\" with a new word made of the current word (\$3) and \$4
+.\" rendered in the first font,
+.\" and with the remaining args following \$4.
+.\" else We are done recursing. \$3 holds the desired output
+.\" word. We emit \$3, change to Roman font, and restore
+.\" the point size to the default.
+.\" fi
+.\"
+.\" Use Xi to add a little bit of space after italic text.
+.\"-------
+.de Xf
+.ds Xi
+.\"-------
+.\" I used to test for the italic font both by its font position
+.\" and its name. Now just test by its name.
+.\"
+.\" .if "\\$1"2" .if !"\\$5"" .ds Xi \^
+.\"-------
+.if "\\$1"I" .if !"\\$5"" .ds Xi \^
+.\"-------
+.\" This is my original code to deal with the recursion.
+.\" Evidently some nroffs can't deal with it.
+.\"-------
+.\" .ie !"\\$4"" \{\
+.\" . Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9"
+.\" .\}
+.\" .el \{\\$3
+.\" . ft R \" Restore the default font, since we don't know
+.\" . \" what the last font change was.
+.\" . ps 10 \" Restore the default point size, since it might
+.\" . \" have been changed by an argument to this macro.
+.\" .\}
+.\"-------
+.\" Here is more portable (though less pretty) code to deal with
+.\" the recursion.
+.\"-------
+.if !"\\$4"" .Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9"
+.if "\\$4"" \\$3\fR\s10
+..
+.TH HISTORY 1 "30 July 1991"
+.SH NAME
+\-, \-\|\-, \-p, \-\|\-p \- shell history programs
+.SH SYNOPSIS
+.B \-
+.RI [ pattern ...]
+.RI [ substitution ...]
+.SH DESCRIPTION
+This set of programs provides a crude history mechanism for the shell
+.IR rc (1).
+It is based on the v8 UNIX programs
+.IR = ,
+.IR == ,
+etc.
+.PP
+The program
+.RI `` \- ''
+runs the shell on the command it is requested to find.
+The program
+.RI `` \-\|\- ''
+edits that command first.
+The programs
+.RI `` \-p ''
+and
+.RI `` \-\|\-p ''
+are similar, except that they print the final command on their standard
+output instead of running the shell.
+.PP
+The commands work by looking for a file
+named by the environment variable
+.Cr $history ,
+and by searching for previous commands in this file.
+Old commands can be edited,
+or simply re-executed according to the rules below:
+.PP
+A command is searched for by examining the lines in
+.Cr $history
+in reverse order.
+Lines which contain a previous invocation of the history
+program itself are ignored.
+If one or more
+.I pattern
+is supplied on the command line,
+then the patterns are used as a means of
+limiting the search.
+Patterns match any substring of a previous command,
+and if more than one pattern is present then all patterns must be
+matched before a command is selected.
+.PP
+Substitutions may also be specified on the command line.
+These have the syntax:
+.Ds
+.Ic old : new
+.De
+.PP
+(Note that the
+.I old
+pattern is used as a search-limiting pattern also.)
+Substitutions happen on the first match.
+.PP
+Finally, a command may be edited in a crude line-mode fashion:
+The line to be edited is printed out, and below it the user
+supplies modifications to the command:
+.TP
+.B any character
+Replaces the character above.
+.TP
+.B space or tab
+Skips over the above character(s).
+.TP
+.B #
+Deletes one character.
+.TP
+.B %
+Replaces one character with a space.
+.TP
+.B ^
+Inserts the rest of the typed line just before the character.
+.TP
+.B $
+Deletes the rest of the line from that character on, and replaces
+it with the rest of the typed line.
+.TP
+.B +
+Appends the rest of the typed line.
+.TP
+.B \-
+Backs up to a previous command satisfying the same matching
+constraints.
+.TP
+.B end of file
+If an end-of-file is read from the keyboard by the editor,
+it aborts with exit status 1 and does not produce any output.
+.SH EXAMPLES
+The history programs work best when their output is reinterpreted by
+the shell using an
+.Cr eval
+command.
+This can be achieved by writing a shell function to perform the
+reinterpretation:
+.Ds
+.Cr "fn - -- {"
+.Cr " comm = \`{$0^p $*}"
+.Cr " if (! ~ $#comm 0) {"
+.Cr " echo $comm >[1=2]"
+.Cr " eval $comm"
+.Cr " }"
+.Cr "}"
diff --git a/history/history.c b/history/history.c
@@ -0,0 +1,333 @@
+/*
+ history.c -- primitive history mechanism
+
+ Paul Haahr & Byron Rakitzis, July 1991.
+
+ This program mimics the att v8 = and == history programs.
+ The edit() algorithm was adapted from a similar program
+ that Boyd Roberts wrote, but otherwise all the code has
+ been written from scratch.
+
+ edit() was subsequently redone by Hugh Redelmeier in order
+ to correctly deal with tab characters in the source line.
+
+ BUGS:
+ There is an implicit assumption that commands are no
+ more than 1k characters long.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char *id = "@(#) history.c 8/91";
+
+#undef FALSE
+#undef TRUE
+typedef enum { FALSE, TRUE } bool;
+
+#define CHUNKSIZE 65536
+
+static struct {
+ char *old, *new;
+} *replace;
+
+static char **search, *progname, *history;
+static char me; /* typically ':' or '-' */
+static bool editit = FALSE, printit = FALSE;
+static int nreplace = 0, nsearch = 0;
+static FILE *fp;
+
+static void *ealloc(size_t n) {
+ void *p = (void *) malloc(n);
+ if (p == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ return p;
+}
+
+static void *erealloc(void *p, size_t n) {
+ p = (void *) realloc(p, n);
+ if (p == NULL) {
+ perror("realloc");
+ exit(1);
+ }
+ return p;
+}
+
+static char *newstr() {
+ return ealloc((size_t)1024);
+}
+
+static char *basename(char *s) {
+ char *t = strrchr(s, '/');
+ return (t == NULL) ? s : t + 1;
+}
+
+/* stupid O(n^2) substring matching routine */
+
+static char *isin(char *target, char *pattern) {
+ size_t plen = strlen(pattern);
+ size_t tlen = strlen(target);
+ for (; tlen >= plen; target++, --tlen)
+ if (strncmp(target, pattern, plen) == 0)
+ return target;
+ return NULL;
+}
+
+/* replace the first match in the string with "new" */
+static char *sub(char *s, char *old, char *new) {
+ char *t, *u;
+
+ t = isin(s, old);
+ u = newstr();
+
+ *t = '\0';
+ while (*old != '\0')
+ old++, t++;
+ strcpy(u, s);
+ strcat(u, new);
+ strcat(u, t);
+ return u;
+}
+
+static char *edit(char *s) {
+ char *final, *f, *end;
+ int col;
+ bool ins;
+
+start:
+ fprintf(stderr, "%s\n", s);
+ f = final = newstr();
+ end = s + strlen(s);
+ col = 0;
+ ins = FALSE;
+
+ for (;; col++) {
+ int c = getchar();
+
+ if (c == me && col == 0) {
+ int peekc = getchar();
+ if (peekc == '\n')
+ return NULL;
+ ungetc(peekc, stdin);
+ }
+ if (c == '\n') {
+ if (col == 0)
+ return s;
+
+ while (s < end) /* copy remainder of string */
+ *f++ = *s++;
+ *f = '\0';
+ s = final;
+ goto start;
+ } else if (ins || s>=end) {
+ /* col need not be accurate -- tabs need not be interpreted */
+ *f++ = c;
+ } else {
+ switch (c) {
+ case '+':
+ while (s < end)
+ *f++ = *s++;
+ *f = '\0';
+ continue;
+ case '%':
+ c = ' ';
+ /* FALLTHROUGH */
+ default:
+ *f++ = c;
+ break;
+ case EOF:
+ exit(1);
+ /* NOTREACHED */
+ case ' ':
+ if (*s == '\t') {
+ int oldcol = col;
+
+ for (;; col++) {
+ int peekc;
+
+ if ((col&07) == 07) {
+ *f++ = '\t'; /* we spaced past a tab */
+ break;
+ }
+ peekc = getchar();
+ if (peekc != ' ') {
+ ungetc(peekc, stdin);
+ if (peekc != '\n') {
+ /* we spaced partially into a tab */
+ do {
+ *f++ = ' ';
+ oldcol++;
+ } while (oldcol <= col);
+ }
+ break;
+ }
+ }
+ } else {
+ *f++ = *s;
+ }
+ break;
+ case '#':
+ break;
+ case '$':
+ end = s; /* truncate s */
+ continue; /* skip incrementing s */
+ case '^':
+ ins = TRUE;
+ continue; /* skip incrementing s */
+ case '\t':
+ for (;; col++) {
+ if ((*f++ = s<end? *s++ : '\t') == '\t') {
+ col = col | 07; /* advance to before next tabstop */
+ }
+ if ((col&07) == 07) /* stop before tabstop */
+ break;
+ }
+ continue; /* skip incrementing s */
+ }
+ if (s<end && (*s!='\t' || (col&07)==07))
+ s++;
+ }
+ }
+}
+
+static char *readhistoryfile(char **last) {
+ char *buf;
+ size_t count, size;
+ long nread;
+
+ if ((history = getenv("history")) == NULL) {
+ fprintf(stderr, "$history not set\n");
+ exit(1);
+ }
+ fp = fopen(history, "r+");
+ if (fp == NULL) {
+ perror(history);
+ exit(1);
+ }
+
+ size = 0;
+ count = 0;
+ buf = ealloc(size = CHUNKSIZE);
+ while ((nread = fread(buf + count, sizeof (char), size - count, fp)) > 0) {
+ count += nread;
+ if (size - count == 0)
+ buf = erealloc(buf, size *= 4);
+ }
+ if (nread == -1) {
+ perror(history);
+ exit(1);
+ }
+ *last = buf + count;
+ return buf;
+}
+
+static char *getcommand() {
+ char *s, *t;
+ static char *hist = NULL, *last;
+
+ if (hist == NULL) {
+ hist = readhistoryfile(&last);
+ *--last = '\0'; /* trim final newline */
+ }
+
+again: s = last;
+ if (s < hist)
+ return NULL;
+ while (*--s != '\n')
+ if (s <= hist) {
+ last = hist - 1;
+ return hist;
+ }
+ *s = '\0';
+ last = s++;
+
+ /*
+ * if the command contains the "me" character at the start of the line
+ * or after any of [`{|()] then try again
+ */
+
+ for (t = s; *t != '\0'; t++)
+ if (*t == me) {
+ char *u = t - 1;
+ while (u >= s && (*u == ' ' || *u == '\t'))
+ --u;
+ if (u < s || *u == '`' || *u == '{' || *u == '|' || *u == '(' || *u == ')')
+ goto again;
+ }
+ return s;
+}
+
+int main(int argc, char **argv) {
+ int i;
+ char *s;
+
+ s = progname = basename(argv[0]);
+ me = *s++;
+ if (*s == me) {
+ s++;
+ editit = TRUE;
+ }
+ if (*s == 'p') {
+ s++;
+ printit = TRUE;
+ }
+/* Nahh...
+ if (*s != '\0') {
+ fprintf(stderr, "\"%s\": bad name for history program\n", progname);
+ exit(1);
+ }
+*/
+
+ if (argc > 1) {
+ replace = ealloc((argc - 1) * sizeof *replace);
+ search = ealloc((argc - 1) * sizeof *search);
+ }
+ for (i = 1; i < argc; i++)
+ if ((s = strchr(argv[i], ':')) == NULL)
+ search[nsearch++] = argv[i];
+ else {
+ *(char *)s = '\0'; /* do we confuse ps too much? */
+ replace[nreplace].old = argv[i];
+ replace[nreplace].new = s + 1;
+ nreplace++;
+ }
+
+next: s = getcommand();
+ if (s == NULL) {
+ fprintf(stderr, "command not matched\n");
+ return 1;
+ }
+ for (i = 0; i < nsearch; i++)
+ if (!isin(s, search[i]))
+ goto next;
+ for (i = 0; i < nreplace; i++)
+ if (!isin(s, replace[i].old))
+ goto next;
+ else
+ s = sub(s, replace[i].old, replace[i].new);
+ if (editit) {
+ s = edit(s);
+ if (s == NULL)
+ goto next;
+ }
+ fseek(fp, 0, 2); /* 2 == end of file. i.e., append command to $history */
+ fprintf(fp, "%s\n", s);
+ fclose(fp);
+ if (printit)
+ printf("%s\n", s);
+ else {
+ char *shell = getenv("SHELL");
+
+ if (!editit)
+ fprintf(stderr, "%s\n", s);
+ if (shell == NULL)
+ shell = "/bin/sh";
+ execl(shell, basename(shell), "-c", s, NULL);
+ perror(shell);
+ exit(1);
+ }
+ return 0;
+}
diff --git a/input.c b/input.c
@@ -0,0 +1,373 @@
+/* input.c: i/o routines for files and pseudo-files (strings) */
+
+#include <errno.h>
+#include <setjmp.h>
+#include "rc.h"
+#include "jbwrap.h"
+
+/*
+ NB: character unget is supported for up to two characters, but NOT
+ in the case of EOF. Since EOF does not fit in a char, it is easiest
+ to support only one unget of EOF.
+*/
+
+typedef struct Input {
+ inputtype t;
+ char *ibuf;
+ int fd, index, read, lineno, last;
+ bool saved, eofread;
+} Input;
+
+#define BUFSIZE ((SIZE_T) 256)
+
+#ifdef READLINE
+extern char *readline(char *);
+extern void add_history(char *);
+static char *rlinebuf;
+#endif
+
+char *prompt, *prompt2;
+bool rcrc;
+
+static int dead(void);
+static int fdgchar(void);
+static int stringgchar(void);
+static void history(void);
+static void ugdead(int);
+static void pushcommon(void);
+
+static char *inbuf;
+static SIZE_T istacksize, chars_out, chars_in;
+static bool eofread = FALSE, save_lineno = TRUE;
+static Input *istack, *itop;
+static char *histstr;
+static int histfd;
+
+static int (*realgchar)(void);
+static void (*realugchar)(int);
+
+int last;
+
+extern int gchar() {
+ if (eofread) {
+ eofread = FALSE;
+ return last = EOF;
+ }
+ return (*realgchar)();
+}
+
+extern void ugchar(int c) {
+ (*realugchar)(c);
+}
+
+static int dead() {
+ return last = EOF;
+}
+
+static void ugdead(int c) {
+ return;
+}
+
+static void ugalive(int c) {
+ if (c == EOF)
+ eofread = TRUE;
+ else
+ inbuf[--chars_out] = c;
+}
+
+/* get the next character from a string. */
+
+static int stringgchar() {
+ return last = (inbuf[chars_out] == '\0' ? EOF : inbuf[chars_out++]);
+}
+
+/* signal-safe readline wrapper */
+
+#ifdef READLINE
+#ifndef SVSIGS
+static char *rc_readline(char *prompt) {
+ char *r;
+ interrupt_happened = FALSE;
+ if (!setjmp(slowbuf.j)) {
+ slow = TRUE;
+ if (!interrupt_happened)
+ r = readline(prompt);
+ else
+ r = NULL;
+ } else
+ r = NULL;
+ slow = FALSE;
+ if (r == NULL)
+ errno = EINTR;
+ SIGCHK;
+ return r;
+}
+#else
+#define rc_readline readline
+#endif /* SVSIGS */
+#endif /* READLINE */
+
+/*
+ read a character from a file-descriptor. If GNU readline is defined, add a newline and doctor
+ the buffer to look like a regular fdgchar buffer.
+*/
+
+static int fdgchar() {
+ if (chars_out >= chars_in + 2) { /* has the buffer been exhausted? if so, replenish it */
+ while (1) {
+#ifdef READLINE
+ if (interactive && istack->fd == 0) {
+ rlinebuf = readline(prompt);
+ if (rlinebuf == NULL) {
+ chars_in = 0;
+ } else {
+ if (*rlinebuf != '\0')
+ add_history(rlinebuf);
+ chars_in = strlen(rlinebuf) + 1;
+ efree(inbuf);
+ inbuf = ealloc(chars_in + 3);
+ strcpy(inbuf+2, rlinebuf);
+ strcat(inbuf+2, "\n");
+ }
+ } else
+#endif
+ {
+ long /*ssize_t*/ r = rc_read(istack->fd, inbuf + 2, BUFSIZE);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue; /* Suppose it was interrupted by a signal */
+ uerror("read");
+ rc_exit(1);
+ }
+ chars_in = (SIZE_T) r;
+ }
+ break;
+ }
+ if (chars_in == 0)
+ return last = EOF;
+ chars_out = 2;
+ if (dashvee)
+ writeall(2, inbuf + 2, chars_in);
+ history();
+ }
+ return last = inbuf[chars_out++];
+}
+
+/* set up the input stack, and put a "dead" input at the bottom, so that yyparse will always read eof */
+
+extern void initinput() {
+ istack = itop = ealloc(istacksize = 256 * sizeof (Input));
+ istack->t = iFd;
+ istack->fd = -1;
+ realugchar = ugalive;
+}
+
+/* push an input source onto the stack. set up a new input buffer, and set gchar() */
+
+static void pushcommon() {
+ SIZE_T idiff;
+ istack->index = chars_out;
+ istack->read = chars_in;
+ istack->ibuf = inbuf;
+ istack->lineno = lineno;
+ istack->saved = save_lineno;
+ istack->last = last;
+ istack->eofread = eofread;
+ istack++;
+ idiff = istack - itop;
+ if (idiff >= istacksize / sizeof (Input)) {
+ itop = erealloc(itop, istacksize *= 2);
+ istack = itop + idiff;
+ }
+ realugchar = ugalive;
+ chars_out = 2;
+ chars_in = 0;
+}
+
+extern void pushfd(int fd) {
+ pushcommon();
+ istack->t = iFd;
+ save_lineno = TRUE;
+ istack->fd = fd;
+ realgchar = fdgchar;
+ inbuf = ealloc(BUFSIZE + 2);
+ lineno = 1;
+}
+
+extern void pushstring(char **a, bool save) {
+ pushcommon();
+ istack->t = iString;
+ save_lineno = save;
+ inbuf = mprint("..%A", a);
+ realgchar = stringgchar;
+ if (save_lineno)
+ lineno = 1;
+ else
+ --lineno;
+}
+
+/* remove an input source from the stack. restore the right kind of getchar (string,fd) etc. */
+
+extern void popinput() {
+ if (istack->t == iFd)
+ close(istack->fd);
+ efree(inbuf);
+ --istack;
+ realgchar = (istack->t == iString ? stringgchar : fdgchar);
+ if (istack->fd == -1) { /* top of input stack */
+ realgchar = dead;
+ realugchar = ugdead;
+ }
+ last = istack->last;
+ eofread = istack->eofread;
+ inbuf = istack->ibuf;
+ chars_out = istack->index;
+ chars_in = istack->read;
+ if (save_lineno)
+ lineno = istack->lineno;
+ else
+ lineno++;
+ save_lineno = istack->saved;
+}
+
+/* flush input characters upto newline. Used by scanerror() */
+
+extern void flushu() {
+ int c;
+ if (last == '\n' || last == EOF)
+ return;
+ while ((c = gchar()) != '\n' && c != EOF)
+ ; /* skip to newline */
+ if (c == EOF)
+ ugchar(c);
+}
+
+/* the wrapper loop in rc: prompt for commands until EOF, calling yyparse and walk() */
+
+extern Node *doit(bool execit) {
+ bool eof;
+ Jbwrap j;
+ Estack e1, e2;
+ Edata jerror;
+ if (dashen)
+ execit = FALSE;
+ setjmp(j.j);
+ jerror.jb = &j;
+ except(eError, jerror, &e1);
+ for (eof = FALSE; !eof;) {
+ Edata block;
+ block.b = newblock();
+ except(eArena, block, &e2);
+ SIGCHK;
+ if (dashell) {
+ char *fname[3];
+ fname[1] = concat(varlookup("home"), word("/.rcrc", NULL))->w;
+ fname[2] = NULL;
+ rcrc = TRUE;
+ dashell = FALSE;
+ b_dot(fname);
+ }
+ if (interactive) {
+ List *s;
+ if (!dashen && fnlookup("prompt") != NULL) {
+ static char *arglist[] = { "prompt", NULL };
+ funcall(arglist);
+ }
+ if ((s = varlookup("prompt")) != NULL) {
+#ifdef READLINE
+ prompt = s->w;
+#else
+ fprint(2, "%s", s->w);
+#endif
+ prompt2 = (s->n == NULL ? "" : s->n->w);
+ }
+ }
+ inityy();
+ if (yyparse() == 1 && execit)
+ rc_raise(eError);
+ eof = (last == EOF); /* "last" can be clobbered during a walk() */
+ if (parsetree != NULL) {
+ if (execit)
+ walk(parsetree, TRUE);
+ else if (dashex && dashen)
+ fprint(2, "%T\n", parsetree);
+ }
+ unexcept(); /* eArena */
+ }
+ popinput();
+ unexcept(); /* eError */
+ return parsetree;
+}
+
+/* parse a function imported from the environment */
+
+extern Node *parseline(char *extdef) {
+ int i = interactive;
+ char *in[2];
+ Node *fun;
+ in[0] = extdef;
+ in[1] = NULL;
+ interactive = FALSE;
+ pushstring(in, TRUE);
+ fun = doit(FALSE);
+ interactive = i;
+ return fun;
+}
+
+/* write last command out to a file. Check to see if $history has changed, also */
+
+static void history() {
+ List *histlist;
+ SIZE_T a;
+ if (!interactive)
+ return;
+ if ((histlist = varlookup("history")) == NULL) {
+ if (histstr != NULL) {
+ efree(histstr);
+ close(histfd);
+ histstr = NULL;
+ }
+ return;
+ }
+ if (histstr == NULL || !streq(histstr, histlist->w)) { /* open new file */
+ if (histstr != NULL) {
+ efree(histstr);
+ close(histfd);
+ }
+ histstr = ecpy(histlist->w);
+ histfd = rc_open(histstr, rAppend);
+ if (histfd < 0) {
+ uerror(histstr);
+ efree(histstr);
+ histstr = NULL;
+ varrm("history", FALSE);
+ }
+ }
+ /*
+ Small unix hack: since read() reads only up to a newline
+ from a terminal, then presumably this write() will write at
+ most only one input line at a time.
+ */
+ for (a = 2; a < chars_in + 2; a++) { /* skip empty lines and comments in history. */
+ if (inbuf[a] == '#' || inbuf[a] == '\n')
+ return;
+ if (inbuf[a] != ' ' && inbuf[a] != '\t')
+ break;
+ }
+ writeall(histfd, inbuf + 2, chars_in);
+}
+
+/* close file descriptors after a fork() */
+
+extern void closefds() {
+ Input *i;
+ if (histstr != NULL) { /* Close an open history file */
+ close(histfd);
+ histstr = NULL; /* But prevent re-closing of the same file-descriptor */
+ }
+ for (i = istack; i != itop; --i) /* close open scripts */
+ if (i->t == iFd && i->fd > 2) {
+ close(i->fd);
+ i->fd = -1;
+ }
+}
diff --git a/jbwrap.h b/jbwrap.h
@@ -0,0 +1,10 @@
+/* certain braindamaged environments don't define jmp_buf as an array, so... */
+
+struct Jbwrap {
+ jmp_buf j;
+};
+
+extern Jbwrap slowbuf; /* for getting out of interrupts while performing slow i/o on BSD */
+
+extern int setjmp(jmp_buf);
+extern void longjmp(jmp_buf, int);
diff --git a/lex.c b/lex.c
@@ -0,0 +1,398 @@
+/* lex.c: rc's lexical analyzer */
+
+#include "rc.h"
+#include "y.tab.h"
+
+/*
+ Special characters (i.e., "non-word") in rc:
+ \t \n # ; & | ^ $ = ~ ` ' { } @ ! ( ) < > \
+
+ The lexical analyzer is fairly straightforward. The only really
+ unclean part concerns backslash continuation and "double
+ backslashes". A backslash followed by a newline is treated as a
+ space, otherwise backslash is not a special characeter (i.e.,
+ it can be part of a word). This introduces a host of unwanted
+ special cases. In our case, \ cannot be a word character, since
+ we wish to read in all word characters in a tight loop.
+
+ Note: to save the trouble of declaring these arrays with TRUEs
+ and FALSEs, I am assuming that FALSE = 0, TRUE = 1. (and so is
+ it declared in rc.h)
+*/
+
+#define BUFSIZE ((SIZE_T) 1000) /* malloc hates power of 2 buffers? */
+#define BUFMAX (8 * BUFSIZE) /* How big the buffer can get before we re-allocate the
+ space at BUFSIZE again. Premature optimization? Maybe.
+ */
+
+typedef enum wordstates {
+ NW, RW, KW /* "nonword", "realword", "keyword" */
+} wordstates;
+
+static void getpair(int);
+
+int lineno;
+
+const char nw[] = {
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const char dnw[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static SIZE_T bufsize = BUFSIZE;
+static char *realbuf = NULL;
+static bool newline = FALSE;
+static bool errset = FALSE;
+static bool prerror = FALSE;
+static wordstates w = NW;
+static int fd_left, fd_right;
+
+#define checkfreecaret {if (w != NW) { w = NW; ugchar(c); return '^'; }}
+
+enum filedescriptors {
+ UNSET = -9, CLOSED = -1
+};
+
+extern int yylex() {
+ static bool dollar = FALSE;
+ bool saw_meta = FALSE;
+ int c;
+ SIZE_T i; /* The purpose of all these local assignments is to */
+ const char *meta; /* allow optimizing compilers like gcc to load these */
+ char *buf = realbuf; /* values into registers. On a sparc this is a */
+ YYSTYPE *y = &yylval; /* win, in code size *and* execution time */
+ if (errset) {
+ errset = FALSE;
+ return '\n';
+ }
+ /* rc variable-names may contain only alnum, '*' and '_', so use dnw if we are scanning one. */
+ meta = (dollar ? dnw : nw);
+ dollar = FALSE;
+ if (newline) {
+ --lineno; /* slight space optimization; print_prompt2() always increments lineno */
+ print_prompt2();
+ newline = FALSE;
+ }
+top: while ((c = gchar()) == ' ' || c == '\t')
+ w = NW;
+ if (c == EOF)
+ return END;
+ if (!meta[(unsigned char) c]) { /* it's a word or keyword. */
+ checkfreecaret;
+ w = RW;
+ i = 0;
+ read: do {
+ buf[i++] = c;
+ if (c == '?' || c == '[' || c == '*')
+ saw_meta = TRUE;
+ if (i >= bufsize)
+ buf = realbuf = erealloc(buf, bufsize *= 2);
+ } while ((c = gchar()) != EOF && !meta[(unsigned char) c]);
+ while (c == '\\') {
+ if ((c = gchar()) == '\n') {
+ print_prompt2();
+ c = ' '; /* Pretend a space was read */
+ break;
+ } else {
+ bs: if (meta != dnw) { /* all words but varnames may have a bslash */
+ buf[i++] = '\\';
+ if (i >= bufsize)
+ buf = realbuf = erealloc(buf, bufsize *= 2);
+ if (!meta[(unsigned char) c])
+ goto read;
+ } else {
+ ugchar(c);
+ c = '\\';
+ break;
+ }
+ }
+ }
+ ugchar(c);
+ buf[i] = '\0';
+ w = KW;
+ if (i == 2) {
+ if (*buf == 'i' && buf[1] == 'f') return IF;
+ if (*buf == 'f' && buf[1] == 'n') return FN;
+ if (*buf == 'i' && buf[1] == 'n') return IN;
+ }
+ if (streq(buf, "for")) return FOR;
+ if (streq(buf, "else")) return ELSE;
+ if (streq(buf, "switch")) return SWITCH;
+ if (streq(buf, "while")) return WHILE;
+ if (streq(buf, "case")) return CASE;
+ w = RW;
+ y->word.w = ncpy(buf);
+ if (saw_meta) {
+ char *r, *s;
+
+ y->word.m = nalloc(strlen(buf) + 1);
+ for (r = buf, s = y->word.m; *r != '\0'; r++, s++)
+ *s = (*r == '?' || *r == '[' || *r == '*');
+ } else {
+ y->word.m = NULL;
+ }
+ return WORD;
+ }
+ if (c == '`' || c == '!' || c == '@' || c == '~' || c == '$' || c == '\'') {
+ checkfreecaret;
+ if (c == '!' || c == '@' || c == '~')
+ w = KW;
+ }
+ switch (c) {
+ case '\0':
+ pr_error("warning: null character ignored");
+ goto top;
+ case '!':
+ return BANG;
+ case '@':
+ return SUBSHELL;
+ case '~':
+ return TWIDDLE;
+ case '`':
+ c = gchar();
+ if (c == '`')
+ return BACKBACK;
+ ugchar(c);
+ return '`';
+ case '$':
+ dollar = TRUE;
+ c = gchar();
+ if (c == '#')
+ return COUNT;
+ if (c == '^')
+ return FLAT;
+ ugchar(c);
+ return '$';
+ case '\'':
+ w = RW;
+ i = 0;
+ do {
+ buf[i++] = c;
+ if (c == '\n')
+ print_prompt2();
+ if (c == EOF) {
+ w = NW;
+ scanerror("eof in quoted string");
+ return HUH;
+ }
+ if (i >= bufsize)
+ buf = realbuf = erealloc(buf, bufsize *= 2);
+ } while ((c = gchar()) != '\'' || (c = gchar()) == '\''); /* quote "'" thus: 'how''s it going?' */
+ ugchar(c);
+ buf[i] = '\0';
+ y->word.w = ncpy(buf);
+ y->word.m = NULL;
+ return WORD;
+ case '\\':
+ if ((c = gchar()) == '\n') {
+ print_prompt2();
+ goto top; /* Pretend it was just another space. */
+ }
+ ugchar(c);
+ c = '\\';
+ checkfreecaret;
+ c = gchar();
+ i = 0;
+ goto bs;
+ case '(':
+ if (w == RW) /* SUB's happen only after real words, not keyowrds, so if () and while () work */
+ c = SUB;
+ w = NW;
+ return c;
+ case '#':
+ while ((c = gchar()) != '\n') /* skip comment until newline */
+ if (c == EOF)
+ return END;
+ /* FALLTHROUGH */
+ case '\n':
+ lineno++;
+ newline = TRUE;
+ /* FALLTHROUGH */
+ case ';':
+ case '^':
+ case ')':
+ case '=':
+ case '{': case '}':
+ w = NW;
+ return c;
+ case '&':
+ w = NW;
+ c = gchar();
+ if (c == '&')
+ return ANDAND;
+ ugchar(c);
+ return '&';
+ case '|':
+ w = NW;
+ c = gchar();
+ if (c == '|')
+ return OROR;
+ getpair(c);
+ if (errset)
+ return HUH;
+ if ((y->pipe.left = fd_left) == UNSET)
+ y->pipe.left = 1; /* default to fd 1 */
+ if ((y->pipe.right = fd_right) == UNSET)
+ y->pipe.right = 0; /* default to fd 0 */
+ if (y->pipe.right == CLOSED) {
+ scanerror("expected digit after '='"); /* can't close a pipe */
+ return HUH;
+ }
+ return PIPE;
+ case '>':
+ c = gchar();
+ if (c == '>') {
+ c = gchar();
+ y->redir.type = rAppend;
+ } else
+ y->redir.type = rCreate;
+ y->redir.fd = 1;
+ goto common;
+ case '<':
+ c = gchar();
+ if (c == '<') {
+ c = gchar();
+ if (c == '<') {
+ c = gchar();
+ y->redir.type = rHerestring;
+ } else {
+ y->redir.type = rHeredoc;
+ }
+ } else
+ y->redir.type = rFrom;
+ y->redir.fd = 0;
+ common:
+ w = NW;
+ getpair(c);
+ if (errset)
+ return HUH;
+ if (fd_right == UNSET) { /* redirection, not dup */
+ if (fd_left != UNSET) {
+ y->redir.fd = fd_left;
+ return SREDIR;
+ }
+ return (y->redir.type == rFrom || y->redir.type == rCreate) ? REDIR : SREDIR;
+ } else { /* dup; recast yylval */
+ y->dup.type = y->redir.type;
+ y->dup.left = fd_left;
+ y->dup.right = fd_right;
+ return DUP;
+ }
+ default:
+ w = NW;
+ return c; /* don't know what it is, let yacc barf on it */
+ }
+}
+
+extern void yyerror(const char *s) {
+ char *tok;
+ if (prerror) { /* don't print "syntax error" if there's a more informative scanerror */
+ prerror = FALSE;
+ return;
+ }
+ if (!interactive) {
+ if (w != NW)
+ tok = realbuf;
+ else if (last == EOF)
+ tok = "eof";
+ else if (last == '\n')
+ tok = "end of line";
+ else
+ tok = nprint((last < 32 || last > 126) ? "(decimal %d)" : "'%c'", last);
+ fprint(2, "line %d: %s near %s\n", lineno - (last == '\n'), s, tok);
+ } else
+ fprint(2, "%s\n", s);
+}
+
+extern void scanerror(char *s) {
+ flushu(); /* flush upto newline */
+ yyerror(s);
+ errset = prerror = TRUE;
+}
+
+extern void inityy() {
+ newline = FALSE;
+ w = NW;
+ hq = NULL;
+ /* return memory to the system if the buffer got too large */
+ if (bufsize > BUFMAX && realbuf != NULL) {
+ efree(realbuf);
+ bufsize = BUFSIZE;
+ realbuf = ealloc(bufsize);
+ } else if (realbuf == NULL)
+ realbuf = ealloc(bufsize);
+}
+
+extern void print_prompt2() {
+ lineno++;
+#ifdef READLINE
+ prompt = prompt2;
+#else
+ if (interactive)
+ fprint(2, "%s", prompt2);
+#endif
+}
+
+/*
+ Scan in a pair of integers for redirections like >[2=1]. CLOSED represents a closed file
+ descriptor (i.e., >[2=]) and UNSET represents an undesignated file descriptor (e.g.,
+ >[2] is represented as (2,UNSET).
+
+ This function makes use of unsigned compares to make range tests in one compare operation.
+*/
+
+static void getpair(int c) {
+ int n;
+ fd_left = fd_right = UNSET;
+ if (c != '[') {
+ ugchar(c);
+ return;
+ }
+ if ((unsigned int) (n = gchar() - '0') > 9) {
+ scanerror("expected digit after '['");
+ return;
+ }
+ while ((unsigned int) (c = gchar() - '0') <= 9)
+ n = n * 10 + c;
+ fd_left = n;
+ c += '0';
+ switch (c) {
+ default:
+ scanerror("expected '=' or ']' after digit");
+ return;
+ case ']':
+ return;
+ case '=':
+ if ((unsigned int) (n = gchar() - '0') > 9) {
+ if (n != ']' - '0') {
+ scanerror("expected digit or ']' after '='");
+ return;
+ }
+ fd_right = CLOSED;
+ } else {
+ while ((unsigned int) (c = gchar() - '0') <= 9)
+ n = n * 10 + c;
+ if (c != ']' - '0') {
+ scanerror("expected ']' after digit");
+ return;
+ }
+ fd_right = n;
+ }
+ }
+}
diff --git a/list.c b/list.c
@@ -0,0 +1,57 @@
+/* list.c: routines for manipulating the List type */
+
+#include "rc.h"
+
+/*
+ These list routines assign meta values of null to the resulting lists;
+ it is impossible to glob with the value of a variable unless this value
+ is rescanned with eval---therefore it is safe to throw away the meta-ness
+ of the list.
+*/
+
+/* free a list from malloc space */
+
+extern void listfree(List *p) {
+ while (p != NULL) {
+ List *n = p->n;
+ efree(p->w);
+ efree(p);
+ p = n;
+ }
+}
+
+/* Copy list into malloc space (for storing a variable) */
+
+extern List *listcpy(List *s, void *(*alloc)(SIZE_T)) {
+ List *top, *r;
+ for (top = r = NULL; s != NULL; s = s->n) {
+ if (top == NULL)
+ r = top = (*alloc)(sizeof (List));
+ else
+ r = r->n = (*alloc)(sizeof (List));
+ r->w = (*alloc)(strlen(s->w) + 1);
+ strcpy(r->w, s->w);
+ r->m = NULL;
+ }
+ if (r != NULL)
+ r->n = NULL;
+ return top;
+}
+
+/* Length of list */
+
+extern SIZE_T listlen(List *s) {
+ SIZE_T size;
+ for (size = 0; s != NULL; s = s->n)
+ size += strlen(s->w) + 1;
+ return size;
+}
+
+/* Number of elements in list */
+
+extern int listnel(List *s) {
+ int nel;
+ for (nel = 0; s != NULL; s = s->n)
+ nel++;
+ return nel;
+}
diff --git a/main.c b/main.c
@@ -0,0 +1,115 @@
+/* main.c: handles initialization of rc and command line options */
+
+#include "rc.h"
+
+bool dashdee, dashee, dashvee, dashex, dashell, dasheye,
+ dashen, dashpee, interactive;
+int rc_pid;
+
+static bool dashoh;
+
+static void assigndefault(char *,...);
+static void checkfd(int, enum redirtype);
+
+extern void main(int argc, char *argv[], char *envp[]) {
+ char *dashsee[2], *dollarzero, *null[1];
+ int c;
+ initprint();
+ dashsee[0] = dashsee[1] = NULL;
+ dollarzero = argv[0];
+ rc_pid = getpid();
+ dashell = (*argv[0] == '-'); /* Unix tradition */
+ while ((c = rc_getopt(argc, argv, "nolpeivdxc:")) != -1)
+ switch (c) {
+ case 'l':
+ dashell = TRUE;
+ break;
+ case 'e':
+ dashee = TRUE;
+ break;
+ case 'i':
+ dasheye = interactive = TRUE;
+ break;
+ case 'v':
+ dashvee = TRUE;
+ break;
+ case 'x':
+ dashex = TRUE;
+ break;
+ case 'd':
+ dashdee = TRUE;
+ break;
+ case 'c':
+ dashsee[0] = rc_optarg;
+ goto quitopts;
+ case 'n':
+ dashen = TRUE;
+ break;
+ case 'p':
+ dashpee = TRUE;
+ break;
+ case 'o':
+ dashoh = TRUE;
+ break;
+ case '?':
+ exit(1);
+ }
+quitopts:
+ argv += rc_optind;
+ /* use isatty() iff -i is not set, and iff the input is not from a script or -c flag */
+ if (!dasheye && dashsee[0] == NULL && *argv == NULL)
+ interactive = isatty(0);
+ if (!dashoh) {
+ checkfd(0, rFrom);
+ checkfd(1, rCreate);
+ checkfd(2, rCreate);
+ }
+ initsignal();
+ inithash();
+ initparse();
+ assigndefault("prompt", "; ", "", (void *)0);
+#ifdef DEFAULTPATH
+ assigndefault("path", DEFAULTPATH, (void *)0);
+#endif
+ assigndefault("ifs", " ", "\t", "\n", (void *)0);
+ assigndefault("pid", nprint("%d", rc_pid), (void *)0);
+ initenv(envp);
+ initinput();
+ null[0] = NULL;
+ starassign(dollarzero, null, FALSE); /* assign $0 to $* */
+ inithandler();
+ if (dashsee[0] != NULL) { /* input from the -c flag? */
+ if (*argv != NULL)
+ starassign(dollarzero, argv, FALSE);
+ pushstring(dashsee, TRUE);
+ } else if (*argv != NULL) { /* else from a file? */
+ b_dot(--argv);
+ rc_exit(getstatus());
+ } else { /* else stdin */
+ pushfd(0);
+ }
+ dasheye = FALSE;
+ doit(TRUE);
+ rc_exit(getstatus());
+}
+
+static void assigndefault(char *name,...) {
+ va_list ap;
+ List *l;
+ char *v;
+ va_start(ap, name);
+ for (l = NULL; (v = va_arg(ap, char *)) != NULL;)
+ l = append(l, word(v, NULL));
+ varassign(name, l, FALSE);
+ if (streq(name, "path"))
+ alias(name, l, FALSE);
+ va_end(ap);
+}
+
+/* open an fd on /dev/null if it is inherited closed */
+
+static void checkfd(int fd, enum redirtype r) {
+ int new = rc_open("/dev/null", r);
+ if (new != fd && new != -1)
+ close(new);
+}
diff --git a/match.c b/match.c
@@ -0,0 +1,99 @@
+/* match.c: pattern matching routines */
+
+#include "rc.h"
+
+static int rangematch(char *, char);
+
+enum { RANGE_FAIL = -1, RANGE_ERROR = -2 };
+
+/* match() matches a single pattern against a single string. */
+
+extern bool match(char *p, char *m, char *s) {
+ int i, j;
+ if (m == NULL)
+ return streq(p, s);
+ i = 0;
+ while (1) {
+ if (p[i] == '\0')
+ return *s == '\0';
+ else if (m[i]) {
+ switch (p[i++]) {
+ case '?':
+ if (*s++ == '\0')
+ return FALSE;
+ break;
+ case '*':
+ while (p[i] == '*' && m[i] == 1) /* collapse multiple stars */
+ i++;
+ if (p[i] == '\0') /* star at end of pattern? */
+ return TRUE;
+ while (*s != '\0')
+ if (match(p + i, m + i, s++))
+ return TRUE;
+ return FALSE;
+ case '[':
+ if (*s == '\0')
+ return FALSE;
+ switch (j = rangematch(p + i, *s)) {
+ default:
+ i += j;
+ break;
+ case RANGE_FAIL:
+ return FALSE;
+ case RANGE_ERROR:
+ if (*s != '[')
+ return FALSE;
+ }
+ s++;
+ break;
+ default:
+ panic("bad metacharacter in match");
+ /* NOTREACHED */
+ return FALSE; /* hush up gcc -Wall */
+ }
+ } else if (p[i++] != *s++)
+ return FALSE;
+ }
+}
+
+/*
+ From the ed(1) man pages (on ranges):
+
+ The `-' is treated as an ordinary character if it occurs first
+ (or first after an initial ^) or last in the string.
+
+ The right square bracket does not terminate the enclosed string
+ if it is the first character (after an initial `^', if any), in
+ the bracketed string.
+
+ rangematch() matches a single character against a class, and returns
+ an integer offset to the end of the range on success, or -1 on
+ failure.
+*/
+
+static int rangematch(char *p, char c) {
+ char *orig = p;
+ bool neg = (*p == '~');
+ bool matched = FALSE;
+ if (neg)
+ p++;
+ if (*p == ']') {
+ p++;
+ matched = (c == ']');
+ }
+ for (; *p != ']'; p++) {
+ if (*p == '\0')
+ return RANGE_ERROR; /* bad syntax */
+ if (p[1] == '-' && p[2] != ']') { /* check for [..-..] but ignore [..-] */
+ if (c >= *p)
+ matched |= (c <= p[2]);
+ p += 2;
+ } else {
+ matched |= (*p == c);
+ }
+ }
+ if (matched ^ neg)
+ return p - orig + 1; /* skip the right-bracket */
+ else
+ return RANGE_FAIL;
+}
diff --git a/mksignal b/mksignal
@@ -0,0 +1,75 @@
+#!/bin/sh
+# generate rc's internal signal table from signal.h
+
+exec > sigmsgs.c
+
+echo '#include "sigmsgs.h"'
+echo
+echo 'Sigmsgs signals[] = {'
+
+sed ' s/\/\*[ ]*//
+ s/[ ]*\*\///
+ s/([@*+!]) //
+ s/[ ]*([a-zA-Z,->& ]*)[ ]*//
+ s/^[ ]*\#[ ]*define/\#define/
+ s/[ ]*signal$//' $1 |
+awk '
+ BEGIN {
+ # assign to nomesg["SIGNAME"] to suppress a long message
+ nomesg["SIGINT"] = 1
+ nomesg["SIGPIPE"] = 1
+ # assign to mesg["SIGNAME"] to override a message
+ mesg["SIGHUP"] = "hangup"
+ mesg["SIGKILL"] = "killed"
+ mesg["SIGQUIT"] = "quit"
+ mesg["SIGTERM"] = "terminated"
+ mesg["SIGURG"] = "urgent condition on i/o channel"
+ mesg["SIGSTOP"] = "stop signal not from tty"
+ mesg["SIGTSTP"] = "stopped"
+ mesg["SIGCONT"] = "continue"
+ mesg["SIGCHLD"] = "child stop or exit"
+ mesg["SIGTTIN"] = "background tty read"
+ mesg["SIGTTOU"] = "background tty write"
+ # assign to ignore["SIGNAME"] to explicitly ignore a named signal
+ ignore["SIGMAX"] = 1
+ }
+ $1 == "#define" && $2 == "NSIG" && $3 ~ /^[0-9]+$/ { nsig = $3 }
+ $1 == "#define" && $2 ~ /^SIG/ && $3 ~ /^[0-9]+$/ && sig[$3] == "" && ignore[$2] == 0 {
+ sig[$3] = $2
+ if ($3 > max)
+ max = $3
+ if (mesg[$2] == "" && nomesg[$2] == 0) {
+ str = $4
+ for (i = 5; i <= NF; i++)
+ str = str " " $i
+ mesg[$2] = str
+ }
+ }
+ END {
+ if (nsig == 0)
+ nsig = max + 1
+ printf " {!!, !!},\n"
+ for (i = 1; i < nsig; i++) {
+ if (sig[i] == "")
+ printf " {!!, !!},\n"
+ else
+ printf " {!%s!, !%s!},\n", sig[i], mesg[sig[i]]
+ }
+ }
+' |
+tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!' 'abcdefghijklmnopqrstuvwxyz"'
+
+echo '};'
+
+exec > sigmsgs.h
+
+echo 'typedef struct {'
+echo ' char *name, *msg;'
+echo '} Sigmsgs;'
+echo 'extern Sigmsgs signals[];'
+
+grep '^ ' sigmsgs.c | # the thing in quotes is ^<tab>
+awk '
+ { sum = sum + 1; }
+ END { print "#define NUMOFSIGNALS", sum }
+'
diff --git a/nalloc.c b/nalloc.c
@@ -0,0 +1,137 @@
+/* nalloc.c: a simple single-arena allocator for command-line-lifetime allocation */
+#include "rc.h"
+
+static struct Block {
+ SIZE_T used, size;
+ char *mem;
+ Block *n;
+} *fl, *ul;
+
+/* alignto() works only with power of 2 blocks and assumes 2's complement arithmetic */
+#define alignto(m, n) ((m + n - 1) & ~(n - 1))
+#define BLOCKSIZE ((SIZE_T) 4096)
+
+/* Allocate a block from the free list or malloc one if none in the fl fit */
+
+static void getblock(SIZE_T n) {
+ Block *r, *p;
+ for (r = fl, p = NULL; r != NULL; p = r, r = r->n)
+ if (n <= r->size)
+ break; /* look for a block which fits the request */
+ if (r != NULL) { /* if one is found, take it off the free list */
+ if (p != NULL)
+ p->n = r->n;
+ else
+ fl = r->n;
+ } else { /* else allocate a new block */
+ r = enew(Block);
+ r->mem = ealloc(r->size = alignto(n, BLOCKSIZE));
+ }
+ r->used = 0;
+ r->n = ul;
+ ul = r;
+}
+
+/*
+ A fast single-arena allocator. Looks at the current block, and if
+ there is not enough room, it goes to getblock() for more. "ul"
+ stands for "used list", and the head of the list is the current
+ block. "ulp" is a register cache for "ul"; this routine is hacked
+ for speed. (sigh, optimizing RISC compilers still can't cache the
+ address of a global in a register)
+*/
+
+extern void *nalloc(SIZE_T n) {
+ SIZE_T base;
+ Block *ulp;
+ n = alignto(n, sizeof (ALIGN_T));
+ ulp = ul;
+ if (ulp != NULL && n + (base = ulp->used) < ulp->size) {
+ ulp->used = base + n;
+ return &ulp->mem[base];
+ } else {
+ getblock(n); /* assert(ul->used) == 0 */
+ (ulp = ul)->used = n;
+ return &ulp->mem[0];
+ }
+}
+
+/*
+ Frees memory from nalloc space by putting it on the free list.
+ Returns free blocks to the system, retaining at least MAXMEM bytes
+ worth of blocks for nalloc.
+*/
+
+#define MAXMEM 500000
+
+extern void nfree() {
+ SIZE_T count;
+ Block *r;
+ if (ul == NULL)
+ return;
+ for (r = ul; r->n != NULL; r = r->n)
+ ; /* get to end of used list */
+ r->n = fl; /* tack free list onto it */
+ fl = ul; /* and make it the free list */
+ ul = NULL; /* finally, zero out the used list */
+ for (r = fl, count = r->size; r->n != NULL; r = r->n, count += r->size) {
+ if (count >= MAXMEM) {
+ Block *tmp = r;
+ r = r->n;
+ tmp->n = NULL; /* terminate the free list */
+ while (r != NULL) { /* free memory off the tail of the free list */
+ tmp = r->n;
+ efree(r->mem);
+ efree(r);
+ r = tmp;
+ }
+ return;
+ }
+ }
+}
+
+/*
+ "Allocates" a new arena by zeroing out the old one. Up to the
+ calling routine to keep the old value of the block around.
+*/
+
+extern Block *newblock() {
+ Block *old = ul;
+ ul = NULL;
+ return old;
+}
+
+/* "Restores" an arena to its saved value. */
+
+extern void restoreblock(Block *old) {
+ nfree();
+ ul = old;
+}
+
+/* generic memory allocation functions */
+
+extern void *ealloc(SIZE_T n) {
+ extern void *malloc(SIZE_T);
+ void *p = malloc(n);
+ if (p == NULL) {
+ uerror("malloc");
+ rc_exit(1);
+ }
+ return p;
+}
+
+extern void *erealloc(void *p, SIZE_T n) {
+ extern void *realloc(void *, SIZE_T);
+ if ((p = realloc(p, n)) == NULL) {
+ uerror("realloc");
+ rc_exit(1);
+ }
+ return p;
+}
+
+extern void efree(void *p) {
+ extern void free(void *);
+ if (p != NULL)
+ free(p);
+}
+
diff --git a/open.c b/open.c
@@ -0,0 +1,29 @@
+/* open.c: to insulate <fcntl.h> from the rest of rc. */
+
+#include <fcntl.h>
+#include "rc.h"
+
+/* prototype for open() follows. comment out if necessary */
+
+/*extern int open(const char *, int,...);*/
+
+/*
+ Opens a file with the necessary flags. Assumes the following
+ declaration for redirtype:
+
+ enum redirtype {
+ rFrom, rCreate, rAppend, rHeredoc, rHerestring
+ };
+*/
+
+static const int mode_masks[] = {
+ /* rFrom */ O_RDONLY,
+ /* rCreate */ O_TRUNC | O_CREAT | O_WRONLY,
+ /* rAppend */ O_APPEND | O_CREAT | O_WRONLY
+};
+
+extern int rc_open(const char *name, redirtype m) {
+ if ((unsigned) m >= arraysize(mode_masks))
+ panic("bad mode passed to rc_open");
+ return open(name, mode_masks[m], 0666);
+}
diff --git a/parse.y b/parse.y
@@ -0,0 +1,174 @@
+/* parse.y */
+
+/*
+ * Adapted from rc grammar, v10 manuals, volume 2.
+ */
+
+%{
+#include "rc.h"
+#ifndef lint
+#define lint /* hush up gcc -Wall, leave out the dumb sccsid's. */
+#endif
+static Node *star, *nolist;
+Node *parsetree; /* not using yylval because bison declares it as an auto */
+%}
+
+%token ANDAND BACKBACK BANG CASE COUNT DUP ELSE END FLAT FN FOR IF IN
+%token OROR PIPE REDIR SREDIR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD HUH
+
+%left WHILE ')' ELSE
+%left ANDAND OROR '\n'
+%left BANG SUBSHELL
+%left PIPE
+%right '$'
+%left SUB
+/*
+*/
+
+%union {
+ struct Node *node;
+ struct Redir redir;
+ struct Pipe pipe;
+ struct Dup dup;
+ struct Word word;
+ char *keyword;
+}
+
+%type <redir> REDIR SREDIR
+%type <pipe> PIPE
+%type <dup> DUP
+%type <word> WORD
+%type <keyword> keyword
+%type <node> assign body brace case cbody cmd cmdsa cmdsan comword epilog
+ first line nlwords paren redir sword simple iftail word words
+
+%start rc
+
+%%
+
+rc : line end { parsetree = $1; YYACCEPT; }
+ | error end { yyerrok; parsetree = NULL; YYABORT; }
+
+/* an rc line may end in end-of-file as well as newline, e.g., rc -c 'ls' */
+end : END /* EOF */ { if (!heredoc(1)) YYABORT; } /* flag error if there is a heredoc in the queue */
+ | '\n' { if (!heredoc(0)) YYABORT; } /* get heredoc on \n */
+
+/* a cmdsa is a command followed by ampersand or newline (used in "line" and "body") */
+cmdsa : cmd ';'
+ | cmd '&' { $$ = ($1 != NULL ? mk(nNowait,$1) : $1); }
+
+/* a line is a single command, or a command terminated by ; or & followed by a line (recursive) */
+line : cmd
+ | cmdsa line { $$ = ($1 != NULL ? mk(nBody,$1,$2) : $2); }
+
+/* a body is like a line, only commands may also be terminated by newline */
+body : cmd
+ | cmdsan body { $$ = ($1 == NULL ? $2 : $2 == NULL ? $1 : mk(nBody,$1,$2)); }
+
+cmdsan : cmdsa
+ | cmd '\n' { $$ = $1; if (!heredoc(0)) YYABORT; } /* get h.d. on \n */
+
+brace : '{' body '}' { $$ = $2; }
+
+paren : '(' body ')' { $$ = $2; }
+
+assign : first '=' word { $$ = mk(nAssign,$1,$3); }
+
+epilog : { $$ = NULL; }
+ | redir epilog { $$ = mk(nEpilog,$1,$2); }
+
+/* a redirection is a dup (e.g., >[1=2]) or a file redirection. (e.g., > /dev/null) */
+redir : DUP { $$ = mk(nDup,$1.type,$1.left,$1.right); }
+ | REDIR word { $$ = mk(nRedir,$1.type,$1.fd,$2);
+ if ($1.type == rHeredoc && !qdoc($2, $$)) YYABORT; /* queue heredocs up */
+ }
+ | SREDIR word { $$ = mk(nRedir,$1.type,$1.fd,$2);
+ if ($1.type == rHeredoc && !qdoc($2, $$)) YYABORT; /* queue heredocs up */
+ }
+
+case : CASE words ';' { $$ = mk(nCase, $2); }
+ | CASE words '\n' { $$ = mk(nCase, $2); }
+
+cbody : cmd { $$ = mk(nCbody, $1, NULL); }
+ | case cbody { $$ = mk(nCbody, $1, $2); }
+ | cmdsan cbody { $$ = mk(nCbody, $1, $2); }
+
+iftail : cmd %prec ELSE
+ | brace ELSE optnl cmd { $$ = mk(nElse,$1,$4); }
+
+cmd : /* empty */ %prec WHILE { $$ = NULL; }
+ | simple
+ | brace epilog { $$ = mk(nBrace,$1,$2); }
+ | IF paren optnl iftail { $$ = mk(nIf,$2,$4); }
+ | FOR '(' word IN words ')' optnl cmd { $$ = mk(nForin,$3,$5,$8); }
+ | FOR '(' word ')' optnl cmd { $$ = mk(nForin,$3,star,$6); }
+ | WHILE paren optnl cmd { $$ = mk(nWhile,$2,$4); }
+ | SWITCH '(' word ')' optnl '{' cbody '}' { $$ = mk(nSwitch,$3,$7); }
+ | TWIDDLE optcaret word words { $$ = mk(nMatch,$3,$4); }
+ | cmd ANDAND optnl cmd { $$ = mk(nAndalso,$1,$4); }
+ | cmd OROR optnl cmd { $$ = mk(nOrelse,$1,$4); }
+ | cmd PIPE optnl cmd { $$ = mk(nPipe,$2.left,$2.right,$1,$4); }
+ | redir cmd %prec BANG { $$ = ($2 != NULL ? mk(nPre,$1,$2) : $1); }
+ | assign cmd %prec BANG { $$ = ($2 != NULL ? mk(nPre,$1,$2) : $1); }
+ | BANG optcaret cmd { $$ = mk(nBang,$3); }
+ | SUBSHELL optcaret cmd { $$ = mk(nSubshell,$3); }
+ | FN words brace { $$ = mk(nNewfn,$2,$3); }
+ | FN words { $$ = mk(nRmfn,$2); }
+
+optcaret : /* empty */
+ | '^'
+
+simple : first
+ | simple word { $$ = ($2 != NULL ? mk(nArgs,$1,$2) : $1); }
+ | simple redir { $$ = mk(nArgs,$1,$2); }
+
+first : comword
+ | first '^' sword { $$ = mk(nConcat,$1,$3); }
+
+sword : comword
+ | keyword { $$ = mk(nWord,$1, NULL); }
+
+word : sword
+ | word '^' sword { $$ = mk(nConcat,$1,$3); }
+
+comword : '$' sword { $$ = mk(nVar,$2); }
+ | '$' sword SUB words ')' { $$ = mk(nVarsub,$2,$4); }
+ | COUNT sword { $$ = mk(nCount,$2); }
+ | FLAT sword { $$ = mk(nFlat, $2); }
+ | '`' sword { $$ = mk(nBackq,nolist,$2); }
+ | '`' brace { $$ = mk(nBackq,nolist,$2); }
+ | BACKBACK word brace { $$ = mk(nBackq,$2,$3); }
+ | BACKBACK word sword { $$ = mk(nBackq,$2,$3); }
+ | '(' nlwords ')' { $$ = $2; }
+ | REDIR brace { $$ = mk(nNmpipe,$1.type,$1.fd,$2); }
+ | WORD { $$ = ($1.w[0] == '\'') ? mk(nQword, $1.w+1, NULL) : mk(nWord,$1.w, $1.m); }
+
+keyword : FOR { $$ = "for"; }
+ | IN { $$ = "in"; }
+ | WHILE { $$ = "while"; }
+ | IF { $$ = "if"; }
+ | SWITCH { $$ = "switch"; }
+ | FN { $$ = "fn"; }
+ | ELSE { $$ = "else"; }
+ | CASE { $$ = "case"; }
+ | TWIDDLE { $$ = "~"; }
+ | BANG { $$ = "!"; }
+ | SUBSHELL { $$ = "@"; }
+
+words : { $$ = NULL; }
+ | words word { $$ = ($1 != NULL ? ($2 != NULL ? mk(nLappend,$1,$2) : $1) : $2); }
+
+nlwords : { $$ = NULL; }
+ | nlwords '\n'
+ | nlwords word { $$ = ($1 != NULL ? ($2 != NULL ? mk(nLappend,$1,$2) : $1) : $2); }
+
+optnl : /* empty */
+ | optnl '\n'
+
+%%
+
+void initparse() {
+ star = treecpy(mk(nVar,mk(nWord,"*",NULL)), ealloc);
+ nolist = treecpy(mk(nVar,mk(nWord,"ifs",NULL)), ealloc);
+}
+
diff --git a/print.c b/print.c
@@ -0,0 +1,456 @@
+/* print.c -- formatted printing routines (Paul Haahr, 12/91) */
+
+#include "rc.h"
+#include <setjmp.h>
+
+#define PRINT_ALLOCSIZE ((SIZE_T)64)
+#define SPRINT_BUFSIZ ((SIZE_T)1024)
+
+#define MAXCONV 256
+
+/*
+ * conversion functions
+ * true return -> flag changes only, not a conversion
+ */
+
+#define Flag(name, flag) \
+static bool name(Format *format, int c) { \
+ format->flags |= flag; \
+ return TRUE; \
+}
+
+Flag(uconv, FMT_unsigned)
+Flag(hconv, FMT_short)
+Flag(lconv, FMT_long)
+Flag(altconv, FMT_altform)
+Flag(leftconv, FMT_leftside)
+Flag(dotconv, FMT_f2set)
+
+static bool digitconv(Format *format, int c) {
+ if (format->flags & FMT_f2set)
+ format->f2 = 10 * format->f2 + c - '0';
+ else {
+ format->flags |= FMT_f1set;
+ format->f1 = 10 * format->f1 + c - '0';
+ }
+ return TRUE;
+}
+
+static bool zeroconv(Format *format, int c) {
+ if (format->flags & (FMT_f1set | FMT_f2set))
+ return digitconv(format, '0');
+ format->flags |= FMT_zeropad;
+ return TRUE;
+}
+
+static void pad(Format *format, SIZE_T len, int c) {
+ while (len-- != 0)
+ fmtputc(format, c);
+}
+
+static bool sconv(Format *format, int c) {
+ char *s = va_arg(format->args, char *);
+ if ((format->flags & FMT_f1set) == 0)
+ fmtcat(format, s);
+ else {
+ SIZE_T len = strlen(s), width = format->f1 - len;
+ if (format->flags & FMT_leftside) {
+ fmtappend(format, s, len);
+ pad(format, width, ' ');
+ } else {
+ pad(format, width, ' ');
+ fmtappend(format, s, len);
+ }
+ }
+ return FALSE;
+}
+
+static char *utoa(unsigned long u, char *t, unsigned int radix, const char *digit) {
+ if (u >= radix) {
+ t = utoa(u / radix, t, radix, digit);
+ u %= radix;
+ }
+ *t++ = digit[u];
+ return t;
+}
+
+static void intconv(Format *format, unsigned int radix, int upper, const char *altform) {
+ static const char * const table[] = {
+ "0123456789abcdefghijklmnopqrstuvwxyz",
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ };
+ char padchar;
+ SIZE_T len, pre, zeroes, padding, width;
+ long n, flags;
+ unsigned long u;
+ char number[64], prefix[20];
+
+ if (radix > 36)
+ return;
+
+ flags = format->flags;
+ if (flags & FMT_long)
+ n = va_arg(format->args, long);
+ else if (flags & FMT_short)
+ n = va_arg(format->args, short);
+ else
+ n = va_arg(format->args, int);
+
+ pre = 0;
+ if ((flags & FMT_unsigned) || n >= 0)
+ u = n;
+ else {
+ prefix[pre++] = '-';
+ u = -n;
+ }
+
+ if (flags & FMT_altform)
+ while (*altform != '\0')
+ prefix[pre++] = *altform++;
+
+ len = utoa(u, number, radix, table[upper]) - number;
+ if ((flags & FMT_f2set) && (SIZE_T) format->f2 > len)
+ zeroes = format->f2 - len;
+ else
+ zeroes = 0;
+
+ width = pre + zeroes + len;
+ if ((flags & FMT_f1set) && (SIZE_T) format->f1 > width) {
+ padding = format->f1 - width;
+ } else
+ padding = 0;
+
+ padchar = ' ';
+ if (padding > 0 && flags & FMT_zeropad) {
+ padchar = '0';
+ if ((flags & FMT_leftside) == 0) {
+ zeroes += padding;
+ padding = 0;
+ }
+ }
+
+
+ if ((flags & FMT_leftside) == 0)
+ pad(format, padding, padchar);
+ fmtappend(format, prefix, pre);
+ pad(format, zeroes, '0');
+ fmtappend(format, number, len);
+ if (flags & FMT_leftside)
+ pad(format, padding, padchar);
+}
+
+static bool cconv(Format *format, int c) {
+ fmtputc(format, va_arg(format->args, int));
+ return FALSE;
+}
+
+static bool dconv(Format *format, int c) {
+ intconv(format, 10, 0, "");
+ return FALSE;
+}
+
+static bool oconv(Format *format, int c) {
+ intconv(format, 8, 0, "0");
+ return FALSE;
+}
+
+static bool xconv(Format *format, int c) {
+ intconv(format, 16, 0, "0x");
+ return FALSE;
+}
+
+static bool pctconv(Format *format, int c) {
+ fmtputc(format, '%');
+ return FALSE;
+}
+
+static bool badconv(Format *format, int c) {
+ panic("bad conversion character in printfmt");
+ /* NOTREACHED */
+ return FALSE; /* hush up gcc -Wall */
+}
+
+
+/*
+ * conversion table management
+ */
+
+static Conv fmttab[MAXCONV];
+
+static void inittab(void) {
+ int i;
+ for (i = 0; i < MAXCONV; i++)
+ fmttab[i] = badconv;
+
+ fmttab['s'] = sconv;
+ fmttab['c'] = cconv;
+ fmttab['d'] = dconv;
+ fmttab['o'] = oconv;
+ fmttab['x'] = xconv;
+ fmttab['%'] = pctconv;
+
+ fmttab['u'] = uconv;
+ fmttab['h'] = hconv;
+ fmttab['l'] = lconv;
+ fmttab['#'] = altconv;
+ fmttab['-'] = leftconv;
+ fmttab['.'] = dotconv;
+
+ fmttab['0'] = zeroconv;
+ for (i = '1'; i <= '9'; i++)
+ fmttab[i] = digitconv;
+}
+
+extern bool (*fmtinstall(int c, bool (*f)(Format *, int)))(Format *, int) {
+/*Conv fmtinstall(int c, Conv f) {*/
+ Conv oldf;
+ if (fmttab[0] == NULL)
+ inittab();
+ c &= MAXCONV - 1;
+ oldf = fmttab[c];
+ if (f != NULL)
+ fmttab[c] = f;
+ return oldf;
+}
+
+
+/*
+ * functions for inserting strings in the format buffer
+ */
+
+extern void fmtappend(Format *format, const char *s, SIZE_T len) {
+ while (format->buf + len > format->bufend) {
+ SIZE_T split = format->bufend - format->buf;
+ memcpy(format->buf, s, split);
+ format->buf += split;
+ s += split;
+ len -= split;
+ (*format->grow)(format, len);
+ }
+ memcpy(format->buf, s, len);
+ format->buf += len;
+}
+
+extern void fmtcat(Format *format, const char *s) {
+ fmtappend(format, s, strlen(s));
+}
+
+/*
+ * printfmt -- the driver routine
+ */
+
+extern int printfmt(Format *format, const char *fmt) {
+ unsigned const char *s = (unsigned const char *) fmt;
+
+ if (fmttab[0] == NULL)
+ inittab();
+
+ for (;;) {
+ int c = *s++;
+ switch (c) {
+ case '%':
+ format->flags = format->f1 = format->f2 = 0;
+ do
+ c = *s++;
+ while ((*fmttab[c])(format, c));
+ break;
+ case '\0':
+ return format->buf - format->bufbegin + format->flushed;
+ default:
+ fmtputc(format, c);
+ break;
+ }
+ }
+}
+
+
+/*
+ * the public entry points
+ */
+
+extern int fmtprint(Format *format, const char *fmt,...) {
+ int n = -format->flushed;
+ va_list saveargs = format->args;
+
+ va_start(format->args, fmt);
+ n += printfmt(format, fmt);
+ va_end(format->args);
+ format->args = saveargs;
+
+ return n + format->flushed;
+}
+
+static void fprint_flush(Format *format, SIZE_T more) {
+ SIZE_T n = format->buf - format->bufbegin;
+ char *buf = format->bufbegin;
+
+ format->flushed += n;
+ format->buf = format->bufbegin;
+ writeall(format->u.n, buf, n);
+}
+
+extern int fprint(int fd, const char *fmt,...) {
+ char buf[1024];
+ Format format;
+
+ format.buf = buf;
+ format.bufbegin = buf;
+ format.bufend = buf + sizeof buf;
+ format.grow = fprint_flush;
+ format.flushed = 0;
+ format.u.n = fd;
+
+ va_start(format.args, fmt);
+ printfmt(&format, fmt);
+ va_end(format.args);
+
+ fprint_flush(&format, (SIZE_T) 0);
+ return format.flushed;
+}
+
+static void memprint_grow(Format *format, SIZE_T more) {
+ char *buf;
+ SIZE_T len = format->bufend - format->bufbegin + 1;
+ len = (len >= more)
+ ? len * 2
+ : ((len + more) + PRINT_ALLOCSIZE) &~ (PRINT_ALLOCSIZE - 1);
+ if (format->u.n)
+ buf = erealloc(format->bufbegin, len);
+ else {
+ SIZE_T used = format->buf - format->bufbegin;
+ buf = nalloc(len);
+ memcpy(buf, format->bufbegin, used);
+ }
+ format->buf = buf + (format->buf - format->bufbegin);
+ format->bufbegin = buf;
+ format->bufend = buf + len - 1;
+}
+
+static char *memprint(Format *format, const char *fmt, char *buf, SIZE_T len) {
+ format->buf = buf;
+ format->bufbegin = buf;
+ format->bufend = buf + len - 1;
+ format->grow = memprint_grow;
+ format->flushed = 0;
+ printfmt(format, fmt);
+ *format->buf = '\0';
+ return format->bufbegin;
+}
+
+extern char *mprint(const char *fmt,...) {
+ Format format;
+ char *result;
+ format.u.n = 1;
+ va_start(format.args, fmt);
+ result = memprint(&format, fmt, ealloc(PRINT_ALLOCSIZE), PRINT_ALLOCSIZE);
+ va_end(format.args);
+ return result;
+}
+
+extern char *nprint(const char *fmt,...) {
+ Format format;
+ char *result;
+ format.u.n = 0;
+ va_start(format.args, fmt);
+ result = memprint(&format, fmt, nalloc(PRINT_ALLOCSIZE), PRINT_ALLOCSIZE);
+ va_end(format.args);
+ return result;
+}
+
+
+/* THESE ARE UNUSED IN rc */
+
+#if 0
+
+extern int print(const char *fmt,...) {
+ char buf[1024];
+ Format format;
+
+ format.buf = buf;
+ format.bufbegin = buf;
+ format.bufend = buf + sizeof buf;
+ format.grow = fprint_flush;
+ format.flushed = 0;
+ format.u.n = 1;
+
+ va_start(format.args, fmt);
+ printfmt(&format, fmt);
+ va_end(format.args);
+
+ fprint_flush(&format, 0);
+ return format.flushed;
+}
+
+extern int eprint(const char *fmt,...) {
+ char buf[1024];
+ Format format;
+
+ format.buf = buf;
+ format.bufbegin = buf;
+ format.bufend = buf + sizeof buf;
+ format.grow = fprint_flush;
+ format.flushed = 0;
+ format.u.n = 2;
+
+ va_start(format.args, fmt);
+ printfmt(&format, fmt);
+ va_end(format.args);
+
+ fprint_flush(&format, 0);
+ return format.flushed;
+}
+
+static void snprint_grow(Format *format, SIZE_T more) {
+ longjmp(format->u.p, 1);
+}
+
+extern int snprint(char *buf, int buflen, const char *fmt,...) {
+ int n;
+ jmp_buf jbuf;
+ Format format;
+
+ if (setjmp(jbuf)) {
+ *format.buf = '\0';
+ return format.buf - format.bufbegin;
+ }
+
+ format.buf = buf;
+ format.bufbegin = buf;
+ format.bufend = buf + buflen - 1;
+ format.grow = snprint_grow;
+ format.flushed = 0;
+ format.u.p = jbuf;
+
+ va_start(format.args, fmt);
+ n = printfmt(&format, fmt);
+ va_end(format.args);
+
+ *format.buf = '\0';
+ return n;
+}
+
+extern int sprint(char *buf, const char *fmt,...) {
+ int n;
+ jmp_buf jbuf;
+ Format format;
+
+ if (setjmp(jbuf)) {
+ *format.buf = '\0';
+ return format.buf - format.bufbegin;
+ }
+
+ format.buf = buf;
+ format.bufbegin = buf;
+ format.bufend = buf + SPRINT_BUFSIZ - 1;
+ format.grow = snprint_grow;
+ format.flushed = 0;
+ format.u.p = jbuf;
+
+ va_start(format.args, fmt);
+ n = printfmt(&format, fmt);
+ va_end(format.args);
+
+ *format.buf = '\0';
+ return n;
+}
+#endif
diff --git a/proto.h b/proto.h
@@ -0,0 +1,81 @@
+/* proto.h
+ This file provides a definition for size_t and align_t that
+ should work for your system. If it does not, it is up to you to
+ make it the right thing. The problem is that I cannot rely upon
+ <sys/params.h> to do the right thing on machines which don't
+ yet have ansi header files. Note that on many RISC machines,
+ align_t must be at least 32 bits wide, and sparc doubles are
+ aligned on 64 bit boundaries, but of course rc does not use
+ doubles in its code, so the "typedef long ALIGN_T" is good
+ enough in the sparc's case. Also for performance reasons on a
+ VAX one would probably want align_t to be 32 bits wide.
+
+ You can override these definitions with compile-line definitions
+ of the same macros.
+*/
+
+#ifndef ALIGN_T
+typedef long ALIGN_T;
+#endif
+#ifndef SIZE_T
+typedef unsigned int SIZE_T;
+#endif
+#ifndef MODE_T
+typedef short int MODE_T;
+#endif
+#ifndef PID_T
+typedef int PID_T;
+#endif
+#ifndef SIG_ATOMIC_T
+typedef int SIG_ATOMIC_T;
+#endif
+
+/* fake stdlib.h */
+
+extern void exit(int);
+extern void qsort(void *, SIZE_T, SIZE_T, int (*)(const void *, const void *));
+
+/* fake string.h */
+
+extern int strncmp(const char *, const char *, SIZE_T);
+extern int strcmp(const char *, const char *);
+extern SIZE_T strlen(const char *);
+extern char *strchr(const char *, int);
+extern char *strrchr(const char *, int);
+extern char *strcpy(char *, const char *);
+extern char *strncpy(char *, const char *, SIZE_T);
+extern char *strcat(char *, const char *);
+extern char *strncat(char *, const char *, SIZE_T);
+extern void *memcpy(void *, const void *, SIZE_T);
+
+/* fake unistd.h */
+
+extern PID_T fork(void);
+extern PID_T getpid(void);
+extern char *getenv(const char *);
+extern int chdir(const char *);
+extern int close(int);
+extern int dup(int);
+extern int dup2(int, int);
+extern int execve(const char *, const char **, const char **);
+extern int execl(const char *,...);
+extern int getegid(void);
+extern int geteuid(void);
+extern int getgroups(int, int *);
+/*extern int ioctl(int, long,...);*/ /* too much trouble leaving this uncommented */
+extern int isatty(int);
+#ifndef SYSVR4 /* declares AND defines this in sys/stat.h!! */
+extern int mknod(const char *, int, int);
+#endif
+extern int pipe(int *);
+extern int read(int, void *, unsigned int);
+extern int setpgrp(int, PID_T);
+extern int unlink(const char *);
+extern int wait(int *);
+extern int write(int, const void *, unsigned int);
+
+/* fake errno.h for mips (which doesn't declare errno in errno.h!?!?) */
+
+#ifdef host_mips
+extern int errno;
+#endif
diff --git a/rc.1 b/rc.1
@@ -0,0 +1,1880 @@
+.\" rc.1
+.\"-------
+.\" Man page portability notes
+.\"
+.\" These are some notes on conventions to maintain for greatest
+.\" portability of this man page to various other versions of
+.\" nroff.
+.\"
+.\" When you want a \ to appear in the output, use \e in the man page.
+.\" (NOTE this comes up in the rc grammar, where to print out '\n' the
+.\" man page must contain '\en'.)
+.\"
+.\" Evidently not all versions of nroff allow the omission of the
+.\" terminal " on a macro argument. Thus what could be written
+.\"
+.\" .Cr "exec >[2] err.out
+.\"
+.\" in true nroffs must be written
+.\"
+.\" .Cr "exec >[2] err.out"
+.\"
+.\" instead.
+.\"
+.\" Use symbolic font names (e.g. R, I, B) instead of the standard
+.\" font positions 1, 2, 3. Note that for Xf to work the standard
+.\" font names must be single characters.
+.\"
+.\" Not all man macros have the RS and RE requests (I altered the Ds
+.\" and De macros and the calls to Ds accordingly).
+.\"
+.\" Thanks to Michael Haardt (u31b3hs@cip-s01.informatik.rwth-aachen.de)
+.\" for pointing out these problems.
+.\"
+.\" Note that sentences should end at the end of a line. nroff and
+.\" troff will supply the correct intersentence spacing, but only if
+.\" the sentences end at the end of a line. Explicit spaces, if given,
+.\" are apparently honored and the normal intersentence spacing is
+.\" supressed.
+.\"
+.\" DaviD W. Sanderson
+.\"-------
+.\" Dd distance to space vertically before a "display"
+.\" These are what n/troff use for interparagraph distance
+.\"-------
+.if t .nr Dd .4v
+.if n .nr Dd 1v
+.\"-------
+.\" Ds begin a display, indented .5 inches from the surrounding text.
+.\"
+.\" Note that uses of Ds and De may NOT be nested.
+.\"-------
+.de Ds
+.\" .RS \\$1
+.sp \\n(Ddu
+.in +0.5i
+.nf
+..
+.\"-------
+.\" De end a display (no trailing vertical spacing)
+.\"-------
+.de De
+.fi
+.in
+.\" .RE
+..
+.\"-------
+.\" I stole the Xf macro from the -man macros on my machine (originally
+.\" "}S", I renamed it so that it won't conflict).
+.\"-------
+.\" Set Cf to the name of the constant width font.
+.\" It will be "C" or "(CW", typically.
+.\" NOTEZ BIEN the lines defining Cf must have no trailing white space:
+.\"-------
+.if t .ds Cf C
+.if n .ds Cf R
+.\"-------
+.\" Rc - Alternate Roman and Courier
+.\"-------
+.de Rc
+.Xf R \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Ic - Alternate Italic and Courier
+.\"-------
+.de Ic
+.Xf I \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Bc - Alternate Bold and Courier
+.\"-------
+.de Bc
+.Xf B \\*(Cf \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Cr - Alternate Courier and Roman
+.\"-------
+.de Cr
+.Xf \\*(Cf R \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Ci - Alternate Courier and Italic
+.\"-------
+.de Ci
+.Xf \\*(Cf I \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Cb - Alternate Courier and Bold
+.\"-------
+.de Cb
+.Xf \\*(Cf B \& "\\$1" "\\$2" "\\$3" "\\$4" "\\$5" "\\$6"
+..
+.\"-------
+.\" Xf - Alternate fonts
+.\"
+.\" \$1 - first font
+.\" \$2 - second font
+.\" \$3 - desired word with embedded font changes, built up by recursion
+.\" \$4 - text for first font
+.\" \$5 - \$9 - remaining args
+.\"
+.\" Every time we are called:
+.\"
+.\" If there is something in \$4
+.\" then Call ourself with the fonts switched,
+.\" with a new word made of the current word (\$3) and \$4
+.\" rendered in the first font,
+.\" and with the remaining args following \$4.
+.\" else We are done recursing. \$3 holds the desired output
+.\" word. We emit \$3, change to Roman font, and restore
+.\" the point size to the default.
+.\" fi
+.\"
+.\" Use Xi to add a little bit of space after italic text.
+.\"-------
+.de Xf
+.ds Xi
+.\"-------
+.\" I used to test for the italic font both by its font position
+.\" and its name. Now just test by its name.
+.\"
+.\" .if "\\$1"2" .if !"\\$5"" .ds Xi \^
+.\"-------
+.if "\\$1"I" .if !"\\$5"" .ds Xi \^
+.\"-------
+.\" This is my original code to deal with the recursion.
+.\" Evidently some nroffs can't deal with it.
+.\"-------
+.\" .ie !"\\$4"" \{\
+.\" . Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9"
+.\" .\}
+.\" .el \{\\$3
+.\" . ft R \" Restore the default font, since we don't know
+.\" . \" what the last font change was.
+.\" . ps 10 \" Restore the default point size, since it might
+.\" . \" have been changed by an argument to this macro.
+.\" .\}
+.\"-------
+.\" Here is more portable (though less pretty) code to deal with
+.\" the recursion.
+.\"-------
+.if !"\\$4"" .Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9"
+.if "\\$4"" \\$3\fR\s10
+..
+.TH RC 1 "28 April 1991"
+.SH NAME
+rc \- shell
+.SH SYNOPSIS
+.B rc
+.RB [ \-eixvldnpo ]
+.RB [ \-c
+.IR command ]
+.RI [ arguments ]
+.SH DESCRIPTION
+.I rc
+is a command interpreter and programming language similar to
+.IR sh (1).
+It is based on the AT&T Plan 9 shell of the same name.
+The shell offers a C-like syntax (much more so than the C shell),
+and a powerful mechanism for manipulating variables.
+It is reasonably small and reasonably fast,
+especially when compared to contemporary shells.
+Its use is intended to be interactive,
+but the language lends itself well to scripts.
+.SH OPTIONS
+.TP
+.Cr \-e
+If the
+.Cr \-e
+option is present, then
+.I rc
+will exit if the exit status of a command is false (nonzero).
+.I rc
+will not exit, however, if a conditional fails, e.g., an
+.Cr if()
+command.
+.TP
+.Cr \-i
+If the
+.Cr \-i
+option is present or if the input to
+.I rc
+is from a terminal (as determined by
+.IR isatty (3))
+then
+.I rc
+will be in
+.I interactive
+mode.
+That is, a prompt (from
+.Cr $prompt(1)\^ )
+will be printed before an
+input line is taken, and
+.I rc
+will ignore the signals
+.Cr SIGINT
+and
+.Cr SIGQUIT .
+.TP
+.Cr \-x
+This option will make
+.I rc
+print every command on standard error before it is executed.
+It can be useful for debugging
+.I rc
+scripts.
+.TP
+.Cr \-v
+This option will echo input to
+.I rc
+on standard error as it is read.
+.TP
+.Cr \-l
+If the
+.Cr \-l
+option is present, or if
+.IR rc 's
+.Cr argv[0][0]
+is a dash
+.Rc ( \- ),
+then
+.I rc
+will behave as a login shell.
+That is, it will try to run commands present in
+.Cr $home/.rcrc ,
+if this file exists, before reading any other input.
+.TP
+.Cr \-d
+This flag causes
+.I rc
+not to ignore
+.Cr SIGQUIT
+or
+.Cr SIGTERM .
+Thus
+.I rc
+can be made to dump core if sent
+.Cr SIGQUIT .
+This option is only useful for debugging
+.IR rc .
+.TP
+.Cr \-n
+This flag causes
+.I rc
+to read its input and parse it, but not to execute any commands.
+This is useful for syntax checking on scripts.
+If used in combination with the
+.Cr \-x
+option,
+.I rc
+will print each command as it is parsed in a form similar to the one
+used for exporting functions into the environment.
+.TP
+.Cr \-p
+This flag prevents
+.I rc
+from initializing shell functions from the environment.
+This allows
+.I rc
+to run in a protected mode, whereby it becomes more difficult for
+an
+.I rc
+script to be subverted by placing false commands in the environment.
+(Note that this presence of this option does NOT mean that it is safe to
+run setuid
+.I rc
+scripts; the usual caveats about the setuid bit still apply.)
+.TP
+.Cr \-o
+This flag prevents the usual practice of trying to open
+.Cr /dev/null
+on file descriptors 0, 1, and 2, if any of those descriptors
+are inherited closed.
+.TP
+.Cr \-c
+If
+.Cr \-c
+is present, commands are executed from the immediately following
+argument.
+Any further arguments to
+.I rc
+are placed in
+.Cr $* .
+.PP
+.SH COMMANDS
+A simple command is a sequence of words, separated by white space
+(space and tab) characters that ends with a newline, semicolon
+.Rc ( ; ),
+or ampersand
+.Rc ( & ).
+The first word of a command is the name of that command.
+If the name begins with
+.Cr / ,
+.Cr ./ ,
+or
+.Cr ../ ,
+then the name is used as an absolute path
+name referring to an executable file.
+Otherwise, the name of the command is looked up in a table
+of shell functions, builtin commands,
+or as a file in the directories named by
+.Cr $path .
+.SS "Background Tasks"
+A command ending with a
+.Cr &
+is run in the background; that is,
+the shell returns immediately rather than waiting for the command to
+complete.
+Background commands have
+.Cr /dev/null
+connected to their standard input unless an explicit redirection for
+standard input is used.
+.SS "Subshells"
+A command prefixed with an at-sign
+.Rc ( @ )
+is executed in a subshell.
+This insulates the parent shell from the effects
+of state changing operations such as a
+.B cd
+or a variable assignment.
+For example:
+.Ds
+.Cr "@ {cd ..; make}"
+.De
+.PP
+will run
+.IR make (1)
+in the parent directory
+.Rc ( .. ),
+but leaves the shell running in the current directory.
+.SS "Line continuation"
+A long logical line may be continued over several physical lines by
+terminating each line (except the last) with a backslash
+.Rc ( \e ).
+The backslash-newline sequence is treated as a space.
+A backslash is not otherwise special to
+.IR rc .
+(In addition,
+inside quotes a backslash loses its special meaning
+even when it is followed by a newline.)
+.SS Quoting
+.IR rc
+interprets several characters specially; special characters
+automatically terminate words.
+The following characters are special:
+.Ds
+.Cr "# ; & | ^ $ = \` ' { } ( ) < >"
+.De
+.PP
+The single quote
+.Rc ( ' )
+prevents special treatment of any character other than itself.
+All characters, including control characters, newlines,
+and backslashes between two quote characters are treated as an
+uninterpreted string.
+A quote character itself may be quoted by placing two quotes in a row.
+The minimal sequence needed to enter the quote character is
+.Cr '''' .
+The empty string is represented by
+.Cr '' .
+Thus:
+.Ds
+.Cr "echo 'What''s the plan, Stan?'"
+.De
+.PP
+prints out
+.Ds
+.Cr "What's the plan, Stan?"
+.De
+.PP
+The number sign
+.Rc ( # )
+begins a comment in
+.IR rc .
+All characters up to but not including the next newline are ignored.
+Note that backslash continuation does not work inside a comment,
+i.e.,
+the backslash is ignored along with everything else.
+.SS Grouping
+Zero or more commands may be grouped within braces
+.Rc (`` { ''
+and
+.Rc `` } ''),
+and are then treated as one command.
+Braces do not otherwise define scope;
+they are used only for command grouping.
+In particular, be wary of the command:
+.Ds
+.Cr "for (i) {"
+.Cr " command"
+.Cr "} | command"
+.De
+.PP
+Since pipe binds tighter than
+.Cr for ,
+this command does not perform what the user expects it to.
+Instead, enclose the whole
+.Cr for
+statement in braces:
+.Ds
+.Cr "{for (i) command} | command"
+.De
+.PP
+Fortunately,
+.IR rc 's
+grammar is simple enough that a (confident) user can
+understand it by examining the skeletal
+.IR yacc (1)
+grammar
+at the end of this man page (see the section entitled
+.BR GRAMMAR ).
+.SS "Input and output"
+.PP
+The standard output may be redirected to a file with
+.Ds
+.Cr "command > file"
+.De
+.PP
+and the standard input may be taken from a file with
+.Ds
+.Cr "command < file"
+.De
+.PP
+File descriptors other than 0 and 1 may be specified also.
+For example, to redirect standard error to a file, use:
+.Ds
+.Cr "command >[2] file"
+.De
+.PP
+In order to duplicate a file descriptor, use
+.Ci >[ n = m ]\fR.
+Thus to redirect both standard output and standard error
+to the same file, use
+.Ds
+.Cr "command > file >[2=1]"
+.De
+.PP
+To close a file descriptor that may be open, use
+.Ci >[ n =]\fR.
+For example, to
+close file descriptor 7:
+.Ds
+.Cr "command >[7=]"
+.De
+.PP
+In order to place the output of a command at the end of an already
+existing file, use:
+.Ds
+.Cr "command >> file"
+.De
+.PP
+If the file does not exist, then it is created.
+.PP
+``Here documents'' are supported as in
+.I sh
+with the use of
+.Ds
+.Cr "command << 'eof-marker'"
+.De
+.PP
+If the end-of-file marker is enclosed in quotes,
+then no variable substitution occurs inside the here document.
+Otherwise, every variable is substituted
+by its space-separated-list value (see
+.BR "Flat Lists" ,
+below),
+and if a
+.Cr ^
+character follows a variable name, it is deleted.
+This allows the unambiguous use of variables adjacent to text, as in
+.Ds
+.Cr $variable^follow
+.De
+To include a literal
+.Cr $
+in a here document when an unquoted end-of-file marker is being used,
+enter it as
+.Cr $$ .
+.PP
+Additionally,
+.I rc
+supports ``here strings'', which are like here documents,
+except that input is taken directly from a string on the command line.
+Its use is illustrated here:
+.Ds
+.Cr "cat <<< 'this is a here string' | wc"
+.De
+.PP
+(This feature enables
+.I rc
+to export functions using here documents into the environment;
+the author does not expect users to find this feature useful.)
+.SS Pipes
+Two or more commands may be combined in a pipeline by placing the
+vertical bar
+.Rc ( \||\| )
+between them.
+The standard output (file descriptor 1)
+of the command on the left is tied to the standard input (file
+descriptor 0) of the command on the right.
+The notation
+.Ci |[ n = m ]
+indicates that file descriptor
+.I n
+of the left process is connected to
+file descriptor
+.I m
+of the right process.
+.Ci |[ n ]
+is a shorthand for
+.Ci |[ n =0]\fR.
+As an example, to pipe the standard error of a command to
+.IR wc (1),
+use:
+.Ds
+.Cr "command |[2] wc"
+.De
+.PP
+The exit status of a pipeline is considered true if and only if every
+command in the pipeline exits true.
+.SS "Commands as Arguments"
+Some commands, like
+.IR cmp (1)
+or
+.IR diff (1),
+take their arguments on the command
+line, and do not read input from standard input.
+It is convenient
+sometimes to build nonlinear pipelines so that a command like
+.I cmp
+can read the output of two other commands at once.
+.I rc
+does it like this:
+.Ds
+.Cr "cmp <{command} <{command}"
+.De
+.PP
+compares the output of the two commands in braces.
+A note: since this form of
+redirection is implemented with some kind of pipe, and since one cannot
+.IR lseek (2)
+on a pipe, commands that use
+.IR lseek (2)
+will hang.
+For example,
+most versions of
+.IR diff (1)
+use
+.IR lseek (2)
+on their inputs.
+.PP
+Data can be sent down a pipe to several commands using
+.IR tee (1)
+and the output version of this notation:
+.Ds
+.Cr "echo hi there | tee >{sed 's/^/p1 /'} >{sed 's/^/p2 /'}"
+.De
+.SH "CONTROL STRUCTURES"
+The following may be used for control flow in
+.IR rc :
+.SS "If-else Statements"
+.PD 0
+.sp
+.Ci "if (" test ") {"
+.br
+.I " cmd"
+.br
+.TP
+.Ci "} else " cmd
+The
+.I test
+is executed, and if its return status is zero, the first
+command is executed, otherwise the second is.
+Braces are not mandatory around the commands.
+However, an
+.Cr else
+statement is valid only if it
+follows a close-brace on the same line.
+Otherwise, the
+.Cr if
+is taken to be a simple-if:
+.Ds
+.Cr "if (test)"
+.Cr " command"
+.De
+.PD
+.SS "While and For Loops"
+.TP
+.Ci "while (" test ) " cmd"
+.I rc
+executes the
+.I test
+and performs the command as long as the
+.I test
+is true.
+.TP
+.Ci "for (" var " in" " list" ) " cmd"
+.I rc
+sets
+.I var
+to each element of
+.I list
+(which may contain variables and backquote substitutions) and runs
+.IR cmd .
+If
+.Rc `` in
+.IR list ''
+is omitted, then
+.I rc
+will set
+.I var
+to each element of
+.Cr $*
+(excluding
+.Cr $0 ).
+For example:
+.Ds
+.Cr "for (i in \`{ls -F | grep '\e*$' | sed 's/\e*$//'}) { commands }"
+.De
+.TP
+\&
+will set
+.Cr $i
+to the name of each file in the current directory that is
+executable.
+.SS "Switch"
+.TP
+.Ci "switch (" list ") { case" " ..." " }"
+.I rc
+looks inside the braces after a
+.Cr switch
+for statements beginning with the word
+.Cr case .
+If any of the patterns following
+.Cr case
+match the list supplied to
+.Cr switch ,
+then the commands up until the next
+.Cr case
+statement are executed.
+The metacharacters
+.Cr "*" ,
+.Cr [
+or
+.Cr ?
+should not be quoted;
+matching is performed only against the strings in
+.IR list ,
+not against file names.
+(Matching for case statements is the same as for the
+.Cr ~
+command.)
+.SS "Logical Operators"
+There are a number of operators in
+.I rc
+which depend on the exit status of a command.
+.Ds
+.Cr "command && command"
+.De
+.PP
+executes the first command and then executes the second command if and only if
+the first command exits with a zero exit status (``true'' in Unix).
+.Ds
+.Cr "command || command"
+.De
+.PP
+executes the first command executing the second command if and only if
+the second command exits with a nonzero exit status (``false'' in Unix).
+.Ds
+.Cr "! command"
+.De
+.PP
+negates the exit status of a command.
+.SH "PATTERN MATCHING"
+There are two forms of pattern matching in
+.IR rc .
+One is traditional shell globbing.
+This occurs in matching for file names in argument lists:
+.Ds
+.Cr "command argument argument ..."
+.De
+.PP
+When the characters
+.Cr "*" ,
+.Cr [
+or
+.Cr ?
+occur in an argument or command,
+.I rc
+looks at the
+argument as a pattern for matching against files.
+(Contrary to the behavior other shells exhibit,
+.I rc
+will only perform pattern matching if a metacharacter occurs unquoted and
+literally in the input.
+Thus,
+.Ds
+.Cr "foo='*'"
+.Cr "echo $foo"
+.De
+.PP
+will always echo just a star.
+In order for non-literal metacharacters to be expanded, an
+.Cr eval
+statement must be used in order to rescan the input.)
+Pattern matching occurs according to the following rules: a
+.Cr *
+matches any number (including zero) of
+characters.
+A
+.Cr ?
+matches any single character, and a
+.Cr [
+followed by a
+number of characters followed by a
+.Cr ]
+matches a single character in that
+class.
+The rules for character class matching are the same as those for
+.IR ed (1),
+with the exception that character class negation is achieved
+with the tilde
+.Rc ( ~ ),
+not the caret
+.Rc ( ^ ),
+since the caret already means
+something else in
+.IR rc .
+.PP
+.I rc
+also matches patterns against strings with the
+.Cr ~
+command:
+.Ds
+.Cr "~ subject pattern pattern ..."
+.De
+.PP
+.Cr ~
+sets
+.Cr $status
+to zero if and only if a supplied pattern matches any
+single element of the subject list.
+Thus
+.Ds
+.Cr "~ foo f*"
+.De
+.PP
+sets status to zero, while
+.Ds
+.Cr "~ (bar baz) f*"
+.De
+.PP
+sets status to one.
+The null list is matched by the null list, so
+.Ds
+.Cr "~ $foo ()"
+.De
+.PP
+checks to see whether
+.Cr $foo
+is empty or not.
+This may also be achieved
+by the test
+.Ds
+.Cr "~ $#foo 0"
+.De
+.PP
+Note that inside a
+.Cr ~
+command
+.I rc
+does not match patterns against file
+names, so it is not necessary to quote the characters
+.Cr "*" ,
+.Cr [
+and
+.Cr "?" .
+However,
+.I rc
+does expand the glob the subject against filenames if it contains
+metacharacters.
+Thus, the command
+.Ds
+.Cr "~ * ?"
+.De
+.PP
+returns true if any of the files in the current directory have a
+single-character name.
+(Note that if the
+.Cr ~
+command is given a list as its first
+argument, then a successful match against any of the elements of that
+list will cause
+.Cr ~
+to return true.
+For example:
+.Ds
+.Cr "~ (foo goo zoo) z*"
+.De
+.PP
+is true.)
+.SH "LISTS AND VARIABLES"
+The primary data structure in
+.IR rc
+is the list, which is a sequence of words.
+Parentheses are used to group lists.
+The empty list is represented by
+.Cr "()" .
+Lists have no hierarchical structure;
+a list inside another list is expanded so the
+outer list contains all the elements of the inner list.
+Thus, the following are all equivalent
+.Ds
+.Cr "one two three"
+
+.Cr "(one two three)"
+
+.Cr "((one) () ((two three)))"
+.De
+.PP
+Note that the null string,
+.Cr "''" ,
+and the null list,
+.Cr "()" ,
+are two very
+different things.
+Assigning the null string to variable is a valid
+operation, but it does not remove its definition.
+For example,
+if
+.Cr $a
+is set to
+.Cr "''" ,
+then
+.Cr "$#a" ,
+returns a 1.
+.SS "List Concatenation"
+Two lists may be joined by the concatenation operator
+.Rc ( ^ ).
+A single word is treated as a list of length one, so
+.Ds
+.Cr "echo foo^bar"
+.De
+.PP
+produces the output
+.Ds
+.Cr foobar
+.De
+.PP
+For lists of more than one element,
+concatenation works according to the following rules:
+if the two lists have the same number of elements,
+then concatenation is pairwise:
+.Ds
+.Cr "echo (a\- b\- c\-)^(1 2 3)"
+.De
+.PP
+produces the output
+.Ds
+.Cr "a\-1 b\-2 c\-3"
+.De
+.PP
+Otherwise, one of the lists must have a single element,
+and then the concatenation is distributive:
+.Ds
+.Cr "cc \-^(O g c) (malloc alloca)^.c"
+.De
+.PP
+has the effect of performing the command
+.Ds
+.Cr "cc \-O \-g \-c malloc.c alloca.c"
+.De
+.SS "Free Carets"
+.I rc
+inserts carets (concatenation operators) for free in certain situations,
+in order to save some typing on the user's behalf.
+For
+example, the above example could also be typed in as:
+.Ds
+.Cr "opts=(O g c) files=(malloc alloca) cc \-$opts $files.c"
+.De
+.PP
+.I rc
+takes care to insert a free-caret between the
+.Rc `` \- ''
+and
+.Cr "$opts" ,
+as well
+as between
+.Cr $files
+and
+.Cr ".c" .
+The rule for free carets is as follows: if
+a word or keyword is immediately
+followed by another word, keyword, dollar-sign or
+backquote, then
+.I rc
+inserts a caret between them.
+.SS "Variables"
+A list may be assigned to a variable, using the notation:
+.Ds
+.Ic var " = " list
+.De
+.PP
+Any sequence of non-special characters, except a sequence including
+only digits, may be used as a variable name.
+All user-defined variables are exported into the environment.
+.PP
+The value of a variable is referenced with the notation:
+.Ds
+.Ci $ var
+.De
+.PP
+Any variable which has not been assigned a value returns the null list,
+.Cr "()" ,
+when referenced.
+In addition, multiple references are allowed:
+.Ds
+.Cr a=foo
+.Cr b=a
+.Cr "echo $$b"
+.De
+.PP
+prints
+.Ds
+.Cr foo
+.De
+.PP
+A variable's definition may also be removed by
+assigning the null list to a variable:
+.Ds
+.Ic var =()
+.De
+.PP
+For ``free careting'' to work correctly,
+.I rc
+must make certain assumptions
+about what characters may appear in a variable name.
+.I rc
+assumes that a variable name consists only of alphanumeric characters,
+underscore
+.Rc ( \|_\| )
+and star
+.Rc ( * ).
+To reference a variable with other
+characters in its name, quote the variable name.
+Thus:
+.Ds
+.Cr "echo $'we$Ird\Variab!le'"
+.De
+.SS "Local Variables"
+Any number of variable assignments may be made local to a single
+command by typing:
+.Ds
+.Cr "a=foo b=bar ... command"
+.De
+.PP
+The command may be a compound command, so for example:
+.Ds
+.Cr "path=. ifs=() {"
+.Cr " " ...
+.Cr }
+.De
+.PP
+sets
+.Cr path
+to
+.Cr .
+and removes
+.Cr ifs
+for the duration of one long compound command.
+.SS "Variable Subscripts"
+Variables may be subscripted with the notation
+.Ds
+.Ci $var( n )
+.De
+.PP
+where
+.I n
+is a list of integers (origin 1).
+The list of subscripts need
+not be in order or even unique.
+Thus, if
+.Ds
+.Cr "a=(one two three)"
+.De
+.PP
+then
+.Ds
+.Cr "echo $a(3 3 3)"
+.De
+.PP
+prints
+.Ds
+.Cr "three three three"
+.De
+.PP
+If
+.I n
+references a nonexistent element, then
+.Ci $var( n )
+returns the null list.
+The notation
+.Ci "$" n\fR,
+where
+.I n
+is an integer, is a shorthand for
+.Ci $*( n )\fR.
+Thus,
+.IR rc 's
+arguments may be referred to as
+.Cr "$1" ,
+.Cr "$2" ,
+and so on.
+.PP
+Note also that the list of subscripts may be given by any of
+.IR rc 's
+list operations:
+.Ds
+.Cr "$var(\`{awk 'BEGIN{for(i=1;i<=10;i++)print i;exit; }'})"
+.De
+.PP
+returns the first 10 elements of
+.Cr $var .
+.PP
+To count the number of elements in a variable, use
+.Ds
+.Cr $#var
+.De
+.PP
+This returns a single-element list, with the number of elements in
+.Cr $var .
+.SS "Flat Lists"
+In order to create a single-element list from a multi-element list,
+with the components space-separated, use
+.Ds
+.Cr $^var
+.De
+.PP
+This is useful when the normal list concatenation rules need to be
+bypassed.
+For example, to append a single period at the end of
+.Cr $path ,
+use:
+.Ds
+.Cr "echo $^path."
+.De
+.SS "Backquote Substitution"
+A list may be formed from the output of a command by using backquote
+substitution:
+.Ds
+.Cr "\`{ command }"
+.De
+.PP
+returns a list formed from the standard output of the command in braces.
+.Cr $ifs
+is used to split the output into list elements.
+By default,
+.Cr $ifs
+has the value space-tab-newline.
+The braces may be omitted if the command is a single word.
+Thus
+.Cr \`ls
+may be used instead of
+.Cr "\`{ls}" .
+This last feature is useful when defining functions that expand
+to useful argument lists.
+A frequent use is:
+.Ds
+.Cr "fn src { echo *.[chy] }"
+.De
+.PP
+followed by
+.Ds
+.Cr "wc \`src"
+.De
+.PP
+(This will print out a word-count of all C source files in the current
+directory.)
+.PP
+In order to override the value of
+.Cr $ifs
+for a single backquote
+substitution, use:
+.Ds
+.Cr "\`\` (ifs-list) { command }"
+.De
+.PP
+.Cr $ifs
+will be temporarily ignored and the command's output will be split as specified by
+the list following the double backquote.
+For example:
+.Ds
+.Cr "\`\` ($nl :) {cat /etc/passwd}"
+.De
+.PP
+splits up
+.Cr /etc/passwd
+into fields, assuming that
+.Cr $nl
+contains a newline
+as its value.
+.SH "SPECIAL VARIABLES"
+Several variables are known to
+.I rc
+and are treated specially.
+.TP
+.Cr *
+The argument list of
+.IR rc .
+.Cr "$1, $2,"
+etc. are the same as
+.Cr $*(1) ,
+.Cr $*(2) ,
+etc.
+The variable
+.Cr $0
+holds the value of
+.Cr argv[0]
+with which
+.I rc
+was invoked.
+Additionally,
+.Cr $0
+is set to the name of a function for the duration of
+the execution of that function, and
+.Cr $0
+is also set to the name of the
+file being interpreted for the duration of a
+.Cr .
+command.
+.TP
+.Cr apid
+The process ID of the last process started in the background.
+.TP
+.Cr apids
+The process IDs of any background processes which are outstanding
+or which have died and have not been waited for yet.
+.TP
+.Cr cdpath
+A list of directories to search for the target of a
+.B cd
+command.
+The empty string stands for the current directory.
+Note that if the
+.Cr $cdpath
+variable does not contain the current directory, then the current
+directory will not be searched; this allows directory searching to
+begin in a directory other than the current directory.
+Note also that an assignment to
+.Cr $cdpath
+causes an automatic assignment to
+.Cr $CDPATH ,
+and vice-versa.
+.TP
+.Cr history
+.Cr $history
+contains the name of a file to which commands are appended as
+.I rc
+reads them.
+This facilitates the use of a stand-alone history program
+(such as
+.IR history (1))
+which parses the contents of the history file and presents them to
+.I rc
+for reinterpretation.
+If
+.Cr $history
+is not set, then
+.I rc
+does not append commands to any file.
+.TP
+.Cr home
+The default directory for the builtin
+.B cd
+command and is the directory
+in which
+.I rc
+looks to find its initialization file,
+.Cr .rcrc ,
+if
+.I rc
+has been started up as a login shell.
+Like
+.Cr $cdpath
+and
+.Cr $CDPATH ,
+.Cr $home
+and
+.Cr $HOME
+are aliased to each other.
+.TP
+.Cr ifs
+The internal field separator, used for splitting up the output of
+backquote commands for digestion as a list.
+.TP
+.Cr path
+This is a list of directories to search in for commands.
+The empty string stands for the current directory.
+Note that like
+.Cr $cdpath
+and
+.Cr $CDPATH ,
+.Cr $path
+and
+.Cr $PATH
+are aliased to each other.
+If
+.Cr $path
+or
+.Cr $PATH
+is not set at startup time,
+.Cr $path
+assumes a default value suitable for your system.
+This is typically
+.Cr "(/usr/ucb /usr/bin /bin .)"
+.TP
+.Cr pid
+The process ID of the currently running
+.IR rc .
+.TP
+.Cr prompt
+This variable holds the two prompts (in list form, of course) that
+.I rc
+prints.
+.Cr $prompt(1)
+is printed before each command is read, and
+.Cr $prompt(2)
+is printed when input is expected to continue on the next
+line.
+.I rc
+sets
+.Cr $prompt
+to
+.Cr "('; ' '')"
+by default.
+The reason for this is that it enables an
+.I rc
+user to grab commands from previous lines using a
+mouse, and to present them to
+.I rc
+for re-interpretation; the semicolon
+prompt is simply ignored by
+.IR rc .
+The null
+.Cr $prompt(2)
+also has its
+justification: an
+.I rc
+script, when typed interactively, will not leave
+.Cr $prompt(2) 's
+on the screen,
+and can therefore be grabbed by a mouse and placed
+directly into a file for use as a shell script, without further editing
+being necessary.
+.TP
+.Cr prompt " (function)"
+If this function is set, then it gets executed every time
+.I rc
+is about to print
+.Cr "$prompt(1)" .
+.TP
+.Cr status
+The exit status of the last command.
+If the command exited with a numeric value,
+that number is the status.
+If the died with a signal,
+the status is the name of that signal; if a core file
+was created, the string
+.Rc `` +core ''
+is appended.
+The value of
+.Cr $status
+for a pipeline is a list, with one entry,
+as above, for each process in the pipeline.
+For example, the command
+.Ds
+.Cr "ls | wc"
+.De
+.TP
+\&
+usually sets
+.Cr $status
+to
+.Cr "(0 0)" .
+.PP
+The values of
+.Cr "$path" ,
+.Cr "$cdpath" ,
+and
+.Cr $home
+are derived from the environment
+values of
+.Cr "$PATH" ,
+.Cr "$CDPATH" ,
+and
+.Cr "$HOME" .
+Otherwise, they are derived from
+the environment values of
+.Cr $path ,
+.Cr $cdpath
+and
+.Cr $home .
+This is for compatibility with other Unix programs, like
+.IR sh (1).
+.Cr $PATH
+and
+.Cr $CDPATH
+are assumed to be colon-separated lists.
+.SH FUNCTIONS
+.I rc
+functions are identical to
+.I rc
+scripts, except that they are stored
+in memory and are automatically exported into the environment.
+A shell function is declared as:
+.Ds
+.Cr "fn name { commands }"
+.De
+.PP
+.I rc
+scans the definition until the close-brace, so the function can
+span more than one line.
+The function definition may be removed by typing
+.Ds
+.Cr "fn name"
+.De
+.PP
+(One or more names may be specified.
+With an accompanying definition, all names receive the same definition.
+This is sometimes useful
+for assigning the same signal handler to many signals.
+Without a definition, all named functions are deleted.)
+When a function is executed,
+.Cr $*
+is set to the arguments to that
+function for the duration of the command.
+Thus a reasonable definition for
+.Cr "l" ,
+a shorthand for
+.IR ls (1),
+could be:
+.Ds
+.Cr "fn l { ls -FC $* }"
+.De
+.PP
+but not
+.Ds
+.Cr "fn l { ls -FC }"
+.De
+.SH "INTERRUPTS AND SIGNALS"
+.I rc
+recognizes a number of signals, and allows the user to define shell
+functions which act as signal handlers.
+.I rc
+by default traps
+.Cr SIGINT
+when it is in interactive mode.
+.Cr SIGQUIT
+and
+.Cr SIGTERM
+are ignored, unless
+.I rc
+has been invoked with the
+.Cr \-d
+flag.
+However, user-defined signal handlers may be written for these and
+all other signals.
+The way to define a signal handler is to
+write a function by the name of the signal in lower case.
+Thus:
+.Ds
+.Cr "fn sighup { echo hangup; rm /tmp/rc$pid.*; exit }"
+.De
+.PP
+In addition to Unix signals,
+.I rc
+recognizes the artificial signal
+.Cr SIGEXIT
+which occurs as
+.I rc
+is about to exit.
+.PP
+In order to remove a signal handler's definition,
+remove it as though it were a regular function.
+For example:
+.Ds
+.Cr "fn sigint"
+.De
+.PP
+returns the handler of
+.Cr SIGINT
+to the default value.
+In order to ignore a signal, set the signal handler's value to
+.Cr "{}" .
+Thus:
+.Ds
+.Cr "fn sigint {}"
+.De
+.PP
+causes SIGINT to be ignored by the shell.
+Only signals that are being ignored are passed on to programs run by
+.IR rc ;
+signal functions are not exported.
+.PP
+On System V-based Unix systems,
+.I rc
+will not allow you to trap
+.Cr SIGCLD .
+.SH "BUILTIN COMMANDS"
+Builtin commands execute in the context of the shell, but otherwise
+behave exactly like other commands.
+Although
+.BR ! ,
+.B ~
+and
+.B @
+are not strictly speaking builtin commands,
+they can usually be used as such.
+.TP
+\&\fB.\fR [\fB\-i\fR] \fIfile \fR[\fIarg ...\fR]
+Reads
+.I file
+as input to
+.IR rc
+and executes its contents.
+With a
+.Cr \-i
+flag, input is interactive.
+Thus from within a shell script,
+.Ds
+.Cr ". \-i /dev/tty"
+.De
+.TP
+\&
+does the ``right'' thing.
+.TP
+.B break
+Breaks from the innermost
+.Cr for
+or
+.Cr while ,
+as in C.
+It is an error to invoke
+.B break
+outside of a loop.
+(Note that there is no
+.B break
+keyword between commands in
+.Cr switch
+statements, unlike C.)
+.TP
+\fBbuiltin \fIcommand \fR[\fIarg ...\fR]
+Executes the command ignoring any function definition of the
+same name.
+This command is present to allow functions with the
+same names as builtins to use the underlying builtin or binary.
+.TP
+\fBcd \fR[\fIdirectory\fR]
+Changes the current directory to
+.IR directory .
+The variable
+.Cr $cdpath
+is searched for possible locations of
+.IR directory ,
+analogous to the searching of
+.Cr $path
+for executable files.
+With no argument,
+.B cd
+changes the current directory to
+.Cr "$home" .
+.TP
+\fBecho \fR[\fB\-n\fR] [\fB\-\|\-\fR] [\fIarg ...\fR]
+Prints its arguments to standard output, terminated by a newline.
+Arguments are separated by spaces.
+If the first argument is
+.Cr "\-n"
+no final newline is printed.
+If the first argument is
+.Cr "\-\|\-" ,
+then all other arguments are echoed literally.
+This is used for echoing a literal
+.Cr "\-n" .
+.TP
+\fBeval \fR[\fIlist\fR]
+Concatenates the elements of
+.I list
+with spaces and feeds the resulting string to
+.I rc
+for re-scanning.
+This is the only time input is rescanned in
+.IR rc .
+.TP
+\fBexec \fR[\fIarg ...\fR]
+Replaces
+.I rc
+with the given command.
+If the exec contains only redirections,
+then these redirections apply to the current shell
+and the shell does not exit.
+For example,
+.Ds
+.Cr "exec >[2] err.out"
+.De
+.TP
+\&
+places further output to standard error in the file
+.IR err.out .
+.TP
+\fBexit \fR[\fIstatus\fR]
+Cause the current shell to exit with the given exit
+.IR status .
+If no argument is given, the current value of
+.Cr $status
+is used.
+.TP
+\fBlimit \fR[\fB\-h\fR] [\fIresource \fR[\fIvalue\fR]]
+Similar to the
+.IR csh (1)
+.B limit
+builtin, this command operates upon the
+BSD-style limits of a process.
+The
+.Cr \-h
+flag displays/alters the hard
+limits.
+The resources which can be shown or altered are
+.BR cputime ,
+.BR filesize ,
+.BR datasize ,
+.BR stacksize ,
+.B coredumpsize
+and
+.BR memoryuse .
+For
+example:
+.Ds
+.Cr "limit coredumpsize 0"
+.De
+.TP
+\&
+disables core dumps.
+.TP
+.B newpgrp
+Puts
+.I rc
+into a new process group.
+This builtin is useful for making
+.I rc
+behave like a job-control shell in a hostile environment.
+One example is the NeXT Terminal program, which implicitly assumes
+that each shell it forks will put itself into a new process group.
+.TP
+\fBreturn \fR[\fIn\fR]
+Returns from the current function, with status
+.IR n ,
+where
+.IR n
+is a single value or a list of possible exit statuses.
+Thus it is legal to have
+.Ds
+.Cr "return (sigpipe 1 2 3)"
+.De
+.TP
+\&
+(This is commonly used to allow a function to return with the exit status
+of a previously executed pipeline of commands.)
+If
+.IR n
+is omitted, then
+.Cr $status
+is left unchanged.
+It is an error to invoke
+.B return
+when not inside a function.
+.TP
+\fBshift \fR[\fIn\fR]
+Deletes
+.I n
+elements from the beginning of
+.Cr $*
+and shifts the other
+elements down by
+.IR n .
+.I n
+defaults to 1.
+(Note that
+.Cr $0
+is not affected by
+.BR shift .)
+.TP
+\fBumask \fR[\fImask\fR]
+Sets the current umask (see
+.IR umask (2))
+to the octal
+.IR mask .
+If no argument is present, the current mask value is printed.
+.TP
+\fBwait \fR[\fIpid\fR]
+Waits for the specified
+.IR pid ,
+which must have been started by
+.IR rc .
+If no
+.I pid
+is specified,
+.I rc
+waits for any child process to exit.
+.TP
+\fBwhatis \fR[\fB\-s\fR] [\fB\-\|\-\fR] [\fIname ...\fR]
+Prints a definition of the named objects.
+For variables, their values
+are printed; for functions, their definitions are; and for executable
+files, path names are printed.
+Without arguments,
+.B whatis
+prints the values of all shell variables and functions.
+With a
+.Cr \-s
+argument,
+.B whatis
+also prints out a list of available signals and their handlers (if any).
+Note that
+.B whatis
+output is suitable for input to
+.IR rc ;
+by saving the output of
+.B whatis
+in a file, it should be possible to recreate the state of
+.I rc
+by sourcing this file with a
+.Cr .
+command.
+Another note:
+.Cr "whatis -s > file"
+cannot be used to store the state of
+.IR rc 's
+signal handlers in a file, because builtins with redirections
+are run in a subshell, and
+.I rc
+always restores signal handlers to their default value after a
+.Cr fork() .
+.TP
+\&
+Since
+.B whatis
+uses
+.IR getopt (3)
+to parse its arguments, you can use the special argument
+.Cr "\-\|\-"
+to terminate its options.
+This allows you to use names beginning with a dash, such as
+the
+.IR history (1)
+commands.
+For example,
+.Ds
+.Cr "whatis \-\|\- \-p"
+.De
+.SH GRAMMAR
+Here is
+.IR rc 's
+grammar, edited to remove semantic actions.
+.Ds
+.ft \*(Cf
+%term ANDAND BACKBACK BANG CASE COUNT DUP ELSE END FLAT FN FOR IF IN
+%term OROR PIPE REDIR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD HUH
+
+%left WHILE ')' ELSE
+%left ANDAND OROR '\en'
+%left BANG SUBSHELL
+%left PIPE
+%right '$'
+%left SUB
+
+%start rc
+
+%%
+
+rc: line end
+ | error end
+
+end: END /* EOF */ | '\en'
+
+cmdsa: cmd ';' | cmd '&'
+
+line: cmd | cmdsa line
+
+body: cmd | cmdsan body
+
+cmdsan: cmdsa | cmd '\en'
+
+brace: '{' body '}'
+
+paren: '(' body ')'
+
+assign: first '=' word
+
+epilog: /* empty */ | redir epilog
+
+redir: DUP | REDIR word
+
+case: CASE words ';' | CASE words '\en'
+
+cbody: cmd | case cbody | cmdsan cbody
+
+iftail: cmd %prec ELSE
+ | brace ELSE optnl cmd
+
+cmd : /* empty */ %prec WHILE
+ | simple
+ | brace epilog
+ | IF paren optnl iftail
+ | FOR '(' word IN words ')' optnl cmd
+ | FOR '(' word ')' optnl cmd
+ | WHILE paren optnl cmd
+ | SWITCH '(' word ')' optnl '{' cbody '}'
+ | TWIDDLE optcaret word words
+ | cmd ANDAND optnl cmd
+ | cmd OROR optnl cmd
+ | cmd PIPE optnl cmd
+ | redir cmd %prec BANG
+ | assign cmd %prec BANG
+ | BANG optcaret cmd
+ | SUBSHELL optcaret cmd
+ | FN words brace
+ | FN words
+
+optcaret: /* empty */ | '^'
+
+simple: first | simple word | simple redir
+
+first: comword | first '^' sword
+
+sword: comword | keyword
+
+word: sword | word '^' sword
+
+comword: '$' sword
+ | '$' sword SUB words ')'
+ | COUNT sword
+ | FLAT sword
+ | '`' sword
+ | '`' brace
+ | BACKBACK word brace | BACKBACK word sword
+ | '(' words ')'
+ | REDIR brace
+ | WORD
+
+keyword: FOR | IN | WHILE | IF | SWITCH
+ | FN | ELSE | CASE | TWIDDLE | BANG | SUBSHELL
+
+words: /* empty */ | words word
+
+optnl: /* empty */ | optnl '\en'
+.ft R
+.De
+.SH FILES
+.Cr $HOME/.rcrc ,
+.Cr /tmp/rc* ,
+.Cr /dev/null
+.SH CREDITS
+.I rc
+was written by Byron Rakitzis, with valuable help
+from Paul Haahr, Hugh Redelmeier and David Sanderson.
+The design of this shell has been copied from the
+.I rc
+that Tom Duff wrote at Bell Labs.
+.SH BUGS
+On systems that support
+.Cr /dev/fd ,
+.Cr <{foo}
+style redirection is implemented that way.
+However, on other systems it is implemented with named pipes,
+and it is sometimes
+possible to foil
+.I rc
+into removing the FIFO it places in
+.Cr /tmp
+prematurely, or it is even possible to cause
+.I rc
+to hang.
+.PP
+The functionality of
+.B shift
+should be available for variables other than
+.Cr "$*" .
+.PP
+.B echo
+is built in only for performance reasons, which is a bad idea.
+.PP
+There should be a way to avoid exporting a variable.
+.PP
+The
+.Cr $^var
+notation for flattening should allow for using an arbitrary
+separating character, not just space.
+.PP
+Bug reports should be mailed to
+.Cr "byron@archone.tamu.edu" .
+.SH INCOMPATIBILITIES
+Here is a list of features which distinguish this incarnation of
+.I rc
+from the one described in the Bell Labs manual pages:
+.PP
+The treatment of
+.Cr if - else
+is different in the v10
+.IR rc :
+that version uses an
+.Cr "if not"
+clause which gets executed
+if the preceding
+.Cr if
+test does not succeed.
+.PP
+Backquotes are slightly different in v10
+.IR rc :
+a backquote must always be followed by a left-brace.
+This restriction is not present for single-word commands in this
+.IR rc .
+.PP
+The following are all new with this version of
+.IR rc :
+The
+.Cr \-n
+option,
+the list flattening operator,
+here strings (they facilitate exporting of functions
+with here documents into the environment),
+the
+.B return
+and
+.B break
+keywords,
+the
+.B echo
+builtin,
+the support for the GNU
+.IR readline (3)
+library and
+the support for the
+.Cr prompt
+function.
+This
+.I rc
+also sets
+.Cr $0
+to the name of a function being executed/file
+being sourced.
+.SH "SEE ALSO"
+``rc \(em A Shell for Plan 9 and UNIX Systems'',
+Unix Research System,
+10th Edition,
+vol. 2. (Saunders College Publishing)
+(This paper is also distributed with this
+.I rc
+in PostScript form.)
+.PP
+.IR history (1)
diff --git a/rc.h b/rc.h
@@ -0,0 +1,380 @@
+#include "config.h"
+#include "proto.h"
+
+/* datatypes */
+
+/* braindamaged IBM header files #define true and false */
+#undef FALSE
+#undef TRUE
+
+#include <stdarg.h>
+
+typedef void builtin_t(char **);
+typedef struct Block Block;
+typedef struct Dup Dup;
+typedef struct Estack Estack;
+typedef struct Function Function;
+typedef struct Hq Hq;
+typedef struct Htab Htab;
+typedef struct Jbwrap Jbwrap;
+typedef struct List List;
+typedef struct Node Node;
+typedef struct Pipe Pipe;
+typedef struct Redir Redir;
+typedef struct Rq Rq;
+typedef struct Variable Variable;
+typedef struct Word Word;
+typedef struct Format Format;
+typedef union Edata Edata;
+
+typedef enum nodetype {
+ nAndalso, nAssign, nBackq, nBang, nBody, nCbody, nNowait, nBrace, nConcat,
+ nCount, nElse, nFlat, nDup, nEpilog, nNewfn, nForin, nIf, nQword,
+ nOrelse, nPipe, nPre, nRedir, nRmfn, nArgs, nSubshell, nCase,
+ nSwitch, nMatch, nVar, nVarsub, nWhile, nWord, nLappend, nNmpipe
+} nodetype;
+
+typedef enum ecodes {
+ eError, eBreak, eReturn, eVarstack, eArena, eFifo, eFd
+} ecodes;
+
+typedef enum bool {
+ FALSE, TRUE
+} bool;
+
+typedef enum inputtype {
+ iFd, iString
+} inputtype;
+
+typedef enum redirtype {
+ rFrom, rCreate, rAppend, rHeredoc, rHerestring
+} redirtype;
+
+typedef bool (*Conv)(Format *, int);
+
+union Edata {
+ Jbwrap *jb;
+ Block *b;
+ char *name;
+ int fd;
+};
+
+struct Estack {
+ ecodes e;
+ bool interactive;
+ Edata data;
+ Estack *prev;
+};
+
+struct List {
+ char *w, *m;
+ List *n;
+};
+
+struct Node {
+ nodetype type;
+ union {
+ char *s;
+ int i;
+ Node *p;
+ } u[4];
+};
+
+struct Pipe {
+ int left, right;
+};
+
+struct Dup {
+ redirtype type;
+ int left, right;
+};
+
+struct Redir {
+ redirtype type;
+ int fd;
+};
+
+struct Word {
+ char *w, *m;
+};
+
+struct Rq {
+ Node *r;
+ struct Rq *n;
+};
+
+struct Function {
+ Node *def;
+ char *extdef;
+};
+
+struct Variable {
+ List *def;
+ char *extdef;
+ Variable *n;
+};
+
+struct Htab {
+ char *name;
+ void *p;
+};
+
+struct Format {
+ /* for the formatting routines */
+ va_list args;
+ long flags, f1, f2;
+ /* for the buffer maintainence routines */
+ char *buf, *bufbegin, *bufend;
+ int flushed;
+ void (*grow)(Format *, SIZE_T);
+ union { int n; void *p; } u;
+};
+
+/* Format->flags values */
+enum {
+ FMT_long = 1, /* %l */
+ FMT_short = 2, /* %h */
+ FMT_unsigned = 4, /* %u */
+ FMT_zeropad = 8, /* %0 */
+ FMT_leftside = 16, /* %- */
+ FMT_altform = 32, /* %# */
+ FMT_f1set = 64, /* %<n> */
+ FMT_f2set = 128 /* %.<n> */
+};
+
+/* macros */
+#define EOF (-1)
+#ifndef NULL
+#define NULL 0
+#endif
+#define a2u(x) n2u(x, 10)
+#define o2u(x) n2u(x, 8)
+#define arraysize(a) ((int)(sizeof(a)/sizeof(*a)))
+#define enew(x) ((x *) ealloc(sizeof(x)))
+#define ecpy(x) strcpy((char *) ealloc(strlen(x) + 1), x)
+#define lookup_fn(s) ((Function *) lookup(s, fp))
+#define lookup_var(s) ((Variable *) lookup(s, vp))
+#define nnew(x) ((x *) nalloc(sizeof(x)))
+#define ncpy(x) (strcpy((char *) nalloc(strlen(x) + 1), x))
+#ifndef offsetof
+#define offsetof(t, m) ((SIZE_T) (((char *) &((t *) 0)->m) - (char *)0))
+#endif
+#define streq(x, y) (*(x) == *(y) && strcmp(x, y) == 0)
+#define conststrlen(x) (sizeof (x) - 1)
+
+/* rc prototypes */
+
+/* main.c */
+extern char *prompt, *prompt2;
+extern Rq *redirq;
+extern bool dashdee, dashee, dashvee, dashex, dashell,
+ dasheye, dashen, dashpee, interactive;
+extern int rc_pid;
+extern int lineno;
+
+/* builtins.c */
+extern builtin_t *isbuiltin(char *);
+extern void b_exec(char **), funcall(char **), b_dot(char **), b_builtin(char **);
+extern char *which(char *, bool);
+
+/* except.c */
+extern bool nl_on_intr;
+extern bool outstanding_cmdarg(void);
+extern void pop_cmdarg(bool);
+extern void rc_raise(ecodes);
+extern void except(ecodes, Edata, Estack *);
+extern void unexcept(void);
+extern void rc_error(char *);
+extern void sigint(int);
+
+/* exec.c */
+extern void exec(List *, bool);
+extern void doredirs(void);
+
+/* footobar.c */
+extern char *fun2str(char *, Node *);
+extern char *list2str(char *, List *);
+extern char **list2array(List *, bool);
+extern char *get_name(char *);
+extern List *parse_var(char *, char *);
+extern Node *parse_fn(char *, char *);
+extern void initprint(void);
+extern void rc_exit(int); /* here for odd reasons; user-defined signal handlers are kept in fn.c */
+
+/* getopt.c */
+extern int rc_getopt(int, char **, char *);
+
+extern int rc_optind, rc_opterr, rc_optopt;
+extern char *rc_optarg;
+
+/* glob.c */
+extern bool lmatch(List *, List *);
+extern List *glob(List *);
+
+/* glom.c */
+extern void assign(List *, List *, bool);
+extern void qredir(Node *);
+extern List *append(List *, List*);
+extern List *flatten(List *);
+extern List *glom(Node *);
+extern List *concat(List *, List *);
+extern List *varsub(List *, List *);
+extern List *word(char *, char *);
+
+/* hash.c */
+extern Htab *fp, *vp;
+extern void *lookup(char *, Htab *);
+extern Function *get_fn_place(char *);
+extern List *varlookup(char *);
+extern Node *fnlookup(char *);
+extern Variable *get_var_place(char *, bool);
+extern bool varassign_string(char *);
+extern char **makeenv(void);
+extern char *fnlookup_string(char *);
+extern char *varlookup_string(char *);
+extern void alias(char *, List *, bool);
+extern void starassign(char *, char **, bool);
+extern void delete_fn(char *);
+extern void delete_var(char *, bool);
+extern void fnassign(char *, Node *);
+extern void fnassign_string(char *);
+extern void fnrm(char *);
+extern void initenv(char **);
+extern void inithash(void);
+extern void setsigdefaults(bool);
+extern void inithandler(void);
+extern void varassign(char *, List *, bool);
+extern void varrm(char *, bool);
+extern void whatare_all_vars(void);
+extern void whatare_all_signals(void);
+extern void prettyprint_var(int, char *, List *);
+extern void prettyprint_fn(int, char *, Node *);
+
+/* heredoc.c */
+extern int heredoc(int);
+extern int qdoc(Node *, Node *);
+extern Hq *hq;
+
+/* input.c */
+extern void initinput(void);
+extern Node *parseline(char *);
+extern int gchar(void);
+extern void ugchar(int);
+extern Node *doit(bool);
+extern void flushu(void);
+extern void pushfd(int);
+extern void pushstring(char **, bool);
+extern void popinput(void);
+extern void closefds(void);
+extern int last;
+extern bool rcrc;
+
+/* lex.c */
+extern int yylex(void);
+extern void inityy(void);
+extern void yyerror(const char *);
+extern void scanerror(char *);
+extern void print_prompt2(void);
+extern const char nw[], dnw[];
+
+/* list.c */
+extern void listfree(List *);
+extern List *listcpy(List *, void *(*)(SIZE_T));
+extern SIZE_T listlen(List *);
+extern int listnel(List *);
+
+/* match.c */
+extern bool match(char *, char *, char *);
+
+/* alloc.c */
+extern void *ealloc(SIZE_T);
+extern void *erealloc(void *, SIZE_T);
+extern void efree(void *);
+extern Block *newblock(void);
+extern void *nalloc(SIZE_T);
+extern void nfree(void);
+extern void restoreblock(Block *);
+
+/* open.c */
+extern int rc_open(const char *, redirtype);
+
+/* print.c */
+/*
+ The following prototype should be:
+extern Conv fmtinstall(int, Conv);
+ but this freaks out SGI's compiler under IRIX3.3.2
+*/
+extern bool (*fmtinstall(int, bool (*)(Format *, int)))(Format *, int);
+extern int printfmt(Format *, const char *);
+extern int fmtprint(Format *, const char *,...);
+extern void fmtappend(Format *, const char *, SIZE_T);
+extern void fmtcat(Format *, const char *);
+extern int fprint(int fd, const char *fmt,...);
+extern char *mprint(const char *fmt,...);
+extern char *nprint(const char *fmt,...);
+/*
+ the following macro should by rights be coded as an expression, not
+ a statement, but certain compilers (notably DEC) have trouble with
+ void expressions inside the ?: operator. (sheesh, give me a break!)
+*/
+#define fmtputc(f, c) {\
+ if ((f)->buf >= (f)->bufend)\
+ (*(f)->grow)((f), (SIZE_T)1);\
+ *(f)->buf++ = (c);\
+}
+
+/* y.tab.c (parse.y) */
+extern Node *parsetree;
+extern int yyparse(void);
+extern void initparse(void);
+
+/* redir.c */
+extern void doredirs(void);
+
+/* signal.c */
+extern void initsignal(void);
+extern void catcher(int);
+extern void sigchk(void);
+extern void (*rc_signal(int, void (*)(int)))(int);
+extern void (*sighandlers[])(int);
+extern volatile SIG_ATOMIC_T slow, interrupt_happened;
+#define SIGCHK sigchk()
+
+/* status.c */
+extern int istrue(void);
+extern int getstatus(void);
+extern void set(bool);
+extern void setstatus(int, int);
+extern List *sgetstatus(void);
+extern void setpipestatus(int [], int);
+extern void statprint(int, int);
+extern void ssetstatus(char **);
+
+/* tree.c */
+extern Node *mk(int /*nodetype*/,...);
+extern Node *treecpy(Node *, void *(*)(SIZE_T));
+extern void treefree(Node *);
+
+/* utils.c */
+extern bool isabsolute(char *);
+extern int n2u(char *, unsigned int);
+extern int rc_read(int, char *, SIZE_T);
+extern int mvfd(int, int);
+extern int starstrcmp(const void *, const void *);
+extern void pr_error(char *);
+extern char *clear(char *, SIZE_T);
+extern void panic(char *);
+extern void uerror(char *);
+extern void writeall(int, char *, SIZE_T);
+
+/* wait.c */
+extern int rc_fork(void);
+extern int rc_wait4(int, int *, bool);
+extern List *sgetapids(void);
+extern void waitforall(void);
+extern bool forked;
+
+/* walk.c */
+extern bool walk(Node *, bool);
+extern bool cond;
+
diff --git a/redir.c b/redir.c
@@ -0,0 +1,77 @@
+/* redir.c: code for opening files and piping heredocs after fork but before exec. */
+
+#include "rc.h"
+
+/*
+ Walk the redirection queue, and open files and dup2 to them. Also,
+ here-documents are treated here by dumping them down a pipe. (this
+ should make here-documents fast on systems with lots of memory which
+ do pipes right. Under sh, a file is copied to /tmp, and then read
+ out of /tmp again. I'm interested in knowing how much faster, say,
+ shar runs when unpacking when invoked with rc instead of sh. On my
+ sun4/280, it runs in about 60-75% of the time of sh for unpacking
+ the rc source distribution.)
+*/
+
+extern void doredirs() {
+ List *fname;
+ int fd, p[2];
+ Rq *r;
+ for (r = redirq; r != NULL; r = r->n) {
+ switch(r->r->type) {
+ default:
+ panic("unexpected node in doredirs");
+ /* NOTREACHED */
+ case nRedir:
+ if (r->r->u[0].i == rHerestring) {
+ fname = flatten(glom(r->r->u[2].p)); /* fname is really a string */
+ if (pipe(p) < 0) {
+ uerror("pipe");
+ rc_error(NULL);
+ }
+ if (rc_fork() == 0) { /* child writes to pipe */
+ setsigdefaults(FALSE);
+ close(p[0]);
+ if (fname != NULL)
+ writeall(p[1], fname->w, strlen(fname->w));
+ exit(0);
+ } else {
+ close(p[1]);
+ if (mvfd(p[0], r->r->u[1].i) < 0)
+ rc_error(NULL);
+ }
+ } else {
+ fname = glob(glom(r->r->u[2].p));
+ if (fname == NULL)
+ rc_error("null filename in redirection");
+ if (fname->n != NULL)
+ rc_error("multi-word filename in redirection");
+ switch (r->r->u[0].i) {
+ default:
+ panic("unexpected node in doredirs");
+ /* NOTREACHED */
+ case rCreate: case rAppend: case rFrom:
+ fd = rc_open(fname->w, r->r->u[0].i);
+ break;
+ }
+ if (fd < 0) {
+ uerror(fname->w);
+ rc_error(NULL);
+ }
+ if (mvfd(fd, r->r->u[1].i) < 0)
+ rc_error(NULL);
+ }
+ break;
+ case nDup:
+ if (r->r->u[2].i == -1)
+ close(r->r->u[1].i);
+ else if (r->r->u[2].i != r->r->u[1].i) {
+ if (dup2(r->r->u[2].i, r->r->u[1].i) < 0) {
+ uerror("dup2");
+ rc_error(NULL);
+ }
+ }
+ }
+ }
+ redirq = NULL;
+}
diff --git a/signal.c b/signal.c
@@ -0,0 +1,75 @@
+/* signal.c: a Hugh-approved signal handler. */
+
+#include <signal.h>
+#include <setjmp.h>
+#include "rc.h"
+#include "sigmsgs.h"
+#include "jbwrap.h"
+
+Jbwrap slowbuf;
+volatile SIG_ATOMIC_T slow, interrupt_happened;
+void (*sighandlers[NUMOFSIGNALS])(int);
+
+static volatile SIG_ATOMIC_T sigcount, caught[NUMOFSIGNALS];
+
+extern void catcher(int s) {
+ if (forked)
+ exit(1); /* exit unconditionally on a signal in a child process */
+ if (caught[s] == 0) {
+ sigcount++;
+ caught[s] = 1;
+ }
+ signal(s, catcher);
+ interrupt_happened = TRUE;
+#ifndef SVSIGS
+ if (slow)
+ longjmp(slowbuf.j, 1);
+#endif
+}
+
+extern void sigchk() {
+ void (*h)(int);
+ int s, i;
+ if (sigcount == 0)
+ return; /* ho hum; life as usual */
+ if (forked)
+ exit(1); /* exit unconditionally on a signal in a child process */
+ for (i = 0, s = -1; i < NUMOFSIGNALS; i++)
+ if (caught[i] != 0) {
+ s = i;
+ --sigcount;
+ caught[s] = 0;
+ break;
+ }
+ if (s == -1)
+ panic("all-zero sig vector with nonzero sigcount");
+ if ((h = sighandlers[s]) == SIG_DFL)
+ panic("caught signal set to SIG_DFL");
+ if (h == SIG_IGN)
+ panic("caught signal set to SIG_IGN");
+ (*h)(s);
+}
+
+extern void (*rc_signal(int s, void (*h)(int)))(int) {
+ void (*old)(int);
+ SIGCHK;
+ old = sighandlers[s];
+ if (h == SIG_DFL || h == SIG_IGN) {
+ signal(s, h);
+ sighandlers[s] = h;
+ } else {
+ sighandlers[s] = h;
+ signal(s, catcher);
+ }
+ return old;
+}
+
+extern void initsignal() {
+ void (*h)(int);
+ int i;
+ for (i = 1; i < NUMOFSIGNALS; i++) {
+ if ((h = signal(i, SIG_DFL)) != SIG_DFL)
+ signal(i, h);
+ sighandlers[i] = h;
+ }
+}
diff --git a/status.c b/status.c
@@ -0,0 +1,139 @@
+/* status.c: functions for printing fancy status messages in rc */
+
+#include "rc.h"
+#include "sigmsgs.h"
+
+/* status == the wait() value of the last command in the pipeline, or the last command */
+
+static int statuses[512];
+static int pipelength = 1;
+
+/*
+ Test to see if rc's status is true. According to td, status is true
+ if and only if every pipe-member has an exit status of zero.
+*/
+
+extern int istrue() {
+ int i;
+ for (i = 0; i < pipelength; i++)
+ if (statuses[i] != 0)
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ Return the status as an integer. A status which has low-bits set is
+ a signal number, whereas a status with high bits set is a value set
+ from exit(). The presence of a signal just sets status to 1. Also,
+ a pipeline with nonzero exit statuses in it just sets status to 1.
+*/
+
+extern int getstatus() {
+ int s;
+ if (pipelength > 1)
+ return !istrue();
+ s = statuses[0];
+ return (s&0xff) ? 1 : (s >> 8) & 0xff;
+}
+
+extern void set(bool code) {
+ setstatus(-1, (!code) << 8); /* exit status 1 == 0x100 */
+}
+
+/* take a pipeline and store the exit statuses. Check to see whether any of the children dumped core */
+
+extern void setpipestatus(int stats[], int num) {
+ int i;
+ for (i = 0; i < (pipelength = num); i++) {
+ statprint(-1, stats[i]);
+ statuses[i] = stats[i];
+ }
+}
+
+/* set a simple status, as opposed to a pipeline */
+
+extern void setstatus(int pid, int i) {
+ pipelength = 1;
+ statuses[0] = i;
+ statprint(pid, i);
+}
+
+/* print a message if termination was with a signal, and if the child dumped core. exit on error if -e is set */
+
+extern void statprint(int pid, int i) {
+ if (i & 0xff) {
+ char *msg = ((i & 0x7f) < NUMOFSIGNALS ? signals[i & 0x7f].msg : "");
+ if (pid != -1)
+ fprint(2, "%d: ", pid);
+ if (i & 0x80) {
+ if (*msg == '\0')
+ fprint(2, "core dumped\n");
+ else
+ fprint(2, "%s--core dumped\n", msg);
+ } else if (*msg != '\0')
+ fprint(2, "%s\n", msg);
+ }
+ if (i != 0 && dashee && !cond)
+ rc_exit(getstatus());
+}
+
+/* prepare a list to be passed back. Used whenever $status is dereferenced */
+
+extern List *sgetstatus() {
+ List *r;
+ int i;
+ for (r = NULL, i = 0; i < pipelength; i++) {
+ List *q = nnew(List);
+ int s = statuses[i];
+ int t;
+ q->n = r;
+ r = q;
+ if ((t = s & 0x7f) != 0) {
+ const char *core = (s & 0x80) ? "+core" : "";
+ if (t < NUMOFSIGNALS && *signals[t].name != '\0')
+ r->w = nprint("%s%s", signals[t].name, core);
+ else
+ r->w = nprint("-%d%s", t, core); /* unknown signals are negated */
+ } else
+ r->w = nprint("%d", (s >> 8) & 0xff);
+ r->m = NULL;
+ }
+ return r;
+}
+
+extern void ssetstatus(char **av) {
+ int i, j, k, l;
+ bool found;
+ for (l = 0; av[l] != NULL; l++)
+ ; /* count up array length */
+ --l;
+ for (i = 0; av[i] != NULL; i++) {
+ j = a2u(av[i]);
+ if (j >= 0) {
+ statuses[l - i] = j << 8;
+ continue;
+ }
+ found = FALSE;
+ for (k = 0; k < NUMOFSIGNALS; k++) {
+ if (streq(signals[k].name, av[i])) {
+ statuses[l - i] = k;
+ found = TRUE;
+ break;
+ }
+ else {
+ SIZE_T len = strlen(signals[k].name);
+ if (strncmp(signals[k].name, av[i], len) == 0 && streq(av[i] + len, "+core")) {
+ statuses[l - i] = k + 0x80;
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ fprint(2, "bad status\n");
+ set(FALSE);
+ return;
+ }
+ }
+ pipelength = i;
+}
diff --git a/tree.c b/tree.c
@@ -0,0 +1,172 @@
+/* tree.c: functions for manipulating parse-trees. (create, copy, delete) */
+
+#include "rc.h"
+
+/* make a new node, pass it back to yyparse. Used to generate the parsetree. */
+
+extern Node *mk(int /*nodetype*/ t,...) {
+ va_list ap;
+ Node *n;
+ va_start(ap, t);
+ switch (t) {
+ default:
+ panic("unexpected node in mk");
+ /* NOTREACHED */
+ case nDup:
+ n = nalloc(offsetof(Node, u[3]));
+ n->u[0].i = va_arg(ap, int);
+ n->u[1].i = va_arg(ap, int);
+ n->u[2].i = va_arg(ap, int);
+ break;
+ case nWord: case nQword:
+ n = nalloc(offsetof(Node, u[2]));
+ n->u[0].s = va_arg(ap, char *);
+ n->u[1].s = va_arg(ap, char *);
+ break;
+ case nBang: case nNowait:
+ case nCount: case nFlat: case nRmfn: case nSubshell:
+ case nVar: case nCase:
+ n = nalloc(offsetof(Node, u[1]));
+ n->u[0].p = va_arg(ap, Node *);
+ break;
+ case nAndalso: case nAssign: case nBackq: case nBody: case nBrace: case nConcat:
+ case nElse: case nEpilog: case nIf: case nNewfn: case nCbody:
+ case nOrelse: case nPre: case nArgs: case nSwitch:
+ case nMatch: case nVarsub: case nWhile: case nLappend:
+ n = nalloc(offsetof(Node, u[2]));
+ n->u[0].p = va_arg(ap, Node *);
+ n->u[1].p = va_arg(ap, Node *);
+ break;
+ case nForin:
+ n = nalloc(offsetof(Node, u[3]));
+ n->u[0].p = va_arg(ap, Node *);
+ n->u[1].p = va_arg(ap, Node *);
+ n->u[2].p = va_arg(ap, Node *);
+ break;
+ case nPipe:
+ n = nalloc(offsetof(Node, u[4]));
+ n->u[0].i = va_arg(ap, int);
+ n->u[1].i = va_arg(ap, int);
+ n->u[2].p = va_arg(ap, Node *);
+ n->u[3].p = va_arg(ap, Node *);
+ break;
+ case nRedir:
+ case nNmpipe:
+ n = nalloc(offsetof(Node, u[3]));
+ n->u[0].i = va_arg(ap, int);
+ n->u[1].i = va_arg(ap, int);
+ n->u[2].p = va_arg(ap, Node *);
+ break;
+ }
+ n->type = t;
+ va_end(ap);
+ return n;
+}
+
+/* copy a tree to malloc space. Used when storing the definition of a function */
+
+extern Node *treecpy(Node *s, void *(*alloc)(SIZE_T)) {
+ Node *n;
+ if (s == NULL)
+ return NULL;
+ switch (s->type) {
+ default:
+ panic("unexpected node in treecpy");
+ /* NOTREACHED */
+ case nDup:
+ n = (*alloc)(offsetof(Node, u[3]));
+ n->u[0].i = s->u[0].i;
+ n->u[1].i = s->u[1].i;
+ n->u[2].i = s->u[2].i;
+ break;
+ case nWord: case nQword:
+ n = (*alloc)(offsetof(Node, u[2]));
+ n->u[0].s = strcpy((char *) (*alloc)(strlen(s->u[0].s) + 1), s->u[0].s);
+ if (s->u[1].s != NULL) {
+ SIZE_T i = strlen(s->u[0].s);
+ n->u[1].s = (*alloc)(i);
+ memcpy(n->u[1].s, s->u[1].s, i);
+ } else
+ n->u[1].s = NULL;
+ break;
+ case nBang: case nNowait: case nCase:
+ case nCount: case nFlat: case nRmfn: case nSubshell: case nVar:
+ n = (*alloc)(offsetof(Node, u[1]));
+ n->u[0].p = treecpy(s->u[0].p, alloc);
+ break;
+ case nAndalso: case nAssign: case nBackq: case nBody: case nBrace: case nConcat:
+ case nElse: case nEpilog: case nIf: case nNewfn: case nCbody:
+ case nOrelse: case nPre: case nArgs: case nSwitch:
+ case nMatch: case nVarsub: case nWhile: case nLappend:
+ n = (*alloc)(offsetof(Node, u[2]));
+ n->u[0].p = treecpy(s->u[0].p, alloc);
+ n->u[1].p = treecpy(s->u[1].p, alloc);
+ break;
+ case nForin:
+ n = (*alloc)(offsetof(Node, u[3]));
+ n->u[0].p = treecpy(s->u[0].p, alloc);
+ n->u[1].p = treecpy(s->u[1].p, alloc);
+ n->u[2].p = treecpy(s->u[2].p, alloc);
+ break;
+ case nPipe:
+ n = (*alloc)(offsetof(Node, u[4]));
+ n->u[0].i = s->u[0].i;
+ n->u[1].i = s->u[1].i;
+ n->u[2].p = treecpy(s->u[2].p, alloc);
+ n->u[3].p = treecpy(s->u[3].p, alloc);
+ break;
+ case nRedir:
+ case nNmpipe:
+ n = (*alloc)(offsetof(Node, u[3]));
+ n->u[0].i = s->u[0].i;
+ n->u[1].i = s->u[1].i;
+ n->u[2].p = treecpy(s->u[2].p, alloc);
+ break;
+ }
+ n->type = s->type;
+ return n;
+}
+
+/* free a function definition that is no longer needed */
+
+extern void treefree(Node *s) {
+ if (s == NULL)
+ return;
+ switch (s->type) {
+ default:
+ panic("unexpected node in treefree");
+ /* NOTREACHED */
+ case nDup:
+ break;
+ case nWord: case nQword:
+ efree(s->u[0].s);
+ efree(s->u[1].s);
+ break;
+ case nBang: case nNowait:
+ case nCount: case nFlat: case nRmfn:
+ case nSubshell: case nVar: case nCase:
+ treefree(s->u[0].p);
+ break;
+ case nAndalso: case nAssign: case nBackq: case nBody: case nBrace: case nConcat:
+ case nElse: case nEpilog: case nIf: case nNewfn:
+ case nOrelse: case nPre: case nArgs: case nCbody:
+ case nSwitch: case nMatch: case nVarsub: case nWhile:
+ case nLappend:
+ treefree(s->u[1].p);
+ treefree(s->u[0].p);
+ break;
+ case nForin:
+ treefree(s->u[2].p);
+ treefree(s->u[1].p);
+ treefree(s->u[0].p);
+ break;
+ case nPipe:
+ treefree(s->u[2].p);
+ treefree(s->u[3].p);
+ break;
+ case nRedir:
+ case nNmpipe:
+ treefree(s->u[2].p);
+ }
+ efree(s);
+}
diff --git a/trip.rc b/trip.rc
Binary files differ.
diff --git a/utils.c b/utils.c
@@ -0,0 +1,125 @@
+/* utils.c: functions of general utility */
+
+#include <errno.h>
+#include <setjmp.h>
+#include "rc.h"
+#include "jbwrap.h"
+
+/* print error with line number on noninteractive shells (i.e., scripts) */
+
+extern void pr_error(char *s) {
+ if (s != NULL) {
+ if (interactive)
+ fprint(2, "%s\n", s);
+ else
+ fprint(2, "line %d: %s\n", lineno - 1, s);
+ }
+}
+
+/* our perror */
+
+extern void uerror(char *s) {
+ extern int sys_nerr;
+ extern char *sys_errlist[];
+ if (errno > sys_nerr)
+ return;
+ if (s != NULL)
+ fprint(2, "%s: %s\n", s, sys_errlist[errno]);
+ else
+ fprint(2, "%s\n", sys_errlist[errno]);
+}
+
+/* Die horribly. This should never get called. Please let me know if it does. */
+
+#define PANICMSG "rc panic: "
+
+extern void panic(char *s) {
+ write(2, PANICMSG, conststrlen(PANICMSG));
+ write(2, s, strlen(s));
+ write(2, "!\n", 2);
+ exit(1);
+}
+
+/* ascii -> unsigned conversion routines. -1 indicates conversion error. */
+
+extern int n2u(char *s, unsigned int base) {
+ unsigned int i;
+ for (i = 0; *s != '\0'; s++) {
+ unsigned int j = (unsigned int) *s - '0';
+ if (j >= base) /* small hack with unsigned ints -- one compare for range test */
+ return -1;
+ i = i * base + j;
+ }
+ return (int) i;
+}
+
+/* The last word in portable ANSI: a strcmp wrapper for qsort */
+
+extern int starstrcmp(const void *s1, const void *s2) {
+ return strcmp(*(char **)s1, *(char **)s2);
+}
+
+/* tests to see if pathname begins with "/", "./", or "../" */
+
+extern bool isabsolute(char *path) {
+ return path[0] == '/' || (path[0] == '.' && (path[1] == '/' || (path[1] == '.' && path[2] == '/')));
+}
+
+/* signal-safe read and write (for BSD slow devices). writeall also allows partial writes */
+
+extern void writeall(int fd, char *buf, SIZE_T remain) {
+ int i;
+ for (i = 0; remain > 0; buf += i, remain -= i) {
+ interrupt_happened = FALSE;
+ if (!setjmp(slowbuf.j)) {
+ slow = TRUE;
+ if (interrupt_happened)
+ break;
+ else if ((i = write(fd, buf, remain)) <= 0)
+ break; /* abort silently on errors in write() */
+ } else
+ break;
+ slow = FALSE;
+ }
+ slow = FALSE;
+ SIGCHK;
+}
+
+extern int rc_read(int fd, char *buf, SIZE_T n) {
+ long /*ssize_t*/ r;
+ interrupt_happened = FALSE;
+ if (!setjmp(slowbuf.j)) {
+ slow = TRUE;
+ if (!interrupt_happened)
+ r = read(fd, buf, n);
+ else
+ r = -2;
+ } else
+ r = -2;
+ slow = FALSE;
+ if (r == -2) {
+ errno = EINTR;
+ r = -1;
+ }
+ SIGCHK;
+ return r;
+}
+
+/* clear out z bytes from character string s */
+
+extern char *clear(char *s, SIZE_T z) {
+ while (z != 0)
+ s[--z] = 0;
+ return s;
+}
+
+/* duplicate a fd and close the old one only if necessary */
+
+extern int mvfd(int i, int j) {
+ if (i != j) {
+ int s = dup2(i, j);
+ close(i);
+ return s;
+ }
+ return 0;
+}
diff --git a/var.c b/var.c
@@ -0,0 +1,226 @@
+/* var.c: provide "public" functions for adding and removing variables from the symbol table */
+
+#include "rc.h"
+
+static void colonassign(char *, List *, bool);
+static void listassign(char *, List *, bool);
+static int hasalias(char *);
+
+static char *const aliases[] = {
+ "home", "HOME", "path", "PATH", "cdpath", "CDPATH"
+};
+
+/* assign a variable in List form to a name, stacking if appropriate */
+
+extern void varassign(char *name, List *def, bool stack) {
+ Variable *new;
+ List *newdef = listcpy(def, ealloc); /* important to do the listcpy first; get_var_place() frees old values */
+ new = get_var_place(name, stack);
+ new->def = newdef;
+ new->extdef = NULL;
+#ifdef READLINE /* need to reset readline() every time TERM or TERMCAP changes */
+ if (interactive && (streq(name, "TERM") || streq(name, "TERMCAP"))) {
+ extern void rl_reset_terminal(char *);
+ rl_reset_terminal(NULL);
+ }
+#endif
+}
+
+/* assign a variable in string form. Check to see if it is aliased (e.g., PATH and path) */
+
+extern bool varassign_string(char *extdef) {
+ static bool aliasset[arraysize(aliases)] = {
+ FALSE, FALSE, FALSE, FALSE, FALSE, FALSE
+ };
+ char *name = get_name(extdef);
+ Variable *new;
+ int i;
+ if (name == NULL)
+ return FALSE; /* add it to bozo env */
+ if ((i = hasalias(name)) != -1) {
+ aliasset[i] = TRUE;
+ i ^= 1; /* set i to the "opposite" case subscript and */
+ if (i&1 && aliasset[i]) /* don't alias variables that are already set in upper case */
+ return TRUE;
+ }
+ new = get_var_place(name, FALSE);
+ new->def = NULL;
+ new->extdef = ealloc(strlen(extdef) + 1);
+ strcpy(new->extdef, extdef);
+ if (i != -1)
+ alias(name, varlookup(name), FALSE);
+ return TRUE;
+}
+
+/*
+ Return a List based on a name lookup. If the list is in external (string) form,
+ convert it to internal (List) form. Treat $n (n is an integer) specially as $*(n).
+ Also check to see if $status is being dereferenced. (we lazily evaluate the List
+ associated with $status)
+*/
+
+extern List *varlookup(char *name) {
+ Variable *look;
+ List *ret, *l;
+ int sub;
+ if (streq(name, "status"))
+ return sgetstatus();
+ if (streq(name, "apids"))
+ return sgetapids();
+ if (*name != '\0' && (sub = a2u(name)) != -1) { /* handle $1, $2, etc. */
+ for (l = varlookup("*"); l != NULL && sub != 0; --sub)
+ l = l->n;
+ if (l == NULL)
+ return NULL;
+ ret = nnew(List);
+ ret->w = l->w;
+ ret->m = NULL;
+ ret->n = NULL;
+ return ret;
+ }
+ look = lookup_var(name);
+ if (look == NULL)
+ return NULL; /* not found */
+ if (look->def != NULL)
+ return look->def;
+ if (look->extdef == NULL)
+ return NULL; /* variable was set to null, e.g., a=() echo foo */
+ ret = parse_var(name, look->extdef);
+ if (ret == NULL) {
+ look->extdef = NULL;
+ return NULL;
+ }
+ return look->def = ret;
+}
+
+/* lookup a variable in external (string) form, converting if necessary. Used by makeenv() */
+
+extern char *varlookup_string(char *name) {
+ Variable *look;
+ look = lookup_var(name);
+ if (look == NULL)
+ return NULL;
+ if (look->extdef != NULL)
+ return look->extdef;
+ if (look->def == NULL)
+ return NULL;
+ return look->extdef = list2str(name, look->def);
+}
+
+/* remove a variable from the symtab. "stack" determines whether a level of scoping is popped or not */
+
+extern void varrm(char *name, bool stack) {
+ int i = hasalias(name);
+ if (streq(name, "*") && !stack) { /* when assigning () to $*, we want to preserve $0 */
+ varassign("*", varlookup("0"), FALSE);
+ return;
+ }
+ delete_var(name, stack);
+ if (i != -1)
+ delete_var(aliases[i^1], stack);
+}
+
+/* assign a value (List) to a variable, using array "a" as input. Used to assign $* */
+
+extern void starassign(char *dollarzero, char **a, bool stack) {
+ List *s, *var;
+ var = nnew(List);
+ var->w = dollarzero;
+ if (*a == NULL) {
+ var->n = NULL;
+ varassign("*", var, stack);
+ return;
+ }
+ var->n = s = nnew(List);
+ while (1) {
+ s->w = *a++;
+ if (*a == NULL) {
+ s->n = NULL;
+ break;
+ } else
+ s = s->n = nnew(List);
+ }
+ varassign("*", var, stack);
+}
+
+/* (ugly name, huh?) assign a colon-separated value to a variable (e.g., PATH) from a List (e.g., path) */
+
+static void colonassign(char *name, List *def, bool stack) {
+ List dud;
+ if (def == NULL) {
+ varassign(name, NULL, stack);
+ return;
+ }
+ dud.w = nprint("%-L", def, ":");
+ dud.n = NULL;
+ varassign(name, &dud, stack);
+}
+
+/* assign a List variable (e.g., path) from a colon-separated string (e.g., PATH) */
+
+static void listassign(char *name, List *def, bool stack) {
+ List *val, *r;
+ char *v, *w;
+ if (def == NULL) {
+ varassign(name, NULL, stack);
+ return;
+ }
+ v = def->w;
+ r = val = enew(List);
+ while ((w = strchr(v, ':')) != NULL) {
+ *w = '\0';
+ r->w = ecpy(v);
+ *w = ':';
+ v = w + 1;
+ r->n = enew(List);
+ r = r->n;
+ }
+ r->w = ecpy(v);
+ r->n = NULL;
+ varassign(name, val, stack);
+}
+
+/* check to see if a particular variable is aliased; return -1 on failure, or the index */
+
+static int hasalias(char *name) {
+ int i;
+ for (i = 0; i < arraysize(aliases); i++)
+ if (streq(name, aliases[i]))
+ return i;
+ return -1;
+}
+
+/* alias a variable to its lowercase equivalent. function pointers are used to specify the conversion function */
+
+extern void alias(char *name, List *s, bool stack) {
+ static void (*vectors[])(char *, List *, bool) = {
+ varassign, varassign, colonassign, listassign, colonassign, listassign
+ };
+ int i = hasalias(name);
+ if (i != -1)
+ (*vectors[i])(aliases[i^1], s, stack); /* xor hack to reverse case of alias entry */
+}
+
+extern void prettyprint_var(int fd, char *name, List *s) {
+ int i;
+ static const char * const keywords[] = {
+ "if", "in", "fn", "for", "else", "switch", "while", "case"
+ };
+ if (s == NULL) {
+ fprint(fd, "%S=()\n", name);
+ return;
+ }
+ if (streq(name, "*")) {
+ s = s->n;
+ if (s == NULL)
+ return; /* Don't print $0, and if $* is not set, skip it */
+ }
+ for (i = 0; i < arraysize(keywords); i++)
+ if (streq(keywords[i], name)) {
+ fprint(fd, "%#S=", name);
+ goto value;
+ }
+ fprint(fd, "%S=", name);
+value:
+ fprint(fd, s->n == NULL ? "%L\n" : "(%L)\n", s, " ");
+}
diff --git a/version.c b/version.c
@@ -0,0 +1 @@
+const char id[] = "@(#)rc version 1.4, 5/26/92.";
diff --git a/wait.c b/wait.c
@@ -0,0 +1,124 @@
+#include <errno.h>
+#include <setjmp.h>
+#include "rc.h"
+#include "jbwrap.h"
+
+bool forked = FALSE;
+
+static int rc_wait(int *);
+
+typedef struct Pid Pid;
+
+static struct Pid {
+ int pid, stat;
+ bool alive;
+ Pid *n;
+} *plist = NULL;
+
+extern int rc_fork() {
+ Pid *new = enew(Pid);
+ int pid = fork();
+ switch (pid) {
+ case -1:
+ uerror("fork");
+ rc_error(NULL);
+ /* NOTREACHED */
+ case 0:
+ forked = TRUE;
+ SIGCHK;
+ return 0;
+ default:
+ new->pid = pid;
+ new->alive = TRUE;
+ new->n = plist;
+ plist = new;
+ return pid;
+ }
+}
+
+extern int rc_wait4(int pid, int *stat, bool nointr) {
+ Pid *r, *prev;
+ int ret;
+ /* first look for a child which may already have exited */
+again: for (r = plist, prev = NULL; r != NULL; prev = r, r = r->n)
+ if (r->pid == pid)
+ break;
+ if (r == NULL) {
+ errno = ECHILD; /* no children */
+ uerror("wait");
+ *stat = 0x100; /* exit(1) */
+ return -1;
+ }
+ if (r->alive) {
+ while (pid != (ret = rc_wait(stat))) {
+ Pid *q;
+ if (ret < 0) {
+ if (nointr)
+ goto again;
+ return ret;
+ }
+ for (q = plist; q != NULL; q = q->n)
+ if (q->pid == ret) {
+ q->alive = FALSE;
+ q->stat = *stat;
+ break;
+ }
+ }
+ } else
+ *stat = r->stat;
+ if (prev == NULL)
+ plist = r->n; /* remove element from head of list */
+ else
+ prev->n = r->n;
+ efree(r);
+ return pid;
+}
+
+extern List *sgetapids() {
+ List *r;
+ Pid *p;
+ for (r = NULL, p = plist; p != NULL; p = p->n) {
+ List *q;
+ if (!p->alive)
+ continue;
+ q = nnew(List);
+ q->w = nprint("%d", p->pid);
+ q->m = NULL;
+ q->n = r;
+ r = q;
+ }
+ return r;
+}
+
+extern void waitforall() {
+ int stat;
+ while (plist != NULL) {
+ int pid = rc_wait4(plist->pid, &stat, FALSE);
+ if (pid > 0)
+ setstatus(pid, stat);
+ else
+ set(FALSE);
+ SIGCHK;
+ }
+}
+
+/*
+ rc_wait: a wait() wrapper that interfaces wait() w/rc signals.
+ Note that the signal queue is not checked in this fn; someone
+ may want to resume the wait() without delivering any signals.
+*/
+
+static int rc_wait(int *stat) {
+ int r;
+ interrupt_happened = FALSE;
+ if (!setjmp(slowbuf.j)) {
+ slow = TRUE;
+ if (!interrupt_happened)
+ r = wait(stat);
+ else
+ r = -1;
+ } else
+ r = -1;
+ slow = FALSE;
+ return r;
+}
diff --git a/walk.c b/walk.c
@@ -0,0 +1,344 @@
+/* walk.c: walks the parse tree. */
+
+#include <signal.h>
+#include <setjmp.h>
+#include "rc.h"
+#include "jbwrap.h"
+
+/*
+ global which indicates whether rc is executing a test;
+ used by rc -e so that if (false) does not exit.
+*/
+bool cond = FALSE;
+
+static bool isallpre(Node *);
+static bool dofork(bool);
+static void dopipe(Node *);
+
+/* Tail-recursive version of walk() */
+
+#define WALK(x, y) { n = x; parent = y; goto top; }
+
+/* walk the parse-tree. "obvious". */
+
+extern bool walk(Node *n, bool parent) {
+top: SIGCHK;
+ if (n == NULL) {
+ if (!parent)
+ exit(0);
+ set(TRUE);
+ return TRUE;
+ }
+ switch (n->type) {
+ case nArgs: case nBackq: case nConcat: case nCount:
+ case nFlat: case nLappend: case nRedir: case nVar:
+ case nVarsub: case nWord: case nQword:
+ exec(glob(glom(n)), parent); /* simple command */
+ break;
+ case nBody:
+ walk(n->u[0].p, TRUE);
+ WALK(n->u[1].p, parent);
+ /* WALK doesn't fall through */
+ case nNowait: {
+ int pid;
+ if ((pid = rc_fork()) == 0) {
+#if !defined(NOJOB) && defined(SIGTTOU) && defined(SIGTTIN) && defined(SIGTSTP)
+ setsigdefaults(FALSE);
+ rc_signal(SIGTTOU, SIG_IGN); /* Berkeleyized version: put it in a new pgroup. */
+ rc_signal(SIGTTIN, SIG_IGN);
+ rc_signal(SIGTSTP, SIG_IGN);
+ setpgrp(0, getpid());
+#else
+ setsigdefaults(TRUE); /* ignore SIGINT, SIGQUIT, SIGTERM */
+#endif
+ mvfd(rc_open("/dev/null", rFrom), 0);
+ walk(n->u[0].p, FALSE);
+ exit(getstatus());
+ }
+ if (interactive)
+ fprint(2, "%d\n", pid);
+ varassign("apid", word(nprint("%d", pid), NULL), FALSE);
+ redirq = NULL; /* kill pre-redir queue */
+ break;
+ }
+ case nAndalso: {
+ bool oldcond = cond;
+ cond = TRUE;
+ if (walk(n->u[0].p, TRUE)) {
+ cond = oldcond;
+ WALK(n->u[1].p, parent);
+ } else
+ cond = oldcond;
+ break;
+ }
+ case nOrelse: {
+ bool oldcond = cond;
+ cond = TRUE;
+ if (!walk(n->u[0].p, TRUE)) {
+ cond = oldcond;
+ WALK(n->u[1].p, parent);
+ } else
+ cond = oldcond;
+ break;
+ }
+ case nBang:
+ set(!walk(n->u[0].p, TRUE));
+ break;
+ case nIf: {
+ bool oldcond = cond;
+ Node *true_cmd = n->u[1].p, *false_cmd = NULL;
+ if (true_cmd != NULL && true_cmd->type == nElse) {
+ false_cmd = true_cmd->u[1].p;
+ true_cmd = true_cmd->u[0].p;
+ }
+ cond = TRUE;
+ if (!walk(n->u[0].p, TRUE))
+ true_cmd = false_cmd; /* run the else clause */
+ cond = oldcond;
+ WALK(true_cmd, parent);
+ }
+ case nWhile: {
+ Jbwrap j;
+ Edata jbreak;
+ Estack e1, e2;
+ bool testtrue, oldcond = cond;
+ cond = TRUE;
+ if (!walk(n->u[0].p, TRUE)) { /* prevent spurious breaks inside test */
+ cond = oldcond;
+ break;
+ }
+ if (setjmp(j.j))
+ break;
+ jbreak.jb = &j;
+ except(eBreak, jbreak, &e1);
+ do {
+ Edata block;
+ block.b = newblock();
+ cond = oldcond;
+ except(eArena, block, &e2);
+ walk(n->u[1].p, TRUE);
+ testtrue = walk(n->u[0].p, TRUE);
+ unexcept(); /* eArena */
+ cond = TRUE;
+ } while (testtrue);
+ cond = oldcond;
+ unexcept(); /* eBreak */
+ break;
+ }
+ case nForin: {
+ List *l, *var = glom(n->u[0].p);
+ Jbwrap j;
+ Estack e1, e2;
+ Edata jbreak;
+ if (setjmp(j.j))
+ break;
+ jbreak.jb = &j;
+ except(eBreak, jbreak, &e1);
+ for (l = listcpy(glob(glom(n->u[1].p)), nalloc); l != NULL; l = l->n) {
+ Edata block;
+ assign(var, word(l->w, NULL), FALSE);
+ block.b = newblock();
+ except(eArena, block, &e2);
+ walk(n->u[2].p, TRUE);
+ unexcept(); /* eArena */
+ }
+ unexcept(); /* eBreak */
+ break;
+ }
+ case nSubshell:
+ if (dofork(TRUE)) {
+ setsigdefaults(FALSE);
+ walk(n->u[0].p, FALSE);
+ rc_exit(getstatus());
+ }
+ break;
+ case nAssign:
+ if (n->u[0].p == NULL)
+ rc_error("null variable name");
+ assign(glom(n->u[0].p), glob(glom(n->u[1].p)), FALSE);
+ set(TRUE);
+ break;
+ case nPipe:
+ dopipe(n);
+ break;
+ case nNewfn: {
+ List *l = glom(n->u[0].p);
+ if (l == NULL)
+ rc_error("null function name");
+ while (l != NULL) {
+ if (dashex)
+ prettyprint_fn(2, l->w, n->u[1].p);
+ fnassign(l->w, n->u[1].p);
+ l = l->n;
+ }
+ set(TRUE);
+ break;
+ }
+ case nRmfn: {
+ List *l = glom(n->u[0].p);
+ while (l != NULL) {
+ if (dashex)
+ fprint(2, "fn %S\n", l->w);
+ fnrm(l->w);
+ l = l->n;
+ }
+ set(TRUE);
+ break;
+ }
+ case nDup:
+ redirq = NULL;
+ break; /* Null command */
+ case nMatch: {
+ List *a = glob(glom(n->u[0].p)), *b = glom(n->u[1].p);
+ if (dashex)
+ fprint(2, (a != NULL && a->n != NULL) ? "~ (%L) %L\n" : "~ %L %L\n", a, " ", b, " ");
+ set(lmatch(a, b));
+ break;
+ }
+ case nSwitch: {
+ List *v = glom(n->u[0].p);
+ while (1) {
+ do {
+ n = n->u[1].p;
+ if (n == NULL)
+ return istrue();
+ } while (n->u[0].p == NULL || n->u[0].p->type != nCase);
+ if (lmatch(v, glom(n->u[0].p->u[0].p))) {
+ for (n = n->u[1].p; n != NULL && (n->u[0].p == NULL || n->u[0].p->type != nCase); n = n->u[1].p)
+ walk(n->u[0].p, TRUE);
+ break;
+ }
+ }
+ break;
+ }
+ case nPre: {
+ List *v;
+ if (n->u[0].p->type == nRedir || n->u[0].p->type == nDup) {
+ qredir(n->u[0].p);
+ walk(n->u[1].p, parent);
+ } else if (n->u[0].p->type == nAssign) {
+ if (isallpre(n->u[1].p)) {
+ walk(n->u[0].p, TRUE);
+ WALK(n->u[1].p, parent);
+ } else {
+ Estack e;
+ Edata var;
+ v = glom(n->u[0].p->u[0].p);
+ assign(v, glob(glom(n->u[0].p->u[1].p)), TRUE);
+ var.name = v->w;
+ except(eVarstack, var, &e);
+ walk(n->u[1].p, parent);
+ varrm(v->w, TRUE);
+ unexcept(); /* eVarstack */
+ }
+ } else
+ panic("unexpected node in preredir section of walk");
+ break;
+ }
+ case nBrace:
+ if (n->u[1].p == NULL) {
+ WALK(n->u[0].p, parent);
+ } else if (dofork(parent)) {
+ setsigdefaults(FALSE);
+ walk(n->u[1].p, TRUE); /* Do redirections */
+ redirq = NULL; /* Reset redirection queue */
+ walk(n->u[0].p, FALSE); /* Do commands */
+ rc_exit(getstatus());
+ /* NOTREACHED */
+ }
+ break;
+ case nEpilog:
+ qredir(n->u[0].p);
+ if (n->u[1].p != NULL) {
+ WALK(n->u[1].p, parent); /* Do more redirections. */
+ } else {
+ doredirs(); /* Okay, we hit the bottom. */
+ }
+ break;
+ case nNmpipe:
+ rc_error("named pipes cannot be executed as commands");
+ /* NOTREACHED */
+ default:
+ panic("unknown node in walk");
+ /* NOTREACHED */
+ }
+ return istrue();
+}
+
+/* checks to see whether a subtree is all pre-command directives, i.e., assignments and redirs only */
+
+static bool isallpre(Node *n) {
+ while (n != NULL && n->type == nPre)
+ n = n->u[1].p;
+ return n == NULL || n->type == nRedir || n->type == nAssign || n->type == nDup;
+}
+
+/*
+ A code-saver. Forks, child returns (for further processing in walk()), and the parent
+ waits for the child to finish, setting $status appropriately.
+*/
+
+static bool dofork(bool parent) {
+ int pid, sp;
+
+ if (!parent || (pid = rc_fork()) == 0)
+ return TRUE;
+ redirq = NULL; /* clear out the pre-redirection queue in the parent */
+ rc_wait4(pid, &sp, TRUE);
+ setstatus(-1, sp);
+ SIGCHK;
+ return FALSE;
+}
+
+static void dopipe(Node *n) {
+ int i, j, sp, pid, fd_prev, fd_out, pids[512], stats[512], p[2];
+ bool intr;
+ Node *r;
+
+ fd_prev = fd_out = 1;
+ for (r = n, i = 0; r != NULL && r->type == nPipe; r = r->u[2].p, i++) {
+ if (i > 500) /* the only hard-wired limit in rc? */
+ rc_error("pipe too long");
+ if (pipe(p) < 0) {
+ uerror("pipe");
+ rc_error(NULL);
+ }
+ if ((pid = rc_fork()) == 0) {
+ setsigdefaults(FALSE);
+ redirq = NULL; /* clear preredir queue */
+ mvfd(p[0], r->u[1].i);
+ if (fd_prev != 1)
+ mvfd(fd_prev, fd_out);
+ close(p[1]);
+ walk(r->u[3].p, FALSE);
+ exit(getstatus());
+ }
+ if (fd_prev != 1)
+ close(fd_prev); /* parent must close all pipe fd's */
+ pids[i] = pid;
+ fd_prev = p[1];
+ fd_out = r->u[0].i;
+ close(p[0]);
+ }
+ if ((pid = rc_fork()) == 0) {
+ setsigdefaults(FALSE);
+ mvfd(fd_prev, fd_out);
+ walk(r, FALSE);
+ exit(getstatus());
+ /* NOTREACHED */
+ }
+ redirq = NULL; /* clear preredir queue */
+ close(fd_prev);
+ pids[i++] = pid;
+
+ /* collect statuses */
+
+ intr = FALSE;
+ for (j = 0; j < i; j++) {
+ rc_wait4(pids[j], &sp, TRUE);
+ stats[j] = sp;
+ intr |= (sp == SIGINT);
+ }
+ setpipestatus(stats, i);
+ SIGCHK;
+}
diff --git a/which.c b/which.c
@@ -0,0 +1,114 @@
+/* which.c: check to see if a file is executable.
+
+ This function was originally written with Maarten Litmaath's which.c as
+ a template, but was changed in order to accomodate the possibility of
+ rc's running setuid or the possibility of executing files not in the
+ primary group. Much of this file has been re-vamped by Paul Haahr.
+ I re-re-vamped the functions that Paul supplied to correct minor bugs
+ and to strip out unneeded functionality.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <errno.h>
+#include "rc.h"
+
+#define X_USR 0100
+#define X_GRP 0010
+#define X_OTH 0001
+#define X_ALL (X_USR|X_GRP|X_OTH)
+
+extern int stat(const char *, struct stat *);
+
+static bool initialized = FALSE;
+static int uid, gid;
+
+#ifdef NGROUPS
+static int ngroups, gidset[NGROUPS];
+
+/* determine whether gid lies in gidset */
+
+static int ingidset(int g) {
+ int i;
+ for (i = 0; i < ngroups; i++)
+ if (g == gidset[i])
+ return 1;
+ return 0;
+}
+#endif
+
+/*
+ A home-grown access/stat. Does the right thing for group-executable files.
+ Returns a bool instead of this -1 nonsense.
+*/
+
+static bool rc_access(char *path, bool verbose) {
+ struct stat st;
+ int mask;
+ if (stat(path, &st) != 0) {
+ if (verbose) /* verbose flag only set for absolute pathname */
+ uerror(path);
+ return FALSE;
+ }
+ if (uid == 0)
+ mask = X_ALL;
+ else if (uid == st.st_uid)
+ mask = X_USR;
+#ifdef NGROUPS
+ else if (gid == st.st_gid || ingidset(st.st_gid))
+#else
+ else if (gid == st.st_gid)
+#endif
+ mask = X_GRP;
+ else
+ mask = X_OTH;
+ if (((st.st_mode & S_IFMT) == S_IFREG) && (st.st_mode & mask))
+ return TRUE;
+ errno = EACCES;
+ if (verbose)
+ uerror(path);
+ return FALSE;
+}
+
+/* return a full pathname by searching $path, and by checking the status of the file */
+
+extern char *which(char *name, bool verbose) {
+ static char *test = NULL;
+ static SIZE_T testlen = 0;
+ List *path;
+ int len;
+ if (name == NULL) /* no filename? can happen with "> foo" as a command */
+ return NULL;
+ if (!initialized) {
+ initialized = TRUE;
+ uid = geteuid();
+ gid = getegid();
+#ifdef NGROUPS
+ ngroups = getgroups(NGROUPS, gidset);
+#endif
+ }
+ if (isabsolute(name)) /* absolute pathname? */
+ return rc_access(name, verbose) ? name : NULL;
+ len = strlen(name);
+ for (path = varlookup("path"); path != NULL; path = path->n) {
+ SIZE_T need = strlen(path->w) + len + 2; /* one for null terminator, one for the '/' */
+ if (testlen < need) {
+ efree(test);
+ test = ealloc(testlen = need);
+ }
+ if (*path->w == '\0') {
+ strcpy(test, name);
+ } else {
+ strcpy(test, path->w);
+ if (!streq(test, "/")) /* "//" is special to POSIX */
+ strcat(test, "/");
+ strcat(test, name);
+ }
+ if (rc_access(test, FALSE))
+ return test;
+ }
+ if (verbose)
+ fprint(2, "%s not found\n", name);
+ return NULL;
+}