From 6bb38f6a01dbf4bbd584e038ceace0a00faa27bb Mon Sep 17 00:00:00 2001 From: Byron Rakitzis Date: Wed, 27 May 1992 00:00:01 +0100 Subject: release: rc-1.4 --- .gitignore | 0 CHANGES | 171 +++++ COPYRIGHT | 26 + EXAMPLES | 749 +++++++++++++++++++++ Makefile | 65 ++ README | 100 +++ addon.c | 22 + addon.h | 38 ++ builtins.c | 532 +++++++++++++++ config.h-dist | 204 ++++++ cpp | 38 ++ except.c | 140 ++++ exec.c | 119 ++++ execve.c | 61 ++ fn.c | 266 ++++++++ footobar.c | 368 +++++++++++ getopt.c | 50 ++ glob.c | 240 +++++++ glom.c | 420 ++++++++++++ hash.c | 302 +++++++++ heredoc.c | 156 +++++ history/Makefile | 6 + history/history.1 | 234 +++++++ history/history.c | 333 ++++++++++ input.c | 373 +++++++++++ jbwrap.h | 10 + lex.c | 398 ++++++++++++ list.c | 57 ++ main.c | 115 ++++ match.c | 99 +++ mksignal | 75 +++ nalloc.c | 137 ++++ open.c | 29 + parse.y | 174 +++++ print.c | 456 +++++++++++++ proto.h | 81 +++ rc.1 | 1880 +++++++++++++++++++++++++++++++++++++++++++++++++++++ rc.h | 380 +++++++++++ redir.c | 77 +++ signal.c | 75 +++ status.c | 139 ++++ tree.c | 172 +++++ trip.rc | Bin 0 -> 14684 bytes utils.c | 125 ++++ var.c | 226 +++++++ version.c | 1 + wait.c | 124 ++++ walk.c | 344 ++++++++++ which.c | 114 ++++ 49 files changed, 10301 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGES create mode 100644 COPYRIGHT create mode 100644 EXAMPLES create mode 100644 Makefile create mode 100644 README create mode 100644 addon.c create mode 100644 addon.h create mode 100644 builtins.c create mode 100644 config.h-dist create mode 100644 cpp create mode 100644 except.c create mode 100644 exec.c create mode 100644 execve.c create mode 100644 fn.c create mode 100644 footobar.c create mode 100644 getopt.c create mode 100644 glob.c create mode 100644 glom.c create mode 100644 hash.c create mode 100644 heredoc.c create mode 100644 history/Makefile create mode 100644 history/history.1 create mode 100644 history/history.c create mode 100644 input.c create mode 100644 jbwrap.h create mode 100644 lex.c create mode 100644 list.c create mode 100644 main.c create mode 100644 match.c create mode 100755 mksignal create mode 100644 nalloc.c create mode 100644 open.c create mode 100644 parse.y create mode 100644 print.c create mode 100644 proto.h create mode 100644 rc.1 create mode 100644 rc.h create mode 100644 redir.c create mode 100644 signal.c create mode 100644 status.c create mode 100644 tree.c create mode 100644 trip.rc create mode 100644 utils.c create mode 100644 var.c create mode 100644 version.c create mode 100644 wait.c create mode 100644 walk.c create mode 100644 which.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..8456cd5 --- /dev/null +++ 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 new file mode 100644 index 0000000..279f66c --- /dev/null +++ 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 new file mode 100644 index 0000000..90bd8df --- /dev/null +++ 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 ) + +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 +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; 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 +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 new file mode 100644 index 0000000..7306f36 --- /dev/null +++ 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 new file mode 100644 index 0000000..401fa8f --- /dev/null +++ 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 + <<>> + (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 new file mode 100644 index 0000000..df3fcd7 --- /dev/null +++ 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 +#include +#include + +#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 new file mode 100644 index 0000000..25f2312 --- /dev/null +++ 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 new file mode 100644 index 0000000..d9b2e2e --- /dev/null +++ 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 +#include +#include +#include "rc.h" +#include "jbwrap.h" +#ifndef NOLIMITS +#include +#include +#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 new file mode 100644 index 0000000..5be81eb --- /dev/null +++ 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 but not + * . (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 new file mode 100644 index 0000000..e3ac656 --- /dev/null +++ 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 new file mode 100644 index 0000000..48ea008 --- /dev/null +++ b/except.c @@ -0,0 +1,140 @@ +#include +#include +#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 new file mode 100644 index 0000000..38d627c --- /dev/null +++ b/exec.c @@ -0,0 +1,119 @@ +/* exec.c */ +#include +#include +#include +#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 new file mode 100644 index 0000000..b21b30c --- /dev/null +++ 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 +#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 new file mode 100644 index 0000000..02de02b --- /dev/null +++ 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 +#include +#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 new file mode 100644 index 0000000..f50db3c --- /dev/null +++ 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 new file mode 100644 index 0000000..423b84a --- /dev/null +++ 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 new file mode 100644 index 0000000..1b0121b --- /dev/null +++ b/glob.c @@ -0,0 +1,240 @@ +/* glob.c: rc's (ugly) globber. This code is not elegant, but it works */ + +#include +#include +#include "rc.h" +#ifdef NODIRENT +#include +#define dirent direct /* need to get the struct declaraction right */ +#else +#include +#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 new file mode 100644 index 0000000..a82183c --- /dev/null +++ b/glom.c @@ -0,0 +1,420 @@ +/* glom.c: builds an argument list out of words, variables, etc. */ + +#include +#include +#include +#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 new file mode 100644 index 0000000..9397218 --- /dev/null +++ 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 new file mode 100644 index 0000000..3930d9e --- /dev/null +++ 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 new file mode 100644 index 0000000..4655947 --- /dev/null +++ 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 new file mode 100644 index 0000000..18ed28f --- /dev/null +++ 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 new file mode 100644 index 0000000..5f2cece --- /dev/null +++ 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 +#include +#include + +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 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 new file mode 100644 index 0000000..9018a54 --- /dev/null +++ b/input.c @@ -0,0 +1,373 @@ +/* input.c: i/o routines for files and pseudo-files (strings) */ + +#include +#include +#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 new file mode 100644 index 0000000..a31ef19 --- /dev/null +++ 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 new file mode 100644 index 0000000..90be65d --- /dev/null +++ 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 new file mode 100644 index 0000000..f795485 --- /dev/null +++ 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 new file mode 100644 index 0000000..0b65adf --- /dev/null +++ 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 new file mode 100644 index 0000000..6f87614 --- /dev/null +++ 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 new file mode 100755 index 0000000..9a9b968 --- /dev/null +++ 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 ^ +awk ' + { sum = sum + 1; } + END { print "#define NUMOFSIGNALS", sum } +' diff --git a/nalloc.c b/nalloc.c new file mode 100644 index 0000000..5498e38 --- /dev/null +++ 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 new file mode 100644 index 0000000..7287e0e --- /dev/null +++ b/open.c @@ -0,0 +1,29 @@ +/* open.c: to insulate from the rest of rc. */ + +#include +#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 new file mode 100644 index 0000000..f1f5526 --- /dev/null +++ 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 SREDIR +%type PIPE +%type DUP +%type WORD +%type keyword +%type 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 new file mode 100644 index 0000000..36fefb5 --- /dev/null +++ b/print.c @@ -0,0 +1,456 @@ +/* print.c -- formatted printing routines (Paul Haahr, 12/91) */ + +#include "rc.h" +#include + +#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 new file mode 100644 index 0000000..a599537 --- /dev/null +++ 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 + 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 new file mode 100644 index 0000000..c9f9e08 --- /dev/null +++ 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 new file mode 100644 index 0000000..9e38d8b --- /dev/null +++ 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 + +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, /* % */ + FMT_f2set = 128 /* %. */ +}; + +/* 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 new file mode 100644 index 0000000..b582341 --- /dev/null +++ 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 new file mode 100644 index 0000000..645fb36 --- /dev/null +++ b/signal.c @@ -0,0 +1,75 @@ +/* signal.c: a Hugh-approved signal handler. */ + +#include +#include +#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 new file mode 100644 index 0000000..1b6325b --- /dev/null +++ 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 new file mode 100644 index 0000000..6ce1ee8 --- /dev/null +++ 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 new file mode 100644 index 0000000..ba38207 Binary files /dev/null and b/trip.rc differ diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..6ab303c --- /dev/null +++ b/utils.c @@ -0,0 +1,125 @@ +/* utils.c: functions of general utility */ + +#include +#include +#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 new file mode 100644 index 0000000..ef780b3 --- /dev/null +++ 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 new file mode 100644 index 0000000..0cdc517 --- /dev/null +++ b/version.c @@ -0,0 +1 @@ +const char id[] = "@(#)rc version 1.4, 5/26/92."; diff --git a/wait.c b/wait.c new file mode 100644 index 0000000..a70acee --- /dev/null +++ b/wait.c @@ -0,0 +1,124 @@ +#include +#include +#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 new file mode 100644 index 0000000..c18921c --- /dev/null +++ b/walk.c @@ -0,0 +1,344 @@ +/* walk.c: walks the parse tree. */ + +#include +#include +#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 new file mode 100644 index 0000000..a9e2f65 --- /dev/null +++ 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 +#include +#include +#include +#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; +} -- cgit v1.2.3