Skip to content
Find file
13261 lines (12158 sloc) 291 KB
/* vi: set sw=4 ts=4: */
/*
* ash shell port for busybox
*
* This code is derived from software contributed to Berkeley by
* Kenneth Almquist.
*
* Original BSD copyright notice is retained at the end of this file.
*
* Copyright (c) 1989, 1991, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au>
* was re-ported from NetBSD and debianized.
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
/*
* The following should be set to reflect the type of system you have:
* JOBS -> 1 if you have Berkeley job control, 0 otherwise.
* define SYSV if you are running under System V.
* define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
* define DEBUG=2 to compile in and turn on debugging.
*
* When debugging is on, debugging info will be written to ./trace and
* a quit signal will generate a core dump.
*/
#define DEBUG 0
/* Tweak debug output verbosity here */
#define DEBUG_TIME 0
#define DEBUG_PID 1
#define DEBUG_SIG 1
#define PROFILE 0
#define JOBS ENABLE_ASH_JOB_CONTROL
#include <paths.h>
#include <setjmp.h>
#include <fnmatch.h>
#include <sys/times.h>
#include "busybox.h" /* for applet_names */
#include "unicode.h"
#include "shell_common.h"
#if ENABLE_SH_MATH_SUPPORT
# include "math.h"
#endif
#if ENABLE_ASH_RANDOM_SUPPORT
# include "random.h"
#else
# define CLEAR_RANDOM_T(rnd) ((void)0)
#endif
#include "NUM_APPLETS.h"
#if NUM_APPLETS == 1
/* STANDALONE does not make sense, and won't compile */
# undef CONFIG_FEATURE_SH_STANDALONE
# undef ENABLE_FEATURE_SH_STANDALONE
# undef IF_FEATURE_SH_STANDALONE
# undef IF_NOT_FEATURE_SH_STANDALONE
# define ENABLE_FEATURE_SH_STANDALONE 0
# define IF_FEATURE_SH_STANDALONE(...)
# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
#if !BB_MMU
# error "Do not even bother, ash will not run on NOMMU machine"
#endif
//config:config ASH
//config: bool "ash"
//config: default y
//config: depends on !NOMMU
//config: help
//config: Tha 'ash' shell adds about 60k in the default configuration and is
//config: the most complete and most pedantically correct shell included with
//config: busybox. This shell is actually a derivative of the Debian 'dash'
//config: shell (by Herbert Xu), which was created by porting the 'ash' shell
//config: (written by Kenneth Almquist) from NetBSD.
//config:
//config:config ASH_BASH_COMPAT
//config: bool "bash-compatible extensions"
//config: default y
//config: depends on ASH
//config: help
//config: Enable bash-compatible extensions.
//config:
//config:config ASH_IDLE_TIMEOUT
//config: bool "Idle timeout variable"
//config: default n
//config: depends on ASH
//config: help
//config: Enables bash-like auto-logout after $TMOUT seconds of idle time.
//config:
//config:config ASH_JOB_CONTROL
//config: bool "Job control"
//config: default y
//config: depends on ASH
//config: help
//config: Enable job control in the ash shell.
//config:
//config:config ASH_ALIAS
//config: bool "Alias support"
//config: default y
//config: depends on ASH
//config: help
//config: Enable alias support in the ash shell.
//config:
//config:config ASH_GETOPTS
//config: bool "Builtin getopt to parse positional parameters"
//config: default y
//config: depends on ASH
//config: help
//config: Enable support for getopts builtin in ash.
//config:
//config:config ASH_BUILTIN_ECHO
//config: bool "Builtin version of 'echo'"
//config: default y
//config: depends on ASH
//config: help
//config: Enable support for echo builtin in ash.
//config:
//config:config ASH_BUILTIN_PRINTF
//config: bool "Builtin version of 'printf'"
//config: default y
//config: depends on ASH
//config: help
//config: Enable support for printf builtin in ash.
//config:
//config:config ASH_BUILTIN_TEST
//config: bool "Builtin version of 'test'"
//config: default y
//config: depends on ASH
//config: help
//config: Enable support for test builtin in ash.
//config:
//config:config ASH_CMDCMD
//config: bool "'command' command to override shell builtins"
//config: default y
//config: depends on ASH
//config: help
//config: Enable support for the ash 'command' builtin, which allows
//config: you to run the specified command with the specified arguments,
//config: even when there is an ash builtin command with the same name.
//config:
//config:config ASH_MAIL
//config: bool "Check for new mail on interactive shells"
//config: default n
//config: depends on ASH
//config: help
//config: Enable "check for new mail" function in the ash shell.
//config:
//config:config ASH_OPTIMIZE_FOR_SIZE
//config: bool "Optimize for size instead of speed"
//config: default y
//config: depends on ASH
//config: help
//config: Compile ash for reduced size at the price of speed.
//config:
//config:config ASH_RANDOM_SUPPORT
//config: bool "Pseudorandom generator and $RANDOM variable"
//config: default y
//config: depends on ASH
//config: help
//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
//config: Each read of "$RANDOM" will generate a new pseudorandom value.
//config: You can reset the generator by using a specified start value.
//config: After "unset RANDOM" the generator will switch off and this
//config: variable will no longer have special treatment.
//config:
//config:config ASH_EXPAND_PRMT
//config: bool "Expand prompt string"
//config: default y
//config: depends on ASH
//config: help
//config: "PS#" may contain volatile content, such as backquote commands.
//config: This option recreates the prompt string from the environment
//config: variable each time it is displayed.
//config:
//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, sh))
//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, bash))
//kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
/* ============ Hash table sizes. Configurable. */
#define VTABSIZE 39
#define ATABSIZE 39
#define CMDTABLESIZE 31 /* should be prime */
/* ============ Shell options */
static const char *const optletters_optnames[] = {
"e" "errexit",
"f" "noglob",
"I" "ignoreeof",
"i" "interactive",
"m" "monitor",
"n" "noexec",
"s" "stdin",
"x" "xtrace",
"v" "verbose",
"C" "noclobber",
"a" "allexport",
"b" "notify",
"u" "nounset",
"\0" "vi"
#if ENABLE_ASH_BASH_COMPAT
,"\0" "pipefail"
#endif
#if DEBUG
,"\0" "nolog"
,"\0" "debug"
#endif
};
#define optletters(n) optletters_optnames[n][0]
#define optnames(n) (optletters_optnames[n] + 1)
enum { NOPTS = ARRAY_SIZE(optletters_optnames) };
/* ============ Misc data */
#define msg_illnum "Illegal number: %s"
/*
* 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_type
* contains a code identifying the exception. 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;
};
struct globals_misc {
/* pid of main shell */
int rootpid;
/* shell level: 0 for the main shell, 1 for its children, and so on */
int shlvl;
#define rootshell (!shlvl)
char *minusc; /* argument to -c option */
char *curdir; // = nullstr; /* current working directory */
char *physdir; // = nullstr; /* physical working directory */
char *arg0; /* value of $0 */
struct jmploc *exception_handler;
volatile int suppress_int; /* counter */
volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
/* last pending signal */
volatile /*sig_atomic_t*/ smallint pending_sig;
smallint exception_type; /* kind of exception (0..5) */
/* 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 */
#define EXEXIT 4 /* exit the shell */
#define EXSIG 5 /* trapped signal in wait(1) */
smallint isloginsh;
char nullstr[1]; /* zero length string */
char optlist[NOPTS];
#define eflag optlist[0]
#define fflag optlist[1]
#define Iflag optlist[2]
#define iflag optlist[3]
#define mflag optlist[4]
#define nflag optlist[5]
#define sflag optlist[6]
#define xflag optlist[7]
#define vflag optlist[8]
#define Cflag optlist[9]
#define aflag optlist[10]
#define bflag optlist[11]
#define uflag optlist[12]
#define viflag optlist[13]
#if ENABLE_ASH_BASH_COMPAT
# define pipefail optlist[14]
#else
# define pipefail 0
#endif
#if DEBUG
# define nolog optlist[14 + ENABLE_ASH_BASH_COMPAT]
# define debug optlist[15 + ENABLE_ASH_BASH_COMPAT]
#endif
/* trap handler commands */
/*
* Sigmode records the current value of the signal handlers for the various
* modes. A value of zero means that the current handler is not known.
* S_HARD_IGN indicates that the signal was ignored on entry to the shell.
*/
char sigmode[NSIG - 1];
#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 */
/* indicates specified signal received */
uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
uint8_t may_have_traps; /* 0: definitely no traps are set, 1: some traps may be set */
char *trap[NSIG];
char **trap_ptr; /* used only by "trap hack" */
/* Rarely referenced stuff */
#if ENABLE_ASH_RANDOM_SUPPORT
random_t random_gen;
#endif
pid_t backgndpid; /* pid of last background process */
smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */
};
extern struct globals_misc *const ash_ptr_to_globals_misc;
#define G_misc (*ash_ptr_to_globals_misc)
#define rootpid (G_misc.rootpid )
#define shlvl (G_misc.shlvl )
#define minusc (G_misc.minusc )
#define curdir (G_misc.curdir )
#define physdir (G_misc.physdir )
#define arg0 (G_misc.arg0 )
#define exception_handler (G_misc.exception_handler)
#define exception_type (G_misc.exception_type )
#define suppress_int (G_misc.suppress_int )
#define pending_int (G_misc.pending_int )
#define pending_sig (G_misc.pending_sig )
#define isloginsh (G_misc.isloginsh )
#define nullstr (G_misc.nullstr )
#define optlist (G_misc.optlist )
#define sigmode (G_misc.sigmode )
#define gotsig (G_misc.gotsig )
#define may_have_traps (G_misc.may_have_traps )
#define trap (G_misc.trap )
#define trap_ptr (G_misc.trap_ptr )
#define random_gen (G_misc.random_gen )
#define backgndpid (G_misc.backgndpid )
#define job_warning (G_misc.job_warning)
#define INIT_G_misc() do { \
(*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \
barrier(); \
curdir = nullstr; \
physdir = nullstr; \
trap_ptr = trap; \
} while (0)
/* ============ DEBUG */
#if DEBUG
static void trace_printf(const char *fmt, ...);
static void trace_vprintf(const char *fmt, va_list va);
# define TRACE(param) trace_printf param
# define TRACEV(param) trace_vprintf param
# define close(fd) do { \
int dfd = (fd); \
if (close(dfd) < 0) \
bb_error_msg("bug on %d: closing %d(0x%x)", \
__LINE__, dfd, dfd); \
} while (0)
#else
# define TRACE(param)
# define TRACEV(param)
#endif
/* ============ Utility functions */
#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
static int isdigit_str9(const char *str)
{
int maxlen = 9 + 1; /* max 9 digits: 999999999 */
while (--maxlen && isdigit(*str))
str++;
return (*str == '\0');
}
static const char *var_end(const char *var)
{
while (*var)
if (*var++ == '=')
break;
return var;
}
/* ============ Interrupts / exceptions */
static void exitshell(void) NORETURN;
/*
* These macros allow the user to suspend the handling of interrupt signals
* over a period of time. This is similar to SIGHOLD or to sigblock, but
* much more efficient and portable. (But hacking the kernel is so much
* more fun than worrying about efficiency and portability. :-))
*/
#define INT_OFF do { \
suppress_int++; \
xbarrier(); \
} while (0)
/*
* 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_type".
*/
static void raise_exception(int) NORETURN;
static void
raise_exception(int e)
{
#if DEBUG
if (exception_handler == NULL)
abort();
#endif
INT_OFF;
exception_type = e;
longjmp(exception_handler->loc, 1);
}
#if DEBUG
#define raise_exception(e) do { \
TRACE(("raising exception %d on line %d\n", (e), __LINE__)); \
raise_exception(e); \
} while (0)
#endif
/*
* 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 INT_OFF macro. (The test for iflag is just
* defensive programming.)
*/
static void raise_interrupt(void) NORETURN;
static void
raise_interrupt(void)
{
int ex_type;
pending_int = 0;
/* Signal is not automatically unmasked after it is raised,
* do it ourself - unmask all signals */
sigprocmask_allsigs(SIG_UNBLOCK);
/* pending_sig = 0; - now done in signal_handler() */
ex_type = EXSIG;
if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
if (!(rootshell && iflag)) {
/* Kill ourself with SIGINT */
signal(SIGINT, SIG_DFL);
raise(SIGINT);
}
ex_type = EXINT;
}
raise_exception(ex_type);
/* NOTREACHED */
}
#if DEBUG
#define raise_interrupt() do { \
TRACE(("raising interrupt on line %d\n", __LINE__)); \
raise_interrupt(); \
} while (0)
#endif
static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
int_on(void)
{
xbarrier();
if (--suppress_int == 0 && pending_int) {
raise_interrupt();
}
}
#define INT_ON int_on()
static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
force_int_on(void)
{
xbarrier();
suppress_int = 0;
if (pending_int)
raise_interrupt();
}
#define FORCE_INT_ON force_int_on()
#define SAVE_INT(v) ((v) = suppress_int)
#define RESTORE_INT(v) do { \
xbarrier(); \
suppress_int = (v); \
if (suppress_int == 0 && pending_int) \
raise_interrupt(); \
} while (0)
/* ============ Stdout/stderr output */
static void
outstr(const char *p, FILE *file)
{
INT_OFF;
fputs(p, file);
INT_ON;
}
static void
flush_stdout_stderr(void)
{
INT_OFF;
fflush_all();
INT_ON;
}
static void
outcslow(int c, FILE *dest)
{
INT_OFF;
putc(c, dest);
fflush(dest);
INT_ON;
}
static int out1fmt(const char *, ...) __attribute__((__format__(__printf__,1,2)));
static int
out1fmt(const char *fmt, ...)
{
va_list ap;
int r;
INT_OFF;
va_start(ap, fmt);
r = vprintf(fmt, ap);
va_end(ap);
INT_ON;
return r;
}
static int fmtstr(char *, size_t, const char *, ...) __attribute__((__format__(__printf__,3,4)));
static int
fmtstr(char *outbuf, size_t length, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
INT_OFF;
ret = vsnprintf(outbuf, length, fmt, ap);
va_end(ap);
INT_ON;
return ret;
}
static void
out1str(const char *p)
{
outstr(p, stdout);
}
static void
out2str(const char *p)
{
outstr(p, stderr);
flush_stdout_stderr();
}
/* ============ Parser structures */
/* control characters in argument strings */
#define CTL_FIRST CTLESC
#define CTLESC ((unsigned char)'\201') /* escape next character */
#define CTLVAR ((unsigned char)'\202') /* variable defn */
#define CTLENDVAR ((unsigned char)'\203')
#define CTLBACKQ ((unsigned char)'\204')
#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */
/* CTLBACKQ | CTLQUOTE == '\205' */
#define CTLARI ((unsigned char)'\206') /* arithmetic expression */
#define CTLENDARI ((unsigned char)'\207')
#define CTLQUOTEMARK ((unsigned char)'\210')
#define CTL_LAST CTLQUOTEMARK
/* 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 VSTRIMRIGHT 0x6 /* ${var%pattern} */
#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */
#define VSTRIMLEFT 0x8 /* ${var#pattern} */
#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
#define VSLENGTH 0xa /* ${#var} */
#if ENABLE_ASH_BASH_COMPAT
#define VSSUBSTR 0xc /* ${var:position:length} */
#define VSREPLACE 0xd /* ${var/pattern/replacement} */
#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */
#endif
static const char dolatstr[] ALIGN1 = {
CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
};
#define NCMD 0
#define NPIPE 1
#define NREDIR 2
#define NBACKGND 3
#define NSUBSHELL 4
#define NAND 5
#define NOR 6
#define NSEMI 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
#if ENABLE_ASH_BASH_COMPAT
#define NTO2 17
#endif
#define NCLOBBER 18
#define NFROM 19
#define NFROMTO 20
#define NAPPEND 21
#define NTOFD 22
#define NFROMFD 23
#define NHERE 24
#define NXHERE 25
#define NNOT 26
#define N_NUMBER 27
union node;
struct ncmd {
smallint type; /* Nxxxx */
union node *assign;
union node *args;
union node *redirect;
};
struct npipe {
smallint type;
smallint pipe_backgnd;
struct nodelist *cmdlist;
};
struct nredir {
smallint type;
union node *n;
union node *redirect;
};
struct nbinary {
smallint type;
union node *ch1;
union node *ch2;
};
struct nif {
smallint type;
union node *test;
union node *ifpart;
union node *elsepart;
};
struct nfor {
smallint type;
union node *args;
union node *body;
char *var;
};
struct ncase {
smallint type;
union node *expr;
union node *cases;
};
struct nclist {
smallint type;
union node *next;
union node *pattern;
union node *body;
};
struct narg {
smallint type;
union node *next;
char *text;
struct nodelist *backquote;
};
/* nfile and ndup layout must match!
* NTOFD (>&fdnum) uses ndup structure, but we may discover mid-flight
* that it is actually NTO2 (>&file), and change its type.
*/
struct nfile {
smallint type;
union node *next;
int fd;
int _unused_dupfd;
union node *fname;
char *expfname;
};
struct ndup {
smallint type;
union node *next;
int fd;
int dupfd;
union node *vname;
char *_unused_expfname;
};
struct nhere {
smallint type;
union node *next;
int fd;
union node *doc;
};
struct nnot {
smallint type;
union node *com;
};
union node {
smallint type;
struct ncmd ncmd;
struct npipe npipe;
struct nredir nredir;
struct nbinary nbinary;
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;
};
/*
* NODE_EOF is returned by parsecmd when it encounters an end of file.
* It must be distinct from NULL.
*/
#define NODE_EOF ((union node *) -1L)
struct nodelist {
struct nodelist *next;
union node *n;
};
struct funcnode {
int count;
union node n;
};
/*
* Free a parse tree.
*/
static void
freefunc(struct funcnode *f)
{
if (f && --f->count < 0)
free(f);
}
/* ============ Debugging output */
#if DEBUG
static FILE *tracefile;
static void
trace_printf(const char *fmt, ...)
{
va_list va;
if (debug != 1)
return;
if (DEBUG_TIME)
fprintf(tracefile, "%u ", (int) time(NULL));
if (DEBUG_PID)
fprintf(tracefile, "[%u] ", (int) getpid());
if (DEBUG_SIG)
fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int);
va_start(va, fmt);
vfprintf(tracefile, fmt, va);
va_end(va);
}
static void
trace_vprintf(const char *fmt, va_list va)
{
if (debug != 1)
return;
if (DEBUG_TIME)
fprintf(tracefile, "%u ", (int) time(NULL));
if (DEBUG_PID)
fprintf(tracefile, "[%u] ", (int) getpid());
if (DEBUG_SIG)
fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int);
vfprintf(tracefile, fmt, va);
}
static void
trace_puts(const char *s)
{
if (debug != 1)
return;
fputs(s, tracefile);
}
static void
trace_puts_quoted(char *s)
{
char *p;
char c;
if (debug != 1)
return;
putc('"', tracefile);
for (p = s; *p; p++) {
switch ((unsigned char)*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
trace_puts_args(char **ap)
{
if (debug != 1)
return;
if (!*ap)
return;
while (1) {
trace_puts_quoted(*ap);
if (!*++ap) {
putc('\n', tracefile);
break;
}
putc(' ', tracefile);
}
}
static void
opentrace(void)
{
char s[100];
#ifdef O_APPEND
int flags;
#endif
if (debug != 1) {
if (tracefile)
fflush(tracefile);
/* leave open because libedit might be using it */
return;
}
strcpy(s, "./trace");
if (tracefile) {
if (!freopen(s, "a", tracefile)) {
fprintf(stderr, "Can't re-open %s\n", s);
debug = 0;
return;
}
} else {
tracefile = fopen(s, "a");
if (tracefile == NULL) {
fprintf(stderr, "Can't open %s\n", s);
debug = 0;
return;
}
}
#ifdef O_APPEND
flags = fcntl(fileno(tracefile), F_GETFL);
if (flags >= 0)
fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
#endif
setlinebuf(tracefile);
fputs("\nTracing started.\n", tracefile);
}
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);
}
}
/* little circular references here... */
static void shtree(union node *n, int ind, char *pfx, FILE *fp);
static void
sharg(union node *arg, FILE *fp)
{
char *p;
struct nodelist *bqlist;
unsigned char subtype;
if (arg->type != NARG) {
out1fmt("<node type %d>\n", arg->type);
abort();
}
bqlist = arg->narg.backquote;
for (p = arg->narg.text; *p; p++) {
switch ((unsigned char)*p) {
case CTLESC:
p++;
putc(*p, fp);
break;
case CTLVAR:
putc('$', fp);
putc('{', fp);
subtype = *++p;
if (subtype == VSLENGTH)
putc('#', fp);
while (*p != '=') {
putc(*p, fp);
p++;
}
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:
out1fmt("<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
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)
putc(' ', fp);
sharg(np, fp);
first = 0;
}
for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
if (!first)
putc(' ', fp);
dftfd = 0;
switch (np->nfile.type) {
case NTO: s = ">>"+1; dftfd = 1; break;
case NCLOBBER: s = ">|"; dftfd = 1; break;
case NAPPEND: s = ">>"; dftfd = 1; break;
#if ENABLE_ASH_BASH_COMPAT
case NTO2:
#endif
case NTOFD: s = ">&"; dftfd = 1; break;
case NFROM: s = "<"; break;
case NFROMFD: s = "<&"; break;
case NFROMTO: s = "<>"; break;
default: s = "*error*"; break;
}
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
shtree(union node *n, int ind, char *pfx, FILE *fp)
{
struct nodelist *lp;
const char *s;
if (n == NULL)
return;
indent(ind, pfx, fp);
if (n == NODE_EOF) {
fputs("<EOF>", fp);
return;
}
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) {
shtree(lp->n, 0, NULL, fp);
if (lp->next)
fputs(" | ", fp);
}
if (n->npipe.pipe_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
showtree(union node *n)
{
trace_puts("showtree called\n");
shtree(n, 1, NULL, stderr);
}
#endif /* DEBUG */
/* ============ Parser data */
/*
* ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
*/
struct strlist {
struct strlist *next;
char *text;
};
struct alias;
struct strpush {
struct strpush *prev; /* preceding string on stack */
char *prev_string;
int prev_left_in_line;
#if ENABLE_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 pf_fd; /* file descriptor (or -1 if string) */
int left_in_line; /* number of chars left in this line */
int left_in_buffer; /* number of chars left in this buffer past the line */
char *next_to_pgetc; /* 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 */
};
static struct parsefile basepf; /* top level input file */
static struct parsefile *g_parsefile = &basepf; /* current input file */
static int startlinno; /* line # where last token started */
static char *commandname; /* currently executing command */
static struct strlist *cmdenviron; /* environment for builtin command */
static uint8_t exitstatus; /* exit status of last command */
/* ============ Message printing */
static void
ash_vmsg(const char *msg, va_list ap)
{
fprintf(stderr, "%s: ", arg0);
if (commandname) {
if (strcmp(arg0, commandname))
fprintf(stderr, "%s: ", commandname);
if (!iflag || g_parsefile->pf_fd > 0)
fprintf(stderr, "line %d: ", startlinno);
}
vfprintf(stderr, msg, ap);
outcslow('\n', stderr);
}
/*
* Exverror is called to raise the error exception. If the second argument
* is not NULL then error prints an error message using printf style
* formatting. It then raises the error exception.
*/
static void ash_vmsg_and_raise(int, const char *, va_list) NORETURN;
static void
ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
{
#if DEBUG
if (msg) {
TRACE(("ash_vmsg_and_raise(%d, \"", cond));
TRACEV((msg, ap));
TRACE(("\") pid=%d\n", getpid()));
} else
TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
if (msg)
#endif
ash_vmsg(msg, ap);
flush_stdout_stderr();
raise_exception(cond);
/* NOTREACHED */
}
static void ash_msg_and_raise_error(const char *, ...) NORETURN;
static void
ash_msg_and_raise_error(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
ash_vmsg_and_raise(EXERROR, msg, ap);
/* NOTREACHED */
va_end(ap);
}
static void raise_error_syntax(const char *) NORETURN;
static void
raise_error_syntax(const char *msg)
{
ash_msg_and_raise_error("syntax error: %s", msg);
/* NOTREACHED */
}
static void ash_msg_and_raise(int, const char *, ...) NORETURN;
static void
ash_msg_and_raise(int cond, const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
ash_vmsg_and_raise(cond, msg, ap);
/* NOTREACHED */
va_end(ap);
}
/*
* error/warning routines for external builtins
*/
static void
ash_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ash_vmsg(fmt, ap);
va_end(ap);
}
/*
* 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, const char *em)
{
if (e == ENOENT || e == ENOTDIR) {
return em;
}
return strerror(e);
}
/* ============ Memory allocation */
#if 0
/* I consider these wrappers nearly useless:
* ok, they return you to nearest exception handler, but
* how much memory do you leak in the process, making
* memory starvation worse?
*/
static void *
ckrealloc(void * p, size_t nbytes)
{
p = realloc(p, nbytes);
if (!p)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
return p;
}
static void *
ckmalloc(size_t nbytes)
{
return ckrealloc(NULL, nbytes);
}
static void *
ckzalloc(size_t nbytes)
{
return memset(ckmalloc(nbytes), 0, nbytes);
}
static char *
ckstrdup(const char *s)
{
char *p = strdup(s);
if (!p)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
return p;
}
#else
/* Using bbox equivalents. They exit if out of memory */
# define ckrealloc xrealloc
# define ckmalloc xmalloc
# define ckzalloc xzalloc
# define ckstrdup xstrdup
#endif
/*
* It appears that grabstackstr() will barf with such alignments
* because stalloc() will return a string allocated in a new stackblock.
*/
#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
enum {
/* Most machines require the value returned from malloc to be aligned
* in some way. The following macro will get this right
* on many machines. */
SHELL_SIZE = sizeof(union { int i; char *cp; double d; }) - 1,
/* Minimum size of a block */
MINSIZE = SHELL_ALIGN(504),
};
struct stack_block {
struct stack_block *prev;
char space[MINSIZE];
};
struct stackmark {
struct stack_block *stackp;
char *stacknxt;
size_t stacknleft;
struct stackmark *marknext;
};
struct globals_memstack {
struct stack_block *g_stackp; // = &stackbase;
struct stackmark *markp;
char *g_stacknxt; // = stackbase.space;
char *sstrend; // = stackbase.space + MINSIZE;
size_t g_stacknleft; // = MINSIZE;
int herefd; // = -1;
struct stack_block stackbase;
};
extern struct globals_memstack *const ash_ptr_to_globals_memstack;
#define G_memstack (*ash_ptr_to_globals_memstack)
#define g_stackp (G_memstack.g_stackp )
#define markp (G_memstack.markp )
#define g_stacknxt (G_memstack.g_stacknxt )
#define sstrend (G_memstack.sstrend )
#define g_stacknleft (G_memstack.g_stacknleft)
#define herefd (G_memstack.herefd )
#define stackbase (G_memstack.stackbase )
#define INIT_G_memstack() do { \
(*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
barrier(); \
g_stackp = &stackbase; \
g_stacknxt = stackbase.space; \
g_stacknleft = MINSIZE; \
sstrend = stackbase.space + MINSIZE; \
herefd = -1; \
} while (0)
#define stackblock() ((void *)g_stacknxt)
#define stackblocksize() g_stacknleft
/*
* 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.
*/
static void *
stalloc(size_t nbytes)
{
char *p;
size_t aligned;
aligned = SHELL_ALIGN(nbytes);
if (aligned > g_stacknleft) {
size_t len;
size_t blocksize;
struct stack_block *sp;
blocksize = aligned;
if (blocksize < MINSIZE)
blocksize = MINSIZE;
len = sizeof(struct stack_block) - MINSIZE + blocksize;
if (len < blocksize)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
INT_OFF;
sp = ckmalloc(len);
sp->prev = g_stackp;
g_stacknxt = sp->space;
g_stacknleft = blocksize;
sstrend = g_stacknxt + blocksize;
g_stackp = sp;
INT_ON;
}
p = g_stacknxt;
g_stacknxt += aligned;
g_stacknleft -= aligned;
return p;
}
static void *
stzalloc(size_t nbytes)
{
return memset(stalloc(nbytes), 0, nbytes);
}
static void
stunalloc(void *p)
{
#if DEBUG
if (!p || (g_stacknxt < (char *)p) || ((char *)p < g_stackp->space)) {
write(STDERR_FILENO, "stunalloc\n", 10);
abort();
}
#endif
g_stacknleft += g_stacknxt - (char *)p;
g_stacknxt = p;
}
/*
* Like strdup but works with the ash stack.
*/
static char *
ststrdup(const char *p)
{
size_t len = strlen(p) + 1;
return memcpy(stalloc(len), p, len);
}
static void
setstackmark(struct stackmark *mark)
{
mark->stackp = g_stackp;
mark->stacknxt = g_stacknxt;
mark->stacknleft = g_stacknleft;
mark->marknext = markp;
markp = mark;
}
static void
popstackmark(struct stackmark *mark)
{
struct stack_block *sp;
if (!mark->stackp)
return;
INT_OFF;
markp = mark->marknext;
while (g_stackp != mark->stackp) {
sp = g_stackp;
g_stackp = sp->prev;
free(sp);
}
g_stacknxt = mark->stacknxt;
g_stacknleft = mark->stacknleft;
sstrend = mark->stacknxt + mark->stacknleft;
INT_ON;
}
/*
* 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)
{
size_t newlen;
newlen = g_stacknleft * 2;
if (newlen < g_stacknleft)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
if (newlen < 128)
newlen += 128;
if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) {
struct stack_block *oldstackp;
struct stackmark *xmark;
struct stack_block *sp;
struct stack_block *prevstackp;
size_t grosslen;
INT_OFF;
oldstackp = g_stackp;
sp = g_stackp;
prevstackp = sp->prev;
grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
sp = ckrealloc(sp, grosslen);
sp->prev = prevstackp;
g_stackp = sp;
g_stacknxt = sp->space;
g_stacknleft = newlen;
sstrend = sp->space + newlen;
/*
* Stack marks pointing to the start of the old block
* must be relocated to point to the new block
*/
xmark = markp;
while (xmark != NULL && xmark->stackp == oldstackp) {
xmark->stackp = g_stackp;
xmark->stacknxt = g_stacknxt;
xmark->stacknleft = g_stacknleft;
xmark = xmark->marknext;
}
INT_ON;
} else {
char *oldspace = g_stacknxt;
size_t oldlen = g_stacknleft;
char *p = stalloc(newlen);
/* free the space we just allocated */
g_stacknxt = memcpy(p, oldspace, oldlen);
g_stacknleft += newlen;
}
}
static void
grabstackblock(size_t len)
{
len = SHELL_ALIGN(len);
g_stacknxt += len;
g_stacknleft -= len;
}
/*
* The following routines are somewhat easier to use than 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 void *
growstackstr(void)
{
size_t len = stackblocksize();
if (herefd >= 0 && len >= 1024) {
full_write(herefd, stackblock(), len);
return stackblock();
}
growstackblock();
return (char *)stackblock() + len;
}
/*
* Called from CHECKSTRSPACE.
*/
static char *
makestrspace(size_t newlen, char *p)
{
size_t len = p - g_stacknxt;
size_t size = stackblocksize();
for (;;) {
size_t nleft;
size = stackblocksize();
nleft = size - len;
if (nleft >= newlen)
break;
growstackblock();
}
return (char *)stackblock() + len;
}
static char *
stack_nputstr(const char *s, size_t n, char *p)
{
p = makestrspace(n, p);
p = (char *)memcpy(p, s, n) + n;
return p;
}
static char *
stack_putstr(const char *s, char *p)
{
return stack_nputstr(s, strlen(s), p);
}
static char *
_STPUTC(int c, char *p)
{
if (p == sstrend)
p = growstackstr();
*p++ = c;
return p;
}
#define STARTSTACKSTR(p) ((p) = stackblock())
#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
#define CHECKSTRSPACE(n, p) do { \
char *q = (p); \
size_t l = (n); \
size_t m = sstrend - q; \
if (l > m) \
(p) = makestrspace(l, q); \
} while (0)
#define USTPUTC(c, p) (*(p)++ = (c))
#define STACKSTRNUL(p) do { \
if ((p) == sstrend) \
(p) = growstackstr(); \
*(p) = '\0'; \
} while (0)
#define STUNPUTC(p) (--(p))
#define STTOPC(p) ((p)[-1])
#define STADJUST(amount, p) ((p) += (amount))
#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock())
#define ungrabstackstr(s, p) stunalloc(s)
#define stackstrend() ((void *)sstrend)
/* ============ String helpers */
/*
* prefix -- see if pfx is a prefix of string.
*/
static char *
prefix(const char *string, const char *pfx)
{
while (*pfx) {
if (*pfx++ != *string++)
return NULL;
}
return (char *) string;
}
/*
* Check for a valid number. This should be elsewhere.
*/
static int
is_number(const char *p)
{
do {
if (!isdigit(*p))
return 0;
} while (*++p != '\0');
return 1;
}
/*
* Convert a string of digits to an integer, printing an error message on
* failure.
*/
static int
number(const char *s)
{
if (!is_number(s))
ash_msg_and_raise_error(msg_illnum, s);
return atoi(s);
}
/*
* 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;
size_t len;
len = strchrnul(s, '\'') - s;
q = p = makestrspace(len + 3, p);
*q++ = '\'';
q = (char *)memcpy(q, s, len) + len;
*q++ = '\'';
s += len;
STADJUST(q - p, p);
if (*s != '\'')
break;
len = 0;
do len++; while (*++s == '\'');
q = p = makestrspace(len + 3, p);
*q++ = '"';
q = (char *)memcpy(q, s - len, len) + len;
*q++ = '"';
STADJUST(q - p, p);
} while (*s);
USTPUTC('\0', p);
return stackblock();
}
/* ============ nextopt */
static char **argptr; /* argument list for builtin commands */
static char *optionarg; /* set by nextopt (like getopt) */
static char *optptr; /* used by nextopt */
/*
* 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 returns the character,
* or '\0' on end of input.
*/
static int
nextopt(const char *optstring)
{
char *p;
const char *q;
char c;
p = optptr;
if (p == NULL || *p == '\0') {
/* We ate entire "-param", take next one */
p = *argptr;
if (p == NULL)
return '\0';
if (*p != '-')
return '\0';
if (*++p == '\0') /* just "-" ? */
return '\0';
argptr++;
if (LONE_DASH(p)) /* "--" ? */
return '\0';
/* p => next "-param" */
}
/* p => some option char in the middle of a "-param" */
c = *p++;
for (q = optstring; *q != c;) {
if (*q == '\0')
ash_msg_and_raise_error("illegal option -%c", c);
if (*++q == ':')
q++;
}
if (*++q == ':') {
if (*p == '\0') {
p = *argptr++;
if (p == NULL)
ash_msg_and_raise_error("no arg for -%c option", c);
}
optionarg = p;
p = NULL;
}
optptr = p;
return c;
}
/* ============ Shell variables */
/*
* The parsefile structure pointed to by the global variable parsefile
* contains information about the current file being read.
*/
struct shparam {
int nparam; /* # of positional parameters (without $0) */
#if ENABLE_ASH_GETOPTS
int optind; /* next parameter to be processed by getopts */
int optoff; /* used by getopts */
#endif
unsigned char malloced; /* if parameter list dynamically allocated */
char **p; /* parameter list */
};
/*
* Free the list of positional parameters.
*/
static void
freeparam(volatile struct shparam *param)
{
if (param->malloced) {
char **ap, **ap1;
ap = ap1 = param->p;
while (*ap)
free(*ap++);
free(ap1);
}
}
#if ENABLE_ASH_GETOPTS
static void FAST_FUNC getoptsreset(const char *value);
#endif
struct var {
struct var *next; /* next entry in hash list */
int flags; /* flags are defined above */
const char *var_text; /* name=value */
void (*var_func)(const char *) FAST_FUNC; /* 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 */
const char *text; /* saved text */
};
/* flags */
#define VEXPORT 0x01 /* variable is exported */
#define VREADONLY 0x02 /* variable cannot be modified */
#define VSTRFIXED 0x04 /* variable struct is statically allocated */
#define VTEXTFIXED 0x08 /* text is statically 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 */
#define VNOSET 0x80 /* do not set variable - just readonly test */
#define VNOSAVE 0x100 /* when text is on the heap before setvareq */
#if ENABLE_ASH_RANDOM_SUPPORT
# define VDYNAMIC 0x200 /* dynamic variable */
#else
# define VDYNAMIC 0
#endif
/* Need to be before varinit_data[] */
#if ENABLE_LOCALE_SUPPORT
static void FAST_FUNC
change_lc_all(const char *value)
{
if (value && *value != '\0')
setlocale(LC_ALL, value);
}
static void FAST_FUNC
change_lc_ctype(const char *value)
{
if (value && *value != '\0')
setlocale(LC_CTYPE, value);
}
#endif
#if ENABLE_ASH_MAIL
static void chkmail(void);
static void changemail(const char *var_value) FAST_FUNC;
#else
# define chkmail() ((void)0)
#endif
static void changepath(const char *) FAST_FUNC;
#if ENABLE_ASH_RANDOM_SUPPORT
static void change_random(const char *) FAST_FUNC;
#endif
static const struct {
int flags;
const char *var_text;
void (*var_func)(const char *) FAST_FUNC;
} varinit_data[] = {
{ VSTRFIXED|VTEXTFIXED , defifsvar , NULL },
#if ENABLE_ASH_MAIL
{ VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL" , changemail },
{ VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH" , changemail },
#endif
{ VSTRFIXED|VTEXTFIXED , bb_PATH_root_path, changepath },
{ VSTRFIXED|VTEXTFIXED , "PS1=$ " , NULL },
{ VSTRFIXED|VTEXTFIXED , "PS2=> " , NULL },
{ VSTRFIXED|VTEXTFIXED , "PS4=+ " , NULL },
#if ENABLE_ASH_GETOPTS
{ VSTRFIXED|VTEXTFIXED , "OPTIND=1" , getoptsreset },
#endif
#if ENABLE_ASH_RANDOM_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
#endif
#if ENABLE_LOCALE_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL" , change_lc_all },
{ VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE" , change_lc_ctype },
#endif
#if ENABLE_FEATURE_EDITING_SAVEHISTORY
{ VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE" , NULL },
#endif
};
struct redirtab;
struct globals_var {
struct shparam shellparam; /* $@ current positional parameters */
struct redirtab *redirlist;
int g_nullredirs;
int preverrout_fd; /* save fd2 before print debug if xflag is set. */
struct var *vartab[VTABSIZE];
struct var varinit[ARRAY_SIZE(varinit_data)];
};
extern struct globals_var *const ash_ptr_to_globals_var;
#define G_var (*ash_ptr_to_globals_var)
#define shellparam (G_var.shellparam )
//#define redirlist (G_var.redirlist )
#define g_nullredirs (G_var.g_nullredirs )
#define preverrout_fd (G_var.preverrout_fd)
#define vartab (G_var.vartab )
#define varinit (G_var.varinit )
#define INIT_G_var() do { \
unsigned i; \
(*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
barrier(); \
for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
varinit[i].flags = varinit_data[i].flags; \
varinit[i].var_text = varinit_data[i].var_text; \
varinit[i].var_func = varinit_data[i].var_func; \
} \
} while (0)
#define vifs varinit[0]
#if ENABLE_ASH_MAIL
# define vmail (&vifs)[1]
# define vmpath (&vmail)[1]
# define vpath (&vmpath)[1]
#else
# define vpath (&vifs)[1]
#endif
#define vps1 (&vpath)[1]
#define vps2 (&vps1)[1]
#define vps4 (&vps2)[1]
#if ENABLE_ASH_GETOPTS
# define voptind (&vps4)[1]
# if ENABLE_ASH_RANDOM_SUPPORT
# define vrandom (&voptind)[1]
# endif
#else
# if ENABLE_ASH_RANDOM_SUPPORT
# define vrandom (&vps4)[1]
# endif
#endif
/*
* 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.var_text + 4)
#define ifsset() ((vifs.flags & VUNSET) == 0)
#if ENABLE_ASH_MAIL
# define mailval() (vmail.var_text + 5)
# define mpathval() (vmpath.var_text + 9)
# define mpathset() ((vmpath.flags & VUNSET) == 0)
#endif
#define pathval() (vpath.var_text + 5)
#define ps1val() (vps1.var_text + 4)
#define ps2val() (vps2.var_text + 4)
#define ps4val() (vps4.var_text + 4)
#if ENABLE_ASH_GETOPTS
# define optindval() (voptind.var_text + 7)
#endif
#if ENABLE_ASH_GETOPTS
static void FAST_FUNC
getoptsreset(const char *value)
{
shellparam.optind = number(value);
shellparam.optoff = -1;
}
#endif
/* math.h has these, otherwise define our private copies */
#if !ENABLE_SH_MATH_SUPPORT
#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
/*
* Return the pointer to the first char which is not part of a legal variable name
* (a letter or underscore followed by letters, underscores, and digits).
*/
static const char*
endofname(const char *name)
{
if (!is_name(*name))
return name;
while (*++name) {
if (!is_in_name(*name))
break;
}
return name;
}
#endif
/*
* Compares two strings up to the first = or '\0'. The first
* string must be terminated by '='; the second may be terminated by
* either '=' or '\0'.
*/
static int
varcmp(const char *p, const char *q)
{
int c, d;
while ((c = *p) == (d = *q)) {
if (!c || c == '=')
goto out;
p++;
q++;
}
if (c == '=')
c = '\0';
if (d == '=')
d = '\0';
out:
return c - d;
}
/*
* Find the appropriate entry in the hash table from the name.
*/
static struct var **
hashvar(const char *p)
{
unsigned hashval;
hashval = ((unsigned char) *p) << 4;
while (*p && *p != '=')
hashval += (unsigned char) *p++;
return &vartab[hashval % VTABSIZE];
}
static int
vpcmp(const void *a, const void *b)
{
return varcmp(*(const char **)a, *(const char **)b);
}
/*
* This routine initializes the builtin variables.
*/
static void
initvar(void)
{
struct var *vp;
struct var *end;
struct var **vpp;
/*
* PS1 depends on uid
*/
#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
vps1.var_text = "PS1=\\w \\$ ";
#else
if (!geteuid())
vps1.var_text = "PS1=# ";
#endif
vp = varinit;
end = vp + ARRAY_SIZE(varinit);
do {
vpp = hashvar(vp->var_text);
vp->next = *vpp;
*vpp = vp;
} while (++vp < end);
}
static struct var **
findvar(struct var **vpp, const char *name)
{
for (; *vpp; vpp = &(*vpp)->next) {
if (varcmp((*vpp)->var_text, name) == 0) {
break;
}
}
return vpp;
}
/*
* Find the value of a variable. Returns NULL if not set.
*/
static const char* FAST_FUNC
lookupvar(const char *name)
{
struct var *v;
v = *findvar(hashvar(name), name);
if (v) {
#if ENABLE_ASH_RANDOM_SUPPORT
/*
* Dynamic variables are implemented roughly the same way they are
* in bash. Namely, they're "special" so long as they aren't unset.
* As soon as they're unset, they're no longer dynamic, and dynamic
* lookup will no longer happen at that point. -- PFM.
*/
if (v->flags & VDYNAMIC)
v->var_func(NULL);
#endif
if (!(v->flags & VUNSET))
return var_end(v->var_text);
}
return NULL;
}
/*
* Search the environment of a builtin command.
*/
static const char *
bltinlookup(const char *name)
{
struct strlist *sp;
for (sp = cmdenviron; sp; sp = sp->next) {
if (varcmp(sp->text, name) == 0)
return var_end(sp->text);
}
return lookupvar(name);
}
/*
* 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.
* Called with interrupts off.
*/
static void
setvareq(char *s, int flags)
{
struct var *vp, **vpp;
vpp = hashvar(s);
flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
vp = *findvar(vpp, s);
if (vp) {
if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
const char *n;
if (flags & VNOSAVE)
free(s);
n = vp->var_text;
ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
}
if (flags & VNOSET)
return;
if (vp->var_func && !(flags & VNOFUNC))
vp->var_func(var_end(s));
if (!(vp->flags & (VTEXTFIXED|VSTACK)))
free((char*)vp->var_text);
flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
} else {
/* variable s is not found */
if (flags & VNOSET)
return;
vp = ckzalloc(sizeof(*vp));
vp->next = *vpp;
/*vp->func = NULL; - ckzalloc did it */
*vpp = vp;
}
if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
s = ckstrdup(s);
vp->var_text = s;
vp->flags = flags;
}
/*
* 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 *q;
char *p;
char *nameeq;
size_t namelen;
size_t vallen;
q = endofname(name);
p = strchrnul(q, '=');
namelen = p - name;
if (!namelen || p != q)
ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
vallen = 0;
if (val == NULL) {
flags |= VUNSET;
} else {
vallen = strlen(val);
}
INT_OFF;
nameeq = ckmalloc(namelen + vallen + 2);
p = memcpy(nameeq, name, namelen) + namelen;
if (val) {
*p++ = '=';
p = memcpy(p, val, vallen) + vallen;
}
*p = '\0';
setvareq(nameeq, flags | VNOSAVE);
INT_ON;
}
static void FAST_FUNC
setvar2(const char *name, const char *val)
{
setvar(name, val, 0);
}
#if ENABLE_ASH_GETOPTS
/*
* Safe version of setvar, returns 1 on success 0 on failure.
*/
static int
setvarsafe(const char *name, const char *val, int flags)
{
int err;
volatile int saveint;
struct jmploc *volatile savehandler = exception_handler;
struct jmploc jmploc;
SAVE_INT(saveint);
if (setjmp(jmploc.loc))
err = 1;
else {
exception_handler = &jmploc;
setvar(name, val, flags);
err = 0;
}
exception_handler = savehandler;
RESTORE_INT(saveint);
return err;
}
#endif
/*
* Unset the specified variable.
*/
static int
unsetvar(const char *s)
{
struct var **vpp;
struct var *vp;
int retval;
vpp = findvar(hashvar(s), s);
vp = *vpp;
retval = 2;
if (vp) {
int flags = vp->flags;
retval = 1;
if (flags & VREADONLY)
goto out;
#if ENABLE_ASH_RANDOM_SUPPORT
vp->flags &= ~VDYNAMIC;
#endif
if (flags & VUNSET)
goto ok;
if ((flags & VSTRFIXED) == 0) {
INT_OFF;
if ((flags & (VTEXTFIXED|VSTACK)) == 0)
free((char*)vp->var_text);
*vpp = vp->next;
free(vp);
INT_ON;
} else {
setvar(s, 0, 0);
vp->flags &= ~VEXPORT;
}
ok:
retval = 0;
}
out:
return retval;
}
/*
* Process a linked list of variable assignments.
*/
static void
listsetvar(struct strlist *list_set_var, int flags)
{
struct strlist *lp = list_set_var;
if (!lp)
return;
INT_OFF;
do {
setvareq(lp->text, flags);
lp = lp->next;
} while (lp);
INT_ON;
}
/*
* Generate a list of variables satisfying the given conditions.
*/
static char **
listvars(int on, int off, char ***end)
{
struct var **vpp;
struct var *vp;
char **ep;
int mask;
STARTSTACKSTR(ep);
vpp = vartab;
mask = on | off;
do {
for (vp = *vpp; vp; vp = vp->next) {
if ((vp->flags & mask) == on) {
if (ep == stackstrend())
ep = growstackstr();
*ep++ = (char*)vp->var_text;
}
}
} while (++vpp < vartab + VTABSIZE);
if (ep == stackstrend())
ep = growstackstr();
if (end)
*end = ep;
*ep++ = NULL;
return grabstackstr(ep);
}
/* ============ Path search helper
*
* The variable path (passed by reference) should be set to the start
* of the path before the first call; path_advance will update
* this value as it proceeds. Successive calls to path_advance 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; /* set by path_advance */
static char *
path_advance(const char **path, const char *name)
{
const char *p;
char *q;
const char *start;
size_t len;
if (*path == NULL)
return NULL;
start = *path;
for (p = start; *p && *p != ':' && *p != '%'; p++)
continue;
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);
}
/* ============ Prompt */
static smallint doprompt; /* if set, prompt the user */
static smallint needprompt; /* true if interactive and at start of line */
#if ENABLE_FEATURE_EDITING
static line_input_t *line_input_state;
static const char *cmdedit_prompt;
static void
putprompt(const char *s)
{
if (ENABLE_ASH_EXPAND_PRMT) {
free((char*)cmdedit_prompt);
cmdedit_prompt = ckstrdup(s);
return;
}
cmdedit_prompt = s;
}
#else
static void
putprompt(const char *s)
{
out2str(s);
}
#endif
#if ENABLE_ASH_EXPAND_PRMT
/* expandstr() needs parsing machinery, so it is far away ahead... */
static const char *expandstr(const char *ps);
#else
#define expandstr(s) s
#endif
static void
setprompt_if(smallint do_set, int whichprompt)
{
const char *prompt;
IF_ASH_EXPAND_PRMT(struct stackmark smark;)
if (!do_set)
return;
needprompt = 0;
switch (whichprompt) {
case 1:
prompt = ps1val();
break;
case 2:
prompt = ps2val();
break;
default: /* 0 */
prompt = nullstr;
}
#if ENABLE_ASH_EXPAND_PRMT
setstackmark(&smark);
stalloc(stackblocksize());
#endif
putprompt(expandstr(prompt));
#if ENABLE_ASH_EXPAND_PRMT
popstackmark(&smark);
#endif
}
/* ============ The cd and pwd commands */
#define CD_PHYSICAL 1
#define CD_PRINT 2
static int
cdopt(void)
{
int flags = 0;
int i, j;
j = 'L';
while ((i = nextopt("LP")) != '\0') {
if (i != j) {
flags ^= CD_PHYSICAL;
j = i;
}
}
return flags;
}
/*
* Update curdir (the name of the current directory) in response to a
* cd command.
*/
static const char *
updatepwd(const char *dir)
{
char *new;
char *p;
char *cdcomppath;
const char *lim;
cdcomppath = ststrdup(dir);
STARTSTACKSTR(new);
if (*dir != '/') {
if (curdir == nullstr)
return 0;
new = stack_putstr(curdir, new);
}
new = makestrspace(strlen(dir) + 2, new);
lim = (char *)stackblock() + 1;
if (*dir != '/') {
if (new[-1] != '/')
USTPUTC('/', new);
if (new > lim && *lim == '/')
lim++;
} else {
USTPUTC('/', new);
cdcomppath++;
if (dir[1] == '/' && dir[2] != '/') {
USTPUTC('/', new);
cdcomppath++;
lim++;
}
}
p = strtok(cdcomppath, "/");
while (p) {
switch (*p) {
case '.':
if (p[1] == '.' && p[2] == '\0') {
while (new > lim) {
STUNPUTC(new);
if (new[-1] == '/')
break;
}
break;
}
if (p[1] == '\0')
break;
/* fall through */
default:
new = stack_putstr(p, new);
USTPUTC('/', new);
}
p = strtok(0, "/");
}
if (new > lim)
STUNPUTC(new);
*new = 0;
return stackblock();
}
/*
* Find out what the current directory is. If we already know the current
* directory, this routine returns immediately.
*/
static char *
getpwd(void)
{
char *dir = getcwd(NULL, 0); /* huh, using glibc extension? */
return dir ? dir : nullstr;
}
static void
setpwd(const char *val, int setold)
{
char *oldcur, *dir;
oldcur = dir = curdir;
if (setold) {
setvar("OLDPWD", oldcur, VEXPORT);
}
INT_OFF;
if (physdir != nullstr) {
if (physdir != oldcur)
free(physdir);
physdir = nullstr;
}
if (oldcur == val || !val) {
char *s = getpwd();
physdir = s;
if (!val)
dir = s;
} else
dir = ckstrdup(val);
if (oldcur != dir && oldcur != nullstr) {
free(oldcur);
}
curdir = dir;
INT_ON;
setvar("PWD", dir, VEXPORT);
}
static void hashcd(void);
/*
* Actually do the chdir. We also call hashcd to let the routines in exec.c
* know that the current directory has changed.
*/
static int
docd(const char *dest, int flags)
{
const char *dir = NULL;
int err;
TRACE(("docd(\"%s\", %d) called\n", dest, flags));
INT_OFF;
if (!(flags & CD_PHYSICAL)) {
dir = updatepwd(dest);
if (dir)
dest = dir;
}
err = chdir(dest);
if (err)
goto out;
setpwd(dir, 1);
hashcd();
out:
INT_ON;
return err;
}
static int FAST_FUNC
cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
const char *dest;
const char *path;
const char *p;
char c;
struct stat statb;
int flags;
flags = cdopt();
dest = *argptr;
if (!dest)
dest = bltinlookup("HOME");
else if (LONE_DASH(dest)) {
dest = bltinlookup("OLDPWD");
flags |= CD_PRINT;
}
if (!dest)
dest = nullstr;
if (*dest == '/')
goto step7;
if (*dest == '.') {
c = dest[1];
dotdot:
switch (c) {
case '\0':
case '/':
goto step6;
case '.':
c = dest[2];
if (c != '.')
goto dotdot;
}
}
if (!*dest)
dest = ".";
path = bltinlookup("CDPATH");
if (!path) {
step6:
step7:
p = dest;
goto docd;
}
do {
c = *path;
p = path_advance(&path, dest);
if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
if (c && c != ':')
flags |= CD_PRINT;
docd:
if (!docd(p, flags))
goto out;
break;
}
} while (path);
ash_msg_and_raise_error("can't cd to %s", dest);
/* NOTREACHED */
out:
if (flags & CD_PRINT)
out1fmt("%s\n", curdir);
return 0;
}
static int FAST_FUNC
pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
int flags;
const char *dir = curdir;
flags = cdopt();
if (flags) {
if (physdir == nullstr)
setpwd(dir, 0);
dir = physdir;
}
out1fmt("%s\n", dir);
return 0;
}
/* ============ ... */
#define IBUFSIZ (ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 1024)
/* 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 PEOF 256
#if ENABLE_ASH_ALIAS
# define PEOA 257
#endif
#define USE_SIT_FUNCTION ENABLE_ASH_OPTIMIZE_FOR_SIZE
#if ENABLE_SH_MATH_SUPPORT
# define SIT_ITEM(a,b,c,d) (a | (b << 4) | (c << 8) | (d << 12))
#else
# define SIT_ITEM(a,b,c,d) (a | (b << 4) | (c << 8))
#endif
static const uint16_t S_I_T[] = {
#if ENABLE_ASH_ALIAS
SIT_ITEM(CSPCL , CIGN , CIGN , CIGN ), /* 0, PEOA */
#endif
SIT_ITEM(CSPCL , CWORD , CWORD, CWORD ), /* 1, ' ' */
SIT_ITEM(CNL , CNL , CNL , CNL ), /* 2, \n */
SIT_ITEM(CWORD , CCTL , CCTL , CWORD ), /* 3, !*-/:=?[]~ */
SIT_ITEM(CDQUOTE , CENDQUOTE, CWORD, CWORD ), /* 4, '"' */
SIT_ITEM(CVAR , CVAR , CWORD, CVAR ), /* 5, $ */
SIT_ITEM(CSQUOTE , CWORD , CENDQUOTE, CWORD), /* 6, "'" */
SIT_ITEM(CSPCL , CWORD , CWORD, CLP ), /* 7, ( */
SIT_ITEM(CSPCL , CWORD , CWORD, CRP ), /* 8, ) */
SIT_ITEM(CBACK , CBACK , CCTL , CBACK ), /* 9, \ */
SIT_ITEM(CBQUOTE , CBQUOTE , CWORD, CBQUOTE), /* 10, ` */
SIT_ITEM(CENDVAR , CENDVAR , CWORD, CENDVAR), /* 11, } */
#if !USE_SIT_FUNCTION
SIT_ITEM(CENDFILE, CENDFILE , CENDFILE, CENDFILE),/* 12, PEOF */
SIT_ITEM(CWORD , CWORD , CWORD, CWORD ), /* 13, 0-9A-Za-z */
SIT_ITEM(CCTL , CCTL , CCTL , CCTL ) /* 14, CTLESC ... */
#endif
#undef SIT_ITEM
};
/* Constants below must match table above */
enum {
#if ENABLE_ASH_ALIAS
CSPCL_CIGN_CIGN_CIGN , /* 0 */
#endif
CSPCL_CWORD_CWORD_CWORD , /* 1 */
CNL_CNL_CNL_CNL , /* 2 */
CWORD_CCTL_CCTL_CWORD , /* 3 */
CDQUOTE_CENDQUOTE_CWORD_CWORD , /* 4 */
CVAR_CVAR_CWORD_CVAR , /* 5 */
CSQUOTE_CWORD_CENDQUOTE_CWORD , /* 6 */
CSPCL_CWORD_CWORD_CLP , /* 7 */
CSPCL_CWORD_CWORD_CRP , /* 8 */
CBACK_CBACK_CCTL_CBACK , /* 9 */
CBQUOTE_CBQUOTE_CWORD_CBQUOTE , /* 10 */
CENDVAR_CENDVAR_CWORD_CENDVAR , /* 11 */
CENDFILE_CENDFILE_CENDFILE_CENDFILE, /* 12 */
CWORD_CWORD_CWORD_CWORD , /* 13 */
CCTL_CCTL_CCTL_CCTL , /* 14 */
};
/* c in SIT(c, syntax) must be an *unsigned char* or PEOA or PEOF,
* caller must ensure proper cast on it if c is *char_ptr!
*/
/* Values for syntax param */
#define BASESYNTAX 0 /* not in quotes */
#define DQSYNTAX 1 /* in double quotes */
#define SQSYNTAX 2 /* in single quotes */
#define ARISYNTAX 3 /* in arithmetic */
#define PSSYNTAX 4 /* prompt. never passed to SIT() */
#if USE_SIT_FUNCTION
static int
SIT(int c, int syntax)
{
static const char spec_symbls[] ALIGN1 = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
# if ENABLE_ASH_ALIAS
static const uint8_t syntax_index_table[] ALIGN1 = {
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 /* "}~" */
};
# else
static const uint8_t syntax_index_table[] ALIGN1 = {
0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */
6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */
2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */
10, 2 /* "}~" */
};
# endif
const char *s;
int indx;
if (c == PEOF)
return CENDFILE;
# if ENABLE_ASH_ALIAS
if (c == PEOA)
indx = 0;
else
# endif
{
/* Cast is purely for paranoia here,
* just in case someone passed signed char to us */
if ((unsigned char)c >= CTL_FIRST
&& (unsigned char)c <= CTL_LAST
) {
return CCTL;
}
s = strchrnul(spec_symbls, c);
if (*s == '\0')
return CWORD;
indx = syntax_index_table[s - spec_symbls];
}
return (S_I_T[indx] >> (syntax*4)) & 0xf;
}
#else /* !USE_SIT_FUNCTION */
static const uint8_t syntax_index_table[] = {
/* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */
/* 0 */ CWORD_CWORD_CWORD_CWORD,
/* 1 */ CWORD_CWORD_CWORD_CWORD,
/* 2 */ CWORD_CWORD_CWORD_CWORD,
/* 3 */ CWORD_CWORD_CWORD_CWORD,
/* 4 */ CWORD_CWORD_CWORD_CWORD,
/* 5 */ CWORD_CWORD_CWORD_CWORD,
/* 6 */ CWORD_CWORD_CWORD_CWORD,
/* 7 */ CWORD_CWORD_CWORD_CWORD,
/* 8 */ CWORD_CWORD_CWORD_CWORD,
/* 9 "\t" */ CSPCL_CWORD_CWORD_CWORD,
/* 10 "\n" */ CNL_CNL_CNL_CNL,
/* 11 */ CWORD_CWORD_CWORD_CWORD,
/* 12 */ CWORD_CWORD_CWORD_CWORD,
/* 13 */ CWORD_CWORD_CWORD_CWORD,
/* 14 */ CWORD_CWORD_CWORD_CWORD,
/* 15 */ CWORD_CWORD_CWORD_CWORD,
/* 16 */ CWORD_CWORD_CWORD_CWORD,
/* 17 */ CWORD_CWORD_CWORD_CWORD,
/* 18 */ CWORD_CWORD_CWORD_CWORD,
/* 19 */ CWORD_CWORD_CWORD_CWORD,
/* 20 */ CWORD_CWORD_CWORD_CWORD,
/* 21 */ CWORD_CWORD_CWORD_CWORD,
/* 22 */ CWORD_CWORD_CWORD_CWORD,
/* 23 */ CWORD_CWORD_CWORD_CWORD,
/* 24 */ CWORD_CWORD_CWORD_CWORD,
/* 25 */ CWORD_CWORD_CWORD_CWORD,
/* 26 */ CWORD_CWORD_CWORD_CWORD,
/* 27 */ CWORD_CWORD_CWORD_CWORD,
/* 28 */ CWORD_CWORD_CWORD_CWORD,
/* 29 */ CWORD_CWORD_CWORD_CWORD,
/* 30 */ CWORD_CWORD_CWORD_CWORD,
/* 31 */ CWORD_CWORD_CWORD_CWORD,
/* 32 " " */ CSPCL_CWORD_CWORD_CWORD,
/* 33 "!" */ CWORD_CCTL_CCTL_CWORD,
/* 34 """ */ CDQUOTE_CENDQUOTE_CWORD_CWORD,
/* 35 "#" */ CWORD_CWORD_CWORD_CWORD,
/* 36 "$" */ CVAR_CVAR_CWORD_CVAR,
/* 37 "%" */ CWORD_CWORD_CWORD_CWORD,
/* 38 "&" */ CSPCL_CWORD_CWORD_CWORD,
/* 39 "'" */ CSQUOTE_CWORD_CENDQUOTE_CWORD,
/* 40 "(" */ CSPCL_CWORD_CWORD_CLP,
/* 41 ")" */ CSPCL_CWORD_CWORD_CRP,
/* 42 "*" */ CWORD_CCTL_CCTL_CWORD,
/* 43 "+" */ CWORD_CWORD_CWORD_CWORD,
/* 44 "," */ CWORD_CWORD_CWORD_CWORD,
/* 45 "-" */ CWORD_CCTL_CCTL_CWORD,
/* 46 "." */ CWORD_CWORD_CWORD_CWORD,
/* 47 "/" */ CWORD_CCTL_CCTL_CWORD,
/* 48 "0" */ CWORD_CWORD_CWORD_CWORD,
/* 49 "1" */ CWORD_CWORD_CWORD_CWORD,
/* 50 "2" */ CWORD_CWORD_CWORD_CWORD,
/* 51 "3" */ CWORD_CWORD_CWORD_CWORD,
/* 52 "4" */ CWORD_CWORD_CWORD_CWORD,
/* 53 "5" */ CWORD_CWORD_CWORD_CWORD,
/* 54 "6" */ CWORD_CWORD_CWORD_CWORD,
/* 55 "7" */ CWORD_CWORD_CWORD_CWORD,
/* 56 "8" */ CWORD_CWORD_CWORD_CWORD,
/* 57 "9" */ CWORD_CWORD_CWORD_CWORD,
/* 58 ":" */ CWORD_CCTL_CCTL_CWORD,
/* 59 ";" */ CSPCL_CWORD_CWORD_CWORD,
/* 60 "<" */ CSPCL_CWORD_CWORD_CWORD,
/* 61 "=" */ CWORD_CCTL_CCTL_CWORD,
/* 62 ">" */ CSPCL_CWORD_CWORD_CWORD,
/* 63 "?" */ CWORD_CCTL_CCTL_CWORD,
/* 64 "@" */ CWORD_CWORD_CWORD_CWORD,
/* 65 "A" */ CWORD_CWORD_CWORD_CWORD,
/* 66 "B" */ CWORD_CWORD_CWORD_CWORD,
/* 67 "C" */ CWORD_CWORD_CWORD_CWORD,
/* 68 "D" */ CWORD_CWORD_CWORD_CWORD,
/* 69 "E" */ CWORD_CWORD_CWORD_CWORD,
/* 70 "F" */ CWORD_CWORD_CWORD_CWORD,
/* 71 "G" */ CWORD_CWORD_CWORD_CWORD,
/* 72 "H" */ CWORD_CWORD_CWORD_CWORD,
/* 73 "I" */ CWORD_CWORD_CWORD_CWORD,
/* 74 "J" */ CWORD_CWORD_CWORD_CWORD,
/* 75 "K" */ CWORD_CWORD_CWORD_CWORD,
/* 76 "L" */ CWORD_CWORD_CWORD_CWORD,
/* 77 "M" */ CWORD_CWORD_CWORD_CWORD,
/* 78 "N" */ CWORD_CWORD_CWORD_CWORD,
/* 79 "O" */ CWORD_CWORD_CWORD_CWORD,
/* 80 "P" */ CWORD_CWORD_CWORD_CWORD,
/* 81 "Q" */ CWORD_CWORD_CWORD_CWORD,
/* 82 "R" */ CWORD_CWORD_CWORD_CWORD,
/* 83 "S" */ CWORD_CWORD_CWORD_CWORD,
/* 84 "T" */ CWORD_CWORD_CWORD_CWORD,
/* 85 "U" */ CWORD_CWORD_CWORD_CWORD,
/* 86 "V" */ CWORD_CWORD_CWORD_CWORD,
/* 87 "W" */ CWORD_CWORD_CWORD_CWORD,
/* 88 "X" */ CWORD_CWORD_CWORD_CWORD,
/* 89 "Y" */ CWORD_CWORD_CWORD_CWORD,
/* 90 "Z" */ CWORD_CWORD_CWORD_CWORD,
/* 91 "[" */ CWORD_CCTL_CCTL_CWORD,
/* 92 "\" */ CBACK_CBACK_CCTL_CBACK,
/* 93 "]" */ CWORD_CCTL_CCTL_CWORD,
/* 94 "^" */ CWORD_CWORD_CWORD_CWORD,
/* 95 "_" */ CWORD_CWORD_CWORD_CWORD,
/* 96 "`" */ CBQUOTE_CBQUOTE_CWORD_CBQUOTE,
/* 97 "a" */ CWORD_CWORD_CWORD_CWORD,
/* 98 "b" */ CWORD_CWORD_CWORD_CWORD,
/* 99 "c" */ CWORD_CWORD_CWORD_CWORD,
/* 100 "d" */ CWORD_CWORD_CWORD_CWORD,
/* 101 "e" */ CWORD_CWORD_CWORD_CWORD,
/* 102 "f" */ CWORD_CWORD_CWORD_CWORD,
/* 103 "g" */ CWORD_CWORD_CWORD_CWORD,
/* 104 "h" */ CWORD_CWORD_CWORD_CWORD,
/* 105 "i" */ CWORD_CWORD_CWORD_CWORD,
/* 106 "j" */ CWORD_CWORD_CWORD_CWORD,
/* 107 "k" */ CWORD_CWORD_CWORD_CWORD,
/* 108 "l" */ CWORD_CWORD_CWORD_CWORD,
/* 109 "m" */ CWORD_CWORD_CWORD_CWORD,
/* 110 "n" */ CWORD_CWORD_CWORD_CWORD,
/* 111 "o" */ CWORD_CWORD_CWORD_CWORD,
/* 112 "p" */ CWORD_CWORD_CWORD_CWORD,
/* 113 "q" */ CWORD_CWORD_CWORD_CWORD,
/* 114 "r" */ CWORD_CWORD_CWORD_CWORD,
/* 115 "s" */ CWORD_CWORD_CWORD_CWORD,
/* 116 "t" */ CWORD_CWORD_CWORD_CWORD,
/* 117 "u" */ CWORD_CWORD_CWORD_CWORD,
/* 118 "v" */ CWORD_CWORD_CWORD_CWORD,
/* 119 "w" */ CWORD_CWORD_CWORD_CWORD,
/* 120 "x" */ CWORD_CWORD_CWORD_CWORD,
/* 121 "y" */ CWORD_CWORD_CWORD_CWORD,
/* 122 "z" */ CWORD_CWORD_CWORD_CWORD,
/* 123 "{" */ CWORD_CWORD_CWORD_CWORD,
/* 124 "|" */ CSPCL_CWORD_CWORD_CWORD,
/* 125 "}" */ CENDVAR_CENDVAR_CWORD_CENDVAR,
/* 126 "~" */ CWORD_CCTL_CCTL_CWORD,
/* 127 del */ CWORD_CWORD_CWORD_CWORD,
/* 128 0x80 */ CWORD_CWORD_CWORD_CWORD,
/* 129 CTLESC */ CCTL_CCTL_CCTL_CCTL,
/* 130 CTLVAR */ CCTL_CCTL_CCTL_CCTL,
/* 131 CTLENDVAR */ CCTL_CCTL_CCTL_CCTL,
/* 132 CTLBACKQ */ CCTL_CCTL_CCTL_CCTL,
/* 133 CTLQUOTE */ CCTL_CCTL_CCTL_CCTL,
/* 134 CTLARI */ CCTL_CCTL_CCTL_CCTL,
/* 135 CTLENDARI */ CCTL_CCTL_CCTL_CCTL,
/* 136 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL,
/* 137 */ CWORD_CWORD_CWORD_CWORD,
/* 138 */ CWORD_CWORD_CWORD_CWORD,
/* 139 */ CWORD_CWORD_CWORD_CWORD,
/* 140 */ CWORD_CWORD_CWORD_CWORD,
/* 141 */ CWORD_CWORD_CWORD_CWORD,
/* 142 */ CWORD_CWORD_CWORD_CWORD,
/* 143 */ CWORD_CWORD_CWORD_CWORD,
/* 144 */ CWORD_CWORD_CWORD_CWORD,
/* 145 */ CWORD_CWORD_CWORD_CWORD,
/* 146 */ CWORD_CWORD_CWORD_CWORD,
/* 147 */ CWORD_CWORD_CWORD_CWORD,
/* 148 */ CWORD_CWORD_CWORD_CWORD,
/* 149 */ CWORD_CWORD_CWORD_CWORD,
/* 150 */ CWORD_CWORD_CWORD_CWORD,
/* 151 */ CWORD_CWORD_CWORD_CWORD,
/* 152 */ CWORD_CWORD_CWORD_CWORD,
/* 153 */ CWORD_CWORD_CWORD_CWORD,
/* 154 */ CWORD_CWORD_CWORD_CWORD,
/* 155 */ CWORD_CWORD_CWORD_CWORD,
/* 156 */ CWORD_CWORD_CWORD_CWORD,
/* 157 */ CWORD_CWORD_CWORD_CWORD,
/* 158 */ CWORD_CWORD_CWORD_CWORD,
/* 159 */ CWORD_CWORD_CWORD_CWORD,
/* 160 */ CWORD_CWORD_CWORD_CWORD,
/* 161 */ CWORD_CWORD_CWORD_CWORD,
/* 162 */ CWORD_CWORD_CWORD_CWORD,
/* 163 */ CWORD_CWORD_CWORD_CWORD,
/* 164 */ CWORD_CWORD_CWORD_CWORD,
/* 165 */ CWORD_CWORD_CWORD_CWORD,
/* 166 */ CWORD_CWORD_CWORD_CWORD,
/* 167 */ CWORD_CWORD_CWORD_CWORD,
/* 168 */ CWORD_CWORD_CWORD_CWORD,
/* 169 */ CWORD_CWORD_CWORD_CWORD,
/* 170 */ CWORD_CWORD_CWORD_CWORD,
/* 171 */ CWORD_CWORD_CWORD_CWORD,
/* 172 */ CWORD_CWORD_CWORD_CWORD,
/* 173 */ CWORD_CWORD_CWORD_CWORD,
/* 174 */ CWORD_CWORD_CWORD_CWORD,
/* 175 */ CWORD_CWORD_CWORD_CWORD,
/* 176 */ CWORD_CWORD_CWORD_CWORD,
/* 177 */ CWORD_CWORD_CWORD_CWORD,
/* 178 */ CWORD_CWORD_CWORD_CWORD,
/* 179 */ CWORD_CWORD_CWORD_CWORD,
/* 180 */ CWORD_CWORD_CWORD_CWORD,
/* 181 */ CWORD_CWORD_CWORD_CWORD,
/* 182 */ CWORD_CWORD_CWORD_CWORD,
/* 183 */ CWORD_CWORD_CWORD_CWORD,
/* 184 */ CWORD_CWORD_CWORD_CWORD,
/* 185 */ CWORD_CWORD_CWORD_CWORD,
/* 186 */ CWORD_CWORD_CWORD_CWORD,
/* 187 */ CWORD_CWORD_CWORD_CWORD,
/* 188 */ CWORD_CWORD_CWORD_CWORD,
/* 189 */ CWORD_CWORD_CWORD_CWORD,
/* 190 */ CWORD_CWORD_CWORD_CWORD,
/* 191 */ CWORD_CWORD_CWORD_CWORD,
/* 192 */ CWORD_CWORD_CWORD_CWORD,
/* 193 */ CWORD_CWORD_CWORD_CWORD,
/* 194 */ CWORD_CWORD_CWORD_CWORD,
/* 195 */ CWORD_CWORD_CWORD_CWORD,
/* 196 */ CWORD_CWORD_CWORD_CWORD,
/* 197 */ CWORD_CWORD_CWORD_CWORD,
/* 198 */ CWORD_CWORD_CWORD_CWORD,
/* 199 */ CWORD_CWORD_CWORD_CWORD,
/* 200 */ CWORD_CWORD_CWORD_CWORD,
/* 201 */ CWORD_CWORD_CWORD_CWORD,
/* 202 */ CWORD_CWORD_CWORD_CWORD,
/* 203 */ CWORD_CWORD_CWORD_CWORD,
/* 204 */ CWORD_CWORD_CWORD_CWORD,
/* 205 */ CWORD_CWORD_CWORD_CWORD,
/* 206 */ CWORD_CWORD_CWORD_CWORD,
/* 207 */ CWORD_CWORD_CWORD_CWORD,
/* 208 */ CWORD_CWORD_CWORD_CWORD,
/* 209 */ CWORD_CWORD_CWORD_CWORD,
/* 210 */ CWORD_CWORD_CWORD_CWORD,
/* 211 */ CWORD_CWORD_CWORD_CWORD,
/* 212 */ CWORD_CWORD_CWORD_CWORD,
/* 213 */ CWORD_CWORD_CWORD_CWORD,
/* 214 */ CWORD_CWORD_CWORD_CWORD,
/* 215 */ CWORD_CWORD_CWORD_CWORD,
/* 216 */ CWORD_CWORD_CWORD_CWORD,
/* 217 */ CWORD_CWORD_CWORD_CWORD,
/* 218 */ CWORD_CWORD_CWORD_CWORD,
/* 219 */ CWORD_CWORD_CWORD_CWORD,
/* 220 */ CWORD_CWORD_CWORD_CWORD,
/* 221 */ CWORD_CWORD_CWORD_CWORD,
/* 222 */ CWORD_CWORD_CWORD_CWORD,
/* 223 */ CWORD_CWORD_CWORD_CWORD,
/* 224 */ CWORD_CWORD_CWORD_CWORD,
/* 225 */ CWORD_CWORD_CWORD_CWORD,
/* 226 */ CWORD_CWORD_CWORD_CWORD,
/* 227 */ CWORD_CWORD_CWORD_CWORD,
/* 228 */ CWORD_CWORD_CWORD_CWORD,
/* 229 */ CWORD_CWORD_CWORD_CWORD,
/* 230 */ CWORD_CWORD_CWORD_CWORD,
/* 231 */ CWORD_CWORD_CWORD_CWORD,
/* 232 */ CWORD_CWORD_CWORD_CWORD,
/* 233 */ CWORD_CWORD_CWORD_CWORD,
/* 234 */ CWORD_CWORD_CWORD_CWORD,
/* 235 */ CWORD_CWORD_CWORD_CWORD,
/* 236 */ CWORD_CWORD_CWORD_CWORD,
/* 237 */ CWORD_CWORD_CWORD_CWORD,
/* 238 */ CWORD_CWORD_CWORD_CWORD,
/* 239 */ CWORD_CWORD_CWORD_CWORD,
/* 230 */ CWORD_CWORD_CWORD_CWORD,
/* 241 */ CWORD_CWORD_CWORD_CWORD,
/* 242 */ CWORD_CWORD_CWORD_CWORD,
/* 243 */ CWORD_CWORD_CWORD_CWORD,
/* 244 */ CWORD_CWORD_CWORD_CWORD,
/* 245 */ CWORD_CWORD_CWORD_CWORD,
/* 246 */ CWORD_CWORD_CWORD_CWORD,
/* 247 */ CWORD_CWORD_CWORD_CWORD,
/* 248 */ CWORD_CWORD_CWORD_CWORD,
/* 249 */ CWORD_CWORD_CWORD_CWORD,
/* 250 */ CWORD_CWORD_CWORD_CWORD,
/* 251 */ CWORD_CWORD_CWORD_CWORD,
/* 252 */ CWORD_CWORD_CWORD_CWORD,
/* 253 */ CWORD_CWORD_CWORD_CWORD,
/* 254 */ CWORD_CWORD_CWORD_CWORD,
/* 255 */ CWORD_CWORD_CWORD_CWORD,
/* PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE,
# if ENABLE_ASH_ALIAS
/* PEOA */ CSPCL_CIGN_CIGN_CIGN,
# endif
};
# define SIT(c, syntax) ((S_I_T[syntax_index_table[c]] >> ((syntax)*4)) & 0xf)
#endif /* !USE_SIT_FUNCTION */
/* ============ Alias handling */
#if ENABLE_ASH_ALIAS
#define ALIASINUSE 1
#define ALIASDEAD 2
struct alias {
struct alias *next;
char *name;
char *val;
int flag;
};
static struct alias **atab; // [ATABSIZE];
#define INIT_G_alias() do { \
atab = xzalloc(ATABSIZE * sizeof(atab[0])); \
} while (0)
static struct alias **
__lookupalias(const char *name) {
unsigned int hashval;
struct alias **app;
const char *p;
unsigned int ch;
p = name;
ch = (unsigned char)*p;
hashval = ch << 4;
while (ch) {
hashval += ch;
ch = (unsigned char)*++p;
}
app = &atab[hashval % ATABSIZE];
for (; *app; app = &(*app)->next) {
if (strcmp(name, (*app)->name) == 0) {
break;
}
}
return app;
}
static struct alias *
lookupalias(const char *name, int check)
{
struct alias *ap = *__lookupalias(name);
if (check && ap && (ap->flag & ALIASINUSE))
return NULL;
return ap;
}
static struct alias *
freealias(struct alias *ap)
{
struct alias *next;
if (ap->flag & ALIASINUSE) {
ap->flag |= ALIASDEAD;
return ap;
}
next = ap->next;
free(ap->name);
free(ap->val);
free(ap);
return next;
}
static void
setalias(const char *name, const char *val)
{
struct alias *ap, **app;
app = __lookupalias(name);
ap = *app;
INT_OFF;
if (ap) {
if (!(ap->flag & ALIASINUSE)) {
free(ap->val);
}
ap->val = ckstrdup(val);
ap->flag &= ~ALIASDEAD;
} else {
/* not found */
ap = ckzalloc(sizeof(struct alias));
ap->name = ckstrdup(name);
ap->val = ckstrdup(val);
/*ap->flag = 0; - ckzalloc did it */
/*ap->next = NULL;*/
*app = ap;
}
INT_ON;
}
static int
unalias(const char *name)
{
struct alias **app;
app = __lookupalias(name);
if (*app) {
INT_OFF;
*app = freealias(*app);
INT_ON;
return 0;
}
return 1;
}
static void
rmaliases(void)
{
struct alias *ap, **app;
int i;
INT_OFF;
for (i = 0; i < ATABSIZE; i++) {
app = &atab[i];
for (ap = *app; ap; ap = *app) {
*app = freealias(*app);
if (ap == *app) {
app = &ap->next;
}
}
}
INT_ON;
}
static void
printalias(const struct alias *ap)
{
out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
}
/*
* TODO - sort output
*/
static int FAST_FUNC
aliascmd(int argc UNUSED_PARAM, char **argv)
{
char *n, *v;
int ret = 0;
struct alias *ap;
if (!argv[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) {
v = strchr(n+1, '=');
if (v == NULL) { /* n+1: funny ksh stuff */
ap = *__lookupalias(n);
if (ap == NULL) {
fprintf(stderr, "%s: %s not found\n", "alias", n);
ret = 1;
} else
printalias(ap);
} else {
*v++ = '\0';
setalias(n, v);
}
}
return ret;
}
static int FAST_FUNC
unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
int i;
while ((i = nextopt("a")) != '\0') {
if (i == 'a') {
rmaliases();
return 0;
}
}
for (i = 0; *argptr; argptr++) {
if (unalias(*argptr)) {
fprintf(stderr, "%s: %s not found\n", "unalias", *argptr);
i = 1;
}
}
return i;
}
#endif /* ASH_ALIAS */
/* ============ jobs.c */
/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
#define FORK_FG 0
#define FORK_BG 1
#define FORK_NOJOB 2
/* mode flags for showjob(s) */
#define SHOW_ONLY_PGID 0x01 /* show only pgid (jobs -p) */
#define SHOW_PIDS 0x02 /* show individual pids, not just one line per job */
#define SHOW_CHANGED 0x04 /* only jobs whose state has changed */
/*
* 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 ps_pid; /* process id */
int ps_status; /* last process status from wait() */
char *ps_cmd; /* text of command being run */
};
struct job {
struct procstat ps0; /* status of process */
struct procstat *ps; /* status or processes when more than one */
#if JOBS
int stopstatus; /* status of a stopped job */
#endif
uint32_t
nprocs: 16, /* number of processes */
state: 8,
#define JOBRUNNING 0 /* at least one proc running */
#define JOBSTOPPED 1 /* all procs are stopped */
#define JOBDONE 2 /* all procs are completed */
#if JOBS
sigint: 1, /* job was killed by SIGINT */
jobctl: 1, /* job running under job control */
#endif
waited: 1, /* true if this entry has been waited for */
used: 1, /* true if this entry is in used */
changed: 1; /* true if status has changed */
struct job *prev_job; /* previous job */
};
static struct job *makejob(/*union node *,*/ int);
static int forkshell(struct job *, union node *, int);
static int waitforjob(struct job *);
#if !JOBS
enum { doing_jobctl = 0 };
#define setjobctl(on) do {} while (0)
#else
static smallint doing_jobctl; //references:8
static void setjobctl(int);
#endif
/*
* Ignore a signal.
*/
static void
ignoresig(int signo)
{
/* Avoid unnecessary system calls. Is it already SIG_IGNed? */
if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
/* No, need to do it */
signal(signo, SIG_IGN);
}
sigmode[signo - 1] = S_HARD_IGN;
}
/*
* Only one usage site - in setsignal()
*/
static void
signal_handler(int signo)
{
gotsig[signo - 1] = 1;
if (signo == SIGINT && !trap[SIGINT]) {
if (!suppress_int) {
pending_sig = 0;
raise_interrupt(); /* does not return */
}
pending_int = 1;
} else {
pending_sig = signo;
}
}
/*
* Set the signal handler for the specified signal. The routine figures
* out what it should be set to.
*/
static void
setsignal(int signo)
{
char *t;
char cur_act, new_act;
struct sigaction act;
t = trap[signo];
new_act = S_DFL;
if (t != NULL) { /* trap for this sig is set */
new_act = S_CATCH;
if (t[0] == '\0') /* trap is "": ignore this sig */
new_act = S_IGN;
}
if (rootshell && new_act == S_DFL) {
switch (signo) {
case SIGINT:
if (iflag || minusc || sflag == 0)
new_act = S_CATCH;
break;
case SIGQUIT:
#if DEBUG
if (debug)
break;
#endif
/* man bash:
* "In all cases, bash ignores SIGQUIT. Non-builtin
* commands run by bash have signal handlers
* set to the values inherited by the shell
* from its parent". */
new_act = S_IGN;
break;
case SIGTERM:
if (iflag)
new_act = S_IGN;
break;
#if JOBS
case SIGTSTP:
case SIGTTOU:
if (mflag)
new_act = S_IGN;
break;
#endif
}
}
//TODO: if !rootshell, we reset SIGQUIT to DFL,
//whereas we have to restore it to what shell got on entry
//from the parent. See comment above
t = &sigmode[signo - 1];
cur_act = *t;
if (cur_act == 0) {
/* current setting is not yet known */
if (sigaction(signo, NULL, &act)) {
/* pretend it worked; maybe we should give a warning,
* but other shells don't. We don't alter sigmode,
* so we retry every time.
* btw, in Linux it never fails. --vda */
return;
}
if (act.sa_handler == SIG_IGN) {
cur_act = S_HARD_IGN;
if (mflag
&& (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
) {
cur_act = S_IGN; /* don't hard ignore these */
}
}
}
if (cur_act == S_HARD_IGN || cur_act == new_act)
return;
act.sa_handler = SIG_DFL;
switch (new_act) {
case S_CATCH:
act.sa_handler = signal_handler;
break;
case S_IGN:
act.sa_handler = SIG_IGN;
break;
}
/* flags and mask matter only if !DFL and !IGN, but we do it
* for all cases for more deterministic behavior:
*/
act.sa_flags = 0;
sigfillset(&act.sa_mask);
sigaction_set(signo, &act);
*t = new_act;
}
/* mode flags for set_curjob */
#define CUR_DELETE 2
#define CUR_RUNNING 1
#define CUR_STOPPED 0
/* mode flags for dowait */
#define DOWAIT_NONBLOCK WNOHANG
#define DOWAIT_BLOCK 0
#if JOBS
/* pgrp of shell on invocation */
static int initialpgrp; //references:2
static int ttyfd = -1; //5
#endif
/* array of jobs */
static struct job *jobtab; //5
/* size of array */
static unsigned njobs; //4
/* current job */
static struct job *curjob; //lots
/* number of presumed living untracked jobs */
static int jobless; //4
static void
set_curjob(struct job *jp, unsigned mode)
{
struct job *jp1;
struct job **jpp, **curp;
/* first remove from list */
jpp = curp = &curjob;
while (1) {
jp1 = *jpp;
if (jp1 == jp)
break;
jpp = &jp1->prev_job;
}
*jpp = jp1->prev_job;
/* Then re-insert in correct position */
jpp = curp;
switch (mode) {
default:
#if DEBUG
abort();
#endif
case CUR_DELETE:
/* job being deleted */
break;
case CUR_RUNNING:
/* newly created job or backgrounded job,
put after all stopped jobs. */
while (1) {
jp1 = *jpp;
#if JOBS
if (!jp1 || jp1->state != JOBSTOPPED)
#endif
break;
jpp = &jp1->prev_job;
}
/* FALLTHROUGH */
#if JOBS
case CUR_STOPPED:
#endif
/* newly stopped job - becomes curjob */
jp->prev_job = *jpp;
*jpp = jp;
break;
}
}
#if JOBS || DEBUG
static int
jobno(const struct job *jp)
{
return jp - jobtab + 1;
}
#endif
/*
* Convert a job name to a job structure.
*/
#if !JOBS
#define getjob(name, getctl) getjob(name)
#endif
static struct job *
getjob(const char *name, int getctl)
{
struct job *jp;
struct job *found;
const char *err_msg = "%s: no such job";
unsigned num;
int c;
const char *p;
char *(*match)(const char *, const char *);
jp = curjob;
p = name;
if (!p)
goto currentjob;
if (*p != '%')
goto err;
c = *++p;
if (!c)
goto currentjob;
if (!p[1]) {
if (c == '+' || c == '%') {
currentjob:
err_msg = "No current job";
goto check;
}
if (c == '-') {
if (jp)
jp = jp->prev_job;
err_msg = "No previous job";
check:
if (!jp)
goto err;
goto gotit;
}
}
if (is_number(p)) {
num = atoi(p);
if (num < njobs) {
jp = jobtab + num - 1;
if (jp->used)
goto gotit;
goto err;
}
}
match = prefix;
if (*p == '?') {
match = strstr;
p++;
}
found = NULL;
while (jp) {
if (match(jp->ps[0].ps_cmd, p)) {
if (found)
goto err;
found = jp;
err_msg = "%s: ambiguous";
}
jp = jp->prev_job;
}
if (!found)
goto err;
jp = found;
gotit:
#if JOBS
err_msg = "job %s not created under job control";
if (getctl && jp->jobctl == 0)
goto err;
#endif
return jp;
err:
ash_msg_and_raise_error(err_msg, name);
}
/*
* Mark a job structure as unused.
*/
static void
freejob(struct job *jp)
{
struct procstat *ps;
int i;
INT_OFF;
for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) {
if (ps->ps_cmd != nullstr)
free(ps->ps_cmd);
}
if (jp->ps != &jp->ps0)
free(jp->ps);
jp->used = 0;
set_curjob(jp, CUR_DELETE);
INT_ON;
}
#if JOBS
static void
xtcsetpgrp(int fd, pid_t pgrp)
{
if (tcsetpgrp(fd, pgrp))
ash_msg_and_raise_error("can't set tty process group (%m)");
}
/*
* 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.
*
* Called with interrupts off.
*/
static void
setjobctl(int on)
{
int fd;
int pgrp;
if (on == doing_jobctl || rootshell == 0)
return;
if (on) {
int ofd;
ofd = fd = open(_PATH_TTY, O_RDWR);
if (fd < 0) {
/* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails.
* That sometimes helps to acquire controlling tty.
* Obviously, a workaround for bugs when someone
* failed to provide a controlling tty to bash! :) */
fd = 2;
while (!isatty(fd))
if (--fd < 0)
goto out;
}
fd = fcntl(fd, F_DUPFD, 10);
if (ofd >= 0)
close(ofd);
if (fd < 0)
goto out;
/* fd is a tty at this point */
close_on_exec_on(fd);
while (1) { /* while we are in the background */
pgrp = tcgetpgrp(fd);
if (pgrp < 0) {
out:
ash_msg("can't access tty; job control turned off");
mflag = on = 0;
goto close;
}
if (pgrp == getpgrp())
break;
killpg(0, SIGTTIN);
}
initialpgrp = pgrp;
setsignal(SIGTSTP);
setsignal(SIGTTOU);
setsignal(SIGTTIN);
pgrp = rootpid;
setpgid(0, pgrp);
xtcsetpgrp(fd, pgrp);
} else {
/* turning job control off */
fd = ttyfd;
pgrp = initialpgrp;
/* was xtcsetpgrp, but this can make exiting ash
* loop forever if pty is already deleted */
tcsetpgrp(fd, pgrp);
setpgid(0, pgrp);
setsignal(SIGTSTP);
setsignal(SIGTTOU);
setsignal(SIGTTIN);
close:
if (fd >= 0)
close(fd);
fd = -1;
}
ttyfd = fd;
doing_jobctl = on;
}
static int FAST_FUNC
killcmd(int argc, char **argv)
{
if (argv[1] && strcmp(argv[1], "-l") != 0) {
int i = 1;
do {
if (argv[i][0] == '%') {
/*
* "kill %N" - job kill
* Converting to pgrp / pid kill
*/
struct job *jp;
char *dst;
int j, n;
jp = getjob(argv[i], 0);
/*
* In jobs started under job control, we signal
* entire process group by kill -PGRP_ID.
* This happens, f.e., in interactive shell.
*
* Otherwise, we signal each child via
* kill PID1 PID2 PID3.
* Testcases:
* sh -c 'sleep 1|sleep 1 & kill %1'
* sh -c 'true|sleep 2 & sleep 1; kill %1'
* sh -c 'true|sleep 1 & sleep 2; kill %1'
*/
n = jp->nprocs; /* can't be 0 (I hope) */
if (jp->jobctl)
n = 1;
dst = alloca(n * sizeof(int)*4);
argv[i] = dst;
for (j = 0; j < n; j++) {
struct procstat *ps = &jp->ps[j];
/* Skip non-running and not-stopped members
* (i.e. dead members) of the job
*/
if (ps->ps_status != -1 && !WIFSTOPPED(ps->ps_status))
continue;
/*
* kill_main has matching code to expect
* leading space. Needed to not confuse
* negative pids with "kill -SIGNAL_NO" syntax
*/
dst += sprintf(dst, jp->jobctl ? " -%u" : " %u", (int)ps->ps_pid);
}
*dst = '\0';
}
} while (argv[++i]);
}
return kill_main(argc, argv);
}
static void
showpipe(struct job *jp /*, FILE *out*/)
{
struct procstat *ps;
struct procstat *psend;
psend = jp->ps + jp->nprocs;
for (ps = jp->ps + 1; ps < psend; ps++)
printf(" | %s", ps->ps_cmd);
outcslow('\n', stdout);
flush_stdout_stderr();
}
static int
restartjob(struct job *jp, int mode)
{
struct procstat *ps;
int i;
int status;
pid_t pgid;
INT_OFF;
if (jp->state == JOBDONE)
goto out;
jp->state = JOBRUNNING;
pgid = jp->ps[0].ps_pid;
if (mode == FORK_FG)
xtcsetpgrp(ttyfd, pgid);
killpg(pgid, SIGCONT);
ps = jp->ps;
i = jp->nprocs;
do {
if (WIFSTOPPED(ps->ps_status)) {
ps->ps_status = -1;
}
ps++;
} while (--i);
out:
status = (mode == FORK_FG) ? waitforjob(jp) : 0;
INT_ON;
return status;
}
static int FAST_FUNC
fg_bgcmd(int argc UNUSED_PARAM, char **argv)
{
struct job *jp;
int mode;
int retval;
mode = (**argv == 'f') ? FORK_FG : FORK_BG;
nextopt(nullstr);
argv = argptr;
do {
jp = getjob(*argv, 1);
if (mode == FORK_BG) {
set_curjob(jp, CUR_RUNNING);
printf("[%d] ", jobno(jp));
}
out1str(jp->ps[0].ps_cmd);
showpipe(jp /*, stdout*/);
retval = restartjob(jp, mode);
} while (*argv && *++argv);
return retval;
}
#endif
static int
sprint_status(char *s, int status, int sigonly)
{
int col;
int st;
col = 0;
if (!WIFEXITED(status)) {
#if JOBS
if (WIFSTOPPED(status))
st = WSTOPSIG(status);
else
#endif
st = WTERMSIG(status);
if (sigonly) {
if (st == SIGINT || st == SIGPIPE)
goto out;
#if JOBS
if (WIFSTOPPED(status))
goto out;
#endif
}
st &= 0x7f;
//TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
col = fmtstr(s, 32, strsignal(st));
if (WCOREDUMP(status)) {
col += fmtstr(s + col, 16, " (core dumped)");
}
} else if (!sigonly) {
st = WEXITSTATUS(status);
if (st)
col = fmtstr(s, 16, "Done(%d)", st);
else
col = fmtstr(s, 16, "Done");
}
out:
return col;
}
static int
dowait(int wait_flags, struct job *job)
{
int pid;
int status;
struct job *jp;
struct job *thisjob;
int state;
TRACE(("dowait(0x%x) called\n", wait_flags));
/* Do a wait system call. If job control is compiled in, we accept
* stopped processes. wait_flags may have WNOHANG, preventing blocking.
* NB: _not_ safe_waitpid, we need to detect EINTR */
if (doing_jobctl)
wait_flags |= WUNTRACED;
pid = waitpid(-1, &status, wait_flags);
TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
pid, status, errno, strerror(errno)));
if (pid <= 0)
return pid;
INT_OFF;
thisjob = NULL;
for (jp = curjob; jp; jp = jp->prev_job) {
struct procstat *ps;
struct procstat *psend;
if (jp->state == JOBDONE)
continue;
state = JOBDONE;
ps = jp->ps;
psend = ps + jp->nprocs;
do {
if (ps->ps_pid == pid) {
TRACE(("Job %d: changing status of proc %d "
"from 0x%x to 0x%x\n",
jobno(jp), pid, ps->ps_status, status));
ps->ps_status = status;
thisjob = jp;
}
if (ps->ps_status == -1)
state = JOBRUNNING;
#if JOBS
if (state == JOBRUNNING)
continue;
if (WIFSTOPPED(ps->ps_status)) {
jp->stopstatus = ps->ps_status;
state = JOBSTOPPED;
}
#endif
} while (++ps < psend);
if (thisjob)
goto gotjob;
}
#if JOBS
if (!WIFSTOPPED(status))
#endif
jobless--;
goto out;
gotjob:
if (state != JOBRUNNING) {
thisjob->changed = 1;
if (thisjob->state != state) {
TRACE(("Job %d: changing state from %d to %d\n",
jobno(thisjob), thisjob->state, state));
thisjob->state = state;
#if JOBS
if (state == JOBSTOPPED) {
set_curjob(thisjob, CUR_STOPPED);
}
#endif
}
}
out:
INT_ON;
if (thisjob && thisjob == job) {
char s[48 + 1];
int len;
len = sprint_status(s, status, 1);
if (len) {
s[len] = '\n';
s[len + 1] = '\0';
out2str(s);
}
}
return pid;
}
static int
blocking_wait_with_raise_on_sig(void)
{
pid_t pid = dowait(DOWAIT_BLOCK, NULL);
if (pid <= 0 && pending_sig)
raise_exception(EXSIG);
return pid;
}
#if JOBS
static void
showjob(FILE *out, struct job *jp, int mode)
{
struct procstat *ps;
struct procstat *psend;
int col;
int indent_col;
char s[80];
ps = jp->ps;
if (mode & SHOW_ONLY_PGID) { /* jobs -p */
/* just output process (group) id of pipeline */
fprintf(out, "%d\n", ps->ps_pid);
return;
}
col = fmtstr(s, 16, "[%d] ", jobno(jp));
indent_col = col;
if (jp == curjob)
s[col - 3] = '+';
else if (curjob && jp == curjob->prev_job)
s[col - 3] = '-';
if (mode & SHOW_PIDS)
col += fmtstr(s + col, 16, "%d ", ps->ps_pid);
psend = ps + jp->nprocs;
if (jp->state == JOBRUNNING) {
strcpy(s + col, "Running");
col += sizeof("Running") - 1;
} else {
int status = psend[-1].ps_status;
if (jp->state == JOBSTOPPED)
status = jp->stopstatus;
col += sprint_status(s + col, status, 0);
}
/* By now, "[JOBID]* [maybe PID] STATUS" is printed */
/* This loop either prints "<cmd1> | <cmd2> | <cmd3>" line
* or prints several "PID | <cmdN>" lines,
* depending on SHOW_PIDS bit.
* We do not print status of individual processes
* between PID and <cmdN>. bash does it, but not very well:
* first line shows overall job status, not process status,
* making it impossible to know 1st process status.
*/
goto start;
do {
/* for each process */
s[0] = '\0';
col = 33;
if (mode & SHOW_PIDS)
col = fmtstr(s, 48, "\n%*c%d ", indent_col, ' ', ps->ps_pid) - 1;
start:
fprintf(out, "%s%*c%s%s",
s,
33 - col >= 0 ? 33 - col : 0, ' ',
ps == jp->ps ? "" : "| ",
ps->ps_cmd
);
} while (++ps != psend);
outcslow('\n', out);
jp->changed = 0;
if (jp->state == JOBDONE) {
TRACE(("showjob: freeing job %d\n", jobno(jp)));
freejob(jp);
}
}
/*
* Print a list of jobs. If "change" is nonzero, only print jobs whose
* statuses have changed since the last call to showjobs.
*/
static void
showjobs(FILE *out, int mode)
{
struct job *jp;
TRACE(("showjobs(0x%x) called\n", mode));
/* Handle all finished jobs */
while (dowait(DOWAIT_NONBLOCK, NULL) > 0)
continue;
for (jp = curjob; jp; jp = jp->prev_job) {
if (!(mode & SHOW_CHANGED) || jp->changed) {
showjob(out, jp, mode);
}
}
}
static int FAST_FUNC
jobscmd(int argc UNUSED_PARAM, char **argv)
{
int mode, m;
mode = 0;
while ((m = nextopt("lp")) != '\0') {