#if ENABLE_HUSH_CASE
# include <fnmatch.h>
#endif
+#include <sys/utsname.h> /* for setting $HOSTNAME */
#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
#include "unicode.h"
#else
# define CLEAR_RANDOM_T(rnd) ((void)0)
#endif
+#ifndef F_DUPFD_CLOEXEC
+# define F_DUPFD_CLOEXEC F_DUPFD
+#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
MAYBE_ASSIGNMENT = 0,
DEFINITELY_ASSIGNMENT = 1,
NOT_ASSIGNMENT = 2,
- /* Not an assigment, but next word may be: "if v=xyz cmd;" */
+ /* Not an assignment, but next word may be: "if v=xyz cmd;" */
WORD_IS_KEYWORD = 3,
};
/* Used for initialization: o_string foo = NULL_O_STRING; */
typedef struct in_str {
const char *p;
- /* eof_flag=1: last char in ->p is really an EOF */
- char eof_flag; /* meaningless if ->p == NULL */
- char peek_buf[2];
#if ENABLE_HUSH_INTERACTIVE
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
+ int peek_buf[2];
int last_char;
FILE *file;
int (*get) (struct in_str *) FAST_FUNC;
int (*peek) (struct in_str *) FAST_FUNC;
} in_str;
#define i_getch(input) ((input)->get(input))
-#define i_peek(input) ((input)->peek(input))
+#define i_peek(input) ((input)->peek(input))
/* The descrip member of this structure is only used to make
* debugging output pretty */
};
+struct FILE_list {
+ struct FILE_list *next;
+ FILE *fp;
+ int fd;
+};
+
+
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
struct globals {
* 1: return is invoked, skip all till end of func
*/
smallint flag_return_in_progress;
+# define G_flag_return_in_progress (G.flag_return_in_progress)
+#else
+# define G_flag_return_in_progress 0
#endif
smallint exiting; /* used to prevent EXIT trap recursion */
/* These four support $?, $#, and $1 */
unsigned handled_SIGCHLD;
smallint we_have_children;
#endif
+ struct FILE_list *FILE_list;
/* Which signals have non-DFL handler (even with no traps set)?
* Set at the start to:
* (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
int debug_indent;
#endif
struct sigaction sa;
- char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
+#if ENABLE_FEATURE_EDITING
+ char user_input_buf[CONFIG_FEATURE_EDITING_MAX_LEN];
+#endif
};
#define G (*ptr_to_globals)
/* Not #defining name to G.name - this quickly gets unwieldy
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv) FAST_FUNC;
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int builtin_history(char **argv) FAST_FUNC;
+#endif
#if ENABLE_HUSH_LOCAL
static int builtin_local(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , NULL),
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+ BLTIN("history" , builtin_history , "Show command history"),
+#endif
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List jobs"),
#endif
BLTIN("source" , builtin_source , "Run commands in a file"),
#endif
BLTIN("trap" , builtin_trap , "Trap signals"),
+ BLTIN("true" , builtin_true , NULL),
BLTIN("type" , builtin_type , "Show command type"),
BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"),
BLTIN("umask" , builtin_umask , "Set file creation mask"),
}
+static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC)
+{
+ /* We avoid taking stdio fds. Mimicking ash: use fds above 9 */
+ int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10);
+ if (newfd < 0) {
+ /* fd was not open? */
+ if (errno == EBADF)
+ return fd;
+ xfunc_die();
+ }
+ close(fd);
+ return newfd;
+}
+
+
+/* Manipulating the list of open FILEs */
+static FILE *remember_FILE(FILE *fp)
+{
+ if (fp) {
+ struct FILE_list *n = xmalloc(sizeof(*n));
+ n->next = G.FILE_list;
+ G.FILE_list = n;
+ n->fp = fp;
+ n->fd = fileno(fp);
+ close_on_exec_on(n->fd);
+ }
+ return fp;
+}
+static void fclose_and_forget(FILE *fp)
+{
+ struct FILE_list **pp = &G.FILE_list;
+ while (*pp) {
+ struct FILE_list *cur = *pp;
+ if (cur->fp == fp) {
+ *pp = cur->next;
+ free(cur);
+ break;
+ }
+ pp = &cur->next;
+ }
+ fclose(fp);
+}
+static int save_FILEs_on_redirect(int fd)
+{
+ struct FILE_list *fl = G.FILE_list;
+ while (fl) {
+ if (fd == fl->fd) {
+ /* We use it only on script files, they are all CLOEXEC */
+ fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC);
+ return 1;
+ }
+ fl = fl->next;
+ }
+ return 0;
+}
+static void restore_redirected_FILEs(void)
+{
+ struct FILE_list *fl = G.FILE_list;
+ while (fl) {
+ int should_be = fileno(fl->fp);
+ if (fl->fd != should_be) {
+ xmove_fd(fl->fd, should_be);
+ fl->fd = should_be;
+ }
+ fl = fl->next;
+ }
+}
+#if ENABLE_FEATURE_SH_STANDALONE
+static void close_all_FILE_list(void)
+{
+ struct FILE_list *fl = G.FILE_list;
+ while (fl) {
+ /* fclose would also free FILE object.
+ * It is disastrous if we share memory with a vforked parent.
+ * I'm not sure we never come here after vfork.
+ * Therefore just close fd, nothing more.
+ */
+ /*fclose(fl->fp); - unsafe */
+ close(fl->fd);
+ fl = fl->next;
+ }
+}
+#endif
+
+
/* Helpers for setting new $n and restoring them back
*/
typedef struct save_arg_t {
* backgrounds (i.e. stops) or kills all members of currently running
* pipe.
*
- * Wait builtin in interruptible by signals for which user trap is set
+ * Wait builtin is interruptible by signals for which user trap is set
* or by SIGINT in interactive shell.
*
* Trap handlers will execute even within trap handlers. (right?)
* are set to '' (ignore) are NOT reset to defaults. We do the same.
*
* Problem: the above approach makes it unwieldy to catch signals while
- * we are in read builtin, of while we read commands from stdin:
+ * we are in read builtin, or while we read commands from stdin:
* masked signals are not visible!
*
* New implementation
* for them - a bit like emulating kernel pending signal mask in userspace.
* We are interested in: signals which need to have special handling
* as described above, and all signals which have traps set.
- * Signals are rocorded in pending_set.
+ * Signals are recorded in pending_set.
* After each pipe execution, we extract any pending signals
* and act on them.
*
return old_sa.sa_handler;
}
+static void hush_exit(int exitcode) NORETURN;
+static void fflush_and__exit(void) NORETURN;
+static void restore_ttypgrp_and__exit(void) NORETURN;
+
+static void restore_ttypgrp_and__exit(void)
+{
+ /* xfunc has failed! die die die */
+ /* no EXIT traps, this is an escape hatch! */
+ G.exiting = 1;
+ hush_exit(xfunc_error_retval);
+}
+
+/* Needed only on some libc:
+ * It was observed that on exit(), fgetc'ed buffered data
+ * gets "unwound" via lseek(fd, -NUM, SEEK_CUR).
+ * With the net effect that even after fork(), not vfork(),
+ * exit() in NOEXECed applet in "sh SCRIPT":
+ * noexec_applet_here
+ * echo END_OF_SCRIPT
+ * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT".
+ * This makes "echo END_OF_SCRIPT" executed twice.
+ * Similar problems can be seen with die_if_script() -> xfunc_die()
+ * and in `cmd` handling.
+ * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit():
+ */
+static void fflush_and__exit(void)
+{
+ fflush_all();
+ _exit(xfunc_error_retval);
+}
+
#if ENABLE_HUSH_JOB
/* After [v]fork, in child: do not restore tty pgrp on xfunc death */
-# define disable_restore_tty_pgrp_on_exit() (die_sleep = 0)
+# define disable_restore_tty_pgrp_on_exit() (die_func = fflush_and__exit)
/* After [v]fork, in parent: restore tty pgrp on xfunc death */
-# define enable_restore_tty_pgrp_on_exit() (die_sleep = -1)
+# define enable_restore_tty_pgrp_on_exit() (die_func = restore_ttypgrp_and__exit)
/* Restores tty foreground process group, and exits.
* May be called as signal handler for fatal signal
* (will resend signal to itself, producing correct exit state)
* or called directly with -EXITCODE.
- * We also call it if xfunc is exiting. */
+ * We also call it if xfunc is exiting.
+ */
static void sigexit(int sig) NORETURN;
static void sigexit(int sig)
{
}
/* Restores tty foreground process group, and exits. */
-static void hush_exit(int exitcode) NORETURN;
static void hush_exit(int exitcode)
{
#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
}
#endif
-#if ENABLE_HUSH_JOB
fflush_all();
+#if ENABLE_HUSH_JOB
sigexit(- (exitcode & 0xff));
#else
- exit(exitcode);
+ _exit(exitcode);
#endif
}
break;
got_sig:
if (G.traps && G.traps[sig]) {
+ debug_printf_exec("%s: sig:%d handler:'%s'\n", __func__, sig, G.traps[sig]);
if (G.traps[sig][0]) {
/* We have user-defined handler */
smalluint save_rcode;
/* not a trap: special action */
switch (sig) {
case SIGINT:
+ debug_printf_exec("%s: sig:%d default SIGINT handler\n", __func__, sig);
/* Builtin was ^C'ed, make it look prettier: */
bb_putchar('\n');
G.flag_SIGINT = 1;
#if ENABLE_HUSH_JOB
case SIGHUP: {
struct pipe *job;
+ debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
/* bash is observed to signal whole process groups,
* not individual processes */
for (job = G.job_list; job; job = job->next) {
#endif
#if ENABLE_HUSH_FAST
case SIGCHLD:
+ debug_printf_exec("%s: sig:%d default SIGCHLD handler\n", __func__, sig);
G.count_SIGCHLD++;
//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
/* Note:
break;
#endif
default: /* ignored: */
+ debug_printf_exec("%s: sig:%d default handling is to ignore\n", __func__, sig);
/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
/* Note:
* We dont do 'last_sig = sig' here -> NOT returning this sig.
/*
- * in_str support
+ * Unicode helper
*/
-static int FAST_FUNC static_get(struct in_str *i)
+static void reinit_unicode_for_hush(void)
{
- int ch = *i->p;
- if (ch != '\0') {
- i->p++;
- i->last_char = ch;
- return ch;
+ /* 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:
+ */
+ if (ENABLE_FEATURE_CHECK_UNICODE_IN_ENV
+ || ENABLE_UNICODE_USING_LOCALE
+ ) {
+ const char *s = get_local_var_value("LC_ALL");
+ if (!s) s = get_local_var_value("LC_CTYPE");
+ if (!s) s = get_local_var_value("LANG");
+ reinit_unicode(s);
}
- return EOF;
}
-static int FAST_FUNC static_peek(struct in_str *i)
-{
- return *i->p;
-}
+/*
+ * in_str support (strings, and "strings" read from files).
+ */
#if ENABLE_HUSH_INTERACTIVE
-
+/* To test correct lineedit/interactive behavior, type from command line:
+ * echo $P\
+ * \
+ * AT\
+ * H\
+ * \
+ * It excercises a lot of corner cases.
+ */
static void cmdedit_update_prompt(void)
{
if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
if (G.PS2 == NULL)
G.PS2 = "> ";
}
-
static const char *setup_prompt_string(int promptmode)
{
const char *prompt_str;
prompt_str = G.PS2;
} else
prompt_str = (promptmode == 0) ? G.PS1 : G.PS2;
- debug_printf("result '%s'\n", prompt_str);
+ debug_printf("prompt_str '%s'\n", prompt_str);
return prompt_str;
}
-
-static void get_user_input(struct in_str *i)
+static int get_user_input(struct in_str *i)
{
int r;
const char *prompt_str;
prompt_str = setup_prompt_string(i->promptmode);
# if ENABLE_FEATURE_EDITING
- /* Enable command line editing only while a command line
- * is actually being read */
do {
- /* 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:
- */
- reinit_unicode(get_local_var_value("LANG"));
-
+ reinit_unicode_for_hush();
G.flag_SIGINT = 0;
/* buglet: SIGINT will not make new prompt to appear _at once_,
* only after <Enter>. (^C will work) */
- r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1);
+ r = read_line_input(G.line_input_state, prompt_str,
+ G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1,
+ /*timeout*/ -1
+ );
/* catch *SIGINT* etc (^C is handled by read_line_input) */
check_and_run_traps();
} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
- i->eof_flag = (r < 0);
- if (i->eof_flag) { /* EOF/error detected */
- G.user_input_buf[0] = EOF; /* yes, it will be truncated, it's ok */
- G.user_input_buf[1] = '\0';
+ if (r < 0) {
+ /* EOF/error detected */
+ i->p = NULL;
+ i->peek_buf[0] = r = EOF;
+ return r;
}
+ i->p = G.user_input_buf;
+ return (unsigned char)*i->p++;
# else
do {
G.flag_SIGINT = 0;
fputs(prompt_str, stdout);
}
fflush_all();
- G.user_input_buf[0] = r = fgetc(i->file);
- /*G.user_input_buf[1] = '\0'; - already is and never changed */
- } while (G.flag_SIGINT);
- i->eof_flag = (r == EOF);
+ r = fgetc(i->file);
+ } while (G.flag_SIGINT || r == '\0');
+ return r;
# endif
- i->p = G.user_input_buf;
}
-
-#endif /* INTERACTIVE */
-
/* This is the magic location that prints prompts
* and gets data back from the user */
+static int fgetc_interactive(struct in_str *i)
+{
+ int ch;
+ /* If it's interactive stdin, get new line. */
+ if (G_interactive_fd && i->file == stdin) {
+ /* Returns first char (or EOF), the rest is in i->p[] */
+ ch = get_user_input(i);
+ i->promptmode = 1; /* PS2 */
+ } else {
+ /* Not stdin: script file, sourced file, etc */
+ do ch = fgetc(i->file); while (ch == '\0');
+ }
+ return ch;
+}
+#else
+static inline int fgetc_interactive(struct in_str *i)
+{
+ int ch;
+ do ch = fgetc(i->file); while (ch == '\0');
+ return ch;
+}
+#endif /* INTERACTIVE */
+
static int FAST_FUNC file_get(struct in_str *i)
{
int ch;
- /* If there is data waiting, eat it up */
- if (i->p && *i->p) {
-#if ENABLE_HUSH_INTERACTIVE
- take_cached:
-#endif
- ch = *i->p++;
- if (i->eof_flag && !*i->p)
- ch = EOF;
- /* note: ch is never NUL */
- } else {
- /* need to double check i->file because we might be doing something
- * more complicated by now, like sourcing or substituting. */
-#if ENABLE_HUSH_INTERACTIVE
- if (G_interactive_fd && i->file == stdin) {
- do {
- get_user_input(i);
- } while (!*i->p); /* need non-empty line */
- i->promptmode = 1; /* PS2 */
- goto take_cached;
- }
+#if ENABLE_FEATURE_EDITING
+ /* This can be stdin, check line editing char[] buffer */
+ if (i->p && *i->p != '\0') {
+ ch = (unsigned char)*i->p++;
+ goto out;
+ }
#endif
- do ch = fgetc(i->file); while (ch == '\0');
+ /* peek_buf[] is an int array, not char. Can contain EOF. */
+ ch = i->peek_buf[0];
+ if (ch != 0) {
+ int ch2 = i->peek_buf[1];
+ i->peek_buf[0] = ch2;
+ if (ch2 == 0) /* very likely, avoid redundant write */
+ goto out;
+ i->peek_buf[1] = 0;
+ goto out;
}
+
+ ch = fgetc_interactive(i);
+ out:
debug_printf("file_get: got '%c' %d\n", ch, ch);
i->last_char = ch;
return ch;
}
-/* All callers guarantee this routine will never
- * be used right after a newline, so prompting is not needed.
- */
static int FAST_FUNC file_peek(struct in_str *i)
{
int ch;
- if (i->p && *i->p) {
- if (i->eof_flag && !i->p[1])
- return EOF;
- return *i->p;
- /* note: ch is never NUL */
+
+#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
+ /* This can be stdin, check line editing char[] buffer */
+ if (i->p && *i->p != '\0')
+ return (unsigned char)*i->p;
+#endif
+ /* peek_buf[] is an int array, not char. Can contain EOF. */
+ ch = i->peek_buf[0];
+ if (ch != 0)
+ return ch;
+
+ /* Need to get a new char */
+ ch = fgetc_interactive(i);
+ debug_printf("file_peek: got '%c' %d\n", ch, ch);
+
+ /* Save it by either rolling back line editing buffer, or in i->peek_buf[0] */
+#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
+ if (i->p) {
+ i->p -= 1;
+ return ch;
}
- do ch = fgetc(i->file); while (ch == '\0');
- i->eof_flag = (ch == EOF);
+#endif
i->peek_buf[0] = ch;
- i->peek_buf[1] = '\0';
- i->p = i->peek_buf;
- debug_printf("file_peek: got '%c' %d\n", ch, ch);
+ /*i->peek_buf[1] = 0; - already is */
+ return ch;
+}
+
+static int FAST_FUNC static_get(struct in_str *i)
+{
+ int ch = (unsigned char)*i->p;
+ if (ch != '\0') {
+ i->p++;
+ i->last_char = ch;
+ return ch;
+ }
+ return EOF;
+}
+
+static int FAST_FUNC static_peek(struct in_str *i)
+{
+ /* Doesn't report EOF on NUL. None of the callers care. */
+ return (unsigned char)*i->p;
+}
+
+/* Only ever called if i_peek() was called, and did not return EOF.
+ * IOW: we know the previous peek saw an ordinary char, not EOF, not NUL,
+ * not end-of-line. Therefore we never need to read a new editing line here.
+ */
+static int i_peek2(struct in_str *i)
+{
+ int ch;
+
+ /* There are two cases when i->p[] buffer exists.
+ * (1) it's a string in_str.
+ * (2) It's a file, and we have a saved line editing buffer.
+ * In both cases, we know that i->p[0] exists and not NUL, and
+ * the peek2 result is in i->p[1].
+ */
+ if (i->p)
+ return (unsigned char)i->p[1];
+
+ /* Now we know it is a file-based in_str. */
+
+ /* peek_buf[] is an int array, not char. Can contain EOF. */
+ /* Is there 2nd char? */
+ ch = i->peek_buf[1];
+ if (ch == 0) {
+ /* We did not read it yet, get it now */
+ do ch = fgetc(i->file); while (ch == '\0');
+ i->peek_buf[1] = ch;
+ }
+
+ debug_printf("file_peek2: got '%c' %d\n", ch, ch);
return ch;
}
static void setup_file_in_str(struct in_str *i, FILE *f)
{
memset(i, 0, sizeof(*i));
- i->peek = file_peek;
i->get = file_get;
+ i->peek = file_peek;
/* i->promptmode = 0; - PS1 (memset did it) */
i->file = f;
/* i->p = NULL; */
static void setup_string_in_str(struct in_str *i, const char *s)
{
memset(i, 0, sizeof(*i));
- i->peek = static_peek;
i->get = static_get;
+ i->peek = static_peek;
/* i->promptmode = 0; - PS1 (memset did it) */
i->p = s;
- /* i->eof_flag = 0; */
}
static void o_grow_by(o_string *o, int len)
{
if (o->length + len > o->maxlen) {
- o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK);
+ o->maxlen += (2 * len) | (B_CHUNK-1);
o->data = xrealloc(o->data, 1 + o->maxlen);
}
}
static void o_addchr(o_string *o, int ch)
{
debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
+ if (o->length < o->maxlen) {
+ /* likely. avoid o_grow_by() call */
+ add:
+ o->data[o->length] = ch;
+ o->length++;
+ o->data[o->length] = '\0';
+ return;
+ }
o_grow_by(o, 1);
- o->data[o->length] = ch;
- o->length++;
+ goto add;
+}
+
+#if 0
+/* Valid only if we know o_string is not empty */
+static void o_delchr(o_string *o)
+{
+ o->length--;
o->data[o->length] = '\0';
}
+#endif
static void o_addblock(o_string *o, const char *str, int len)
{
struct pipe *pi = ctx->pipe;
struct command *command = ctx->command;
+#if 0 /* Instead we emit error message at run time */
+ if (ctx->pending_redirect) {
+ /* For example, "cmd >" (no filename to redirect to) */
+ die_if_script("syntax error: %s", "invalid redirect");
+ ctx->pending_redirect = NULL;
+ }
+#endif
+
if (command) {
if (IS_NULL_CMD(command)) {
debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
old->command->group = ctx->list_head;
old->command->cmd_type = CMD_NORMAL;
# if !BB_MMU
- o_addstr(&old->as_string, ctx->as_string.data);
- o_free_unsafe(&ctx->as_string);
- old->command->group_as_string = xstrdup(old->as_string.data);
- debug_printf_parse("pop, remembering as:'%s'\n",
- old->command->group_as_string);
+ /* At this point, the compound command's string is in
+ * ctx->as_string... except for the leading keyword!
+ * Consider this example: "echo a | if true; then echo a; fi"
+ * ctx->as_string will contain "true; then echo a; fi",
+ * with "if " remaining in old->as_string!
+ */
+ {
+ char *str;
+ int len = old->as_string.length;
+ /* Concatenate halves */
+ o_addstr(&old->as_string, ctx->as_string.data);
+ o_free_unsafe(&ctx->as_string);
+ /* Find where leading keyword starts in first half */
+ str = old->as_string.data + len;
+ if (str > old->as_string.data)
+ str--; /* skip whitespace after keyword */
+ while (str > old->as_string.data && isalpha(str[-1]))
+ str--;
+ /* Ugh, we're done with this horrid hack */
+ old->command->group_as_string = xstrdup(str);
+ debug_printf_parse("pop, remembering as:'%s'\n",
+ old->command->group_as_string);
+ }
# endif
*ctx = *old; /* physical copy */
free(old);
debug_printf_parse("duplicating redirect '%d>&%d'\n",
redir->rd_fd, redir->rd_dup);
} else {
+#if 0 /* Instead we emit error message at run time */
+ if (ctx->pending_redirect) {
+ /* For example, "cmd > <file" */
+ die_if_script("syntax error: %s", "invalid redirect");
+ }
+#endif
/* Set ctx->pending_redirect, so we know what to do at the
* end of the next parsed word. */
ctx->pending_redirect = redir;
/* command remains "open", available for possible redirects */
}
+static int i_getch_and_eat_bkslash_nl(struct in_str *input)
+{
+ for (;;) {
+ int ch, ch2;
+
+ ch = i_getch(input);
+ if (ch != '\\')
+ return ch;
+ ch2 = i_peek(input);
+ if (ch2 != '\n')
+ return ch;
+ /* backslash+newline, skip it */
+ i_getch(input);
+ }
+}
+
+static int i_peek_and_eat_bkslash_nl(struct in_str *input)
+{
+ for (;;) {
+ int ch, ch2;
+
+ ch = i_peek(input);
+ if (ch != '\\')
+ return ch;
+ ch2 = i_peek2(input);
+ if (ch2 != '\n')
+ return ch;
+ /* backslash+newline, skip it */
+ i_getch(input);
+ i_getch(input);
+ }
+}
+
#if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS
/* Subroutines for copying $(...) and `...` things */
static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
if (!dbl)
break;
/* we look for closing )) of $((EXPR)) */
- if (i_peek(input) == end_ch) {
+ if (i_peek_and_eat_bkslash_nl(input) == end_ch) {
i_getch(input); /* eat second ')' */
break;
}
syntax_error_unterm_ch(')');
return 0;
}
+#if 0
+ if (ch == '\n') {
+ /* "backslash+newline", ignore both */
+ o_delchr(dest); /* undo insertion of '\' */
+ continue;
+ }
+#endif
o_addchr(dest, ch);
continue;
}
o_string *dest,
struct in_str *input, unsigned char quote_mask)
{
- int ch = i_peek(input); /* first character after the $ */
+ int ch = i_peek_and_eat_bkslash_nl(input); /* first character after the $ */
debug_printf_parse("parse_dollar entered: ch='%c'\n", ch);
if (isalpha(ch)) {
debug_printf_parse(": '%c'\n", ch);
o_addchr(dest, ch | quote_mask);
quote_mask = 0;
- ch = i_peek(input);
- if (!isalnum(ch) && ch != '_')
+ ch = i_peek_and_eat_bkslash_nl(input);
+ if (!isalnum(ch) && ch != '_') {
+ /* End of variable name reached */
break;
+ }
ch = i_getch(input);
nommu_addchr(as_string, ch);
}
ch = i_getch(input); /* eat '{' */
nommu_addchr(as_string, ch);
- ch = i_getch(input); /* first char after '{' */
+ ch = i_getch_and_eat_bkslash_nl(input); /* first char after '{' */
/* It should be ${?}, or ${#var},
* or even ${?+subst} - operator acting on a special variable,
* or the beginning of variable name.
ch = i_getch(input);
nommu_addchr(as_string, ch);
# if ENABLE_SH_MATH_SUPPORT
- if (i_peek(input) == '(') {
+ if (i_peek_and_eat_bkslash_nl(input) == '(') {
ch = i_getch(input);
nommu_addchr(as_string, ch);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
case '_':
ch = i_getch(input);
nommu_addchr(as_string, ch);
- ch = i_peek(input);
+ ch = i_peek_and_eat_bkslash_nl(input);
if (isalnum(ch)) { /* it's $_name or $_123 */
ch = '_';
goto make_var;
pi = NULL;
}
#if !BB_MMU
- debug_printf_parse("as_string '%s'\n", ctx.as_string.data);
+ debug_printf_parse("as_string1 '%s'\n", ctx.as_string.data);
if (pstring)
*pstring = ctx.as_string.data;
else
) {
o_free(&dest);
#if !BB_MMU
- debug_printf_parse("as_string '%s'\n", ctx.as_string.data);
+ debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data);
if (pstring)
*pstring = ctx.as_string.data;
else
* with redirect_opt_num(), but bash doesn't do it.
* "echo foo 2| cat" yields "foo 2". */
done_command(&ctx);
-#if !BB_MMU
- o_reset_to_empty_unquoted(&ctx.as_string);
-#endif
}
goto new_cmd;
case '(':
/* Handle any expansions */
if (exp_op == 'L') {
+ reinit_unicode_for_hush();
debug_printf_expand("expand: length(%s)=", val);
- val = utoa(val ? strlen(val) : 0);
+ val = utoa(val ? unicode_strlen(val) : 0);
debug_printf_expand("%s\n", val);
} else if (exp_op) {
if (exp_op == '%' || exp_op == '#') {
!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
}
break;
-
} /* switch (char after <SPECIAL_VAR_SYMBOL>) */
if (val && val[0]) {
n++;
}
}
- overlapping_strcpy((char*)list, list[0]);
+ overlapping_strcpy((char*)list, list[0] ? list[0] : "");
debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
return (char*)list;
}
debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
run_and_free_list(pipe_list);
empty = 0;
-#if ENABLE_HUSH_FUNCTIONS
- if (G.flag_return_in_progress == 1)
+ if (G_flag_return_in_progress == 1)
break;
-#endif
}
}
* Our solution: ONLY bare $(trap) or `trap` is special.
*/
s = skip_whitespace(s);
- if (strncmp(s, "trap", 4) == 0
+ if (is_prefixed_with(s, "trap")
&& skip_whitespace(s + 4)[0] == '\0'
) {
static const char *const argv[] = { NULL, NULL };
builtin_trap((char**)argv);
- exit(0); /* not _exit() - we need to fflush */
+ fflush_all(); /* important */
+ _exit(0);
}
# if BB_MMU
reset_traps_to_defaults();
free(to_free);
# endif
close(channel[1]);
- close_on_exec_on(channel[0]);
- return xfdopen_for_read(channel[0]);
+ return remember_FILE(xfdopen_for_read(channel[0]));
}
/* Return code is exit status of the process that is run. */
}
debug_printf("done reading from `cmd` pipe, closing it\n");
- fclose(fp);
+ fclose_and_forget(fp);
/* We need to extract exitcode. Test case
* "true; echo `sleep 1; false` $?"
* should print 1 */
wait(NULL); /* wait till child has died */
}
+/* fd: redirect wants this fd to be used (e.g. 3>file).
+ * Move all conflicting internally used fds,
+ * and remember them so that we can restore them later.
+ */
+static int save_fds_on_redirect(int fd, int squirrel[3])
+{
+ if (squirrel) {
+ /* Handle redirects of fds 0,1,2 */
+
+ /* If we collide with an already moved stdio fd... */
+ if (fd == squirrel[0]) {
+ squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD);
+ return 1;
+ }
+ if (fd == squirrel[1]) {
+ squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD);
+ return 1;
+ }
+ if (fd == squirrel[2]) {
+ squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD);
+ return 1;
+ }
+ /* If we are about to redirect stdio fd, and did not yet move it... */
+ if (fd <= 2 && squirrel[fd] < 0) {
+ /* We avoid taking stdio fds */
+ squirrel[fd] = fcntl(fd, F_DUPFD, 10);
+ if (squirrel[fd] < 0 && errno != EBADF)
+ xfunc_die();
+ return 0; /* "we did not close fd" */
+ }
+ }
+
+#if ENABLE_HUSH_INTERACTIVE
+ if (fd != 0 && fd == G.interactive_fd) {
+ G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC);
+ return 1;
+ }
+#endif
+
+ /* Are we called from setup_redirects(squirrel==NULL)? Two cases:
+ * (1) Redirect in a forked child. No need to save FILEs' fds,
+ * we aren't going to use them anymore, ok to trash.
+ * (2) "exec 3>FILE". Bummer. We can save FILEs' fds,
+ * but how are we doing to use them?
+ * "fileno(fd) = new_fd" can't be done.
+ */
+ if (!squirrel)
+ return 0;
+
+ return save_FILEs_on_redirect(fd);
+}
+
+static void restore_redirects(int squirrel[3])
+{
+ int i, fd;
+ for (i = 0; i <= 2; i++) {
+ fd = squirrel[i];
+ if (fd != -1) {
+ /* We simply die on error */
+ xmove_fd(fd, i);
+ }
+ }
+
+ /* Moved G.interactive_fd stays on new fd, not doing anything for it */
+
+ restore_redirected_FILEs();
+}
+
/* squirrel != NULL means we squirrel away copies of stdin, stdout,
* and stderr if they are redirected. */
static int setup_redirects(struct command *prog, int squirrel[])
for (redir = prog->redirects; redir; redir = redir->next) {
if (redir->rd_type == REDIRECT_HEREDOC2) {
- /* rd_fd<<HERE case */
- if (squirrel && redir->rd_fd < 3
- && squirrel[redir->rd_fd] < 0
- ) {
- squirrel[redir->rd_fd] = dup(redir->rd_fd);
- }
+ /* "rd_fd<<HERE" case */
+ save_fds_on_redirect(redir->rd_fd, squirrel);
/* for REDIRECT_HEREDOC2, rd_filename holds _contents_
* of the heredoc */
debug_printf_parse("set heredoc '%s'\n",
}
if (redir->rd_dup == REDIRFD_TO_FILE) {
- /* rd_fd<*>file case (<*> is <,>,>>,<>) */
+ /* "rd_fd<*>file" case (<*> is <,>,>>,<>) */
char *p;
if (redir->rd_filename == NULL) {
- /* Something went wrong in the parse.
- * Pretend it didn't happen */
- bb_error_msg("bug in redirect parse");
+ /*
+ * Examples:
+ * "cmd >" (no filename)
+ * "cmd > <file" (2nd redirect starts too early)
+ */
+ die_if_script("syntax error: %s", "invalid redirect");
continue;
}
mode = redir_table[redir->rd_type].mode;
openfd = open_or_warn(p, mode);
free(p);
if (openfd < 0) {
- /* this could get lost if stderr has been redirected, but
- * bash and ash both lose it as well (though zsh doesn't!) */
-//what the above comment tries to say?
+ /* Error message from open_or_warn can be lost
+ * if stderr has been redirected, but bash
+ * and ash both lose it as well
+ * (though zsh doesn't!)
+ */
return 1;
}
} else {
- /* rd_fd<*>rd_dup or rd_fd<*>- cases */
+ /* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */
openfd = redir->rd_dup;
}
if (openfd != redir->rd_fd) {
- if (squirrel && redir->rd_fd < 3
- && squirrel[redir->rd_fd] < 0
- ) {
- squirrel[redir->rd_fd] = dup(redir->rd_fd);
- }
+ int closed = save_fds_on_redirect(redir->rd_fd, squirrel);
if (openfd == REDIRFD_CLOSE) {
- /* "n>-" means "close me" */
- close(redir->rd_fd);
+ /* "rd_fd >&-" means "close me" */
+ if (!closed) {
+ /* ^^^ optimization: saving may already
+ * have closed it. If not... */
+ close(redir->rd_fd);
+ }
} else {
xdup2(openfd, redir->rd_fd);
if (redir->rd_dup == REDIRFD_TO_FILE)
+ /* "rd_fd > FILE" */
close(openfd);
+ /* else: "rd_fd > rd_dup" */
}
}
}
return 0;
}
-static void restore_redirects(int squirrel[])
-{
- int i, fd;
- for (i = 0; i < 3; i++) {
- fd = squirrel[i];
- if (fd != -1) {
- /* We simply die on error */
- xmove_fd(fd, i);
- }
- }
-}
-
static char *find_in_path(const char *arg)
{
char *ret = NULL;
save_and_replace_G_args(&sv, argv);
/* "we are in function, ok to use return" */
- sv_flg = G.flag_return_in_progress;
- G.flag_return_in_progress = -1;
+ sv_flg = G_flag_return_in_progress;
+ G_flag_return_in_progress = -1;
# if ENABLE_HUSH_LOCAL
G.func_nest_level++;
# endif
G.func_nest_level--;
}
# endif
- G.flag_return_in_progress = sv_flg;
+ G_flag_return_in_progress = sv_flg;
restore_G_args(&sv, argv);
* Never returns.
* Don't exit() here. If you don't exec, use _exit instead.
* The at_exit handlers apparently confuse the calling process,
- * in particular stdin handling. Not sure why? -- because of vfork! (vda) */
+ * in particular stdin handling. Not sure why? -- because of vfork! (vda)
+ */
static void pseudo_exec_argv(nommu_save_t *nommu_save,
char **argv, int assignment_cnt,
char **argv_expanded) NORETURN;
if (a >= 0) {
# if BB_MMU /* see above why on NOMMU it is not allowed */
if (APPLET_IS_NOEXEC(a)) {
+ /* Do not leak open fds from opened script files etc */
+ close_all_FILE_list();
debug_printf_exec("running applet '%s'\n", argv[0]);
run_applet_no_and_exit(a, argv);
}
int sig = WTERMSIG(status);
if (i == fg_pipe->num_cmds-1)
/* TODO: use strsignal() instead for bash compat? but that's bloat... */
- printf("%s\n", sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig));
+ puts(sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig));
/* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */
/* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
* Maybe we need to use sig | 128? */
if (x->b_function == builtin_exec && argv_expanded[1] == NULL) {
debug_printf("exec with redirects only\n");
rcode = setup_redirects(command, NULL);
+ /* rcode=1 can be if redir file can't be opened */
goto clean_up_and_ret1;
}
}
if (pipefds.rd > 1)
close(pipefds.rd);
/* Like bash, explicit redirects override pipes,
- * and the pipe fd is available for dup'ing. */
- if (setup_redirects(command, NULL))
+ * and the pipe fd (fd#1) is available for dup'ing:
+ * "cmd1 2>&1 | cmd2": fd#1 is duped to fd#2, thus stderr
+ * of cmd1 goes into pipe.
+ */
+ if (setup_redirects(command, NULL)) {
+ /* Happens when redir file can't be opened:
+ * $ hush -c 'echo FOO >&2 | echo BAR 3>/qwe/rty; echo BAZ'
+ * FOO
+ * hush: can't open '/qwe/rty': No such file or directory
+ * BAZ
+ * (echo BAR is not executed, it hits _exit(1) below)
+ */
_exit(1);
+ }
/* Stores to nommu_save list of env vars putenv'ed
* (NOMMU, on MMU we don't need that) */
for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
if (G.flag_SIGINT)
break;
+ if (G_flag_return_in_progress == 1)
+ break;
IF_HAS_KEYWORDS(rword = pi->res_word;)
debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
* and we should not execute CMD */
debug_printf_exec("skipped cmd because of || or &&\n");
last_followup = pi->followup;
- continue;
+ goto dont_check_jobs_but_continue;
}
}
last_followup = pi->followup;
G.flag_break_continue = 0;
/* else: e.g. "continue 2" should *break* once, *then* continue */
} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
- if (G.depth_break_continue != 0 || fbc == BC_BREAK)
- goto check_jobs_and_break;
+ if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
+ checkjobs(NULL);
+ break;
+ }
/* "continue": simulate end of loop */
rword = RES_DONE;
continue;
}
#endif
-#if ENABLE_HUSH_FUNCTIONS
- if (G.flag_return_in_progress == 1) {
- /* same as "goto check_jobs_and_break" */
+ if (G_flag_return_in_progress == 1) {
checkjobs(NULL);
break;
}
-#endif
} else if (pi->followup == PIPE_BG) {
/* What does bash do with attempts to background builtins? */
/* even bash 3.2 doesn't do that well with nested bg:
if (rword == RES_IF || rword == RES_ELIF)
cond_code = rcode;
#endif
+ check_jobs_and_continue:
+ checkjobs(NULL);
+ dont_check_jobs_but_continue: ;
#if ENABLE_HUSH_LOOPS
/* Beware of "while false; true; do ..."! */
if (pi->next
/* "while false; do...done" - exitcode 0 */
G.last_exitcode = rcode = EXIT_SUCCESS;
debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
- goto check_jobs_and_break;
+ break;
}
}
if (rword == RES_UNTIL) {
if (!rcode) {
debug_printf_exec(": until expr is true: breaking\n");
- check_jobs_and_break:
- checkjobs(NULL);
break;
}
}
}
#endif
-
- check_jobs_and_continue:
- checkjobs(NULL);
} /* for (pi) */
#if ENABLE_HUSH_JOB
INIT_G();
if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */
G.last_exitcode = EXIT_SUCCESS;
+
#if ENABLE_HUSH_FAST
G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
#endif
/* Export PWD */
set_pwd_var(/*exp:*/ 1);
+
+#if ENABLE_HUSH_BASH_COMPAT
+ /* Set (but not export) HOSTNAME unless already set */
+ if (!get_local_var_value("HOSTNAME")) {
+ struct utsname uts;
+ uname(&uts);
+ set_local_var_from_halves("HOSTNAME", uts.nodename);
+ }
/* bash also exports SHLVL and _,
* and sets (but doesn't export) the following variables:
* BASH=/bin/bash
* HOSTTYPE=i386
* MACHTYPE=i386-pc-linux-gnu
* OSTYPE=linux-gnu
- * HOSTNAME=<xxxxxxxxxx>
* PPID=<NNNNN> - we also do it elsewhere
* EUID=<NNNNN>
* UID=<NNNNN>
* PS2='> '
* PS4='+ '
*/
+#endif
#if ENABLE_FEATURE_EDITING
G.line_input_state = new_line_input_t(FOR_SHELL);
/* Initialize some more globals to non-zero values */
cmdedit_update_prompt();
- if (setjmp(die_jmp)) {
- /* xfunc has failed! die die die */
- /* no EXIT traps, this is an escape hatch! */
- G.exiting = 1;
- hush_exit(xfunc_error_retval);
- }
+ die_func = restore_ttypgrp_and__exit;
/* Shell is non-interactive at first. We need to call
* install_special_sighandlers() if we are going to execute "sh <script>",
debug_printf("sourcing /etc/profile\n");
input = fopen_for_read("/etc/profile");
if (input != NULL) {
- close_on_exec_on(fileno(input));
+ remember_FILE(input);
install_special_sighandlers();
parse_and_run_file(input);
- fclose(input);
+ fclose_and_forget(input);
}
/* bash: after sourcing /etc/profile,
* tries to source (in the given order):
G.global_argv++;
debug_printf("running script '%s'\n", G.global_argv[0]);
input = xfopen_for_read(G.global_argv[0]);
- close_on_exec_on(fileno(input));
+ remember_FILE(input);
install_special_sighandlers();
parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP
- fclose(input);
+ fclose_and_forget(input);
#endif
goto final_return;
}
/* Grab control of the terminal */
tcsetpgrp(G_interactive_fd, getpid());
}
- /* -1 is special - makes xfuncs longjmp, not exit
- * (we reset die_sleep = 0 whereever we [v]fork) */
- enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
+ enable_restore_tty_pgrp_on_exit();
# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
{
int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int msh_main(int argc, char **argv)
{
- //bb_error_msg("msh is deprecated, please use hush instead");
+ bb_error_msg("msh is deprecated, please use hush instead");
return hush_main(argc, argv);
}
#endif
}
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM)
+{
+ show_history(G.line_input_state);
+ return EXIT_SUCCESS;
+}
+#endif
+
#if ENABLE_HUSH_JOB
static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
{
if (arg_path)
filename = arg_path;
}
- input = fopen_or_warn(filename, "r");
+ input = remember_FILE(fopen_or_warn(filename, "r"));
free(arg_path);
if (!input) {
/* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
*/
return EXIT_FAILURE;
}
- close_on_exec_on(fileno(input));
#if ENABLE_HUSH_FUNCTIONS
- sv_flg = G.flag_return_in_progress;
+ sv_flg = G_flag_return_in_progress;
/* "we are inside sourced file, ok to use return" */
- G.flag_return_in_progress = -1;
+ G_flag_return_in_progress = -1;
#endif
if (argv[1])
save_and_replace_G_args(&sv, argv);
+ /* "false; . ./empty_line; echo Zero:$?" should print 0 */
+ G.last_exitcode = 0;
parse_and_run_file(input);
- fclose(input);
+ fclose_and_forget(input);
if (argv[1])
restore_G_args(&sv, argv);
#if ENABLE_HUSH_FUNCTIONS
- G.flag_return_in_progress = sv_flg;
+ G_flag_return_in_progress = sv_flg;
#endif
return G.last_exitcode;
int rc;
mode_t mask;
+ rc = 1;
mask = umask(0);
argv = skip_dash_dash(argv);
if (argv[0]) {
mode_t old_mask = mask;
- mask ^= 0777;
- rc = bb_parse_mode(argv[0], &mask);
- mask ^= 0777;
- if (rc == 0) {
+ /* numeric umasks are taken as-is */
+ /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */
+ if (!isdigit(argv[0][0]))
+ mask ^= 0777;
+ mask = bb_parse_mode(argv[0], mask);
+ if (!isdigit(argv[0][0]))
+ mask ^= 0777;
+ if ((unsigned)mask > 0777) {
mask = old_mask;
/* bash messages:
* bash: umask: 'q': invalid symbolic mode operator
* bash: umask: 999: octal number out of range
*/
bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]);
+ rc = 0;
}
} else {
- rc = 1;
/* Mimic bash */
printf("%04o\n", (unsigned) mask);
/* fall through and restore mask which we set to 0 */
return EXIT_FAILURE;
}
if (waitpid(pid, &status, 0) == pid) {
+ ret = WEXITSTATUS(status);
if (WIFSIGNALED(status))
ret = 128 + WTERMSIG(status);
- else if (WIFEXITED(status))
- ret = WEXITSTATUS(status);
- else /* wtf? */
- ret = EXIT_FAILURE;
} else {
bb_perror_msg("wait %s", *argv);
ret = 127;
unsigned depth;
if (G.depth_of_loop == 0) {
bb_error_msg("%s: only meaningful in a loop", argv[0]);
+ /* if we came from builtin_continue(), need to undo "= 1" */
+ G.flag_break_continue = 0;
return EXIT_SUCCESS; /* bash compat */
}
- G.flag_break_continue++; /* BC_BREAK = 1 */
+ G.flag_break_continue++; /* BC_BREAK = 1, or BC_CONTINUE = 2 */
G.depth_break_continue = depth = parse_numeric_argv1(argv, 1, 1);
if (depth == UINT_MAX)
{
int rc;
- if (G.flag_return_in_progress != -1) {
+ if (G_flag_return_in_progress != -1) {
bb_error_msg("%s: not in a function or sourced script", argv[0]);
return EXIT_FAILURE; /* bash compat */
}
- G.flag_return_in_progress = 1;
+ G_flag_return_in_progress = 1;
/* bash:
* out of range: wraps around at 256, does not error out