12526 lines
		
	
	
		
			267 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			12526 lines
		
	
	
		
			267 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.
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
/* 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 CONFIG_ASH_JOB_CONTROL
 | 
						|
#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 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 CONFIG_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 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 CONFIG_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 CONFIG_ASH_OPTIMIZE_FOR_SIZE
 | 
						|
#define out2c(c)        putc((c), stderr)
 | 
						|
#else
 | 
						|
static void out2c(int c)           { putc(c, stderr); }
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
#ifdef CONFIG_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 *);
 | 
						|
 | 
						|
static void find_command (const char *, struct cmdentry *, int, const char *);
 | 
						|
 | 
						|
static inline void hashcd (void);
 | 
						|
 | 
						|
/* 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 CONFIG_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(char *name, char *val)
 | 
						|
{
 | 
						|
	struct alias *ap, **app;
 | 
						|
 | 
						|
	app = __lookupalias(name);
 | 
						|
	ap = *app;
 | 
						|
	INTOFF;
 | 
						|
	if (ap) {
 | 
						|
		if (!(ap->flag & ALIASINUSE)) {
 | 
						|
			ckfree(ap->val);
 | 
						|
		}
 | 
						|
		ap->val = xstrdup(val);
 | 
						|
		ap->flag &= ~ALIASDEAD;
 | 
						|
	} else {
 | 
						|
		/* not found */
 | 
						|
		ap = xmalloc(sizeof (struct alias));
 | 
						|
		ap->name = xstrdup(name);
 | 
						|
		ap->val = xstrdup(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(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 CONFIG_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 CONFIG_ASH_JOB_CONTROL
 | 
						|
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 CONFIG_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 CONFIG_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 CONFIG_ASH_MATH_SUPPORT
 | 
						|
static int letcmd (int, char **);
 | 
						|
#endif
 | 
						|
static int typecmd (int, char **);
 | 
						|
#ifdef CONFIG_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 CONFIG_ASH_ALIAS
 | 
						|
	{ BUILTIN_REG_ASSG  "alias", aliascmd },
 | 
						|
#endif
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	{ BUILTIN_REGULAR   "bg", bgcmd },
 | 
						|
#endif
 | 
						|
	{ BUILTIN_SPECIAL   "break", breakcmd },
 | 
						|
	{ BUILTIN_SPECIAL   "builtin", bltincmd },
 | 
						|
	{ BUILTIN_REGULAR   "cd", cdcmd },
 | 
						|
	{ BUILTIN_NOSPEC    "chdir", cdcmd },
 | 
						|
#ifdef CONFIG_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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	{ BUILTIN_REGULAR   "fg", fgcmd },
 | 
						|
#endif
 | 
						|
#ifdef CONFIG_ASH_GETOPTS
 | 
						|
	{ BUILTIN_REGULAR   "getopts", getoptscmd },
 | 
						|
#endif
 | 
						|
	{ BUILTIN_NOSPEC    "hash", hashcmd },
 | 
						|
	{ BUILTIN_NOSPEC    "help", helpcmd },
 | 
						|
	{ BUILTIN_REGULAR   "jobs", jobscmd },
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	{ BUILTIN_REGULAR   "kill", killcmd },
 | 
						|
#endif
 | 
						|
#ifdef CONFIG_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 CONFIG_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 CONFIG_ASH_JOB_CONTROLTOPPED 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 CONFIG_ASH_JOB_CONTROL
 | 
						|
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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 CONFIG_ASH_JOB_CONTROL
 | 
						|
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 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(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;
 | 
						|
	}
 | 
						|
	hashcd();
 | 
						|
	/*
 | 
						|
	 * 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.
 | 
						|
	 */
 | 
						|
	/* If dest is NULL, we don't know the current directory */
 | 
						|
	if (dest == NULL || curdir == nullstr)
 | 
						|
		setpwd(0, 1);
 | 
						|
	else
 | 
						|
		setpwd(dest, 1);
 | 
						|
 | 
						|
	INTON;
 | 
						|
	if (print && iflag)
 | 
						|
		puts(curdir);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
pwdcmd(int argc, char **argv)
 | 
						|
{
 | 
						|
	puts(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 */
 | 
						|
	short  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 CONFIG_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 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(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 = xmalloc(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) {
 | 
						|
		/*
 | 
						|
		 * 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.
 | 
						|
		 */
 | 
						|
 | 
						|
		struct cmdentry entry;
 | 
						|
		union node *lpn = lp->n;
 | 
						|
		if (lpn->type == NCMD && lpn->ncmd.args && goodname(lpn->ncmd.args->narg.text))
 | 
						|
			find_command(lpn->ncmd.args->narg.text, &entry, 0, pathval());
 | 
						|
 | 
						|
		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 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(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.
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * 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(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(int argc, char **argv)
 | 
						|
{
 | 
						|
	int n = argc > 1 ? number(argv[1]) : 1;
 | 
						|
 | 
						|
	if (n <= 0)
 | 
						|
		error("Illegal number: %s", argv[1]);
 | 
						|
	if (n > loopnest)
 | 
						|
		n = loopnest;
 | 
						|
	if (n > 0) {
 | 
						|
		evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
 | 
						|
		skipcount = n;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * The return command.
 | 
						|
 */
 | 
						|
 | 
						|
static int
 | 
						|
returncmd(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(int argc, char **argv)
 | 
						|
{
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef CONFIG_TRUE
 | 
						|
static int
 | 
						|
true_main(int argc, char **argv)
 | 
						|
{
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/*
 | 
						|
 * Controls whether the shell is interactive or not.
 | 
						|
 */
 | 
						|
 | 
						|
static void setsignal(int signo);
 | 
						|
 | 
						|
#ifdef CONFIG_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 CONFIG_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(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(char **argv, char **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 CONFIG_ASH_ALIAS
 | 
						|
      /* from alias.c: */
 | 
						|
      {
 | 
						|
	      rmaliases();
 | 
						|
      }
 | 
						|
#endif
 | 
						|
      /* from eval.c: */
 | 
						|
      {
 | 
						|
	      exitstatus = 0;
 | 
						|
      }
 | 
						|
 | 
						|
      /* from exec.c: */
 | 
						|
      {
 | 
						|
	      deletefuncs();
 | 
						|
      }
 | 
						|
 | 
						|
      /* from jobs.c: */
 | 
						|
      {
 | 
						|
	      backgndpid = -1;
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	      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 CONFIG_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 = xmalloc(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 = xstrdup(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(int argc, char **argv)
 | 
						|
{
 | 
						|
	struct tblentry **pp;
 | 
						|
	struct tblentry *cmdp;
 | 
						|
	int c;
 | 
						|
	int verbose;
 | 
						|
	struct cmdentry entry;
 | 
						|
	char *name;
 | 
						|
#ifdef CONFIG_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 CONFIG_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
 | 
						|
				puts(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(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
 | 
						|
	}
 | 
						|
	puts(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 inline 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(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 = xmalloc(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 CONFIG_ASH_CMDCMD
 | 
						|
static int
 | 
						|
commandcmd(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(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 inline 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 inline 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(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(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: /* ??? */
 | 
						|
			return;
 | 
						|
		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 CONFIG_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);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return;
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
exptilde(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 CONFIG_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(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(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(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 inline 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(int start, int end, int nulonly)
 | 
						|
{
 | 
						|
	struct ifsregion *ifsp;
 | 
						|
 | 
						|
	if (ifslastp == NULL) {
 | 
						|
		ifsp = &ifsfirst;
 | 
						|
	} else {
 | 
						|
		INTOFF;
 | 
						|
		ifsp = (struct ifsregion *)xmalloc(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(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(void)
 | 
						|
{
 | 
						|
	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)
 | 
						|
{
 | 
						|
	struct strlist *sp;
 | 
						|
 | 
						|
	sp = (struct strlist *)stalloc(sizeof *sp);
 | 
						|
	sp->text = sstrdup(name);
 | 
						|
	*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(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 inline void
 | 
						|
addglob(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(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 = xmalloc(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(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(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(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(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(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(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 CONFIG_ASH_ALIAS
 | 
						|
static int
 | 
						|
pgetc2(void)
 | 
						|
{
 | 
						|
	int c;
 | 
						|
	do {
 | 
						|
		c = pgetc_macro();
 | 
						|
	} while (c == PEOA);
 | 
						|
	return c;
 | 
						|
}
 | 
						|
#else
 | 
						|
static inline int pgetc2(void) { 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 CONFIG_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 CONFIG_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 = xmalloc(sizeof (struct strpush));
 | 
						|
		sp->prev = parsefile->strpush;
 | 
						|
		parsefile->strpush = sp;
 | 
						|
	} else
 | 
						|
		sp = parsefile->strpush = &(parsefile->basestrpush);
 | 
						|
	sp->prevstring = parsenextc;
 | 
						|
	sp->prevnleft = parsenleft;
 | 
						|
#ifdef CONFIG_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 *)xmalloc(sizeof (struct parsefile));
 | 
						|
	pf->prev = parsefile;
 | 
						|
	pf->fd = -1;
 | 
						|
	pf->strpush = NULL;
 | 
						|
	pf->basestrpush.prev = NULL;
 | 
						|
	parsefile = pf;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
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 CONFIG_ASH_JOB_CONTROL
 | 
						|
/*
 | 
						|
 * 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 CONFIG_ASH_JOB_CONTROL
 | 
						|
static int
 | 
						|
killcmd(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)
 | 
						|
					puts(name);
 | 
						|
			}
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
		name = u_signal_names(*argptr, &signo, -1);
 | 
						|
		if (name)
 | 
						|
			puts(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(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);
 | 
						|
	status = waitforjob(jp);
 | 
						|
	return status;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
bgcmd(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(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(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(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 CONFIG_ASH_JOB_CONTROL
 | 
						|
				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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	if (curjob == jp - jobtab + 1)
 | 
						|
		curjob = 0;
 | 
						|
#endif
 | 
						|
	INTON;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
waitcmd(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 CONFIG_ASH_JOB_CONTROL
 | 
						|
				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 CONFIG_ASH_JOB_CONTROL
 | 
						|
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 CONFIG_ASH_JOB_CONTROL
 | 
						|
		} 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 = xmalloc(4 * sizeof jobtab[0]);
 | 
						|
			} else {
 | 
						|
				jp = xmalloc((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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	jp->jobctl = jobctl;
 | 
						|
#endif
 | 
						|
	if (nprocs > 1) {
 | 
						|
		jp->ps = xmalloc(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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 CONFIG_ASH_JOB_CONTROL
 | 
						|
		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) {
 | 
						|
#else
 | 
						|
		if (mode == FORK_BG) {
 | 
						|
#endif
 | 
						|
			ignoresig(SIGINT);
 | 
						|
			ignoresig(SIGQUIT);
 | 
						|
			if ((jp == NULL || jp->nprocs == 0) &&
 | 
						|
			    ! fd0_redirected_p ()) {
 | 
						|
				close(0);
 | 
						|
				if (open(devnull, O_RDONLY) != 0)
 | 
						|
					error(nullerr, devnull);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	int mypgrp = getpgrp();
 | 
						|
#endif
 | 
						|
	int status;
 | 
						|
	int st;
 | 
						|
	struct sigaction act, oact;
 | 
						|
 | 
						|
	INTOFF;
 | 
						|
	intreceived = 0;
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	if (!jobctl) {
 | 
						|
#else
 | 
						|
	if (!iflag) {
 | 
						|
#endif
 | 
						|
		sigaction(SIGINT, &oact, 0);
 | 
						|
		if (intreceived && trap[SIGINT]) kill(getpid(), SIGINT);
 | 
						|
	}
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 == CONFIG_ASH_JOB_CONTROLTOPPED)
 | 
						|
		curjob = jp - jobtab + 1;
 | 
						|
#endif
 | 
						|
	status = jp->ps[jp->nprocs - 1].status;
 | 
						|
	/* convert to 8 bits */
 | 
						|
	if (WIFEXITED(status))
 | 
						|
		st = WEXITSTATUS(status);
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	else if (WIFSTOPPED(status))
 | 
						|
		st = WSTOPSIG(status) + 128;
 | 
						|
#endif
 | 
						|
	else
 | 
						|
		st = WTERMSIG(status) + 128;
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	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 : CONFIG_ASH_JOB_CONTROLTOPPED;
 | 
						|
				if (jp->state != state) {
 | 
						|
					TRACE(("Job %d: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
 | 
						|
					jp->state = state;
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
					if (done && curjob == jp - jobtab + 1)
 | 
						|
						curjob = 0;             /* no current job */
 | 
						|
#endif
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	INTON;
 | 
						|
	if (! rootshell || ! iflag || (job && thisjob == job)) {
 | 
						|
		core = WCOREDUMP(status);
 | 
						|
#ifdef CONFIG_ASH_JOB_CONTROL
 | 
						|
		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 CONFIG_ASH_JOB_CONTROL
 | 
						|
			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 == CONFIG_ASH_JOB_CONTROLTOPPED) {
 | 
						|
			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 = xmalloc(MAXCMDTEXT);
 | 
						|
	cmdnleft = MAXCMDTEXT - 4;
 | 
						|
	cmdtxt(n);
 | 
						|
	*cmdnextc = '\0';
 | 
						|
	return name;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void waitonint(int sig) {
 | 
						|
	intreceived = 1;
 | 
						|
	return;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef CONFIG_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 /* CONFIG_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(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 CONFIG_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(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(int argc, char **argv)
 | 
						|
{
 | 
						|
	struct strlist *sp;
 | 
						|
	volatile struct shparam saveparam;
 | 
						|
	exitstatus = 0;
 | 
						|
 | 
						|
	for (sp = cmdenviron; sp ; sp = sp->next)
 | 
						|
		setvareq(xstrdup(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(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 = xmalloc(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 = xrealloc((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(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)) {
 | 
						|
				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(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 *nodexstrdup (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) = nodexstrdup(*((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 = nodexstrdup(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 = nodexstrdup(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 *
 | 
						|
nodexstrdup(const char *s)
 | 
						|
{
 | 
						|
	const char *p = s;
 | 
						|
	char *q = funcstring;
 | 
						|
	char   *rtn = funcstring;
 | 
						|
 | 
						|
	while ((*q++ = *p++) != '\0')
 | 
						|
		continue;
 | 
						|
	funcstring = q;
 | 
						|
	return rtn;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef CONFIG_ASH_GETOPTS
 | 
						|
static int getopts (char *, char *, char **, int *, int *);
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Process the shell command line arguments.
 | 
						|
 */
 | 
						|
 | 
						|
static void
 | 
						|
procargs(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 = xmalloc((nparam + 1) * sizeof *ap);
 | 
						|
	while (*argv) {
 | 
						|
		*ap++ = xstrdup(*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(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(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 CONFIG_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(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(const char *name, const char *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(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(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 CONFIG_ASH_ALIAS
 | 
						|
	int savecheckalias = checkalias;
 | 
						|
	int savecheckkwd = checkkwd;
 | 
						|
	struct alias *ap;
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
	int alreadyseen = tokpushback;
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef CONFIG_ASH_ALIAS
 | 
						|
top:
 | 
						|
#endif
 | 
						|
 | 
						|
	t = xxreadtoken();
 | 
						|
 | 
						|
#ifdef CONFIG_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 CONFIG_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 CONFIG_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 CONFIG_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 CONFIG_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 CONFIG_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 CONFIG_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 = xmalloc(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 CONFIG_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(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(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 = xmalloc(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(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(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(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(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(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(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(const char *s)
 | 
						|
{
 | 
						|
	if (tracefile == NULL)
 | 
						|
		return;
 | 
						|
	fputs(s, tracefile);
 | 
						|
	if (strchr(s, '\n'))
 | 
						|
		fflush(tracefile);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
trstring(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(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(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 = xstrdup(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 CONFIG_ASH_JOB_CONTROL
 | 
						|
		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(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 CONFIG_ASH_JOB_CONTROL
 | 
						|
	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(const char *name, const char *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 = xmalloc(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(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 CONFIG_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 = xmalloc(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(struct strlist *mylist)
 | 
						|
{
 | 
						|
	struct strlist *lp;
 | 
						|
 | 
						|
	INTOFF;
 | 
						|
	for (lp = mylist ; lp ; lp = lp->next) {
 | 
						|
		setvareq(xstrdup(lp->text), 0);
 | 
						|
	}
 | 
						|
	INTON;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Find the value of a variable.  Returns NULL if not set.
 | 
						|
 */
 | 
						|
 | 
						|
static const char *
 | 
						|
lookupvar(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 = xstrdup(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(int argc, char **argv)
 | 
						|
{
 | 
						|
	showvars(nullstr, VUNSET, VUNSET);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * The export and readonly commands.
 | 
						|
 */
 | 
						|
 | 
						|
static int
 | 
						|
exportcmd(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(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(char *name)
 | 
						|
{
 | 
						|
	struct localvar *lvp;
 | 
						|
	struct var **vpp;
 | 
						|
	struct var *vp;
 | 
						|
 | 
						|
	INTOFF;
 | 
						|
	lvp = xmalloc(sizeof (struct localvar));
 | 
						|
	if (name[0] == '-' && name[1] == '\0') {
 | 
						|
		char *p;
 | 
						|
		p = xmalloc(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(xstrdup(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(xstrdup(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(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(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.46 2002/01/09 15:37:36 andersen 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 CONFIG_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], xstrdup(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.
 | 
						|
 */
 |