| /* 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 }, |
| #if defined(CONFIG_FEATURE_COMMAND_EDITING) && defined(CONFIG_FEATURE_SH_FANCY_PROMPT) |
| { &vps1, VSTRFIXED|VTEXTFIXED, "PS1=\\w \\$ ", |
| NULL }, |
| #endif /* else 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; |
| } |
| } |
| #if !defined(CONFIG_FEATURE_COMMAND_EDITING) || !defined(CONFIG_FEATURE_SH_FANCY_PROMPT) |
| /* |
| * 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; |
| } |
| #endif |
| } |
| |
| /* |
| * 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.47 2002/04/13 05:37:10 timr 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. |
| */ |