* 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.
+ * When debugging is on (DEBUG is 1 and "set -o debug" was executed),
+ * 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 JOBS ENABLE_ASH_JOB_CONTROL
-#include "busybox.h" /* for applet_names */
#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"
-#include "math.h"
+#if ENABLE_SH_MATH_SUPPORT
+# include "math.h"
+#endif
#if ENABLE_ASH_RANDOM_SUPPORT
# include "random.h"
#else
# error "Do not even bother, ash will not run on NOMMU machine"
#endif
-//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
-
//config:config ASH
//config: bool "ash"
//config: default y
//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: Enable job control in the ash shell.
//config:
//config:config ASH_ALIAS
-//config: bool "alias support"
+//config: bool "Alias support"
//config: default y
//config: depends on ASH
//config: help
//config: default y
//config: depends on ASH
//config: help
-//config: Enable getopts builtin in the ash shell.
+//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 to ash.
+//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 to ash.
+//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 to ash.
+//config: Enable support for test builtin in ash.
//config:
//config:config ASH_CMDCMD
//config: bool "'command' command to override shell builtins"
//config: default n
//config: depends on ASH
//config: help
-//config: Enable "check for new mail" in the ash shell.
+//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: variable each time it is displayed.
//config:
-//usage:#define ash_trivial_usage NOUSAGE_STR
-//usage:#define ash_full_usage ""
-//usage:#define sh_trivial_usage NOUSAGE_STR
-//usage:#define sh_full_usage ""
-//usage:#define bash_trivial_usage NOUSAGE_STR
-//usage:#define bash_full_usage ""
+//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. */
/* ============ Utility functions */
#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
+#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
+
static int isdigit_str9(const char *str)
{
int maxlen = 9 + 1; /* max 9 digits: 999999999 */
/* ============ 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
#endif
#if ENABLE_ASH_MAIL
static void chkmail(void);
-static void changemail(const char *) FAST_FUNC;
+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
const char *var_text;
void (*var_func)(const char *) FAST_FUNC;
} varinit_data[] = {
+ /*
+ * Note: VEXPORT would not work correctly here for NOFORK applets:
+ * some environment strings may be constant.
+ */
{ VSTRFIXED|VTEXTFIXED , defifsvar , NULL },
#if ENABLE_ASH_MAIL
{ VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL" , changemail },
}
#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
free(vp);
INT_ON;
} else {
- setvar(s, 0, 0);
+ setvar2(s, 0);
vp->flags &= ~VEXPORT;
}
ok:
switch (new_act) {
case S_CATCH:
act.sa_handler = signal_handler;
- act.sa_flags = 0; /* matters only if !DFL and !IGN */
- sigfillset(&act.sa_mask); /* ditto */
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;
/* first remove from list */
jpp = curp = &curjob;
- do {
+ while (1) {
jp1 = *jpp;
if (jp1 == jp)
break;
jpp = &jp1->prev_job;
- } while (1);
+ }
*jpp = jp1->prev_job;
/* Then re-insert in correct position */
break;
case CUR_RUNNING:
/* newly created job or backgrounded job,
- put after all stopped jobs. */
- do {
+ * put after all stopped jobs.
+ */
+ while (1) {
jp1 = *jpp;
#if JOBS
if (!jp1 || jp1->state != JOBSTOPPED)
#endif
break;
jpp = &jp1->prev_job;
- } while (1);
+ }
/* FALLTHROUGH */
#if JOBS
case CUR_STOPPED:
goto out;
/* fd is a tty at this point */
close_on_exec_on(fd);
- do { /* while we are in the background */
+ while (1) { /* while we are in the background */
pgrp = tcgetpgrp(fd);
if (pgrp < 0) {
out:
if (pgrp == getpgrp())
break;
killpg(0, SIGTTIN);
- } while (1);
+ }
initialpgrp = pgrp;
setsignal(SIGTSTP);
static int FAST_FUNC
killcmd(int argc, char **argv)
{
- int i = 1;
if (argv[1] && strcmp(argv[1], "-l") != 0) {
+ int i = 1;
do {
if (argv[i][0] == '%') {
- struct job *jp = getjob(argv[i], 0);
- unsigned pid = jp->ps[0].ps_pid;
- /* Enough space for ' -NNN<nul>' */
- argv[i] = alloca(sizeof(int)*3 + 3);
- /* kill_main has matching code to expect
- * leading space. Needed to not confuse
- * negative pids with "kill -SIGNAL_NO" syntax */
- sprintf(argv[i], " -%u", pid);
+ /*
+ * "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]);
}
#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)");
break;
job = job->prev_job;
}
- } else
+ } else {
job = getjob(*argv, 0);
+ }
/* loop until process terminated or stopped */
while (job->state == JOBRUNNING)
blocking_wait_with_raise_on_sig();
#if JOBS
/* do job control only in root shell */
doing_jobctl = 0;
- if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
+ if (mode != FORK_NOJOB && jp->jobctl && oldlvl == 0) {
pid_t pgrp;
if (jp->nprocs == 0)
ash_msg_and_raise_error("can't open '%s'", bb_dev_null);
}
}
- if (!oldlvl) {
+ if (oldlvl == 0) {
if (iflag) { /* why if iflag only? */
setsignal(SIGINT);
setsignal(SIGTERM);
* Code for dealing with input/output redirection.
*/
+#undef EMPTY
+#undef CLOSED
#define EMPTY -2 /* marks an unused slot in redirtab */
#define CLOSED -3 /* marks a slot of previously-closed fd */
char *fname;
int f;
+ fname = redir->nfile.expfname;
switch (redir->nfile.type) {
case NFROM:
- fname = redir->nfile.expfname;
f = open(fname, O_RDONLY);
if (f < 0)
goto eopen;
break;
case NFROMTO:
- fname = redir->nfile.expfname;
f = open(fname, O_RDWR|O_CREAT, 0666);
if (f < 0)
goto ecreate;
#endif
/* Take care of noclobber mode. */
if (Cflag) {
- fname = redir->nfile.expfname;
f = noclobberopen(fname);
if (f < 0)
goto ecreate;
}
/* FALLTHROUGH */
case NCLOBBER:
- fname = redir->nfile.expfname;
f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (f < 0)
goto ecreate;
break;
case NAPPEND:
- fname = redir->nfile.expfname;
f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666);
if (f < 0)
goto ecreate;
/*
* Our own itoa().
*/
+#if !ENABLE_SH_MATH_SUPPORT
+/* cvtnum() is used even if math support is off (to prepare $? values and such) */
+typedef long arith_t;
+# define ARITH_FMT "%ld"
+#endif
static int
cvtnum(arith_t num)
{
read:
if (in.fd < 0)
break;
- i = nonblock_safe_read(in.fd, buf, sizeof(buf));
+ i = nonblock_immune_read(in.fd, buf, sizeof(buf), /*loop_on_EINTR:*/ 1);
TRACE(("expbackq: read returns %d\n", i));
if (i <= 0)
break;
p = expdest - 1;
*p = '\0';
p--;
- do {
+ while (1) {
int esc;
while ((unsigned char)*p != CTLARI) {
}
p -= esc + 1;
- } while (1);
+ }
begoff = p - start;
length++;
goto addquote;
case CTLVAR:
+ TRACE(("argstr: evalvar('%s')\n", p));
p = evalvar(p, flags, var_str_list);
+ TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
goto start;
case CTLBACKQ:
c = '\0';
IF_ASH_BASH_COMPAT(const char *repl = NULL;)
IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
int saveherefd = herefd;
- int amount, workloc, resetloc;
+ int amount, resetloc;
+ IF_ASH_BASH_COMPAT(int workloc;)
int zero;
char *(*scan)(char*, char*, char*, char*, int, int);
switch (subtype) {
case VSASSIGN:
- setvar(varname, startp, 0);
+ setvar2(varname, startp);
amount = startp - expdest;
STADJUST(amount, expdest);
return startp;
rmescend--;
str = (char *)stackblock() + strloc;
preglob(str, varflags & VSQUOTE, 0);
- workloc = expdest - (char *)stackblock();
#if ENABLE_ASH_BASH_COMPAT
+ workloc = expdest - (char *)stackblock();
if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
char *idx, *end;
patloc = expdest - (char *)stackblock();
if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
startloc, varflags,
-//TODO: | EXP_REDIR too? All other such places do it too
- /* quotes: */ flags & (EXP_FULL | EXP_CASE),
+ /* quotes: */ flags & (EXP_FULL | EXP_CASE | EXP_REDIR),
var_str_list)
) {
int amount = expdest - (
STARTSTACKSTR(expdest);
ifsfirst.next = NULL;
ifslastp = NULL;
+ TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
argstr(arg->narg.text, flag,
/* var_str_list: */ arglist ? arglist->list : NULL);
p = _STPUTC('\0', expdest);
return; /* here document expanded */
}
p = grabstackstr(p);
+ TRACE(("expandarg: p:'%s'\n", p));
exparg.lastp = &exparg.list;
/*
* TODO - EXP_REDIR
exparg.lastp = &exparg.list;
expandmeta(exparg.list /*, flag*/);
} else {
- if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+ if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */
rmescapes(p, 0);
+ TRACE(("expandarg: rmescapes:'%s'\n", p));
+ }
sp = stzalloc(sizeof(*sp));
sp->text = p;
*exparg.lastp = sp;
static void
tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp)
{
- int repeated = 0;
-
#if ENABLE_FEATURE_SH_STANDALONE
if (applet_no >= 0) {
if (APPLET_IS_NOEXEC(applet_no)) {
#else
execve(cmd, argv, envp);
#endif
- if (repeated) {
+ if (cmd == (char*) bb_busybox_exec_path) {
+ /* We already visited ENOEXEC branch below, don't do it again */
+//TODO: try execve(initial_argv0_of_shell, argv, envp) before giving up?
free(argv);
return;
}
if (errno == ENOEXEC) {
+ /* Run "cmd" as a shell script:
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
+ * "If the execve() function fails with ENOEXEC, the shell
+ * shall execute a command equivalent to having a shell invoked
+ * with the command name as its first operand,
+ * with any remaining arguments passed to the new shell"
+ *
+ * That is, do not use $SHELL, user's shell, or /bin/sh;
+ * just call ourselves.
+ *
+ * Note that bash reads ~80 chars of the file, and if it sees
+ * a zero byte before it sees newline, it doesn't try to
+ * interpret it, but fails with "cannot execute binary file"
+ * message and exit code 126. For one, this prevents attempts
+ * to interpret foreign ELF binaries as shell scripts.
+ */
char **ap;
char **new;
for (ap = argv; *ap; ap++)
continue;
- ap = new = ckmalloc((ap - argv + 2) * sizeof(ap[0]));
- ap[1] = cmd;
- ap[0] = cmd = (char *)DEFAULT_SHELL;
- ap += 2;
- argv++;
- while ((*ap++ = *argv++) != NULL)
+ new = ckmalloc((ap - argv + 2) * sizeof(new[0]));
+ new[0] = (char*) "ash";
+ new[1] = cmd;
+ ap = new + 2;
+ while ((*ap++ = *++argv) != NULL)
continue;
+ cmd = (char*) bb_busybox_exec_path;
argv = new;
- repeated++;
goto repeat;
}
}
int e;
char **envp;
int exerrno;
-#if ENABLE_FEATURE_SH_STANDALONE
- int applet_no = -1;
-#endif
+ int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
clearredir(/*drop:*/ 1);
envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
#endif
) {
tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp);
+ if (applet_no >= 0) {
+ /* We tried execing ourself, but it didn't work.
+ * Maybe /proc/self/exe doesn't exist?
+ * Try $PATH search.
+ */
+ goto try_PATH;
+ }
e = errno;
} else {
+ try_PATH:
e = ENOENT;
while ((cmdname = path_advance(&path, argv[0])) != NULL) {
if (--idx < 0 && pathopt == NULL) {
for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
if (cmdp->cmdtype == CMDNORMAL
|| (cmdp->cmdtype == CMDBUILTIN
- && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
+ && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
&& builtinloc > 0)
) {
cmdp->rehash = 1;
loopnest++;
flags &= EV_TESTED;
for (sp = arglist.list; sp; sp = sp->next) {
- setvar(n->nfor.var, sp->text, 0);
+ setvar2(n->nfor.var, sp->text);
evaltree(n->nfor.body, flags);
if (evalskip) {
if (evalskip == SKIPCONT && --skipcount <= 0) {
case NCLOBBER:
case NAPPEND:
expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+ TRACE(("expredir expanded to '%s'\n", fn.list->text));
#if ENABLE_ASH_BASH_COMPAT
store_expfname:
+#endif
+#if 0
+// By the design of stack allocator, the loop of this kind:
+// while true; do while true; do break; done </dev/null; done
+// will look like a memory leak: ash plans to free expfname's
+// of "/dev/null" as soon as it finishes running the loop
+// (in this case, never).
+// This "fix" is wrong:
+ if (redir->nfile.expfname)
+ stunalloc(redir->nfile.expfname);
+// It results in corrupted state of stacked allocations.
#endif
redir->nfile.expfname = fn.list->text;
break;
#if !ENABLE_FEATURE_SH_EXTRA_QUIET
static int helpcmd(int, char **) FAST_FUNC;
#endif
+#if MAX_HISTORY
+static int historycmd(int, char **) FAST_FUNC;
+#endif
#if ENABLE_SH_MATH_SUPPORT
static int letcmd(int, char **) FAST_FUNC;
#endif
#if !ENABLE_FEATURE_SH_EXTRA_QUIET
{ BUILTIN_NOSPEC "help" , helpcmd },
#endif
+#if MAX_HISTORY
+ { BUILTIN_NOSPEC "history" , historycmd },
+#endif
#if JOBS
{ BUILTIN_REGULAR "jobs" , jobscmd },
{ BUILTIN_REGULAR "kill" , killcmd },
/* Now locate the command. */
if (argc) {
- const char *oldpath;
int cmd_flag = DO_ERR;
-
+#if ENABLE_ASH_CMDCMD
+ const char *oldpath = path + 5;
+#endif
path += 5;
- oldpath = path;
for (;;) {
find_command(argv[0], &cmdentry, cmd_flag, path);
if (cmdentry.cmdtype == CMDUNKNOWN) {
* '_' in 'vi' command mode during line editing...
* However I implemented that within libedit itself.
*/
- setvar("_", lastarg, 0);
+ setvar2("_", lastarg);
}
popstackmark(&smark);
}
#if ENABLE_FEATURE_EDITING
retry:
if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
- nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
+ nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1);
else {
-#if ENABLE_FEATURE_TAB_COMPLETION
+ int timeout = -1;
+# if ENABLE_ASH_IDLE_TIMEOUT
+ if (iflag) {
+ const char *tmout_var = lookupvar("TMOUT");
+ if (tmout_var) {
+ timeout = atoi(tmout_var) * 1000;
+ if (timeout <= 0)
+ timeout = -1;
+ }
+ }
+# endif
+# if ENABLE_FEATURE_TAB_COMPLETION
line_input_state->path_lookup = pathval();
-#endif
- nr = read_line_input(cmdedit_prompt, buf, IBUFSIZ, line_input_state);
+# endif
+ /* Unicode support should be activated even if LANG is set
+ * _during_ shell execution, not only if it was set when
+ * shell was started. Therefore, re-check LANG every time:
+ */
+ {
+ const char *s = lookupvar("LC_ALL");
+ if (!s) s = lookupvar("LC_CTYPE");
+ if (!s) s = lookupvar("LANG");
+ reinit_unicode(s);
+ }
+ nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout);
if (nr == 0) {
/* Ctrl+C pressed */
if (trap[SIGINT]) {
}
goto retry;
}
- if (nr < 0 && errno == 0) {
- /* Ctrl+D pressed */
- nr = 0;
+ if (nr < 0) {
+ if (errno == 0) {
+ /* Ctrl+D pressed */
+ nr = 0;
+ }
+# if ENABLE_ASH_IDLE_TIMEOUT
+ else if (errno == EAGAIN && timeout > 0) {
+ printf("\007timed out waiting for input: auto-logout\n");
+ exitshell();
+ }
+# endif
}
}
#else
- nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
+ nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1);
#endif
-#if 0
-/* nonblock_safe_read() handles this problem */
+#if 0 /* disabled: nonblock_immune_read() handles this problem */
if (nr < 0) {
if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
int flags = fcntl(0, F_GETFL);
else if (*argptr == NULL)
setparam(argptr);
}
- break; /* "-" or "--" terminates options */
+ break; /* "-" or "--" terminates options */
}
}
/* first char was + or - */
if (!argv[1])
return showvars(nullstr, 0, VUNSET);
+
INT_OFF;
- retval = 1;
- if (!options(0)) { /* if no parse error... */
- retval = 0;
+ retval = options(/*cmdline:*/ 0);
+ if (retval == 0) { /* if no parse error... */
optschanged();
if (*argptr != NULL) {
setparam(argptr);
INT_ON;
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. */
+ * treatment to some slashes, and then push the string and
+ * reread it as input, interpreting it normally.
+ */
char *pout;
size_t psavelen;
char *pstr;
inter = 0;
if (iflag && top) {
inter++;
-#if ENABLE_ASH_MAIL
chkmail();
-#endif
}
n = parsecmd(inter);
#if DEBUG
/* "false; . empty_file; echo $?" should print 0, not 1: */
exitstatus = 0;
+ /* This aborts if file isn't found, which is POSIXly correct.
+ * bash returns exitcode 1 instead.
+ */
fullname = find_dot_file(argv[1]);
-
argv += 2;
argc -= 2;
if (argc) { /* argc > 0, argv[0] != NULL */
shellparam.p = argv;
};
+ /* This aborts if file can't be opened, which is POSIXly correct.
+ * bash returns exitcode 1 instead.
+ */
setinputfile(fullname, INPUT_PUSH_FILE);
commandname = fullname;
cmdloop(0);
}
if ((act & DO_NOFUNC)
|| !prefix(pathopt, "func")
- ) { /* ignore unimplemented options */
+ ) { /* ignore unimplemented options */
continue;
}
}
}
#endif /* FEATURE_SH_EXTRA_QUIET */
+#if MAX_HISTORY
+static int FAST_FUNC
+historycmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
+{
+ show_history(line_input_state);
+ return EXIT_SUCCESS;
+}
+#endif
+
/*
* The export and readonly commands.
*/
char *name;
const char *p;
char **aptr;
- int flag = argv[0][0] == 'r' ? VREADONLY : VEXPORT;
+ char opt;
+ int flag;
+ int flag_off;
- if (nextopt("p") != 'p') {
+ /* "readonly" in bash accepts, but ignores -n.
+ * We do the same: it saves a conditional in nextopt's param.
+ */
+ flag_off = 0;
+ while ((opt = nextopt("np")) != '\0') {
+ if (opt == 'n')
+ flag_off = VEXPORT;
+ }
+ flag = VEXPORT;
+ if (argv[0][0] == 'r') {
+ flag = VREADONLY;
+ flag_off = 0; /* readonly ignores -n */
+ }
+ flag_off = ~flag_off;
+
+ /*if (opt_p_not_specified) - bash doesnt check this. Try "export -p NAME" */
+ {
aptr = argptr;
name = *aptr;
if (name) {
} else {
vp = *findvar(hashvar(name), name);
if (vp) {
- vp->flags |= flag;
+ vp->flags = ((vp->flags | flag) & flag_off);
continue;
}
}
- setvar(name, p, flag);
+ setvar(name, p, (flag & flag_off));
} while ((name = *++aptr) != NULL);
return 0;
}
}
+
+ /* No arguments. Show the list of exported or readonly vars.
+ * -n is ignored.
+ */
showvars(argv[0], flag, 0);
return 0;
}
}
}
+ /* "read -s" needs to save/restore termios, can't allow ^C
+ * to jump out of it.
+ */
+ INT_OFF;
r = shell_builtin_read(setvar2,
argptr,
bltinlookup("IFS"), /* can be NULL */
opt_t,
opt_u
);
+ INT_ON;
if ((uintptr_t)r > 1)
ash_msg_and_raise_error(r);
/*
* Called to exit the shell.
*/
-static void exitshell(void) NORETURN;
static void
exitshell(void)
{
char *p;
int status;
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+ save_history(line_input_state);
+#endif
+
status = exitstatus;
TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
if (setjmp(loc.loc)) {
/* bash re-enables SIGHUP which is SIG_IGNed on entry.
* Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
*/
- signal(SIGHUP, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
/* from var.c: */
{
}
}
- setvar("PPID", utoa(getppid()), 0);
-
+ setvar2("PPID", utoa(getppid()));
+#if ENABLE_ASH_BASH_COMPAT
+ p = lookupvar("SHLVL");
+ setvar2("SHLVL", utoa(p ? atoi(p) + 1 : 1));
+#endif
p = lookupvar("PWD");
- if (p)
+ if (p) {
if (*p != '/' || stat(p, &st1) || stat(".", &st2)
- || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
+ || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino
+ ) {
p = '\0';
+ }
+ }
setpwd(p, 0);
}
}
+
+//usage:#define ash_trivial_usage
+//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:#define ash_full_usage "\n\n"
+//usage: "Unix shell interpreter"
+
+//usage:#if ENABLE_FEATURE_SH_IS_ASH
+//usage:# define sh_trivial_usage ash_trivial_usage
+//usage:# define sh_full_usage ash_full_usage
+//usage:#endif
+//usage:#if ENABLE_FEATURE_BASH_IS_ASH
+//usage:# define bash_trivial_usage ash_trivial_usage
+//usage:# define bash_full_usage ash_full_usage
+//usage:#endif
+
/*
* Process the shell command line arguments.
*/
for (i = 0; i < NOPTS; i++)
optlist[i] = 2;
argptr = xargv;
- if (options(1)) {
+ if (options(/*cmdline:*/ 1)) {
/* it already printed err message */
raise_exception(EXERROR);
}
setstackmark(&smark);
procargs(argv);
-#if ENABLE_FEATURE_EDITING_SAVEHISTORY
- if (iflag) {
- const char *hp = lookupvar("HISTFILE");
-
- if (hp == NULL) {
- hp = lookupvar("HOME");
- if (hp != NULL) {
- char *defhp = concat_path_file(hp, ".ash_history");
- setvar("HISTFILE", defhp, 0);
- free(defhp);
- }
- }
- }
-#endif
- if (/* argv[0] && */ argv[0][0] == '-')
+ if (argv[0] && argv[0][0] == '-')
isloginsh = 1;
if (isloginsh) {
+ const char *hp;
+
state = 1;
read_profile("/etc/profile");
state1:
state = 2;
- read_profile(".profile");
+ hp = lookupvar("HOME");
+ if (hp) {
+ hp = concat_path_file(hp, ".profile");
+ read_profile(hp);
+ free((char*)hp);
+ }
}
state2:
state = 3;
}
if (sflag || minusc == NULL) {
-#if defined MAX_HISTORY && MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY
+#if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY
if (iflag) {
const char *hp = lookupvar("HISTFILE");
+ if (!hp) {
+ hp = lookupvar("HOME");
+ if (hp) {
+ hp = concat_path_file(hp, ".ash_history");
+ setvar2("HISTFILE", hp);
+ free((char*)hp);
+ hp = lookupvar("HISTFILE");
+ }
+ }
if (hp)
line_input_state->hist_file = hp;
+# if ENABLE_FEATURE_SH_HISTFILESIZE
+ hp = lookupvar("HISTFILESIZE");
+ line_input_state->max_history = size_from_HISTFILESIZE(hp);
+# endif
}
#endif
state4: /* XXX ??? - why isn't this before the "if" statement */