12742 lines
		
	
	
		
			269 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			12742 lines
		
	
	
		
			269 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* vi: set sw=4 ts=4: */
 | |
| /*
 | |
|  * ash shell port for busybox
 | |
|  *
 | |
|  * Copyright (c) 1989, 1991, 1993, 1994
 | |
|  *      The Regents of the University of California.  All rights reserved.
 | |
|  *
 | |
|  * This code is derived from software contributed to Berkeley by
 | |
|  * Kenneth Almquist.
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License as published by
 | |
|  * the Free Software Foundation; either version 2 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 | |
|  * General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program; if not, write to the Free Software
 | |
|  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 | |
|  *
 | |
|  * This version of ash is adapted from the source in Debian's ash 0.3.8-5
 | |
|  * package.
 | |
|  *
 | |
|  * Modified by Erik Andersen <andersee@debian.org> and
 | |
|  * Vladimir Oleynik <dzo@simtreas.ru> to be used in busybox
 | |
|  *
 | |
|  *
 | |
|  * Original copyright notice is retained at the end of this file.
 | |
|  */
 | |
| 
 | |
| 
 | |
| /* These defines allow you to adjust the feature set to be compiled
 | |
|  * into the ash shell.   As a rule, enabling these options will make
 | |
|  * ash get bigger...   With all of these options off, ash adds about
 | |
|  * 60k to busybox on an x86 system.*/
 | |
| 
 | |
| 
 | |
| /* Enable job control.  This allows you to run jobs in the background,
 | |
|  * which is great when ash is being  used as an interactive shell, but
 | |
|  * it completely useless for is all you are doing is running scripts.
 | |
|  * This adds about 2.5k on an x86 system. */
 | |
| #undef JOBS
 | |
| 
 | |
| /* This enables alias support in ash.  If you want to support things
 | |
|  * like "alias ls='ls -l'" with ash, enable this.  This is only useful
 | |
|  * when ash is used as an intractive shell.   This adds about 1.5k */
 | |
| #define ASH_ALIAS
 | |
| 
 | |
| /* If you need ash to act as a full Posix shell, with full math
 | |
|  * support, enable this.   This adds a bit over 2k an x86 system. */
 | |
| //#undef ASH_MATH_SUPPORT
 | |
| #define ASH_MATH_SUPPORT
 | |
| 
 | |
| /* Getopts is used by shell procedures to parse positional parameters.
 | |
|  * You probably want to leave this disabled, and use the busybox getopt
 | |
|  * applet if you want to do this sort of thing.  There are some scripts
 | |
|  * out there that use it, so it you need it, enable.  Most people will
 | |
|  * leave this disabled.  This adds 1k on an x86 system. */
 | |
| #undef ASH_GETOPTS
 | |
| 
 | |
| /* This allows you to override shell builtins and use whatever is on
 | |
|  * the filesystem.  This is most useful when ash is acting as a
 | |
|  * standalone shell.   Adds about 272 bytes. */
 | |
| #undef ASH_CMDCMD
 | |
| 
 | |
| /* Check for new mail on interactive shells? */
 | |
| #undef ASH_MAIL
 | |
| 
 | |
| /* Optimize size vs speed as size */
 | |
| #define ASH_OPTIMIZE_FOR_SIZE
 | |
| 
 | |
| /* Enable this to compile in extra debugging noise.  When debugging is
 | |
|  * on, debugging info will be written to $HOME/trace and a quit signal
 | |
|  * will generate a core dump. */
 | |
| #undef DEBUG
 | |
| 
 | |
| /* These are here to work with glibc -- Don't change these... */
 | |
| #undef FNMATCH_BROKEN
 | |
| #undef GLOB_BROKEN
 | |
| #define IFS_BROKEN
 | |
| 
 | |
| #include <assert.h>
 | |
| #include <stddef.h>
 | |
| #include <ctype.h>
 | |
| #include <dirent.h>
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <limits.h>
 | |
| #include <paths.h>
 | |
| #include <setjmp.h>
 | |
| #include <signal.h>
 | |
| #include <stdarg.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <sysexits.h>
 | |
| #include <unistd.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/cdefs.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <sys/param.h>
 | |
| #include <sys/resource.h>
 | |
| #include <sys/time.h>
 | |
| #include <sys/times.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/wait.h>
 | |
| #include "pwd.h"
 | |
| 
 | |
| 
 | |
| #if !defined(FNMATCH_BROKEN)
 | |
| #include <fnmatch.h>
 | |
| #endif
 | |
| #if !defined(GLOB_BROKEN)
 | |
| #include <glob.h>
 | |
| #endif
 | |
| 
 | |
| #ifdef JOBS
 | |
| #include <termios.h>
 | |
| #endif
 | |
| 
 | |
| #include "busybox.h"
 | |
| #include "cmdedit.h"
 | |
| 
 | |
| /*
 | |
|  * This file was generated by the mksyntax program.
 | |
|  */
 | |
| 
 | |
| /* Syntax classes */
 | |
| #define CWORD 0                 /* character is nothing special */
 | |
| #define CNL 1                   /* newline character */
 | |
| #define CBACK 2                 /* a backslash character */
 | |
| #define CSQUOTE 3               /* single quote */
 | |
| #define CDQUOTE 4               /* double quote */
 | |
| #define CENDQUOTE 5             /* a terminating quote */
 | |
| #define CBQUOTE 6               /* backwards single quote */
 | |
| #define CVAR 7                  /* a dollar sign */
 | |
| #define CENDVAR 8               /* a '}' character */
 | |
| #define CLP 9                   /* a left paren in arithmetic */
 | |
| #define CRP 10                  /* a right paren in arithmetic */
 | |
| #define CENDFILE 11             /* end of file */
 | |
| #define CCTL 12                 /* like CWORD, except it must be escaped */
 | |
| #define CSPCL 13                /* these terminate a word */
 | |
| #define CIGN 14                 /* character should be ignored */
 | |
| 
 | |
| #define SYNBASE 130
 | |
| #define PEOF -130
 | |
| 
 | |
| #define PEOA -129
 | |
| 
 | |
| #define TEOF 0
 | |
| #define TNL 1
 | |
| #define TREDIR 2
 | |
| #define TWORD 3
 | |
| #define TASSIGN 4
 | |
| #define TSEMI 5
 | |
| #define TBACKGND 6
 | |
| #define TAND 7
 | |
| #define TOR 8
 | |
| #define TPIPE 9
 | |
| #define TLP 10
 | |
| #define TRP 11
 | |
| #define TENDCASE 12
 | |
| #define TENDBQUOTE 13
 | |
| #define TNOT 14
 | |
| #define TCASE 15
 | |
| #define TDO 16
 | |
| #define TDONE 17
 | |
| #define TELIF 18
 | |
| #define TELSE 19
 | |
| #define TESAC 20
 | |
| #define TFI 21
 | |
| #define TFOR 22
 | |
| #define TIF 23
 | |
| #define TIN 24
 | |
| #define TTHEN 25
 | |
| #define TUNTIL 26
 | |
| #define TWHILE 27
 | |
| #define TBEGIN 28
 | |
| #define TEND 29
 | |
| 
 | |
| 
 | |
| 
 | |
| /* control characters in argument strings */
 | |
| #define CTLESC '\201'
 | |
| #define CTLVAR '\202'
 | |
| #define CTLENDVAR '\203'
 | |
| #define CTLBACKQ '\204'
 | |
| #define CTLQUOTE 01             /* ored with CTLBACKQ code if in quotes */
 | |
| /*      CTLBACKQ | CTLQUOTE == '\205' */
 | |
| #define CTLARI  '\206'
 | |
| #define CTLENDARI '\207'
 | |
| #define CTLQUOTEMARK '\210'
 | |
| 
 | |
| 
 | |
| #define is_digit(c)     ((c)>='0' && (c)<='9')
 | |
| #define is_name(c)      (((c) < CTLESC || (c) > CTLENDARI) && ((c) == '_' || isalpha((unsigned char) (c))))
 | |
| #define is_in_name(c)   (((c) < CTLESC || (c) > CTLENDARI) && ((c) == '_' || isalnum((unsigned char) (c))))
 | |
| 
 | |
| /*
 | |
|  * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise
 | |
|  * (assuming ascii char codes, as the original implementation did)
 | |
|  */
 | |
| #define is_special(c) \
 | |
|     ( (((unsigned int)c) - 33 < 32) \
 | |
| 			 && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1))
 | |
| 
 | |
| #define digit_val(c)    ((c) - '0')
 | |
| 
 | |
| 
 | |
| #define _DIAGASSERT(x)
 | |
| 
 | |
| 
 | |
| 
 | |
| #define S_DFL 1                 /* default signal handling (SIG_DFL) */
 | |
| #define S_CATCH 2               /* signal is caught */
 | |
| #define S_IGN 3                 /* signal is ignored (SIG_IGN) */
 | |
| #define S_HARD_IGN 4            /* signal is ignored permenantly */
 | |
| #define S_RESET 5               /* temporary - to reset a hard ignored sig */
 | |
| 
 | |
| 
 | |
| /* variable substitution byte (follows CTLVAR) */
 | |
| #define VSTYPE  0x0f            /* type of variable substitution */
 | |
| #define VSNUL   0x10            /* colon--treat the empty string as unset */
 | |
| #define VSQUOTE 0x80            /* inside double quotes--suppress splitting */
 | |
| 
 | |
| /* values of VSTYPE field */
 | |
| #define VSNORMAL        0x1             /* normal variable:  $var or ${var} */
 | |
| #define VSMINUS         0x2             /* ${var-text} */
 | |
| #define VSPLUS          0x3             /* ${var+text} */
 | |
| #define VSQUESTION      0x4             /* ${var?message} */
 | |
| #define VSASSIGN        0x5             /* ${var=text} */
 | |
| #define VSTRIMLEFT      0x6             /* ${var#pattern} */
 | |
| #define VSTRIMLEFTMAX   0x7             /* ${var##pattern} */
 | |
| #define VSTRIMRIGHT     0x8             /* ${var%pattern} */
 | |
| #define VSTRIMRIGHTMAX  0x9             /* ${var%%pattern} */
 | |
| #define VSLENGTH        0xa             /* ${#var} */
 | |
| 
 | |
| /* flags passed to redirect */
 | |
| #define REDIR_PUSH 01           /* save previous values of file descriptors */
 | |
| #define REDIR_BACKQ 02          /* save the command output to pipe */
 | |
| 
 | |
| /*
 | |
|  * BSD setjmp saves the signal mask, which violates ANSI C and takes time,
 | |
|  * so we use _setjmp instead.
 | |
|  */
 | |
| 
 | |
| #if defined(BSD)
 | |
| #define setjmp(jmploc)  _setjmp(jmploc)
 | |
| #define longjmp(jmploc, val)    _longjmp(jmploc, val)
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Most machines require the value returned from malloc to be aligned
 | |
|  * in some way.  The following macro will get this right on many machines.
 | |
|  */
 | |
| 
 | |
| #ifndef ALIGN
 | |
| union align {
 | |
| 	int i;
 | |
| 	char *cp;
 | |
| };
 | |
| 
 | |
| #define ALIGN(nbytes)   (((nbytes) + sizeof(union align) - 1) & ~(sizeof(union align) - 1))
 | |
| #endif
 | |
| 
 | |
| #ifdef CONFIG_LOCALE_SUPPORT
 | |
| #include <locale.h>
 | |
| static void change_lc_all(const char *value);
 | |
| static void change_lc_ctype(const char *value);
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * These macros allow the user to suspend the handling of interrupt signals
 | |
|  * over a period of time.  This is similar to SIGHOLD to or sigblock, but
 | |
|  * much more efficient and portable.  (But hacking the kernel is so much
 | |
|  * more fun than worrying about efficiency and portability. :-))
 | |
|  */
 | |
| 
 | |
| static void onint (void);
 | |
| static volatile int suppressint;
 | |
| static volatile int intpending;
 | |
| 
 | |
| #define INTOFF suppressint++
 | |
| #ifndef ASH_OPTIMIZE_FOR_SIZE
 | |
| #define INTON { if (--suppressint == 0 && intpending) onint(); }
 | |
| #define FORCEINTON {suppressint = 0; if (intpending) onint();}
 | |
| #else
 | |
| static void __inton (void);
 | |
| static void forceinton (void);
 | |
| #define INTON __inton()
 | |
| #define FORCEINTON forceinton()
 | |
| #endif
 | |
| 
 | |
| #define CLEAR_PENDING_INT intpending = 0
 | |
| #define int_pending() intpending
 | |
| 
 | |
| 
 | |
| typedef void *pointer;
 | |
| #ifndef NULL
 | |
| #define NULL (void *)0
 | |
| #endif
 | |
| 
 | |
| static inline pointer  ckmalloc (int sz)          { return xmalloc(sz);     }
 | |
| static inline pointer  ckrealloc(void *p, int sz) { return xrealloc(p, sz); }
 | |
| static inline char *   savestr  (const char *s)   { return xstrdup(s);      }
 | |
| 
 | |
| static pointer stalloc (int);
 | |
| static void stunalloc (pointer);
 | |
| static void ungrabstackstr (char *, char *);
 | |
| static char * growstackstr(void);
 | |
| static char * makestrspace(size_t newlen);
 | |
| static char *sstrdup (const char *);
 | |
| 
 | |
| /*
 | |
|  * Parse trees for commands are allocated in lifo order, so we use a stack
 | |
|  * to make this more efficient, and also to avoid all sorts of exception
 | |
|  * handling code to handle interrupts in the middle of a parse.
 | |
|  *
 | |
|  * The size 504 was chosen because the Ultrix malloc handles that size
 | |
|  * well.
 | |
|  */
 | |
| 
 | |
| #define MINSIZE 504             /* minimum size of a block */
 | |
| 
 | |
| 
 | |
| struct stack_block {
 | |
| 	struct stack_block *prev;
 | |
| 	char space[MINSIZE];
 | |
| };
 | |
| 
 | |
| static struct stack_block stackbase;
 | |
| static struct stack_block *stackp = &stackbase;
 | |
| static struct stackmark *markp;
 | |
| static char *stacknxt = stackbase.space;
 | |
| static int stacknleft = MINSIZE;
 | |
| 
 | |
| 
 | |
| #define equal(s1, s2)   (strcmp(s1, s2) == 0)
 | |
| 
 | |
| #define stackblock() stacknxt
 | |
| #define stackblocksize() stacknleft
 | |
| #define STARTSTACKSTR(p)        p = stackblock(), sstrnleft = stackblocksize()
 | |
| 
 | |
| #define STPUTC(c, p)    (--sstrnleft >= 0? (*p++ = (c)) : (p = growstackstr(), *p++ = (c)))
 | |
| #define CHECKSTRSPACE(n, p)     { if (sstrnleft < n) p = makestrspace(n); }
 | |
| #define STACKSTRNUL(p)  (sstrnleft == 0? (p = growstackstr(), *p = '\0') : (*p = '\0'))
 | |
| 
 | |
| 
 | |
| #define USTPUTC(c, p)   (--sstrnleft, *p++ = (c))
 | |
| #define STUNPUTC(p)     (++sstrnleft, --p)
 | |
| #define STTOPC(p)       p[-1]
 | |
| #define STADJUST(amount, p)     (p += (amount), sstrnleft -= (amount))
 | |
| #define grabstackstr(p) stalloc(stackblocksize() - sstrnleft)
 | |
| 
 | |
| #define ckfree(p)       free((pointer)(p))
 | |
| 
 | |
| 
 | |
| #ifdef DEBUG
 | |
| #define TRACE(param)    trace param
 | |
| typedef union node unode;
 | |
| static void trace (const char *, ...);
 | |
| static void trargs (char **);
 | |
| static void showtree (unode *);
 | |
| static void trputc (int);
 | |
| static void trputs (const char *);
 | |
| static void opentrace (void);
 | |
| #else
 | |
| #define TRACE(param)
 | |
| #endif
 | |
| 
 | |
| #define NSEMI 0
 | |
| #define NCMD 1
 | |
| #define NPIPE 2
 | |
| #define NREDIR 3
 | |
| #define NBACKGND 4
 | |
| #define NSUBSHELL 5
 | |
| #define NAND 6
 | |
| #define NOR 7
 | |
| #define NIF 8
 | |
| #define NWHILE 9
 | |
| #define NUNTIL 10
 | |
| #define NFOR 11
 | |
| #define NCASE 12
 | |
| #define NCLIST 13
 | |
| #define NDEFUN 14
 | |
| #define NARG 15
 | |
| #define NTO 16
 | |
| #define NFROM 17
 | |
| #define NFROMTO 18
 | |
| #define NAPPEND 19
 | |
| #define NTOOV 20
 | |
| #define NTOFD 21
 | |
| #define NFROMFD 22
 | |
| #define NHERE 23
 | |
| #define NXHERE 24
 | |
| #define NNOT 25
 | |
| 
 | |
| /*
 | |
|  * expandarg() flags
 | |
|  */
 | |
| #define EXP_FULL        0x1     /* perform word splitting & file globbing */
 | |
| #define EXP_TILDE       0x2     /* do normal tilde expansion */
 | |
| #define EXP_VARTILDE    0x4     /* expand tildes in an assignment */
 | |
| #define EXP_REDIR       0x8     /* file glob for a redirection (1 match only) */
 | |
| #define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
 | |
| #define EXP_RECORD      0x20    /* need to record arguments for ifs breakup */
 | |
| 
 | |
| 
 | |
| #define NOPTS   16
 | |
| 
 | |
| static char optet_vals[NOPTS];
 | |
| 
 | |
| static const char * const optlist[NOPTS] = {
 | |
| 	"e" "errexit",
 | |
| 	"f" "noglob",
 | |
| 	"I" "ignoreeof",
 | |
| 	"i" "interactive",
 | |
| 	"m" "monitor",
 | |
| 	"n" "noexec",
 | |
| 	"s" "stdin",
 | |
| 	"x" "xtrace",
 | |
| 	"v" "verbose",
 | |
| 	"V" "vi",
 | |
| 	"E" "emacs",
 | |
| 	"C" "noclobber",
 | |
| 	"a" "allexport",
 | |
| 	"b" "notify",
 | |
| 	"u" "nounset",
 | |
| 	"q" "quietprofile"
 | |
| };
 | |
| 
 | |
| #define optent_name(optent) (optent+1)
 | |
| #define optent_letter(optent) optent[0]
 | |
| #define optent_val(optent) optet_vals[optent]
 | |
| 
 | |
| #define eflag optent_val(0)
 | |
| #define fflag optent_val(1)
 | |
| #define Iflag optent_val(2)
 | |
| #define iflag optent_val(3)
 | |
| #define mflag optent_val(4)
 | |
| #define nflag optent_val(5)
 | |
| #define sflag optent_val(6)
 | |
| #define xflag optent_val(7)
 | |
| #define vflag optent_val(8)
 | |
| #define Vflag optent_val(9)
 | |
| #define Eflag optent_val(10)
 | |
| #define Cflag optent_val(11)
 | |
| #define aflag optent_val(12)
 | |
| #define bflag optent_val(13)
 | |
| #define uflag optent_val(14)
 | |
| #define qflag optent_val(15)
 | |
| 
 | |
| 
 | |
| /* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
 | |
| #define FORK_FG 0
 | |
| #define FORK_BG 1
 | |
| #define FORK_NOJOB 2
 | |
| 
 | |
| 
 | |
| struct nbinary {
 | |
|       int type;
 | |
|       union node *ch1;
 | |
|       union node *ch2;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct ncmd {
 | |
|       int type;
 | |
|       int backgnd;
 | |
|       union node *assign;
 | |
|       union node *args;
 | |
|       union node *redirect;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct npipe {
 | |
|       int type;
 | |
|       int backgnd;
 | |
|       struct nodelist *cmdlist;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nredir {
 | |
|       int type;
 | |
|       union node *n;
 | |
|       union node *redirect;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nif {
 | |
|       int type;
 | |
|       union node *test;
 | |
|       union node *ifpart;
 | |
|       union node *elsepart;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nfor {
 | |
|       int type;
 | |
|       union node *args;
 | |
|       union node *body;
 | |
|       char *var;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct ncase {
 | |
|       int type;
 | |
|       union node *expr;
 | |
|       union node *cases;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nclist {
 | |
|       int type;
 | |
|       union node *next;
 | |
|       union node *pattern;
 | |
|       union node *body;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct narg {
 | |
|       int type;
 | |
|       union node *next;
 | |
|       char *text;
 | |
|       struct nodelist *backquote;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nfile {
 | |
|       int type;
 | |
|       union node *next;
 | |
|       int fd;
 | |
|       union node *fname;
 | |
|       char *expfname;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct ndup {
 | |
|       int type;
 | |
|       union node *next;
 | |
|       int fd;
 | |
|       int dupfd;
 | |
|       union node *vname;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nhere {
 | |
|       int type;
 | |
|       union node *next;
 | |
|       int fd;
 | |
|       union node *doc;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nnot {
 | |
|       int type;
 | |
|       union node *com;
 | |
| };
 | |
| 
 | |
| 
 | |
| union node {
 | |
|       int type;
 | |
|       struct nbinary nbinary;
 | |
|       struct ncmd ncmd;
 | |
|       struct npipe npipe;
 | |
|       struct nredir nredir;
 | |
|       struct nif nif;
 | |
|       struct nfor nfor;
 | |
|       struct ncase ncase;
 | |
|       struct nclist nclist;
 | |
|       struct narg narg;
 | |
|       struct nfile nfile;
 | |
|       struct ndup ndup;
 | |
|       struct nhere nhere;
 | |
|       struct nnot nnot;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct nodelist {
 | |
| 	struct nodelist *next;
 | |
| 	union node *n;
 | |
| };
 | |
| 
 | |
| struct backcmd {                /* result of evalbackcmd */
 | |
| 	int fd;                 /* file descriptor to read from */
 | |
| 	char *buf;              /* buffer */
 | |
| 	int nleft;              /* number of chars in buffer */
 | |
| 	struct job *jp;         /* job structure for command */
 | |
| };
 | |
| 
 | |
| struct cmdentry {
 | |
| 	int cmdtype;
 | |
| 	union param {
 | |
| 		int index;
 | |
| 		union node *func;
 | |
| 		const struct builtincmd *cmd;
 | |
| 	} u;
 | |
| };
 | |
| 
 | |
| struct strlist {
 | |
| 	struct strlist *next;
 | |
| 	char *text;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct arglist {
 | |
| 	struct strlist *list;
 | |
| 	struct strlist **lastp;
 | |
| };
 | |
| 
 | |
| struct strpush {
 | |
| 	struct strpush *prev;   /* preceding string on stack */
 | |
| 	char *prevstring;
 | |
| 	int prevnleft;
 | |
| #ifdef ASH_ALIAS
 | |
| 	struct alias *ap;       /* if push was associated with an alias */
 | |
| #endif
 | |
| 	char *string;           /* remember the string since it may change */
 | |
| };
 | |
| 
 | |
| struct parsefile {
 | |
| 	struct parsefile *prev; /* preceding file on stack */
 | |
| 	int linno;              /* current line */
 | |
| 	int fd;                 /* file descriptor (or -1 if string) */
 | |
| 	int nleft;              /* number of chars left in this line */
 | |
| 	int lleft;              /* number of chars left in this buffer */
 | |
| 	char *nextc;            /* next char in buffer */
 | |
| 	char *buf;              /* input buffer */
 | |
| 	struct strpush *strpush; /* for pushing strings at this level */
 | |
| 	struct strpush basestrpush; /* so pushing one is fast */
 | |
| };
 | |
| 
 | |
| struct stackmark {
 | |
| 	struct stack_block *stackp;
 | |
| 	char *stacknxt;
 | |
| 	int stacknleft;
 | |
| 	struct stackmark *marknext;
 | |
| };
 | |
| 
 | |
| struct shparam {
 | |
| 	int nparam;             /* # of positional parameters (without $0) */
 | |
| 	unsigned char malloc;   /* if parameter list dynamically allocated */
 | |
| 	char **p;               /* parameter list */
 | |
| 	int optind;             /* next parameter to be processed by getopts */
 | |
| 	int optoff;             /* used by getopts */
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * When commands are first encountered, they are entered in a hash table.
 | |
|  * This ensures that a full path search will not have to be done for them
 | |
|  * on each invocation.
 | |
|  *
 | |
|  * We should investigate converting to a linear search, even though that
 | |
|  * would make the command name "hash" a misnomer.
 | |
|  */
 | |
| #define CMDTABLESIZE 31         /* should be prime */
 | |
| #define ARB 1                   /* actual size determined at run time */
 | |
| 
 | |
| 
 | |
| 
 | |
| struct tblentry {
 | |
| 	struct tblentry *next;  /* next entry in hash chain */
 | |
| 	union param param;      /* definition of builtin function */
 | |
| 	short cmdtype;          /* index identifying command */
 | |
| 	char rehash;            /* if set, cd done since entry created */
 | |
| 	char cmdname[ARB];      /* name of command */
 | |
| };
 | |
| 
 | |
| 
 | |
| static struct tblentry *cmdtable[CMDTABLESIZE];
 | |
| static int builtinloc = -1;             /* index in path of %builtin, or -1 */
 | |
| static int exerrno = 0;                 /* Last exec error */
 | |
| 
 | |
| 
 | |
| static void tryexec (char *, char **, char **);
 | |
| static void printentry (struct tblentry *, int);
 | |
| static void clearcmdentry (int);
 | |
| static struct tblentry *cmdlookup (const char *, int);
 | |
| static void delete_cmd_entry (void);
 | |
| static int path_change (const char *, int *);
 | |
| 
 | |
| 
 | |
| static void flushall (void);
 | |
| static void out2fmt (const char *, ...)
 | |
|     __attribute__((__format__(__printf__,1,2)));
 | |
| static int xwrite (int, const char *, int);
 | |
| 
 | |
| static inline void outstr (const char *p, FILE *file) { fputs(p, file); }
 | |
| static void out1str(const char *p) { outstr(p, stdout); }
 | |
| static void out2str(const char *p) { outstr(p, stderr); }
 | |
| 
 | |
| #ifndef ASH_OPTIMIZE_FOR_SIZE
 | |
| #define out2c(c)        putc((c), stderr)
 | |
| #else
 | |
| static void out2c(int c)           { putc(c, stderr); }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| #ifdef ASH_OPTIMIZE_FOR_SIZE
 | |
| #define USE_SIT_FUNCTION
 | |
| #endif
 | |
| 
 | |
| /* number syntax index */
 | |
| #define  BASESYNTAX  0                  /* not in quotes */
 | |
| #define  DQSYNTAX    1                  /* in double quotes */
 | |
| #define  SQSYNTAX    2                  /* in single quotes */
 | |
| #define  ARISYNTAX   3                  /* in arithmetic */
 | |
| 
 | |
| static const char S_I_T[][4] = {
 | |
|   /*  0 */  { CSPCL,    CIGN,      CIGN,      CIGN     },   /* PEOA */
 | |
|   /*  1 */  { CSPCL,    CWORD,     CWORD,     CWORD    },   /* ' ' */
 | |
|   /*  2 */  { CNL,      CNL,       CNL,       CNL      },   /* \n */
 | |
|   /*  3 */  { CWORD,    CCTL,      CCTL,      CWORD    },   /* !*-/:=?[]~ */
 | |
|   /*  4 */  { CDQUOTE,  CENDQUOTE, CWORD,     CDQUOTE  },   /* '"' */
 | |
|   /*  5 */  { CVAR,     CVAR,      CWORD,     CVAR     },   /* $ */
 | |
|   /*  6 */  { CSQUOTE,  CWORD,     CENDQUOTE, CSQUOTE  },   /* "'" */
 | |
|   /*  7 */  { CSPCL,    CWORD,     CWORD,     CLP      },   /* ( */
 | |
|   /*  8 */  { CSPCL,    CWORD,     CWORD,     CRP      },   /* ) */
 | |
|   /*  9 */  { CBACK,    CBACK,     CCTL,      CBACK    },   /* \ */
 | |
|   /* 10 */  { CBQUOTE,  CBQUOTE,   CWORD,     CBQUOTE  },   /* ` */
 | |
|   /* 11 */  { CENDVAR,  CENDVAR,   CWORD,     CENDVAR  },   /* } */
 | |
| #ifndef USE_SIT_FUNCTION
 | |
|   /* 12 */  { CENDFILE, CENDFILE,  CENDFILE,  CENDFILE },   /* PEOF */
 | |
|   /* 13 */  { CWORD,    CWORD,     CWORD,     CWORD    },   /* 0-9A-Za-z */
 | |
|   /* 14 */  { CCTL,     CCTL,      CCTL,      CCTL     }    /* CTLESC ... */
 | |
| #endif
 | |
| };
 | |
| 
 | |
| #ifdef USE_SIT_FUNCTION
 | |
| 
 | |
| #define U_C(c) ((unsigned char)(c))
 | |
| 
 | |
| static int SIT(int c, int syntax)
 | |
| {
 | |
| 	static const char spec_symbls[]="\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
 | |
| 	static const char syntax_index_table [] = {
 | |
| 				1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */
 | |
| 				7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */
 | |
| 				3, 1, 3, 3, 9, 3,10, 1, /* "=>?[\\]`|" */
 | |
| 				11,3 }; /* "}~" */
 | |
| 	const char *s;
 | |
| 	int indx;
 | |
| 
 | |
| 	if(c==PEOF)             /* 2^8+2 */
 | |
| 		return CENDFILE;
 | |
| 	if(c==PEOA)             /* 2^8+1 */
 | |
| 		indx = 0;
 | |
| 	 else if(U_C(c)>=U_C(CTLESC) && U_C(c)<=U_C(CTLQUOTEMARK))
 | |
| 		return CCTL;
 | |
| 	 else {
 | |
| 		s = strchr(spec_symbls, c);
 | |
| 		if(s==0)
 | |
| 			return CWORD;
 | |
| 		indx = syntax_index_table[(s-spec_symbls)];
 | |
| 	}
 | |
| 	return S_I_T[indx][syntax];
 | |
| }
 | |
| 
 | |
| #else  /* USE_SIT_FUNCTION */
 | |
| 
 | |
| #define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax]
 | |
| 
 | |
| #define CSPCL_CIGN_CIGN_CIGN                           0
 | |
| #define CSPCL_CWORD_CWORD_CWORD                        1
 | |
| #define CNL_CNL_CNL_CNL                                2
 | |
| #define CWORD_CCTL_CCTL_CWORD                          3
 | |
| #define CDQUOTE_CENDQUOTE_CWORD_CDQUOTE                4
 | |
| #define CVAR_CVAR_CWORD_CVAR                           5
 | |
| #define CSQUOTE_CWORD_CENDQUOTE_CSQUOTE                6
 | |
| #define CSPCL_CWORD_CWORD_CLP                          7
 | |
| #define CSPCL_CWORD_CWORD_CRP                          8
 | |
| #define CBACK_CBACK_CCTL_CBACK                         9
 | |
| #define CBQUOTE_CBQUOTE_CWORD_CBQUOTE                 10
 | |
| #define CENDVAR_CENDVAR_CWORD_CENDVAR                 11
 | |
| #define CENDFILE_CENDFILE_CENDFILE_CENDFILE           12
 | |
| #define CWORD_CWORD_CWORD_CWORD                       13
 | |
| #define CCTL_CCTL_CCTL_CCTL                           14
 | |
| 
 | |
| static const char syntax_index_table[258] = {
 | |
| 		 /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */
 | |
|   /*   0  -130 PEOF */  CENDFILE_CENDFILE_CENDFILE_CENDFILE,
 | |
|   /*   1  -129 PEOA */  CSPCL_CIGN_CIGN_CIGN,
 | |
|   /*   2  -128 0xff */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*   3  -127      */  CCTL_CCTL_CCTL_CCTL,    /* CTLQUOTEMARK */
 | |
|   /*   4  -126      */  CCTL_CCTL_CCTL_CCTL,
 | |
|   /*   5  -125      */  CCTL_CCTL_CCTL_CCTL,
 | |
|   /*   6  -124      */  CCTL_CCTL_CCTL_CCTL,
 | |
|   /*   7  -123      */  CCTL_CCTL_CCTL_CCTL,
 | |
|   /*   8  -122      */  CCTL_CCTL_CCTL_CCTL,
 | |
|   /*   9  -121      */  CCTL_CCTL_CCTL_CCTL,
 | |
|   /*  10  -120      */  CCTL_CCTL_CCTL_CCTL,    /* CTLESC */
 | |
|   /*  11  -119      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  12  -118      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  13  -117      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  14  -116      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  15  -115      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  16  -114      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  17  -113      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  18  -112      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  19  -111      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  20  -110      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  21  -109      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  22  -108      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  23  -107      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  24  -106      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  25  -105      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  26  -104      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  27  -103      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  28  -102      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  29  -101      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  30  -100      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  31   -99      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  32   -98      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  33   -97      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  34   -96      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  35   -95      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  36   -94      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  37   -93      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  38   -92      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  39   -91      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  40   -90      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  41   -89      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  42   -88      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  43   -87      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  44   -86      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  45   -85      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  46   -84      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  47   -83      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  48   -82      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  49   -81      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  50   -80      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  51   -79      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  52   -78      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  53   -77      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  54   -76      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  55   -75      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  56   -74      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  57   -73      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  58   -72      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  59   -71      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  60   -70      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  61   -69      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  62   -68      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  63   -67      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  64   -66      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  65   -65      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  66   -64      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  67   -63      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  68   -62      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  69   -61      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  70   -60      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  71   -59      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  72   -58      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  73   -57      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  74   -56      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  75   -55      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  76   -54      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  77   -53      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  78   -52      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  79   -51      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  80   -50      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  81   -49      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  82   -48      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  83   -47      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  84   -46      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  85   -45      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  86   -44      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  87   -43      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  88   -42      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  89   -41      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  90   -40      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  91   -39      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  92   -38      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  93   -37      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  94   -36      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  95   -35      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  96   -34      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  97   -33      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  98   -32      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /*  99   -31      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 100   -30      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 101   -29      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 102   -28      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 103   -27      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 104   -26      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 105   -25      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 106   -24      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 107   -23      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 108   -22      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 109   -21      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 110   -20      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 111   -19      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 112   -18      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 113   -17      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 114   -16      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 115   -15      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 116   -14      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 117   -13      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 118   -12      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 119   -11      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 120   -10      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 121    -9      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 122    -8      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 123    -7      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 124    -6      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 125    -5      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 126    -4      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 127    -3      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 128    -2      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 129    -1      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 130     0      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 131     1      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 132     2      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 133     3      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 134     4      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 135     5      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 136     6      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 137     7      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 138     8      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 139     9 "\t" */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 140    10 "\n" */  CNL_CNL_CNL_CNL,
 | |
|   /* 141    11      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 142    12      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 143    13      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 144    14      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 145    15      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 146    16      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 147    17      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 148    18      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 149    19      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 150    20      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 151    21      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 152    22      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 153    23      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 154    24      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 155    25      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 156    26      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 157    27      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 158    28      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 159    29      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 160    30      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 161    31      */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 162    32  " " */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 163    33  "!" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 164    34  """ */  CDQUOTE_CENDQUOTE_CWORD_CDQUOTE,
 | |
|   /* 165    35  "#" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 166    36  "$" */  CVAR_CVAR_CWORD_CVAR,
 | |
|   /* 167    37  "%" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 168    38  "&" */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 169    39  "'" */  CSQUOTE_CWORD_CENDQUOTE_CSQUOTE,
 | |
|   /* 170    40  "(" */  CSPCL_CWORD_CWORD_CLP,
 | |
|   /* 171    41  ")" */  CSPCL_CWORD_CWORD_CRP,
 | |
|   /* 172    42  "*" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 173    43  "+" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 174    44  "," */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 175    45  "-" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 176    46  "." */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 177    47  "/" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 178    48  "0" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 179    49  "1" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 180    50  "2" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 181    51  "3" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 182    52  "4" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 183    53  "5" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 184    54  "6" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 185    55  "7" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 186    56  "8" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 187    57  "9" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 188    58  ":" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 189    59  ";" */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 190    60  "<" */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 191    61  "=" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 192    62  ">" */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 193    63  "?" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 194    64  "@" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 195    65  "A" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 196    66  "B" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 197    67  "C" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 198    68  "D" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 199    69  "E" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 200    70  "F" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 201    71  "G" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 202    72  "H" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 203    73  "I" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 204    74  "J" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 205    75  "K" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 206    76  "L" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 207    77  "M" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 208    78  "N" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 209    79  "O" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 210    80  "P" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 211    81  "Q" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 212    82  "R" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 213    83  "S" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 214    84  "T" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 215    85  "U" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 216    86  "V" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 217    87  "W" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 218    88  "X" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 219    89  "Y" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 220    90  "Z" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 221    91  "[" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 222    92  "\" */  CBACK_CBACK_CCTL_CBACK,
 | |
|   /* 223    93  "]" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 224    94  "^" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 225    95  "_" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 226    96  "`" */  CBQUOTE_CBQUOTE_CWORD_CBQUOTE,
 | |
|   /* 227    97  "a" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 228    98  "b" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 229    99  "c" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 230   100  "d" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 231   101  "e" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 232   102  "f" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 233   103  "g" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 234   104  "h" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 235   105  "i" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 236   106  "j" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 237   107  "k" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 238   108  "l" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 239   109  "m" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 240   110  "n" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 241   111  "o" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 242   112  "p" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 243   113  "q" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 244   114  "r" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 245   115  "s" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 246   116  "t" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 247   117  "u" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 248   118  "v" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 249   119  "w" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 250   120  "x" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 251   121  "y" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 252   122  "z" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 253   123  "{" */  CWORD_CWORD_CWORD_CWORD,
 | |
|   /* 254   124  "|" */  CSPCL_CWORD_CWORD_CWORD,
 | |
|   /* 255   125  "}" */  CENDVAR_CENDVAR_CWORD_CENDVAR,
 | |
|   /* 256   126  "~" */  CWORD_CCTL_CCTL_CWORD,
 | |
|   /* 257   127      */  CWORD_CWORD_CWORD_CWORD,
 | |
| };
 | |
| 
 | |
| #endif  /* USE_SIT_FUNCTION */
 | |
| 
 | |
| 
 | |
| /* first char is indicating which tokens mark the end of a list */
 | |
| static const char *const tokname_array[] = {
 | |
| 	"\1end of file",
 | |
| 	"\0newline",
 | |
| 	"\0redirection",
 | |
| 	"\0word",
 | |
| 	"\0assignment",
 | |
| 	"\0;",
 | |
| 	"\0&",
 | |
| 	"\0&&",
 | |
| 	"\0||",
 | |
| 	"\0|",
 | |
| 	"\0(",
 | |
| 	"\1)",
 | |
| 	"\1;;",
 | |
| 	"\1`",
 | |
| #define KWDOFFSET 14
 | |
| 	/* the following are keywords */
 | |
| 	"\0!",
 | |
| 	"\0case",
 | |
| 	"\1do",
 | |
| 	"\1done",
 | |
| 	"\1elif",
 | |
| 	"\1else",
 | |
| 	"\1esac",
 | |
| 	"\1fi",
 | |
| 	"\0for",
 | |
| 	"\0if",
 | |
| 	"\0in",
 | |
| 	"\1then",
 | |
| 	"\0until",
 | |
| 	"\0while",
 | |
| 	"\0{",
 | |
| 	"\1}",
 | |
| };
 | |
| 
 | |
| static const char *tokname(int tok)
 | |
| {
 | |
| 	static char buf[16];
 | |
| 
 | |
| 	if(tok>=TSEMI)
 | |
| 		buf[0] = '"';
 | |
| 	sprintf(buf+(tok>=TSEMI), "%s%c",
 | |
| 			tokname_array[tok]+1, (tok>=TSEMI ? '"' : 0));
 | |
| 	return buf;
 | |
| }
 | |
| 
 | |
| static int plinno = 1;          /* input line number */
 | |
| 
 | |
| static int parselleft;          /* copy of parsefile->lleft */
 | |
| 
 | |
| static struct parsefile basepf; /* top level input file */
 | |
| static char basebuf[BUFSIZ];    /* buffer for top level input file */
 | |
| static struct parsefile *parsefile = &basepf;  /* current input file */
 | |
| 
 | |
| /*
 | |
|  * NEOF is returned by parsecmd when it encounters an end of file.  It
 | |
|  * must be distinct from NULL, so we use the address of a variable that
 | |
|  * happens to be handy.
 | |
|  */
 | |
| 
 | |
| static int tokpushback;         /* last token pushed back */
 | |
| #define NEOF ((union node *)&tokpushback)
 | |
| static int checkkwd;            /* 1 == check for kwds, 2 == also eat newlines */
 | |
| 
 | |
| 
 | |
| static void error (const char *, ...) __attribute__((__noreturn__));
 | |
| static void exerror (int, const char *, ...) __attribute__((__noreturn__));
 | |
| static void shellexec (char **, char **, const char *, int)
 | |
|     __attribute__((noreturn));
 | |
| static void exitshell (int) __attribute__((noreturn));
 | |
| 
 | |
| static int  goodname(const char *);
 | |
| static void ignoresig (int);
 | |
| static void onsig (int);
 | |
| static void dotrap (void);
 | |
| static int  decode_signal (const char *, int);
 | |
| 
 | |
| static void shprocvar(void);
 | |
| static void deletefuncs(void);
 | |
| static void setparam (char **);
 | |
| static void freeparam (volatile struct shparam *);
 | |
| 
 | |
| /* reasons for skipping commands (see comment on breakcmd routine) */
 | |
| #define SKIPBREAK       1
 | |
| #define SKIPCONT        2
 | |
| #define SKIPFUNC        3
 | |
| #define SKIPFILE        4
 | |
| 
 | |
| /* values of cmdtype */
 | |
| #define CMDUNKNOWN -1           /* no entry in table for command */
 | |
| #define CMDNORMAL 0             /* command is an executable program */
 | |
| #define CMDBUILTIN 1            /* command is a shell builtin */
 | |
| #define CMDFUNCTION 2           /* command is a shell function */
 | |
| 
 | |
| #define DO_ERR  1               /* find_command prints errors */
 | |
| #define DO_ABS  2               /* find_command checks absolute paths */
 | |
| #define DO_NOFUN        4       /* find_command ignores functions */
 | |
| #define DO_BRUTE        8       /* find_command ignores hash table */
 | |
| 
 | |
| /*
 | |
|  * Shell variables.
 | |
|  */
 | |
| 
 | |
| /* flags */
 | |
| #define VEXPORT         0x01    /* variable is exported */
 | |
| #define VREADONLY       0x02    /* variable cannot be modified */
 | |
| #define VSTRFIXED       0x04    /* variable struct is staticly allocated */
 | |
| #define VTEXTFIXED      0x08    /* text is staticly allocated */
 | |
| #define VSTACK          0x10    /* text is allocated on the stack */
 | |
| #define VUNSET          0x20    /* the variable is not set */
 | |
| #define VNOFUNC         0x40    /* don't call the callback function */
 | |
| 
 | |
| 
 | |
| struct var {
 | |
| 	struct var *next;               /* next entry in hash list */
 | |
| 	int flags;                      /* flags are defined above */
 | |
| 	char *text;                     /* name=value */
 | |
| 	void (*func) (const char *);
 | |
| 					/* function to be called when  */
 | |
| 					/* the variable gets set/unset */
 | |
| };
 | |
| 
 | |
| struct localvar {
 | |
| 	struct localvar *next;          /* next local variable in list */
 | |
| 	struct var *vp;                 /* the variable that was made local */
 | |
| 	int flags;                      /* saved flags */
 | |
| 	char *text;                     /* saved text */
 | |
| };
 | |
| 
 | |
| 
 | |
| #if defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN)
 | |
| #define rmescapes(p) _rmescapes((p), 0)
 | |
| static char *_rmescapes (char *, int);
 | |
| #else
 | |
| static void rmescapes (char *);
 | |
| #endif
 | |
| 
 | |
| static int  casematch (union node *, const char *);
 | |
| static void clearredir(void);
 | |
| static void popstring(void);
 | |
| static void readcmdfile (const char *);
 | |
| 
 | |
| static int number (const char *);
 | |
| static int is_number (const char *, int *num);
 | |
| static char *single_quote (const char *);
 | |
| static int nextopt (const char *);
 | |
| 
 | |
| static void redirect (union node *, int);
 | |
| static void popredir (void);
 | |
| static int dup_as_newfd (int, int);
 | |
| 
 | |
| static void changepath(const char *newval);
 | |
| static void getoptsreset(const char *value);
 | |
| 
 | |
| 
 | |
| static int parsenleft;                  /* copy of parsefile->nleft */
 | |
| static char *parsenextc;                /* copy of parsefile->nextc */
 | |
| static int rootpid;     /* pid of main shell */
 | |
| static int rootshell;   /* true if we aren't a child of the main shell */
 | |
| 
 | |
| static const char spcstr[] = " ";
 | |
| static const char snlfmt[] = "%s\n";
 | |
| 
 | |
| static int sstrnleft;
 | |
| static int herefd = -1;
 | |
| 
 | |
| static struct localvar *localvars;
 | |
| 
 | |
| static struct var vifs;
 | |
| static struct var vmail;
 | |
| static struct var vmpath;
 | |
| static struct var vpath;
 | |
| static struct var vps1;
 | |
| static struct var vps2;
 | |
| static struct var voptind;
 | |
| #ifdef CONFIG_LOCALE_SUPPORT
 | |
| static struct var vlc_all;
 | |
| static struct var vlc_ctype;
 | |
| #endif
 | |
| 
 | |
| struct varinit {
 | |
| 	struct var *var;
 | |
| 	int flags;
 | |
| 	const char *text;
 | |
| 	void (*func) (const char *);
 | |
| };
 | |
| 
 | |
| static const char defpathvar[] =
 | |
| 	"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
 | |
| #define defpath (defpathvar + 5)
 | |
| 
 | |
| #ifdef IFS_BROKEN
 | |
| static const char defifsvar[] = "IFS= \t\n";
 | |
| #define defifs (defifsvar + 4)
 | |
| #else
 | |
| static const char defifs[] = " \t\n";
 | |
| #endif
 | |
| 
 | |
| static const struct varinit varinit[] = {
 | |
| #ifdef IFS_BROKEN
 | |
| 	{ &vifs,        VSTRFIXED|VTEXTFIXED,           defifsvar,
 | |
| #else
 | |
| 	{ &vifs,        VSTRFIXED|VTEXTFIXED|VUNSET,    "IFS=",
 | |
| #endif
 | |
| 	  NULL },
 | |
| 	{ &vmail,       VSTRFIXED|VTEXTFIXED|VUNSET,    "MAIL=",
 | |
| 	  NULL },
 | |
| 	{ &vmpath,      VSTRFIXED|VTEXTFIXED|VUNSET,    "MAILPATH=",
 | |
| 	  NULL },
 | |
| 	{ &vpath,       VSTRFIXED|VTEXTFIXED,           defpathvar,
 | |
| 	  changepath },
 | |
| 	/*
 | |
| 	 * vps1 depends on uid
 | |
| 	 */
 | |
| 	{ &vps2,        VSTRFIXED|VTEXTFIXED,           "PS2=> ",
 | |
| 	  NULL },
 | |
| 	{ &voptind,     VSTRFIXED|VTEXTFIXED,           "OPTIND=1",
 | |
| 	  getoptsreset },
 | |
| #ifdef CONFIG_LOCALE_SUPPORT
 | |
| 	{ &vlc_all,     VSTRFIXED|VTEXTFIXED|VUNSET,    "LC_ALL=",
 | |
| 	  change_lc_all },
 | |
| 	{ &vlc_ctype,   VSTRFIXED|VTEXTFIXED|VUNSET,    "LC_CTYPE=",
 | |
| 	  change_lc_ctype },
 | |
| #endif
 | |
| 	{ NULL, 0,                              NULL,
 | |
| 	  NULL }
 | |
| };
 | |
| 
 | |
| #define VTABSIZE 39
 | |
| 
 | |
| static struct var *vartab[VTABSIZE];
 | |
| 
 | |
| /*
 | |
|  * The following macros access the values of the above variables.
 | |
|  * They have to skip over the name.  They return the null string
 | |
|  * for unset variables.
 | |
|  */
 | |
| 
 | |
| #define ifsval()        (vifs.text + 4)
 | |
| #define ifsset()        ((vifs.flags & VUNSET) == 0)
 | |
| #define mailval()       (vmail.text + 5)
 | |
| #define mpathval()      (vmpath.text + 9)
 | |
| #define pathval()       (vpath.text + 5)
 | |
| #define ps1val()        (vps1.text + 4)
 | |
| #define ps2val()        (vps2.text + 4)
 | |
| #define optindval()     (voptind.text + 7)
 | |
| 
 | |
| #define mpathset()      ((vmpath.flags & VUNSET) == 0)
 | |
| 
 | |
| static void initvar (void);
 | |
| static void setvar (const char *, const char *, int);
 | |
| static void setvareq (char *, int);
 | |
| static void listsetvar (struct strlist *);
 | |
| static const char *lookupvar (const char *);
 | |
| static const char *bltinlookup (const char *);
 | |
| static char **environment (void);
 | |
| static int showvarscmd (int, char **);
 | |
| static void mklocal (char *);
 | |
| static void poplocalvars (void);
 | |
| static int unsetvar (const char *);
 | |
| static int varequal (const char *, const char *);
 | |
| 
 | |
| 
 | |
| static char *arg0;                      /* value of $0 */
 | |
| static struct shparam shellparam;       /* current positional parameters */
 | |
| static char **argptr;                   /* argument list for builtin commands */
 | |
| static char *optionarg;                 /* set by nextopt (like getopt) */
 | |
| static char *optptr;                    /* used by nextopt */
 | |
| static char *minusc;                    /* argument to -c option */
 | |
| 
 | |
| 
 | |
| #ifdef ASH_ALIAS
 | |
| 
 | |
| #define ALIASINUSE      1
 | |
| #define ALIASDEAD       2
 | |
| 
 | |
| #define ATABSIZE 39
 | |
| 
 | |
| struct alias {
 | |
| 	struct alias *next;
 | |
| 	char *name;
 | |
| 	char *val;
 | |
| 	int flag;
 | |
| };
 | |
| 
 | |
| static struct alias *atab[ATABSIZE];
 | |
| 
 | |
| static void setalias (char *, char *);
 | |
| static struct alias **hashalias (const char *);
 | |
| static struct alias *freealias (struct alias *);
 | |
| static struct alias **__lookupalias (const char *);
 | |
| 
 | |
| static void
 | |
| setalias(name, val)
 | |
| 	char *name, *val;
 | |
| {
 | |
| 	struct alias *ap, **app;
 | |
| 
 | |
| 	app = __lookupalias(name);
 | |
| 	ap = *app;
 | |
| 	INTOFF;
 | |
| 	if (ap) {
 | |
| 		if (!(ap->flag & ALIASINUSE)) {
 | |
| 			ckfree(ap->val);
 | |
| 		}
 | |
| 		ap->val = savestr(val);
 | |
| 		ap->flag &= ~ALIASDEAD;
 | |
| 	} else {
 | |
| 		/* not found */
 | |
| 		ap = ckmalloc(sizeof (struct alias));
 | |
| 		ap->name = savestr(name);
 | |
| 		ap->val = savestr(val);
 | |
| 		ap->flag = 0;
 | |
| 		ap->next = 0;
 | |
| 		*app = ap;
 | |
| 	}
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| static int
 | |
| unalias(char *name)
 | |
| {
 | |
| 	struct alias **app;
 | |
| 
 | |
| 	app = __lookupalias(name);
 | |
| 
 | |
| 	if (*app) {
 | |
| 		INTOFF;
 | |
| 		*app = freealias(*app);
 | |
| 		INTON;
 | |
| 		return (0);
 | |
| 	}
 | |
| 
 | |
| 	return (1);
 | |
| }
 | |
| 
 | |
| static void
 | |
| rmaliases(void)
 | |
| {
 | |
| 	struct alias *ap, **app;
 | |
| 	int i;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	for (i = 0; i < ATABSIZE; i++) {
 | |
| 		app = &atab[i];
 | |
| 		for (ap = *app; ap; ap = *app) {
 | |
| 			*app = freealias(*app);
 | |
| 			if (ap == *app) {
 | |
| 				app = &ap->next;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| static void
 | |
| printalias(const struct alias *ap) {
 | |
| 	char *p;
 | |
| 
 | |
| 	p = single_quote(ap->val);
 | |
| 	printf("alias %s=%s\n", ap->name, p);
 | |
| 	stunalloc(p);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * TODO - sort output
 | |
|  */
 | |
| static int
 | |
| aliascmd(int argc, char **argv)
 | |
| {
 | |
| 	char *n, *v;
 | |
| 	int ret = 0;
 | |
| 	struct alias *ap;
 | |
| 
 | |
| 	if (argc == 1) {
 | |
| 		int i;
 | |
| 
 | |
| 		for (i = 0; i < ATABSIZE; i++)
 | |
| 			for (ap = atab[i]; ap; ap = ap->next) {
 | |
| 				printalias(ap);
 | |
| 			}
 | |
| 		return (0);
 | |
| 	}
 | |
| 	while ((n = *++argv) != NULL) {
 | |
| 		if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */
 | |
| 			if ((ap = *__lookupalias(n)) == NULL) {
 | |
| 				out2fmt("%s: %s not found\n", "alias", n);
 | |
| 				ret = 1;
 | |
| 			} else
 | |
| 				printalias(ap);
 | |
| 		}
 | |
| 		else {
 | |
| 			*v++ = '\0';
 | |
| 			setalias(n, v);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return (ret);
 | |
| }
 | |
| 
 | |
| static int
 | |
| unaliascmd(int argc, char **argv)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	while ((i = nextopt("a")) != '\0') {
 | |
| 		if (i == 'a') {
 | |
| 			rmaliases();
 | |
| 			return (0);
 | |
| 		}
 | |
| 	}
 | |
| 	for (i = 0; *argptr; argptr++) {
 | |
| 		if (unalias(*argptr)) {
 | |
| 			out2fmt("%s: %s not found\n", "unalias", *argptr);
 | |
| 			i = 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return (i);
 | |
| }
 | |
| 
 | |
| static struct alias **
 | |
| hashalias(p)
 | |
| 	const char *p;
 | |
| 	{
 | |
| 	unsigned int hashval;
 | |
| 
 | |
| 	hashval = *p << 4;
 | |
| 	while (*p)
 | |
| 		hashval+= *p++;
 | |
| 	return &atab[hashval % ATABSIZE];
 | |
| }
 | |
| 
 | |
| static struct alias *
 | |
| freealias(struct alias *ap) {
 | |
| 	struct alias *next;
 | |
| 
 | |
| 	if (ap->flag & ALIASINUSE) {
 | |
| 		ap->flag |= ALIASDEAD;
 | |
| 		return ap;
 | |
| 	}
 | |
| 
 | |
| 	next = ap->next;
 | |
| 	ckfree(ap->name);
 | |
| 	ckfree(ap->val);
 | |
| 	ckfree(ap);
 | |
| 	return next;
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct alias **
 | |
| __lookupalias(const char *name) {
 | |
| 	struct alias **app = hashalias(name);
 | |
| 
 | |
| 	for (; *app; app = &(*app)->next) {
 | |
| 		if (equal(name, (*app)->name)) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return app;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| /* The generated file arith.c has been replaced with a custom hand
 | |
|  * written implementation written by Aaron Lehmann <aaronl@vitelus.com>.
 | |
|  * This is now part of libbb, so that it can be used by all the shells
 | |
|  * in busybox. */
 | |
| static void expari (int);
 | |
| #endif
 | |
| 
 | |
| static char *trap[NSIG];                /* trap handler commands */
 | |
| static char sigmode[NSIG - 1];  /* current value of signal */
 | |
| static char gotsig[NSIG - 1];           /* indicates specified signal received */
 | |
| static int pendingsigs;                 /* indicates some signal received */
 | |
| 
 | |
| /*
 | |
|  * This file was generated by the mkbuiltins program.
 | |
|  */
 | |
| 
 | |
| #ifdef JOBS
 | |
| static int bgcmd (int, char **);
 | |
| static int fgcmd (int, char **);
 | |
| static int killcmd (int, char **);
 | |
| #endif
 | |
| static int bltincmd (int, char **);
 | |
| static int cdcmd (int, char **);
 | |
| static int breakcmd (int, char **);
 | |
| #ifdef ASH_CMDCMD
 | |
| static int commandcmd (int, char **);
 | |
| #endif
 | |
| static int dotcmd (int, char **);
 | |
| static int evalcmd (int, char **);
 | |
| static int execcmd (int, char **);
 | |
| static int exitcmd (int, char **);
 | |
| static int exportcmd (int, char **);
 | |
| static int histcmd (int, char **);
 | |
| static int hashcmd (int, char **);
 | |
| static int helpcmd (int, char **);
 | |
| static int jobscmd (int, char **);
 | |
| static int localcmd (int, char **);
 | |
| static int pwdcmd (int, char **);
 | |
| static int readcmd (int, char **);
 | |
| static int returncmd (int, char **);
 | |
| static int setcmd (int, char **);
 | |
| static int setvarcmd (int, char **);
 | |
| static int shiftcmd (int, char **);
 | |
| static int trapcmd (int, char **);
 | |
| static int umaskcmd (int, char **);
 | |
| #ifdef ASH_ALIAS
 | |
| static int aliascmd (int, char **);
 | |
| static int unaliascmd (int, char **);
 | |
| #endif
 | |
| static int unsetcmd (int, char **);
 | |
| static int waitcmd (int, char **);
 | |
| static int ulimitcmd (int, char **);
 | |
| static int timescmd (int, char **);
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| static int letcmd (int, char **);
 | |
| #endif
 | |
| static int typecmd (int, char **);
 | |
| #ifdef ASH_GETOPTS
 | |
| static int getoptscmd (int, char **);
 | |
| #endif
 | |
| 
 | |
| #ifndef CONFIG_TRUE
 | |
| static int true_main (int, char **);
 | |
| #endif
 | |
| #ifndef CONFIG_FALSE
 | |
| static int false_main (int, char **);
 | |
| #endif
 | |
| 
 | |
| static void     setpwd (const char *, int);
 | |
| 
 | |
| 
 | |
| #define BUILTIN_NOSPEC  "0"
 | |
| #define BUILTIN_SPECIAL "1"
 | |
| #define BUILTIN_REGULAR "2"
 | |
| #define BUILTIN_ASSIGN  "4"
 | |
| #define BUILTIN_SPEC_ASSG  "5"
 | |
| #define BUILTIN_REG_ASSG   "6"
 | |
| 
 | |
| #define IS_BUILTIN_SPECIAL(builtincmd) ((builtincmd)->name[0] & 1)
 | |
| #define IS_BUILTIN_REGULAR(builtincmd) ((builtincmd)->name[0] & 2)
 | |
| #define IS_BUILTIN_ASSIGN(builtincmd) ((builtincmd)->name[0] & 4)
 | |
| 
 | |
| struct builtincmd {
 | |
| 	const char *name;
 | |
| 	int (*const builtinfunc) (int, char **);
 | |
| 	//unsigned flags;
 | |
| };
 | |
| 
 | |
| 
 | |
| /* It is CRUCIAL that this listing be kept in ascii order, otherwise
 | |
|  * the binary search in find_builtin() will stop working. If you value
 | |
|  * your kneecaps, you'll be sure to *make sure* that any changes made
 | |
|  * to this array result in the listing remaining in ascii order. You
 | |
|  * have been warned.
 | |
|  */
 | |
| static const struct builtincmd builtincmds[] = {
 | |
| 	{ BUILTIN_SPECIAL   ".", dotcmd },    /* first, see declare DOTCMD */
 | |
| 	{ BUILTIN_SPECIAL   ":", true_main },
 | |
| #ifdef ASH_ALIAS
 | |
| 	{ BUILTIN_REG_ASSG  "alias", aliascmd },
 | |
| #endif
 | |
| #ifdef JOBS
 | |
| 	{ BUILTIN_REGULAR   "bg", bgcmd },
 | |
| #endif
 | |
| 	{ BUILTIN_SPECIAL   "break", breakcmd },
 | |
| 	{ BUILTIN_SPECIAL   "builtin", bltincmd },
 | |
| 	{ BUILTIN_REGULAR   "cd", cdcmd },
 | |
| 	{ BUILTIN_NOSPEC    "chdir", cdcmd },
 | |
| #ifdef ASH_CMDCMD
 | |
| 	{ BUILTIN_REGULAR   "command", commandcmd },
 | |
| #endif
 | |
| 	{ BUILTIN_SPECIAL   "continue", breakcmd },
 | |
| 	{ BUILTIN_SPECIAL   "eval", evalcmd },
 | |
| 	{ BUILTIN_SPECIAL   "exec", execcmd },
 | |
| 	{ BUILTIN_SPECIAL   "exit", exitcmd },
 | |
| 	{ BUILTIN_SPEC_ASSG "export", exportcmd },
 | |
| 	{ BUILTIN_REGULAR   "false", false_main },
 | |
| 	{ BUILTIN_REGULAR   "fc", histcmd },
 | |
| #ifdef JOBS
 | |
| 	{ BUILTIN_REGULAR   "fg", fgcmd },
 | |
| #endif
 | |
| #ifdef ASH_GETOPTS
 | |
| 	{ BUILTIN_REGULAR   "getopts", getoptscmd },
 | |
| #endif
 | |
| 	{ BUILTIN_NOSPEC    "hash", hashcmd },
 | |
| 	{ BUILTIN_NOSPEC    "help", helpcmd },
 | |
| 	{ BUILTIN_REGULAR   "jobs", jobscmd },
 | |
| #ifdef JOBS
 | |
| 	{ BUILTIN_REGULAR   "kill", killcmd },
 | |
| #endif
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| 	{ BUILTIN_REGULAR    "let", letcmd },
 | |
| #endif
 | |
| 	{ BUILTIN_ASSIGN    "local", localcmd },
 | |
| 	{ BUILTIN_NOSPEC    "pwd", pwdcmd },
 | |
| 	{ BUILTIN_REGULAR   "read", readcmd },
 | |
| 	{ BUILTIN_SPEC_ASSG "readonly", exportcmd },
 | |
| 	{ BUILTIN_SPECIAL   "return", returncmd },
 | |
| 	{ BUILTIN_SPECIAL   "set", setcmd },
 | |
| 	{ BUILTIN_NOSPEC    "setvar", setvarcmd },
 | |
| 	{ BUILTIN_SPECIAL   "shift", shiftcmd },
 | |
| 	{ BUILTIN_SPECIAL   "times", timescmd },
 | |
| 	{ BUILTIN_SPECIAL   "trap", trapcmd },
 | |
| 	{ BUILTIN_REGULAR   "true", true_main },
 | |
| 	{ BUILTIN_NOSPEC    "type", typecmd },
 | |
| 	{ BUILTIN_NOSPEC    "ulimit", ulimitcmd },
 | |
| 	{ BUILTIN_REGULAR   "umask", umaskcmd },
 | |
| #ifdef ASH_ALIAS
 | |
| 	{ BUILTIN_REGULAR   "unalias", unaliascmd },
 | |
| #endif
 | |
| 	{ BUILTIN_SPECIAL   "unset", unsetcmd },
 | |
| 	{ BUILTIN_REGULAR   "wait", waitcmd },
 | |
| };
 | |
| #define NUMBUILTINS  (sizeof (builtincmds) / sizeof (struct builtincmd) )
 | |
| 
 | |
| #define DOTCMD &builtincmds[0]
 | |
| static struct builtincmd *BLTINCMD;
 | |
| static struct builtincmd *EXECCMD;
 | |
| static struct builtincmd *EVALCMD;
 | |
| 
 | |
| /* states */
 | |
| #define JOBSTOPPED 1            /* all procs are stopped */
 | |
| #define JOBDONE 2               /* all procs are completed */
 | |
| 
 | |
| /*
 | |
|  * A job structure contains information about a job.  A job is either a
 | |
|  * single process or a set of processes contained in a pipeline.  In the
 | |
|  * latter case, pidlist will be non-NULL, and will point to a -1 terminated
 | |
|  * array of pids.
 | |
|  */
 | |
| 
 | |
| struct procstat {
 | |
| 	pid_t pid;              /* process id */
 | |
| 	int status;             /* status flags (defined above) */
 | |
| 	char *cmd;              /* text of command being run */
 | |
| };
 | |
| 
 | |
| 
 | |
| static int job_warning;         /* user was warned about stopped jobs */
 | |
| 
 | |
| #ifdef JOBS
 | |
| static void setjobctl(int enable);
 | |
| #else
 | |
| #define setjobctl(on)   /* do nothing */
 | |
| #endif
 | |
| 
 | |
| 
 | |
| struct job {
 | |
| 	struct procstat ps0;    /* status of process */
 | |
| 	struct procstat *ps;    /* status or processes when more than one */
 | |
| 	short nprocs;           /* number of processes */
 | |
| 	short pgrp;             /* process group of this job */
 | |
| 	char state;             /* true if job is finished */
 | |
| 	char used;              /* true if this entry is in used */
 | |
| 	char changed;           /* true if status has changed */
 | |
| #ifdef JOBS
 | |
| 	char jobctl;            /* job running under job control */
 | |
| #endif
 | |
| };
 | |
| 
 | |
| static struct job *jobtab;      /* array of jobs */
 | |
| static int njobs;               /* size of array */
 | |
| static int backgndpid = -1;     /* pid of last background process */
 | |
| #ifdef JOBS
 | |
| static int initialpgrp;         /* pgrp of shell on invocation */
 | |
| static int curjob;              /* current job */
 | |
| static int jobctl;
 | |
| #endif
 | |
| static int intreceived;
 | |
| 
 | |
| static struct job *makejob (const union node *, int);
 | |
| static int forkshell (struct job *, const union node *, int);
 | |
| static int waitforjob (struct job *);
 | |
| 
 | |
| static int docd (char *, int);
 | |
| static void updatepwd (const char *);
 | |
| static void getpwd (void);
 | |
| 
 | |
| static char *padvance (const char **, const char *);
 | |
| 
 | |
| static char nullstr[1];         /* zero length string */
 | |
| static char *curdir = nullstr;          /* current working directory */
 | |
| 
 | |
| static int
 | |
| cdcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	const char *dest;
 | |
| 	const char *path;
 | |
| 	char *p;
 | |
| 	struct stat statb;
 | |
| 	int print = 0;
 | |
| 
 | |
| 	nextopt(nullstr);
 | |
| 	if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME")) == NULL)
 | |
| 		error("HOME not set");
 | |
| 	if (*dest == '\0')
 | |
| 		dest = ".";
 | |
| 	if (dest[0] == '-' && dest[1] == '\0') {
 | |
| 		dest = bltinlookup("OLDPWD");
 | |
| 		if (!dest || !*dest) {
 | |
| 			dest = curdir;
 | |
| 		}
 | |
| 		print = 1;
 | |
| 		if (dest)
 | |
| 			print = 1;
 | |
| 		else
 | |
| 			dest = ".";
 | |
| 	}
 | |
| 	if (*dest == '/' || (path = bltinlookup("CDPATH")) == NULL)
 | |
| 		path = nullstr;
 | |
| 	while ((p = padvance(&path, dest)) != NULL) {
 | |
| 		if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
 | |
| 			if (!print) {
 | |
| 				/*
 | |
| 				 * XXX - rethink
 | |
| 				 */
 | |
| 				if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
 | |
| 					p += 2;
 | |
| 				print = strcmp(p, dest);
 | |
| 			}
 | |
| 			if (docd(p, print) >= 0)
 | |
| 				return 0;
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| 	error("can't cd to %s", dest);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Actually do the chdir.  In an interactive shell, print the
 | |
|  * directory name if "print" is nonzero.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| docd(char *dest, int print)
 | |
| {
 | |
| 	TRACE(("docd(\"%s\", %d) called\n", dest, print));
 | |
| 	INTOFF;
 | |
| 	if (chdir(dest) < 0) {
 | |
| 		INTON;
 | |
| 		return -1;
 | |
| 	}
 | |
| 	updatepwd(dest);
 | |
| 	INTON;
 | |
| 	if (print && iflag)
 | |
| 		printf(snlfmt, curdir);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Update curdir (the name of the current directory) in response to a
 | |
|  * cd command.  We also call hashcd to let the routines in exec.c know
 | |
|  * that the current directory has changed.
 | |
|  */
 | |
| 
 | |
| static void hashcd (void);
 | |
| 
 | |
| static void
 | |
| updatepwd(const char *dir)
 | |
| {
 | |
| 	hashcd();                               /* update command hash table */
 | |
| 
 | |
| 	/* If our argument is NULL, we don't know the current directory */
 | |
| 	if (dir == NULL || curdir == nullstr)  {
 | |
| 		setpwd(0, 1);
 | |
| 		return;
 | |
| 	}
 | |
| 	setpwd(dir, 1);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| pwdcmd(int argc, char **argv)
 | |
| {
 | |
| 	printf(snlfmt, curdir);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* Ask system the current directory */
 | |
| static void
 | |
| getpwd(void)
 | |
| {
 | |
| 	curdir = xgetcwd(0);
 | |
| 	if(curdir==0)
 | |
| 		curdir = nullstr;
 | |
| }
 | |
| 
 | |
| static void
 | |
| setpwd(const char *val, int setold)
 | |
| {
 | |
| 	char *cated = NULL;
 | |
| 
 | |
| 	if (setold) {
 | |
| 		setvar("OLDPWD", curdir, VEXPORT);
 | |
| 	}
 | |
| 	INTOFF;
 | |
| 	if (curdir != nullstr) {
 | |
| 		if(val!=NULL && *val != '/')
 | |
| 			val = cated = concat_path_file(curdir, val);
 | |
| 		free(curdir);
 | |
| 	}
 | |
| 	if (!val)
 | |
| 		getpwd();
 | |
| 	 else
 | |
| 		curdir = simplify_path(val);
 | |
| 	free(cated);
 | |
| 	INTON;
 | |
| 	setvar("PWD", curdir, VEXPORT);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Errors and exceptions.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * Code to handle exceptions in C.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * We enclose jmp_buf in a structure so that we can declare pointers to
 | |
|  * jump locations.  The global variable handler contains the location to
 | |
|  * jump to when an exception occurs, and the global variable exception
 | |
|  * contains a code identifying the exeception.  To implement nested
 | |
|  * exception handlers, the user should save the value of handler on entry
 | |
|  * to an inner scope, set handler to point to a jmploc structure for the
 | |
|  * inner scope, and restore handler on exit from the scope.
 | |
|  */
 | |
| 
 | |
| struct jmploc {
 | |
| 	jmp_buf loc;
 | |
| };
 | |
| 
 | |
| /* exceptions */
 | |
| #define EXINT 0         /* SIGINT received */
 | |
| #define EXERROR 1       /* a generic error */
 | |
| #define EXSHELLPROC 2   /* execute a shell procedure */
 | |
| #define EXEXEC 3        /* command execution failed */
 | |
| 
 | |
| static struct jmploc *handler;
 | |
| static int exception;
 | |
| 
 | |
| static void exverror (int, const char *, va_list)
 | |
|     __attribute__((__noreturn__));
 | |
| 
 | |
| /*
 | |
|  * Called to raise an exception.  Since C doesn't include exceptions, we
 | |
|  * just do a longjmp to the exception handler.  The type of exception is
 | |
|  * stored in the global variable "exception".
 | |
|  */
 | |
| 
 | |
| static void exraise (int) __attribute__((__noreturn__));
 | |
| 
 | |
| static void
 | |
| exraise(int e)
 | |
| {
 | |
| #ifdef DEBUG
 | |
| 	if (handler == NULL)
 | |
| 		abort();
 | |
| #endif
 | |
| 	flushall();
 | |
| 	exception = e;
 | |
| 	longjmp(handler->loc, 1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called from trap.c when a SIGINT is received.  (If the user specifies
 | |
|  * that SIGINT is to be trapped or ignored using the trap builtin, then
 | |
|  * this routine is not called.)  Suppressint is nonzero when interrupts
 | |
|  * are held using the INTOFF macro.  The call to _exit is necessary because
 | |
|  * there is a short period after a fork before the signal handlers are
 | |
|  * set to the appropriate value for the child.  (The test for iflag is
 | |
|  * just defensive programming.)
 | |
|  */
 | |
| 
 | |
| static void
 | |
| onint(void) {
 | |
| 	sigset_t mysigset;
 | |
| 
 | |
| 	if (suppressint) {
 | |
| 		intpending++;
 | |
| 		return;
 | |
| 	}
 | |
| 	intpending = 0;
 | |
| 	sigemptyset(&mysigset);
 | |
| 	sigprocmask(SIG_SETMASK, &mysigset, NULL);
 | |
| 	if (rootshell && iflag)
 | |
| 		exraise(EXINT);
 | |
| 	else {
 | |
| 		signal(SIGINT, SIG_DFL);
 | |
| 		raise(SIGINT);
 | |
| 	}
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| static char *commandname;       /* currently executing command */
 | |
| 
 | |
| /*
 | |
|  * Exverror is called to raise the error exception.  If the first argument
 | |
|  * is not NULL then error prints an error message using printf style
 | |
|  * formatting.  It then raises the error exception.
 | |
|  */
 | |
| static void
 | |
| exverror(int cond, const char *msg, va_list ap)
 | |
| {
 | |
| 	CLEAR_PENDING_INT;
 | |
| 	INTOFF;
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 	if (msg)
 | |
| 		TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid()));
 | |
| 	else
 | |
| 		TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid()));
 | |
| #endif
 | |
| 	if (msg) {
 | |
| 		if (commandname)
 | |
| 			out2fmt("%s: ", commandname);
 | |
| 		vfprintf(stderr, msg, ap);
 | |
| 		out2c('\n');
 | |
| 	}
 | |
| 	exraise(cond);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| error(const char *msg, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 	va_start(ap, msg);
 | |
| 	exverror(EXERROR, msg, ap);
 | |
| 	/* NOTREACHED */
 | |
| 	va_end(ap);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| exerror(int cond, const char *msg, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 	va_start(ap, msg);
 | |
| 	exverror(cond, msg, ap);
 | |
| 	/* NOTREACHED */
 | |
| 	va_end(ap);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Table of error messages.
 | |
|  */
 | |
| 
 | |
| struct errname {
 | |
| 	short errcode;          /* error number */
 | |
| 	char  action;           /* operation which encountered the error */
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Types of operations (passed to the errmsg routine).
 | |
|  */
 | |
| 
 | |
| #define E_OPEN 01       /* opening a file */
 | |
| #define E_CREAT 02      /* creating a file */
 | |
| #define E_EXEC 04       /* executing a program */
 | |
| 
 | |
| #define ALL (E_OPEN|E_CREAT|E_EXEC)
 | |
| 
 | |
| static const struct errname errormsg[] = {
 | |
| 	{ EINTR,        ALL     },
 | |
| 	{ EACCES,       ALL     },
 | |
| 	{ EIO,          ALL     },
 | |
| 	{ ENOENT,       E_OPEN  },
 | |
| 	{ ENOENT,       E_CREAT },
 | |
| 	{ ENOENT,       E_EXEC  },
 | |
| 	{ ENOTDIR,      E_OPEN  },
 | |
| 	{ ENOTDIR,      E_CREAT },
 | |
| 	{ ENOTDIR,      E_EXEC  },
 | |
| 	{ EISDIR,       ALL     },
 | |
| 	{ EEXIST,       E_CREAT },
 | |
| #ifdef EMFILE
 | |
| 	{ EMFILE,       ALL     },
 | |
| #endif
 | |
| 	{ ENFILE,       ALL     },
 | |
| 	{ ENOSPC,       ALL     },
 | |
| #ifdef EDQUOT
 | |
| 	{ EDQUOT,       ALL     },
 | |
| #endif
 | |
| #ifdef ENOSR
 | |
| 	{ ENOSR,        ALL     },
 | |
| #endif
 | |
| 	{ ENXIO,        ALL     },
 | |
| 	{ EROFS,        ALL     },
 | |
| 	{ ETXTBSY,      ALL     },
 | |
| #ifdef EAGAIN
 | |
| 	{ EAGAIN,       E_EXEC  },
 | |
| #endif
 | |
| 	{ ENOMEM,       ALL     },
 | |
| #ifdef ENOLINK
 | |
| 	{ ENOLINK,      ALL     },
 | |
| #endif
 | |
| #ifdef EMULTIHOP
 | |
| 	{ EMULTIHOP,    ALL     },
 | |
| #endif
 | |
| #ifdef ECOMM
 | |
| 	{ ECOMM,        ALL     },
 | |
| #endif
 | |
| #ifdef ESTALE
 | |
| 	{ ESTALE,       ALL     },
 | |
| #endif
 | |
| #ifdef ETIMEDOUT
 | |
| 	{ ETIMEDOUT,    ALL     },
 | |
| #endif
 | |
| #ifdef ELOOP
 | |
| 	{ ELOOP,        ALL     },
 | |
| #endif
 | |
| 	{ E2BIG,        E_EXEC  },
 | |
| #ifdef ELIBACC
 | |
| 	{ ELIBACC,      E_EXEC  },
 | |
| #endif
 | |
| };
 | |
| 
 | |
| #define ERRNAME_SIZE (sizeof(errormsg)/sizeof(struct errname))
 | |
| 
 | |
| /*
 | |
|  * Return a string describing an error.  The returned string may be a
 | |
|  * pointer to a static buffer that will be overwritten on the next call.
 | |
|  * Action describes the operation that got the error.
 | |
|  */
 | |
| 
 | |
| static const char *
 | |
| errmsg(int e, int action)
 | |
| {
 | |
| 	struct errname const *ep;
 | |
| 	static char buf[12];
 | |
| 
 | |
| 	for (ep = errormsg ; ep < errormsg+ERRNAME_SIZE; ep++) {
 | |
| 		if (ep->errcode == e && (ep->action & action) != 0)
 | |
| 			return strerror(e);
 | |
| 	}
 | |
| 
 | |
| 	snprintf(buf, sizeof buf, "error %d", e);
 | |
| 	return buf;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef ASH_OPTIMIZE_FOR_SIZE
 | |
| static void
 | |
| __inton() {
 | |
| 	if (--suppressint == 0 && intpending) {
 | |
| 		onint();
 | |
| 	}
 | |
| }
 | |
| static void forceinton (void) {
 | |
| 	suppressint = 0;
 | |
| 	if (intpending)
 | |
| 		onint();
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /* flags in argument to evaltree */
 | |
| #define EV_EXIT 01              /* exit after evaluating tree */
 | |
| #define EV_TESTED 02            /* exit status is checked; ignore -e flag */
 | |
| #define EV_BACKCMD 04           /* command executing within back quotes */
 | |
| 
 | |
| static int evalskip;                    /* set if we are skipping commands */
 | |
| static int skipcount;           /* number of levels to skip */
 | |
| static int loopnest;            /* current loop nesting level */
 | |
| static int funcnest;                    /* depth of function calls */
 | |
| 
 | |
| 
 | |
| static struct strlist *cmdenviron;      /* environment for builtin command */
 | |
| static int exitstatus;                  /* exit status of last command */
 | |
| static int oexitstatus;         /* saved exit status */
 | |
| 
 | |
| static void evalsubshell (const union node *, int);
 | |
| static void expredir (union node *);
 | |
| static void prehash (union node *);
 | |
| static void eprintlist (struct strlist *);
 | |
| 
 | |
| static union node *parsecmd(int);
 | |
| /*
 | |
|  * Called to reset things after an exception.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * The eval commmand.
 | |
|  */
 | |
| static void evalstring (char *, int);
 | |
| 
 | |
| static int
 | |
| evalcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	char *p;
 | |
| 	char *concat;
 | |
| 	char **ap;
 | |
| 
 | |
| 	if (argc > 1) {
 | |
| 		p = argv[1];
 | |
| 		if (argc > 2) {
 | |
| 			STARTSTACKSTR(concat);
 | |
| 			ap = argv + 2;
 | |
| 			for (;;) {
 | |
| 				while (*p)
 | |
| 					STPUTC(*p++, concat);
 | |
| 				if ((p = *ap++) == NULL)
 | |
| 					break;
 | |
| 				STPUTC(' ', concat);
 | |
| 			}
 | |
| 			STPUTC('\0', concat);
 | |
| 			p = grabstackstr(concat);
 | |
| 		}
 | |
| 		evalstring(p, EV_TESTED);
 | |
| 	}
 | |
| 	return exitstatus;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Execute a command or commands contained in a string.
 | |
|  */
 | |
| 
 | |
| static void evaltree (union node *, int);
 | |
| static void setinputstring (char *);
 | |
| static void popfile (void);
 | |
| static void setstackmark(struct stackmark *mark);
 | |
| static void popstackmark(struct stackmark *mark);
 | |
| 
 | |
| 
 | |
| static void
 | |
| evalstring(char *s, int flag)
 | |
| {
 | |
| 	union node *n;
 | |
| 	struct stackmark smark;
 | |
| 
 | |
| 	setstackmark(&smark);
 | |
| 	setinputstring(s);
 | |
| 	while ((n = parsecmd(0)) != NEOF) {
 | |
| 		evaltree(n, flag);
 | |
| 		popstackmark(&smark);
 | |
| 	}
 | |
| 	popfile();
 | |
| 	popstackmark(&smark);
 | |
| }
 | |
| 
 | |
| static struct builtincmd *find_builtin (const char *);
 | |
| static void expandarg (union node *, struct arglist *, int);
 | |
| static void calcsize (const union node *);
 | |
| static union node *copynode (const union node *);
 | |
| 
 | |
| /*
 | |
|  * Make a copy of a parse tree.
 | |
|  */
 | |
| 
 | |
| static int     funcblocksize;           /* size of structures in function */
 | |
| static int     funcstringsize;          /* size of strings in node */
 | |
| static pointer funcblock;              /* block to allocate function from */
 | |
| static char   *funcstring;              /* block to allocate strings from */
 | |
| 
 | |
| 
 | |
| static inline union node *
 | |
| copyfunc(union node *n)
 | |
| {
 | |
| 	if (n == NULL)
 | |
| 		return NULL;
 | |
| 	funcblocksize = 0;
 | |
| 	funcstringsize = 0;
 | |
| 	calcsize(n);
 | |
| 	funcblock = ckmalloc(funcblocksize + funcstringsize);
 | |
| 	funcstring = (char *) funcblock + funcblocksize;
 | |
| 	return copynode(n);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Free a parse tree.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| freefunc(union node *n)
 | |
| {
 | |
| 	if (n)
 | |
| 		ckfree(n);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Add a new command entry, replacing any existing command entry for
 | |
|  * the same name.
 | |
|  */
 | |
| 
 | |
| static inline void
 | |
| addcmdentry(char *name, struct cmdentry *entry)
 | |
| {
 | |
| 	struct tblentry *cmdp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	cmdp = cmdlookup(name, 1);
 | |
| 	if (cmdp->cmdtype == CMDFUNCTION) {
 | |
| 		freefunc(cmdp->param.func);
 | |
| 	}
 | |
| 	cmdp->cmdtype = entry->cmdtype;
 | |
| 	cmdp->param = entry->u;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| static inline void
 | |
| evalloop(const union node *n, int flags)
 | |
| {
 | |
| 	int status;
 | |
| 
 | |
| 	loopnest++;
 | |
| 	status = 0;
 | |
| 	for (;;) {
 | |
| 		evaltree(n->nbinary.ch1, EV_TESTED);
 | |
| 		if (evalskip) {
 | |
| skipping:         if (evalskip == SKIPCONT && --skipcount <= 0) {
 | |
| 				evalskip = 0;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (evalskip == SKIPBREAK && --skipcount <= 0)
 | |
| 				evalskip = 0;
 | |
| 			break;
 | |
| 		}
 | |
| 		if (n->type == NWHILE) {
 | |
| 			if (exitstatus != 0)
 | |
| 				break;
 | |
| 		} else {
 | |
| 			if (exitstatus == 0)
 | |
| 				break;
 | |
| 		}
 | |
| 		evaltree(n->nbinary.ch2, flags & EV_TESTED);
 | |
| 		status = exitstatus;
 | |
| 		if (evalskip)
 | |
| 			goto skipping;
 | |
| 	}
 | |
| 	loopnest--;
 | |
| 	exitstatus = status;
 | |
| }
 | |
| 
 | |
| static void
 | |
| evalfor(const union node *n, int flags)
 | |
| {
 | |
| 	struct arglist arglist;
 | |
| 	union node *argp;
 | |
| 	struct strlist *sp;
 | |
| 	struct stackmark smark;
 | |
| 
 | |
| 	setstackmark(&smark);
 | |
| 	arglist.lastp = &arglist.list;
 | |
| 	for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
 | |
| 		oexitstatus = exitstatus;
 | |
| 		expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
 | |
| 		if (evalskip)
 | |
| 			goto out;
 | |
| 	}
 | |
| 	*arglist.lastp = NULL;
 | |
| 
 | |
| 	exitstatus = 0;
 | |
| 	loopnest++;
 | |
| 	for (sp = arglist.list ; sp ; sp = sp->next) {
 | |
| 		setvar(n->nfor.var, sp->text, 0);
 | |
| 		evaltree(n->nfor.body, flags & EV_TESTED);
 | |
| 		if (evalskip) {
 | |
| 			if (evalskip == SKIPCONT && --skipcount <= 0) {
 | |
| 				evalskip = 0;
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (evalskip == SKIPBREAK && --skipcount <= 0)
 | |
| 				evalskip = 0;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	loopnest--;
 | |
| out:
 | |
| 	popstackmark(&smark);
 | |
| }
 | |
| 
 | |
| static inline void
 | |
| evalcase(const union node *n, int flags)
 | |
| {
 | |
| 	union node *cp;
 | |
| 	union node *patp;
 | |
| 	struct arglist arglist;
 | |
| 	struct stackmark smark;
 | |
| 
 | |
| 	setstackmark(&smark);
 | |
| 	arglist.lastp = &arglist.list;
 | |
| 	oexitstatus = exitstatus;
 | |
| 	expandarg(n->ncase.expr, &arglist, EXP_TILDE);
 | |
| 	for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) {
 | |
| 		for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
 | |
| 			if (casematch(patp, arglist.list->text)) {
 | |
| 				if (evalskip == 0) {
 | |
| 					evaltree(cp->nclist.body, flags);
 | |
| 				}
 | |
| 				goto out;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| out:
 | |
| 	popstackmark(&smark);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Evaluate a pipeline.  All the processes in the pipeline are children
 | |
|  * of the process creating the pipeline.  (This differs from some versions
 | |
|  * of the shell, which make the last process in a pipeline the parent
 | |
|  * of all the rest.)
 | |
|  */
 | |
| 
 | |
| static inline void evalpipe(union node *n)
 | |
| {
 | |
| 	struct job *jp;
 | |
| 	struct nodelist *lp;
 | |
| 	int pipelen;
 | |
| 	int prevfd;
 | |
| 	int pip[2];
 | |
| 
 | |
| 	TRACE(("evalpipe(0x%lx) called\n", (long)n));
 | |
| 	pipelen = 0;
 | |
| 	for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
 | |
| 		pipelen++;
 | |
| 	INTOFF;
 | |
| 	jp = makejob(n, pipelen);
 | |
| 	prevfd = -1;
 | |
| 	for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
 | |
| 		prehash(lp->n);
 | |
| 		pip[1] = -1;
 | |
| 		if (lp->next) {
 | |
| 			if (pipe(pip) < 0) {
 | |
| 				close(prevfd);
 | |
| 				error("Pipe call failed");
 | |
| 			}
 | |
| 		}
 | |
| 		if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
 | |
| 			INTON;
 | |
| 			if (prevfd > 0) {
 | |
| 				close(0);
 | |
| 				dup_as_newfd(prevfd, 0);
 | |
| 				close(prevfd);
 | |
| 				if (pip[0] == 0) {
 | |
| 					pip[0] = -1;
 | |
| 				}
 | |
| 			}
 | |
| 			if (pip[1] >= 0) {
 | |
| 				if (pip[0] >= 0) {
 | |
| 					close(pip[0]);
 | |
| 				}
 | |
| 				if (pip[1] != 1) {
 | |
| 					close(1);
 | |
| 					dup_as_newfd(pip[1], 1);
 | |
| 					close(pip[1]);
 | |
| 				}
 | |
| 			}
 | |
| 			evaltree(lp->n, EV_EXIT);
 | |
| 		}
 | |
| 		if (prevfd >= 0)
 | |
| 			close(prevfd);
 | |
| 		prevfd = pip[0];
 | |
| 		close(pip[1]);
 | |
| 	}
 | |
| 	INTON;
 | |
| 	if (n->npipe.backgnd == 0) {
 | |
| 		INTOFF;
 | |
| 		exitstatus = waitforjob(jp);
 | |
| 		TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
 | |
| 		INTON;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void find_command (const char *, struct cmdentry *, int, const char *);
 | |
| 
 | |
| static int
 | |
| isassignment(const char *word) {
 | |
| 	if (!is_name(*word)) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 	do {
 | |
| 		word++;
 | |
| 	} while (is_in_name(*word));
 | |
| 	return *word == '=';
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| evalcommand(union node *cmd, int flags)
 | |
| {
 | |
| 	struct stackmark smark;
 | |
| 	union node *argp;
 | |
| 	struct arglist arglist;
 | |
| 	struct arglist varlist;
 | |
| 	char **argv;
 | |
| 	int argc;
 | |
| 	char **envp;
 | |
| 	struct strlist *sp;
 | |
| 	int mode;
 | |
| 	struct cmdentry cmdentry;
 | |
| 	struct job *jp;
 | |
| 	char *volatile savecmdname;
 | |
| 	volatile struct shparam saveparam;
 | |
| 	struct localvar *volatile savelocalvars;
 | |
| 	volatile int e;
 | |
| 	char *lastarg;
 | |
| 	const char *path;
 | |
| 	const struct builtincmd *firstbltin;
 | |
| 	struct jmploc *volatile savehandler;
 | |
| 	struct jmploc jmploc;
 | |
| #if __GNUC__
 | |
| 	/* Avoid longjmp clobbering */
 | |
| 	(void) &argv;
 | |
| 	(void) &argc;
 | |
| 	(void) &lastarg;
 | |
| 	(void) &flags;
 | |
| #endif
 | |
| 
 | |
| 	/* First expand the arguments. */
 | |
| 	TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
 | |
| 	setstackmark(&smark);
 | |
| 	arglist.lastp = &arglist.list;
 | |
| 	varlist.lastp = &varlist.list;
 | |
| 	arglist.list = 0;
 | |
| 	oexitstatus = exitstatus;
 | |
| 	exitstatus = 0;
 | |
| 	path = pathval();
 | |
| 	for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
 | |
| 		expandarg(argp, &varlist, EXP_VARTILDE);
 | |
| 	}
 | |
| 	for (
 | |
| 		argp = cmd->ncmd.args; argp && !arglist.list;
 | |
| 		argp = argp->narg.next
 | |
| 	) {
 | |
| 		expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
 | |
| 	}
 | |
| 	if (argp) {
 | |
| 		struct builtincmd *bcmd;
 | |
| 		int pseudovarflag;
 | |
| 		bcmd = find_builtin(arglist.list->text);
 | |
| 		pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
 | |
| 		for (; argp; argp = argp->narg.next) {
 | |
| 			if (pseudovarflag && isassignment(argp->narg.text)) {
 | |
| 				expandarg(argp, &arglist, EXP_VARTILDE);
 | |
| 				continue;
 | |
| 			}
 | |
| 			expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
 | |
| 		}
 | |
| 	}
 | |
| 	*arglist.lastp = NULL;
 | |
| 	*varlist.lastp = NULL;
 | |
| 	expredir(cmd->ncmd.redirect);
 | |
| 	argc = 0;
 | |
| 	for (sp = arglist.list ; sp ; sp = sp->next)
 | |
| 		argc++;
 | |
| 	argv = stalloc(sizeof (char *) * (argc + 1));
 | |
| 
 | |
| 	for (sp = arglist.list ; sp ; sp = sp->next) {
 | |
| 		TRACE(("evalcommand arg: %s\n", sp->text));
 | |
| 		*argv++ = sp->text;
 | |
| 	}
 | |
| 	*argv = NULL;
 | |
| 	lastarg = NULL;
 | |
| 	if (iflag && funcnest == 0 && argc > 0)
 | |
| 		lastarg = argv[-1];
 | |
| 	argv -= argc;
 | |
| 
 | |
| 	/* Print the command if xflag is set. */
 | |
| 	if (xflag) {
 | |
| 		out2c('+');
 | |
| 		eprintlist(varlist.list);
 | |
| 		eprintlist(arglist.list);
 | |
| 		out2c('\n');
 | |
| 	}
 | |
| 
 | |
| 	/* Now locate the command. */
 | |
| 	if (argc == 0) {
 | |
| 		cmdentry.cmdtype = CMDBUILTIN;
 | |
| 		firstbltin = cmdentry.u.cmd = BLTINCMD;
 | |
| 	} else {
 | |
| 		const char *oldpath;
 | |
| 		int findflag = DO_ERR;
 | |
| 		int oldfindflag;
 | |
| 
 | |
| 		/*
 | |
| 		 * Modify the command lookup path, if a PATH= assignment
 | |
| 		 * is present
 | |
| 		 */
 | |
| 		for (sp = varlist.list ; sp ; sp = sp->next)
 | |
| 			if (varequal(sp->text, defpathvar)) {
 | |
| 				path = sp->text + 5;
 | |
| 				findflag |= DO_BRUTE;
 | |
| 			}
 | |
| 		oldpath = path;
 | |
| 		oldfindflag = findflag;
 | |
| 		firstbltin = 0;
 | |
| 		for(;;) {
 | |
| 			find_command(argv[0], &cmdentry, findflag, path);
 | |
| 			if (cmdentry.cmdtype == CMDUNKNOWN) {   /* command not found */
 | |
| 				exitstatus = 127;
 | |
| 				goto out;
 | |
| 			}
 | |
| 			/* implement bltin and command here */
 | |
| 			if (cmdentry.cmdtype != CMDBUILTIN) {
 | |
| 				break;
 | |
| 			}
 | |
| 			if (!firstbltin) {
 | |
| 				firstbltin = cmdentry.u.cmd;
 | |
| 			}
 | |
| 			if (cmdentry.u.cmd == BLTINCMD) {
 | |
| 				for(;;) {
 | |
| 					struct builtincmd *bcmd;
 | |
| 
 | |
| 					argv++;
 | |
| 					if (--argc == 0)
 | |
| 						goto found;
 | |
| 					if (!(bcmd = find_builtin(*argv))) {
 | |
| 						out2fmt("%s: not found\n", *argv);
 | |
| 						exitstatus = 127;
 | |
| 						goto out;
 | |
| 					}
 | |
| 					cmdentry.u.cmd = bcmd;
 | |
| 					if (bcmd != BLTINCMD)
 | |
| 						break;
 | |
| 				}
 | |
| 			}
 | |
| 			if (cmdentry.u.cmd == find_builtin("command")) {
 | |
| 				argv++;
 | |
| 				if (--argc == 0) {
 | |
| 					goto found;
 | |
| 				}
 | |
| 				if (*argv[0] == '-') {
 | |
| 					if (!equal(argv[0], "-p")) {
 | |
| 						argv--;
 | |
| 						argc++;
 | |
| 						break;
 | |
| 					}
 | |
| 					argv++;
 | |
| 					if (--argc == 0) {
 | |
| 						goto found;
 | |
| 					}
 | |
| 					path = defpath;
 | |
| 					findflag |= DO_BRUTE;
 | |
| 				} else {
 | |
| 					path = oldpath;
 | |
| 					findflag = oldfindflag;
 | |
| 				}
 | |
| 				findflag |= DO_NOFUN;
 | |
| 				continue;
 | |
| 			}
 | |
| found:
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Fork off a child process if necessary. */
 | |
| 	if (cmd->ncmd.backgnd
 | |
| 	 || (cmdentry.cmdtype == CMDNORMAL && (flags & EV_EXIT) == 0)
 | |
| 	) {
 | |
| 		jp = makejob(cmd, 1);
 | |
| 		mode = cmd->ncmd.backgnd;
 | |
| 		if (forkshell(jp, cmd, mode) != 0)
 | |
| 			goto parent;    /* at end of routine */
 | |
| 		flags |= EV_EXIT;
 | |
| 	}
 | |
| 
 | |
| 	/* This is the child process if a fork occurred. */
 | |
| 	/* Execute the command. */
 | |
| 	if (cmdentry.cmdtype == CMDFUNCTION) {
 | |
| #ifdef DEBUG
 | |
| 		trputs("Shell function:  ");  trargs(argv);
 | |
| #endif
 | |
| 		exitstatus = oexitstatus;
 | |
| 		redirect(cmd->ncmd.redirect, REDIR_PUSH);
 | |
| 		saveparam = shellparam;
 | |
| 		shellparam.malloc = 0;
 | |
| 		shellparam.nparam = argc - 1;
 | |
| 		shellparam.p = argv + 1;
 | |
| 		INTOFF;
 | |
| 		savelocalvars = localvars;
 | |
| 		localvars = NULL;
 | |
| 		INTON;
 | |
| 		if (setjmp(jmploc.loc)) {
 | |
| 			if (exception == EXSHELLPROC) {
 | |
| 				freeparam((volatile struct shparam *)
 | |
| 				    &saveparam);
 | |
| 			} else {
 | |
| 				saveparam.optind = shellparam.optind;
 | |
| 				saveparam.optoff = shellparam.optoff;
 | |
| 				freeparam(&shellparam);
 | |
| 				shellparam = saveparam;
 | |
| 			}
 | |
| 			poplocalvars();
 | |
| 			localvars = savelocalvars;
 | |
| 			handler = savehandler;
 | |
| 			longjmp(handler->loc, 1);
 | |
| 		}
 | |
| 		savehandler = handler;
 | |
| 		handler = &jmploc;
 | |
| 		for (sp = varlist.list ; sp ; sp = sp->next)
 | |
| 			mklocal(sp->text);
 | |
| 		funcnest++;
 | |
| 		evaltree(cmdentry.u.func, flags & EV_TESTED);
 | |
| 		funcnest--;
 | |
| 		INTOFF;
 | |
| 		poplocalvars();
 | |
| 		localvars = savelocalvars;
 | |
| 		saveparam.optind = shellparam.optind;
 | |
| 		saveparam.optoff = shellparam.optoff;
 | |
| 		freeparam(&shellparam);
 | |
| 		shellparam = saveparam;
 | |
| 		handler = savehandler;
 | |
| 		popredir();
 | |
| 		INTON;
 | |
| 		if (evalskip == SKIPFUNC) {
 | |
| 			evalskip = 0;
 | |
| 			skipcount = 0;
 | |
| 		}
 | |
| 		if (flags & EV_EXIT)
 | |
| 			exitshell(exitstatus);
 | |
| 	} else if (cmdentry.cmdtype == CMDBUILTIN) {
 | |
| #ifdef DEBUG
 | |
| 		trputs("builtin command:  ");  trargs(argv);
 | |
| #endif
 | |
| 		mode = (cmdentry.u.cmd == EXECCMD)? 0 : REDIR_PUSH;
 | |
| 		redirect(cmd->ncmd.redirect, mode);
 | |
| 		savecmdname = commandname;
 | |
| 		if (IS_BUILTIN_SPECIAL(firstbltin)) {
 | |
| 			listsetvar(varlist.list);
 | |
| 		} else {
 | |
| 			cmdenviron = varlist.list;
 | |
| 		}
 | |
| 		e = -1;
 | |
| 		if (setjmp(jmploc.loc)) {
 | |
| 			e = exception;
 | |
| 			exitstatus = (e == EXINT)? SIGINT+128 : 2;
 | |
| 			goto cmddone;
 | |
| 		}
 | |
| 		savehandler = handler;
 | |
| 		handler = &jmploc;
 | |
| 		commandname = argv[0];
 | |
| 		argptr = argv + 1;
 | |
| 		optptr = NULL;                  /* initialize nextopt */
 | |
| 		exitstatus = (*cmdentry.u.cmd->builtinfunc)(argc, argv);
 | |
| 		flushall();
 | |
| cmddone:
 | |
| 		cmdenviron = NULL;
 | |
| 		if (e != EXSHELLPROC) {
 | |
| 			commandname = savecmdname;
 | |
| 			if (flags & EV_EXIT)
 | |
| 				exitshell(exitstatus);
 | |
| 		}
 | |
| 		handler = savehandler;
 | |
| 		if (e != -1) {
 | |
| 			if ((e != EXERROR && e != EXEXEC)
 | |
| 			   || cmdentry.u.cmd == BLTINCMD
 | |
| 			   || cmdentry.u.cmd == DOTCMD
 | |
| 			   || cmdentry.u.cmd == EVALCMD
 | |
| 			   || cmdentry.u.cmd == EXECCMD)
 | |
| 				exraise(e);
 | |
| 			FORCEINTON;
 | |
| 		}
 | |
| 		if (cmdentry.u.cmd != EXECCMD)
 | |
| 			popredir();
 | |
| 	} else {
 | |
| #ifdef DEBUG
 | |
| 		trputs("normal command:  ");  trargs(argv);
 | |
| #endif
 | |
| 		redirect(cmd->ncmd.redirect, 0);
 | |
| 		clearredir();
 | |
| 		for (sp = varlist.list ; sp ; sp = sp->next)
 | |
| 			setvareq(sp->text, VEXPORT|VSTACK);
 | |
| 		envp = environment();
 | |
| 		shellexec(argv, envp, path, cmdentry.u.index);
 | |
| 	}
 | |
| 	goto out;
 | |
| 
 | |
| parent: /* parent process gets here (if we forked) */
 | |
| 	if (mode == 0) {        /* argument to fork */
 | |
| 		INTOFF;
 | |
| 		exitstatus = waitforjob(jp);
 | |
| 		INTON;
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	if (lastarg)
 | |
| 		setvar("_", lastarg, 0);
 | |
| 	popstackmark(&smark);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Evaluate a parse tree.  The value is left in the global variable
 | |
|  * exitstatus.
 | |
|  */
 | |
| static void
 | |
| evaltree(n, flags)
 | |
| 	union node *n;
 | |
| 	int flags;
 | |
| {
 | |
| 	int checkexit = 0;
 | |
| 	if (n == NULL) {
 | |
| 		TRACE(("evaltree(NULL) called\n"));
 | |
| 		goto out;
 | |
| 	}
 | |
| 	TRACE(("evaltree(0x%lx: %d) called\n", (long)n, n->type));
 | |
| 	switch (n->type) {
 | |
| 	case NSEMI:
 | |
| 		evaltree(n->nbinary.ch1, flags & EV_TESTED);
 | |
| 		if (evalskip)
 | |
| 			goto out;
 | |
| 		evaltree(n->nbinary.ch2, flags);
 | |
| 		break;
 | |
| 	case NAND:
 | |
| 		evaltree(n->nbinary.ch1, EV_TESTED);
 | |
| 		if (evalskip || exitstatus != 0)
 | |
| 			goto out;
 | |
| 		evaltree(n->nbinary.ch2, flags);
 | |
| 		break;
 | |
| 	case NOR:
 | |
| 		evaltree(n->nbinary.ch1, EV_TESTED);
 | |
| 		if (evalskip || exitstatus == 0)
 | |
| 			goto out;
 | |
| 		evaltree(n->nbinary.ch2, flags);
 | |
| 		break;
 | |
| 	case NREDIR:
 | |
| 		expredir(n->nredir.redirect);
 | |
| 		redirect(n->nredir.redirect, REDIR_PUSH);
 | |
| 		evaltree(n->nredir.n, flags);
 | |
| 		popredir();
 | |
| 		break;
 | |
| 	case NSUBSHELL:
 | |
| 		evalsubshell(n, flags);
 | |
| 		break;
 | |
| 	case NBACKGND:
 | |
| 		evalsubshell(n, flags);
 | |
| 		break;
 | |
| 	case NIF: {
 | |
| 		evaltree(n->nif.test, EV_TESTED);
 | |
| 		if (evalskip)
 | |
| 			goto out;
 | |
| 		if (exitstatus == 0)
 | |
| 			evaltree(n->nif.ifpart, flags);
 | |
| 		else if (n->nif.elsepart)
 | |
| 			evaltree(n->nif.elsepart, flags);
 | |
| 		else
 | |
| 			exitstatus = 0;
 | |
| 		break;
 | |
| 	}
 | |
| 	case NWHILE:
 | |
| 	case NUNTIL:
 | |
| 		evalloop(n, flags);
 | |
| 		break;
 | |
| 	case NFOR:
 | |
| 		evalfor(n, flags);
 | |
| 		break;
 | |
| 	case NCASE:
 | |
| 		evalcase(n, flags);
 | |
| 		break;
 | |
| 	case NDEFUN: {
 | |
| 		struct builtincmd *bcmd;
 | |
| 		struct cmdentry entry;
 | |
| 		if (
 | |
| 			(bcmd = find_builtin(n->narg.text)) &&
 | |
| 			IS_BUILTIN_SPECIAL(bcmd)
 | |
| 		) {
 | |
| 			out2fmt("%s is a special built-in\n", n->narg.text);
 | |
| 			exitstatus = 1;
 | |
| 			break;
 | |
| 		}
 | |
| 		entry.cmdtype = CMDFUNCTION;
 | |
| 		entry.u.func = copyfunc(n->narg.next);
 | |
| 		addcmdentry(n->narg.text, &entry);
 | |
| 		exitstatus = 0;
 | |
| 		break;
 | |
| 	}
 | |
| 	case NNOT:
 | |
| 		evaltree(n->nnot.com, EV_TESTED);
 | |
| 		exitstatus = !exitstatus;
 | |
| 		break;
 | |
| 
 | |
| 	case NPIPE:
 | |
| 		evalpipe(n);
 | |
| 		checkexit = 1;
 | |
| 		break;
 | |
| 	case NCMD:
 | |
| 		evalcommand(n, flags);
 | |
| 		checkexit = 1;
 | |
| 		break;
 | |
| #ifdef DEBUG
 | |
| 	default:
 | |
| 		printf("Node type = %d\n", n->type);
 | |
| 		break;
 | |
| #endif
 | |
| 	}
 | |
| out:
 | |
| 	if (pendingsigs)
 | |
| 		dotrap();
 | |
| 	if (
 | |
| 		flags & EV_EXIT ||
 | |
| 		(checkexit && eflag && exitstatus && !(flags & EV_TESTED))
 | |
| 	)
 | |
| 		exitshell(exitstatus);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Kick off a subshell to evaluate a tree.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| evalsubshell(const union node *n, int flags)
 | |
| {
 | |
| 	struct job *jp;
 | |
| 	int backgnd = (n->type == NBACKGND);
 | |
| 
 | |
| 	expredir(n->nredir.redirect);
 | |
| 	jp = makejob(n, 1);
 | |
| 	if (forkshell(jp, n, backgnd) == 0) {
 | |
| 		if (backgnd)
 | |
| 			flags &=~ EV_TESTED;
 | |
| 		redirect(n->nredir.redirect, 0);
 | |
| 		evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
 | |
| 	}
 | |
| 	if (! backgnd) {
 | |
| 		INTOFF;
 | |
| 		exitstatus = waitforjob(jp);
 | |
| 		INTON;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compute the names of the files in a redirection list.
 | |
|  */
 | |
| 
 | |
| static void fixredir(union node *n, const char *text, int err);
 | |
| 
 | |
| static void
 | |
| expredir(union node *n)
 | |
| {
 | |
| 	union node *redir;
 | |
| 
 | |
| 	for (redir = n ; redir ; redir = redir->nfile.next) {
 | |
| 		struct arglist fn;
 | |
| 		fn.lastp = &fn.list;
 | |
| 		oexitstatus = exitstatus;
 | |
| 		switch (redir->type) {
 | |
| 		case NFROMTO:
 | |
| 		case NFROM:
 | |
| 		case NTO:
 | |
| 		case NAPPEND:
 | |
| 		case NTOOV:
 | |
| 			expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
 | |
| 			redir->nfile.expfname = fn.list->text;
 | |
| 			break;
 | |
| 		case NFROMFD:
 | |
| 		case NTOFD:
 | |
| 			if (redir->ndup.vname) {
 | |
| 				expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
 | |
| 				fixredir(redir, fn.list->text, 1);
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Execute a command inside back quotes.  If it's a builtin command, we
 | |
|  * want to save its output in a block obtained from malloc.  Otherwise
 | |
|  * we fork off a subprocess and get the output of the command via a pipe.
 | |
|  * Should be called with interrupts off.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| evalbackcmd(union node *n, struct backcmd *result)
 | |
| {
 | |
| 	int pip[2];
 | |
| 	struct job *jp;
 | |
| 	struct stackmark smark;         /* unnecessary */
 | |
| 
 | |
| 	setstackmark(&smark);
 | |
| 	result->fd = -1;
 | |
| 	result->buf = NULL;
 | |
| 	result->nleft = 0;
 | |
| 	result->jp = NULL;
 | |
| 	if (n == NULL) {
 | |
| 		exitstatus = 0;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	exitstatus = 0;
 | |
| 	if (pipe(pip) < 0)
 | |
| 		error("Pipe call failed");
 | |
| 	jp = makejob(n, 1);
 | |
| 	if (forkshell(jp, n, FORK_NOJOB) == 0) {
 | |
| 		FORCEINTON;
 | |
| 		close(pip[0]);
 | |
| 		if (pip[1] != 1) {
 | |
| 			close(1);
 | |
| 			dup_as_newfd(pip[1], 1);
 | |
| 			close(pip[1]);
 | |
| 		}
 | |
| 		eflag = 0;
 | |
| 		evaltree(n, EV_EXIT);
 | |
| 	}
 | |
| 	close(pip[1]);
 | |
| 	result->fd = pip[0];
 | |
| 	result->jp = jp;
 | |
| out:
 | |
| 	popstackmark(&smark);
 | |
| 	TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
 | |
| 		result->fd, result->buf, result->nleft, result->jp));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Execute a simple command.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * Search for a command.  This is called before we fork so that the
 | |
|  * location of the command will be available in the parent as well as
 | |
|  * the child.  The check for "goodname" is an overly conservative
 | |
|  * check that the name will not be subject to expansion.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| prehash(n)
 | |
| 	union node *n;
 | |
| {
 | |
| 	struct cmdentry entry;
 | |
| 
 | |
| 	if (n->type == NCMD && n->ncmd.args)
 | |
| 		if (goodname(n->ncmd.args->narg.text))
 | |
| 			find_command(n->ncmd.args->narg.text, &entry, 0,
 | |
| 				     pathval());
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Builtin commands.  Builtin commands whose functions are closely
 | |
|  * tied to evaluation are implemented here.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * No command given, or a bltin command with no arguments.  Set the
 | |
|  * specified variables.
 | |
|  */
 | |
| 
 | |
| int
 | |
| bltincmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	/*
 | |
| 	 * Preserve exitstatus of a previous possible redirection
 | |
| 	 * as POSIX mandates
 | |
| 	 */
 | |
| 	return exitstatus;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Handle break and continue commands.  Break, continue, and return are
 | |
|  * all handled by setting the evalskip flag.  The evaluation routines
 | |
|  * above all check this flag, and if it is set they start skipping
 | |
|  * commands rather than executing them.  The variable skipcount is
 | |
|  * the number of loops to break/continue, or the number of function
 | |
|  * levels to return.  (The latter is always 1.)  It should probably
 | |
|  * be an error to break out of more loops than exist, but it isn't
 | |
|  * in the standard shell so we don't make it one here.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| breakcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	int n = argc > 1 ? number(argv[1]) : 1;
 | |
| 
 | |
| 	if (n > loopnest)
 | |
| 		n = loopnest;
 | |
| 	if (n > 0) {
 | |
| 		evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
 | |
| 		skipcount = n;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The return command.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| returncmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	int ret = argc > 1 ? number(argv[1]) : oexitstatus;
 | |
| 
 | |
| 	if (funcnest) {
 | |
| 		evalskip = SKIPFUNC;
 | |
| 		skipcount = 1;
 | |
| 		return ret;
 | |
| 	}
 | |
| 	else {
 | |
| 		/* Do what ksh does; skip the rest of the file */
 | |
| 		evalskip = SKIPFILE;
 | |
| 		skipcount = 1;
 | |
| 		return ret;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifndef CONFIG_FALSE
 | |
| static int
 | |
| false_main(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	return 1;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #ifndef CONFIG_TRUE
 | |
| static int
 | |
| true_main(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Controls whether the shell is interactive or not.
 | |
|  */
 | |
| 
 | |
| static void setsignal(int signo);
 | |
| 
 | |
| #ifdef ASH_MAIL
 | |
| static void chkmail(int silent);
 | |
| #endif
 | |
| 
 | |
| static void
 | |
| setinteractive(int on)
 | |
| {
 | |
| 	static int is_interactive;
 | |
| 	static int do_banner=0;
 | |
| 
 | |
| 	if (on == is_interactive)
 | |
| 		return;
 | |
| 	setsignal(SIGINT);
 | |
| 	setsignal(SIGQUIT);
 | |
| 	setsignal(SIGTERM);
 | |
| #ifdef ASH_MAIL
 | |
| 	chkmail(1);
 | |
| #endif
 | |
| 	is_interactive = on;
 | |
| 	if (do_banner==0 && is_interactive) {
 | |
| 		/* Looks like they want an interactive shell */
 | |
| #ifndef CONFIG_FEATURE_SH_EXTRA_QUIET 
 | |
| 		printf( "\n\n" BB_BANNER " Built-in shell (ash)\n");
 | |
| 		printf( "Enter 'help' for a list of built-in commands.\n\n");
 | |
| #endif
 | |
| 		do_banner=1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void
 | |
| optschanged(void)
 | |
| {
 | |
| 	setinteractive(iflag);
 | |
| 	setjobctl(mflag);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| execcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	if (argc > 1) {
 | |
| 		struct strlist *sp;
 | |
| 
 | |
| 		iflag = 0;              /* exit on error */
 | |
| 		mflag = 0;
 | |
| 		optschanged();
 | |
| 		for (sp = cmdenviron; sp ; sp = sp->next)
 | |
| 			setvareq(sp->text, VEXPORT|VSTACK);
 | |
| 		shellexec(argv + 1, environment(), pathval(), 0);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| eprintlist(struct strlist *sp)
 | |
| {
 | |
| 	for (; sp; sp = sp->next) {
 | |
| 		out2fmt(" %s",sp->text);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Exec a program.  Never returns.  If you change this routine, you may
 | |
|  * have to change the find_command routine as well.
 | |
|  */
 | |
| 
 | |
| static const char *pathopt;     /* set by padvance */
 | |
| 
 | |
| static void
 | |
| shellexec(argv, envp, path, idx)
 | |
| 	char **argv, **envp;
 | |
| 	const char *path;
 | |
| 	int idx;
 | |
| {
 | |
| 	char *cmdname;
 | |
| 	int e;
 | |
| 
 | |
| 	if (strchr(argv[0], '/') != NULL) {
 | |
| 		tryexec(argv[0], argv, envp);
 | |
| 		e = errno;
 | |
| 	} else {
 | |
| 		e = ENOENT;
 | |
| 		while ((cmdname = padvance(&path, argv[0])) != NULL) {
 | |
| 			if (--idx < 0 && pathopt == NULL) {
 | |
| 				tryexec(cmdname, argv, envp);
 | |
| 				if (errno != ENOENT && errno != ENOTDIR)
 | |
| 					e = errno;
 | |
| 			}
 | |
| 			stunalloc(cmdname);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Map to POSIX errors */
 | |
| 	switch (e) {
 | |
| 	case EACCES:
 | |
| 		exerrno = 126;
 | |
| 		break;
 | |
| 	case ENOENT:
 | |
| 		exerrno = 127;
 | |
| 		break;
 | |
| 	default:
 | |
| 		exerrno = 2;
 | |
| 		break;
 | |
| 	}
 | |
| 	exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Clear traps on a fork.
 | |
|  */
 | |
| static void
 | |
| clear_traps(void) {
 | |
| 	char **tp;
 | |
| 
 | |
| 	for (tp = trap ; tp < &trap[NSIG] ; tp++) {
 | |
| 		if (*tp && **tp) {      /* trap not NULL or SIG_IGN */
 | |
| 			INTOFF;
 | |
| 			ckfree(*tp);
 | |
| 			*tp = NULL;
 | |
| 			if (tp != &trap[0])
 | |
| 				setsignal(tp - trap);
 | |
| 			INTON;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| initshellproc(void) {
 | |
| 
 | |
| #ifdef ASH_ALIAS
 | |
|       /* from alias.c: */
 | |
|       {
 | |
| 	      rmaliases();
 | |
|       }
 | |
| #endif
 | |
|       /* from eval.c: */
 | |
|       {
 | |
| 	      exitstatus = 0;
 | |
|       }
 | |
| 
 | |
|       /* from exec.c: */
 | |
|       {
 | |
| 	      deletefuncs();
 | |
|       }
 | |
| 
 | |
|       /* from jobs.c: */
 | |
|       {
 | |
| 	      backgndpid = -1;
 | |
| #ifdef JOBS
 | |
| 	      jobctl = 0;
 | |
| #endif
 | |
|       }
 | |
| 
 | |
|       /* from options.c: */
 | |
|       {
 | |
| 	      int i;
 | |
| 
 | |
| 	      for (i = 0; i < NOPTS; i++)
 | |
| 		      optent_val(i) = 0;
 | |
| 	      optschanged();
 | |
| 
 | |
|       }
 | |
| 
 | |
|       /* from redir.c: */
 | |
|       {
 | |
| 	      clearredir();
 | |
|       }
 | |
| 
 | |
|       /* from trap.c: */
 | |
|       {
 | |
| 	      char *sm;
 | |
| 
 | |
| 	      clear_traps();
 | |
| 	      for (sm = sigmode ; sm < sigmode + NSIG - 1; sm++) {
 | |
| 		      if (*sm == S_IGN)
 | |
| 			      *sm = S_HARD_IGN;
 | |
| 	      }
 | |
|       }
 | |
| 
 | |
|       /* from var.c: */
 | |
|       {
 | |
| 	      shprocvar();
 | |
|       }
 | |
| }
 | |
| 
 | |
| static int preadbuffer(void);
 | |
| static void pushfile (void);
 | |
| 
 | |
| /*
 | |
|  * Read a character from the script, returning PEOF on end of file.
 | |
|  * Nul characters in the input are silently discarded.
 | |
|  */
 | |
| 
 | |
| #ifndef ASH_OPTIMIZE_FOR_SIZE
 | |
| #define pgetc_macro()   (--parsenleft >= 0? *parsenextc++ : preadbuffer())
 | |
| static int
 | |
| pgetc(void)
 | |
| {
 | |
| 	return pgetc_macro();
 | |
| }
 | |
| #else
 | |
| static int
 | |
| pgetc_macro(void)
 | |
| {
 | |
| 	return --parsenleft >= 0? *parsenextc++ : preadbuffer();
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| pgetc(void)
 | |
| {
 | |
| 	return pgetc_macro();
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Undo the last call to pgetc.  Only one character may be pushed back.
 | |
|  * PEOF may be pushed back.
 | |
|  */
 | |
| 
 | |
| static void pungetc(void) 
 | |
| {
 | |
| 	parsenleft++;
 | |
| 	parsenextc--;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| popfile(void) {
 | |
| 	struct parsefile *pf = parsefile;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	if (pf->fd >= 0)
 | |
| 		close(pf->fd);
 | |
| 	if (pf->buf)
 | |
| 		ckfree(pf->buf);
 | |
| 	while (pf->strpush)
 | |
| 		popstring();
 | |
| 	parsefile = pf->prev;
 | |
| 	ckfree(pf);
 | |
| 	parsenleft = parsefile->nleft;
 | |
| 	parselleft = parsefile->lleft;
 | |
| 	parsenextc = parsefile->nextc;
 | |
| 	plinno = parsefile->linno;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Return to top level.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| popallfiles(void) {
 | |
| 	while (parsefile != &basepf)
 | |
| 		popfile();
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Close the file(s) that the shell is reading commands from.  Called
 | |
|  * after a fork is done.
 | |
|  */
 | |
| 
 | |
| static void closescript(void) 
 | |
| {
 | |
| 	popallfiles();
 | |
| 	if (parsefile->fd > 0) {
 | |
| 		close(parsefile->fd);
 | |
| 		parsefile->fd = 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Like setinputfile, but takes an open file descriptor.  Call this with
 | |
|  * interrupts off.
 | |
|  */
 | |
| 
 | |
| static void setinputfd(int fd, int push)
 | |
| {
 | |
| 	(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
 | |
| 	if (push) {
 | |
| 		pushfile();
 | |
| 		parsefile->buf = 0;
 | |
| 	} else {
 | |
| 		closescript();
 | |
| 		while (parsefile->strpush)
 | |
| 			popstring();
 | |
| 	}
 | |
| 	parsefile->fd = fd;
 | |
| 	if (parsefile->buf == NULL)
 | |
| 		parsefile->buf = ckmalloc(BUFSIZ);
 | |
| 	parselleft = parsenleft = 0;
 | |
| 	plinno = 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Set the input to take input from a file.  If push is set, push the
 | |
|  * old input onto the stack first.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| setinputfile(const char *fname, int push)
 | |
| {
 | |
| 	int fd;
 | |
| 	int myfileno2;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	if ((fd = open(fname, O_RDONLY)) < 0)
 | |
| 		error("Can't open %s", fname);
 | |
| 	if (fd < 10) {
 | |
| 		myfileno2 = dup_as_newfd(fd, 10);
 | |
| 		close(fd);
 | |
| 		if (myfileno2 < 0)
 | |
| 			error("Out of file descriptors");
 | |
| 		fd = myfileno2;
 | |
| 	}
 | |
| 	setinputfd(fd, push);
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| tryexec(char *cmd, char **argv, char **envp)
 | |
| {
 | |
| 	int e;
 | |
| 
 | |
| #ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL
 | |
| 	char *name = cmd;
 | |
| 	char** argv_l=argv;
 | |
| 	int argc_l;
 | |
| #ifdef CONFIG_FEATURE_SH_APPLETS_ALWAYS_WIN
 | |
| 	name = get_last_path_component(name);
 | |
| #endif
 | |
| 	argv_l=envp;
 | |
| 	for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++)
 | |
| 		putenv(*argv_l);
 | |
| 	argv_l=argv;
 | |
| 	for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++)
 | |
| 	optind = 1;
 | |
| 	run_applet_by_name(name, argc_l, argv);
 | |
| #endif
 | |
| 	execve(cmd, argv, envp);
 | |
| 	e = errno;
 | |
| 	if (e == ENOEXEC) {
 | |
| 		INTOFF;
 | |
| 		initshellproc();
 | |
| 		setinputfile(cmd, 0);
 | |
| 		commandname = arg0 = savestr(argv[0]);
 | |
| 		setparam(argv + 1);
 | |
| 		exraise(EXSHELLPROC);
 | |
| 	}
 | |
| 	errno = e;
 | |
| }
 | |
| 
 | |
| static char *commandtext (const union node *);
 | |
| 
 | |
| /*
 | |
|  * Do a path search.  The variable path (passed by reference) should be
 | |
|  * set to the start of the path before the first call; padvance will update
 | |
|  * this value as it proceeds.  Successive calls to padvance will return
 | |
|  * the possible path expansions in sequence.  If an option (indicated by
 | |
|  * a percent sign) appears in the path entry then the global variable
 | |
|  * pathopt will be set to point to it; otherwise pathopt will be set to
 | |
|  * NULL.
 | |
|  */
 | |
| 
 | |
| static const char *pathopt;
 | |
| 
 | |
| static void growstackblock(void);
 | |
| 
 | |
| 
 | |
| static char *
 | |
| padvance(const char **path, const char *name)
 | |
| {
 | |
| 	const char *p;
 | |
| 	char *q;
 | |
| 	const char *start;
 | |
| 	int len;
 | |
| 
 | |
| 	if (*path == NULL)
 | |
| 		return NULL;
 | |
| 	start = *path;
 | |
| 	for (p = start ; *p && *p != ':' && *p != '%' ; p++);
 | |
| 	len = p - start + strlen(name) + 2;     /* "2" is for '/' and '\0' */
 | |
| 	while (stackblocksize() < len)
 | |
| 		growstackblock();
 | |
| 	q = stackblock();
 | |
| 	if (p != start) {
 | |
| 		memcpy(q, start, p - start);
 | |
| 		q += p - start;
 | |
| 		*q++ = '/';
 | |
| 	}
 | |
| 	strcpy(q, name);
 | |
| 	pathopt = NULL;
 | |
| 	if (*p == '%') {
 | |
| 		pathopt = ++p;
 | |
| 		while (*p && *p != ':')  p++;
 | |
| 	}
 | |
| 	if (*p == ':')
 | |
| 		*path = p + 1;
 | |
| 	else
 | |
| 		*path = NULL;
 | |
| 	return stalloc(len);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Wrapper around strcmp for qsort/bsearch/...
 | |
|  */
 | |
| static int
 | |
| pstrcmp(const void *a, const void *b)
 | |
| {
 | |
| 	return strcmp((const char *) a, (*(const char *const *) b) + 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Find a keyword is in a sorted array.
 | |
|  */
 | |
| 
 | |
| static const char *const *
 | |
| findkwd(const char *s)
 | |
| {
 | |
| 	return  bsearch(s, tokname_array + KWDOFFSET,
 | |
| 					(sizeof(tokname_array)/sizeof(const char *)) - KWDOFFSET,
 | |
| 					sizeof(const char *), pstrcmp);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*** Command hashing code ***/
 | |
| 
 | |
| 
 | |
| static int
 | |
| hashcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct tblentry **pp;
 | |
| 	struct tblentry *cmdp;
 | |
| 	int c;
 | |
| 	int verbose;
 | |
| 	struct cmdentry entry;
 | |
| 	char *name;
 | |
| #ifdef ASH_ALIAS
 | |
| 	const struct alias *ap;
 | |
| #endif
 | |
| 
 | |
| 	verbose = 0;
 | |
| 	while ((c = nextopt("rvV")) != '\0') {
 | |
| 		if (c == 'r') {
 | |
| 			clearcmdentry(0);
 | |
| 			return 0;
 | |
| 		} else if (c == 'v' || c == 'V') {
 | |
| 			verbose = c;
 | |
| 		}
 | |
| 	}
 | |
| 	if (*argptr == NULL) {
 | |
| 		for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
 | |
| 			for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
 | |
| 				if (cmdp->cmdtype != CMDBUILTIN) {
 | |
| 					printentry(cmdp, verbose);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return 0;
 | |
| 	}
 | |
| 	c = 0;
 | |
| 	while ((name = *argptr++) != NULL) {
 | |
| 		if ((cmdp = cmdlookup(name, 0)) != NULL
 | |
| 		 && (cmdp->cmdtype == CMDNORMAL
 | |
| 		     || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
 | |
| 			delete_cmd_entry();
 | |
| #ifdef ASH_ALIAS
 | |
| 	/* Then look at the aliases */
 | |
| 		if ((ap = *__lookupalias(name)) != NULL) {
 | |
| 			if (verbose=='v')
 | |
| 				printf("%s is an alias for %s\n", name, ap->val);
 | |
| 			else
 | |
| 				printalias(ap);
 | |
| 			continue;
 | |
| 		}
 | |
| #endif
 | |
| 			/* First look at the keywords */
 | |
| 		if (findkwd(name)!=0) {
 | |
| 			if (verbose=='v')
 | |
| 				printf("%s is a shell keyword\n", name);
 | |
| 			else
 | |
| 				printf(snlfmt, name);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		find_command(name, &entry, DO_ERR, pathval());
 | |
| 		if (entry.cmdtype == CMDUNKNOWN) c = 1;
 | |
| 		else if (verbose) {
 | |
| 			cmdp = cmdlookup(name, 0);
 | |
| 			if (cmdp) printentry(cmdp, verbose=='v');
 | |
| 			flushall();
 | |
| 		}
 | |
| 	}
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| static void
 | |
| printentry(cmdp, verbose)
 | |
| 	struct tblentry *cmdp;
 | |
| 	int verbose;
 | |
| 	{
 | |
| 	int idx;
 | |
| 	const char *path;
 | |
| 	char *name;
 | |
| 
 | |
| 	printf("%s%s", cmdp->cmdname, (verbose ? " is " : ""));
 | |
| 	if (cmdp->cmdtype == CMDNORMAL) {
 | |
| 		idx = cmdp->param.index;
 | |
| 		path = pathval();
 | |
| 		do {
 | |
| 			name = padvance(&path, cmdp->cmdname);
 | |
| 			stunalloc(name);
 | |
| 		} while (--idx >= 0);
 | |
| 		if(verbose)
 | |
| 			out1str(name);
 | |
| 	} else if (cmdp->cmdtype == CMDBUILTIN) {
 | |
| 		if(verbose)
 | |
| 			out1str("a shell builtin");
 | |
| 	} else if (cmdp->cmdtype == CMDFUNCTION) {
 | |
| 		if (verbose) {
 | |
| 			INTOFF;
 | |
| 			out1str("a function\n");
 | |
| 			name = commandtext(cmdp->param.func);
 | |
| 			printf("%s() {\n %s\n}", cmdp->cmdname, name);
 | |
| 			ckfree(name);
 | |
| 			INTON;
 | |
| 		}
 | |
| #ifdef DEBUG
 | |
| 	} else {
 | |
| 		error("internal error: cmdtype %d", cmdp->cmdtype);
 | |
| #endif
 | |
| 	}
 | |
| 	printf(snlfmt, cmdp->rehash ? "*" : nullstr);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*** List the available builtins ***/
 | |
| 
 | |
| 
 | |
| static int helpcmd(int argc, char** argv)
 | |
| {
 | |
| 	int col, i;
 | |
| 
 | |
| 	printf("\nBuilt-in commands:\n-------------------\n");
 | |
| 	for (col=0, i=0; i < NUMBUILTINS; i++) {
 | |
| 		col += printf("%c%s", ((col == 0) ? '\t' : ' '),
 | |
| 				builtincmds[i].name+1);
 | |
| 		if (col > 60) {
 | |
| 			printf("\n");
 | |
| 			col = 0;
 | |
| 		}
 | |
| 	}
 | |
| #ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL
 | |
| 	{
 | |
| 		extern const struct BB_applet applets[];
 | |
| 		extern const size_t NUM_APPLETS;
 | |
| 
 | |
| 		for (i=0; i < NUM_APPLETS; i++) {
 | |
| 
 | |
| 			col += printf("%c%s", ((col == 0) ? '\t' : ' '),
 | |
| 					applets[i].name);
 | |
| 			if (col > 60) {
 | |
| 				printf("\n");
 | |
| 				col = 0;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| #endif
 | |
| 	printf("\n\n");
 | |
| 	return EXIT_SUCCESS;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Resolve a command name.  If you change this routine, you may have to
 | |
|  * change the shellexec routine as well.
 | |
|  */
 | |
| 
 | |
| static int prefix (const char *, const char *);
 | |
| 
 | |
| static void
 | |
| find_command(const char *name, struct cmdentry *entry, int act, const char *path)
 | |
| {
 | |
| 	struct tblentry *cmdp;
 | |
| 	int idx;
 | |
| 	int prev;
 | |
| 	char *fullname;
 | |
| 	struct stat statb;
 | |
| 	int e;
 | |
| 	int bltin;
 | |
| 	int firstchange;
 | |
| 	int updatetbl;
 | |
| 	int regular;
 | |
| 	struct builtincmd *bcmd;
 | |
| 
 | |
| 	/* If name contains a slash, don't use the hash table */
 | |
| 	if (strchr(name, '/') != NULL) {
 | |
| 		if (act & DO_ABS) {
 | |
| 			while (stat(name, &statb) < 0) {
 | |
| 				if (errno != ENOENT && errno != ENOTDIR)
 | |
| 					e = errno;
 | |
| 				entry->cmdtype = CMDUNKNOWN;
 | |
| 				entry->u.index = -1;
 | |
| 				return;
 | |
| 			}
 | |
| 			entry->cmdtype = CMDNORMAL;
 | |
| 			entry->u.index = -1;
 | |
| 			return;
 | |
| 		}
 | |
| 		entry->cmdtype = CMDNORMAL;
 | |
| 		entry->u.index = 0;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	updatetbl = 1;
 | |
| 	if (act & DO_BRUTE) {
 | |
| 		firstchange = path_change(path, &bltin);
 | |
| 	} else {
 | |
| 		bltin = builtinloc;
 | |
| 		firstchange = 9999;
 | |
| 	}
 | |
| 
 | |
| 	/* If name is in the table, and not invalidated by cd, we're done */
 | |
| 	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0) {
 | |
| 		if (cmdp->cmdtype == CMDFUNCTION) {
 | |
| 			if (act & DO_NOFUN) {
 | |
| 				updatetbl = 0;
 | |
| 			} else {
 | |
| 				goto success;
 | |
| 			}
 | |
| 		} else if (act & DO_BRUTE) {
 | |
| 			if ((cmdp->cmdtype == CMDNORMAL &&
 | |
| 			     cmdp->param.index >= firstchange) ||
 | |
| 			    (cmdp->cmdtype == CMDBUILTIN &&
 | |
| 			     ((builtinloc < 0 && bltin >= 0) ?
 | |
| 			      bltin : builtinloc) >= firstchange)) {
 | |
| 				/* need to recompute the entry */
 | |
| 			} else {
 | |
| 				goto success;
 | |
| 			}
 | |
| 		} else {
 | |
| 			goto success;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	bcmd = find_builtin(name);
 | |
| 	regular = bcmd && IS_BUILTIN_REGULAR(bcmd);
 | |
| 
 | |
| 	if (regular) {
 | |
| 		if (cmdp && (cmdp->cmdtype == CMDBUILTIN)) {
 | |
| 			goto success;
 | |
| 		}
 | |
| 	} else if (act & DO_BRUTE) {
 | |
| 		if (firstchange == 0) {
 | |
| 			updatetbl = 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* If %builtin not in path, check for builtin next */
 | |
| 	if (regular || (bltin < 0 && bcmd)) {
 | |
| builtin:
 | |
| 		if (!updatetbl) {
 | |
| 			entry->cmdtype = CMDBUILTIN;
 | |
| 			entry->u.cmd = bcmd;
 | |
| 			return;
 | |
| 		}
 | |
| 		INTOFF;
 | |
| 		cmdp = cmdlookup(name, 1);
 | |
| 		cmdp->cmdtype = CMDBUILTIN;
 | |
| 		cmdp->param.cmd = bcmd;
 | |
| 		INTON;
 | |
| 		goto success;
 | |
| 	}
 | |
| 
 | |
| 	/* We have to search path. */
 | |
| 	prev = -1;              /* where to start */
 | |
| 	if (cmdp && cmdp->rehash) {     /* doing a rehash */
 | |
| 		if (cmdp->cmdtype == CMDBUILTIN)
 | |
| 			prev = builtinloc;
 | |
| 		else
 | |
| 			prev = cmdp->param.index;
 | |
| 	}
 | |
| 
 | |
| 	e = ENOENT;
 | |
| 	idx = -1;
 | |
| loop:
 | |
| 	while ((fullname = padvance(&path, name)) != NULL) {
 | |
| 		stunalloc(fullname);
 | |
| 		idx++;
 | |
| 		if (idx >= firstchange) {
 | |
| 			updatetbl = 0;
 | |
| 		}
 | |
| 		if (pathopt) {
 | |
| 			if (prefix("builtin", pathopt)) {
 | |
| 				if ((bcmd = find_builtin(name))) {
 | |
| 					goto builtin;
 | |
| 				}
 | |
| 				continue;
 | |
| 			} else if (!(act & DO_NOFUN) &&
 | |
| 				   prefix("func", pathopt)) {
 | |
| 				/* handled below */
 | |
| 			} else {
 | |
| 				continue;       /* ignore unimplemented options */
 | |
| 			}
 | |
| 		}
 | |
| 		/* if rehash, don't redo absolute path names */
 | |
| 		if (fullname[0] == '/' && idx <= prev &&
 | |
| 		    idx < firstchange) {
 | |
| 			if (idx < prev)
 | |
| 				continue;
 | |
| 			TRACE(("searchexec \"%s\": no change\n", name));
 | |
| 			goto success;
 | |
| 		}
 | |
| 		while (stat(fullname, &statb) < 0) {
 | |
| 			if (errno != ENOENT && errno != ENOTDIR)
 | |
| 				e = errno;
 | |
| 			goto loop;
 | |
| 		}
 | |
| 		e = EACCES;     /* if we fail, this will be the error */
 | |
| 		if (!S_ISREG(statb.st_mode))
 | |
| 			continue;
 | |
| 		if (pathopt) {          /* this is a %func directory */
 | |
| 			stalloc(strlen(fullname) + 1);
 | |
| 			readcmdfile(fullname);
 | |
| 			if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
 | |
| 				error("%s not defined in %s", name, fullname);
 | |
| 			stunalloc(fullname);
 | |
| 			goto success;
 | |
| 		}
 | |
| 		TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
 | |
| 		/* If we aren't called with DO_BRUTE and cmdp is set, it must
 | |
| 		   be a function and we're being called with DO_NOFUN */
 | |
| 		if (!updatetbl) {
 | |
| 			entry->cmdtype = CMDNORMAL;
 | |
| 			entry->u.index = idx;
 | |
| 			return;
 | |
| 		}
 | |
| 		INTOFF;
 | |
| 		cmdp = cmdlookup(name, 1);
 | |
| 		cmdp->cmdtype = CMDNORMAL;
 | |
| 		cmdp->param.index = idx;
 | |
| 		INTON;
 | |
| 		goto success;
 | |
| 	}
 | |
| 
 | |
| 	/* We failed.  If there was an entry for this command, delete it */
 | |
| 	if (cmdp && updatetbl)
 | |
| 		delete_cmd_entry();
 | |
| 	if (act & DO_ERR)
 | |
| 		out2fmt("%s: %s\n", name, errmsg(e, E_EXEC));
 | |
| 	entry->cmdtype = CMDUNKNOWN;
 | |
| 	return;
 | |
| 
 | |
| success:
 | |
| 	cmdp->rehash = 0;
 | |
| 	entry->cmdtype = cmdp->cmdtype;
 | |
| 	entry->u = cmdp->param;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Search the table of builtin commands.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| bstrcmp(const void *name, const void *b)
 | |
| {
 | |
| 	return strcmp((const char *)name, (*(const char *const *) b)+1);
 | |
| }
 | |
| 
 | |
| static struct builtincmd *
 | |
| find_builtin(const char *name)
 | |
| {
 | |
| 	struct builtincmd *bp;
 | |
| 
 | |
| 	bp = bsearch(name, builtincmds, NUMBUILTINS, sizeof(struct builtincmd),
 | |
| 		bstrcmp
 | |
| 	);
 | |
| 	return bp;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called when a cd is done.  Marks all commands so the next time they
 | |
|  * are executed they will be rehashed.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| hashcd(void) {
 | |
| 	struct tblentry **pp;
 | |
| 	struct tblentry *cmdp;
 | |
| 
 | |
| 	for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
 | |
| 		for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
 | |
| 			if (cmdp->cmdtype == CMDNORMAL
 | |
| 			 || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))
 | |
| 				cmdp->rehash = 1;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called before PATH is changed.  The argument is the new value of PATH;
 | |
|  * pathval() still returns the old value at this point.  Called with
 | |
|  * interrupts off.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| changepath(const char *newval)
 | |
| {
 | |
| 	int firstchange;
 | |
| 	int bltin;
 | |
| 
 | |
| 	firstchange = path_change(newval, &bltin);
 | |
| 	if (builtinloc < 0 && bltin >= 0)
 | |
| 		builtinloc = bltin;             /* zap builtins */
 | |
| 	clearcmdentry(firstchange);
 | |
| 	builtinloc = bltin;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Clear out command entries.  The argument specifies the first entry in
 | |
|  * PATH which has changed.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| clearcmdentry(firstchange)
 | |
| 	int firstchange;
 | |
| {
 | |
| 	struct tblentry **tblp;
 | |
| 	struct tblentry **pp;
 | |
| 	struct tblentry *cmdp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
 | |
| 		pp = tblp;
 | |
| 		while ((cmdp = *pp) != NULL) {
 | |
| 			if ((cmdp->cmdtype == CMDNORMAL &&
 | |
| 			     cmdp->param.index >= firstchange)
 | |
| 			 || (cmdp->cmdtype == CMDBUILTIN &&
 | |
| 			     builtinloc >= firstchange)) {
 | |
| 				*pp = cmdp->next;
 | |
| 				ckfree(cmdp);
 | |
| 			} else {
 | |
| 				pp = &cmdp->next;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Delete all functions.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| deletefuncs(void) {
 | |
| 	struct tblentry **tblp;
 | |
| 	struct tblentry **pp;
 | |
| 	struct tblentry *cmdp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
 | |
| 		pp = tblp;
 | |
| 		while ((cmdp = *pp) != NULL) {
 | |
| 			if (cmdp->cmdtype == CMDFUNCTION) {
 | |
| 				*pp = cmdp->next;
 | |
| 				freefunc(cmdp->param.func);
 | |
| 				ckfree(cmdp);
 | |
| 			} else {
 | |
| 				pp = &cmdp->next;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Locate a command in the command hash table.  If "add" is nonzero,
 | |
|  * add the command to the table if it is not already present.  The
 | |
|  * variable "lastcmdentry" is set to point to the address of the link
 | |
|  * pointing to the entry, so that delete_cmd_entry can delete the
 | |
|  * entry.
 | |
|  */
 | |
| 
 | |
| static struct tblentry **lastcmdentry;
 | |
| 
 | |
| static struct tblentry *
 | |
| cmdlookup(const char *name, int add)
 | |
| {
 | |
| 	int hashval;
 | |
| 	const char *p;
 | |
| 	struct tblentry *cmdp;
 | |
| 	struct tblentry **pp;
 | |
| 
 | |
| 	p = name;
 | |
| 	hashval = *p << 4;
 | |
| 	while (*p)
 | |
| 		hashval += *p++;
 | |
| 	hashval &= 0x7FFF;
 | |
| 	pp = &cmdtable[hashval % CMDTABLESIZE];
 | |
| 	for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
 | |
| 		if (equal(cmdp->cmdname, name))
 | |
| 			break;
 | |
| 		pp = &cmdp->next;
 | |
| 	}
 | |
| 	if (add && cmdp == NULL) {
 | |
| 		INTOFF;
 | |
| 		cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
 | |
| 					+ strlen(name) + 1);
 | |
| 		cmdp->next = NULL;
 | |
| 		cmdp->cmdtype = CMDUNKNOWN;
 | |
| 		cmdp->rehash = 0;
 | |
| 		strcpy(cmdp->cmdname, name);
 | |
| 		INTON;
 | |
| 	}
 | |
| 	lastcmdentry = pp;
 | |
| 	return cmdp;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Delete the command entry returned on the last lookup.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| delete_cmd_entry() {
 | |
| 	struct tblentry *cmdp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	cmdp = *lastcmdentry;
 | |
| 	*lastcmdentry = cmdp->next;
 | |
| 	ckfree(cmdp);
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| static const unsigned char nodesize[26] = {
 | |
|       ALIGN(sizeof (struct nbinary)),
 | |
|       ALIGN(sizeof (struct ncmd)),
 | |
|       ALIGN(sizeof (struct npipe)),
 | |
|       ALIGN(sizeof (struct nredir)),
 | |
|       ALIGN(sizeof (struct nredir)),
 | |
|       ALIGN(sizeof (struct nredir)),
 | |
|       ALIGN(sizeof (struct nbinary)),
 | |
|       ALIGN(sizeof (struct nbinary)),
 | |
|       ALIGN(sizeof (struct nif)),
 | |
|       ALIGN(sizeof (struct nbinary)),
 | |
|       ALIGN(sizeof (struct nbinary)),
 | |
|       ALIGN(sizeof (struct nfor)),
 | |
|       ALIGN(sizeof (struct ncase)),
 | |
|       ALIGN(sizeof (struct nclist)),
 | |
|       ALIGN(sizeof (struct narg)),
 | |
|       ALIGN(sizeof (struct narg)),
 | |
|       ALIGN(sizeof (struct nfile)),
 | |
|       ALIGN(sizeof (struct nfile)),
 | |
|       ALIGN(sizeof (struct nfile)),
 | |
|       ALIGN(sizeof (struct nfile)),
 | |
|       ALIGN(sizeof (struct nfile)),
 | |
|       ALIGN(sizeof (struct ndup)),
 | |
|       ALIGN(sizeof (struct ndup)),
 | |
|       ALIGN(sizeof (struct nhere)),
 | |
|       ALIGN(sizeof (struct nhere)),
 | |
|       ALIGN(sizeof (struct nnot)),
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Delete a function if it exists.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| unsetfunc(char *name)
 | |
| {
 | |
| 	struct tblentry *cmdp;
 | |
| 
 | |
| 	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
 | |
| 		freefunc(cmdp->param.func);
 | |
| 		delete_cmd_entry();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Locate and print what a word is...
 | |
|  */
 | |
| 
 | |
| static int
 | |
| typecmd(int argc, char **argv)
 | |
| {
 | |
| 	int i;
 | |
| 	int err = 0;
 | |
| 	char *argv_a[2];
 | |
| 
 | |
| 	argv_a[1] = 0;
 | |
| 
 | |
| 	for (i = 1; i < argc; i++) {
 | |
| 		argv_a[0] = argv[i];
 | |
| 		argptr = argv_a;
 | |
| 		optptr = "v";
 | |
| 		err |= hashcmd(argc, argv);
 | |
| 	}
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| #ifdef ASH_CMDCMD
 | |
| static int
 | |
| commandcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	int c;
 | |
| 	int default_path = 0;
 | |
| 	int verify_only = 0;
 | |
| 	int verbose_verify_only = 0;
 | |
| 
 | |
| 	while ((c = nextopt("pvV")) != '\0')
 | |
| 		switch (c) {
 | |
| 		case 'p':
 | |
| 			default_path = 1;
 | |
| 			break;
 | |
| 		case 'v':
 | |
| 			verify_only = 1;
 | |
| 			break;
 | |
| 		case 'V':
 | |
| 			verbose_verify_only = 1;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 	if (default_path + verify_only + verbose_verify_only > 1 ||
 | |
| 	    !*argptr) {
 | |
| 			out2str(
 | |
| 				"command [-p] command [arg ...]\n"
 | |
| 				"command {-v|-V} command\n");
 | |
| 			return EX_USAGE;
 | |
| 	}
 | |
| 
 | |
| 	if (verify_only || verbose_verify_only) {
 | |
| 		char *argv_a[2];
 | |
| 
 | |
| 		argv_a[1] = 0;
 | |
| 		argv_a[0] = *argptr;
 | |
| 		argptr = argv_a;
 | |
| 		optptr = verbose_verify_only ? "v" : "V"; /* reverse special */
 | |
| 		return hashcmd(argc, argv);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static int
 | |
| path_change(newval, bltin)
 | |
| 	const char *newval;
 | |
| 	int *bltin;
 | |
| {
 | |
| 	const char *old, *new;
 | |
| 	int idx;
 | |
| 	int firstchange;
 | |
| 
 | |
| 	old = pathval();
 | |
| 	new = newval;
 | |
| 	firstchange = 9999;     /* assume no change */
 | |
| 	idx = 0;
 | |
| 	*bltin = -1;
 | |
| 	for (;;) {
 | |
| 		if (*old != *new) {
 | |
| 			firstchange = idx;
 | |
| 			if ((*old == '\0' && *new == ':')
 | |
| 			 || (*old == ':' && *new == '\0'))
 | |
| 				firstchange++;
 | |
| 			old = new;      /* ignore subsequent differences */
 | |
| 		}
 | |
| 		if (*new == '\0')
 | |
| 			break;
 | |
| 		if (*new == '%' && *bltin < 0 && prefix("builtin", new + 1))
 | |
| 			*bltin = idx;
 | |
| 		if (*new == ':') {
 | |
| 			idx++;
 | |
| 		}
 | |
| 		new++, old++;
 | |
| 	}
 | |
| 	if (builtinloc >= 0 && *bltin < 0)
 | |
| 		firstchange = 0;
 | |
| 	return firstchange;
 | |
| }
 | |
| /*
 | |
|  * Routines to expand arguments to commands.  We have to deal with
 | |
|  * backquotes, shell variables, and file metacharacters.
 | |
|  */
 | |
| /*
 | |
|  * _rmescape() flags
 | |
|  */
 | |
| #define RMESCAPE_ALLOC  0x1     /* Allocate a new string */
 | |
| #define RMESCAPE_GLOB   0x2     /* Add backslashes for glob */
 | |
| 
 | |
| /*
 | |
|  * Structure specifying which parts of the string should be searched
 | |
|  * for IFS characters.
 | |
|  */
 | |
| 
 | |
| struct ifsregion {
 | |
| 	struct ifsregion *next; /* next region in list */
 | |
| 	int begoff;             /* offset of start of region */
 | |
| 	int endoff;             /* offset of end of region */
 | |
| 	int nulonly;            /* search for nul bytes only */
 | |
| };
 | |
| 
 | |
| 
 | |
| static char *expdest;                   /* output of current string */
 | |
| static struct nodelist *argbackq;      /* list of back quote expressions */
 | |
| static struct ifsregion ifsfirst;      /* first struct in list of ifs regions */
 | |
| static struct ifsregion *ifslastp;     /* last struct in list */
 | |
| static struct arglist exparg;          /* holds expanded arg list */
 | |
| 
 | |
| static void argstr (char *, int);
 | |
| static char *exptilde (char *, int);
 | |
| static void expbackq (union node *, int, int);
 | |
| static int subevalvar (char *, char *, int, int, int, int, int);
 | |
| static int varisset (char *, int);
 | |
| static void strtodest (const char *, int, int);
 | |
| static void varvalue (char *, int, int);
 | |
| static void recordregion (int, int, int);
 | |
| static void removerecordregions (int);
 | |
| static void ifsbreakup (char *, struct arglist *);
 | |
| static void ifsfree (void);
 | |
| static void expandmeta (struct strlist *, int);
 | |
| #if defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN)
 | |
| #define preglob(p) _rmescapes((p), RMESCAPE_ALLOC | RMESCAPE_GLOB)
 | |
| #if !defined(GLOB_BROKEN)
 | |
| static void addglob (const glob_t *);
 | |
| #endif
 | |
| #endif
 | |
| #if !(defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
 | |
| static void expmeta (char *, char *);
 | |
| #endif
 | |
| #if !(defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
 | |
| static struct strlist *expsort (struct strlist *);
 | |
| static struct strlist *msort (struct strlist *, int);
 | |
| #endif
 | |
| static int patmatch (char *, char *, int);
 | |
| #if defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN)
 | |
| static int patmatch2 (char *, char *, int);
 | |
| #else
 | |
| static int pmatch (char *, char *, int);
 | |
| #define patmatch2 patmatch
 | |
| #endif
 | |
| static char *cvtnum (int, char *);
 | |
| 
 | |
| /*
 | |
|  * Expand shell variables and backquotes inside a here document.
 | |
|  */
 | |
| 
 | |
| /* arg: the document, fd: where to write the expanded version */
 | |
| static inline void
 | |
| expandhere(union node *arg, int fd)
 | |
| {
 | |
| 	herefd = fd;
 | |
| 	expandarg(arg, (struct arglist *)NULL, 0);
 | |
| 	xwrite(fd, stackblock(), expdest - stackblock());
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Perform variable substitution and command substitution on an argument,
 | |
|  * placing the resulting list of arguments in arglist.  If EXP_FULL is true,
 | |
|  * perform splitting and file name expansion.  When arglist is NULL, perform
 | |
|  * here document expansion.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| expandarg(arg, arglist, flag)
 | |
| 	union node *arg;
 | |
| 	struct arglist *arglist;
 | |
| 	int flag;
 | |
| {
 | |
| 	struct strlist *sp;
 | |
| 	char *p;
 | |
| 
 | |
| 	argbackq = arg->narg.backquote;
 | |
| 	STARTSTACKSTR(expdest);
 | |
| 	ifsfirst.next = NULL;
 | |
| 	ifslastp = NULL;
 | |
| 	argstr(arg->narg.text, flag);
 | |
| 	if (arglist == NULL) {
 | |
| 		return;                 /* here document expanded */
 | |
| 	}
 | |
| 	STPUTC('\0', expdest);
 | |
| 	p = grabstackstr(expdest);
 | |
| 	exparg.lastp = &exparg.list;
 | |
| 	/*
 | |
| 	 * TODO - EXP_REDIR
 | |
| 	 */
 | |
| 	if (flag & EXP_FULL) {
 | |
| 		ifsbreakup(p, &exparg);
 | |
| 		*exparg.lastp = NULL;
 | |
| 		exparg.lastp = &exparg.list;
 | |
| 		expandmeta(exparg.list, flag);
 | |
| 	} else {
 | |
| 		if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
 | |
| 			rmescapes(p);
 | |
| 		sp = (struct strlist *)stalloc(sizeof (struct strlist));
 | |
| 		sp->text = p;
 | |
| 		*exparg.lastp = sp;
 | |
| 		exparg.lastp = &sp->next;
 | |
| 	}
 | |
| 	ifsfree();
 | |
| 	*exparg.lastp = NULL;
 | |
| 	if (exparg.list) {
 | |
| 		*arglist->lastp = exparg.list;
 | |
| 		arglist->lastp = exparg.lastp;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Expand a variable, and return a pointer to the next character in the
 | |
|  * input string.
 | |
|  */
 | |
| 
 | |
| static inline char * evalvar(char *p, int flag)
 | |
| {
 | |
| 	int subtype;
 | |
| 	int varflags;
 | |
| 	char *var;
 | |
| 	const char *val;
 | |
| 	int patloc;
 | |
| 	int c;
 | |
| 	int set;
 | |
| 	int special;
 | |
| 	int startloc;
 | |
| 	int varlen;
 | |
| 	int easy;
 | |
| 	int quotes = flag & (EXP_FULL | EXP_CASE);
 | |
| 
 | |
| 	varflags = *p++;
 | |
| 	subtype = varflags & VSTYPE;
 | |
| 	var = p;
 | |
| 	special = 0;
 | |
| 	if (! is_name(*p))
 | |
| 		special = 1;
 | |
| 	p = strchr(p, '=') + 1;
 | |
| again: /* jump here after setting a variable with ${var=text} */
 | |
| 	if (special) {
 | |
| 		set = varisset(var, varflags & VSNUL);
 | |
| 		val = NULL;
 | |
| 	} else {
 | |
| 		val = lookupvar(var);
 | |
| 		if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
 | |
| 			val = NULL;
 | |
| 			set = 0;
 | |
| 		} else
 | |
| 			set = 1;
 | |
| 	}
 | |
| 	varlen = 0;
 | |
| 	startloc = expdest - stackblock();
 | |
| 	if (set && subtype != VSPLUS) {
 | |
| 		/* insert the value of the variable */
 | |
| 		if (special) {
 | |
| 			varvalue(var, varflags & VSQUOTE, flag);
 | |
| 			if (subtype == VSLENGTH) {
 | |
| 				varlen = expdest - stackblock() - startloc;
 | |
| 				STADJUST(-varlen, expdest);
 | |
| 			}
 | |
| 		} else {
 | |
| 			if (subtype == VSLENGTH) {
 | |
| 				varlen = strlen(val);
 | |
| 			} else {
 | |
| 				strtodest(
 | |
| 					val,
 | |
| 					varflags & VSQUOTE ?
 | |
| 						DQSYNTAX : BASESYNTAX,
 | |
| 					quotes
 | |
| 				);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (subtype == VSPLUS)
 | |
| 		set = ! set;
 | |
| 
 | |
| 	easy = ((varflags & VSQUOTE) == 0 ||
 | |
| 		(*var == '@' && shellparam.nparam != 1));
 | |
| 
 | |
| 
 | |
| 	switch (subtype) {
 | |
| 	case VSLENGTH:
 | |
| 		expdest = cvtnum(varlen, expdest);
 | |
| 		goto record;
 | |
| 
 | |
| 	case VSNORMAL:
 | |
| 		if (!easy)
 | |
| 			break;
 | |
| record:
 | |
| 		recordregion(startloc, expdest - stackblock(),
 | |
| 			     varflags & VSQUOTE);
 | |
| 		break;
 | |
| 
 | |
| 	case VSPLUS:
 | |
| 	case VSMINUS:
 | |
| 		if (!set) {
 | |
| 			argstr(p, flag);
 | |
| 			break;
 | |
| 		}
 | |
| 		if (easy)
 | |
| 			goto record;
 | |
| 		break;
 | |
| 
 | |
| 	case VSTRIMLEFT:
 | |
| 	case VSTRIMLEFTMAX:
 | |
| 	case VSTRIMRIGHT:
 | |
| 	case VSTRIMRIGHTMAX:
 | |
| 		if (!set)
 | |
| 			break;
 | |
| 		/*
 | |
| 		 * Terminate the string and start recording the pattern
 | |
| 		 * right after it
 | |
| 		 */
 | |
| 		STPUTC('\0', expdest);
 | |
| 		patloc = expdest - stackblock();
 | |
| 		if (subevalvar(p, NULL, patloc, subtype,
 | |
| 			       startloc, varflags, quotes) == 0) {
 | |
| 			int amount = (expdest - stackblock() - patloc) + 1;
 | |
| 			STADJUST(-amount, expdest);
 | |
| 		}
 | |
| 		/* Remove any recorded regions beyond start of variable */
 | |
| 		removerecordregions(startloc);
 | |
| 		goto record;
 | |
| 
 | |
| 	case VSASSIGN:
 | |
| 	case VSQUESTION:
 | |
| 		if (!set) {
 | |
| 			if (subevalvar(p, var, 0, subtype, startloc,
 | |
| 				       varflags, quotes)) {
 | |
| 				varflags &= ~VSNUL;
 | |
| 				/*
 | |
| 				 * Remove any recorded regions beyond
 | |
| 				 * start of variable
 | |
| 				 */
 | |
| 				removerecordregions(startloc);
 | |
| 				goto again;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		if (easy)
 | |
| 			goto record;
 | |
| 		break;
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 	default:
 | |
| 		abort();
 | |
| #endif
 | |
| 	}
 | |
| 
 | |
| 	if (subtype != VSNORMAL) {      /* skip to end of alternative */
 | |
| 		int nesting = 1;
 | |
| 		for (;;) {
 | |
| 			if ((c = *p++) == CTLESC)
 | |
| 				p++;
 | |
| 			else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
 | |
| 				if (set)
 | |
| 					argbackq = argbackq->next;
 | |
| 			} else if (c == CTLVAR) {
 | |
| 				if ((*p++ & VSTYPE) != VSNORMAL)
 | |
| 					nesting++;
 | |
| 			} else if (c == CTLENDVAR) {
 | |
| 				if (--nesting == 0)
 | |
| 					break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return p;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
 | |
|  * characters to allow for further processing.  Otherwise treat
 | |
|  * $@ like $* since no splitting will be performed.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| argstr(p, flag)
 | |
| 	char *p;
 | |
| 	int flag;
 | |
| {
 | |
| 	char c;
 | |
| 	int quotes = flag & (EXP_FULL | EXP_CASE);      /* do CTLESC */
 | |
| 	int firsteq = 1;
 | |
| 
 | |
| 	if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
 | |
| 		p = exptilde(p, flag);
 | |
| 	for (;;) {
 | |
| 		switch (c = *p++) {
 | |
| 		case '\0':
 | |
| 		case CTLENDVAR: /* ??? */
 | |
| 			goto breakloop;
 | |
| 		case CTLQUOTEMARK:
 | |
| 			/* "$@" syntax adherence hack */
 | |
| 			if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
 | |
| 				break;
 | |
| 			if ((flag & EXP_FULL) != 0)
 | |
| 				STPUTC(c, expdest);
 | |
| 			break;
 | |
| 		case CTLESC:
 | |
| 			if (quotes)
 | |
| 				STPUTC(c, expdest);
 | |
| 			c = *p++;
 | |
| 			STPUTC(c, expdest);
 | |
| 			break;
 | |
| 		case CTLVAR:
 | |
| 			p = evalvar(p, flag);
 | |
| 			break;
 | |
| 		case CTLBACKQ:
 | |
| 		case CTLBACKQ|CTLQUOTE:
 | |
| 			expbackq(argbackq->n, c & CTLQUOTE, flag);
 | |
| 			argbackq = argbackq->next;
 | |
| 			break;
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| 		case CTLENDARI:
 | |
| 			expari(flag);
 | |
| 			break;
 | |
| #endif
 | |
| 		case ':':
 | |
| 		case '=':
 | |
| 			/*
 | |
| 			 * sort of a hack - expand tildes in variable
 | |
| 			 * assignments (after the first '=' and after ':'s).
 | |
| 			 */
 | |
| 			STPUTC(c, expdest);
 | |
| 			if (flag & EXP_VARTILDE && *p == '~') {
 | |
| 				if (c == '=') {
 | |
| 					if (firsteq)
 | |
| 						firsteq = 0;
 | |
| 					else
 | |
| 						break;
 | |
| 				}
 | |
| 				p = exptilde(p, flag);
 | |
| 			}
 | |
| 			break;
 | |
| 		default:
 | |
| 			STPUTC(c, expdest);
 | |
| 		}
 | |
| 	}
 | |
| breakloop:;
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| static char *
 | |
| exptilde(p, flag)
 | |
| 	char *p;
 | |
| 	int flag;
 | |
| {
 | |
| 	char c, *startp = p;
 | |
| 	struct passwd *pw;
 | |
| 	const char *home;
 | |
| 	int quotes = flag & (EXP_FULL | EXP_CASE);
 | |
| 
 | |
| 	while ((c = *p) != '\0') {
 | |
| 		switch(c) {
 | |
| 		case CTLESC:
 | |
| 			return (startp);
 | |
| 		case CTLQUOTEMARK:
 | |
| 			return (startp);
 | |
| 		case ':':
 | |
| 			if (flag & EXP_VARTILDE)
 | |
| 				goto done;
 | |
| 			break;
 | |
| 		case '/':
 | |
| 			goto done;
 | |
| 		}
 | |
| 		p++;
 | |
| 	}
 | |
| done:
 | |
| 	*p = '\0';
 | |
| 	if (*(startp+1) == '\0') {
 | |
| 		if ((home = lookupvar("HOME")) == NULL)
 | |
| 			goto lose;
 | |
| 	} else {
 | |
| 		if ((pw = getpwnam(startp+1)) == NULL)
 | |
| 			goto lose;
 | |
| 		home = pw->pw_dir;
 | |
| 	}
 | |
| 	if (*home == '\0')
 | |
| 		goto lose;
 | |
| 	*p = c;
 | |
| 	strtodest(home, SQSYNTAX, quotes);
 | |
| 	return (p);
 | |
| lose:
 | |
| 	*p = c;
 | |
| 	return (startp);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| removerecordregions(int endoff)
 | |
| {
 | |
| 	if (ifslastp == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	if (ifsfirst.endoff > endoff) {
 | |
| 		while (ifsfirst.next != NULL) {
 | |
| 			struct ifsregion *ifsp;
 | |
| 			INTOFF;
 | |
| 			ifsp = ifsfirst.next->next;
 | |
| 			ckfree(ifsfirst.next);
 | |
| 			ifsfirst.next = ifsp;
 | |
| 			INTON;
 | |
| 		}
 | |
| 		if (ifsfirst.begoff > endoff)
 | |
| 			ifslastp = NULL;
 | |
| 		else {
 | |
| 			ifslastp = &ifsfirst;
 | |
| 			ifsfirst.endoff = endoff;
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	ifslastp = &ifsfirst;
 | |
| 	while (ifslastp->next && ifslastp->next->begoff < endoff)
 | |
| 		ifslastp=ifslastp->next;
 | |
| 	while (ifslastp->next != NULL) {
 | |
| 		struct ifsregion *ifsp;
 | |
| 		INTOFF;
 | |
| 		ifsp = ifslastp->next->next;
 | |
| 		ckfree(ifslastp->next);
 | |
| 		ifslastp->next = ifsp;
 | |
| 		INTON;
 | |
| 	}
 | |
| 	if (ifslastp->endoff > endoff)
 | |
| 		ifslastp->endoff = endoff;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| /*
 | |
|  * Expand arithmetic expression.  Backup to start of expression,
 | |
|  * evaluate, place result in (backed up) result, adjust string position.
 | |
|  */
 | |
| static void
 | |
| expari(int flag)
 | |
| {
 | |
| 	char *p, *start;
 | |
| 	int errcode;
 | |
| 	int result;
 | |
| 	int begoff;
 | |
| 	int quotes = flag & (EXP_FULL | EXP_CASE);
 | |
| 	int quoted;
 | |
| 
 | |
| 	/*      ifsfree(); */
 | |
| 
 | |
| 	/*
 | |
| 	 * This routine is slightly over-complicated for
 | |
| 	 * efficiency.  First we make sure there is
 | |
| 	 * enough space for the result, which may be bigger
 | |
| 	 * than the expression if we add exponentation.  Next we
 | |
| 	 * scan backwards looking for the start of arithmetic.  If the
 | |
| 	 * next previous character is a CTLESC character, then we
 | |
| 	 * have to rescan starting from the beginning since CTLESC
 | |
| 	 * characters have to be processed left to right.
 | |
| 	 */
 | |
| 	CHECKSTRSPACE(10, expdest);
 | |
| 	USTPUTC('\0', expdest);
 | |
| 	start = stackblock();
 | |
| 	p = expdest - 1;
 | |
| 	while (*p != CTLARI && p >= start)
 | |
| 		--p;
 | |
| 	if (*p != CTLARI)
 | |
| 		error("missing CTLARI (shouldn't happen)");
 | |
| 	if (p > start && *(p-1) == CTLESC)
 | |
| 		for (p = start; *p != CTLARI; p++)
 | |
| 			if (*p == CTLESC)
 | |
| 				p++;
 | |
| 
 | |
| 	if (p[1] == '"')
 | |
| 		quoted=1;
 | |
| 	else
 | |
| 		quoted=0;
 | |
| 	begoff = p - start;
 | |
| 	removerecordregions(begoff);
 | |
| 	if (quotes)
 | |
| 		rmescapes(p+2);
 | |
| 	result = arith(p+2, &errcode);
 | |
| 	if (errcode < 0) {
 | |
| 		if(errcode == -2)
 | |
| 			error("divide by zero");
 | |
| 		else
 | |
| 			error("syntax error: \"%s\"\n", p+2);
 | |
| 	}
 | |
| 	snprintf(p, 12, "%d", result);
 | |
| 
 | |
| 	while (*p++)
 | |
| 		;
 | |
| 
 | |
| 	if (quoted == 0)
 | |
| 		recordregion(begoff, p - 1 - start, 0);
 | |
| 	result = expdest - p + 1;
 | |
| 	STADJUST(-result, expdest);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Expand stuff in backwards quotes.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| expbackq(cmd, quoted, flag)
 | |
| 	union node *cmd;
 | |
| 	int quoted;
 | |
| 	int flag;
 | |
| {
 | |
| 	volatile struct backcmd in;
 | |
| 	int i;
 | |
| 	char buf[128];
 | |
| 	char *p;
 | |
| 	char *dest = expdest;
 | |
| 	volatile struct ifsregion saveifs;
 | |
| 	struct ifsregion *volatile savelastp;
 | |
| 	struct nodelist *volatile saveargbackq;
 | |
| 	char lastc;
 | |
| 	int startloc = dest - stackblock();
 | |
| 	int syntax = quoted ? DQSYNTAX : BASESYNTAX;
 | |
| 	volatile int saveherefd;
 | |
| 	int quotes = flag & (EXP_FULL | EXP_CASE);
 | |
| 	struct jmploc jmploc;
 | |
| 	struct jmploc *volatile savehandler;
 | |
| 	int ex;
 | |
| 
 | |
| #if __GNUC__
 | |
| 	/* Avoid longjmp clobbering */
 | |
| 	(void) &dest;
 | |
| 	(void) &syntax;
 | |
| #endif
 | |
| 
 | |
| 	in.fd = -1;
 | |
| 	in.buf = 0;
 | |
| 	in.jp = 0;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	saveifs = ifsfirst;
 | |
| 	savelastp = ifslastp;
 | |
| 	saveargbackq = argbackq;
 | |
| 	saveherefd = herefd;
 | |
| 	herefd = -1;
 | |
| 	if ((ex = setjmp(jmploc.loc))) {
 | |
| 		goto err1;
 | |
| 	}
 | |
| 	savehandler = handler;
 | |
| 	handler = &jmploc;
 | |
| 	INTON;
 | |
| 	p = grabstackstr(dest);
 | |
| 	evalbackcmd(cmd, (struct backcmd *) &in);
 | |
| 	ungrabstackstr(p, dest);
 | |
| err1:
 | |
| 	INTOFF;
 | |
| 	ifsfirst = saveifs;
 | |
| 	ifslastp = savelastp;
 | |
| 	argbackq = saveargbackq;
 | |
| 	herefd = saveherefd;
 | |
| 	if (ex) {
 | |
| 		goto err2;
 | |
| 	}
 | |
| 
 | |
| 	p = in.buf;
 | |
| 	lastc = '\0';
 | |
| 	for (;;) {
 | |
| 		if (--in.nleft < 0) {
 | |
| 			if (in.fd < 0)
 | |
| 				break;
 | |
| 			i = safe_read(in.fd, buf, sizeof buf);
 | |
| 			TRACE(("expbackq: read returns %d\n", i));
 | |
| 			if (i <= 0)
 | |
| 				break;
 | |
| 			p = buf;
 | |
| 			in.nleft = i - 1;
 | |
| 		}
 | |
| 		lastc = *p++;
 | |
| 		if (lastc != '\0') {
 | |
| 			if (quotes && SIT(lastc, syntax) == CCTL)
 | |
| 				STPUTC(CTLESC, dest);
 | |
| 			STPUTC(lastc, dest);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Eat all trailing newlines */
 | |
| 	for (; dest > stackblock() && dest[-1] == '\n';)
 | |
| 		STUNPUTC(dest);
 | |
| 
 | |
| err2:
 | |
| 	if (in.fd >= 0)
 | |
| 		close(in.fd);
 | |
| 	if (in.buf)
 | |
| 		ckfree(in.buf);
 | |
| 	if (in.jp)
 | |
| 		exitstatus = waitforjob(in.jp);
 | |
| 	handler = savehandler;
 | |
| 	if (ex) {
 | |
| 		longjmp(handler->loc, 1);
 | |
| 	}
 | |
| 	if (quoted == 0)
 | |
| 		recordregion(startloc, dest - stackblock(), 0);
 | |
| 	TRACE(("evalbackq: size=%d: \"%.*s\"\n",
 | |
| 		(dest - stackblock()) - startloc,
 | |
| 		(dest - stackblock()) - startloc,
 | |
| 		stackblock() + startloc));
 | |
| 	expdest = dest;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| static int
 | |
| subevalvar(p, str, strloc, subtype, startloc, varflags, quotes)
 | |
| 	char *p;
 | |
| 	char *str;
 | |
| 	int strloc;
 | |
| 	int subtype;
 | |
| 	int startloc;
 | |
| 	int varflags;
 | |
| 	int quotes;
 | |
| {
 | |
| 	char *startp;
 | |
| 	char *loc = NULL;
 | |
| 	char *q;
 | |
| 	int c = 0;
 | |
| 	int saveherefd = herefd;
 | |
| 	struct nodelist *saveargbackq = argbackq;
 | |
| 	int amount;
 | |
| 
 | |
| 	herefd = -1;
 | |
| 	argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
 | |
| 	STACKSTRNUL(expdest);
 | |
| 	herefd = saveherefd;
 | |
| 	argbackq = saveargbackq;
 | |
| 	startp = stackblock() + startloc;
 | |
| 	if (str == NULL)
 | |
| 	    str = stackblock() + strloc;
 | |
| 
 | |
| 	switch (subtype) {
 | |
| 	case VSASSIGN:
 | |
| 		setvar(str, startp, 0);
 | |
| 		amount = startp - expdest;
 | |
| 		STADJUST(amount, expdest);
 | |
| 		varflags &= ~VSNUL;
 | |
| 		if (c != 0)
 | |
| 			*loc = c;
 | |
| 		return 1;
 | |
| 
 | |
| 	case VSQUESTION:
 | |
| 		if (*p != CTLENDVAR) {
 | |
| 			out2fmt(snlfmt, startp);
 | |
| 			error((char *)NULL);
 | |
| 		}
 | |
| 		error("%.*s: parameter %snot set", p - str - 1,
 | |
| 		      str, (varflags & VSNUL) ? "null or "
 | |
| 					      : nullstr);
 | |
| 		/* NOTREACHED */
 | |
| 
 | |
| 	case VSTRIMLEFT:
 | |
| 		for (loc = startp; loc < str; loc++) {
 | |
| 			c = *loc;
 | |
| 			*loc = '\0';
 | |
| 			if (patmatch2(str, startp, quotes))
 | |
| 				goto recordleft;
 | |
| 			*loc = c;
 | |
| 			if (quotes && *loc == CTLESC)
 | |
| 				loc++;
 | |
| 		}
 | |
| 		return 0;
 | |
| 
 | |
| 	case VSTRIMLEFTMAX:
 | |
| 		for (loc = str - 1; loc >= startp;) {
 | |
| 			c = *loc;
 | |
| 			*loc = '\0';
 | |
| 			if (patmatch2(str, startp, quotes))
 | |
| 				goto recordleft;
 | |
| 			*loc = c;
 | |
| 			loc--;
 | |
| 			if (quotes && loc > startp && *(loc - 1) == CTLESC) {
 | |
| 				for (q = startp; q < loc; q++)
 | |
| 					if (*q == CTLESC)
 | |
| 						q++;
 | |
| 				if (q > loc)
 | |
| 					loc--;
 | |
| 			}
 | |
| 		}
 | |
| 		return 0;
 | |
| 
 | |
| 	case VSTRIMRIGHT:
 | |
| 		for (loc = str - 1; loc >= startp;) {
 | |
| 			if (patmatch2(str, loc, quotes))
 | |
| 				goto recordright;
 | |
| 			loc--;
 | |
| 			if (quotes && loc > startp && *(loc - 1) == CTLESC) {
 | |
| 				for (q = startp; q < loc; q++)
 | |
| 					if (*q == CTLESC)
 | |
| 						q++;
 | |
| 				if (q > loc)
 | |
| 					loc--;
 | |
| 			}
 | |
| 		}
 | |
| 		return 0;
 | |
| 
 | |
| 	case VSTRIMRIGHTMAX:
 | |
| 		for (loc = startp; loc < str - 1; loc++) {
 | |
| 			if (patmatch2(str, loc, quotes))
 | |
| 				goto recordright;
 | |
| 			if (quotes && *loc == CTLESC)
 | |
| 				loc++;
 | |
| 		}
 | |
| 		return 0;
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 	default:
 | |
| 		abort();
 | |
| #endif
 | |
| 	}
 | |
| 
 | |
| recordleft:
 | |
| 	*loc = c;
 | |
| 	amount = ((str - 1) - (loc - startp)) - expdest;
 | |
| 	STADJUST(amount, expdest);
 | |
| 	while (loc != str - 1)
 | |
| 		*startp++ = *loc++;
 | |
| 	return 1;
 | |
| 
 | |
| recordright:
 | |
| 	amount = loc - expdest;
 | |
| 	STADJUST(amount, expdest);
 | |
| 	STPUTC('\0', expdest);
 | |
| 	STADJUST(-1, expdest);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Test whether a specialized variable is set.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| varisset(name, nulok)
 | |
| 	char *name;
 | |
| 	int nulok;
 | |
| {
 | |
| 	if (*name == '!')
 | |
| 		return backgndpid != -1;
 | |
| 	else if (*name == '@' || *name == '*') {
 | |
| 		if (*shellparam.p == NULL)
 | |
| 			return 0;
 | |
| 
 | |
| 		if (nulok) {
 | |
| 			char **av;
 | |
| 
 | |
| 			for (av = shellparam.p; *av; av++)
 | |
| 				if (**av != '\0')
 | |
| 					return 1;
 | |
| 			return 0;
 | |
| 		}
 | |
| 	} else if (is_digit(*name)) {
 | |
| 		char *ap;
 | |
| 		int num = atoi(name);
 | |
| 
 | |
| 		if (num > shellparam.nparam)
 | |
| 			return 0;
 | |
| 
 | |
| 		if (num == 0)
 | |
| 			ap = arg0;
 | |
| 		else
 | |
| 			ap = shellparam.p[num - 1];
 | |
| 
 | |
| 		if (nulok && (ap == NULL || *ap == '\0'))
 | |
| 			return 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Put a string on the stack.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| strtodest(const char *p, int syntax, int quotes)
 | |
| {
 | |
| 	while (*p) {
 | |
| 		if (quotes && SIT(*p,syntax) == CCTL)
 | |
| 			STPUTC(CTLESC, expdest);
 | |
| 		STPUTC(*p++, expdest);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Add the value of a specialized variable to the stack string.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| varvalue(char *name, int quoted, int flags)
 | |
| {
 | |
| 	int num;
 | |
| 	char *p;
 | |
| 	int i;
 | |
| 	int sep;
 | |
| 	int sepq = 0;
 | |
| 	char **ap;
 | |
| 	int syntax;
 | |
| 	int allow_split = flags & EXP_FULL;
 | |
| 	int quotes = flags & (EXP_FULL | EXP_CASE);
 | |
| 
 | |
| 	syntax = quoted ? DQSYNTAX : BASESYNTAX;
 | |
| 	switch (*name) {
 | |
| 	case '$':
 | |
| 		num = rootpid;
 | |
| 		goto numvar;
 | |
| 	case '?':
 | |
| 		num = oexitstatus;
 | |
| 		goto numvar;
 | |
| 	case '#':
 | |
| 		num = shellparam.nparam;
 | |
| 		goto numvar;
 | |
| 	case '!':
 | |
| 		num = backgndpid;
 | |
| numvar:
 | |
| 		expdest = cvtnum(num, expdest);
 | |
| 		break;
 | |
| 	case '-':
 | |
| 		for (i = 0 ; i < NOPTS ; i++) {
 | |
| 			if (optent_val(i))
 | |
| 				STPUTC(optent_letter(optlist[i]), expdest);
 | |
| 		}
 | |
| 		break;
 | |
| 	case '@':
 | |
| 		if (allow_split && quoted) {
 | |
| 			sep = 1 << CHAR_BIT;
 | |
| 			goto param;
 | |
| 		}
 | |
| 		/* fall through */
 | |
| 	case '*':
 | |
| 		sep = ifsset() ? ifsval()[0] : ' ';
 | |
| 		if (quotes) {
 | |
| 			sepq = SIT(sep,syntax) == CCTL;
 | |
| 		}
 | |
| param:
 | |
| 		for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
 | |
| 			strtodest(p, syntax, quotes);
 | |
| 			if (*ap && sep) {
 | |
| 				if (sepq)
 | |
| 					STPUTC(CTLESC, expdest);
 | |
| 				STPUTC(sep, expdest);
 | |
| 			}
 | |
| 		}
 | |
| 		break;
 | |
| 	case '0':
 | |
| 		strtodest(arg0, syntax, quotes);
 | |
| 		break;
 | |
| 	default:
 | |
| 		num = atoi(name);
 | |
| 		if (num > 0 && num <= shellparam.nparam) {
 | |
| 			strtodest(shellparam.p[num - 1], syntax, quotes);
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Record the fact that we have to scan this region of the
 | |
|  * string for IFS characters.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| recordregion(start, end, nulonly)
 | |
| 	int start;
 | |
| 	int end;
 | |
| 	int nulonly;
 | |
| {
 | |
| 	struct ifsregion *ifsp;
 | |
| 
 | |
| 	if (ifslastp == NULL) {
 | |
| 		ifsp = &ifsfirst;
 | |
| 	} else {
 | |
| 		INTOFF;
 | |
| 		ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
 | |
| 		ifsp->next = NULL;
 | |
| 		ifslastp->next = ifsp;
 | |
| 		INTON;
 | |
| 	}
 | |
| 	ifslastp = ifsp;
 | |
| 	ifslastp->begoff = start;
 | |
| 	ifslastp->endoff = end;
 | |
| 	ifslastp->nulonly = nulonly;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Break the argument string into pieces based upon IFS and add the
 | |
|  * strings to the argument list.  The regions of the string to be
 | |
|  * searched for IFS characters have been stored by recordregion.
 | |
|  */
 | |
| static void
 | |
| ifsbreakup(string, arglist)
 | |
| 	char *string;
 | |
| 	struct arglist *arglist;
 | |
| 	{
 | |
| 	struct ifsregion *ifsp;
 | |
| 	struct strlist *sp;
 | |
| 	char *start;
 | |
| 	char *p;
 | |
| 	char *q;
 | |
| 	const char *ifs, *realifs;
 | |
| 	int ifsspc;
 | |
| 	int nulonly;
 | |
| 
 | |
| 
 | |
| 	start = string;
 | |
| 	ifsspc = 0;
 | |
| 	nulonly = 0;
 | |
| 	realifs = ifsset() ? ifsval() : defifs;
 | |
| 	if (ifslastp != NULL) {
 | |
| 		ifsp = &ifsfirst;
 | |
| 		do {
 | |
| 			p = string + ifsp->begoff;
 | |
| 			nulonly = ifsp->nulonly;
 | |
| 			ifs = nulonly ? nullstr : realifs;
 | |
| 			ifsspc = 0;
 | |
| 			while (p < string + ifsp->endoff) {
 | |
| 				q = p;
 | |
| 				if (*p == CTLESC)
 | |
| 					p++;
 | |
| 				if (strchr(ifs, *p)) {
 | |
| 					if (!nulonly)
 | |
| 						ifsspc = (strchr(defifs, *p) != NULL);
 | |
| 					/* Ignore IFS whitespace at start */
 | |
| 					if (q == start && ifsspc) {
 | |
| 						p++;
 | |
| 						start = p;
 | |
| 						continue;
 | |
| 					}
 | |
| 					*q = '\0';
 | |
| 					sp = (struct strlist *)stalloc(sizeof *sp);
 | |
| 					sp->text = start;
 | |
| 					*arglist->lastp = sp;
 | |
| 					arglist->lastp = &sp->next;
 | |
| 					p++;
 | |
| 					if (!nulonly) {
 | |
| 						for (;;) {
 | |
| 							if (p >= string + ifsp->endoff) {
 | |
| 								break;
 | |
| 							}
 | |
| 							q = p;
 | |
| 							if (*p == CTLESC)
 | |
| 								p++;
 | |
| 							if (strchr(ifs, *p) == NULL ) {
 | |
| 								p = q;
 | |
| 								break;
 | |
| 							} else if (strchr(defifs, *p) == NULL) {
 | |
| 								if (ifsspc) {
 | |
| 									p++;
 | |
| 									ifsspc = 0;
 | |
| 								} else {
 | |
| 									p = q;
 | |
| 									break;
 | |
| 								}
 | |
| 							} else
 | |
| 								p++;
 | |
| 						}
 | |
| 					}
 | |
| 					start = p;
 | |
| 				} else
 | |
| 					p++;
 | |
| 			}
 | |
| 		} while ((ifsp = ifsp->next) != NULL);
 | |
| 		if (!(*start || (!ifsspc && start > string && nulonly))) {
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sp = (struct strlist *)stalloc(sizeof *sp);
 | |
| 	sp->text = start;
 | |
| 	*arglist->lastp = sp;
 | |
| 	arglist->lastp = &sp->next;
 | |
| }
 | |
| 
 | |
| static void
 | |
| ifsfree()
 | |
| {
 | |
| 	while (ifsfirst.next != NULL) {
 | |
| 		struct ifsregion *ifsp;
 | |
| 		INTOFF;
 | |
| 		ifsp = ifsfirst.next->next;
 | |
| 		ckfree(ifsfirst.next);
 | |
| 		ifsfirst.next = ifsp;
 | |
| 		INTON;
 | |
| 	}
 | |
| 	ifslastp = NULL;
 | |
| 	ifsfirst.next = NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Add a file name to the list.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| addfname(const char *name)
 | |
| {
 | |
| 	char *p;
 | |
| 	struct strlist *sp;
 | |
| 
 | |
| 	p = sstrdup(name);
 | |
| 	sp = (struct strlist *)stalloc(sizeof *sp);
 | |
| 	sp->text = p;
 | |
| 	*exparg.lastp = sp;
 | |
| 	exparg.lastp = &sp->next;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Expand shell metacharacters.  At this point, the only control characters
 | |
|  * should be escapes.  The results are stored in the list exparg.
 | |
|  */
 | |
| 
 | |
| #if defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
 | |
| static void
 | |
| expandmeta(str, flag)
 | |
| 	struct strlist *str;
 | |
| 	int flag;
 | |
| {
 | |
| 	const char *p;
 | |
| 	glob_t pglob;
 | |
| 	/* TODO - EXP_REDIR */
 | |
| 
 | |
| 	while (str) {
 | |
| 		if (fflag)
 | |
| 			goto nometa;
 | |
| 		p = preglob(str->text);
 | |
| 		INTOFF;
 | |
| 		switch (glob(p, 0, 0, &pglob)) {
 | |
| 		case 0:
 | |
| 			if(pglob.gl_pathv[1]==0 && !strcmp(p, pglob.gl_pathv[0]))
 | |
| 				goto nometa2;
 | |
| 			addglob(&pglob);
 | |
| 			globfree(&pglob);
 | |
| 			INTON;
 | |
| 			break;
 | |
| 		case GLOB_NOMATCH:
 | |
| nometa2:
 | |
| 			globfree(&pglob);
 | |
| 			INTON;
 | |
| nometa:
 | |
| 			*exparg.lastp = str;
 | |
| 			rmescapes(str->text);
 | |
| 			exparg.lastp = &str->next;
 | |
| 			break;
 | |
| 		default:        /* GLOB_NOSPACE */
 | |
| 			error("Out of space");
 | |
| 		}
 | |
| 		str = str->next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Add the result of glob(3) to the list.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| addglob(pglob)
 | |
| 	const glob_t *pglob;
 | |
| {
 | |
| 	char **p = pglob->gl_pathv;
 | |
| 
 | |
| 	do {
 | |
| 		addfname(*p);
 | |
| 	} while (*++p);
 | |
| }
 | |
| 
 | |
| 
 | |
| #else   /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
 | |
| static char *expdir;
 | |
| 
 | |
| 
 | |
| static void
 | |
| expandmeta(str, flag)
 | |
| 	struct strlist *str;
 | |
| 	int flag;
 | |
| {
 | |
| 	char *p;
 | |
| 	struct strlist **savelastp;
 | |
| 	struct strlist *sp;
 | |
| 	char c;
 | |
| 	/* TODO - EXP_REDIR */
 | |
| 
 | |
| 	while (str) {
 | |
| 		if (fflag)
 | |
| 			goto nometa;
 | |
| 		p = str->text;
 | |
| 		for (;;) {                      /* fast check for meta chars */
 | |
| 			if ((c = *p++) == '\0')
 | |
| 				goto nometa;
 | |
| 			if (c == '*' || c == '?' || c == '[' || c == '!')
 | |
| 				break;
 | |
| 		}
 | |
| 		savelastp = exparg.lastp;
 | |
| 		INTOFF;
 | |
| 		if (expdir == NULL) {
 | |
| 			int i = strlen(str->text);
 | |
| 			expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
 | |
| 		}
 | |
| 
 | |
| 		expmeta(expdir, str->text);
 | |
| 		ckfree(expdir);
 | |
| 		expdir = NULL;
 | |
| 		INTON;
 | |
| 		if (exparg.lastp == savelastp) {
 | |
| 			/*
 | |
| 			 * no matches
 | |
| 			 */
 | |
| nometa:
 | |
| 			*exparg.lastp = str;
 | |
| 			rmescapes(str->text);
 | |
| 			exparg.lastp = &str->next;
 | |
| 		} else {
 | |
| 			*exparg.lastp = NULL;
 | |
| 			*savelastp = sp = expsort(*savelastp);
 | |
| 			while (sp->next != NULL)
 | |
| 				sp = sp->next;
 | |
| 			exparg.lastp = &sp->next;
 | |
| 		}
 | |
| 		str = str->next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Do metacharacter (i.e. *, ?, [...]) expansion.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| expmeta(enddir, name)
 | |
| 	char *enddir;
 | |
| 	char *name;
 | |
| 	{
 | |
| 	char *p;
 | |
| 	const char *cp;
 | |
| 	char *q;
 | |
| 	char *start;
 | |
| 	char *endname;
 | |
| 	int metaflag;
 | |
| 	struct stat statb;
 | |
| 	DIR *dirp;
 | |
| 	struct dirent *dp;
 | |
| 	int atend;
 | |
| 	int matchdot;
 | |
| 
 | |
| 	metaflag = 0;
 | |
| 	start = name;
 | |
| 	for (p = name ; ; p++) {
 | |
| 		if (*p == '*' || *p == '?')
 | |
| 			metaflag = 1;
 | |
| 		else if (*p == '[') {
 | |
| 			q = p + 1;
 | |
| 			if (*q == '!')
 | |
| 				q++;
 | |
| 			for (;;) {
 | |
| 				while (*q == CTLQUOTEMARK)
 | |
| 					q++;
 | |
| 				if (*q == CTLESC)
 | |
| 					q++;
 | |
| 				if (*q == '/' || *q == '\0')
 | |
| 					break;
 | |
| 				if (*++q == ']') {
 | |
| 					metaflag = 1;
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) {
 | |
| 			metaflag = 1;
 | |
| 		} else if (*p == '\0')
 | |
| 			break;
 | |
| 		else if (*p == CTLQUOTEMARK)
 | |
| 			continue;
 | |
| 		else if (*p == CTLESC)
 | |
| 			p++;
 | |
| 		if (*p == '/') {
 | |
| 			if (metaflag)
 | |
| 				break;
 | |
| 			start = p + 1;
 | |
| 		}
 | |
| 	}
 | |
| 	if (metaflag == 0) {    /* we've reached the end of the file name */
 | |
| 		if (enddir != expdir)
 | |
| 			metaflag++;
 | |
| 		for (p = name ; ; p++) {
 | |
| 			if (*p == CTLQUOTEMARK)
 | |
| 				continue;
 | |
| 			if (*p == CTLESC)
 | |
| 				p++;
 | |
| 			*enddir++ = *p;
 | |
| 			if (*p == '\0')
 | |
| 				break;
 | |
| 		}
 | |
| 		if (metaflag == 0 || lstat(expdir, &statb) >= 0)
 | |
| 			addfname(expdir);
 | |
| 		return;
 | |
| 	}
 | |
| 	endname = p;
 | |
| 	if (start != name) {
 | |
| 		p = name;
 | |
| 		while (p < start) {
 | |
| 			while (*p == CTLQUOTEMARK)
 | |
| 				p++;
 | |
| 			if (*p == CTLESC)
 | |
| 				p++;
 | |
| 			*enddir++ = *p++;
 | |
| 		}
 | |
| 	}
 | |
| 	if (enddir == expdir) {
 | |
| 		cp = ".";
 | |
| 	} else if (enddir == expdir + 1 && *expdir == '/') {
 | |
| 		cp = "/";
 | |
| 	} else {
 | |
| 		cp = expdir;
 | |
| 		enddir[-1] = '\0';
 | |
| 	}
 | |
| 	if ((dirp = opendir(cp)) == NULL)
 | |
| 		return;
 | |
| 	if (enddir != expdir)
 | |
| 		enddir[-1] = '/';
 | |
| 	if (*endname == 0) {
 | |
| 		atend = 1;
 | |
| 	} else {
 | |
| 		atend = 0;
 | |
| 		*endname++ = '\0';
 | |
| 	}
 | |
| 	matchdot = 0;
 | |
| 	p = start;
 | |
| 	while (*p == CTLQUOTEMARK)
 | |
| 		p++;
 | |
| 	if (*p == CTLESC)
 | |
| 		p++;
 | |
| 	if (*p == '.')
 | |
| 		matchdot++;
 | |
| 	while (! int_pending() && (dp = readdir(dirp)) != NULL) {
 | |
| 		if (dp->d_name[0] == '.' && ! matchdot)
 | |
| 			continue;
 | |
| 		if (patmatch(start, dp->d_name, 0)) {
 | |
| 			if (atend) {
 | |
| 				strcpy(enddir, dp->d_name);
 | |
| 				addfname(expdir);
 | |
| 			} else {
 | |
| 				for (p = enddir, cp = dp->d_name;
 | |
| 				     (*p++ = *cp++) != '\0';)
 | |
| 					continue;
 | |
| 				p[-1] = '/';
 | |
| 				expmeta(p, endname);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	closedir(dirp);
 | |
| 	if (! atend)
 | |
| 		endname[-1] = '/';
 | |
| }
 | |
| #endif  /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
 | |
| 
 | |
| 
 | |
| 
 | |
| #if !(defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
 | |
| /*
 | |
|  * Sort the results of file name expansion.  It calculates the number of
 | |
|  * strings to sort and then calls msort (short for merge sort) to do the
 | |
|  * work.
 | |
|  */
 | |
| 
 | |
| static struct strlist *
 | |
| expsort(str)
 | |
| 	struct strlist *str;
 | |
| 	{
 | |
| 	int len;
 | |
| 	struct strlist *sp;
 | |
| 
 | |
| 	len = 0;
 | |
| 	for (sp = str ; sp ; sp = sp->next)
 | |
| 		len++;
 | |
| 	return msort(str, len);
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct strlist *
 | |
| msort(list, len)
 | |
| 	struct strlist *list;
 | |
| 	int len;
 | |
| {
 | |
| 	struct strlist *p, *q = NULL;
 | |
| 	struct strlist **lpp;
 | |
| 	int half;
 | |
| 	int n;
 | |
| 
 | |
| 	if (len <= 1)
 | |
| 		return list;
 | |
| 	half = len >> 1;
 | |
| 	p = list;
 | |
| 	for (n = half ; --n >= 0 ; ) {
 | |
| 		q = p;
 | |
| 		p = p->next;
 | |
| 	}
 | |
| 	q->next = NULL;                 /* terminate first half of list */
 | |
| 	q = msort(list, half);          /* sort first half of list */
 | |
| 	p = msort(p, len - half);               /* sort second half */
 | |
| 	lpp = &list;
 | |
| 	for (;;) {
 | |
| 		if (strcmp(p->text, q->text) < 0) {
 | |
| 			*lpp = p;
 | |
| 			lpp = &p->next;
 | |
| 			if ((p = *lpp) == NULL) {
 | |
| 				*lpp = q;
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			*lpp = q;
 | |
| 			lpp = &q->next;
 | |
| 			if ((q = *lpp) == NULL) {
 | |
| 				*lpp = p;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return list;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns true if the pattern matches the string.
 | |
|  */
 | |
| 
 | |
| #if defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN)
 | |
| /* squoted: string might have quote chars */
 | |
| static int
 | |
| patmatch(char *pattern, char *string, int squoted)
 | |
| {
 | |
| 	const char *p;
 | |
| 	char *q;
 | |
| 
 | |
| 	p = preglob(pattern);
 | |
| 	q = squoted ? _rmescapes(string, RMESCAPE_ALLOC) : string;
 | |
| 
 | |
| 	return !fnmatch(p, q, 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| patmatch2(char *pattern, char *string, int squoted)
 | |
| {
 | |
| 	char *p;
 | |
| 	int res;
 | |
| 
 | |
| 	sstrnleft--;
 | |
| 	p = grabstackstr(expdest);
 | |
| 	res = patmatch(pattern, string, squoted);
 | |
| 	ungrabstackstr(p, expdest);
 | |
| 	return res;
 | |
| }
 | |
| #else
 | |
| static int
 | |
| patmatch(char *pattern, char *string, int squoted) {
 | |
| 	return pmatch(pattern, string, squoted);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| pmatch(char *pattern, char *string, int squoted)
 | |
| {
 | |
| 	char *p, *q;
 | |
| 	char c;
 | |
| 
 | |
| 	p = pattern;
 | |
| 	q = string;
 | |
| 	for (;;) {
 | |
| 		switch (c = *p++) {
 | |
| 		case '\0':
 | |
| 			goto breakloop;
 | |
| 		case CTLESC:
 | |
| 			if (squoted && *q == CTLESC)
 | |
| 				q++;
 | |
| 			if (*q++ != *p++)
 | |
| 				return 0;
 | |
| 			break;
 | |
| 		case CTLQUOTEMARK:
 | |
| 			continue;
 | |
| 		case '?':
 | |
| 			if (squoted && *q == CTLESC)
 | |
| 				q++;
 | |
| 			if (*q++ == '\0')
 | |
| 				return 0;
 | |
| 			break;
 | |
| 		case '*':
 | |
| 			c = *p;
 | |
| 			while (c == CTLQUOTEMARK || c == '*')
 | |
| 				c = *++p;
 | |
| 			if (c != CTLESC &&  c != CTLQUOTEMARK &&
 | |
| 			    c != '?' && c != '*' && c != '[') {
 | |
| 				while (*q != c) {
 | |
| 					if (squoted && *q == CTLESC &&
 | |
| 					    q[1] == c)
 | |
| 						break;
 | |
| 					if (*q == '\0')
 | |
| 						return 0;
 | |
| 					if (squoted && *q == CTLESC)
 | |
| 						q++;
 | |
| 					q++;
 | |
| 				}
 | |
| 			}
 | |
| 			do {
 | |
| 				if (pmatch(p, q, squoted))
 | |
| 					return 1;
 | |
| 				if (squoted && *q == CTLESC)
 | |
| 					q++;
 | |
| 			} while (*q++ != '\0');
 | |
| 			return 0;
 | |
| 		case '[': {
 | |
| 			char *endp;
 | |
| 			int invert, found;
 | |
| 			char chr;
 | |
| 
 | |
| 			endp = p;
 | |
| 			if (*endp == '!')
 | |
| 				endp++;
 | |
| 			for (;;) {
 | |
| 				while (*endp == CTLQUOTEMARK)
 | |
| 					endp++;
 | |
| 				if (*endp == '\0')
 | |
| 					goto dft;               /* no matching ] */
 | |
| 				if (*endp == CTLESC)
 | |
| 					endp++;
 | |
| 				if (*++endp == ']')
 | |
| 					break;
 | |
| 			}
 | |
| 			invert = 0;
 | |
| 			if (*p == '!') {
 | |
| 				invert++;
 | |
| 				p++;
 | |
| 			}
 | |
| 			found = 0;
 | |
| 			chr = *q++;
 | |
| 			if (squoted && chr == CTLESC)
 | |
| 				chr = *q++;
 | |
| 			if (chr == '\0')
 | |
| 				return 0;
 | |
| 			c = *p++;
 | |
| 			do {
 | |
| 				if (c == CTLQUOTEMARK)
 | |
| 					continue;
 | |
| 				if (c == CTLESC)
 | |
| 					c = *p++;
 | |
| 				if (*p == '-' && p[1] != ']') {
 | |
| 					p++;
 | |
| 					while (*p == CTLQUOTEMARK)
 | |
| 						p++;
 | |
| 					if (*p == CTLESC)
 | |
| 						p++;
 | |
| 					if (chr >= c && chr <= *p)
 | |
| 						found = 1;
 | |
| 					p++;
 | |
| 				} else {
 | |
| 					if (chr == c)
 | |
| 						found = 1;
 | |
| 				}
 | |
| 			} while ((c = *p++) != ']');
 | |
| 			if (found == invert)
 | |
| 				return 0;
 | |
| 			break;
 | |
| 		}
 | |
| dft:            default:
 | |
| 			if (squoted && *q == CTLESC)
 | |
| 				q++;
 | |
| 			if (*q++ != c)
 | |
| 				return 0;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| breakloop:
 | |
| 	if (*q != '\0')
 | |
| 		return 0;
 | |
| 	return 1;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Remove any CTLESC characters from a string.
 | |
|  */
 | |
| 
 | |
| #if defined(__GLIBC__) && __GLIBC__ >= 2 && !defined(FNMATCH_BROKEN)
 | |
| static char *
 | |
| _rmescapes(char *str, int flag)
 | |
| {
 | |
| 	char *p, *q, *r;
 | |
| 	static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
 | |
| 
 | |
| 	p = strpbrk(str, qchars);
 | |
| 	if (!p) {
 | |
| 		return str;
 | |
| 	}
 | |
| 	q = p;
 | |
| 	r = str;
 | |
| 	if (flag & RMESCAPE_ALLOC) {
 | |
| 		size_t len = p - str;
 | |
| 		q = r = stalloc(strlen(p) + len + 1);
 | |
| 		if (len > 0) {
 | |
| 			memcpy(q, str, len);
 | |
| 			q += len;
 | |
| 		}
 | |
| 	}
 | |
| 	while (*p) {
 | |
| 		if (*p == CTLQUOTEMARK) {
 | |
| 			p++;
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (*p == CTLESC) {
 | |
| 			p++;
 | |
| 			if (flag & RMESCAPE_GLOB && *p != '/') {
 | |
| 				*q++ = '\\';
 | |
| 			}
 | |
| 		}
 | |
| 		*q++ = *p++;
 | |
| 	}
 | |
| 	*q = '\0';
 | |
| 	return r;
 | |
| }
 | |
| #else
 | |
| static void
 | |
| rmescapes(str)
 | |
| 	char *str;
 | |
| {
 | |
| 	char *p, *q;
 | |
| 
 | |
| 	p = str;
 | |
| 	while (*p != CTLESC && *p != CTLQUOTEMARK) {
 | |
| 		if (*p++ == '\0')
 | |
| 			return;
 | |
| 	}
 | |
| 	q = p;
 | |
| 	while (*p) {
 | |
| 		if (*p == CTLQUOTEMARK) {
 | |
| 			p++;
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (*p == CTLESC)
 | |
| 			p++;
 | |
| 		*q++ = *p++;
 | |
| 	}
 | |
| 	*q = '\0';
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * See if a pattern matches in a case statement.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| casematch(union node *pattern, const char *val)
 | |
| {
 | |
| 	struct stackmark smark;
 | |
| 	int result;
 | |
| 	char *p;
 | |
| 
 | |
| 	setstackmark(&smark);
 | |
| 	argbackq = pattern->narg.backquote;
 | |
| 	STARTSTACKSTR(expdest);
 | |
| 	ifslastp = NULL;
 | |
| 	argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
 | |
| 	STPUTC('\0', expdest);
 | |
| 	p = grabstackstr(expdest);
 | |
| 	result = patmatch(p, (char *)val, 0);
 | |
| 	popstackmark(&smark);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Our own itoa().
 | |
|  */
 | |
| 
 | |
| static char *
 | |
| cvtnum(num, buf)
 | |
| 	int num;
 | |
| 	char *buf;
 | |
| 	{
 | |
| 	int len;
 | |
| 
 | |
| 	CHECKSTRSPACE(32, buf);
 | |
| 	len = sprintf(buf, "%d", num);
 | |
| 	STADJUST(len, buf);
 | |
| 	return buf;
 | |
| }
 | |
| /*
 | |
|  * Editline and history functions (and glue).
 | |
|  */
 | |
| static int histcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	error("not compiled with history support");
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| struct redirtab {
 | |
| 	struct redirtab *next;
 | |
| 	short renamed[10]; /* Current ash support only 0-9 descriptors */
 | |
| 	/* char on arm (and others) can't be negative */
 | |
| };
 | |
| 
 | |
| static struct redirtab *redirlist;
 | |
| 
 | |
| extern char **environ;
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Initialization code.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| init(void) {
 | |
| 
 | |
|       /* from cd.c: */
 | |
|       {
 | |
| 	      curdir = nullstr;
 | |
| 	      setpwd(0, 0);
 | |
|       }
 | |
| 
 | |
|       /* from input.c: */
 | |
|       {
 | |
| 	      basepf.nextc = basepf.buf = basebuf;
 | |
|       }
 | |
| 
 | |
|       /* from var.c: */
 | |
|       {
 | |
| 	      char **envp;
 | |
| 	      char ppid[32];
 | |
| 
 | |
| 	      initvar();
 | |
| 	      for (envp = environ ; *envp ; envp++) {
 | |
| 		      if (strchr(*envp, '=')) {
 | |
| 			      setvareq(*envp, VEXPORT|VTEXTFIXED);
 | |
| 		      }
 | |
| 	      }
 | |
| 
 | |
| 	      snprintf(ppid, sizeof(ppid), "%d", (int) getppid());
 | |
| 	      setvar("PPID", ppid, 0);
 | |
|       }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * This routine is called when an error or an interrupt occurs in an
 | |
|  * interactive shell and control is returned to the main command loop.
 | |
|  */
 | |
| 
 | |
| /* 1 == check for aliases, 2 == also check for assignments */
 | |
| static int checkalias;  /* also used in no alias mode for check assignments */
 | |
| 
 | |
| static void
 | |
| reset(void) {
 | |
| 
 | |
|       /* from eval.c: */
 | |
|       {
 | |
| 	      evalskip = 0;
 | |
| 	      loopnest = 0;
 | |
| 	      funcnest = 0;
 | |
|       }
 | |
| 
 | |
|       /* from input.c: */
 | |
|       {
 | |
| 	      if (exception != EXSHELLPROC)
 | |
| 		      parselleft = parsenleft = 0;      /* clear input buffer */
 | |
| 	      popallfiles();
 | |
|       }
 | |
| 
 | |
|       /* from parser.c: */
 | |
|       {
 | |
| 	      tokpushback = 0;
 | |
| 	      checkkwd = 0;
 | |
| 	      checkalias = 0;
 | |
|       }
 | |
| 
 | |
|       /* from redir.c: */
 | |
|       {
 | |
| 	      while (redirlist)
 | |
| 		      popredir();
 | |
|       }
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * This file implements the input routines used by the parser.
 | |
|  */
 | |
| 
 | |
| #ifdef CONFIG_FEATURE_COMMAND_EDITING
 | |
| static const char * cmdedit_prompt;
 | |
| static inline void putprompt(const char *s) {
 | |
|     cmdedit_prompt = s;
 | |
| }
 | |
| #else
 | |
| static inline void putprompt(const char *s) {
 | |
|     out2str(s);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #define EOF_NLEFT -99           /* value of parsenleft when EOF pushed back */
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Same as pgetc(), but ignores PEOA.
 | |
|  */
 | |
| 
 | |
| #ifdef ASH_ALIAS
 | |
| static int
 | |
| pgetc2(void)
 | |
| {
 | |
| 	int c;
 | |
| 	do {
 | |
| 		c = pgetc_macro();
 | |
| 	} while (c == PEOA);
 | |
| 	return c;
 | |
| }
 | |
| #else
 | |
| static inline int pgetc2() { return pgetc_macro(); }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Read a line from the script.
 | |
|  */
 | |
| 
 | |
| static inline char *
 | |
| pfgets(char *line, int len)
 | |
| {
 | |
| 	char *p = line;
 | |
| 	int nleft = len;
 | |
| 	int c;
 | |
| 
 | |
| 	while (--nleft > 0) {
 | |
| 		c = pgetc2();
 | |
| 		if (c == PEOF) {
 | |
| 			if (p == line)
 | |
| 				return NULL;
 | |
| 			break;
 | |
| 		}
 | |
| 		*p++ = c;
 | |
| 		if (c == '\n')
 | |
| 			break;
 | |
| 	}
 | |
| 	*p = '\0';
 | |
| 	return line;
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| preadfd(void)
 | |
| {
 | |
|     int nr;
 | |
|     char *buf =  parsefile->buf;
 | |
|     parsenextc = buf;
 | |
| 
 | |
| retry:
 | |
| #ifdef CONFIG_FEATURE_COMMAND_EDITING
 | |
| 	{
 | |
| 	    if (!iflag || parsefile->fd)
 | |
| 		    nr = safe_read(parsefile->fd, buf, BUFSIZ - 1);
 | |
| 	    else {
 | |
| 		    nr = cmdedit_read_input((char*)cmdedit_prompt, buf);
 | |
| 	    }
 | |
| 	}
 | |
| #else
 | |
| 	nr = safe_read(parsefile->fd, buf, BUFSIZ - 1);
 | |
| #endif
 | |
| 
 | |
| 	if (nr < 0) {
 | |
| 		if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
 | |
| 			int flags = fcntl(0, F_GETFL, 0);
 | |
| 			if (flags >= 0 && flags & O_NONBLOCK) {
 | |
| 				flags &=~ O_NONBLOCK;
 | |
| 				if (fcntl(0, F_SETFL, flags) >= 0) {
 | |
| 					out2str("sh: turning off NDELAY mode\n");
 | |
| 					goto retry;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nr;
 | |
| }
 | |
| 
 | |
| static void
 | |
| popstring(void)
 | |
| {
 | |
| 	struct strpush *sp = parsefile->strpush;
 | |
| 
 | |
| 	INTOFF;
 | |
| #ifdef ASH_ALIAS
 | |
| 	if (sp->ap) {
 | |
| 		if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') {
 | |
| 			if (!checkalias) {
 | |
| 				checkalias = 1;
 | |
| 			}
 | |
| 		}
 | |
| 		if (sp->string != sp->ap->val) {
 | |
| 			ckfree(sp->string);
 | |
| 		}
 | |
| 
 | |
| 		sp->ap->flag &= ~ALIASINUSE;
 | |
| 		if (sp->ap->flag & ALIASDEAD) {
 | |
| 			unalias(sp->ap->name);
 | |
| 		}
 | |
| 	}
 | |
| #endif
 | |
| 	parsenextc = sp->prevstring;
 | |
| 	parsenleft = sp->prevnleft;
 | |
| /*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
 | |
| 	parsefile->strpush = sp->prev;
 | |
| 	if (sp != &(parsefile->basestrpush))
 | |
| 		ckfree(sp);
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Refill the input buffer and return the next input character:
 | |
|  *
 | |
|  * 1) If a string was pushed back on the input, pop it;
 | |
|  * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
 | |
|  *    from a string so we can't refill the buffer, return EOF.
 | |
|  * 3) If the is more stuff in this buffer, use it else call read to fill it.
 | |
|  * 4) Process input up to the next newline, deleting nul characters.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| preadbuffer(void)
 | |
| {
 | |
| 	char *p, *q;
 | |
| 	int more;
 | |
| 	char savec;
 | |
| 
 | |
| 	while (parsefile->strpush) {
 | |
| #ifdef ASH_ALIAS
 | |
| 		if (parsenleft == -1 && parsefile->strpush->ap &&
 | |
| 			parsenextc[-1] != ' ' && parsenextc[-1] != '\t') {
 | |
| 			return PEOA;
 | |
| 		}
 | |
| #endif
 | |
| 		popstring();
 | |
| 		if (--parsenleft >= 0)
 | |
| 			return (*parsenextc++);
 | |
| 	}
 | |
| 	if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
 | |
| 		return PEOF;
 | |
| 	flushall();
 | |
| 
 | |
| again:
 | |
| 	if (parselleft <= 0) {
 | |
| 		if ((parselleft = preadfd()) <= 0) {
 | |
| 			parselleft = parsenleft = EOF_NLEFT;
 | |
| 			return PEOF;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	q = p = parsenextc;
 | |
| 
 | |
| 	/* delete nul characters */
 | |
| 	for (more = 1; more;) {
 | |
| 		switch (*p) {
 | |
| 		case '\0':
 | |
| 			p++;    /* Skip nul */
 | |
| 			goto check;
 | |
| 
 | |
| 
 | |
| 		case '\n':
 | |
| 			parsenleft = q - parsenextc;
 | |
| 			more = 0; /* Stop processing here */
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		*q++ = *p++;
 | |
| check:
 | |
| 		if (--parselleft <= 0 && more) {
 | |
| 			parsenleft = q - parsenextc - 1;
 | |
| 			if (parsenleft < 0)
 | |
| 				goto again;
 | |
| 			more = 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	savec = *q;
 | |
| 	*q = '\0';
 | |
| 
 | |
| 	if (vflag) {
 | |
| 		out2str(parsenextc);
 | |
| 	}
 | |
| 
 | |
| 	*q = savec;
 | |
| 
 | |
| 	return *parsenextc++;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Push a string back onto the input at this current parsefile level.
 | |
|  * We handle aliases this way.
 | |
|  */
 | |
| static void
 | |
| pushstring(char *s, int len, void *ap)
 | |
| {
 | |
| 	struct strpush *sp;
 | |
| 
 | |
| 	INTOFF;
 | |
| /*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
 | |
| 	if (parsefile->strpush) {
 | |
| 		sp = ckmalloc(sizeof (struct strpush));
 | |
| 		sp->prev = parsefile->strpush;
 | |
| 		parsefile->strpush = sp;
 | |
| 	} else
 | |
| 		sp = parsefile->strpush = &(parsefile->basestrpush);
 | |
| 	sp->prevstring = parsenextc;
 | |
| 	sp->prevnleft = parsenleft;
 | |
| #ifdef ASH_ALIAS
 | |
| 	sp->ap = (struct alias *)ap;
 | |
| 	if (ap) {
 | |
| 		((struct alias *)ap)->flag |= ALIASINUSE;
 | |
| 		sp->string = s;
 | |
| 	}
 | |
| #endif
 | |
| 	parsenextc = s;
 | |
| 	parsenleft = len;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Like setinputfile, but takes input from a string.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| setinputstring(char *string)
 | |
| {
 | |
| 	INTOFF;
 | |
| 	pushfile();
 | |
| 	parsenextc = string;
 | |
| 	parsenleft = strlen(string);
 | |
| 	parsefile->buf = NULL;
 | |
| 	plinno = 1;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * To handle the "." command, a stack of input files is used.  Pushfile
 | |
|  * adds a new entry to the stack and popfile restores the previous level.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| pushfile(void) {
 | |
| 	struct parsefile *pf;
 | |
| 
 | |
| 	parsefile->nleft = parsenleft;
 | |
| 	parsefile->lleft = parselleft;
 | |
| 	parsefile->nextc = parsenextc;
 | |
| 	parsefile->linno = plinno;
 | |
| 	pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
 | |
| 	pf->prev = parsefile;
 | |
| 	pf->fd = -1;
 | |
| 	pf->strpush = NULL;
 | |
| 	pf->basestrpush.prev = NULL;
 | |
| 	parsefile = pf;
 | |
| }
 | |
| 
 | |
| #ifdef JOBS
 | |
| static void restartjob (struct job *);
 | |
| #endif
 | |
| static void freejob (struct job *);
 | |
| static struct job *getjob (const char *);
 | |
| static int dowait (int, struct job *);
 | |
| static void waitonint(int);
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * We keep track of whether or not fd0 has been redirected.  This is for
 | |
|  * background commands, where we want to redirect fd0 to /dev/null only
 | |
|  * if it hasn't already been redirected.
 | |
| */
 | |
| static int fd0_redirected = 0;
 | |
| 
 | |
| /* Return true if fd 0 has already been redirected at least once.  */
 | |
| static inline int
 | |
| fd0_redirected_p (void) 
 | |
| {
 | |
| 	return fd0_redirected != 0;
 | |
| }
 | |
| 
 | |
| static void dupredirect (const union node *, int, int fd1dup);
 | |
| 
 | |
| #ifdef JOBS
 | |
| /*
 | |
|  * Turn job control on and off.
 | |
|  *
 | |
|  * Note:  This code assumes that the third arg to ioctl is a character
 | |
|  * pointer, which is true on Berkeley systems but not System V.  Since
 | |
|  * System V doesn't have job control yet, this isn't a problem now.
 | |
|  */
 | |
| 
 | |
| 
 | |
| 
 | |
| static void setjobctl(int enable)
 | |
| {
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 	int ldisc;
 | |
| #endif
 | |
| 
 | |
| 	if (enable == jobctl || rootshell == 0)
 | |
| 		return;
 | |
| 	if (enable) {
 | |
| 		do { /* while we are in the background */
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 			if (ioctl(2, TIOCGPGRP, (char *)&initialpgrp) < 0) {
 | |
| #else
 | |
| 			initialpgrp = tcgetpgrp(2);
 | |
| 			if (initialpgrp < 0) {
 | |
| #endif
 | |
| 				out2str("sh: can't access tty; job control turned off\n");
 | |
| 				mflag = 0;
 | |
| 				return;
 | |
| 			}
 | |
| 			if (initialpgrp == -1)
 | |
| 				initialpgrp = getpgrp();
 | |
| 			else if (initialpgrp != getpgrp()) {
 | |
| 				killpg(initialpgrp, SIGTTIN);
 | |
| 				continue;
 | |
| 			}
 | |
| 		} while (0);
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 		if (ioctl(2, TIOCGETD, (char *)&ldisc) < 0 || ldisc != NTTYDISC) {
 | |
| 			out2str("sh: need new tty driver to run job control; job control turned off\n");
 | |
| 			mflag = 0;
 | |
| 			return;
 | |
| 		}
 | |
| #endif
 | |
| 		setsignal(SIGTSTP);
 | |
| 		setsignal(SIGTTOU);
 | |
| 		setsignal(SIGTTIN);
 | |
| 		setpgid(0, rootpid);
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 		ioctl(2, TIOCSPGRP, (char *)&rootpid);
 | |
| #else
 | |
| 		tcsetpgrp(2, rootpid);
 | |
| #endif
 | |
| 	} else { /* turning job control off */
 | |
| 		setpgid(0, initialpgrp);
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 		ioctl(2, TIOCSPGRP, (char *)&initialpgrp);
 | |
| #else
 | |
| 		tcsetpgrp(2, initialpgrp);
 | |
| #endif
 | |
| 		setsignal(SIGTSTP);
 | |
| 		setsignal(SIGTTOU);
 | |
| 		setsignal(SIGTTIN);
 | |
| 	}
 | |
| 	jobctl = enable;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| #ifdef JOBS
 | |
| static int
 | |
| killcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	int signo = -1;
 | |
| 	int list = 0;
 | |
| 	int i;
 | |
| 	pid_t pid;
 | |
| 	struct job *jp;
 | |
| 
 | |
| 	if (argc <= 1) {
 | |
| usage:
 | |
| 		error(
 | |
| "Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
 | |
| "kill -l [exitstatus]"
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	if (*argv[1] == '-') {
 | |
| 		signo = decode_signal(argv[1] + 1, 1);
 | |
| 		if (signo < 0) {
 | |
| 			int c;
 | |
| 
 | |
| 			while ((c = nextopt("ls:")) != '\0')
 | |
| 				switch (c) {
 | |
| 				case 'l':
 | |
| 					list = 1;
 | |
| 					break;
 | |
| 				case 's':
 | |
| 					signo = decode_signal(optionarg, 1);
 | |
| 					if (signo < 0) {
 | |
| 						error(
 | |
| 							"invalid signal number or name: %s",
 | |
| 							optionarg
 | |
| 						);
 | |
| 					}
 | |
| 					break;
 | |
| #ifdef DEBUG
 | |
| 				default:
 | |
| 					error(
 | |
| 	"nextopt returned character code 0%o", c);
 | |
| #endif
 | |
| 			}
 | |
| 		} else
 | |
| 			argptr++;
 | |
| 	}
 | |
| 
 | |
| 	if (!list && signo < 0)
 | |
| 		signo = SIGTERM;
 | |
| 
 | |
| 	if ((signo < 0 || !*argptr) ^ list) {
 | |
| 		goto usage;
 | |
| 	}
 | |
| 
 | |
| 	if (list) {
 | |
| 		const char *name;
 | |
| 
 | |
| 		if (!*argptr) {
 | |
| 			out1str("0\n");
 | |
| 			for (i = 1; i < NSIG; i++) {
 | |
| 				name = u_signal_names(0, &i, 1);
 | |
| 				if(name)
 | |
| 					printf(snlfmt, name);
 | |
| 			}
 | |
| 			return 0;
 | |
| 		}
 | |
| 		name = u_signal_names(*argptr, &signo, -1);
 | |
| 		if (name)
 | |
| 			printf(snlfmt, name);
 | |
| 		else
 | |
| 			error("invalid signal number or exit status: %s",
 | |
| 			      *argptr);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	do {
 | |
| 		if (**argptr == '%') {
 | |
| 			jp = getjob(*argptr);
 | |
| 			if (jp->jobctl == 0)
 | |
| 				error("job %s not created under job control",
 | |
| 				      *argptr);
 | |
| 			pid = -jp->ps[0].pid;
 | |
| 		} else
 | |
| 			pid = atoi(*argptr);
 | |
| 		if (kill(pid, signo) != 0)
 | |
| 			error("%s: %m", *argptr);
 | |
| 	} while (*++argptr);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| fgcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct job *jp;
 | |
| 	int pgrp;
 | |
| 	int status;
 | |
| 
 | |
| 	jp = getjob(argv[1]);
 | |
| 	if (jp->jobctl == 0)
 | |
| 		error("job not created under job control");
 | |
| 	pgrp = jp->ps[0].pid;
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 	ioctl(2, TIOCSPGRP, (char *)&pgrp);
 | |
| #else
 | |
| 	tcsetpgrp(2, pgrp);
 | |
| #endif
 | |
| 	restartjob(jp);
 | |
| 	INTOFF;
 | |
| 	status = waitforjob(jp);
 | |
| 	INTON;
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| bgcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct job *jp;
 | |
| 
 | |
| 	do {
 | |
| 		jp = getjob(*++argv);
 | |
| 		if (jp->jobctl == 0)
 | |
| 			error("job not created under job control");
 | |
| 		restartjob(jp);
 | |
| 	} while (--argc > 1);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| restartjob(jp)
 | |
| 	struct job *jp;
 | |
| {
 | |
| 	struct procstat *ps;
 | |
| 	int i;
 | |
| 
 | |
| 	if (jp->state == JOBDONE)
 | |
| 		return;
 | |
| 	INTOFF;
 | |
| 	killpg(jp->ps[0].pid, SIGCONT);
 | |
| 	for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
 | |
| 		if (WIFSTOPPED(ps->status)) {
 | |
| 			ps->status = -1;
 | |
| 			jp->state = 0;
 | |
| 		}
 | |
| 	}
 | |
| 	INTON;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static void showjobs(int change);
 | |
| 
 | |
| 
 | |
| static int
 | |
| jobscmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	showjobs(0);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Print a list of jobs.  If "change" is nonzero, only print jobs whose
 | |
|  * statuses have changed since the last call to showjobs.
 | |
|  *
 | |
|  * If the shell is interrupted in the process of creating a job, the
 | |
|  * result may be a job structure containing zero processes.  Such structures
 | |
|  * will be freed here.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| showjobs(change)
 | |
| 	int change;
 | |
| {
 | |
| 	int jobno;
 | |
| 	int procno;
 | |
| 	int i;
 | |
| 	struct job *jp;
 | |
| 	struct procstat *ps;
 | |
| 	int col;
 | |
| 	char s[64];
 | |
| 
 | |
| 	TRACE(("showjobs(%d) called\n", change));
 | |
| 	while (dowait(0, (struct job *)NULL) > 0);
 | |
| 	for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
 | |
| 		if (! jp->used)
 | |
| 			continue;
 | |
| 		if (jp->nprocs == 0) {
 | |
| 			freejob(jp);
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (change && ! jp->changed)
 | |
| 			continue;
 | |
| 		procno = jp->nprocs;
 | |
| 		for (ps = jp->ps ; ; ps++) {    /* for each process */
 | |
| 			if (ps == jp->ps)
 | |
| 				snprintf(s, 64, "[%d] %ld ", jobno,
 | |
| 				    (long)ps->pid);
 | |
| 			else
 | |
| 				snprintf(s, 64, "    %ld ",
 | |
| 				    (long)ps->pid);
 | |
| 			out1str(s);
 | |
| 			col = strlen(s);
 | |
| 			s[0] = '\0';
 | |
| 			if (ps->status == -1) {
 | |
| 				/* don't print anything */
 | |
| 			} else if (WIFEXITED(ps->status)) {
 | |
| 				snprintf(s, 64, "Exit %d",
 | |
| 				       WEXITSTATUS(ps->status));
 | |
| 			} else {
 | |
| #ifdef JOBS
 | |
| 				if (WIFSTOPPED(ps->status))
 | |
| 					i = WSTOPSIG(ps->status);
 | |
| 				else /* WIFSIGNALED(ps->status) */
 | |
| #endif
 | |
| 					i = WTERMSIG(ps->status);
 | |
| 				if ((i & 0x7F) < NSIG && sys_siglist[i & 0x7F])
 | |
| 					strcpy(s, sys_siglist[i & 0x7F]);
 | |
| 				else
 | |
| 					snprintf(s, 64, "Signal %d", i & 0x7F);
 | |
| 				if (WCOREDUMP(ps->status))
 | |
| 					strcat(s, " (core dumped)");
 | |
| 			}
 | |
| 			out1str(s);
 | |
| 			col += strlen(s);
 | |
| 			printf(
 | |
| 				"%*c%s\n", 30 - col >= 0 ? 30 - col : 0, ' ',
 | |
| 				ps->cmd
 | |
| 			);
 | |
| 			if (--procno <= 0)
 | |
| 				break;
 | |
| 		}
 | |
| 		jp->changed = 0;
 | |
| 		if (jp->state == JOBDONE) {
 | |
| 			freejob(jp);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Mark a job structure as unused.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| freejob(struct job *jp)
 | |
| {
 | |
| 	const struct procstat *ps;
 | |
| 	int i;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) {
 | |
| 		if (ps->cmd != nullstr)
 | |
| 			ckfree(ps->cmd);
 | |
| 	}
 | |
| 	if (jp->ps != &jp->ps0)
 | |
| 		ckfree(jp->ps);
 | |
| 	jp->used = 0;
 | |
| #ifdef JOBS
 | |
| 	if (curjob == jp - jobtab + 1)
 | |
| 		curjob = 0;
 | |
| #endif
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static int
 | |
| waitcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct job *job;
 | |
| 	int status, retval;
 | |
| 	struct job *jp;
 | |
| 
 | |
| 	if (--argc > 0) {
 | |
| start:
 | |
| 		job = getjob(*++argv);
 | |
| 	} else {
 | |
| 		job = NULL;
 | |
| 	}
 | |
| 	for (;;) {      /* loop until process terminated or stopped */
 | |
| 		if (job != NULL) {
 | |
| 			if (job->state) {
 | |
| 				status = job->ps[job->nprocs - 1].status;
 | |
| 				if (! iflag)
 | |
| 					freejob(job);
 | |
| 				if (--argc) {
 | |
| 					goto start;
 | |
| 				}
 | |
| 				if (WIFEXITED(status))
 | |
| 					retval = WEXITSTATUS(status);
 | |
| #ifdef JOBS
 | |
| 				else if (WIFSTOPPED(status))
 | |
| 					retval = WSTOPSIG(status) + 128;
 | |
| #endif
 | |
| 				else {
 | |
| 					/* XXX: limits number of signals */
 | |
| 					retval = WTERMSIG(status) + 128;
 | |
| 				}
 | |
| 				return retval;
 | |
| 			}
 | |
| 		} else {
 | |
| 			for (jp = jobtab ; ; jp++) {
 | |
| 				if (jp >= jobtab + njobs) {     /* no running procs */
 | |
| 					return 0;
 | |
| 				}
 | |
| 				if (jp->used && jp->state == 0)
 | |
| 					break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (dowait(2, 0) < 0 && errno == EINTR) {
 | |
| 			return 129;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Convert a job name to a job structure.
 | |
|  */
 | |
| 
 | |
| static struct job *
 | |
| getjob(const char *name)
 | |
| {
 | |
| 	int jobno;
 | |
| 	struct job *jp;
 | |
| 	int pid;
 | |
| 	int i;
 | |
| 
 | |
| 	if (name == NULL) {
 | |
| #ifdef JOBS
 | |
| currentjob:
 | |
| 		if ((jobno = curjob) == 0 || jobtab[jobno - 1].used == 0)
 | |
| 			error("No current job");
 | |
| 		return &jobtab[jobno - 1];
 | |
| #else
 | |
| 		error("No current job");
 | |
| #endif
 | |
| 	} else if (name[0] == '%') {
 | |
| 		if (is_digit(name[1])) {
 | |
| 			jobno = number(name + 1);
 | |
| 			if (jobno > 0 && jobno <= njobs
 | |
| 			 && jobtab[jobno - 1].used != 0)
 | |
| 				return &jobtab[jobno - 1];
 | |
| #ifdef JOBS
 | |
| 		} else if (name[1] == '%' && name[2] == '\0') {
 | |
| 			goto currentjob;
 | |
| #endif
 | |
| 		} else {
 | |
| 			struct job *found = NULL;
 | |
| 			for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
 | |
| 				if (jp->used && jp->nprocs > 0
 | |
| 				 && prefix(name + 1, jp->ps[0].cmd)) {
 | |
| 					if (found)
 | |
| 						error("%s: ambiguous", name);
 | |
| 					found = jp;
 | |
| 				}
 | |
| 			}
 | |
| 			if (found)
 | |
| 				return found;
 | |
| 		}
 | |
| 	} else if (is_number(name, &pid)) {
 | |
| 		for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
 | |
| 			if (jp->used && jp->nprocs > 0
 | |
| 			 && jp->ps[jp->nprocs - 1].pid == pid)
 | |
| 				return jp;
 | |
| 		}
 | |
| 	}
 | |
| 	error("No such job: %s", name);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Return a new job structure,
 | |
|  */
 | |
| 
 | |
| static struct job *
 | |
| makejob(const union node *node, int nprocs)
 | |
| {
 | |
| 	int i;
 | |
| 	struct job *jp;
 | |
| 
 | |
| 	for (i = njobs, jp = jobtab ; ; jp++) {
 | |
| 		if (--i < 0) {
 | |
| 			INTOFF;
 | |
| 			if (njobs == 0) {
 | |
| 				jobtab = ckmalloc(4 * sizeof jobtab[0]);
 | |
| 			} else {
 | |
| 				jp = ckmalloc((njobs + 4) * sizeof jobtab[0]);
 | |
| 				memcpy(jp, jobtab, njobs * sizeof jp[0]);
 | |
| 				/* Relocate `ps' pointers */
 | |
| 				for (i = 0; i < njobs; i++)
 | |
| 					if (jp[i].ps == &jobtab[i].ps0)
 | |
| 						jp[i].ps = &jp[i].ps0;
 | |
| 				ckfree(jobtab);
 | |
| 				jobtab = jp;
 | |
| 			}
 | |
| 			jp = jobtab + njobs;
 | |
| 			for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0);
 | |
| 			INTON;
 | |
| 			break;
 | |
| 		}
 | |
| 		if (jp->used == 0)
 | |
| 			break;
 | |
| 	}
 | |
| 	INTOFF;
 | |
| 	jp->state = 0;
 | |
| 	jp->used = 1;
 | |
| 	jp->changed = 0;
 | |
| 	jp->nprocs = 0;
 | |
| #ifdef JOBS
 | |
| 	jp->jobctl = jobctl;
 | |
| #endif
 | |
| 	if (nprocs > 1) {
 | |
| 		jp->ps = ckmalloc(nprocs * sizeof (struct procstat));
 | |
| 	} else {
 | |
| 		jp->ps = &jp->ps0;
 | |
| 	}
 | |
| 	INTON;
 | |
| 	TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
 | |
| 	    jp - jobtab + 1));
 | |
| 	return jp;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Fork of a subshell.  If we are doing job control, give the subshell its
 | |
|  * own process group.  Jp is a job structure that the job is to be added to.
 | |
|  * N is the command that will be evaluated by the child.  Both jp and n may
 | |
|  * be NULL.  The mode parameter can be one of the following:
 | |
|  *      FORK_FG - Fork off a foreground process.
 | |
|  *      FORK_BG - Fork off a background process.
 | |
|  *      FORK_NOJOB - Like FORK_FG, but don't give the process its own
 | |
|  *                   process group even if job control is on.
 | |
|  *
 | |
|  * When job control is turned off, background processes have their standard
 | |
|  * input redirected to /dev/null (except for the second and later processes
 | |
|  * in a pipeline).
 | |
|  */
 | |
| 
 | |
| 
 | |
| 
 | |
| static int
 | |
| forkshell(struct job *jp, const union node *n, int mode)
 | |
| {
 | |
| 	int pid;
 | |
| #ifdef JOBS
 | |
| 	int pgrp;
 | |
| #endif
 | |
| 	const char *devnull = _PATH_DEVNULL;
 | |
| 	const char *nullerr = "Can't open %s";
 | |
| 
 | |
| 	TRACE(("forkshell(%%%d, 0x%lx, %d) called\n", jp - jobtab, (long)n,
 | |
| 	    mode));
 | |
| 	INTOFF;
 | |
| #if !defined(__UCLIBC__) || defined(__UCLIBC_HAS_MMU__)
 | |
| 	pid = fork();
 | |
| #else
 | |
| 	pid = vfork();
 | |
| #endif
 | |
| 	if (pid == -1) {
 | |
| 		TRACE(("Fork failed, errno=%d\n", errno));
 | |
| 		INTON;
 | |
| 		error("Cannot fork");
 | |
| 	}
 | |
| 	if (pid == 0) {
 | |
| 		struct job *p;
 | |
| 		int wasroot;
 | |
| 		int i;
 | |
| 
 | |
| 		TRACE(("Child shell %d\n", getpid()));
 | |
| 		wasroot = rootshell;
 | |
| 		rootshell = 0;
 | |
| 		closescript();
 | |
| 		INTON;
 | |
| 		clear_traps();
 | |
| #ifdef JOBS
 | |
| 		jobctl = 0;             /* do job control only in root shell */
 | |
| 		if (wasroot && mode != FORK_NOJOB && mflag) {
 | |
| 			if (jp == NULL || jp->nprocs == 0)
 | |
| 				pgrp = getpid();
 | |
| 			else
 | |
| 				pgrp = jp->ps[0].pid;
 | |
| 			setpgid(0, pgrp);
 | |
| 			if (mode == FORK_FG) {
 | |
| 				/*** this causes superfluous TIOCSPGRPS ***/
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 				if (ioctl(2, TIOCSPGRP, (char *)&pgrp) < 0)
 | |
| 					error("TIOCSPGRP failed, errno=%d", errno);
 | |
| #else
 | |
| 				if (tcsetpgrp(2, pgrp) < 0)
 | |
| 					error("tcsetpgrp failed, errno=%d", errno);
 | |
| #endif
 | |
| 			}
 | |
| 			setsignal(SIGTSTP);
 | |
| 			setsignal(SIGTTOU);
 | |
| 		} else if (mode == FORK_BG) {
 | |
| 			ignoresig(SIGINT);
 | |
| 			ignoresig(SIGQUIT);
 | |
| 			if ((jp == NULL || jp->nprocs == 0) &&
 | |
| 			    ! fd0_redirected_p ()) {
 | |
| 				close(0);
 | |
| 				if (open(devnull, O_RDONLY) != 0)
 | |
| 					error(nullerr, devnull);
 | |
| 			}
 | |
| 		}
 | |
| #else
 | |
| 		if (mode == FORK_BG) {
 | |
| 			ignoresig(SIGINT);
 | |
| 			ignoresig(SIGQUIT);
 | |
| 			if ((jp == NULL || jp->nprocs == 0) &&
 | |
| 			    ! fd0_redirected_p ()) {
 | |
| 				close(0);
 | |
| 				if (open(devnull, O_RDONLY) != 0)
 | |
| 					error(nullerr, devnull);
 | |
| 			}
 | |
| 		}
 | |
| #endif
 | |
| 		for (i = njobs, p = jobtab ; --i >= 0 ; p++)
 | |
| 			if (p->used)
 | |
| 				freejob(p);
 | |
| 		if (wasroot && iflag) {
 | |
| 			setsignal(SIGINT);
 | |
| 			setsignal(SIGQUIT);
 | |
| 			setsignal(SIGTERM);
 | |
| 		}
 | |
| 		return pid;
 | |
| 	}
 | |
| #ifdef JOBS
 | |
| 	if (rootshell && mode != FORK_NOJOB && mflag) {
 | |
| 		if (jp == NULL || jp->nprocs == 0)
 | |
| 			pgrp = pid;
 | |
| 		else
 | |
| 			pgrp = jp->ps[0].pid;
 | |
| 		setpgid(pid, pgrp);
 | |
| 	}
 | |
| #endif
 | |
| 	if (mode == FORK_BG)
 | |
| 		backgndpid = pid;               /* set $! */
 | |
| 	if (jp) {
 | |
| 		struct procstat *ps = &jp->ps[jp->nprocs++];
 | |
| 		ps->pid = pid;
 | |
| 		ps->status = -1;
 | |
| 		ps->cmd = nullstr;
 | |
| 		if (iflag && rootshell && n)
 | |
| 			ps->cmd = commandtext(n);
 | |
| 	}
 | |
| 	INTON;
 | |
| 	TRACE(("In parent shell:  child = %d\n", pid));
 | |
| 	return pid;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Wait for job to finish.
 | |
|  *
 | |
|  * Under job control we have the problem that while a child process is
 | |
|  * running interrupts generated by the user are sent to the child but not
 | |
|  * to the shell.  This means that an infinite loop started by an inter-
 | |
|  * active user may be hard to kill.  With job control turned off, an
 | |
|  * interactive user may place an interactive program inside a loop.  If
 | |
|  * the interactive program catches interrupts, the user doesn't want
 | |
|  * these interrupts to also abort the loop.  The approach we take here
 | |
|  * is to have the shell ignore interrupt signals while waiting for a
 | |
|  * forground process to terminate, and then send itself an interrupt
 | |
|  * signal if the child process was terminated by an interrupt signal.
 | |
|  * Unfortunately, some programs want to do a bit of cleanup and then
 | |
|  * exit on interrupt; unless these processes terminate themselves by
 | |
|  * sending a signal to themselves (instead of calling exit) they will
 | |
|  * confuse this approach.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| waitforjob(struct job *jp)
 | |
| {
 | |
| #ifdef JOBS
 | |
| 	int mypgrp = getpgrp();
 | |
| #endif
 | |
| 	int status;
 | |
| 	int st;
 | |
| 	struct sigaction act, oact;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	intreceived = 0;
 | |
| #ifdef JOBS
 | |
| 	if (!jobctl) {
 | |
| #else
 | |
| 	if (!iflag) {
 | |
| #endif
 | |
| 		sigaction(SIGINT, 0, &act);
 | |
| 		act.sa_handler = waitonint;
 | |
| 		sigaction(SIGINT, &act, &oact);
 | |
| 	}
 | |
| 	TRACE(("waitforjob(%%%d) called\n", jp - jobtab + 1));
 | |
| 	while (jp->state == 0) {
 | |
| 		dowait(1, jp);
 | |
| 	}
 | |
| #ifdef JOBS
 | |
| 	if (!jobctl) {
 | |
| #else
 | |
| 	if (!iflag) {
 | |
| #endif
 | |
| 		sigaction(SIGINT, &oact, 0);
 | |
| 		if (intreceived && trap[SIGINT]) kill(getpid(), SIGINT);
 | |
| 	}
 | |
| #ifdef JOBS
 | |
| 	if (jp->jobctl) {
 | |
| #ifdef OLD_TTY_DRIVER
 | |
| 		if (ioctl(2, TIOCSPGRP, (char *)&mypgrp) < 0)
 | |
| 			error("TIOCSPGRP failed, errno=%d\n", errno);
 | |
| #else
 | |
| 		if (tcsetpgrp(2, mypgrp) < 0)
 | |
| 			error("tcsetpgrp failed, errno=%d\n", errno);
 | |
| #endif
 | |
| 	}
 | |
| 	if (jp->state == JOBSTOPPED)
 | |
| 		curjob = jp - jobtab + 1;
 | |
| #endif
 | |
| 	status = jp->ps[jp->nprocs - 1].status;
 | |
| 	/* convert to 8 bits */
 | |
| 	if (WIFEXITED(status))
 | |
| 		st = WEXITSTATUS(status);
 | |
| #ifdef JOBS
 | |
| 	else if (WIFSTOPPED(status))
 | |
| 		st = WSTOPSIG(status) + 128;
 | |
| #endif
 | |
| 	else
 | |
| 		st = WTERMSIG(status) + 128;
 | |
| #ifdef JOBS
 | |
| 	if (jp->jobctl) {
 | |
| 		/*
 | |
| 		 * This is truly gross.
 | |
| 		 * If we're doing job control, then we did a TIOCSPGRP which
 | |
| 		 * caused us (the shell) to no longer be in the controlling
 | |
| 		 * session -- so we wouldn't have seen any ^C/SIGINT.  So, we
 | |
| 		 * intuit from the subprocess exit status whether a SIGINT
 | |
| 		 * occured, and if so interrupt ourselves.  Yuck.  - mycroft
 | |
| 		 */
 | |
| 		if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
 | |
| 			raise(SIGINT);
 | |
| 	}
 | |
| 	if (jp->state == JOBDONE)
 | |
| 
 | |
| #endif
 | |
| 		freejob(jp);
 | |
| 	INTON;
 | |
| 	return st;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Wait for a process to terminate.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * Do a wait system call.  If job control is compiled in, we accept
 | |
|  * stopped processes.  If block is zero, we return a value of zero
 | |
|  * rather than blocking.
 | |
|  *
 | |
|  * System V doesn't have a non-blocking wait system call.  It does
 | |
|  * have a SIGCLD signal that is sent to a process when one of it's
 | |
|  * children dies.  The obvious way to use SIGCLD would be to install
 | |
|  * a handler for SIGCLD which simply bumped a counter when a SIGCLD
 | |
|  * was received, and have waitproc bump another counter when it got
 | |
|  * the status of a process.  Waitproc would then know that a wait
 | |
|  * system call would not block if the two counters were different.
 | |
|  * This approach doesn't work because if a process has children that
 | |
|  * have not been waited for, System V will send it a SIGCLD when it
 | |
|  * installs a signal handler for SIGCLD.  What this means is that when
 | |
|  * a child exits, the shell will be sent SIGCLD signals continuously
 | |
|  * until is runs out of stack space, unless it does a wait call before
 | |
|  * restoring the signal handler.  The code below takes advantage of
 | |
|  * this (mis)feature by installing a signal handler for SIGCLD and
 | |
|  * then checking to see whether it was called.  If there are any
 | |
|  * children to be waited for, it will be.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| static inline int
 | |
| waitproc(int block, int *status)
 | |
| {
 | |
| 	int flags;
 | |
| 
 | |
| 	flags = 0;
 | |
| #ifdef JOBS
 | |
| 	if (jobctl)
 | |
| 		flags |= WUNTRACED;
 | |
| #endif
 | |
| 	if (block == 0)
 | |
| 		flags |= WNOHANG;
 | |
| 	return wait3(status, flags, (struct rusage *)NULL);
 | |
| }
 | |
| 
 | |
| static int
 | |
| dowait(int block, struct job *job)
 | |
| {
 | |
| 	int pid;
 | |
| 	int status;
 | |
| 	struct procstat *sp;
 | |
| 	struct job *jp;
 | |
| 	struct job *thisjob;
 | |
| 	int done;
 | |
| 	int stopped;
 | |
| 	int core;
 | |
| 	int sig;
 | |
| 
 | |
| 	TRACE(("dowait(%d) called\n", block));
 | |
| 	do {
 | |
| 		pid = waitproc(block, &status);
 | |
| 		TRACE(("wait returns %d, status=%d\n", pid, status));
 | |
| 	} while (!(block & 2) && pid == -1 && errno == EINTR);
 | |
| 	if (pid <= 0)
 | |
| 		return pid;
 | |
| 	INTOFF;
 | |
| 	thisjob = NULL;
 | |
| 	for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
 | |
| 		if (jp->used) {
 | |
| 			done = 1;
 | |
| 			stopped = 1;
 | |
| 			for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
 | |
| 				if (sp->pid == -1)
 | |
| 					continue;
 | |
| 				if (sp->pid == pid) {
 | |
| 					TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", pid, sp->status, status));
 | |
| 					sp->status = status;
 | |
| 					thisjob = jp;
 | |
| 				}
 | |
| 				if (sp->status == -1)
 | |
| 					stopped = 0;
 | |
| 				else if (WIFSTOPPED(sp->status))
 | |
| 					done = 0;
 | |
| 			}
 | |
| 			if (stopped) {          /* stopped or done */
 | |
| 				int state = done? JOBDONE : JOBSTOPPED;
 | |
| 				if (jp->state != state) {
 | |
| 					TRACE(("Job %d: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
 | |
| 					jp->state = state;
 | |
| #ifdef JOBS
 | |
| 					if (done && curjob == jp - jobtab + 1)
 | |
| 						curjob = 0;             /* no current job */
 | |
| #endif
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	INTON;
 | |
| 	if (! rootshell || ! iflag || (job && thisjob == job)) {
 | |
| 		core = WCOREDUMP(status);
 | |
| #ifdef JOBS
 | |
| 		if (WIFSTOPPED(status)) sig = WSTOPSIG(status);
 | |
| 		else
 | |
| #endif
 | |
| 		if (WIFEXITED(status)) sig = 0;
 | |
| 		else sig = WTERMSIG(status);
 | |
| 
 | |
| 		if (sig != 0 && sig != SIGINT && sig != SIGPIPE) {
 | |
| 			if (thisjob != job)
 | |
| 				out2fmt("%d: ", pid);
 | |
| #ifdef JOBS
 | |
| 			if (sig == SIGTSTP && rootshell && iflag)
 | |
| 				out2fmt("%%%ld ",
 | |
| 				    (long)(job - jobtab + 1));
 | |
| #endif
 | |
| 			if (sig < NSIG && sys_siglist[sig])
 | |
| 				out2str(sys_siglist[sig]);
 | |
| 			else
 | |
| 				out2fmt("Signal %d", sig);
 | |
| 			if (core)
 | |
| 				out2str(" - core dumped");
 | |
| 			out2c('\n');
 | |
| 		} else {
 | |
| 			TRACE(("Not printing status: status=%d, sig=%d\n",
 | |
| 			       status, sig));
 | |
| 		}
 | |
| 	} else {
 | |
| 		TRACE(("Not printing status, rootshell=%d, job=0x%x\n", rootshell, job));
 | |
| 		if (thisjob)
 | |
| 			thisjob->changed = 1;
 | |
| 	}
 | |
| 	return pid;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * return 1 if there are stopped jobs, otherwise 0
 | |
|  */
 | |
| static int
 | |
| stoppedjobs(void)
 | |
| {
 | |
| 	int jobno;
 | |
| 	struct job *jp;
 | |
| 
 | |
| 	if (job_warning)
 | |
| 		return (0);
 | |
| 	for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) {
 | |
| 		if (jp->used == 0)
 | |
| 			continue;
 | |
| 		if (jp->state == JOBSTOPPED) {
 | |
| 			out2str("You have stopped jobs.\n");
 | |
| 			job_warning = 2;
 | |
| 			return (1);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return (0);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Return a string identifying a command (to be printed by the
 | |
|  * jobs command.
 | |
|  */
 | |
| 
 | |
| static char *cmdnextc;
 | |
| static int cmdnleft;
 | |
| #define MAXCMDTEXT      200
 | |
| 
 | |
| static void
 | |
| cmdputs(const char *s)
 | |
| {
 | |
| 	const char *p;
 | |
| 	char *q;
 | |
| 	char c;
 | |
| 	int subtype = 0;
 | |
| 
 | |
| 	if (cmdnleft <= 0)
 | |
| 		return;
 | |
| 	p = s;
 | |
| 	q = cmdnextc;
 | |
| 	while ((c = *p++) != '\0') {
 | |
| 		if (c == CTLESC)
 | |
| 			*q++ = *p++;
 | |
| 		else if (c == CTLVAR) {
 | |
| 			*q++ = '$';
 | |
| 			if (--cmdnleft > 0)
 | |
| 				*q++ = '{';
 | |
| 			subtype = *p++;
 | |
| 		} else if (c == '=' && subtype != 0) {
 | |
| 			*q++ = "}-+?="[(subtype & VSTYPE) - VSNORMAL];
 | |
| 			subtype = 0;
 | |
| 		} else if (c == CTLENDVAR) {
 | |
| 			*q++ = '}';
 | |
| 		} else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE)
 | |
| 			cmdnleft++;             /* ignore it */
 | |
| 		else
 | |
| 			*q++ = c;
 | |
| 		if (--cmdnleft <= 0) {
 | |
| 			*q++ = '.';
 | |
| 			*q++ = '.';
 | |
| 			*q++ = '.';
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	cmdnextc = q;
 | |
| }
 | |
| 
 | |
| #define CMDTXT_TABLE
 | |
| #ifdef CMDTXT_TABLE
 | |
| /*
 | |
|  * To collect a lot of redundant code in cmdtxt() case statements, we
 | |
|  * implement a mini language here.  Each type of node struct has an
 | |
|  * associated instruction sequence that operates on its members via
 | |
|  * their offsets.  The instruction are pack in unsigned chars with
 | |
|  * format   IIDDDDDE   where the bits are
 | |
|  *   I : part of the instruction opcode, which are
 | |
|  *       00 : member is a pointer to another node -- process it recursively
 | |
|  *       40 : member is a pointer to a char string -- output it
 | |
|  *       80 : output the string whose index is stored in the data field
 | |
|  *       CC : flag signaling that this case needs external processing
 | |
|  *   D : data - either the (shifted) index of a fixed string to output or
 | |
|  *              the actual offset of the member to operate on in the struct
 | |
|  *              (since we assume bit 0 is set, the offset is not shifted)
 | |
|  *   E : flag signaling end of instruction sequence
 | |
|  *
 | |
|  * WARNING: In order to handle larger offsets for 64bit archs, this code
 | |
|  *          assumes that no offset can be an odd number and stores the
 | |
|  *          end-of-instructions flag in bit 0.
 | |
|  */
 | |
| 
 | |
| #define CMDTXT_NOMORE      0x01 /* NOTE: no offset should be odd */
 | |
| #define CMDTXT_CHARPTR     0x40
 | |
| #define CMDTXT_STRING      0x80
 | |
| #define CMDTXT_SPECIAL     0xC0
 | |
| #define CMDTXT_OFFSETMASK  0x3E
 | |
| 
 | |
| static const char * const cmdtxt_strings[] = {
 | |
|  /* 0     1    2    3       4       5      6          7     */
 | |
| 	"; ", "(", ")", " && ", " || ", "if ", "; then ", "...",
 | |
|  /* 8         9        10       11        12      13       */
 | |
|     "while ", "; do ", "; done", "until ", "for ", " in ...",
 | |
|  /* 14       15     16        17     */
 | |
| 	"case ", "???", "() ...", "<<..."
 | |
| };
 | |
| 
 | |
| static const char * const redir_strings[] = {
 | |
| 	">", "<", "<>", ">>", ">|", ">&", "<&"
 | |
| };
 | |
| 
 | |
| static const unsigned char cmdtxt_ops[] = {
 | |
| #define CMDTXT_NSEMI    0
 | |
| 	offsetof(union node, nbinary.ch1),
 | |
| 	0|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch2)|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NCMD     (CMDTXT_NSEMI + 3)
 | |
| #define CMDTXT_NPIPE    (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NCASE    (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NTO      (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NFROM    (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NFROMTO  (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NAPPEND  (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NTOOV    (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NTOFD    (CMDTXT_NCMD)
 | |
| #define  CMDTXT_NFROMFD  (CMDTXT_NCMD)
 | |
| 	CMDTXT_SPECIAL,
 | |
| #define CMDTXT_NREDIR   (CMDTXT_NPIPE + 1)
 | |
| #define CMDTXT_NBACKGND (CMDTXT_NREDIR)
 | |
| 	offsetof(union node, nredir.n)|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NSUBSHELL (CMDTXT_NBACKGND + 1)
 | |
| 	(1*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nredir.n),
 | |
| 	(2*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NAND     (CMDTXT_NSUBSHELL + 3)
 | |
| 	offsetof(union node, nbinary.ch1),
 | |
| 	(3*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch2)|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NOR      (CMDTXT_NAND + 3)
 | |
| 	offsetof(union node, nbinary.ch1),
 | |
| 	(4*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch2)|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NIF      (CMDTXT_NOR + 3)
 | |
| 	(5*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nif.test),
 | |
| 	(6*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nif.ifpart),
 | |
| 	(7*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NWHILE   (CMDTXT_NIF + 5)
 | |
| 	(8*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch1),
 | |
| 	(9*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch2),
 | |
| 	(10*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NUNTIL   (CMDTXT_NWHILE + 5)
 | |
| 	(11*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch1),
 | |
| 	(9*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nbinary.ch2),
 | |
| 	(10*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NFOR     (CMDTXT_NUNTIL + 5)
 | |
| 	(12*2)|CMDTXT_STRING,
 | |
| 	offsetof(union node, nfor.var)|CMDTXT_CHARPTR,
 | |
| 	(13*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NCLIST   (CMDTXT_NFOR + 3) /* TODO: IS THIS CORRECT??? */
 | |
| #define  CMDTXT_NNOT     (CMDTXT_NCLIST)        /* TODO: IS THIS CORRECT??? */
 | |
| 	(15*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NDEFUN   (CMDTXT_NCLIST + 1)
 | |
| 	offsetof(union node, narg.text)|CMDTXT_CHARPTR,
 | |
| 	(16*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NARG     (CMDTXT_NDEFUN + 2)
 | |
| 	offsetof(union node, narg.text)|CMDTXT_CHARPTR|CMDTXT_NOMORE,
 | |
| #define CMDTXT_NHERE    (CMDTXT_NARG + 1)
 | |
| #define CMDTXT_NXHERE   (CMDTXT_NHERE)
 | |
| 	(17*2)|CMDTXT_STRING|CMDTXT_NOMORE,
 | |
| };
 | |
| 
 | |
| #if CMDTXT_NXHERE != 36
 | |
| #error CMDTXT_NXHERE
 | |
| #endif
 | |
| 
 | |
| static const unsigned char cmdtxt_ops_index[26] = {
 | |
| 	CMDTXT_NSEMI,
 | |
| 	CMDTXT_NCMD,
 | |
| 	CMDTXT_NPIPE,
 | |
| 	CMDTXT_NREDIR,
 | |
| 	CMDTXT_NBACKGND,
 | |
| 	CMDTXT_NSUBSHELL,
 | |
| 	CMDTXT_NAND,
 | |
| 	CMDTXT_NOR,
 | |
| 	CMDTXT_NIF,
 | |
| 	CMDTXT_NWHILE,
 | |
| 	CMDTXT_NUNTIL,
 | |
| 	CMDTXT_NFOR,
 | |
| 	CMDTXT_NCASE,
 | |
| 	CMDTXT_NCLIST,
 | |
| 	CMDTXT_NDEFUN,
 | |
| 	CMDTXT_NARG,
 | |
| 	CMDTXT_NTO,
 | |
| 	CMDTXT_NFROM,
 | |
| 	CMDTXT_NFROMTO,
 | |
| 	CMDTXT_NAPPEND,
 | |
| 	CMDTXT_NTOOV,
 | |
| 	CMDTXT_NTOFD,
 | |
| 	CMDTXT_NFROMFD,
 | |
| 	CMDTXT_NHERE,
 | |
| 	CMDTXT_NXHERE,
 | |
| 	CMDTXT_NNOT,
 | |
| };
 | |
| 
 | |
| static void
 | |
| cmdtxt(const union node *n)
 | |
| {
 | |
| 	const char *p;
 | |
| 
 | |
| 	if (n == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	p = cmdtxt_ops + (int) cmdtxt_ops_index[n->type];
 | |
| 	if ((*p & CMDTXT_SPECIAL) != CMDTXT_SPECIAL) { /* normal case */
 | |
| 		do {
 | |
| 			if (*p & CMDTXT_STRING) { /* output fixed string */
 | |
| 				cmdputs(cmdtxt_strings[((int)(*p & CMDTXT_OFFSETMASK) >> 1)]);
 | |
| 			} else {
 | |
| 				const char *pf = ((const char *) n)
 | |
| 								  + ((int)(*p & CMDTXT_OFFSETMASK));
 | |
| 				if (*p & CMDTXT_CHARPTR) { /* output dynamic string */
 | |
| 					cmdputs(*((const char **) pf));
 | |
| 				} else {		/* output field */
 | |
| 					cmdtxt(*((const union node **) pf));
 | |
| 				}
 | |
| 			}
 | |
| 		} while (!(*p++ & CMDTXT_NOMORE));
 | |
| 	} else if (n->type == NCMD) {
 | |
| 		union node *np;
 | |
| 		for (np = n->ncmd.args ; np ; np = np->narg.next) {
 | |
| 			cmdtxt(np);
 | |
| 			if (np->narg.next)
 | |
| 				cmdputs(spcstr);
 | |
| 		}
 | |
| 		for (np = n->ncmd.redirect ; np ; np = np->nfile.next) {
 | |
| 			cmdputs(spcstr);
 | |
| 			cmdtxt(np);
 | |
| 		}
 | |
| 	} else if (n->type == NPIPE) {
 | |
| 		struct nodelist *lp;
 | |
| 		for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
 | |
| 			cmdtxt(lp->n);
 | |
| 			if (lp->next)
 | |
| 				cmdputs(" | ");
 | |
| 		}
 | |
| 	} else if (n->type == NCASE) {
 | |
| 		cmdputs(cmdtxt_strings[14]);
 | |
| 		cmdputs(n->ncase.expr->narg.text);
 | |
| 		cmdputs(cmdtxt_strings[13]);
 | |
| 	} else {
 | |
| #if (NTO != 16) || (NFROM != 17) || (NFROMTO != 18) || (NAPPEND != 19) || (NTOOV != 20) || (NTOFD != 21) || (NFROMFD != 22)
 | |
| #error Assumption violated regarding range and ordering of NTO ... NFROMFD!
 | |
| #endif
 | |
| 		char s[2];
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 		assert((n->type >= NTO) && (n->type <= NFROMFD));
 | |
| #endif
 | |
| 
 | |
| 		p = redir_strings[n->type - NTO];
 | |
| 		if (n->nfile.fd != ('>' == *p)) {
 | |
| 			s[0] = n->nfile.fd + '0';
 | |
| 			s[1] = '\0';
 | |
| 			cmdputs(s);
 | |
| 		}
 | |
| 		cmdputs(p);
 | |
| 		if (n->type >= NTOFD) {
 | |
| 			s[0] = n->ndup.dupfd + '0';
 | |
| 			s[1] = '\0';
 | |
| 			cmdputs(s);
 | |
| 		} else {
 | |
| 			cmdtxt(n->nfile.fname);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| #else  /* CMDTXT_TABLE */
 | |
| static void
 | |
| cmdtxt(const union node *n)
 | |
| {
 | |
| 	union node *np;
 | |
| 	struct nodelist *lp;
 | |
| 	const char *p;
 | |
| 	int i;
 | |
| 	char s[2];
 | |
| 
 | |
| 	if (n == NULL)
 | |
| 		return;
 | |
| 	switch (n->type) {
 | |
| 	case NSEMI:
 | |
| 		cmdtxt(n->nbinary.ch1);
 | |
| 		cmdputs("; ");
 | |
| 		cmdtxt(n->nbinary.ch2);
 | |
| 		break;
 | |
| 	case NAND:
 | |
| 		cmdtxt(n->nbinary.ch1);
 | |
| 		cmdputs(" && ");
 | |
| 		cmdtxt(n->nbinary.ch2);
 | |
| 		break;
 | |
| 	case NOR:
 | |
| 		cmdtxt(n->nbinary.ch1);
 | |
| 		cmdputs(" || ");
 | |
| 		cmdtxt(n->nbinary.ch2);
 | |
| 		break;
 | |
| 	case NPIPE:
 | |
| 		for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
 | |
| 			cmdtxt(lp->n);
 | |
| 			if (lp->next)
 | |
| 				cmdputs(" | ");
 | |
| 		}
 | |
| 		break;
 | |
| 	case NSUBSHELL:
 | |
| 		cmdputs("(");
 | |
| 		cmdtxt(n->nredir.n);
 | |
| 		cmdputs(")");
 | |
| 		break;
 | |
| 	case NREDIR:
 | |
| 	case NBACKGND:
 | |
| 		cmdtxt(n->nredir.n);
 | |
| 		break;
 | |
| 	case NIF:
 | |
| 		cmdputs("if ");
 | |
| 		cmdtxt(n->nif.test);
 | |
| 		cmdputs("; then ");
 | |
| 		cmdtxt(n->nif.ifpart);
 | |
| 		cmdputs("...");
 | |
| 		break;
 | |
| 	case NWHILE:
 | |
| 		cmdputs("while ");
 | |
| 		goto until;
 | |
| 	case NUNTIL:
 | |
| 		cmdputs("until ");
 | |
| until:
 | |
| 		cmdtxt(n->nbinary.ch1);
 | |
| 		cmdputs("; do ");
 | |
| 		cmdtxt(n->nbinary.ch2);
 | |
| 		cmdputs("; done");
 | |
| 		break;
 | |
| 	case NFOR:
 | |
| 		cmdputs("for ");
 | |
| 		cmdputs(n->nfor.var);
 | |
| 		cmdputs(" in ...");
 | |
| 		break;
 | |
| 	case NCASE:
 | |
| 		cmdputs("case ");
 | |
| 		cmdputs(n->ncase.expr->narg.text);
 | |
| 		cmdputs(" in ...");
 | |
| 		break;
 | |
| 	case NDEFUN:
 | |
| 		cmdputs(n->narg.text);
 | |
| 		cmdputs("() ...");
 | |
| 		break;
 | |
| 	case NCMD:
 | |
| 		for (np = n->ncmd.args ; np ; np = np->narg.next) {
 | |
| 			cmdtxt(np);
 | |
| 			if (np->narg.next)
 | |
| 				cmdputs(spcstr);
 | |
| 		}
 | |
| 		for (np = n->ncmd.redirect ; np ; np = np->nfile.next) {
 | |
| 			cmdputs(spcstr);
 | |
| 			cmdtxt(np);
 | |
| 		}
 | |
| 		break;
 | |
| 	case NARG:
 | |
| 		cmdputs(n->narg.text);
 | |
| 		break;
 | |
| 	case NTO:
 | |
| 		p = ">";  i = 1;  goto redir;
 | |
| 	case NAPPEND:
 | |
| 		p = ">>";  i = 1;  goto redir;
 | |
| 	case NTOFD:
 | |
| 		p = ">&";  i = 1;  goto redir;
 | |
| 	case NTOOV:
 | |
| 		p = ">|";  i = 1;  goto redir;
 | |
| 	case NFROM:
 | |
| 		p = "<";  i = 0;  goto redir;
 | |
| 	case NFROMFD:
 | |
| 		p = "<&";  i = 0;  goto redir;
 | |
| 	case NFROMTO:
 | |
| 		p = "<>";  i = 0;  goto redir;
 | |
| redir:
 | |
| 		if (n->nfile.fd != i) {
 | |
| 			s[0] = n->nfile.fd + '0';
 | |
| 			s[1] = '\0';
 | |
| 			cmdputs(s);
 | |
| 		}
 | |
| 		cmdputs(p);
 | |
| 		if (n->type == NTOFD || n->type == NFROMFD) {
 | |
| 			s[0] = n->ndup.dupfd + '0';
 | |
| 			s[1] = '\0';
 | |
| 			cmdputs(s);
 | |
| 		} else {
 | |
| 			cmdtxt(n->nfile.fname);
 | |
| 		}
 | |
| 		break;
 | |
| 	case NHERE:
 | |
| 	case NXHERE:
 | |
| 		cmdputs("<<...");
 | |
| 		break;
 | |
| 	default:
 | |
| 		cmdputs("???");
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| #endif /* CMDTXT_TABLE */
 | |
| 
 | |
| static char *
 | |
| commandtext(const union node *n)
 | |
| {
 | |
| 	char *name;
 | |
| 
 | |
| 	cmdnextc = name = ckmalloc(MAXCMDTEXT);
 | |
| 	cmdnleft = MAXCMDTEXT - 4;
 | |
| 	cmdtxt(n);
 | |
| 	*cmdnextc = '\0';
 | |
| 	return name;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void waitonint(int sig) {
 | |
| 	intreceived = 1;
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| #ifdef ASH_MAIL
 | |
| 
 | |
| /*
 | |
|  * Routines to check for mail.
 | |
|  */
 | |
| 
 | |
| 
 | |
| #define MAXMBOXES 10
 | |
| 
 | |
| 
 | |
| static int nmboxes;                     /* number of mailboxes */
 | |
| static time_t mailtime[MAXMBOXES];      /* times of mailboxes */
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Print appropriate message(s) if mail has arrived.  If the argument is
 | |
|  * nozero, then the value of MAIL has changed, so we just update the
 | |
|  * values.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| chkmail(int silent)
 | |
| {
 | |
| 	int i;
 | |
| 	const char *mpath;
 | |
| 	char *p;
 | |
| 	char *q;
 | |
| 	struct stackmark smark;
 | |
| 	struct stat statb;
 | |
| 
 | |
| 	if (silent)
 | |
| 		nmboxes = 10;
 | |
| 	if (nmboxes == 0)
 | |
| 		return;
 | |
| 	setstackmark(&smark);
 | |
| 	mpath = mpathset()? mpathval() : mailval();
 | |
| 	for (i = 0 ; i < nmboxes ; i++) {
 | |
| 		p = padvance(&mpath, nullstr);
 | |
| 		if (p == NULL)
 | |
| 			break;
 | |
| 		if (*p == '\0')
 | |
| 			continue;
 | |
| 		for (q = p ; *q ; q++);
 | |
| #ifdef DEBUG
 | |
| 		if (q[-1] != '/')
 | |
| 			abort();
 | |
| #endif
 | |
| 		q[-1] = '\0';                   /* delete trailing '/' */
 | |
| 		if (stat(p, &statb) < 0)
 | |
| 			statb.st_size = 0;
 | |
| 		if (statb.st_size > mailtime[i] && ! silent) {
 | |
| 			out2fmt(snlfmt,
 | |
| 				pathopt? pathopt : "you have mail");
 | |
| 		}
 | |
| 		mailtime[i] = statb.st_size;
 | |
| 	}
 | |
| 	nmboxes = i;
 | |
| 	popstackmark(&smark);
 | |
| }
 | |
| 
 | |
| #endif /* ASH_MAIL */
 | |
| 
 | |
| #define PROFILE 0
 | |
| 
 | |
| #if PROFILE
 | |
| static short profile_buf[16384];
 | |
| extern int etext();
 | |
| #endif
 | |
| 
 | |
| static void read_profile (const char *);
 | |
| static void cmdloop (int);
 | |
| static void options (int);
 | |
| static void setoption (int, int);
 | |
| static void procargs (int, char **);
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Main routine.  We initialize things, parse the arguments, execute
 | |
|  * profiles if we're a login shell, and then call cmdloop to execute
 | |
|  * commands.  The setjmp call sets up the location to jump to when an
 | |
|  * exception occurs.  When an exception occurs the variable "state"
 | |
|  * is used to figure out how far we had gotten.
 | |
|  */
 | |
| 
 | |
| int
 | |
| ash_main(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct jmploc jmploc;
 | |
| 	struct stackmark smark;
 | |
| 	volatile int state;
 | |
| 	const char *shinit;
 | |
| 
 | |
| 	BLTINCMD = find_builtin("builtin");
 | |
| 	EXECCMD = find_builtin("exec");
 | |
| 	EVALCMD = find_builtin("eval");
 | |
| 
 | |
| #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
 | |
| 	unsetenv("PS1");
 | |
| 	unsetenv("PS2");
 | |
| #endif
 | |
| 
 | |
| #if PROFILE
 | |
| 	monitor(4, etext, profile_buf, sizeof profile_buf, 50);
 | |
| #endif
 | |
| #if defined(linux) || defined(__GNU__)
 | |
| 	signal(SIGCHLD, SIG_DFL);
 | |
| #endif
 | |
| 	state = 0;
 | |
| 	if (setjmp(jmploc.loc)) {
 | |
| 		INTOFF;
 | |
| 		/*
 | |
| 		 * When a shell procedure is executed, we raise the
 | |
| 		 * exception EXSHELLPROC to clean up before executing
 | |
| 		 * the shell procedure.
 | |
| 		 */
 | |
| 		if (exception == EXSHELLPROC) {
 | |
| 			rootpid = getpid();
 | |
| 			rootshell = 1;
 | |
| 			minusc = NULL;
 | |
| 			state = 3;
 | |
| 		} else {
 | |
| 			if (exception == EXEXEC) {
 | |
| 				exitstatus = exerrno;
 | |
| 			} else if (exception == EXERROR) {
 | |
| 				exitstatus = 2;
 | |
| 			}
 | |
| 		    if (state == 0 || iflag == 0 || ! rootshell)
 | |
| 			    exitshell(exitstatus);
 | |
| 		}
 | |
| 		reset();
 | |
| 		if (exception == EXINT) {
 | |
| 			out2c('\n');
 | |
| 		}
 | |
| 		popstackmark(&smark);
 | |
| 		FORCEINTON;                             /* enable interrupts */
 | |
| 		if (state == 1)
 | |
| 			goto state1;
 | |
| 		else if (state == 2)
 | |
| 			goto state2;
 | |
| 		else if (state == 3)
 | |
| 			goto state3;
 | |
| 		else
 | |
| 			goto state4;
 | |
| 	}
 | |
| 	handler = &jmploc;
 | |
| #ifdef DEBUG
 | |
| 	opentrace();
 | |
| 	trputs("Shell args:  ");  trargs(argv);
 | |
| #endif
 | |
| 	rootpid = getpid();
 | |
| 	rootshell = 1;
 | |
| 	init();
 | |
| 	setstackmark(&smark);
 | |
| 	procargs(argc, argv);
 | |
| 	if (argv[0] && argv[0][0] == '-') {
 | |
| 		state = 1;
 | |
| 		read_profile("/etc/profile");
 | |
| state1:
 | |
| 		state = 2;
 | |
| 		read_profile(".profile");
 | |
| 	}
 | |
| state2:
 | |
| 	state = 3;
 | |
| #ifndef linux
 | |
| 	if (getuid() == geteuid() && getgid() == getegid()) {
 | |
| #endif
 | |
| 		if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
 | |
| 			state = 3;
 | |
| 			read_profile(shinit);
 | |
| 		}
 | |
| #ifndef linux
 | |
| 	}
 | |
| #endif
 | |
| state3:
 | |
| 	state = 4;
 | |
| 	if (sflag == 0 || minusc) {
 | |
| 		static const char sigs[] =  {
 | |
| 		    SIGINT, SIGQUIT, SIGHUP,
 | |
| #ifdef SIGTSTP
 | |
| 		    SIGTSTP,
 | |
| #endif
 | |
| 		    SIGPIPE
 | |
| 		};
 | |
| #define SIGSSIZE ((sizeof(sigs)/sizeof(sigs[0])) - 1) /* trailing nul */
 | |
| 		int i;
 | |
| 
 | |
| 		for (i = 0; i < SIGSSIZE; i++)
 | |
| 		    setsignal(sigs[i]);
 | |
| 	}
 | |
| 
 | |
| 	if (minusc)
 | |
| 		evalstring(minusc, 0);
 | |
| 
 | |
| 	if (sflag || minusc == NULL) {
 | |
| state4: /* XXX ??? - why isn't this before the "if" statement */
 | |
| 		cmdloop(1);
 | |
| 	}
 | |
| #if PROFILE
 | |
| 	monitor(0);
 | |
| #endif
 | |
| 	exitshell(exitstatus);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Read and execute commands.  "Top" is nonzero for the top level command
 | |
|  * loop; it turns on prompting if the shell is interactive.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| cmdloop(int top)
 | |
| {
 | |
| 	union node *n;
 | |
| 	struct stackmark smark;
 | |
| 	int inter;
 | |
| 	int numeof = 0;
 | |
| 
 | |
| 	TRACE(("cmdloop(%d) called\n", top));
 | |
| 	setstackmark(&smark);
 | |
| 	for (;;) {
 | |
| 		if (pendingsigs)
 | |
| 			dotrap();
 | |
| 		inter = 0;
 | |
| 		if (iflag && top) {
 | |
| 			inter++;
 | |
| 			showjobs(1);
 | |
| #ifdef ASH_MAIL
 | |
| 			chkmail(0);
 | |
| #endif
 | |
| 			flushall();
 | |
| 		}
 | |
| 		n = parsecmd(inter);
 | |
| 		/* showtree(n); DEBUG */
 | |
| 		if (n == NEOF) {
 | |
| 			if (!top || numeof >= 50)
 | |
| 				break;
 | |
| 			if (!stoppedjobs()) {
 | |
| 				if (!Iflag)
 | |
| 					break;
 | |
| 				out2str("\nUse \"exit\" to leave shell.\n");
 | |
| 			}
 | |
| 			numeof++;
 | |
| 		} else if (n != NULL && nflag == 0) {
 | |
| 			job_warning = (job_warning == 2) ? 1 : 0;
 | |
| 			numeof = 0;
 | |
| 			evaltree(n, 0);
 | |
| 		}
 | |
| 		popstackmark(&smark);
 | |
| 		setstackmark(&smark);
 | |
| 		if (evalskip == SKIPFILE) {
 | |
| 			evalskip = 0;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	popstackmark(&smark);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Read /etc/profile or .profile.  Return on error.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| read_profile(name)
 | |
| 	const char *name;
 | |
| {
 | |
| 	int fd;
 | |
| 	int xflag_save;
 | |
| 	int vflag_save;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	if ((fd = open(name, O_RDONLY)) >= 0)
 | |
| 		setinputfd(fd, 1);
 | |
| 	INTON;
 | |
| 	if (fd < 0)
 | |
| 		return;
 | |
| 	/* -q turns off -x and -v just when executing init files */
 | |
| 	/* Note: Might do a little redundant work, but reduces code size. */
 | |
| 	xflag_save = xflag;
 | |
| 	vflag_save = vflag;
 | |
| 	if (qflag)  {
 | |
| 		vflag = xflag = 0;
 | |
| 	}
 | |
| 	cmdloop(0);
 | |
| 	xflag = xflag_save;
 | |
| 	vflag = vflag_save;
 | |
| 	popfile();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Read a file containing shell functions.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| readcmdfile(const char *name)
 | |
| {
 | |
| 	int fd;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	if ((fd = open(name, O_RDONLY)) >= 0)
 | |
| 		setinputfd(fd, 1);
 | |
| 	else
 | |
| 		error("Can't open %s", name);
 | |
| 	INTON;
 | |
| 	cmdloop(0);
 | |
| 	popfile();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Take commands from a file.  To be compatable we should do a path
 | |
|  * search for the file, which is necessary to find sub-commands.
 | |
|  */
 | |
| 
 | |
| static inline char *
 | |
| find_dot_file(char *mybasename)
 | |
| {
 | |
| 	char *fullname;
 | |
| 	const char *path = pathval();
 | |
| 	struct stat statb;
 | |
| 
 | |
| 	/* don't try this for absolute or relative paths */
 | |
| 	if (strchr(mybasename, '/'))
 | |
| 		return mybasename;
 | |
| 
 | |
| 	while ((fullname = padvance(&path, mybasename)) != NULL) {
 | |
| 		if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
 | |
| 			/*
 | |
| 			 * Don't bother freeing here, since it will
 | |
| 			 * be freed by the caller.
 | |
| 			 */
 | |
| 			return fullname;
 | |
| 		}
 | |
| 		stunalloc(fullname);
 | |
| 	}
 | |
| 
 | |
| 	/* not found in the PATH */
 | |
| 	error("%s: not found", mybasename);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| static int
 | |
| dotcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct strlist *sp;
 | |
| 	volatile struct shparam saveparam;
 | |
| 	exitstatus = 0;
 | |
| 
 | |
| 	for (sp = cmdenviron; sp ; sp = sp->next)
 | |
| 		setvareq(savestr(sp->text), VSTRFIXED|VTEXTFIXED);
 | |
| 
 | |
| 	if (argc >= 2) {                /* That's what SVR2 does */
 | |
| 		char *fullname;
 | |
| 		struct stackmark smark;
 | |
| 
 | |
| 		setstackmark(&smark);
 | |
| 		fullname = find_dot_file(argv[1]);
 | |
| 
 | |
| 		if (argc>2) {
 | |
| 			saveparam = shellparam;
 | |
| 			shellparam.malloc = 0;
 | |
| 			shellparam.nparam = argc - 2;
 | |
| 			shellparam.p = argv + 2;
 | |
| 		};
 | |
| 
 | |
| 		setinputfile(fullname, 1);
 | |
| 		commandname = fullname;
 | |
| 		cmdloop(0);
 | |
| 		popfile();
 | |
| 
 | |
| 		if (argc>2) {
 | |
| 			freeparam(&shellparam);
 | |
| 			shellparam = saveparam;
 | |
| 		};
 | |
| 
 | |
| 		popstackmark(&smark);
 | |
| 	}
 | |
| 	return exitstatus;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| exitcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	if (stoppedjobs())
 | |
| 		return 0;
 | |
| 	if (argc > 1)
 | |
| 		exitstatus = number(argv[1]);
 | |
| 	else
 | |
| 		exitstatus = oexitstatus;
 | |
| 	exitshell(exitstatus);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| static pointer
 | |
| stalloc(int nbytes)
 | |
| {
 | |
| 	char *p;
 | |
| 
 | |
| 	nbytes = ALIGN(nbytes);
 | |
| 	if (nbytes > stacknleft) {
 | |
| 		int blocksize;
 | |
| 		struct stack_block *sp;
 | |
| 
 | |
| 		blocksize = nbytes;
 | |
| 		if (blocksize < MINSIZE)
 | |
| 			blocksize = MINSIZE;
 | |
| 		INTOFF;
 | |
| 		sp = ckmalloc(sizeof(struct stack_block) - MINSIZE + blocksize);
 | |
| 		sp->prev = stackp;
 | |
| 		stacknxt = sp->space;
 | |
| 		stacknleft = blocksize;
 | |
| 		stackp = sp;
 | |
| 		INTON;
 | |
| 	}
 | |
| 	p = stacknxt;
 | |
| 	stacknxt += nbytes;
 | |
| 	stacknleft -= nbytes;
 | |
| 	return p;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| stunalloc(pointer p)
 | |
| {
 | |
| #ifdef DEBUG
 | |
| 	if (p == NULL) {                /*DEBUG */
 | |
| 		write(2, "stunalloc\n", 10);
 | |
| 		abort();
 | |
| 	}
 | |
| #endif
 | |
| 	if (!(stacknxt >= (char *)p && (char *)p >= stackp->space)) {
 | |
| 		p = stackp->space;
 | |
| 	}
 | |
| 	stacknleft += stacknxt - (char *)p;
 | |
| 	stacknxt = p;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| setstackmark(struct stackmark *mark)
 | |
| {
 | |
| 	mark->stackp = stackp;
 | |
| 	mark->stacknxt = stacknxt;
 | |
| 	mark->stacknleft = stacknleft;
 | |
| 	mark->marknext = markp;
 | |
| 	markp = mark;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| popstackmark(struct stackmark *mark)
 | |
| {
 | |
| 	struct stack_block *sp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	markp = mark->marknext;
 | |
| 	while (stackp != mark->stackp) {
 | |
| 		sp = stackp;
 | |
| 		stackp = sp->prev;
 | |
| 		ckfree(sp);
 | |
| 	}
 | |
| 	stacknxt = mark->stacknxt;
 | |
| 	stacknleft = mark->stacknleft;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * When the parser reads in a string, it wants to stick the string on the
 | |
|  * stack and only adjust the stack pointer when it knows how big the
 | |
|  * string is.  Stackblock (defined in stack.h) returns a pointer to a block
 | |
|  * of space on top of the stack and stackblocklen returns the length of
 | |
|  * this block.  Growstackblock will grow this space by at least one byte,
 | |
|  * possibly moving it (like realloc).  Grabstackblock actually allocates the
 | |
|  * part of the block that has been used.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| growstackblock(void) {
 | |
| 	char *p;
 | |
| 	int newlen = ALIGN(stacknleft * 2 + 100);
 | |
| 	char *oldspace = stacknxt;
 | |
| 	int oldlen = stacknleft;
 | |
| 	struct stack_block *sp;
 | |
| 	struct stack_block *oldstackp;
 | |
| 
 | |
| 	if (stacknxt == stackp->space && stackp != &stackbase) {
 | |
| 		INTOFF;
 | |
| 		oldstackp = stackp;
 | |
| 		sp = stackp;
 | |
| 		stackp = sp->prev;
 | |
| 		sp = ckrealloc((pointer)sp, sizeof(struct stack_block) - MINSIZE + newlen);
 | |
| 		sp->prev = stackp;
 | |
| 		stackp = sp;
 | |
| 		stacknxt = sp->space;
 | |
| 		stacknleft = newlen;
 | |
| 		{
 | |
| 		  /* Stack marks pointing to the start of the old block
 | |
| 		   * must be relocated to point to the new block
 | |
| 		   */
 | |
| 		  struct stackmark *xmark;
 | |
| 		  xmark = markp;
 | |
| 		  while (xmark != NULL && xmark->stackp == oldstackp) {
 | |
| 		    xmark->stackp = stackp;
 | |
| 		    xmark->stacknxt = stacknxt;
 | |
| 		    xmark->stacknleft = stacknleft;
 | |
| 		    xmark = xmark->marknext;
 | |
| 		  }
 | |
| 		}
 | |
| 		INTON;
 | |
| 	} else {
 | |
| 		p = stalloc(newlen);
 | |
| 		memcpy(p, oldspace, oldlen);
 | |
| 		stacknxt = p;                   /* free the space */
 | |
| 		stacknleft += newlen;           /* we just allocated */
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static inline void
 | |
| grabstackblock(int len)
 | |
| {
 | |
| 	len = ALIGN(len);
 | |
| 	stacknxt += len;
 | |
| 	stacknleft -= len;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The following routines are somewhat easier to use that the above.
 | |
|  * The user declares a variable of type STACKSTR, which may be declared
 | |
|  * to be a register.  The macro STARTSTACKSTR initializes things.  Then
 | |
|  * the user uses the macro STPUTC to add characters to the string.  In
 | |
|  * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
 | |
|  * grown as necessary.  When the user is done, she can just leave the
 | |
|  * string there and refer to it using stackblock().  Or she can allocate
 | |
|  * the space for it using grabstackstr().  If it is necessary to allow
 | |
|  * someone else to use the stack temporarily and then continue to grow
 | |
|  * the string, the user should use grabstack to allocate the space, and
 | |
|  * then call ungrabstr(p) to return to the previous mode of operation.
 | |
|  *
 | |
|  * USTPUTC is like STPUTC except that it doesn't check for overflow.
 | |
|  * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
 | |
|  * is space for at least one character.
 | |
|  */
 | |
| 
 | |
| 
 | |
| static char *
 | |
| growstackstr(void) {
 | |
| 	int len = stackblocksize();
 | |
| 	if (herefd >= 0 && len >= 1024) {
 | |
| 		xwrite(herefd, stackblock(), len);
 | |
| 		sstrnleft = len - 1;
 | |
| 		return stackblock();
 | |
| 	}
 | |
| 	growstackblock();
 | |
| 	sstrnleft = stackblocksize() - len - 1;
 | |
| 	return stackblock() + len;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called from CHECKSTRSPACE.
 | |
|  */
 | |
| 
 | |
| static char *
 | |
| makestrspace(size_t newlen) {
 | |
| 	int len = stackblocksize() - sstrnleft;
 | |
| 	do {
 | |
| 		growstackblock();
 | |
| 		sstrnleft = stackblocksize() - len;
 | |
| 	} while (sstrnleft < newlen);
 | |
| 	return stackblock() + len;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static void
 | |
| ungrabstackstr(char *s, char *p)
 | |
| {
 | |
| 	stacknleft += stacknxt - s;
 | |
| 	stacknxt = s;
 | |
| 	sstrnleft = stacknleft - (p - s);
 | |
| }
 | |
| /*
 | |
|  * Miscelaneous builtins.
 | |
|  */
 | |
| 
 | |
| 
 | |
| #undef rflag
 | |
| 
 | |
| #if !defined(__GLIBC__) || __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
 | |
| typedef long rlim_t;
 | |
| #endif
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The read builtin.  The -e option causes backslashes to escape the
 | |
|  * following character.
 | |
|  *
 | |
|  * This uses unbuffered input, which may be avoidable in some cases.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| readcmd(int argc, char **argv)
 | |
| {
 | |
| 	char **ap;
 | |
| 	int backslash;
 | |
| 	char c;
 | |
| 	int rflag;
 | |
| 	char *prompt;
 | |
| 	const char *ifs;
 | |
| 	char *p;
 | |
| 	int startword;
 | |
| 	int status;
 | |
| 	int i;
 | |
| 
 | |
| 	rflag = 0;
 | |
| 	prompt = NULL;
 | |
| 	while ((i = nextopt("p:r")) != '\0') {
 | |
| 		if (i == 'p')
 | |
| 			prompt = optionarg;
 | |
| 		else
 | |
| 			rflag = 1;
 | |
| 	}
 | |
| 	if (prompt && isatty(0)) {
 | |
| 		out2str(prompt);     /* read without cmdedit */
 | |
| 		flushall();
 | |
| 	}
 | |
| 	if (*(ap = argptr) == NULL)
 | |
| 		error("arg count");
 | |
| 	if ((ifs = bltinlookup("IFS")) == NULL)
 | |
| 		ifs = defifs;
 | |
| 	status = 0;
 | |
| 	startword = 1;
 | |
| 	backslash = 0;
 | |
| 	STARTSTACKSTR(p);
 | |
| 	for (;;) {
 | |
| 		if (read(0, &c, 1) != 1) {
 | |
| 			status = 1;
 | |
| 			break;
 | |
| 		}
 | |
| 		if (c == '\0')
 | |
| 			continue;
 | |
| 		if (backslash) {
 | |
| 			backslash = 0;
 | |
| 			if (c != '\n')
 | |
| 				STPUTC(c, p);
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (!rflag && c == '\\') {
 | |
| 			backslash++;
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (c == '\n')
 | |
| 			break;
 | |
| 		if (startword && *ifs == ' ' && strchr(ifs, c)) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		startword = 0;
 | |
| 		if (backslash && c == '\\') {
 | |
| 			if (read(0, &c, 1) != 1) {
 | |
| 				status = 1;
 | |
| 				break;
 | |
| 			}
 | |
| 			STPUTC(c, p);
 | |
| 		} else if (ap[1] != NULL && strchr(ifs, c) != NULL) {
 | |
| 			STACKSTRNUL(p);
 | |
| 			setvar(*ap, stackblock(), 0);
 | |
| 			ap++;
 | |
| 			startword = 1;
 | |
| 			STARTSTACKSTR(p);
 | |
| 		} else {
 | |
| 			STPUTC(c, p);
 | |
| 		}
 | |
| 	}
 | |
| 	STACKSTRNUL(p);
 | |
| 	/* Remove trailing blanks */
 | |
| 	while (stackblock() <= --p && strchr(ifs, *p) != NULL)
 | |
| 		*p = '\0';
 | |
| 	setvar(*ap, stackblock(), 0);
 | |
| 	while (*++ap != NULL)
 | |
| 		setvar(*ap, nullstr, 0);
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static int
 | |
| umaskcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	static const char permuser[3] = "ugo";
 | |
| 	static const char permmode[3] = "rwx";
 | |
| 	static const short int permmask[] = {
 | |
| 		S_IRUSR, S_IWUSR, S_IXUSR,
 | |
| 		S_IRGRP, S_IWGRP, S_IXGRP,
 | |
| 		S_IROTH, S_IWOTH, S_IXOTH
 | |
| 	};
 | |
| 
 | |
| 	char *ap;
 | |
| 	mode_t mask;
 | |
| 	int i;
 | |
| 	int symbolic_mode = 0;
 | |
| 
 | |
| 	while (nextopt("S") != '\0') {
 | |
| 		symbolic_mode = 1;
 | |
| 	}
 | |
| 
 | |
| 	INTOFF;
 | |
| 	mask = umask(0);
 | |
| 	umask(mask);
 | |
| 	INTON;
 | |
| 
 | |
| 	if ((ap = *argptr) == NULL) {
 | |
| 		if (symbolic_mode) {
 | |
| 			char buf[18];
 | |
| 			char *p = buf;
 | |
| 			for (i=0 ; i<3 ; i++) {
 | |
| 				int j;
 | |
| 				*p++ = permuser[i];
 | |
| 				*p++ = '=';
 | |
| 				for (j=0 ; j<3 ; j++) {
 | |
| 					if ((mask & permmask[3*i+j]) == 0) {
 | |
| 						*p++ = permmode[j];
 | |
| 					}
 | |
| 				}
 | |
| 				*p++ = ',';
 | |
| 			}
 | |
| 			*--p = 0;
 | |
| 			puts(buf);
 | |
| 		} else {
 | |
| 			printf("%.4o\n", mask);
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (is_digit((unsigned char)*ap)) {
 | |
| 			mask = 0;
 | |
| 			do {
 | |
| 				if (*ap >= '8' || *ap < '0')
 | |
| 					error("Illegal number: %s", argv[1]);
 | |
| 				mask = (mask << 3) + (*ap - '0');
 | |
| 			} while (*++ap != '\0');
 | |
| 			umask(mask);
 | |
| 		} else {
 | |
| 			mask = ~mask & 0777;
 | |
| 			if (parse_mode(ap, &mask) == FALSE) {
 | |
| 				error("Illegal mode: %s", ap);
 | |
| 			}
 | |
| 			umask(~mask & 0777);
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * ulimit builtin
 | |
|  *
 | |
|  * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
 | |
|  * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
 | |
|  * ash by J.T. Conklin.
 | |
|  *
 | |
|  * Public domain.
 | |
|  */
 | |
| 
 | |
| struct limits {
 | |
| 	const char *name;
 | |
| 	short   cmd;
 | |
| 	short   factor; /* multiply by to get rlim_{cur,max} values */
 | |
| };
 | |
| 
 | |
| static const struct limits limits[] = {
 | |
| #ifdef RLIMIT_CPU
 | |
| 	{ "time(seconds)",             RLIMIT_CPU,        1 },
 | |
| #endif
 | |
| #ifdef RLIMIT_FSIZE
 | |
| 	{ "file(blocks)",              RLIMIT_FSIZE,    512 },
 | |
| #endif
 | |
| #ifdef RLIMIT_DATA
 | |
| 	{ "data(kbytes)",              RLIMIT_DATA,    1024 },
 | |
| #endif
 | |
| #ifdef RLIMIT_STACK
 | |
| 	{ "stack(kbytes)",             RLIMIT_STACK,   1024 },
 | |
| #endif
 | |
| #ifdef  RLIMIT_CORE
 | |
| 	{ "coredump(blocks)",          RLIMIT_CORE,     512 },
 | |
| #endif
 | |
| #ifdef RLIMIT_RSS
 | |
| 	{ "memory(kbytes)",            RLIMIT_RSS,     1024 },
 | |
| #endif
 | |
| #ifdef RLIMIT_MEMLOCK
 | |
| 	{ "locked memory(kbytes)",     RLIMIT_MEMLOCK, 1024 },
 | |
| #endif
 | |
| #ifdef RLIMIT_NPROC
 | |
| 	{ "process(processes)",        RLIMIT_NPROC,      1 },
 | |
| #endif
 | |
| #ifdef RLIMIT_NOFILE
 | |
| 	{ "nofiles(descriptors)",      RLIMIT_NOFILE,     1 },
 | |
| #endif
 | |
| #ifdef RLIMIT_VMEM
 | |
| 	{ "vmemory(kbytes)",           RLIMIT_VMEM,    1024 },
 | |
| #endif
 | |
| #ifdef RLIMIT_SWAP
 | |
| 	{ "swap(kbytes)",              RLIMIT_SWAP,    1024 },
 | |
| #endif
 | |
| 	{ NULL,                         0,                 0 }
 | |
| };
 | |
| 
 | |
| static int
 | |
| ulimitcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	static const char unlimited_string[] = "unlimited";
 | |
| 	int     c;
 | |
| 	rlim_t val = 0;
 | |
| 	enum { SOFT = 0x1, HARD = 0x2 }
 | |
| 			how = SOFT | HARD;
 | |
| 	const struct limits     *l;
 | |
| 	int             set, all = 0;
 | |
| 	int             optc, what;
 | |
| 	struct rlimit   limit;
 | |
| 
 | |
| 	what = 'f';
 | |
| 
 | |
| 	while ((optc = nextopt("HSa"
 | |
| #ifdef RLIMIT_CPU
 | |
| 	"t"
 | |
| #endif
 | |
| #ifdef RLIMIT_FSIZE
 | |
| 	"f"
 | |
| #endif
 | |
| #ifdef RLIMIT_DATA
 | |
| 	"d"
 | |
| #endif
 | |
| #ifdef RLIMIT_STACK
 | |
| 	"s"
 | |
| #endif
 | |
| #ifdef  RLIMIT_CORE
 | |
| 	"c"
 | |
| #endif
 | |
| #ifdef RLIMIT_RSS
 | |
| 	"m"
 | |
| #endif
 | |
| #ifdef RLIMIT_MEMLOCK
 | |
| 	"l"
 | |
| #endif
 | |
| #ifdef RLIMIT_NPROC
 | |
| 	"p"
 | |
| #endif
 | |
| #ifdef RLIMIT_NOFILE
 | |
| 	"n"
 | |
| #endif
 | |
| #ifdef RLIMIT_VMEM
 | |
| 	"v"
 | |
| #endif
 | |
| #ifdef RLIMIT_SWAP
 | |
| 	"w"
 | |
| #endif
 | |
| 					)) != '\0') {
 | |
| 		if (optc == 'H') {
 | |
| 			how = HARD;
 | |
| 		} else if (optc == 'S') {
 | |
| 			how = SOFT;
 | |
| 		} else if (optc == 'a') {
 | |
| 			all = 1;
 | |
| 		} else {
 | |
| 			what = optc;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for (l = limits; l->name; l++) {
 | |
| 		if(l->name[0] == what)
 | |
| 			break;
 | |
| 		if(l->name[1]=='w' && what=='w')
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	set = *argptr ? 1 : 0;
 | |
| 	if (set) {
 | |
| 		char *p = *argptr;
 | |
| 
 | |
| 		if (all || argptr[1])
 | |
| 			error("too many arguments");
 | |
| 		if (strcmp(p, unlimited_string) == 0)
 | |
| 			val = RLIM_INFINITY;
 | |
| 		else {
 | |
| 			val = (rlim_t) 0;
 | |
| 
 | |
| 			while ((c = *p++) >= '0' && c <= '9')
 | |
| 			{
 | |
| 				val = (val * 10) + (long)(c - '0');
 | |
| 				if (val < (rlim_t) 0)
 | |
| 					break;
 | |
| 			}
 | |
| 			if (c)
 | |
| 				error("bad number");
 | |
| 			val *= l->factor;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (all) {
 | |
| 		for (l = limits; l->name; l++) {
 | |
| 			printf("%-20s ", l->name);
 | |
| 			getrlimit(l->cmd, &limit);
 | |
| 		OUTPUT_LIMIT:
 | |
| 			if (how & SOFT)
 | |
| 				val = limit.rlim_cur;
 | |
| 			else if (how & HARD)
 | |
| 				val = limit.rlim_max;
 | |
| 
 | |
| 			if (val == RLIM_INFINITY)
 | |
| 				puts(unlimited_string);
 | |
| 			else
 | |
| 			{
 | |
| 				val /= l->factor;
 | |
| 				printf("%lld\n", (long long) val);
 | |
| 			}
 | |
| 			if (!all) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!set) {
 | |
| 		goto OUTPUT_LIMIT;
 | |
| 	}
 | |
| 
 | |
| 	getrlimit(l->cmd, &limit);
 | |
| 	if (how & HARD)
 | |
| 		limit.rlim_max = val;
 | |
| 	if (how & SOFT)
 | |
| 		limit.rlim_cur = val;
 | |
| 	if (setrlimit(l->cmd, &limit) < 0)
 | |
| 		error("error setting limit (%m)");
 | |
| 	return 0;
 | |
| }
 | |
| /*
 | |
|  * prefix -- see if pfx is a prefix of string.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| prefix(char const *pfx, char const *string)
 | |
| {
 | |
| 	while (*pfx) {
 | |
| 		if (*pfx++ != *string++)
 | |
| 			return 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Return true if s is a string of digits, and save munber in intptr
 | |
|  * nagative is bad
 | |
|  */
 | |
| 
 | |
| static int
 | |
| is_number(const char *p, int *intptr)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	do {
 | |
| 		if (! is_digit(*p))
 | |
| 			return 0;
 | |
| 		ret *= 10;
 | |
| 		ret += digit_val(*p);
 | |
| 		p++;
 | |
| 	} while (*p != '\0');
 | |
| 
 | |
| 	*intptr = ret;
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Convert a string of digits to an integer, printing an error message on
 | |
|  * failure.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| number(const char *s)
 | |
| {
 | |
| 	int i;
 | |
| 	if (! is_number(s, &i))
 | |
| 		error("Illegal number: %s", s);
 | |
| 	return i;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Produce a possibly single quoted string suitable as input to the shell.
 | |
|  * The return string is allocated on the stack.
 | |
|  */
 | |
| 
 | |
| static char *
 | |
| single_quote(const char *s) {
 | |
| 	char *p;
 | |
| 
 | |
| 	STARTSTACKSTR(p);
 | |
| 
 | |
| 	do {
 | |
| 		char *q = p;
 | |
| 		size_t len1, len1p, len2, len2p;
 | |
| 
 | |
| 		len1 = strcspn(s, "'");
 | |
| 		len2 = strspn(s + len1, "'");
 | |
| 
 | |
| 		len1p = len1 ? len1 + 2 : len1;
 | |
| 		len2p = len2 + ((len2 < 2) ? len2 : 2);
 | |
| 
 | |
| 		CHECKSTRSPACE(len1p + len2p + 1, p);
 | |
| 
 | |
| 		if (len1) {
 | |
| 			*p = '\'';
 | |
| 			q = p + 1 + len1;
 | |
| 			memcpy(p + 1, s, len1);
 | |
| 			*q++ = '\'';
 | |
| 			s += len1;
 | |
| 		}
 | |
| 
 | |
| 		if (len2 > 1) {
 | |
| 			*q = '"';
 | |
| 			q += 1 + len2;
 | |
| 			memcpy(q + 1, s, len2);
 | |
| 			*q = '"';
 | |
| 			s += len2;
 | |
| 		} else if (len2 == 1) {
 | |
| 			*q++ = '\\';
 | |
| 			*q = '\'';
 | |
| 			s++;
 | |
| 		}
 | |
| 
 | |
| 		STADJUST(len1p + len2p, p);
 | |
| 	} while (*s);
 | |
| 
 | |
| 	USTPUTC(0, p);
 | |
| 
 | |
| 	return grabstackstr(p);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Like strdup but works with the ash stack.
 | |
|  */
 | |
| 
 | |
| static char *
 | |
| sstrdup(const char *p)
 | |
| {
 | |
| 	size_t len = strlen(p) + 1;
 | |
| 	return memcpy(stalloc(len), p, len);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Routine for dealing with parsed shell commands.
 | |
|  */
 | |
| 
 | |
| 
 | |
| static void sizenodelist (const struct nodelist *);
 | |
| static struct nodelist *copynodelist (const struct nodelist *);
 | |
| static char *nodesavestr (const char *);
 | |
| 
 | |
| #define CALCSIZE_TABLE
 | |
| #define COPYNODE_TABLE
 | |
| #if defined(CALCSIZE_TABLE) || defined(COPYNODE_TABLE)
 | |
| /*
 | |
|  * To collect a lot of redundant code in case statements for copynode()
 | |
|  * and calcsize(), we implement a mini language here.  Each type of node
 | |
|  * struct has an associated instruction sequence that operates on its
 | |
|  * members via their offsets.  The instruction are pack in unsigned chars
 | |
|  * with format   IIDDDDDE   where the bits are
 | |
|  *   I : part of the instruction opcode, which are
 | |
|  *       00 : member is a pointer to another node
 | |
|  *       40 : member is an integer
 | |
|  *       80 : member is a pointer to a nodelist
 | |
|  *       CC : member is a pointer to a char string
 | |
|  *   D : data - the actual offset of the member to operate on in the struct
 | |
|  *              (since we assume bit 0 is set, it is not shifted)
 | |
|  *   E : flag signaling end of instruction sequence
 | |
|  *
 | |
|  * WARNING: In order to handle larger offsets for 64bit archs, this code
 | |
|  *          assumes that no offset can be an odd number and stores the
 | |
|  *          end-of-instructions flag in bit 0.
 | |
|  */
 | |
| 
 | |
| #define NODE_INTEGER    0x40
 | |
| #define NODE_NODELIST   0x80
 | |
| #define NODE_CHARPTR    0xC0
 | |
| #define NODE_NOMORE             0x01    /* Note: no offset should be odd (aligned)*/
 | |
| #define NODE_MBRMASK    0xC0
 | |
| #define NODE_OFFSETMASK 0x3E
 | |
| 
 | |
| static const unsigned char copynode_ops[35] = {
 | |
| #define COPYNODE_OPS0   0
 | |
| 	offsetof(union node, nbinary.ch2),
 | |
| 	offsetof(union node, nbinary.ch1)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS1   (COPYNODE_OPS0 + 2)
 | |
| 	offsetof(union node, ncmd.redirect),
 | |
| 	offsetof(union node, ncmd.args),
 | |
| 	offsetof(union node, ncmd.assign),
 | |
| 	offsetof(union node, ncmd.backgnd)|NODE_INTEGER|NODE_NOMORE,
 | |
| #define COPYNODE_OPS2   (COPYNODE_OPS1 + 4)
 | |
| 	offsetof(union node, npipe.cmdlist)|NODE_NODELIST,
 | |
| 	offsetof(union node, npipe.backgnd)|NODE_INTEGER|NODE_NOMORE,
 | |
| #define COPYNODE_OPS3   (COPYNODE_OPS2 + 2)
 | |
| 	offsetof(union node, nredir.redirect),
 | |
| 	offsetof(union node, nredir.n)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS4   (COPYNODE_OPS3 + 2)
 | |
| 	offsetof(union node, nif.elsepart),
 | |
| 	offsetof(union node, nif.ifpart),
 | |
| 	offsetof(union node, nif.test)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS5   (COPYNODE_OPS4 + 3)
 | |
| 	offsetof(union node, nfor.var)|NODE_CHARPTR,
 | |
| 	offsetof(union node, nfor.body),
 | |
| 	offsetof(union node, nfor.args)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS6   (COPYNODE_OPS5 + 3)
 | |
| 	offsetof(union node, ncase.cases),
 | |
| 	offsetof(union node, ncase.expr)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS7   (COPYNODE_OPS6 + 2)
 | |
| 	offsetof(union node, nclist.body),
 | |
| 	offsetof(union node, nclist.pattern),
 | |
| 	offsetof(union node, nclist.next)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS8   (COPYNODE_OPS7 + 3)
 | |
| 	offsetof(union node, narg.backquote)|NODE_NODELIST,
 | |
| 	offsetof(union node, narg.text)|NODE_CHARPTR,
 | |
| 	offsetof(union node, narg.next)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS9   (COPYNODE_OPS8 + 3)
 | |
| 	offsetof(union node, nfile.fname),
 | |
| 	offsetof(union node, nfile.fd)|NODE_INTEGER,
 | |
| 	offsetof(union node, nfile.next)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS10   (COPYNODE_OPS9 + 3)
 | |
| 	offsetof(union node, ndup.vname),
 | |
| 	offsetof(union node, ndup.dupfd)|NODE_INTEGER,
 | |
| 	offsetof(union node, ndup.fd)|NODE_INTEGER,
 | |
| 	offsetof(union node, ndup.next)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS11   (COPYNODE_OPS10 + 4)
 | |
| 	offsetof(union node, nhere.doc),
 | |
| 	offsetof(union node, nhere.fd)|NODE_INTEGER,
 | |
| 	offsetof(union node, nhere.next)|NODE_NOMORE,
 | |
| #define COPYNODE_OPS12   (COPYNODE_OPS11 + 3)
 | |
| 	offsetof(union node, nnot.com)|NODE_NOMORE,
 | |
| };
 | |
| 
 | |
| #if COPYNODE_OPS12 != 34
 | |
| #error COPYNODE_OPS12 is incorrect
 | |
| #endif
 | |
| 
 | |
| static const unsigned char copynode_ops_index[26] = {
 | |
| 	COPYNODE_OPS0, /* NSEMI */
 | |
| 	COPYNODE_OPS1, /* NCMD */
 | |
| 	COPYNODE_OPS2, /* NPIPE */
 | |
| 	COPYNODE_OPS3, /* NREDIR */
 | |
| 	COPYNODE_OPS3, /* NBACKGND */
 | |
| 	COPYNODE_OPS3, /* NSUBSHELL */
 | |
| 	COPYNODE_OPS0, /* NAND */
 | |
| 	COPYNODE_OPS0, /* NOR */
 | |
| 	COPYNODE_OPS4, /* NIF */
 | |
| 	COPYNODE_OPS0, /* NWHILE */
 | |
| 	COPYNODE_OPS0, /* NUNTIL */
 | |
| 	COPYNODE_OPS5, /* NFOR */
 | |
| 	COPYNODE_OPS6, /* NCASE */
 | |
| 	COPYNODE_OPS7, /* NCLIST */
 | |
| 	COPYNODE_OPS8, /* NDEFUN */
 | |
| 	COPYNODE_OPS8, /* NARG */
 | |
| 	COPYNODE_OPS9, /* NTO */
 | |
| 	COPYNODE_OPS9, /* NFROM */
 | |
| 	COPYNODE_OPS9, /* NFROMTO */
 | |
| 	COPYNODE_OPS9, /* NAPPEND */
 | |
| 	COPYNODE_OPS9, /* NTOOV */
 | |
| 	COPYNODE_OPS10, /* NTOFD */
 | |
| 	COPYNODE_OPS10, /* NFROMFD */
 | |
| 	COPYNODE_OPS11, /* NHERE */
 | |
| 	COPYNODE_OPS11, /* NXHERE */
 | |
| 	COPYNODE_OPS12, /* NNOT */
 | |
| };
 | |
| 
 | |
| #if NODE_CHARPTR != NODE_MBRMASK
 | |
| #error NODE_CHARPTR != NODE_MBRMASK!!!
 | |
| #endif
 | |
| #endif /* defined(CALCSIZE_TABLE) || defined(COPYNODE_TABLE) */
 | |
| 
 | |
| #ifdef COPYNODE_TABLE
 | |
| static union node *
 | |
| copynode(const union node *n)
 | |
| {
 | |
|       union node *new;
 | |
| 	  const unsigned char *p;
 | |
| 
 | |
|       if (n == NULL) {
 | |
|           return NULL;
 | |
| 	  }
 | |
|       new = funcblock;
 | |
|       new->type = n->type;
 | |
|       funcblock = (char *) funcblock + (int) nodesize[n->type];
 | |
| 	  p = copynode_ops + (int) copynode_ops_index[n->type];
 | |
| 	  do {
 | |
| 		  char *nn = ((char *) new) + ((int)(*p & NODE_OFFSETMASK));
 | |
| 		  const char *no = ((const char *) n) + ((int)(*p & NODE_OFFSETMASK));
 | |
| 
 | |
| 		  if (!(*p & NODE_MBRMASK)) { /* standard node */
 | |
| 			  *((union node **)nn) = copynode(*((const union node **) no));
 | |
| 		  } else if ((*p & NODE_MBRMASK) == NODE_CHARPTR) { /* string */
 | |
| 			  *((const char **)nn) = nodesavestr(*((const char **)no));
 | |
| 		  } else if (*p & NODE_NODELIST) { /* nodelist */
 | |
| 			  *((struct nodelist **)nn)
 | |
| 				  = copynodelist(*((const struct nodelist **) no));
 | |
| 		  } else {                              /* integer */
 | |
| 			  *((int *) nn) = *((int *) no);
 | |
| 		  }
 | |
| 	  } while (!(*p++ & NODE_NOMORE));
 | |
|       return new;
 | |
| }
 | |
| #else  /* COPYNODE_TABLE */
 | |
| static union node *
 | |
| copynode(const union node *n)
 | |
| {
 | |
|       union node *new;
 | |
| 
 | |
|       if (n == NULL)
 | |
|         return NULL;
 | |
|       new = funcblock;
 | |
|       funcblock = (char *) funcblock + nodesize[n->type];
 | |
|       switch (n->type) {
 | |
|       case NSEMI:
 | |
|       case NAND:
 | |
|       case NOR:
 | |
|       case NWHILE:
 | |
|       case NUNTIL:
 | |
| 	    new->nbinary.ch2 = copynode(n->nbinary.ch2);
 | |
| 	    new->nbinary.ch1 = copynode(n->nbinary.ch1);
 | |
| 	    break;
 | |
|       case NCMD:
 | |
| 	    new->ncmd.redirect = copynode(n->ncmd.redirect);
 | |
| 	    new->ncmd.args = copynode(n->ncmd.args);
 | |
| 	    new->ncmd.assign = copynode(n->ncmd.assign);
 | |
| 	    new->ncmd.backgnd = n->ncmd.backgnd;
 | |
| 	    break;
 | |
|       case NPIPE:
 | |
| 	    new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
 | |
| 	    new->npipe.backgnd = n->npipe.backgnd;
 | |
| 	    break;
 | |
|       case NREDIR:
 | |
|       case NBACKGND:
 | |
|       case NSUBSHELL:
 | |
| 	    new->nredir.redirect = copynode(n->nredir.redirect);
 | |
| 	    new->nredir.n = copynode(n->nredir.n);
 | |
| 	    break;
 | |
|       case NIF:
 | |
| 	    new->nif.elsepart = copynode(n->nif.elsepart);
 | |
| 	    new->nif.ifpart = copynode(n->nif.ifpart);
 | |
| 	    new->nif.test = copynode(n->nif.test);
 | |
| 	    break;
 | |
|       case NFOR:
 | |
| 	    new->nfor.var = nodesavestr(n->nfor.var);
 | |
| 	    new->nfor.body = copynode(n->nfor.body);
 | |
| 	    new->nfor.args = copynode(n->nfor.args);
 | |
| 	    break;
 | |
|       case NCASE:
 | |
| 	    new->ncase.cases = copynode(n->ncase.cases);
 | |
| 	    new->ncase.expr = copynode(n->ncase.expr);
 | |
| 	    break;
 | |
|       case NCLIST:
 | |
| 	    new->nclist.body = copynode(n->nclist.body);
 | |
| 	    new->nclist.pattern = copynode(n->nclist.pattern);
 | |
| 	    new->nclist.next = copynode(n->nclist.next);
 | |
| 	    break;
 | |
|       case NDEFUN:
 | |
|       case NARG:
 | |
| 	    new->narg.backquote = copynodelist(n->narg.backquote);
 | |
| 	    new->narg.text = nodesavestr(n->narg.text);
 | |
| 	    new->narg.next = copynode(n->narg.next);
 | |
| 	    break;
 | |
|       case NTO:
 | |
|       case NFROM:
 | |
|       case NFROMTO:
 | |
|       case NAPPEND:
 | |
|       case NTOOV:
 | |
| 	    new->nfile.fname = copynode(n->nfile.fname);
 | |
| 	    new->nfile.fd = n->nfile.fd;
 | |
| 	    new->nfile.next = copynode(n->nfile.next);
 | |
| 	    break;
 | |
|       case NTOFD:
 | |
|       case NFROMFD:
 | |
| 	    new->ndup.vname = copynode(n->ndup.vname);
 | |
| 	    new->ndup.dupfd = n->ndup.dupfd;
 | |
| 	    new->ndup.fd = n->ndup.fd;
 | |
| 	    new->ndup.next = copynode(n->ndup.next);
 | |
| 	    break;
 | |
|       case NHERE:
 | |
|       case NXHERE:
 | |
| 	    new->nhere.doc = copynode(n->nhere.doc);
 | |
| 	    new->nhere.fd = n->nhere.fd;
 | |
| 	    new->nhere.next = copynode(n->nhere.next);
 | |
| 	    break;
 | |
|       case NNOT:
 | |
| 	    new->nnot.com = copynode(n->nnot.com);
 | |
| 	    break;
 | |
|       };
 | |
|       new->type = n->type;
 | |
|       return new;
 | |
| }
 | |
| #endif /* COPYNODE_TABLE */
 | |
| 
 | |
| #ifdef CALCSIZE_TABLE
 | |
| static void
 | |
| calcsize(const union node *n)
 | |
| {
 | |
| 	  const unsigned char *p;
 | |
| 
 | |
|       if (n == NULL)
 | |
| 	    return;
 | |
|       funcblocksize += (int) nodesize[n->type];
 | |
| 
 | |
| 	  p = copynode_ops + (int) copynode_ops_index[n->type];
 | |
| 	  do {
 | |
| 		  const char *no = ((const char *) n) + ((int)(*p & NODE_OFFSETMASK));
 | |
| 
 | |
| 		  if (!(*p & NODE_MBRMASK)) { /* standard node */
 | |
| 			  calcsize(*((const union node **) no));
 | |
| 		  } else if ((*p & NODE_MBRMASK) == NODE_CHARPTR) { /* string */
 | |
| 			  funcstringsize += strlen(*((const char **)no)) + 1;
 | |
| 		  } else if (*p & NODE_NODELIST) { /* nodelist */
 | |
| 			  sizenodelist(*((const struct nodelist **) no));
 | |
| 		  }	/* else integer -- ignore */
 | |
| 	  } while (!(*p++ & NODE_NOMORE));
 | |
| }
 | |
| #else  /* CALCSIZE_TABLE */
 | |
| static void
 | |
| calcsize(const union node *n)
 | |
| {
 | |
|       if (n == NULL)
 | |
| 	    return;
 | |
|       funcblocksize += nodesize[n->type];
 | |
|       switch (n->type) {
 | |
|       case NSEMI:
 | |
|       case NAND:
 | |
|       case NOR:
 | |
|       case NWHILE:
 | |
|       case NUNTIL:
 | |
| 	    calcsize(n->nbinary.ch2);
 | |
| 	    calcsize(n->nbinary.ch1);
 | |
| 	    break;
 | |
|       case NCMD:
 | |
| 	    calcsize(n->ncmd.redirect);
 | |
| 	    calcsize(n->ncmd.args);
 | |
| 	    calcsize(n->ncmd.assign);
 | |
| 	    break;
 | |
|       case NPIPE:
 | |
| 	    sizenodelist(n->npipe.cmdlist);
 | |
| 	    break;
 | |
|       case NREDIR:
 | |
|       case NBACKGND:
 | |
|       case NSUBSHELL:
 | |
| 	    calcsize(n->nredir.redirect);
 | |
| 	    calcsize(n->nredir.n);
 | |
| 	    break;
 | |
|       case NIF:
 | |
| 	    calcsize(n->nif.elsepart);
 | |
| 	    calcsize(n->nif.ifpart);
 | |
| 	    calcsize(n->nif.test);
 | |
| 	    break;
 | |
|       case NFOR:
 | |
| 	    funcstringsize += strlen(n->nfor.var) + 1;
 | |
| 	    calcsize(n->nfor.body);
 | |
| 	    calcsize(n->nfor.args);
 | |
| 	    break;
 | |
|       case NCASE:
 | |
| 	    calcsize(n->ncase.cases);
 | |
| 	    calcsize(n->ncase.expr);
 | |
| 	    break;
 | |
|       case NCLIST:
 | |
| 	    calcsize(n->nclist.body);
 | |
| 	    calcsize(n->nclist.pattern);
 | |
| 	    calcsize(n->nclist.next);
 | |
| 	    break;
 | |
|       case NDEFUN:
 | |
|       case NARG:
 | |
| 	    sizenodelist(n->narg.backquote);
 | |
| 	    funcstringsize += strlen(n->narg.text) + 1;
 | |
| 	    calcsize(n->narg.next);
 | |
| 	    break;
 | |
|       case NTO:
 | |
|       case NFROM:
 | |
|       case NFROMTO:
 | |
|       case NAPPEND:
 | |
|       case NTOOV:
 | |
| 	    calcsize(n->nfile.fname);
 | |
| 	    calcsize(n->nfile.next);
 | |
| 	    break;
 | |
|       case NTOFD:
 | |
|       case NFROMFD:
 | |
| 	    calcsize(n->ndup.vname);
 | |
| 	    calcsize(n->ndup.next);
 | |
| 	    break;
 | |
|       case NHERE:
 | |
|       case NXHERE:
 | |
| 	    calcsize(n->nhere.doc);
 | |
| 	    calcsize(n->nhere.next);
 | |
| 	    break;
 | |
|       case NNOT:
 | |
| 	    calcsize(n->nnot.com);
 | |
| 	    break;
 | |
|       };
 | |
| }
 | |
| #endif /* CALCSIZE_TABLE */
 | |
| 
 | |
| static void
 | |
| sizenodelist(const struct nodelist *lp)
 | |
| {
 | |
| 	while (lp) {
 | |
| 		funcblocksize += ALIGN(sizeof(struct nodelist));
 | |
| 		calcsize(lp->n);
 | |
| 		lp = lp->next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct nodelist *
 | |
| copynodelist(const struct nodelist *lp)
 | |
| {
 | |
| 	struct nodelist *start;
 | |
| 	struct nodelist **lpp;
 | |
| 
 | |
| 	lpp = &start;
 | |
| 	while (lp) {
 | |
| 		*lpp = funcblock;
 | |
| 		funcblock = (char *) funcblock + ALIGN(sizeof(struct nodelist));
 | |
| 		(*lpp)->n = copynode(lp->n);
 | |
| 		lp = lp->next;
 | |
| 		lpp = &(*lpp)->next;
 | |
| 	}
 | |
| 	*lpp = NULL;
 | |
| 	return start;
 | |
| }
 | |
| 
 | |
| 
 | |
| static char *
 | |
| nodesavestr(const char *s)
 | |
| {
 | |
| 	const char *p = s;
 | |
| 	char *q = funcstring;
 | |
| 	char   *rtn = funcstring;
 | |
| 
 | |
| 	while ((*q++ = *p++) != '\0')
 | |
| 		continue;
 | |
| 	funcstring = q;
 | |
| 	return rtn;
 | |
| }
 | |
| 
 | |
| #ifdef ASH_GETOPTS
 | |
| static int getopts (char *, char *, char **, int *, int *);
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Process the shell command line arguments.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| procargs(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	argptr = argv;
 | |
| 	if (argc > 0)
 | |
| 		argptr++;
 | |
| 	for (i = 0; i < NOPTS; i++)
 | |
| 		optent_val(i) = 2;
 | |
| 	options(1);
 | |
| 	if (*argptr == NULL && minusc == NULL)
 | |
| 		sflag = 1;
 | |
| 	if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1))
 | |
| 		iflag = 1;
 | |
| 	if (mflag == 2)
 | |
| 		mflag = iflag;
 | |
| 	for (i = 0; i < NOPTS; i++)
 | |
| 		if (optent_val(i) == 2)
 | |
| 			optent_val(i) = 0;
 | |
| 	arg0 = argv[0];
 | |
| 	if (sflag == 0 && minusc == NULL) {
 | |
| 		commandname = argv[0];
 | |
| 		arg0 = *argptr++;
 | |
| 		setinputfile(arg0, 0);
 | |
| 		commandname = arg0;
 | |
| 	}
 | |
| 	/* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
 | |
| 	if (argptr && minusc && *argptr)
 | |
| 		arg0 = *argptr++;
 | |
| 
 | |
| 	shellparam.p = argptr;
 | |
| 	shellparam.optind = 1;
 | |
| 	shellparam.optoff = -1;
 | |
| 	/* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
 | |
| 	while (*argptr) {
 | |
| 		shellparam.nparam++;
 | |
| 		argptr++;
 | |
| 	}
 | |
| 	optschanged();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Process shell options.  The global variable argptr contains a pointer
 | |
|  * to the argument list; we advance it past the options.
 | |
|  */
 | |
| 
 | |
| static inline void
 | |
| minus_o(const char *name, int val)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (name == NULL) {
 | |
| 		out1str("Current option settings\n");
 | |
| 		for (i = 0; i < NOPTS; i++)
 | |
| 			printf("%-16s%s\n", optent_name(optlist[i]),
 | |
| 				optent_val(i) ? "on" : "off");
 | |
| 	} else {
 | |
| 		for (i = 0; i < NOPTS; i++)
 | |
| 			if (equal(name, optent_name(optlist[i]))) {
 | |
| 				setoption(optent_letter(optlist[i]), val);
 | |
| 				return;
 | |
| 			}
 | |
| 		error("Illegal option -o %s", name);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| options(int cmdline)
 | |
| {
 | |
| 	char *p;
 | |
| 	int val;
 | |
| 	int c;
 | |
| 
 | |
| 	if (cmdline)
 | |
| 		minusc = NULL;
 | |
| 	while ((p = *argptr) != NULL) {
 | |
| 		argptr++;
 | |
| 		if ((c = *p++) == '-') {
 | |
| 			val = 1;
 | |
| 			if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
 | |
| 				if (!cmdline) {
 | |
| 					/* "-" means turn off -x and -v */
 | |
| 					if (p[0] == '\0')
 | |
| 						xflag = vflag = 0;
 | |
| 					/* "--" means reset params */
 | |
| 					else if (*argptr == NULL)
 | |
| 						setparam(argptr);
 | |
| 				}
 | |
| 				break;    /* "-" or  "--" terminates options */
 | |
| 			}
 | |
| 		} else if (c == '+') {
 | |
| 			val = 0;
 | |
| 		} else {
 | |
| 			argptr--;
 | |
| 			break;
 | |
| 		}
 | |
| 		while ((c = *p++) != '\0') {
 | |
| 			if (c == 'c' && cmdline) {
 | |
| 				char *q;
 | |
| #ifdef NOHACK   /* removing this code allows sh -ce 'foo' for compat */
 | |
| 				if (*p == '\0')
 | |
| #endif
 | |
| 					q = *argptr++;
 | |
| 				if (q == NULL || minusc != NULL)
 | |
| 					error("Bad -c option");
 | |
| 				minusc = q;
 | |
| #ifdef NOHACK
 | |
| 				break;
 | |
| #endif
 | |
| 			} else if (c == 'o') {
 | |
| 				minus_o(*argptr, val);
 | |
| 				if (*argptr)
 | |
| 					argptr++;
 | |
| 			} else {
 | |
| 				setoption(c, val);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| setoption(int flag, int val)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < NOPTS; i++)
 | |
| 		if (optent_letter(optlist[i]) == flag) {
 | |
| 			optent_val(i) = val;
 | |
| 			if (val) {
 | |
| 				/* #%$ hack for ksh semantics */
 | |
| 				if (flag == 'V')
 | |
| 					Eflag = 0;
 | |
| 				else if (flag == 'E')
 | |
| 					Vflag = 0;
 | |
| 			}
 | |
| 			return;
 | |
| 		}
 | |
| 	error("Illegal option -%c", flag);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Set the shell parameters.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| setparam(char **argv)
 | |
| {
 | |
| 	char **newparam;
 | |
| 	char **ap;
 | |
| 	int nparam;
 | |
| 
 | |
| 	for (nparam = 0 ; argv[nparam] ; nparam++);
 | |
| 	ap = newparam = ckmalloc((nparam + 1) * sizeof *ap);
 | |
| 	while (*argv) {
 | |
| 		*ap++ = savestr(*argv++);
 | |
| 	}
 | |
| 	*ap = NULL;
 | |
| 	freeparam(&shellparam);
 | |
| 	shellparam.malloc = 1;
 | |
| 	shellparam.nparam = nparam;
 | |
| 	shellparam.p = newparam;
 | |
| 	shellparam.optind = 1;
 | |
| 	shellparam.optoff = -1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Free the list of positional parameters.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| freeparam(volatile struct shparam *param)
 | |
| {
 | |
| 	char **ap;
 | |
| 
 | |
| 	if (param->malloc) {
 | |
| 		for (ap = param->p ; *ap ; ap++)
 | |
| 			ckfree(*ap);
 | |
| 		ckfree(param->p);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The shift builtin command.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| shiftcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	int n;
 | |
| 	char **ap1, **ap2;
 | |
| 
 | |
| 	n = 1;
 | |
| 	if (argc > 1)
 | |
| 		n = number(argv[1]);
 | |
| 	if (n > shellparam.nparam)
 | |
| 		error("can't shift that many");
 | |
| 	INTOFF;
 | |
| 	shellparam.nparam -= n;
 | |
| 	for (ap1 = shellparam.p ; --n >= 0 ; ap1++) {
 | |
| 		if (shellparam.malloc)
 | |
| 			ckfree(*ap1);
 | |
| 	}
 | |
| 	ap2 = shellparam.p;
 | |
| 	while ((*ap2++ = *ap1++) != NULL);
 | |
| 	shellparam.optind = 1;
 | |
| 	shellparam.optoff = -1;
 | |
| 	INTON;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The set command builtin.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| setcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	if (argc == 1)
 | |
| 		return showvarscmd(argc, argv);
 | |
| 	INTOFF;
 | |
| 	options(0);
 | |
| 	optschanged();
 | |
| 	if (*argptr != NULL) {
 | |
| 		setparam(argptr);
 | |
| 	}
 | |
| 	INTON;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| getoptsreset(const char *value)
 | |
| {
 | |
| 	shellparam.optind = number(value);
 | |
| 	shellparam.optoff = -1;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_LOCALE_SUPPORT
 | |
| static void change_lc_all(const char *value)
 | |
| {
 | |
| 	if(value != 0 && *value != 0)
 | |
| 		setlocale(LC_ALL, value);
 | |
| }
 | |
| 
 | |
| static void change_lc_ctype(const char *value)
 | |
| {
 | |
| 	if(value != 0 && *value != 0)
 | |
| 		setlocale(LC_CTYPE, value);
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #ifdef ASH_GETOPTS
 | |
| /*
 | |
|  * The getopts builtin.  Shellparam.optnext points to the next argument
 | |
|  * to be processed.  Shellparam.optptr points to the next character to
 | |
|  * be processed in the current argument.  If shellparam.optnext is NULL,
 | |
|  * then it's the first time getopts has been called.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| getoptscmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	char **optbase;
 | |
| 
 | |
| 	if (argc < 3)
 | |
| 		error("Usage: getopts optstring var [arg]");
 | |
| 	else if (argc == 3) {
 | |
| 		optbase = shellparam.p;
 | |
| 		if (shellparam.optind > shellparam.nparam + 1) {
 | |
| 			shellparam.optind = 1;
 | |
| 			shellparam.optoff = -1;
 | |
| 		}
 | |
| 	}
 | |
| 	else {
 | |
| 		optbase = &argv[3];
 | |
| 		if (shellparam.optind > argc - 2) {
 | |
| 			shellparam.optind = 1;
 | |
| 			shellparam.optoff = -1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return getopts(argv[1], argv[2], optbase, &shellparam.optind,
 | |
| 		       &shellparam.optoff);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Safe version of setvar, returns 1 on success 0 on failure.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| setvarsafe(name, val, flags)
 | |
| 	const char *name, *val;
 | |
| 	int flags;
 | |
| {
 | |
| 	struct jmploc jmploc;
 | |
| 	struct jmploc *volatile savehandler = handler;
 | |
| 	int err = 0;
 | |
| #ifdef __GNUC__
 | |
| 	(void) &err;
 | |
| #endif
 | |
| 
 | |
| 	if (setjmp(jmploc.loc))
 | |
| 		err = 1;
 | |
| 	else {
 | |
| 		handler = &jmploc;
 | |
| 		setvar(name, val, flags);
 | |
| 	}
 | |
| 	handler = savehandler;
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int
 | |
| getopts(optstr, optvar, optfirst, myoptind, optoff)
 | |
| 	char *optstr;
 | |
| 	char *optvar;
 | |
| 	char **optfirst;
 | |
| 	int *myoptind;
 | |
| 	int *optoff;
 | |
| {
 | |
| 	char *p, *q;
 | |
| 	char c = '?';
 | |
| 	int done = 0;
 | |
| 	int err = 0;
 | |
| 	char s[10];
 | |
| 	char **optnext = optfirst + *myoptind - 1;
 | |
| 
 | |
| 	if (*myoptind <= 1 || *optoff < 0 || !(*(optnext - 1)) ||
 | |
| 	    strlen(*(optnext - 1)) < *optoff)
 | |
| 		p = NULL;
 | |
| 	else
 | |
| 		p = *(optnext - 1) + *optoff;
 | |
| 	if (p == NULL || *p == '\0') {
 | |
| 		/* Current word is done, advance */
 | |
| 		if (optnext == NULL)
 | |
| 			return 1;
 | |
| 		p = *optnext;
 | |
| 		if (p == NULL || *p != '-' || *++p == '\0') {
 | |
| atend:
 | |
| 			*myoptind = optnext - optfirst + 1;
 | |
| 			p = NULL;
 | |
| 			done = 1;
 | |
| 			goto out;
 | |
| 		}
 | |
| 		optnext++;
 | |
| 		if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
 | |
| 			goto atend;
 | |
| 	}
 | |
| 
 | |
| 	c = *p++;
 | |
| 	for (q = optstr; *q != c; ) {
 | |
| 		if (*q == '\0') {
 | |
| 			if (optstr[0] == ':') {
 | |
| 				s[0] = c;
 | |
| 				s[1] = '\0';
 | |
| 				err |= setvarsafe("OPTARG", s, 0);
 | |
| 			}
 | |
| 			else {
 | |
| 				out2fmt("Illegal option -%c\n", c);
 | |
| 				(void) unsetvar("OPTARG");
 | |
| 			}
 | |
| 			c = '?';
 | |
| 			goto bad;
 | |
| 		}
 | |
| 		if (*++q == ':')
 | |
| 			q++;
 | |
| 	}
 | |
| 
 | |
| 	if (*++q == ':') {
 | |
| 		if (*p == '\0' && (p = *optnext) == NULL) {
 | |
| 			if (optstr[0] == ':') {
 | |
| 				s[0] = c;
 | |
| 				s[1] = '\0';
 | |
| 				err |= setvarsafe("OPTARG", s, 0);
 | |
| 				c = ':';
 | |
| 			}
 | |
| 			else {
 | |
| 				out2fmt("No arg for -%c option\n", c);
 | |
| 				(void) unsetvar("OPTARG");
 | |
| 				c = '?';
 | |
| 			}
 | |
| 			goto bad;
 | |
| 		}
 | |
| 
 | |
| 		if (p == *optnext)
 | |
| 			optnext++;
 | |
| 		setvarsafe("OPTARG", p, 0);
 | |
| 		p = NULL;
 | |
| 	}
 | |
| 	else
 | |
| 		setvarsafe("OPTARG", "", 0);
 | |
| 	*myoptind = optnext - optfirst + 1;
 | |
| 	goto out;
 | |
| 
 | |
| bad:
 | |
| 	*myoptind = 1;
 | |
| 	p = NULL;
 | |
| out:
 | |
| 	*optoff = p ? p - *(optnext - 1) : -1;
 | |
| 	snprintf(s, sizeof(s), "%d", *myoptind);
 | |
| 	err |= setvarsafe("OPTIND", s, VNOFUNC);
 | |
| 	s[0] = c;
 | |
| 	s[1] = '\0';
 | |
| 	err |= setvarsafe(optvar, s, 0);
 | |
| 	if (err) {
 | |
| 		*myoptind = 1;
 | |
| 		*optoff = -1;
 | |
| 		exraise(EXERROR);
 | |
| 	}
 | |
| 	return done;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * XXX - should get rid of.  have all builtins use getopt(3).  the
 | |
|  * library getopt must have the BSD extension static variable "optreset"
 | |
|  * otherwise it can't be used within the shell safely.
 | |
|  *
 | |
|  * Standard option processing (a la getopt) for builtin routines.  The
 | |
|  * only argument that is passed to nextopt is the option string; the
 | |
|  * other arguments are unnecessary.  It return the character, or '\0' on
 | |
|  * end of input.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| nextopt(const char *optstring)
 | |
| {
 | |
| 	char *p;
 | |
| 	const char *q;
 | |
| 	char c;
 | |
| 
 | |
| 	if ((p = optptr) == NULL || *p == '\0') {
 | |
| 		p = *argptr;
 | |
| 		if (p == NULL || *p != '-' || *++p == '\0')
 | |
| 			return '\0';
 | |
| 		argptr++;
 | |
| 		if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
 | |
| 			return '\0';
 | |
| 	}
 | |
| 	c = *p++;
 | |
| 	for (q = optstring ; *q != c ; ) {
 | |
| 		if (*q == '\0')
 | |
| 			error("Illegal option -%c", c);
 | |
| 		if (*++q == ':')
 | |
| 			q++;
 | |
| 	}
 | |
| 	if (*++q == ':') {
 | |
| 		if (*p == '\0' && (p = *argptr++) == NULL)
 | |
| 			error("No arg for -%c option", c);
 | |
| 		optionarg = p;
 | |
| 		p = NULL;
 | |
| 	}
 | |
| 	optptr = p;
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| static void
 | |
| flushall() {
 | |
| 	INTOFF;
 | |
| 	fflush(stdout);
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| out2fmt(const char *fmt, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 	va_start(ap, fmt);
 | |
| 	vfprintf(stderr, fmt, ap);
 | |
| 	va_end(ap);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Version of write which resumes after a signal is caught.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| xwrite(int fd, const char *buf, int nbytes)
 | |
| {
 | |
| 	int ntry;
 | |
| 	int i;
 | |
| 	int n;
 | |
| 
 | |
| 	n = nbytes;
 | |
| 	ntry = 0;
 | |
| 	for (;;) {
 | |
| 		i = write(fd, buf, n);
 | |
| 		if (i > 0) {
 | |
| 			if ((n -= i) <= 0)
 | |
| 				return nbytes;
 | |
| 			buf += i;
 | |
| 			ntry = 0;
 | |
| 		} else if (i == 0) {
 | |
| 			if (++ntry > 10)
 | |
| 				return nbytes - n;
 | |
| 		} else if (errno != EINTR) {
 | |
| 			return -1;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Shell command parser.
 | |
|  */
 | |
| 
 | |
| #define EOFMARKLEN 79
 | |
| 
 | |
| 
 | |
| 
 | |
| struct heredoc {
 | |
| 	struct heredoc *next;   /* next here document in list */
 | |
| 	union node *here;               /* redirection node */
 | |
| 	char *eofmark;          /* string indicating end of input */
 | |
| 	int striptabs;          /* if set, strip leading tabs */
 | |
| };
 | |
| 
 | |
| static struct heredoc *heredoclist;     /* list of here documents to read */
 | |
| static int parsebackquote;              /* nonzero if we are inside backquotes */
 | |
| static int doprompt;                    /* if set, prompt the user */
 | |
| static int needprompt;                  /* true if interactive and at start of line */
 | |
| static int lasttoken;                   /* last token read */
 | |
| 
 | |
| static char *wordtext;                  /* text of last word returned by readtoken */
 | |
| 
 | |
| static struct nodelist *backquotelist;
 | |
| static union node *redirnode;
 | |
| static struct heredoc *heredoc;
 | |
| static int quoteflag;                   /* set if (part of) last token was quoted */
 | |
| static int startlinno;                  /* line # where last token started */
 | |
| 
 | |
| 
 | |
| static union node *list (int);
 | |
| static union node *andor (void);
 | |
| static union node *pipeline (void);
 | |
| static union node *command (void);
 | |
| static union node *simplecmd(union node **rpp, union node *redir);
 | |
| static void parsefname (void);
 | |
| static void parseheredoc (void);
 | |
| static char peektoken (void);
 | |
| static int readtoken (void);
 | |
| static int xxreadtoken (void);
 | |
| static int readtoken1 (int, int, const char *, int);
 | |
| static int noexpand (char *);
 | |
| static void synexpect (int) __attribute__((noreturn));
 | |
| static void synerror (const char *) __attribute__((noreturn));
 | |
| static void setprompt (int);
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
 | |
|  * valid parse tree indicating a blank line.)
 | |
|  */
 | |
| 
 | |
| static union node *
 | |
| parsecmd(int interact)
 | |
| {
 | |
| 	int t;
 | |
| 
 | |
| 	tokpushback = 0;
 | |
| 	doprompt = interact;
 | |
| 	if (doprompt)
 | |
| 		setprompt(1);
 | |
| 	else
 | |
| 		setprompt(0);
 | |
| 	needprompt = 0;
 | |
| 	t = readtoken();
 | |
| 	if (t == TEOF)
 | |
| 		return NEOF;
 | |
| 	if (t == TNL)
 | |
| 		return NULL;
 | |
| 	tokpushback++;
 | |
| 	return list(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| static union node *
 | |
| list(nlflag)
 | |
| 	int nlflag;
 | |
| {
 | |
| 	union node *n1, *n2, *n3;
 | |
| 	int tok;
 | |
| 
 | |
| 	checkkwd = 2;
 | |
| 	if (nlflag == 0 && peektoken())
 | |
| 		return NULL;
 | |
| 	n1 = NULL;
 | |
| 	for (;;) {
 | |
| 		n2 = andor();
 | |
| 		tok = readtoken();
 | |
| 		if (tok == TBACKGND) {
 | |
| 			if (n2->type == NCMD || n2->type == NPIPE) {
 | |
| 				n2->ncmd.backgnd = 1;
 | |
| 			} else if (n2->type == NREDIR) {
 | |
| 				n2->type = NBACKGND;
 | |
| 			} else {
 | |
| 				n3 = (union node *)stalloc(sizeof (struct nredir));
 | |
| 				n3->type = NBACKGND;
 | |
| 				n3->nredir.n = n2;
 | |
| 				n3->nredir.redirect = NULL;
 | |
| 				n2 = n3;
 | |
| 			}
 | |
| 		}
 | |
| 		if (n1 == NULL) {
 | |
| 			n1 = n2;
 | |
| 		}
 | |
| 		else {
 | |
| 			n3 = (union node *)stalloc(sizeof (struct nbinary));
 | |
| 			n3->type = NSEMI;
 | |
| 			n3->nbinary.ch1 = n1;
 | |
| 			n3->nbinary.ch2 = n2;
 | |
| 			n1 = n3;
 | |
| 		}
 | |
| 		switch (tok) {
 | |
| 		case TBACKGND:
 | |
| 		case TSEMI:
 | |
| 			tok = readtoken();
 | |
| 			/* fall through */
 | |
| 		case TNL:
 | |
| 			if (tok == TNL) {
 | |
| 				parseheredoc();
 | |
| 				if (nlflag)
 | |
| 					return n1;
 | |
| 			} else {
 | |
| 				tokpushback++;
 | |
| 			}
 | |
| 			checkkwd = 2;
 | |
| 			if (peektoken())
 | |
| 				return n1;
 | |
| 			break;
 | |
| 		case TEOF:
 | |
| 			if (heredoclist)
 | |
| 				parseheredoc();
 | |
| 			else
 | |
| 				pungetc();              /* push back EOF on input */
 | |
| 			return n1;
 | |
| 		default:
 | |
| 			if (nlflag)
 | |
| 				synexpect(-1);
 | |
| 			tokpushback++;
 | |
| 			return n1;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static union node *
 | |
| andor() {
 | |
| 	union node *n1, *n2, *n3;
 | |
| 	int t;
 | |
| 
 | |
| 	checkkwd = 1;
 | |
| 	n1 = pipeline();
 | |
| 	for (;;) {
 | |
| 		if ((t = readtoken()) == TAND) {
 | |
| 			t = NAND;
 | |
| 		} else if (t == TOR) {
 | |
| 			t = NOR;
 | |
| 		} else {
 | |
| 			tokpushback++;
 | |
| 			return n1;
 | |
| 		}
 | |
| 		checkkwd = 2;
 | |
| 		n2 = pipeline();
 | |
| 		n3 = (union node *)stalloc(sizeof (struct nbinary));
 | |
| 		n3->type = t;
 | |
| 		n3->nbinary.ch1 = n1;
 | |
| 		n3->nbinary.ch2 = n2;
 | |
| 		n1 = n3;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static union node *
 | |
| pipeline() {
 | |
| 	union node *n1, *n2, *pipenode;
 | |
| 	struct nodelist *lp, *prev;
 | |
| 	int negate;
 | |
| 
 | |
| 	negate = 0;
 | |
| 	TRACE(("pipeline: entered\n"));
 | |
| 	if (readtoken() == TNOT) {
 | |
| 		negate = !negate;
 | |
| 		checkkwd = 1;
 | |
| 	} else
 | |
| 		tokpushback++;
 | |
| 	n1 = command();
 | |
| 	if (readtoken() == TPIPE) {
 | |
| 		pipenode = (union node *)stalloc(sizeof (struct npipe));
 | |
| 		pipenode->type = NPIPE;
 | |
| 		pipenode->npipe.backgnd = 0;
 | |
| 		lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
 | |
| 		pipenode->npipe.cmdlist = lp;
 | |
| 		lp->n = n1;
 | |
| 		do {
 | |
| 			prev = lp;
 | |
| 			lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
 | |
| 			checkkwd = 2;
 | |
| 			lp->n = command();
 | |
| 			prev->next = lp;
 | |
| 		} while (readtoken() == TPIPE);
 | |
| 		lp->next = NULL;
 | |
| 		n1 = pipenode;
 | |
| 	}
 | |
| 	tokpushback++;
 | |
| 	if (negate) {
 | |
| 		n2 = (union node *)stalloc(sizeof (struct nnot));
 | |
| 		n2->type = NNOT;
 | |
| 		n2->nnot.com = n1;
 | |
| 		return n2;
 | |
| 	} else
 | |
| 		return n1;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static union node *
 | |
| command(void) {
 | |
| 	union node *n1, *n2;
 | |
| 	union node *ap, **app;
 | |
| 	union node *cp, **cpp;
 | |
| 	union node *redir, **rpp;
 | |
| 	int t;
 | |
| 
 | |
| 	redir = NULL;
 | |
| 	n1 = NULL;
 | |
| 	rpp = &redir;
 | |
| 
 | |
| 	/* Check for redirection which may precede command */
 | |
| 	while (readtoken() == TREDIR) {
 | |
| 		*rpp = n2 = redirnode;
 | |
| 		rpp = &n2->nfile.next;
 | |
| 		parsefname();
 | |
| 	}
 | |
| 	tokpushback++;
 | |
| 
 | |
| 	switch (readtoken()) {
 | |
| 	case TIF:
 | |
| 		n1 = (union node *)stalloc(sizeof (struct nif));
 | |
| 		n1->type = NIF;
 | |
| 		n1->nif.test = list(0);
 | |
| 		if (readtoken() != TTHEN)
 | |
| 			synexpect(TTHEN);
 | |
| 		n1->nif.ifpart = list(0);
 | |
| 		n2 = n1;
 | |
| 		while (readtoken() == TELIF) {
 | |
| 			n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
 | |
| 			n2 = n2->nif.elsepart;
 | |
| 			n2->type = NIF;
 | |
| 			n2->nif.test = list(0);
 | |
| 			if (readtoken() != TTHEN)
 | |
| 				synexpect(TTHEN);
 | |
| 			n2->nif.ifpart = list(0);
 | |
| 		}
 | |
| 		if (lasttoken == TELSE)
 | |
| 			n2->nif.elsepart = list(0);
 | |
| 		else {
 | |
| 			n2->nif.elsepart = NULL;
 | |
| 			tokpushback++;
 | |
| 		}
 | |
| 		if (readtoken() != TFI)
 | |
| 			synexpect(TFI);
 | |
| 		checkkwd = 1;
 | |
| 		break;
 | |
| 	case TWHILE:
 | |
| 	case TUNTIL: {
 | |
| 		int got;
 | |
| 		n1 = (union node *)stalloc(sizeof (struct nbinary));
 | |
| 		n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
 | |
| 		n1->nbinary.ch1 = list(0);
 | |
| 		if ((got=readtoken()) != TDO) {
 | |
| TRACE(("expecting DO got %s %s\n", tokname(got), got == TWORD ? wordtext : ""));
 | |
| 			synexpect(TDO);
 | |
| 		}
 | |
| 		n1->nbinary.ch2 = list(0);
 | |
| 		if (readtoken() != TDONE)
 | |
| 			synexpect(TDONE);
 | |
| 		checkkwd = 1;
 | |
| 		break;
 | |
| 	}
 | |
| 	case TFOR:
 | |
| 		if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
 | |
| 			synerror("Bad for loop variable");
 | |
| 		n1 = (union node *)stalloc(sizeof (struct nfor));
 | |
| 		n1->type = NFOR;
 | |
| 		n1->nfor.var = wordtext;
 | |
| 		checkkwd = 1;
 | |
| 		if (readtoken() == TIN) {
 | |
| 			app = ≈
 | |
| 			while (readtoken() == TWORD) {
 | |
| 				n2 = (union node *)stalloc(sizeof (struct narg));
 | |
| 				n2->type = NARG;
 | |
| 				n2->narg.text = wordtext;
 | |
| 				n2->narg.backquote = backquotelist;
 | |
| 				*app = n2;
 | |
| 				app = &n2->narg.next;
 | |
| 			}
 | |
| 			*app = NULL;
 | |
| 			n1->nfor.args = ap;
 | |
| 			if (lasttoken != TNL && lasttoken != TSEMI)
 | |
| 				synexpect(-1);
 | |
| 		} else {
 | |
| 			static char argvars[5] = {CTLVAR, VSNORMAL|VSQUOTE,
 | |
| 								   '@', '=', '\0'};
 | |
| 			n2 = (union node *)stalloc(sizeof (struct narg));
 | |
| 			n2->type = NARG;
 | |
| 			n2->narg.text = argvars;
 | |
| 			n2->narg.backquote = NULL;
 | |
| 			n2->narg.next = NULL;
 | |
| 			n1->nfor.args = n2;
 | |
| 			/*
 | |
| 			 * Newline or semicolon here is optional (but note
 | |
| 			 * that the original Bourne shell only allowed NL).
 | |
| 			 */
 | |
| 			if (lasttoken != TNL && lasttoken != TSEMI)
 | |
| 				tokpushback++;
 | |
| 		}
 | |
| 		checkkwd = 2;
 | |
| 		if (readtoken() != TDO)
 | |
| 			synexpect(TDO);
 | |
| 		n1->nfor.body = list(0);
 | |
| 		if (readtoken() != TDONE)
 | |
| 			synexpect(TDONE);
 | |
| 		checkkwd = 1;
 | |
| 		break;
 | |
| 	case TCASE:
 | |
| 		n1 = (union node *)stalloc(sizeof (struct ncase));
 | |
| 		n1->type = NCASE;
 | |
| 		if (readtoken() != TWORD)
 | |
| 			synexpect(TWORD);
 | |
| 		n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg));
 | |
| 		n2->type = NARG;
 | |
| 		n2->narg.text = wordtext;
 | |
| 		n2->narg.backquote = backquotelist;
 | |
| 		n2->narg.next = NULL;
 | |
| 		do {
 | |
| 			checkkwd = 1;
 | |
| 		} while (readtoken() == TNL);
 | |
| 		if (lasttoken != TIN)
 | |
| 			synerror("expecting \"in\"");
 | |
| 		cpp = &n1->ncase.cases;
 | |
| 		checkkwd = 2, readtoken();
 | |
| 		do {
 | |
| 			if (lasttoken == TLP)
 | |
| 				readtoken();
 | |
| 			*cpp = cp = (union node *)stalloc(sizeof (struct nclist));
 | |
| 			cp->type = NCLIST;
 | |
| 			app = &cp->nclist.pattern;
 | |
| 			for (;;) {
 | |
| 				*app = ap = (union node *)stalloc(sizeof (struct narg));
 | |
| 				ap->type = NARG;
 | |
| 				ap->narg.text = wordtext;
 | |
| 				ap->narg.backquote = backquotelist;
 | |
| 				if (checkkwd = 2, readtoken() != TPIPE)
 | |
| 					break;
 | |
| 				app = &ap->narg.next;
 | |
| 				readtoken();
 | |
| 			}
 | |
| 			ap->narg.next = NULL;
 | |
| 			if (lasttoken != TRP)
 | |
| 				synexpect(TRP);
 | |
| 			cp->nclist.body = list(0);
 | |
| 
 | |
| 			checkkwd = 2;
 | |
| 			if ((t = readtoken()) != TESAC) {
 | |
| 				if (t != TENDCASE)
 | |
| 					synexpect(TENDCASE);
 | |
| 				else
 | |
| 					checkkwd = 2, readtoken();
 | |
| 			}
 | |
| 			cpp = &cp->nclist.next;
 | |
| 		} while(lasttoken != TESAC);
 | |
| 		*cpp = NULL;
 | |
| 		checkkwd = 1;
 | |
| 		break;
 | |
| 	case TLP:
 | |
| 		n1 = (union node *)stalloc(sizeof (struct nredir));
 | |
| 		n1->type = NSUBSHELL;
 | |
| 		n1->nredir.n = list(0);
 | |
| 		n1->nredir.redirect = NULL;
 | |
| 		if (readtoken() != TRP)
 | |
| 			synexpect(TRP);
 | |
| 		checkkwd = 1;
 | |
| 		break;
 | |
| 	case TBEGIN:
 | |
| 		n1 = list(0);
 | |
| 		if (readtoken() != TEND)
 | |
| 			synexpect(TEND);
 | |
| 		checkkwd = 1;
 | |
| 		break;
 | |
| 	/* Handle an empty command like other simple commands.  */
 | |
| 	case TSEMI:
 | |
| 	case TAND:
 | |
| 	case TOR:
 | |
| 	case TNL:
 | |
| 	case TEOF:
 | |
| 	case TRP:
 | |
| 	case TBACKGND:
 | |
| 		/*
 | |
| 		 * An empty command before a ; doesn't make much sense, and
 | |
| 		 * should certainly be disallowed in the case of `if ;'.
 | |
| 		 */
 | |
| 		if (!redir)
 | |
| 			synexpect(-1);
 | |
| 	case TWORD:
 | |
| 		tokpushback++;
 | |
| 		n1 = simplecmd(rpp, redir);
 | |
| 		return n1;
 | |
| 	default:
 | |
| 		synexpect(-1);
 | |
| 		/* NOTREACHED */
 | |
| 	}
 | |
| 
 | |
| 	/* Now check for redirection which may follow command */
 | |
| 	while (readtoken() == TREDIR) {
 | |
| 		*rpp = n2 = redirnode;
 | |
| 		rpp = &n2->nfile.next;
 | |
| 		parsefname();
 | |
| 	}
 | |
| 	tokpushback++;
 | |
| 	*rpp = NULL;
 | |
| 	if (redir) {
 | |
| 		if (n1->type != NSUBSHELL) {
 | |
| 			n2 = (union node *)stalloc(sizeof (struct nredir));
 | |
| 			n2->type = NREDIR;
 | |
| 			n2->nredir.n = n1;
 | |
| 			n1 = n2;
 | |
| 		}
 | |
| 		n1->nredir.redirect = redir;
 | |
| 	}
 | |
| 
 | |
| 	return n1;
 | |
| }
 | |
| 
 | |
| 
 | |
| static union node *
 | |
| simplecmd(union node **rpp, union node *redir) {
 | |
| 	union node *args, **app;
 | |
| 	union node *n = NULL;
 | |
| 	union node *vars, **vpp;
 | |
| 	union node **orig_rpp;
 | |
| 
 | |
| 	args = NULL;
 | |
| 	app = &args;
 | |
| 	vars = NULL;
 | |
| 	vpp = &vars;
 | |
| 
 | |
| 	/* If we don't have any redirections already, then we must reset
 | |
| 	  rpp to be the address of the local redir variable.  */
 | |
| 	if (redir == 0)
 | |
| 	rpp = &redir;
 | |
| 	/* We save the incoming value, because we need this for shell
 | |
| 	  functions.  There can not be a redirect or an argument between
 | |
| 	  the function name and the open parenthesis.  */
 | |
| 	orig_rpp = rpp;
 | |
| 
 | |
| 	checkalias = 2;
 | |
| 	for (;;) {
 | |
| 		switch (readtoken()) {
 | |
| 		case TWORD:
 | |
| 		case TASSIGN:
 | |
| 			n = (union node *)stalloc(sizeof (struct narg));
 | |
| 			n->type = NARG;
 | |
| 			n->narg.text = wordtext;
 | |
| 			n->narg.backquote = backquotelist;
 | |
| 			if (lasttoken == TWORD) {
 | |
| 				*app = n;
 | |
| 				app = &n->narg.next;
 | |
| 			} else {
 | |
| 				*vpp = n;
 | |
| 				vpp = &n->narg.next;
 | |
| 			}
 | |
| 			break;
 | |
| 		case TREDIR:
 | |
| 			*rpp = n = redirnode;
 | |
| 			rpp = &n->nfile.next;
 | |
| 			parsefname();   /* read name of redirection file */
 | |
| 			break;
 | |
| 		case TLP:
 | |
| 			if (
 | |
| 				args && app == &args->narg.next &&
 | |
| 				!vars && rpp == orig_rpp
 | |
| 			) {
 | |
| 				/* We have a function */
 | |
| 				if (readtoken() != TRP)
 | |
| 					synexpect(TRP);
 | |
| 				n->type = NDEFUN;
 | |
| 				checkkwd = 2;
 | |
| 				n->narg.next = command();
 | |
| 				return n;
 | |
| 			}
 | |
| 			/* fall through */
 | |
| 		default:
 | |
| 			tokpushback++;
 | |
| 			goto out;
 | |
| 		}
 | |
| 	}
 | |
| out:
 | |
| 	*app = NULL;
 | |
| 	*vpp = NULL;
 | |
| 	*rpp = NULL;
 | |
| 	n = (union node *)stalloc(sizeof (struct ncmd));
 | |
| 	n->type = NCMD;
 | |
| 	n->ncmd.backgnd = 0;
 | |
| 	n->ncmd.args = args;
 | |
| 	n->ncmd.assign = vars;
 | |
| 	n->ncmd.redirect = redir;
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static union node *
 | |
| makename(void) {
 | |
| 	union node *n;
 | |
| 
 | |
| 	n = (union node *)stalloc(sizeof (struct narg));
 | |
| 	n->type = NARG;
 | |
| 	n->narg.next = NULL;
 | |
| 	n->narg.text = wordtext;
 | |
| 	n->narg.backquote = backquotelist;
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static void fixredir(union node *n, const char *text, int err)
 | |
| {
 | |
| 	TRACE(("Fix redir %s %d\n", text, err));
 | |
| 	if (!err)
 | |
| 		n->ndup.vname = NULL;
 | |
| 
 | |
| 	if (is_digit(text[0]) && text[1] == '\0')
 | |
| 		n->ndup.dupfd = digit_val(text[0]);
 | |
| 	else if (text[0] == '-' && text[1] == '\0')
 | |
| 		n->ndup.dupfd = -1;
 | |
| 	else {
 | |
| 
 | |
| 		if (err)
 | |
| 			synerror("Bad fd number");
 | |
| 		else
 | |
| 			n->ndup.vname = makename();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| parsefname(void) {
 | |
| 	union node *n = redirnode;
 | |
| 
 | |
| 	if (readtoken() != TWORD)
 | |
| 		synexpect(-1);
 | |
| 	if (n->type == NHERE) {
 | |
| 		struct heredoc *here = heredoc;
 | |
| 		struct heredoc *p;
 | |
| 		int i;
 | |
| 
 | |
| 		if (quoteflag == 0)
 | |
| 			n->type = NXHERE;
 | |
| 		TRACE(("Here document %d\n", n->type));
 | |
| 		if (here->striptabs) {
 | |
| 			while (*wordtext == '\t')
 | |
| 				wordtext++;
 | |
| 		}
 | |
| 		if (! noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN)
 | |
| 			synerror("Illegal eof marker for << redirection");
 | |
| 		rmescapes(wordtext);
 | |
| 		here->eofmark = wordtext;
 | |
| 		here->next = NULL;
 | |
| 		if (heredoclist == NULL)
 | |
| 			heredoclist = here;
 | |
| 		else {
 | |
| 			for (p = heredoclist ; p->next ; p = p->next);
 | |
| 			p->next = here;
 | |
| 		}
 | |
| 	} else if (n->type == NTOFD || n->type == NFROMFD) {
 | |
| 		fixredir(n, wordtext, 0);
 | |
| 	} else {
 | |
| 		n->nfile.fname = makename();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Input any here documents.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| parseheredoc() {
 | |
| 	struct heredoc *here;
 | |
| 	union node *n;
 | |
| 
 | |
| 	while (heredoclist) {
 | |
| 		here = heredoclist;
 | |
| 		heredoclist = here->next;
 | |
| 		if (needprompt) {
 | |
| 			setprompt(2);
 | |
| 			needprompt = 0;
 | |
| 		}
 | |
| 		readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
 | |
| 				here->eofmark, here->striptabs);
 | |
| 		n = (union node *)stalloc(sizeof (struct narg));
 | |
| 		n->narg.type = NARG;
 | |
| 		n->narg.next = NULL;
 | |
| 		n->narg.text = wordtext;
 | |
| 		n->narg.backquote = backquotelist;
 | |
| 		here->here->nhere.doc = n;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static char
 | |
| peektoken() {
 | |
| 	int t;
 | |
| 
 | |
| 	t = readtoken();
 | |
| 	tokpushback++;
 | |
| 	return tokname_array[t][0];
 | |
| }
 | |
| 
 | |
| static int
 | |
| readtoken() {
 | |
| 	int t;
 | |
| 
 | |
| #ifdef ASH_ALIAS
 | |
| 	int savecheckalias = checkalias;
 | |
| 	int savecheckkwd = checkkwd;
 | |
| 	struct alias *ap;
 | |
| #endif
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 	int alreadyseen = tokpushback;
 | |
| #endif
 | |
| 
 | |
| #ifdef ASH_ALIAS
 | |
| top:
 | |
| #endif
 | |
| 
 | |
| 	t = xxreadtoken();
 | |
| 
 | |
| #ifdef ASH_ALIAS
 | |
| 	checkalias = savecheckalias;
 | |
| #endif
 | |
| 
 | |
| 	if (checkkwd) {
 | |
| 		/*
 | |
| 		 * eat newlines
 | |
| 		 */
 | |
| 		if (checkkwd == 2) {
 | |
| 			checkkwd = 0;
 | |
| 			while (t == TNL) {
 | |
| 				parseheredoc();
 | |
| 				t = xxreadtoken();
 | |
| 			}
 | |
| 		}
 | |
| 		checkkwd = 0;
 | |
| 		/*
 | |
| 		 * check for keywords
 | |
| 		 */
 | |
| 		if (t == TWORD && !quoteflag)
 | |
| 		{
 | |
| 			const char *const *pp;
 | |
| 
 | |
| 			if ((pp = findkwd(wordtext))) {
 | |
| 				lasttoken = t = pp - tokname_array;
 | |
| 				TRACE(("keyword %s recognized\n", tokname(t)));
 | |
| 				goto out;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	if (t != TWORD) {
 | |
| 		if (t != TREDIR) {
 | |
| 			checkalias = 0;
 | |
| 		}
 | |
| 	} else if (checkalias == 2 && isassignment(wordtext)) {
 | |
| 		lasttoken = t = TASSIGN;
 | |
| #ifdef ASH_ALIAS
 | |
| 	} else if (checkalias) {
 | |
| 		if (!quoteflag && (ap = *__lookupalias(wordtext)) != NULL && !(ap->flag & ALIASINUSE)) {
 | |
| 			if (*ap->val) {
 | |
| 				pushstring(ap->val, strlen(ap->val), ap);
 | |
| 			}
 | |
| 			checkkwd = savecheckkwd;
 | |
| 			goto top;
 | |
| 		}
 | |
| 		checkalias = 0;
 | |
| #endif
 | |
| 	}
 | |
| out:
 | |
| #ifdef DEBUG
 | |
| 	if (!alreadyseen)
 | |
| 	    TRACE(("token %s %s\n", tokname(t), t == TWORD || t == TASSIGN ? wordtext : ""));
 | |
| 	else
 | |
| 	    TRACE(("reread token %s %s\n", tokname(t), t == TWORD || t == TASSIGN ? wordtext : ""));
 | |
| #endif
 | |
| 	return (t);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Read the next input token.
 | |
|  * If the token is a word, we set backquotelist to the list of cmds in
 | |
|  *      backquotes.  We set quoteflag to true if any part of the word was
 | |
|  *      quoted.
 | |
|  * If the token is TREDIR, then we set redirnode to a structure containing
 | |
|  *      the redirection.
 | |
|  * In all cases, the variable startlinno is set to the number of the line
 | |
|  *      on which the token starts.
 | |
|  *
 | |
|  * [Change comment:  here documents and internal procedures]
 | |
|  * [Readtoken shouldn't have any arguments.  Perhaps we should make the
 | |
|  *  word parsing code into a separate routine.  In this case, readtoken
 | |
|  *  doesn't need to have any internal procedures, but parseword does.
 | |
|  *  We could also make parseoperator in essence the main routine, and
 | |
|  *  have parseword (readtoken1?) handle both words and redirection.]
 | |
|  */
 | |
| 
 | |
| #define NEW_xxreadtoken
 | |
| #ifdef NEW_xxreadtoken
 | |
| 
 | |
| static const char xxreadtoken_chars[] = "\n()&|;"; /* singles must be first! */
 | |
| static const char xxreadtoken_tokens[] = {
 | |
| 	TNL, TLP, TRP,				/* only single occurrence allowed */
 | |
| 	TBACKGND, TPIPE, TSEMI,		/* if single occurrence */
 | |
| 	TEOF,						/* corresponds to trailing nul */
 | |
| 	TAND, TOR, TENDCASE,		/* if double occurrence */
 | |
| };
 | |
| 
 | |
| #define xxreadtoken_doubles \
 | |
| 	(sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars))
 | |
| #define xxreadtoken_singles \
 | |
| 	(sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1)
 | |
| 
 | |
| static int
 | |
| xxreadtoken() {
 | |
| 	int c;
 | |
| 
 | |
| 	if (tokpushback) {
 | |
| 		tokpushback = 0;
 | |
| 		return lasttoken;
 | |
| 	}
 | |
| 	if (needprompt) {
 | |
| 		setprompt(2);
 | |
| 		needprompt = 0;
 | |
| 	}
 | |
| 	startlinno = plinno;
 | |
| 	for (;;) {      /* until token or start of word found */
 | |
| 		c = pgetc_macro();
 | |
| 
 | |
| 		if ((c!=' ') && (c!='\t')
 | |
| #ifdef ASH_ALIAS
 | |
| 			&& (c!=PEOA)
 | |
| #endif
 | |
| 			) {
 | |
| 			if (c=='#') {
 | |
| 				while ((c = pgetc()) != '\n' && c != PEOF);
 | |
| 				pungetc();
 | |
| 			} else if (c=='\\') {
 | |
| 				if (pgetc() != '\n') {
 | |
| 					pungetc();
 | |
| 					goto READTOKEN1;
 | |
| 				}
 | |
| 				startlinno = ++plinno;
 | |
| 				setprompt(doprompt ? 2 : 0);
 | |
| 			} else {
 | |
| 				const char *p
 | |
| 					= xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
 | |
| 
 | |
| 				if (c!=PEOF) {
 | |
| 					if (c=='\n') {
 | |
| 						plinno++;
 | |
| 						needprompt = doprompt;
 | |
| 					}
 | |
| 
 | |
| 					p = strchr(xxreadtoken_chars, c);
 | |
| 					if (p == NULL) {
 | |
| 					READTOKEN1:
 | |
| 						return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
 | |
| 					}
 | |
| 			
 | |
| 					if (p-xxreadtoken_chars >= xxreadtoken_singles) {
 | |
| 						if (pgetc() == *p) { /* double occurrence? */
 | |
| 							p += xxreadtoken_doubles + 1;
 | |
| 						} else {
 | |
| 							pungetc();
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				return lasttoken = xxreadtoken_tokens[p-xxreadtoken_chars];
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| #else
 | |
| #define RETURN(token)   return lasttoken = token
 | |
| 
 | |
| static int
 | |
| xxreadtoken() {
 | |
| 	int c;
 | |
| 
 | |
| 	if (tokpushback) {
 | |
| 		tokpushback = 0;
 | |
| 		return lasttoken;
 | |
| 	}
 | |
| 	if (needprompt) {
 | |
| 		setprompt(2);
 | |
| 		needprompt = 0;
 | |
| 	}
 | |
| 	startlinno = plinno;
 | |
| 	for (;;) {      /* until token or start of word found */
 | |
| 		c = pgetc_macro();
 | |
| 		switch (c) {
 | |
| 		case ' ': case '\t':
 | |
| #ifdef ASH_ALIAS
 | |
| 		case PEOA:
 | |
| #endif
 | |
| 			continue;
 | |
| 		case '#':
 | |
| 			while ((c = pgetc()) != '\n' && c != PEOF);
 | |
| 			pungetc();
 | |
| 			continue;
 | |
| 		case '\\':
 | |
| 			if (pgetc() == '\n') {
 | |
| 				startlinno = ++plinno;
 | |
| 				if (doprompt)
 | |
| 					setprompt(2);
 | |
| 				else
 | |
| 					setprompt(0);
 | |
| 				continue;
 | |
| 			}
 | |
| 			pungetc();
 | |
| 			goto breakloop;
 | |
| 		case '\n':
 | |
| 			plinno++;
 | |
| 			needprompt = doprompt;
 | |
| 			RETURN(TNL);
 | |
| 		case PEOF:
 | |
| 			RETURN(TEOF);
 | |
| 		case '&':
 | |
| 			if (pgetc() == '&')
 | |
| 				RETURN(TAND);
 | |
| 			pungetc();
 | |
| 			RETURN(TBACKGND);
 | |
| 		case '|':
 | |
| 			if (pgetc() == '|')
 | |
| 				RETURN(TOR);
 | |
| 			pungetc();
 | |
| 			RETURN(TPIPE);
 | |
| 		case ';':
 | |
| 			if (pgetc() == ';')
 | |
| 				RETURN(TENDCASE);
 | |
| 			pungetc();
 | |
| 			RETURN(TSEMI);
 | |
| 		case '(':
 | |
| 			RETURN(TLP);
 | |
| 		case ')':
 | |
| 			RETURN(TRP);
 | |
| 		default:
 | |
| 			goto breakloop;
 | |
| 		}
 | |
| 	}
 | |
| breakloop:
 | |
| 	return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
 | |
| #undef RETURN
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
 | |
|  * is not NULL, read a here document.  In the latter case, eofmark is the
 | |
|  * word which marks the end of the document and striptabs is true if
 | |
|  * leading tabs should be stripped from the document.  The argument firstc
 | |
|  * is the first character of the input token or document.
 | |
|  *
 | |
|  * Because C does not have internal subroutines, I have simulated them
 | |
|  * using goto's to implement the subroutine linkage.  The following macros
 | |
|  * will run code that appears at the end of readtoken1.
 | |
|  */
 | |
| 
 | |
| #define CHECKEND()      {goto checkend; checkend_return:;}
 | |
| #define PARSEREDIR()    {goto parseredir; parseredir_return:;}
 | |
| #define PARSESUB()      {goto parsesub; parsesub_return:;}
 | |
| #define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
 | |
| #define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
 | |
| #define PARSEARITH()    {goto parsearith; parsearith_return:;}
 | |
| 
 | |
| static int
 | |
| readtoken1(int firstc, int syntax, const char *eofmark, int striptabs)
 | |
| {
 | |
| 	int c = firstc;
 | |
| 	char *out;
 | |
| 	int len;
 | |
| 	char line[EOFMARKLEN + 1];
 | |
| 	struct nodelist *bqlist;
 | |
| 	int quotef;
 | |
| 	int dblquote;
 | |
| 	int varnest;    /* levels of variables expansion */
 | |
| 	int arinest;    /* levels of arithmetic expansion */
 | |
| 	int parenlevel; /* levels of parens in arithmetic */
 | |
| 	int dqvarnest;  /* levels of variables expansion within double quotes */
 | |
| 	int oldstyle;
 | |
| 	int prevsyntax; /* syntax before arithmetic */
 | |
| #if __GNUC__
 | |
| 	/* Avoid longjmp clobbering */
 | |
| 	(void) &out;
 | |
| 	(void) "ef;
 | |
| 	(void) &dblquote;
 | |
| 	(void) &varnest;
 | |
| 	(void) &arinest;
 | |
| 	(void) &parenlevel;
 | |
| 	(void) &dqvarnest;
 | |
| 	(void) &oldstyle;
 | |
| 	(void) &prevsyntax;
 | |
| 	(void) &syntax;
 | |
| #endif
 | |
| 
 | |
| 	startlinno = plinno;
 | |
| 	dblquote = 0;
 | |
| 	if (syntax == DQSYNTAX)
 | |
| 		dblquote = 1;
 | |
| 	quotef = 0;
 | |
| 	bqlist = NULL;
 | |
| 	varnest = 0;
 | |
| 	arinest = 0;
 | |
| 	parenlevel = 0;
 | |
| 	dqvarnest = 0;
 | |
| 
 | |
| 	STARTSTACKSTR(out);
 | |
| 	loop: { /* for each line, until end of word */
 | |
| 		CHECKEND();     /* set c to PEOF if at end of here document */
 | |
| 		for (;;) {      /* until end of line or end of word */
 | |
| 			CHECKSTRSPACE(3, out);  /* permit 3 calls to USTPUTC */
 | |
| 			switch(SIT(c,syntax)) {
 | |
| 			case CNL:       /* '\n' */
 | |
| 				if (syntax == BASESYNTAX)
 | |
| 					goto endword;   /* exit outer loop */
 | |
| 				USTPUTC(c, out);
 | |
| 				plinno++;
 | |
| 				if (doprompt)
 | |
| 					setprompt(2);
 | |
| 				else
 | |
| 					setprompt(0);
 | |
| 				c = pgetc();
 | |
| 				goto loop;              /* continue outer loop */
 | |
| 			case CWORD:
 | |
| 				USTPUTC(c, out);
 | |
| 				break;
 | |
| 			case CCTL:
 | |
| 				if ((eofmark == NULL || dblquote) &&
 | |
| 				    dqvarnest == 0)
 | |
| 					USTPUTC(CTLESC, out);
 | |
| 				USTPUTC(c, out);
 | |
| 				break;
 | |
| 			case CBACK:     /* backslash */
 | |
| 				c = pgetc2();
 | |
| 				if (c == PEOF) {
 | |
| 					USTPUTC('\\', out);
 | |
| 					pungetc();
 | |
| 				} else if (c == '\n') {
 | |
| 					if (doprompt)
 | |
| 						setprompt(2);
 | |
| 					else
 | |
| 						setprompt(0);
 | |
| 				} else {
 | |
| 					if (dblquote && c != '\\' && c != '`' && c != '$'
 | |
| 							 && (c != '"' || eofmark != NULL))
 | |
| 						USTPUTC('\\', out);
 | |
| 					if (SIT(c,SQSYNTAX) == CCTL)
 | |
| 						USTPUTC(CTLESC, out);
 | |
| 					else if (eofmark == NULL)
 | |
| 						USTPUTC(CTLQUOTEMARK, out);
 | |
| 					USTPUTC(c, out);
 | |
| 					quotef++;
 | |
| 				}
 | |
| 				break;
 | |
| 			case CSQUOTE:
 | |
| 				if (eofmark == NULL)
 | |
| 					USTPUTC(CTLQUOTEMARK, out);
 | |
| 				syntax = SQSYNTAX;
 | |
| 				break;
 | |
| 			case CDQUOTE:
 | |
| 				if (eofmark == NULL)
 | |
| 					USTPUTC(CTLQUOTEMARK, out);
 | |
| 				syntax = DQSYNTAX;
 | |
| 				dblquote = 1;
 | |
| 				break;
 | |
| 			case CENDQUOTE:
 | |
| 				if (eofmark != NULL && arinest == 0 &&
 | |
| 				    varnest == 0) {
 | |
| 					USTPUTC(c, out);
 | |
| 				} else {
 | |
| 					if (arinest) {
 | |
| 						syntax = ARISYNTAX;
 | |
| 						dblquote = 0;
 | |
| 					} else if (eofmark == NULL &&
 | |
| 						   dqvarnest == 0) {
 | |
| 						syntax = BASESYNTAX;
 | |
| 						dblquote = 0;
 | |
| 					}
 | |
| 					quotef++;
 | |
| 				}
 | |
| 				break;
 | |
| 			case CVAR:      /* '$' */
 | |
| 				PARSESUB();             /* parse substitution */
 | |
| 				break;
 | |
| 			case CENDVAR:   /* '}' */
 | |
| 				if (varnest > 0) {
 | |
| 					varnest--;
 | |
| 					if (dqvarnest > 0) {
 | |
| 						dqvarnest--;
 | |
| 					}
 | |
| 					USTPUTC(CTLENDVAR, out);
 | |
| 				} else {
 | |
| 					USTPUTC(c, out);
 | |
| 				}
 | |
| 				break;
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| 			case CLP:       /* '(' in arithmetic */
 | |
| 				parenlevel++;
 | |
| 				USTPUTC(c, out);
 | |
| 				break;
 | |
| 			case CRP:       /* ')' in arithmetic */
 | |
| 				if (parenlevel > 0) {
 | |
| 					USTPUTC(c, out);
 | |
| 					--parenlevel;
 | |
| 				} else {
 | |
| 					if (pgetc() == ')') {
 | |
| 						if (--arinest == 0) {
 | |
| 							USTPUTC(CTLENDARI, out);
 | |
| 							syntax = prevsyntax;
 | |
| 							if (syntax == DQSYNTAX)
 | |
| 								dblquote = 1;
 | |
| 							else
 | |
| 								dblquote = 0;
 | |
| 						} else
 | |
| 							USTPUTC(')', out);
 | |
| 					} else {
 | |
| 						/*
 | |
| 						 * unbalanced parens
 | |
| 						 *  (don't 2nd guess - no error)
 | |
| 						 */
 | |
| 						pungetc();
 | |
| 						USTPUTC(')', out);
 | |
| 					}
 | |
| 				}
 | |
| 				break;
 | |
| #endif
 | |
| 			case CBQUOTE:   /* '`' */
 | |
| 				PARSEBACKQOLD();
 | |
| 				break;
 | |
| 			case CENDFILE:
 | |
| 				goto endword;           /* exit outer loop */
 | |
| 			case CIGN:
 | |
| 				break;
 | |
| 			default:
 | |
| 				if (varnest == 0)
 | |
| 					goto endword;   /* exit outer loop */
 | |
| #ifdef ASH_ALIAS
 | |
| 				if (c != PEOA)
 | |
| #endif
 | |
| 					USTPUTC(c, out);
 | |
| 
 | |
| 			}
 | |
| 			c = pgetc_macro();
 | |
| 		}
 | |
| 	}
 | |
| endword:
 | |
| 	if (syntax == ARISYNTAX)
 | |
| 		synerror("Missing '))'");
 | |
| 	if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL)
 | |
| 		synerror("Unterminated quoted string");
 | |
| 	if (varnest != 0) {
 | |
| 		startlinno = plinno;
 | |
| 		synerror("Missing '}'");
 | |
| 	}
 | |
| 	USTPUTC('\0', out);
 | |
| 	len = out - stackblock();
 | |
| 	out = stackblock();
 | |
| 	if (eofmark == NULL) {
 | |
| 		if ((c == '>' || c == '<')
 | |
| 		 && quotef == 0
 | |
| 		 && len <= 2
 | |
| 		 && (*out == '\0' || is_digit(*out))) {
 | |
| 			PARSEREDIR();
 | |
| 			return lasttoken = TREDIR;
 | |
| 		} else {
 | |
| 			pungetc();
 | |
| 		}
 | |
| 	}
 | |
| 	quoteflag = quotef;
 | |
| 	backquotelist = bqlist;
 | |
| 	grabstackblock(len);
 | |
| 	wordtext = out;
 | |
| 	return lasttoken = TWORD;
 | |
| /* end of readtoken routine */
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Check to see whether we are at the end of the here document.  When this
 | |
|  * is called, c is set to the first character of the next input line.  If
 | |
|  * we are at the end of the here document, this routine sets the c to PEOF.
 | |
|  */
 | |
| 
 | |
| checkend: {
 | |
| 	if (eofmark) {
 | |
| #ifdef ASH_ALIAS
 | |
| 		if (c == PEOA) {
 | |
| 			c = pgetc2();
 | |
| 		}
 | |
| #endif
 | |
| 		if (striptabs) {
 | |
| 			while (c == '\t') {
 | |
| 				c = pgetc2();
 | |
| 			}
 | |
| 		}
 | |
| 		if (c == *eofmark) {
 | |
| 			if (pfgets(line, sizeof line) != NULL) {
 | |
| 				const char *p, *q;
 | |
| 
 | |
| 				p = line;
 | |
| 				for (q = eofmark + 1 ; *q && *p == *q ; p++, q++);
 | |
| 				if (*p == '\n' && *q == '\0') {
 | |
| 					c = PEOF;
 | |
| 					plinno++;
 | |
| 					needprompt = doprompt;
 | |
| 				} else {
 | |
| 					pushstring(line, strlen(line), NULL);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	goto checkend_return;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Parse a redirection operator.  The variable "out" points to a string
 | |
|  * specifying the fd to be redirected.  The variable "c" contains the
 | |
|  * first character of the redirection operator.
 | |
|  */
 | |
| 
 | |
| parseredir: {
 | |
| 	char fd = *out;
 | |
| 	union node *np;
 | |
| 
 | |
| 	np = (union node *)stalloc(sizeof (struct nfile));
 | |
| 	if (c == '>') {
 | |
| 		np->nfile.fd = 1;
 | |
| 		c = pgetc();
 | |
| 		if (c == '>')
 | |
| 			np->type = NAPPEND;
 | |
| 		else if (c == '&')
 | |
| 			np->type = NTOFD;
 | |
| 		else if (c == '|')
 | |
| 			np->type = NTOOV;
 | |
| 		else {
 | |
| 			np->type = NTO;
 | |
| 			pungetc();
 | |
| 		}
 | |
| 	} else {        /* c == '<' */
 | |
| 		np->nfile.fd = 0;
 | |
| 		switch (c = pgetc()) {
 | |
| 		case '<':
 | |
| 			if (sizeof (struct nfile) != sizeof (struct nhere)) {
 | |
| 				np = (union node *)stalloc(sizeof (struct nhere));
 | |
| 				np->nfile.fd = 0;
 | |
| 			}
 | |
| 			np->type = NHERE;
 | |
| 			heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
 | |
| 			heredoc->here = np;
 | |
| 			if ((c = pgetc()) == '-') {
 | |
| 				heredoc->striptabs = 1;
 | |
| 			} else {
 | |
| 				heredoc->striptabs = 0;
 | |
| 				pungetc();
 | |
| 			}
 | |
| 			break;
 | |
| 
 | |
| 		case '&':
 | |
| 			np->type = NFROMFD;
 | |
| 			break;
 | |
| 
 | |
| 		case '>':
 | |
| 			np->type = NFROMTO;
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			np->type = NFROM;
 | |
| 			pungetc();
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (fd != '\0')
 | |
| 		np->nfile.fd = digit_val(fd);
 | |
| 	redirnode = np;
 | |
| 	goto parseredir_return;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Parse a substitution.  At this point, we have read the dollar sign
 | |
|  * and nothing else.
 | |
|  */
 | |
| 
 | |
| parsesub: {
 | |
| 	int subtype;
 | |
| 	int typeloc;
 | |
| 	int flags;
 | |
| 	char *p;
 | |
| 	static const char types[] = "}-+?=";
 | |
| 
 | |
| 	c = pgetc();
 | |
| 	if (
 | |
| 		c <= PEOA  ||
 | |
| 		(c != '(' && c != '{' && !is_name(c) && !is_special(c))
 | |
| 	) {
 | |
| 		USTPUTC('$', out);
 | |
| 		pungetc();
 | |
| 	} else if (c == '(') {  /* $(command) or $((arith)) */
 | |
| 		if (pgetc() == '(') {
 | |
| 			PARSEARITH();
 | |
| 		} else {
 | |
| 			pungetc();
 | |
| 			PARSEBACKQNEW();
 | |
| 		}
 | |
| 	} else {
 | |
| 		USTPUTC(CTLVAR, out);
 | |
| 		typeloc = out - stackblock();
 | |
| 		USTPUTC(VSNORMAL, out);
 | |
| 		subtype = VSNORMAL;
 | |
| 		if (c == '{') {
 | |
| 			c = pgetc();
 | |
| 			if (c == '#') {
 | |
| 				if ((c = pgetc()) == '}')
 | |
| 					c = '#';
 | |
| 				else
 | |
| 					subtype = VSLENGTH;
 | |
| 			}
 | |
| 			else
 | |
| 				subtype = 0;
 | |
| 		}
 | |
| 		if (c > PEOA && is_name(c)) {
 | |
| 			do {
 | |
| 				STPUTC(c, out);
 | |
| 				c = pgetc();
 | |
| 			} while (c > PEOA && is_in_name(c));
 | |
| 		} else if (is_digit(c)) {
 | |
| 			do {
 | |
| 				USTPUTC(c, out);
 | |
| 				c = pgetc();
 | |
| 			} while (is_digit(c));
 | |
| 		}
 | |
| 		else if (is_special(c)) {
 | |
| 			USTPUTC(c, out);
 | |
| 			c = pgetc();
 | |
| 		}
 | |
| 		else
 | |
| badsub:                 synerror("Bad substitution");
 | |
| 
 | |
| 		STPUTC('=', out);
 | |
| 		flags = 0;
 | |
| 		if (subtype == 0) {
 | |
| 			switch (c) {
 | |
| 			case ':':
 | |
| 				flags = VSNUL;
 | |
| 				c = pgetc();
 | |
| 				/*FALLTHROUGH*/
 | |
| 			default:
 | |
| 				p = strchr(types, c);
 | |
| 				if (p == NULL)
 | |
| 					goto badsub;
 | |
| 				subtype = p - types + VSNORMAL;
 | |
| 				break;
 | |
| 			case '%':
 | |
| 			case '#':
 | |
| 				{
 | |
| 					int cc = c;
 | |
| 					subtype = c == '#' ? VSTRIMLEFT :
 | |
| 							     VSTRIMRIGHT;
 | |
| 					c = pgetc();
 | |
| 					if (c == cc)
 | |
| 						subtype++;
 | |
| 					else
 | |
| 						pungetc();
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			pungetc();
 | |
| 		}
 | |
| 		if (dblquote || arinest)
 | |
| 			flags |= VSQUOTE;
 | |
| 		*(stackblock() + typeloc) = subtype | flags;
 | |
| 		if (subtype != VSNORMAL) {
 | |
| 			varnest++;
 | |
| 			if (dblquote) {
 | |
| 				dqvarnest++;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	goto parsesub_return;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called to parse command substitutions.  Newstyle is set if the command
 | |
|  * is enclosed inside $(...); nlpp is a pointer to the head of the linked
 | |
|  * list of commands (passed by reference), and savelen is the number of
 | |
|  * characters on the top of the stack which must be preserved.
 | |
|  */
 | |
| 
 | |
| parsebackq: {
 | |
| 	struct nodelist **nlpp;
 | |
| 	int savepbq;
 | |
| 	union node *n;
 | |
| 	char *volatile str;
 | |
| 	struct jmploc jmploc;
 | |
| 	struct jmploc *volatile savehandler;
 | |
| 	int savelen;
 | |
| 	int saveprompt;
 | |
| #ifdef __GNUC__
 | |
| 	(void) &saveprompt;
 | |
| #endif
 | |
| 
 | |
| 	savepbq = parsebackquote;
 | |
| 	if (setjmp(jmploc.loc)) {
 | |
| 		if (str)
 | |
| 			ckfree(str);
 | |
| 		parsebackquote = 0;
 | |
| 		handler = savehandler;
 | |
| 		longjmp(handler->loc, 1);
 | |
| 	}
 | |
| 	INTOFF;
 | |
| 	str = NULL;
 | |
| 	savelen = out - stackblock();
 | |
| 	if (savelen > 0) {
 | |
| 		str = ckmalloc(savelen);
 | |
| 		memcpy(str, stackblock(), savelen);
 | |
| 	}
 | |
| 	savehandler = handler;
 | |
| 	handler = &jmploc;
 | |
| 	INTON;
 | |
| 	if (oldstyle) {
 | |
| 		/* We must read until the closing backquote, giving special
 | |
| 		   treatment to some slashes, and then push the string and
 | |
| 		   reread it as input, interpreting it normally.  */
 | |
| 		char *pout;
 | |
| 		int pc;
 | |
| 		int psavelen;
 | |
| 		char *pstr;
 | |
| 
 | |
| 
 | |
| 		STARTSTACKSTR(pout);
 | |
| 		for (;;) {
 | |
| 			if (needprompt) {
 | |
| 				setprompt(2);
 | |
| 				needprompt = 0;
 | |
| 			}
 | |
| 			switch (pc = pgetc()) {
 | |
| 			case '`':
 | |
| 				goto done;
 | |
| 
 | |
| 			case '\\':
 | |
| 				if ((pc = pgetc()) == '\n') {
 | |
| 					plinno++;
 | |
| 					if (doprompt)
 | |
| 						setprompt(2);
 | |
| 					else
 | |
| 						setprompt(0);
 | |
| 					/*
 | |
| 					 * If eating a newline, avoid putting
 | |
| 					 * the newline into the new character
 | |
| 					 * stream (via the STPUTC after the
 | |
| 					 * switch).
 | |
| 					 */
 | |
| 					continue;
 | |
| 				}
 | |
| 				if (pc != '\\' && pc != '`' && pc != '$'
 | |
| 				    && (!dblquote || pc != '"'))
 | |
| 					STPUTC('\\', pout);
 | |
| 				if (pc > PEOA) {
 | |
| 					break;
 | |
| 				}
 | |
| 				/* fall through */
 | |
| 
 | |
| 			case PEOF:
 | |
| #ifdef ASH_ALIAS
 | |
| 			case PEOA:
 | |
| #endif
 | |
| 				startlinno = plinno;
 | |
| 				synerror("EOF in backquote substitution");
 | |
| 
 | |
| 			case '\n':
 | |
| 				plinno++;
 | |
| 				needprompt = doprompt;
 | |
| 				break;
 | |
| 
 | |
| 			default:
 | |
| 				break;
 | |
| 			}
 | |
| 			STPUTC(pc, pout);
 | |
| 		}
 | |
| done:
 | |
| 		STPUTC('\0', pout);
 | |
| 		psavelen = pout - stackblock();
 | |
| 		if (psavelen > 0) {
 | |
| 			pstr = grabstackstr(pout);
 | |
| 			setinputstring(pstr);
 | |
| 		}
 | |
| 	}
 | |
| 	nlpp = &bqlist;
 | |
| 	while (*nlpp)
 | |
| 		nlpp = &(*nlpp)->next;
 | |
| 	*nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
 | |
| 	(*nlpp)->next = NULL;
 | |
| 	parsebackquote = oldstyle;
 | |
| 
 | |
| 	if (oldstyle) {
 | |
| 		saveprompt = doprompt;
 | |
| 		doprompt = 0;
 | |
| 	}
 | |
| 
 | |
| 	n = list(0);
 | |
| 
 | |
| 	if (oldstyle)
 | |
| 		doprompt = saveprompt;
 | |
| 	else {
 | |
| 		if (readtoken() != TRP)
 | |
| 			synexpect(TRP);
 | |
| 	}
 | |
| 
 | |
| 	(*nlpp)->n = n;
 | |
| 	if (oldstyle) {
 | |
| 		/*
 | |
| 		 * Start reading from old file again, ignoring any pushed back
 | |
| 		 * tokens left from the backquote parsing
 | |
| 		 */
 | |
| 		popfile();
 | |
| 		tokpushback = 0;
 | |
| 	}
 | |
| 	while (stackblocksize() <= savelen)
 | |
| 		growstackblock();
 | |
| 	STARTSTACKSTR(out);
 | |
| 	if (str) {
 | |
| 		memcpy(out, str, savelen);
 | |
| 		STADJUST(savelen, out);
 | |
| 		INTOFF;
 | |
| 		ckfree(str);
 | |
| 		str = NULL;
 | |
| 		INTON;
 | |
| 	}
 | |
| 	parsebackquote = savepbq;
 | |
| 	handler = savehandler;
 | |
| 	if (arinest || dblquote)
 | |
| 		USTPUTC(CTLBACKQ | CTLQUOTE, out);
 | |
| 	else
 | |
| 		USTPUTC(CTLBACKQ, out);
 | |
| 	if (oldstyle)
 | |
| 		goto parsebackq_oldreturn;
 | |
| 	else
 | |
| 		goto parsebackq_newreturn;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Parse an arithmetic expansion (indicate start of one and set state)
 | |
|  */
 | |
| parsearith: {
 | |
| 
 | |
| 	if (++arinest == 1) {
 | |
| 		prevsyntax = syntax;
 | |
| 		syntax = ARISYNTAX;
 | |
| 		USTPUTC(CTLARI, out);
 | |
| 		if (dblquote)
 | |
| 			USTPUTC('"',out);
 | |
| 		else
 | |
| 			USTPUTC(' ',out);
 | |
| 	} else {
 | |
| 		/*
 | |
| 		 * we collapse embedded arithmetic expansion to
 | |
| 		 * parenthesis, which should be equivalent
 | |
| 		 */
 | |
| 		USTPUTC('(', out);
 | |
| 	}
 | |
| 	goto parsearith_return;
 | |
| }
 | |
| 
 | |
| } /* end of readtoken */
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns true if the text contains nothing to expand (no dollar signs
 | |
|  * or backquotes).
 | |
|  */
 | |
| 
 | |
| static int
 | |
| noexpand(text)
 | |
| 	char *text;
 | |
| 	{
 | |
| 	char *p;
 | |
| 	char c;
 | |
| 
 | |
| 	p = text;
 | |
| 	while ((c = *p++) != '\0') {
 | |
| 		if (c == CTLQUOTEMARK)
 | |
| 			continue;
 | |
| 		if (c == CTLESC)
 | |
| 			p++;
 | |
| 		else if (SIT(c,BASESYNTAX) == CCTL)
 | |
| 			return 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Return true if the argument is a legal variable name (a letter or
 | |
|  * underscore followed by zero or more letters, underscores, and digits).
 | |
|  */
 | |
| 
 | |
| static int
 | |
| goodname(const char *name)
 | |
| {
 | |
| 	const char *p;
 | |
| 
 | |
| 	p = name;
 | |
| 	if (! is_name(*p))
 | |
| 		return 0;
 | |
| 	while (*++p) {
 | |
| 		if (! is_in_name(*p))
 | |
| 			return 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called when an unexpected token is read during the parse.  The argument
 | |
|  * is the token that is expected, or -1 if more than one type of token can
 | |
|  * occur at this point.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| synexpect(token)
 | |
| 	int token;
 | |
| {
 | |
| 	char msg[64];
 | |
| 	int l;
 | |
| 
 | |
| 	l = sprintf(msg, "%s unexpected", tokname(lasttoken));
 | |
| 	if (token >= 0)
 | |
| 		sprintf(msg+l, " (expecting %s)", tokname(token));
 | |
| 	synerror(msg);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| synerror(const char *msg)
 | |
| {
 | |
| 	if (commandname)
 | |
| 		out2fmt("%s: %d: ", commandname, startlinno);
 | |
| 	out2fmt("Syntax error: %s\n", msg);
 | |
| 	error((char *)NULL);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * called by editline -- any expansions to the prompt
 | |
|  *    should be added here.
 | |
|  */
 | |
| static void
 | |
| setprompt(int whichprompt)
 | |
| {
 | |
|     char *prompt;
 | |
|     switch (whichprompt) {
 | |
| 	case 1:
 | |
| 		prompt = ps1val();
 | |
| 		break;
 | |
| 	case 2:
 | |
| 		prompt = ps2val();
 | |
| 		break;
 | |
| 	default:                /* 0 */
 | |
| 		prompt = "";
 | |
|     }
 | |
|     putprompt(prompt);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Code for dealing with input/output redirection.
 | |
|  */
 | |
| 
 | |
| #define EMPTY -2                /* marks an unused slot in redirtab */
 | |
| #ifndef PIPE_BUF
 | |
| # define PIPESIZE 4096          /* amount of buffering in a pipe */
 | |
| #else
 | |
| # define PIPESIZE PIPE_BUF
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Open a file in noclobber mode.
 | |
|  * The code was copied from bash.
 | |
|  */
 | |
| static inline int
 | |
| noclobberopen(const char *fname)
 | |
| {
 | |
| 	int r, fd;
 | |
| 	struct stat finfo, finfo2;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the file exists and is a regular file, return an error
 | |
| 	 * immediately.
 | |
| 	 */
 | |
| 	r = stat(fname, &finfo);
 | |
| 	if (r == 0 && S_ISREG(finfo.st_mode)) {
 | |
| 		errno = EEXIST;
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * If the file was not present (r != 0), make sure we open it
 | |
| 	 * exclusively so that if it is created before we open it, our open
 | |
| 	 * will fail.  Make sure that we do not truncate an existing file.
 | |
| 	 * Note that we don't turn on O_EXCL unless the stat failed -- if the
 | |
| 	 * file was not a regular file, we leave O_EXCL off.
 | |
| 	 */
 | |
| 	if (r != 0)
 | |
| 		return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
 | |
| 	fd = open(fname, O_WRONLY|O_CREAT, 0666);
 | |
| 
 | |
| 	/* If the open failed, return the file descriptor right away. */
 | |
| 	if (fd < 0)
 | |
| 		return fd;
 | |
| 
 | |
| 	/*
 | |
| 	 * OK, the open succeeded, but the file may have been changed from a
 | |
| 	 * non-regular file to a regular file between the stat and the open.
 | |
| 	 * We are assuming that the O_EXCL open handles the case where FILENAME
 | |
| 	 * did not exist and is symlinked to an existing file between the stat
 | |
| 	 * and open.
 | |
| 	 */
 | |
| 
 | |
| 	/*
 | |
| 	 * If we can open it and fstat the file descriptor, and neither check
 | |
| 	 * revealed that it was a regular file, and the file has not been
 | |
| 	 * replaced, return the file descriptor.
 | |
| 	 */
 | |
| 	 if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) &&
 | |
| 	     finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino)
 | |
| 		return fd;
 | |
| 
 | |
| 	/* The file has been replaced.  badness. */
 | |
| 	close(fd);
 | |
| 	errno = EEXIST;
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Handle here documents.  Normally we fork off a process to write the
 | |
|  * data to a pipe.  If the document is short, we can stuff the data in
 | |
|  * the pipe without forking.
 | |
|  */
 | |
| 
 | |
| static inline int
 | |
| openhere(const union node *redir)
 | |
| {
 | |
| 	int pip[2];
 | |
| 	int len = 0;
 | |
| 
 | |
| 	if (pipe(pip) < 0)
 | |
| 		error("Pipe call failed");
 | |
| 	if (redir->type == NHERE) {
 | |
| 		len = strlen(redir->nhere.doc->narg.text);
 | |
| 		if (len <= PIPESIZE) {
 | |
| 			xwrite(pip[1], redir->nhere.doc->narg.text, len);
 | |
| 			goto out;
 | |
| 		}
 | |
| 	}
 | |
| 	if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
 | |
| 		close(pip[0]);
 | |
| 		signal(SIGINT, SIG_IGN);
 | |
| 		signal(SIGQUIT, SIG_IGN);
 | |
| 		signal(SIGHUP, SIG_IGN);
 | |
| #ifdef SIGTSTP
 | |
| 		signal(SIGTSTP, SIG_IGN);
 | |
| #endif
 | |
| 		signal(SIGPIPE, SIG_DFL);
 | |
| 		if (redir->type == NHERE)
 | |
| 			xwrite(pip[1], redir->nhere.doc->narg.text, len);
 | |
| 		else
 | |
| 			expandhere(redir->nhere.doc, pip[1]);
 | |
| 		_exit(0);
 | |
| 	}
 | |
| out:
 | |
| 	close(pip[1]);
 | |
| 	return pip[0];
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline int
 | |
| openredirect(const union node *redir)
 | |
| {
 | |
| 	char *fname;
 | |
| 	int f;
 | |
| 
 | |
| 	switch (redir->nfile.type) {
 | |
| 	case NFROM:
 | |
| 		fname = redir->nfile.expfname;
 | |
| 		if ((f = open(fname, O_RDONLY)) < 0)
 | |
| 			goto eopen;
 | |
| 		break;
 | |
| 	case NFROMTO:
 | |
| 		fname = redir->nfile.expfname;
 | |
| 		if ((f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0)
 | |
| 			goto ecreate;
 | |
| 		break;
 | |
| 	case NTO:
 | |
| 		/* Take care of noclobber mode. */
 | |
| 		if (Cflag) {
 | |
| 			fname = redir->nfile.expfname;
 | |
| 			if ((f = noclobberopen(fname)) < 0)
 | |
| 				goto ecreate;
 | |
| 			break;
 | |
| 		}
 | |
| 	case NTOOV:
 | |
| 		fname = redir->nfile.expfname;
 | |
| #ifdef O_CREAT
 | |
| 		if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
 | |
| 			goto ecreate;
 | |
| #else
 | |
| 		if ((f = creat(fname, 0666)) < 0)
 | |
| 			goto ecreate;
 | |
| #endif
 | |
| 		break;
 | |
| 	case NAPPEND:
 | |
| 		fname = redir->nfile.expfname;
 | |
| #ifdef O_APPEND
 | |
| 		if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
 | |
| 			goto ecreate;
 | |
| #else
 | |
| 		if ((f = open(fname, O_WRONLY)) < 0
 | |
| 		 && (f = creat(fname, 0666)) < 0)
 | |
| 			goto ecreate;
 | |
| 		lseek(f, (off_t)0, 2);
 | |
| #endif
 | |
| 		break;
 | |
| 	default:
 | |
| #ifdef DEBUG
 | |
| 		abort();
 | |
| #endif
 | |
| 		/* Fall through to eliminate warning. */
 | |
| 	case NTOFD:
 | |
| 	case NFROMFD:
 | |
| 		f = -1;
 | |
| 		break;
 | |
| 	case NHERE:
 | |
| 	case NXHERE:
 | |
| 		f = openhere(redir);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return f;
 | |
| ecreate:
 | |
| 	error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
 | |
| eopen:
 | |
| 	error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
 | |
|  * old file descriptors are stashed away so that the redirection can be
 | |
|  * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
 | |
|  * standard output, and the standard error if it becomes a duplicate of
 | |
|  * stdout.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| redirect(union node *redir, int flags)
 | |
| {
 | |
| 	union node *n;
 | |
| 	struct redirtab *sv = NULL;
 | |
| 	int i;
 | |
| 	int fd;
 | |
| 	int newfd;
 | |
| 	int try;
 | |
| 	int fd1dup = flags & REDIR_BACKQ;; /* stdout `cmd` redir to pipe */
 | |
| 
 | |
| 	if (flags & REDIR_PUSH) {
 | |
| 		sv = ckmalloc(sizeof (struct redirtab));
 | |
| 		for (i = 0 ; i < 10 ; i++)
 | |
| 			sv->renamed[i] = EMPTY;
 | |
| 		sv->next = redirlist;
 | |
| 		redirlist = sv;
 | |
| 	}
 | |
| 	for (n = redir ; n ; n = n->nfile.next) {
 | |
| 		fd = n->nfile.fd;
 | |
| 		try = 0;
 | |
| 		if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
 | |
| 		    n->ndup.dupfd == fd)
 | |
| 			continue; /* redirect from/to same file descriptor */
 | |
| 
 | |
| 		INTOFF;
 | |
| 		newfd = openredirect(n);
 | |
| 		if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) {
 | |
| 			if (newfd == fd) {
 | |
| 				try++;
 | |
| 			} else if ((i = fcntl(fd, F_DUPFD, 10)) == -1) {
 | |
| 				switch (errno) {
 | |
| 				case EBADF:
 | |
| 					if (!try) {
 | |
| 						dupredirect(n, newfd, fd1dup);
 | |
| 						try++;
 | |
| 						break;
 | |
| 					}
 | |
| 					/* FALLTHROUGH*/
 | |
| 				default:
 | |
| 					if (newfd >= 0) {
 | |
| 						close(newfd);
 | |
| 					}
 | |
| 					INTON;
 | |
| 					error("%d: %m", fd);
 | |
| 					/* NOTREACHED */
 | |
| 				}
 | |
| 			}
 | |
| 			if (!try) {
 | |
| 				close(fd);
 | |
| 				if (flags & REDIR_PUSH) {
 | |
| 					sv->renamed[fd] = i;
 | |
| 				}
 | |
| 			}
 | |
| 		} else if (fd != newfd) {
 | |
| 			close(fd);
 | |
| 		}
 | |
| 		if (fd == 0)
 | |
| 			fd0_redirected++;
 | |
| 		if (!try)
 | |
| 			dupredirect(n, newfd, fd1dup);
 | |
| 		INTON;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| dupredirect(const union node *redir, int f, int fd1dup)
 | |
| {
 | |
| 	int fd = redir->nfile.fd;
 | |
| 
 | |
| 	if(fd==1)
 | |
| 		fd1dup = 0;
 | |
| 	if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
 | |
| 		if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
 | |
| 			if (redir->ndup.dupfd!=1 || fd1dup!=1)
 | |
| 				dup_as_newfd(redir->ndup.dupfd, fd);
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (f != fd) {
 | |
| 		dup_as_newfd(f, fd);
 | |
| 		close(f);
 | |
| 	}
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Undo the effects of the last redirection.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| popredir(void)
 | |
| {
 | |
| 	struct redirtab *rp = redirlist;
 | |
| 	int i;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	for (i = 0 ; i < 10 ; i++) {
 | |
| 		if (rp->renamed[i] != EMPTY) {
 | |
| 			if (i == 0)
 | |
| 				fd0_redirected--;
 | |
| 			close(i);
 | |
| 			if (rp->renamed[i] >= 0) {
 | |
| 				dup_as_newfd(rp->renamed[i], i);
 | |
| 				close(rp->renamed[i]);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	redirlist = rp->next;
 | |
| 	ckfree(rp);
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Discard all saved file descriptors.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| clearredir(void) {
 | |
| 	struct redirtab *rp;
 | |
| 	int i;
 | |
| 
 | |
| 	for (rp = redirlist ; rp ; rp = rp->next) {
 | |
| 		for (i = 0 ; i < 10 ; i++) {
 | |
| 			if (rp->renamed[i] >= 0) {
 | |
| 				close(rp->renamed[i]);
 | |
| 			}
 | |
| 			rp->renamed[i] = EMPTY;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Copy a file descriptor to be >= to.  Returns -1
 | |
|  * if the source file descriptor is closed, EMPTY if there are no unused
 | |
|  * file descriptors left.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| dup_as_newfd(from, to)
 | |
| 	int from;
 | |
| 	int to;
 | |
| {
 | |
| 	int newfd;
 | |
| 
 | |
| 	newfd = fcntl(from, F_DUPFD, to);
 | |
| 	if (newfd < 0) {
 | |
| 		if (errno == EMFILE)
 | |
| 			return EMPTY;
 | |
| 		else
 | |
| 			error("%d: %m", from);
 | |
| 	}
 | |
| 	return newfd;
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| /*
 | |
|  * Debugging stuff.
 | |
|  */
 | |
| static void shtree (union node *, int, char *, FILE*);
 | |
| static void shcmd (union node *, FILE *);
 | |
| static void sharg (union node *, FILE *);
 | |
| static void indent (int, char *, FILE *);
 | |
| static void trstring (char *);
 | |
| 
 | |
| 
 | |
| static void
 | |
| showtree(n)
 | |
| 	unode *n;
 | |
| {
 | |
| 	trputs("showtree called\n");
 | |
| 	shtree(n, 1, NULL, stdout);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| shtree(n, ind, pfx, fp)
 | |
| 	union node *n;
 | |
| 	int ind;
 | |
| 	char *pfx;
 | |
| 	FILE *fp;
 | |
| {
 | |
| 	struct nodelist *lp;
 | |
| 	const char *s;
 | |
| 
 | |
| 	if (n == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	indent(ind, pfx, fp);
 | |
| 	switch(n->type) {
 | |
| 	case NSEMI:
 | |
| 		s = "; ";
 | |
| 		goto binop;
 | |
| 	case NAND:
 | |
| 		s = " && ";
 | |
| 		goto binop;
 | |
| 	case NOR:
 | |
| 		s = " || ";
 | |
| binop:
 | |
| 		shtree(n->nbinary.ch1, ind, NULL, fp);
 | |
| 	   /*    if (ind < 0) */
 | |
| 			fputs(s, fp);
 | |
| 		shtree(n->nbinary.ch2, ind, NULL, fp);
 | |
| 		break;
 | |
| 	case NCMD:
 | |
| 		shcmd(n, fp);
 | |
| 		if (ind >= 0)
 | |
| 			putc('\n', fp);
 | |
| 		break;
 | |
| 	case NPIPE:
 | |
| 		for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
 | |
| 			shcmd(lp->n, fp);
 | |
| 			if (lp->next)
 | |
| 				fputs(" | ", fp);
 | |
| 		}
 | |
| 		if (n->npipe.backgnd)
 | |
| 			fputs(" &", fp);
 | |
| 		if (ind >= 0)
 | |
| 			putc('\n', fp);
 | |
| 		break;
 | |
| 	default:
 | |
| 		fprintf(fp, "<node type %d>", n->type);
 | |
| 		if (ind >= 0)
 | |
| 			putc('\n', fp);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static void
 | |
| shcmd(cmd, fp)
 | |
| 	union node *cmd;
 | |
| 	FILE *fp;
 | |
| {
 | |
| 	union node *np;
 | |
| 	int first;
 | |
| 	const char *s;
 | |
| 	int dftfd;
 | |
| 
 | |
| 	first = 1;
 | |
| 	for (np = cmd->ncmd.args ; np ; np = np->narg.next) {
 | |
| 		if (! first)
 | |
| 			putchar(' ');
 | |
| 		sharg(np, fp);
 | |
| 		first = 0;
 | |
| 	}
 | |
| 	for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) {
 | |
| 		if (! first)
 | |
| 			putchar(' ');
 | |
| #if 1
 | |
| 		s = "*error*";
 | |
| 		dftfd = 0;
 | |
| 		if ((np->nfile.type <= NFROMFD) && (np->nfile.type >= NTO)) {
 | |
| 			s = redir_strings[np->nfile.type - NTO];
 | |
| 			if (*s == '>') {
 | |
| 				dftfd = 1;
 | |
| 			}
 | |
| 		}
 | |
| #else
 | |
| 		switch (np->nfile.type) {
 | |
| 			case NTO:       s = ">";  dftfd = 1; break;
 | |
| 			case NAPPEND:   s = ">>"; dftfd = 1; break;
 | |
| 			case NTOFD:     s = ">&"; dftfd = 1; break;
 | |
| 			case NTOOV:     s = ">|"; dftfd = 1; break;
 | |
| 			case NFROM:     s = "<";  dftfd = 0; break;
 | |
| 			case NFROMFD:   s = "<&"; dftfd = 0; break;
 | |
| 			case NFROMTO:   s = "<>"; dftfd = 0; break;
 | |
| 			default:        s = "*error*"; dftfd = 0; break;
 | |
| 		}
 | |
| #endif
 | |
| 		if (np->nfile.fd != dftfd)
 | |
| 			fprintf(fp, "%d", np->nfile.fd);
 | |
| 		fputs(s, fp);
 | |
| 		if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
 | |
| 			fprintf(fp, "%d", np->ndup.dupfd);
 | |
| 		} else {
 | |
| 			sharg(np->nfile.fname, fp);
 | |
| 		}
 | |
| 		first = 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void
 | |
| sharg(arg, fp)
 | |
| 	union node *arg;
 | |
| 	FILE *fp;
 | |
| 	{
 | |
| 	char *p;
 | |
| 	struct nodelist *bqlist;
 | |
| 	int subtype;
 | |
| 
 | |
| 	if (arg->type != NARG) {
 | |
| 		printf("<node type %d>\n", arg->type);
 | |
| 		fflush(stdout);
 | |
| 		abort();
 | |
| 	}
 | |
| 	bqlist = arg->narg.backquote;
 | |
| 	for (p = arg->narg.text ; *p ; p++) {
 | |
| 		switch (*p) {
 | |
| 		case CTLESC:
 | |
| 			putc(*++p, fp);
 | |
| 			break;
 | |
| 		case CTLVAR:
 | |
| 			putc('$', fp);
 | |
| 			putc('{', fp);
 | |
| 			subtype = *++p;
 | |
| 			if (subtype == VSLENGTH)
 | |
| 				putc('#', fp);
 | |
| 
 | |
| 			while (*p != '=')
 | |
| 				putc(*p++, fp);
 | |
| 
 | |
| 			if (subtype & VSNUL)
 | |
| 				putc(':', fp);
 | |
| 
 | |
| 			switch (subtype & VSTYPE) {
 | |
| 			case VSNORMAL:
 | |
| 				putc('}', fp);
 | |
| 				break;
 | |
| 			case VSMINUS:
 | |
| 				putc('-', fp);
 | |
| 				break;
 | |
| 			case VSPLUS:
 | |
| 				putc('+', fp);
 | |
| 				break;
 | |
| 			case VSQUESTION:
 | |
| 				putc('?', fp);
 | |
| 				break;
 | |
| 			case VSASSIGN:
 | |
| 				putc('=', fp);
 | |
| 				break;
 | |
| 			case VSTRIMLEFT:
 | |
| 				putc('#', fp);
 | |
| 				break;
 | |
| 			case VSTRIMLEFTMAX:
 | |
| 				putc('#', fp);
 | |
| 				putc('#', fp);
 | |
| 				break;
 | |
| 			case VSTRIMRIGHT:
 | |
| 				putc('%', fp);
 | |
| 				break;
 | |
| 			case VSTRIMRIGHTMAX:
 | |
| 				putc('%', fp);
 | |
| 				putc('%', fp);
 | |
| 				break;
 | |
| 			case VSLENGTH:
 | |
| 				break;
 | |
| 			default:
 | |
| 				printf("<subtype %d>", subtype);
 | |
| 			}
 | |
| 			break;
 | |
| 		case CTLENDVAR:
 | |
| 		     putc('}', fp);
 | |
| 		     break;
 | |
| 		case CTLBACKQ:
 | |
| 		case CTLBACKQ|CTLQUOTE:
 | |
| 			putc('$', fp);
 | |
| 			putc('(', fp);
 | |
| 			shtree(bqlist->n, -1, NULL, fp);
 | |
| 			putc(')', fp);
 | |
| 			break;
 | |
| 		default:
 | |
| 			putc(*p, fp);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| indent(amount, pfx, fp)
 | |
| 	int amount;
 | |
| 	char *pfx;
 | |
| 	FILE *fp;
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0 ; i < amount ; i++) {
 | |
| 		if (pfx && i == amount - 1)
 | |
| 			fputs(pfx, fp);
 | |
| 		putc('\t', fp);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| FILE *tracefile;
 | |
| 
 | |
| #if DEBUG == 2
 | |
| static int debug = 1;
 | |
| #else
 | |
| static int debug = 0;
 | |
| #endif
 | |
| 
 | |
| 
 | |
| static void
 | |
| trputc(c)
 | |
| 	int c;
 | |
| {
 | |
| 	if (tracefile == NULL)
 | |
| 		return;
 | |
| 	putc(c, tracefile);
 | |
| 	if (c == '\n')
 | |
| 		fflush(tracefile);
 | |
| }
 | |
| 
 | |
| static void
 | |
| trace(const char *fmt, ...)
 | |
| {
 | |
| 	va_list va;
 | |
| 	va_start(va, fmt);
 | |
| 	if (tracefile != NULL) {
 | |
| 		(void) vfprintf(tracefile, fmt, va);
 | |
| 		if (strchr(fmt, '\n'))
 | |
| 			(void) fflush(tracefile);
 | |
| 	}
 | |
| 	va_end(va);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| trputs(s)
 | |
| 	const char *s;
 | |
| {
 | |
| 	if (tracefile == NULL)
 | |
| 		return;
 | |
| 	fputs(s, tracefile);
 | |
| 	if (strchr(s, '\n'))
 | |
| 		fflush(tracefile);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| trstring(s)
 | |
| 	char *s;
 | |
| {
 | |
| 	char *p;
 | |
| 	char c;
 | |
| 
 | |
| 	if (tracefile == NULL)
 | |
| 		return;
 | |
| 	putc('"', tracefile);
 | |
| 	for (p = s ; *p ; p++) {
 | |
| 		switch (*p) {
 | |
| 		case '\n':  c = 'n';  goto backslash;
 | |
| 		case '\t':  c = 't';  goto backslash;
 | |
| 		case '\r':  c = 'r';  goto backslash;
 | |
| 		case '"':  c = '"';  goto backslash;
 | |
| 		case '\\':  c = '\\';  goto backslash;
 | |
| 		case CTLESC:  c = 'e';  goto backslash;
 | |
| 		case CTLVAR:  c = 'v';  goto backslash;
 | |
| 		case CTLVAR+CTLQUOTE:  c = 'V';  goto backslash;
 | |
| 		case CTLBACKQ:  c = 'q';  goto backslash;
 | |
| 		case CTLBACKQ+CTLQUOTE:  c = 'Q';  goto backslash;
 | |
| backslash:        putc('\\', tracefile);
 | |
| 			putc(c, tracefile);
 | |
| 			break;
 | |
| 		default:
 | |
| 			if (*p >= ' ' && *p <= '~')
 | |
| 				putc(*p, tracefile);
 | |
| 			else {
 | |
| 				putc('\\', tracefile);
 | |
| 				putc(*p >> 6 & 03, tracefile);
 | |
| 				putc(*p >> 3 & 07, tracefile);
 | |
| 				putc(*p & 07, tracefile);
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	putc('"', tracefile);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| trargs(ap)
 | |
| 	char **ap;
 | |
| {
 | |
| 	if (tracefile == NULL)
 | |
| 		return;
 | |
| 	while (*ap) {
 | |
| 		trstring(*ap++);
 | |
| 		if (*ap)
 | |
| 			putc(' ', tracefile);
 | |
| 		else
 | |
| 			putc('\n', tracefile);
 | |
| 	}
 | |
| 	fflush(tracefile);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| opentrace() {
 | |
| 	char s[100];
 | |
| #ifdef O_APPEND
 | |
| 	int flags;
 | |
| #endif
 | |
| 
 | |
| 	if (!debug)
 | |
| 		return;
 | |
| #ifdef not_this_way
 | |
| 	{
 | |
| 		char *p;
 | |
| 		if ((p = getenv("HOME")) == NULL) {
 | |
| 			if (geteuid() == 0)
 | |
| 				p = "/";
 | |
| 			else
 | |
| 				p = "/tmp";
 | |
| 		}
 | |
| 		strcpy(s, p);
 | |
| 		strcat(s, "/trace");
 | |
| 	}
 | |
| #else
 | |
| 	strcpy(s, "./trace");
 | |
| #endif /* not_this_way */
 | |
| 	if ((tracefile = wfopen(s, "a")) == NULL)
 | |
| 		return;
 | |
| #ifdef O_APPEND
 | |
| 	if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0)
 | |
| 		fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
 | |
| #endif
 | |
| 	fputs("\nTracing started.\n", tracefile);
 | |
| 	fflush(tracefile);
 | |
| }
 | |
| #endif /* DEBUG */
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The trap builtin.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| trapcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	char *action;
 | |
| 	char **ap;
 | |
| 	int signo;
 | |
| 
 | |
| 	if (argc <= 1) {
 | |
| 		for (signo = 0 ; signo < NSIG ; signo++) {
 | |
| 			if (trap[signo] != NULL) {
 | |
| 				char *p;
 | |
| 				const char *sn;
 | |
| 
 | |
| 				p = single_quote(trap[signo]);
 | |
| 				sn = sys_siglist[signo];
 | |
| 				if(sn==NULL)
 | |
| 					sn = u_signal_names(0, &signo, 0);
 | |
| 				if(sn==NULL)
 | |
| 					sn = "???";
 | |
| 				printf("trap -- %s %s\n", p, sn);
 | |
| 				stunalloc(p);
 | |
| 			}
 | |
| 		}
 | |
| 		return 0;
 | |
| 	}
 | |
| 	ap = argv + 1;
 | |
| 	if (argc == 2)
 | |
| 		action = NULL;
 | |
| 	else
 | |
| 		action = *ap++;
 | |
| 	while (*ap) {
 | |
| 		if ((signo = decode_signal(*ap, 0)) < 0)
 | |
| 			error("%s: bad trap", *ap);
 | |
| 		INTOFF;
 | |
| 		if (action) {
 | |
| 			if (action[0] == '-' && action[1] == '\0')
 | |
| 				action = NULL;
 | |
| 			else
 | |
| 				action = savestr(action);
 | |
| 		}
 | |
| 		if (trap[signo])
 | |
| 			ckfree(trap[signo]);
 | |
| 		trap[signo] = action;
 | |
| 		if (signo != 0)
 | |
| 			setsignal(signo);
 | |
| 		INTON;
 | |
| 		ap++;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Set the signal handler for the specified signal.  The routine figures
 | |
|  * out what it should be set to.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| setsignal(int signo)
 | |
| {
 | |
| 	int action;
 | |
| 	char *t;
 | |
| 	struct sigaction act;
 | |
| 
 | |
| 	if ((t = trap[signo]) == NULL)
 | |
| 		action = S_DFL;
 | |
| 	else if (*t != '\0')
 | |
| 		action = S_CATCH;
 | |
| 	else
 | |
| 		action = S_IGN;
 | |
| 	if (rootshell && action == S_DFL) {
 | |
| 		switch (signo) {
 | |
| 		case SIGINT:
 | |
| 			if (iflag || minusc || sflag == 0)
 | |
| 				action = S_CATCH;
 | |
| 			break;
 | |
| 		case SIGQUIT:
 | |
| #ifdef DEBUG
 | |
| 			{
 | |
| 
 | |
| 			if (debug)
 | |
| 				break;
 | |
| 			}
 | |
| #endif
 | |
| 			/* FALLTHROUGH */
 | |
| 		case SIGTERM:
 | |
| 			if (iflag)
 | |
| 				action = S_IGN;
 | |
| 			break;
 | |
| #ifdef JOBS
 | |
| 		case SIGTSTP:
 | |
| 		case SIGTTOU:
 | |
| 			if (mflag)
 | |
| 				action = S_IGN;
 | |
| 			break;
 | |
| #endif
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	t = &sigmode[signo - 1];
 | |
| 	if (*t == 0) {
 | |
| 		/*
 | |
| 		 * current setting unknown
 | |
| 		 */
 | |
| 		if (sigaction(signo, 0, &act) == -1) {
 | |
| 			/*
 | |
| 			 * Pretend it worked; maybe we should give a warning
 | |
| 			 * here, but other shells don't. We don't alter
 | |
| 			 * sigmode, so that we retry every time.
 | |
| 			 */
 | |
| 			return;
 | |
| 		}
 | |
| 		if (act.sa_handler == SIG_IGN) {
 | |
| 			if (mflag && (signo == SIGTSTP ||
 | |
| 			     signo == SIGTTIN || signo == SIGTTOU)) {
 | |
| 				*t = S_IGN;     /* don't hard ignore these */
 | |
| 			} else
 | |
| 				*t = S_HARD_IGN;
 | |
| 		} else {
 | |
| 			*t = S_RESET;   /* force to be set */
 | |
| 		}
 | |
| 	}
 | |
| 	if (*t == S_HARD_IGN || *t == action)
 | |
| 		return;
 | |
| 	act.sa_handler = ((action == S_CATCH) ? onsig
 | |
| 					  : ((action == S_IGN) ? SIG_IGN : SIG_DFL));
 | |
| 	*t = action;
 | |
| 	act.sa_flags = 0;
 | |
| 	sigemptyset(&act.sa_mask);
 | |
| 	sigaction(signo, &act, 0);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Ignore a signal.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| ignoresig(signo)
 | |
| 	int signo;
 | |
| {
 | |
| 	if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
 | |
| 		signal(signo, SIG_IGN);
 | |
| 	}
 | |
| 	sigmode[signo - 1] = S_HARD_IGN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Signal handler.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| onsig(int signo)
 | |
| {
 | |
| 	if (signo == SIGINT && trap[SIGINT] == NULL) {
 | |
| 		onint();
 | |
| 		return;
 | |
| 	}
 | |
| 	gotsig[signo - 1] = 1;
 | |
| 	pendingsigs++;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called to execute a trap.  Perhaps we should avoid entering new trap
 | |
|  * handlers while we are executing a trap handler.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| dotrap(void)
 | |
| {
 | |
| 	int i;
 | |
| 	int savestatus;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		for (i = 1 ; ; i++) {
 | |
| 			if (gotsig[i - 1])
 | |
| 				break;
 | |
| 			if (i >= NSIG - 1)
 | |
| 				goto done;
 | |
| 		}
 | |
| 		gotsig[i - 1] = 0;
 | |
| 		savestatus=exitstatus;
 | |
| 		evalstring(trap[i], 0);
 | |
| 		exitstatus=savestatus;
 | |
| 	}
 | |
| done:
 | |
| 	pendingsigs = 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Called to exit the shell.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| exitshell(int status)
 | |
| {
 | |
| 	struct jmploc loc1, loc2;
 | |
| 	char *p;
 | |
| 
 | |
| 	TRACE(("exitshell(%d) pid=%d\n", status, getpid()));
 | |
| 	if (setjmp(loc1.loc)) {
 | |
| 		goto l1;
 | |
| 	}
 | |
| 	if (setjmp(loc2.loc)) {
 | |
| 		goto l2;
 | |
| 	}
 | |
| 	handler = &loc1;
 | |
| 	if ((p = trap[0]) != NULL && *p != '\0') {
 | |
| 		trap[0] = NULL;
 | |
| 		evalstring(p, 0);
 | |
| 	}
 | |
| l1:   handler = &loc2;                  /* probably unnecessary */
 | |
| 	flushall();
 | |
| #ifdef JOBS
 | |
| 	setjobctl(0);
 | |
| #endif
 | |
| l2:   _exit(status);
 | |
| 	/* NOTREACHED */
 | |
| }
 | |
| 
 | |
| static int decode_signal(const char *string, int minsig)
 | |
| {
 | |
| 	int signo;
 | |
| 	const char *name = u_signal_names(string, &signo, minsig);
 | |
| 
 | |
| 	return name ? signo : -1;
 | |
| }
 | |
| 
 | |
| static struct var **hashvar (const char *);
 | |
| static void showvars (const char *, int, int);
 | |
| static struct var **findvar (struct var **, const char *);
 | |
| 
 | |
| /*
 | |
|  * Initialize the varable symbol tables and import the environment
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * This routine initializes the builtin variables.  It is called when the
 | |
|  * shell is initialized and again when a shell procedure is spawned.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| initvar() {
 | |
| 	const struct varinit *ip;
 | |
| 	struct var *vp;
 | |
| 	struct var **vpp;
 | |
| 
 | |
| 	for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
 | |
| 		if ((vp->flags & VEXPORT) == 0) {
 | |
| 			vpp = hashvar(ip->text);
 | |
| 			vp->next = *vpp;
 | |
| 			*vpp = vp;
 | |
| 			vp->text = xstrdup(ip->text);
 | |
| 			vp->flags = ip->flags;
 | |
| 			vp->func = ip->func;
 | |
| 		}
 | |
| 	}
 | |
| 	/*
 | |
| 	 * PS1 depends on uid
 | |
| 	 */
 | |
| 	if ((vps1.flags & VEXPORT) == 0) {
 | |
| 		vpp = hashvar("PS1=");
 | |
| 		vps1.next = *vpp;
 | |
| 		*vpp = &vps1;
 | |
| 		vps1.text = xstrdup(geteuid() ? "PS1=$ " : "PS1=# ");
 | |
| 		vps1.flags = VSTRFIXED|VTEXTFIXED;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Set the value of a variable.  The flags argument is ored with the
 | |
|  * flags of the variable.  If val is NULL, the variable is unset.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| setvar(name, val, flags)
 | |
| 	const char *name, *val;
 | |
| 	int flags;
 | |
| {
 | |
| 	const char *p;
 | |
| 	int len;
 | |
| 	int namelen;
 | |
| 	char *nameeq;
 | |
| 	int isbad;
 | |
| 	int vallen = 0;
 | |
| 
 | |
| 	isbad = 0;
 | |
| 	p = name;
 | |
| 	if (! is_name(*p))
 | |
| 		isbad = 1;
 | |
| 	p++;
 | |
| 	for (;;) {
 | |
| 		if (! is_in_name(*p)) {
 | |
| 			if (*p == '\0' || *p == '=')
 | |
| 				break;
 | |
| 			isbad = 1;
 | |
| 		}
 | |
| 		p++;
 | |
| 	}
 | |
| 	namelen = p - name;
 | |
| 	if (isbad)
 | |
| 		error("%.*s: bad variable name", namelen, name);
 | |
| 	len = namelen + 2;              /* 2 is space for '=' and '\0' */
 | |
| 	if (val == NULL) {
 | |
| 		flags |= VUNSET;
 | |
| 	} else {
 | |
| 		len += vallen = strlen(val);
 | |
| 	}
 | |
| 	INTOFF;
 | |
| 	nameeq = ckmalloc(len);
 | |
| 	memcpy(nameeq, name, namelen);
 | |
| 	nameeq[namelen] = '=';
 | |
| 	if (val) {
 | |
| 		memcpy(nameeq + namelen + 1, val, vallen + 1);
 | |
| 	} else {
 | |
| 		nameeq[namelen + 1] = '\0';
 | |
| 	}
 | |
| 	setvareq(nameeq, flags);
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Same as setvar except that the variable and value are passed in
 | |
|  * the first argument as name=value.  Since the first argument will
 | |
|  * be actually stored in the table, it should not be a string that
 | |
|  * will go away.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| setvareq(s, flags)
 | |
| 	char *s;
 | |
| 	int flags;
 | |
| {
 | |
| 	struct var *vp, **vpp;
 | |
| 
 | |
| 	vpp = hashvar(s);
 | |
| 	flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
 | |
| 	if ((vp = *findvar(vpp, s))) {
 | |
| 		if (vp->flags & VREADONLY) {
 | |
| 			size_t len = strchr(s, '=') - s;
 | |
| 			error("%.*s: is read only", len, s);
 | |
| 		}
 | |
| 		INTOFF;
 | |
| 
 | |
| 		if (vp->func && (flags & VNOFUNC) == 0)
 | |
| 			(*vp->func)(strchr(s, '=') + 1);
 | |
| 
 | |
| 		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 | |
| 			ckfree(vp->text);
 | |
| 
 | |
| 		vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
 | |
| 		vp->flags |= flags;
 | |
| 		vp->text = s;
 | |
| 
 | |
| #ifdef ASH_MAIL
 | |
| 		/*
 | |
| 		 * We could roll this to a function, to handle it as
 | |
| 		 * a regular variable function callback, but why bother?
 | |
| 		 */
 | |
| 		if (iflag && (vp == &vmpath || (vp == &vmail && !mpathset())))
 | |
| 			chkmail(1);
 | |
| #endif
 | |
| 		INTON;
 | |
| 		return;
 | |
| 	}
 | |
| 	/* not found */
 | |
| 	vp = ckmalloc(sizeof (*vp));
 | |
| 	vp->flags = flags;
 | |
| 	vp->text = s;
 | |
| 	vp->next = *vpp;
 | |
| 	vp->func = NULL;
 | |
| 	*vpp = vp;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Process a linked list of variable assignments.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| listsetvar(mylist)
 | |
| 	struct strlist *mylist;
 | |
| 	{
 | |
| 	struct strlist *lp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	for (lp = mylist ; lp ; lp = lp->next) {
 | |
| 		setvareq(savestr(lp->text), 0);
 | |
| 	}
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Find the value of a variable.  Returns NULL if not set.
 | |
|  */
 | |
| 
 | |
| static const char *
 | |
| lookupvar(name)
 | |
| 	const char *name;
 | |
| {
 | |
| 	struct var *v;
 | |
| 
 | |
| 	if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) {
 | |
| 		return strchr(v->text, '=') + 1;
 | |
| 	}
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Search the environment of a builtin command.
 | |
|  */
 | |
| 
 | |
| static const char *
 | |
| bltinlookup(const char *name)
 | |
| {
 | |
| 	const struct strlist *sp;
 | |
| 
 | |
| 	for (sp = cmdenviron ; sp ; sp = sp->next) {
 | |
| 		if (varequal(sp->text, name))
 | |
| 			return strchr(sp->text, '=') + 1;
 | |
| 	}
 | |
| 	return lookupvar(name);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Generate a list of exported variables.  This routine is used to construct
 | |
|  * the third argument to execve when executing a program.
 | |
|  */
 | |
| 
 | |
| static char **
 | |
| environment() {
 | |
| 	int nenv;
 | |
| 	struct var **vpp;
 | |
| 	struct var *vp;
 | |
| 	char **env;
 | |
| 	char **ep;
 | |
| 
 | |
| 	nenv = 0;
 | |
| 	for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
 | |
| 		for (vp = *vpp ; vp ; vp = vp->next)
 | |
| 			if (vp->flags & VEXPORT)
 | |
| 				nenv++;
 | |
| 	}
 | |
| 	ep = env = stalloc((nenv + 1) * sizeof *env);
 | |
| 	for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
 | |
| 		for (vp = *vpp ; vp ; vp = vp->next)
 | |
| 			if (vp->flags & VEXPORT)
 | |
| 				*ep++ = vp->text;
 | |
| 	}
 | |
| 	*ep = NULL;
 | |
| 	return env;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called when a shell procedure is invoked to clear out nonexported
 | |
|  * variables.  It is also necessary to reallocate variables of with
 | |
|  * VSTACK set since these are currently allocated on the stack.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| shprocvar(void) {
 | |
| 	struct var **vpp;
 | |
| 	struct var *vp, **prev;
 | |
| 
 | |
| 	for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
 | |
| 		for (prev = vpp ; (vp = *prev) != NULL ; ) {
 | |
| 			if ((vp->flags & VEXPORT) == 0) {
 | |
| 				*prev = vp->next;
 | |
| 				if ((vp->flags & VTEXTFIXED) == 0)
 | |
| 					ckfree(vp->text);
 | |
| 				if ((vp->flags & VSTRFIXED) == 0)
 | |
| 					ckfree(vp);
 | |
| 			} else {
 | |
| 				if (vp->flags & VSTACK) {
 | |
| 					vp->text = savestr(vp->text);
 | |
| 					vp->flags &=~ VSTACK;
 | |
| 				}
 | |
| 				prev = &vp->next;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	initvar();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Command to list all variables which are set.  Currently this command
 | |
|  * is invoked from the set command when the set command is called without
 | |
|  * any variables.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| showvarscmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	showvars(nullstr, VUNSET, VUNSET);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The export and readonly commands.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| exportcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	struct var *vp;
 | |
| 	char *name;
 | |
| 	const char *p;
 | |
| 	int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
 | |
| 	int pflag;
 | |
| 
 | |
| 	listsetvar(cmdenviron);
 | |
| 	pflag = (nextopt("p") == 'p');
 | |
| 	if (argc > 1 && !pflag) {
 | |
| 		while ((name = *argptr++) != NULL) {
 | |
| 			if ((p = strchr(name, '=')) != NULL) {
 | |
| 				p++;
 | |
| 			} else {
 | |
| 				if ((vp = *findvar(hashvar(name), name))) {
 | |
| 					vp->flags |= flag;
 | |
| 					goto found;
 | |
| 				}
 | |
| 			}
 | |
| 			setvar(name, p, flag);
 | |
| found:;
 | |
| 		}
 | |
| 	} else {
 | |
| 		showvars(argv[0], flag, 0);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The "local" command.
 | |
|  */
 | |
| 
 | |
| /* funcnest nonzero if we are currently evaluating a function */
 | |
| 
 | |
| static int
 | |
| localcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	char *name;
 | |
| 
 | |
| 	if (! funcnest)
 | |
| 		error("Not in a function");
 | |
| 	while ((name = *argptr++) != NULL) {
 | |
| 		mklocal(name);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Make a variable a local variable.  When a variable is made local, it's
 | |
|  * value and flags are saved in a localvar structure.  The saved values
 | |
|  * will be restored when the shell function returns.  We handle the name
 | |
|  * "-" as a special case.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| mklocal(name)
 | |
| 	char *name;
 | |
| 	{
 | |
| 	struct localvar *lvp;
 | |
| 	struct var **vpp;
 | |
| 	struct var *vp;
 | |
| 
 | |
| 	INTOFF;
 | |
| 	lvp = ckmalloc(sizeof (struct localvar));
 | |
| 	if (name[0] == '-' && name[1] == '\0') {
 | |
| 		char *p;
 | |
| 		p = ckmalloc(sizeof optet_vals);
 | |
| 		lvp->text = memcpy(p, optet_vals, sizeof optet_vals);
 | |
| 		vp = NULL;
 | |
| 	} else {
 | |
| 		vpp = hashvar(name);
 | |
| 		vp = *findvar(vpp, name);
 | |
| 		if (vp == NULL) {
 | |
| 			if (strchr(name, '='))
 | |
| 				setvareq(savestr(name), VSTRFIXED);
 | |
| 			else
 | |
| 				setvar(name, NULL, VSTRFIXED);
 | |
| 			vp = *vpp;      /* the new variable */
 | |
| 			lvp->text = NULL;
 | |
| 			lvp->flags = VUNSET;
 | |
| 		} else {
 | |
| 			lvp->text = vp->text;
 | |
| 			lvp->flags = vp->flags;
 | |
| 			vp->flags |= VSTRFIXED|VTEXTFIXED;
 | |
| 			if (strchr(name, '='))
 | |
| 				setvareq(savestr(name), 0);
 | |
| 		}
 | |
| 	}
 | |
| 	lvp->vp = vp;
 | |
| 	lvp->next = localvars;
 | |
| 	localvars = lvp;
 | |
| 	INTON;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Called after a function returns.
 | |
|  */
 | |
| 
 | |
| static void
 | |
| poplocalvars() {
 | |
| 	struct localvar *lvp;
 | |
| 	struct var *vp;
 | |
| 
 | |
| 	while ((lvp = localvars) != NULL) {
 | |
| 		localvars = lvp->next;
 | |
| 		vp = lvp->vp;
 | |
| 		if (vp == NULL) {       /* $- saved */
 | |
| 			memcpy(optet_vals, lvp->text, sizeof optet_vals);
 | |
| 			ckfree(lvp->text);
 | |
| 		} else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
 | |
| 			(void)unsetvar(vp->text);
 | |
| 		} else {
 | |
| 			if ((vp->flags & VTEXTFIXED) == 0)
 | |
| 				ckfree(vp->text);
 | |
| 			vp->flags = lvp->flags;
 | |
| 			vp->text = lvp->text;
 | |
| 		}
 | |
| 		ckfree(lvp);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| setvarcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	if (argc <= 2)
 | |
| 		return unsetcmd(argc, argv);
 | |
| 	else if (argc == 3)
 | |
| 		setvar(argv[1], argv[2], 0);
 | |
| 	else
 | |
| 		error("List assignment not implemented");
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * The unset builtin command.  We unset the function before we unset the
 | |
|  * variable to allow a function to be unset when there is a readonly variable
 | |
|  * with the same name.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| unsetcmd(argc, argv)
 | |
| 	int argc;
 | |
| 	char **argv;
 | |
| {
 | |
| 	char **ap;
 | |
| 	int i;
 | |
| 	int flg_func = 0;
 | |
| 	int flg_var = 0;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	while ((i = nextopt("vf")) != '\0') {
 | |
| 		if (i == 'f')
 | |
| 			flg_func = 1;
 | |
| 		else
 | |
| 			flg_var = 1;
 | |
| 	}
 | |
| 	if (flg_func == 0 && flg_var == 0)
 | |
| 		flg_var = 1;
 | |
| 
 | |
| 	for (ap = argptr; *ap ; ap++) {
 | |
| 		if (flg_func)
 | |
| 			unsetfunc(*ap);
 | |
| 		if (flg_var)
 | |
| 			ret |= unsetvar(*ap);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Unset the specified variable.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| unsetvar(const char *s)
 | |
| {
 | |
| 	struct var **vpp;
 | |
| 	struct var *vp;
 | |
| 
 | |
| 	vpp = findvar(hashvar(s), s);
 | |
| 	vp = *vpp;
 | |
| 	if (vp) {
 | |
| 		if (vp->flags & VREADONLY)
 | |
| 			return (1);
 | |
| 		INTOFF;
 | |
| 		if (*(strchr(vp->text, '=') + 1) != '\0')
 | |
| 			setvar(s, nullstr, 0);
 | |
| 		vp->flags &= ~VEXPORT;
 | |
| 		vp->flags |= VUNSET;
 | |
| 		if ((vp->flags & VSTRFIXED) == 0) {
 | |
| 			if ((vp->flags & VTEXTFIXED) == 0)
 | |
| 				ckfree(vp->text);
 | |
| 			*vpp = vp->next;
 | |
| 			ckfree(vp);
 | |
| 		}
 | |
| 		INTON;
 | |
| 		return (0);
 | |
| 	}
 | |
| 
 | |
| 	return (0);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Find the appropriate entry in the hash table from the name.
 | |
|  */
 | |
| 
 | |
| static struct var **
 | |
| hashvar(const char *p)
 | |
| {
 | |
| 	unsigned int hashval;
 | |
| 
 | |
| 	hashval = ((unsigned char) *p) << 4;
 | |
| 	while (*p && *p != '=')
 | |
| 		hashval += (unsigned char) *p++;
 | |
| 	return &vartab[hashval % VTABSIZE];
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns true if the two strings specify the same varable.  The first
 | |
|  * variable name is terminated by '='; the second may be terminated by
 | |
|  * either '=' or '\0'.
 | |
|  */
 | |
| 
 | |
| static int
 | |
| varequal(const char *p, const char *q)
 | |
| {
 | |
| 	while (*p == *q++) {
 | |
| 		if (*p++ == '=')
 | |
| 			return 1;
 | |
| 	}
 | |
| 	if (*p == '=' && *(q - 1) == '\0')
 | |
| 		return 1;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| showvars(const char *myprefix, int mask, int xor)
 | |
| {
 | |
| 	struct var **vpp;
 | |
| 	struct var *vp;
 | |
| 	const char *sep = myprefix == nullstr ? myprefix : spcstr;
 | |
| 
 | |
| 	for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
 | |
| 		for (vp = *vpp ; vp ; vp = vp->next) {
 | |
| 			if ((vp->flags & mask) ^ xor) {
 | |
| 				char *p;
 | |
| 				int len;
 | |
| 
 | |
| 				p = strchr(vp->text, '=') + 1;
 | |
| 				len = p - vp->text;
 | |
| 				p = single_quote(p);
 | |
| 
 | |
| 				printf("%s%s%.*s%s\n", myprefix, sep, len,
 | |
| 					vp->text, p);
 | |
| 				stunalloc(p);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct var **
 | |
| findvar(struct var **vpp, const char *name)
 | |
| {
 | |
| 	for (; *vpp; vpp = &(*vpp)->next) {
 | |
| 		if (varequal((*vpp)->text, name)) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	return vpp;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Copyright (c) 1999 Herbert Xu <herbert@debian.org>
 | |
|  * This file contains code for the times builtin.
 | |
|  * $Id: ash.c,v 1.36 2001/11/12 16:57:26 kraai Exp $
 | |
|  */
 | |
| static int timescmd (int argc, char **argv)
 | |
| {
 | |
| 	struct tms buf;
 | |
| 	long int clk_tck = sysconf(_SC_CLK_TCK);
 | |
| 
 | |
| 	times(&buf);
 | |
| 	printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n",
 | |
| 	       (int) (buf.tms_utime / clk_tck / 60),
 | |
| 	       ((double) buf.tms_utime) / clk_tck,
 | |
| 	       (int) (buf.tms_stime / clk_tck / 60),
 | |
| 	       ((double) buf.tms_stime) / clk_tck,
 | |
| 	       (int) (buf.tms_cutime / clk_tck / 60),
 | |
| 	       ((double) buf.tms_cutime) / clk_tck,
 | |
| 	       (int) (buf.tms_cstime / clk_tck / 60),
 | |
| 	       ((double) buf.tms_cstime) / clk_tck);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #ifdef ASH_MATH_SUPPORT
 | |
| /* The let builtin.  */
 | |
| int letcmd(int argc, char **argv)
 | |
| {
 | |
| 	int errcode;
 | |
| 	long result=0;
 | |
| 	if (argc == 2) {
 | |
| 		char *tmp, *expression, p[13];
 | |
| 		expression = strchr(argv[1], '=');
 | |
| 		if (!expression) {
 | |
| 			/* Cannot use 'error()' here, or the return code
 | |
| 			 * will be incorrect */
 | |
| 			out2fmt("sh: let: syntax error: \"%s\"\n", argv[1]);
 | |
| 			return 0;
 | |
| 		}
 | |
| 		*expression = '\0';
 | |
| 		tmp = ++expression;
 | |
| 		result = arith(tmp, &errcode);
 | |
| 		if (errcode < 0) {
 | |
| 			/* Cannot use 'error()' here, or the return code
 | |
| 			 * will be incorrect */
 | |
| 			out2fmt("sh: let: ");
 | |
| 			if(errcode == -2)
 | |
| 				out2fmt("divide by zero");
 | |
| 			else
 | |
| 				out2fmt("syntax error: \"%s=%s\"\n", argv[1], expression);
 | |
| 			return 0;
 | |
| 		}
 | |
| 		snprintf(p, 12, "%ld", result);
 | |
| 		setvar(argv[1], savestr(p), 0);
 | |
| 	} else if (argc >= 3)
 | |
| 		synerror("invalid operand");
 | |
| 	return !result;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| 
 | |
| /*-
 | |
|  * Copyright (c) 1989, 1991, 1993, 1994
 | |
|  *      The Regents of the University of California.  All rights reserved.
 | |
|  *
 | |
|  * This code is derived from software contributed to Berkeley by
 | |
|  * Kenneth Almquist.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions
 | |
|  * are met:
 | |
|  * 1. Redistributions of source code must retain the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer.
 | |
|  * 2. Redistributions in binary form must reproduce the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer in the
 | |
|  *    documentation and/or other materials provided with the distribution.
 | |
|  *
 | |
|  * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
 | |
|  *              ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
 | |
|  *
 | |
|  * 4. Neither the name of the University nor the names of its contributors
 | |
|  *    may be used to endorse or promote products derived from this software
 | |
|  *    without specific prior written permission.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 | |
|  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | |
|  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 | |
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | |
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | |
|  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | |
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | |
|  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | |
|  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | |
|  * SUCH DAMAGE.
 | |
|  */
 |