* 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 */
/* ============ 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 */
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:
break;
case CUR_RUNNING:
/* newly created job or backgrounded job,
- put after all stopped jobs. */
+ * put after all stopped jobs.
+ */
while (1) {
jp1 = *jpp;
#if JOBS
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;
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;
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);
}
* _during_ shell execution, not only if it was set when
* shell was started. Therefore, re-check LANG every time:
*/
- reinit_unicode(lookupvar("LANG"));
+ {
+ 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 */
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;
/* "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);
}
#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;
+
+ /* "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 (nextopt("p") != 'p') {
+ /*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);
}
}
- 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 != '/' || stat(p, &st1) || stat(".", &st2)
setstackmark(&smark);
procargs(argv);
-#if ENABLE_FEATURE_EDITING_SAVEHISTORY
- if (iflag) {
- const char *hp = lookupvar("HISTFILE");
- if (!hp) {
- hp = lookupvar("HOME");
- if (hp) {
- char *defhp = concat_path_file(hp, ".ash_history");
- setvar("HISTFILE", defhp, 0);
- free(defhp);
- }
- }
- }
-#endif
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 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