tls: code shrink
[oweals/busybox.git] / shell / ash.c
index 70a278f42e34b37a6c65c805a0e6a3f5b7736fc9..04e4006c89d4b0a1b6680eb6e7d1a63090c0927d 100644 (file)
 //config:      you to run the specified command or builtin,
 //config:      even when there is a function with the same name.
 //config:
+//config:config ASH_EMBEDDED_SCRIPTS
+//config:      bool "Embed scripts in the binary"
+//config:      default y
+//config:      depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:      help
+//config:      Allow scripts to be compressed and embedded in the busybox
+//config:      binary. The scripts should be placed in the 'embed' directory
+//config:      at build time. Like applets, scripts can be run as
+//config:      'busybox SCRIPT ...' or by linking their name to the binary.
+//config:
+//config:      This also allows applets to be implemented as scripts: place
+//config:      the script in 'applets_sh' and a stub C file containing
+//config:      configuration in the appropriate subsystem directory.
+//config:
 //config:endif # ash options
 
 //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
 
 #define JOBS ENABLE_ASH_JOB_CONTROL
 
-#include <setjmp.h>
 #include <fnmatch.h>
 #include <sys/times.h>
 #include <sys/utsname.h> /* for setting $HOSTNAME */
 #include "busybox.h" /* for applet_names */
+#if ENABLE_ASH_EMBEDDED_SCRIPTS
+# include "embedded_scripts.h"
+#else
+# define NUM_SCRIPTS 0
+#endif
 
 /* So far, all bash compat is controlled by one config option */
 /* Separate defines document which part of code implements what */
 #define IF_BASH_PATTERN_SUBST       IF_ASH_BASH_COMPAT
 #define    BASH_SUBSTR          ENABLE_ASH_BASH_COMPAT
 #define IF_BASH_SUBSTR              IF_ASH_BASH_COMPAT
-/* [[ EXPR ]] */
+/* BASH_TEST2: [[ EXPR ]]
+ * Status of [[ support:
+ * We replace && and || with -a and -o
+ * TODO:
+ * singleword+noglob expansion:
+ *   v='a b'; [[ $v = 'a b' ]]; echo 0:$?
+ *   [[ /bin/n* ]]; echo 0:$?
+ * -a/-o are not AND/OR ops! (they are just strings)
+ * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
+ * = is glob match operator, not equality operator: STR = GLOB
+ * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
+ * == same as =
+ * add =~ regex match operator: STR =~ REGEX
+ */
 #define    BASH_TEST2           (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST)
 #define    BASH_SOURCE          ENABLE_ASH_BASH_COMPAT
 #define    BASH_PIPEFAIL        ENABLE_ASH_BASH_COMPAT
@@ -2393,13 +2424,12 @@ setvar(const char *name, const char *val, int flags)
        }
 
        INT_OFF;
-       nameeq = ckmalloc(namelen + vallen + 2);
+       nameeq = ckzalloc(namelen + vallen + 2);
        p = mempcpy(nameeq, name, namelen);
        if (val) {
                *p++ = '=';
-               p = mempcpy(p, val, vallen);
+               memcpy(p, val, vallen);
        }
-       *p = '\0';
        vp = setvareq(nameeq, flags | VNOSAVE);
        INT_ON;
 
@@ -5391,7 +5421,7 @@ openredirect(union node *redir)
                                f = open(fname, O_WRONLY, 0666);
                                if (f < 0)
                                        goto ecreate;
-                               if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+                               if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) {
                                        close(f);
                                        errno = EEXIST;
                                        goto ecreate;
@@ -5890,7 +5920,7 @@ static int substr_atoi(const char *s)
 #define EXP_CASE        0x10    /* keeps quotes around for CASE pattern */
 #define EXP_VARTILDE2   0x20    /* expand tildes after colons only */
 #define EXP_WORD        0x40    /* expand word in parameter expansion */
-#define EXP_QUOTED      0x80    /* expand word in double quotes */
+#define EXP_QUOTED      0x100   /* expand word in double quotes */
 /*
  * rmescape() flags
  */
@@ -5975,7 +6005,10 @@ ifsbreakup(char *string, struct arglist *arglist)
                realifs = ifsset() ? ifsval() : defifs;
                ifsp = &ifsfirst;
                do {
+                       int afternul;
+
                        p = string + ifsp->begoff;
+                       afternul = nulonly;
                        nulonly = ifsp->nulonly;
                        ifs = nulonly ? nullstr : realifs;
                        ifsspc = 0;
@@ -5987,7 +6020,7 @@ ifsbreakup(char *string, struct arglist *arglist)
                                        p++;
                                        continue;
                                }
-                               if (!nulonly)
+                               if (!(afternul || nulonly))
                                        ifsspc = (strchr(defifs, *p) != NULL);
                                /* Ignore IFS whitespace at start */
                                if (q == start && ifsspc) {
@@ -6221,9 +6254,7 @@ memtodest(const char *p, size_t len, int syntax, int quotes)
                        if (quotes & QUOTES_ESC) {
                                int n = SIT(c, syntax);
                                if (n == CCTL
-                                || (((quotes & EXP_FULL) || syntax != BASESYNTAX)
-                                    && n == CBACK
-                                   )
+                                || (syntax != BASESYNTAX && n == CBACK)
                                ) {
                                        USTPUTC(CTLESC, q);
                                }
@@ -6650,12 +6681,12 @@ argstr(char *p, int flags)
                case CTLENDVAR: /* ??? */
                        goto breakloop;
                case CTLQUOTEMARK:
-                       inquotes ^= EXP_QUOTED;
                        /* "$@" syntax adherence hack */
-                       if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
-                               p = evalvar(p + 1, flags | inquotes) + 1;
+                       if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
+                               p = evalvar(p + 1, flags | EXP_QUOTED) + 1;
                                goto start;
                        }
+                       inquotes ^= EXP_QUOTED;
  addquote:
                        if (flags & QUOTES_ESC) {
                                p--;
@@ -6839,8 +6870,15 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
                /* Find '/' and replace with NUL */
                repl = p;
+               /* The pattern can't be empty.
+                * IOW: if the first char after "${v//" is a slash,
+                * it does not terminate the pattern - it's the first char of the pattern:
+                *  v=/dev/ram; echo ${v////-}  prints -dev-ram (pattern is "/")
+                *  v=/dev/ram; echo ${v///r/-} prints /dev-am  (pattern is "/r")
+                */
+               if (*repl == '/')
+                       repl++;
                for (;;) {
-                       /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
                        if (*repl == '\0') {
                                repl = NULL;
                                break;
@@ -6849,6 +6887,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
                                *repl = '\0';
                                break;
                        }
+                       /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
                        if ((unsigned char)*repl == CTLESC && repl[1])
                                repl++;
                        repl++;
@@ -7152,14 +7191,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
  * ash -c 'echo ${#1#}'  name:'1=#'
  */
 static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, int *quotedp)
+varvalue(char *name, int varflags, int flags, int quoted)
 {
        const char *p;
        int num;
        int i;
        ssize_t len = 0;
        int sep;
-       int quoted = *quotedp;
        int subtype = varflags & VSTYPE;
        int discard = subtype == VSPLUS || subtype == VSLENGTH;
        int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
@@ -7207,13 +7245,27 @@ varvalue(char *name, int varflags, int flags, int *quotedp)
        case '*': {
                char **ap;
                char sepc;
+               char c;
 
-               if (quoted)
-                       sep = 0;
-               sep |= ifsset() ? ifsval()[0] : ' ';
+               /* We will set c to 0 or ~0 depending on whether
+                * we're doing field splitting.  We won't do field
+                * splitting if either we're quoted or sep is zero.
+                *
+                * Instead of testing (quoted || !sep) the following
+                * trick optimises away any branches by using the
+                * fact that EXP_QUOTED (which is the only bit that
+                * can be set in quoted) is the same as EXP_FULL <<
+                * CHAR_BIT (which is the only bit that can be set
+                * in sep).
+                */
+#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
+#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
+#endif
+               c = !((quoted | ~sep) & EXP_QUOTED) - 1;
+               sep &= ~quoted;
+               sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
  param:
                sepc = sep;
-               *quotedp = !sepc;
                ap = shellparam.p;
                if (!ap)
                        return -1;
@@ -7278,7 +7330,6 @@ evalvar(char *p, int flag)
        char varflags;
        char subtype;
        int quoted;
-       char easy;
        char *var;
        int patloc;
        int startloc;
@@ -7292,12 +7343,11 @@ evalvar(char *p, int flag)
 
        quoted = flag & EXP_QUOTED;
        var = p;
-       easy = (!quoted || (*var == '@' && shellparam.nparam));
        startloc = expdest - (char *)stackblock();
        p = strchr(p, '=') + 1; //TODO: use var_end(p)?
 
  again:
-       varlen = varvalue(var, varflags, flag, &quoted);
+       varlen = varvalue(var, varflags, flag, quoted);
        if (varflags & VSNUL)
                varlen--;
 
@@ -7343,8 +7393,11 @@ evalvar(char *p, int flag)
 
        if (subtype == VSNORMAL) {
  record:
-               if (!easy)
-                       goto end;
+               if (quoted) {
+                       quoted = *var == '@' && shellparam.nparam;
+                       if (!quoted)
+                               goto end;
+               }
                recordregion(startloc, expdest - (char *)stackblock(), quoted);
                goto end;
        }
@@ -7560,9 +7613,16 @@ expandmeta(struct strlist *str /*, int flag*/)
 /*
  * Do metacharacter (i.e. *, ?, [...]) expansion.
  */
+typedef struct exp_t {
+       char *dir;
+       unsigned dir_max;
+} exp_t;
 static void
-expmeta(char *expdir, char *enddir, char *name)
+expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
 {
+#define expdir exp->dir
+#define expdir_max exp->dir_max
+       char *enddir = expdir + expdir_len;
        char *p;
        const char *cp;
        char *start;
@@ -7595,7 +7655,7 @@ expmeta(char *expdir, char *enddir, char *name)
                                }
                        }
                } else {
-                       if (*p == '\\')
+                       if (*p == '\\' && p[1])
                                esc++;
                        if (p[esc] == '/') {
                                if (metaflag)
@@ -7605,15 +7665,15 @@ expmeta(char *expdir, char *enddir, char *name)
                }
        }
        if (metaflag == 0) {    /* we've reached the end of the file name */
-               if (enddir != expdir)
-                       metaflag++;
+               if (!expdir_len)
+                       return;
                p = name;
                do {
-                       if (*p == '\\')
+                       if (*p == '\\' && p[1])
                                p++;
                        *enddir++ = *p;
                } while (*p++);
-               if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+               if (lstat(expdir, &statb) == 0)
                        addfname(expdir);
                return;
        }
@@ -7621,24 +7681,19 @@ expmeta(char *expdir, char *enddir, char *name)
        if (name < start) {
                p = name;
                do {
-                       if (*p == '\\')
+                       if (*p == '\\' && p[1])
                                p++;
                        *enddir++ = *p++;
                } while (p < start);
        }
-       if (enddir == expdir) {
+       *enddir = '\0';
+       cp = expdir;
+       expdir_len = enddir - cp;
+       if (!expdir_len)
                cp = ".";
-       } else if (enddir == expdir + 1 && *expdir == '/') {
-               cp = "/";
-       } else {
-               cp = expdir;
-               enddir[-1] = '\0';
-       }
        dirp = opendir(cp);
        if (dirp == NULL)
                return;
-       if (enddir != expdir)
-               enddir[-1] = '/';
        if (*endname == 0) {
                atend = 1;
        } else {
@@ -7646,6 +7701,7 @@ expmeta(char *expdir, char *enddir, char *name)
                *endname = '\0';
                endname += esc + 1;
        }
+       name_len -= endname - name;
        matchdot = 0;
        p = start;
        if (*p == '\\')
@@ -7660,16 +7716,30 @@ expmeta(char *expdir, char *enddir, char *name)
                                strcpy(enddir, dp->d_name);
                                addfname(expdir);
                        } else {
-                               for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
-                                       continue;
-                               p[-1] = '/';
-                               expmeta(expdir, p, endname);
+                               unsigned offset;
+                               unsigned len;
+
+                               p = stpcpy(enddir, dp->d_name);
+                               *p = '/';
+
+                               offset = p - expdir + 1;
+                               len = offset + name_len + NAME_MAX;
+                               if (len > expdir_max) {
+                                       len += PATH_MAX;
+                                       expdir = ckrealloc(expdir, len);
+                                       expdir_max = len;
+                               }
+
+                               expmeta(exp, endname, name_len, offset);
+                               enddir = expdir + expdir_len;
                        }
                }
        }
        closedir(dirp);
        if (!atend)
                endname[-esc - 1] = esc ? '\\' : '/';
+#undef expdir
+#undef expdir_max
 }
 
 static struct strlist *
@@ -7742,10 +7812,11 @@ expandmeta(struct strlist *str /*, int flag*/)
        /* TODO - EXP_REDIR */
 
        while (str) {
-               char *expdir;
+               exp_t exp;
                struct strlist **savelastp;
                struct strlist *sp;
                char *p;
+               unsigned len;
 
                if (fflag)
                        goto nometa;
@@ -7755,13 +7826,12 @@ expandmeta(struct strlist *str /*, int flag*/)
 
                INT_OFF;
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
-               {
-                       int i = strlen(str->text);
-//BUGGY estimation of how long expanded name can be
-                       expdir = ckmalloc(i < 2048 ? 2048 : i+1);
-               }
-               expmeta(expdir, expdir, p);
-               free(expdir);
+               len = strlen(p);
+               exp.dir_max = len + PATH_MAX;
+               exp.dir = ckmalloc(exp.dir_max);
+
+               expmeta(&exp, p, len, 0);
+               free(exp.dir);
                if (p != str->text)
                        free(p);
                INT_ON;
@@ -7972,6 +8042,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c
 #else
        execve(cmd, argv, envp);
 #endif
+
        if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
                /* Run "cmd" as a shell script:
                 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
@@ -8044,15 +8115,15 @@ static void shellexec(char *prog, char **argv, const char *path, int idx)
 
        /* Map to POSIX errors */
        switch (e) {
-       case EACCES:
+       default:
                exerrno = 126;
                break;
+       case ELOOP:
+       case ENAMETOOLONG:
        case ENOENT:
+       case ENOTDIR:
                exerrno = 127;
                break;
-       default:
-               exerrno = 2;
-               break;
        }
        exitstatus = exerrno;
        TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
@@ -8480,7 +8551,8 @@ describe_command(char *command, const char *path, int describe_command_verbose)
 
        case CMDFUNCTION:
                if (describe_command_verbose) {
-                       out1str(" is a shell function");
+                       /*out1str(" is a shell function");*/
+                       out1str(" is a function"); /* bash says this */
                } else {
                        out1str(command);
                }
@@ -9559,9 +9631,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
        shellparam.optind = 1;
        shellparam.optoff = -1;
 #endif
-       pushlocalvars();
        evaltree(func->n.ndefun.body, flags & EV_TESTED);
-       poplocalvars(0);
  funcdone:
        INT_OFF;
        funcline = savefuncline;
@@ -9889,6 +9959,7 @@ find_builtin(const char *name)
 /*
  * Execute a simple command.
  */
+static void unwindfiles(struct parsefile *stop);
 static int
 isassignment(const char *p)
 {
@@ -9911,6 +9982,7 @@ evalcommand(union node *cmd, int flags)
                "\0\0", bltincmd /* why three NULs? */
        };
        struct localvar_list *localvar_stop;
+       struct parsefile *file_stop;
        struct redirtab *redir_stop;
        struct stackmark smark;
        union node *argp;
@@ -9936,6 +10008,7 @@ evalcommand(union node *cmd, int flags)
        TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
        setstackmark(&smark);
        localvar_stop = pushlocalvars();
+       file_stop = g_parsefile;
        back_exitstatus = 0;
 
        cmdentry.cmdtype = CMDBUILTIN;
@@ -10194,7 +10267,6 @@ evalcommand(union node *cmd, int flags)
                goto readstatus;
 
        case CMDFUNCTION:
-               poplocalvars(1);
                /* See above for the rationale */
                dowait(DOWAIT_NONBLOCK, NULL);
                if (evalfun(cmdentry.u.func, argc, argv, flags))
@@ -10208,6 +10280,7 @@ evalcommand(union node *cmd, int flags)
        if (cmd->ncmd.redirect)
                popredir(/*drop:*/ cmd_is_exec);
        unwindredir(redir_stop);
+       unwindfiles(file_stop);
        unwindlocalvars(localvar_stop);
        if (lastarg) {
                /* dsl: I think this is intended to be used to support
@@ -10730,14 +10803,20 @@ popfile(void)
        INT_ON;
 }
 
+static void
+unwindfiles(struct parsefile *stop)
+{
+       while (g_parsefile != stop)
+               popfile();
+}
+
 /*
  * Return to top level.
  */
 static void
 popallfiles(void)
 {
-       while (g_parsefile != &basepf)
-               popfile();
+       unwindfiles(&basepf);
 }
 
 /*
@@ -11609,10 +11688,12 @@ simplecmd(void)
                                case TLP:
                                        function_flag = 0;
                                        break;
+# if BASH_TEST2
                                case TWORD:
                                        if (strcmp("[[", wordtext) == 0)
                                                goto do_func;
                                        /* fall through */
+# endif
                                default:
                                        raise_error_unexpected_syntax(-1);
                                }
@@ -11660,7 +11741,8 @@ simplecmd(void)
        *vpp = NULL;
        *rpp = NULL;
        n = stzalloc(sizeof(struct ncmd));
-       n->type = NCMD;
+       if (NCMD != 0)
+               n->type = NCMD;
        n->ncmd.linno = savelinno;
        n->ncmd.args = args;
        n->ncmd.assign = vars;
@@ -11956,7 +12038,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
        smallint pssyntax;   /* we are expanding a prompt string */
        IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
        /* syntax stack */
-       struct synstack synbase = { .syntax = syntax };
+       struct synstack synbase = { };
        struct synstack *synstack = &synbase;
 
 #if ENABLE_ASH_EXPAND_PRMT
@@ -11966,6 +12048,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
 #else
        pssyntax = 0; /* constant */
 #endif
+       synstack->syntax = syntax;
+
        if (syntax == DQSYNTAX)
                synstack->dblquote = 1;
        quotef = 0;
@@ -11979,8 +12063,11 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
                CHECKSTRSPACE(4, out);  /* permit 4 calls to USTPUTC */
                switch (SIT(c, synstack->syntax)) {
                case CNL:       /* '\n' */
-                       if (synstack->syntax == BASESYNTAX)
+                       if (synstack->syntax == BASESYNTAX
+                        && !synstack->varnest
+                       ) {
                                goto endword;   /* exit outer loop */
+                       }
                        USTPUTC(c, out);
                        nlprompt();
                        c = pgetc();
@@ -12371,7 +12458,7 @@ parsesub: {
                                STPUTC(c, out);
                                c = pgetc_eatbnl();
                        } while (isdigit(c));
-               } else {
+               } else if (c != '}') {
                        /* $[{[#]]<specialchar>[}] */
                        int cc = c;
 
@@ -12397,7 +12484,8 @@ parsesub: {
                        }
 
                        USTPUTC(cc, out);
-               }
+               } else
+                       goto badsub;
 
                if (c != '}' && subtype == VSLENGTH) {
                        /* ${#VAR didn't end with } */
@@ -12464,10 +12552,12 @@ parsesub: {
                        pungetc();
                }
 
-               if (newsyn == ARISYNTAX && subtype > VSNORMAL)
+               if (newsyn == ARISYNTAX)
                        newsyn = DQSYNTAX;
 
-               if (newsyn != synstack->syntax) {
+               if ((newsyn != synstack->syntax || synstack->innerdq)
+                && subtype != VSNORMAL
+               ) {
                        synstack_push(&synstack,
                                synstack->prev ?: alloca(sizeof(*synstack)),
                                newsyn);
@@ -12871,6 +12961,7 @@ parseheredoc(void)
        heredoclist = NULL;
 
        while (here) {
+               tokpushback = 0;
                setprompt_if(needprompt, 2);
                readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX,
                                here->eofmark, here->striptabs);
@@ -13700,38 +13791,35 @@ letcmd(int argc UNUSED_PARAM, char **argv)
 static int FAST_FUNC
 readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 {
-       char *opt_n = NULL;
-       char *opt_p = NULL;
-       char *opt_t = NULL;
-       char *opt_u = NULL;
-       char *opt_d = NULL; /* optimized out if !BASH */
-       int read_flags = 0;
+       struct builtin_read_params params;
        const char *r;
        int i;
 
+       memset(&params, 0, sizeof(params));
+
        while ((i = nextopt("p:u:rt:n:sd:")) != '\0') {
                switch (i) {
                case 'p':
-                       opt_p = optionarg;
+                       params.opt_p = optionarg;
                        break;
                case 'n':
-                       opt_n = optionarg;
+                       params.opt_n = optionarg;
                        break;
                case 's':
-                       read_flags |= BUILTIN_READ_SILENT;
+                       params.read_flags |= BUILTIN_READ_SILENT;
                        break;
                case 't':
-                       opt_t = optionarg;
+                       params.opt_t = optionarg;
                        break;
                case 'r':
-                       read_flags |= BUILTIN_READ_RAW;
+                       params.read_flags |= BUILTIN_READ_RAW;
                        break;
                case 'u':
-                       opt_u = optionarg;
+                       params.opt_u = optionarg;
                        break;
 #if BASH_READ_D
                case 'd':
-                       opt_d = optionarg;
+                       params.opt_d = optionarg;
                        break;
 #endif
                default:
@@ -13739,21 +13827,16 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                }
        }
 
+       params.argv = argptr;
+       params.setvar = setvar0;
+       params.ifs = bltinlookup("IFS"); /* can be NULL */
+
        /* "read -s" needs to save/restore termios, can't allow ^C
         * to jump out of it.
         */
  again:
        INT_OFF;
-       r = shell_builtin_read(setvar0,
-               argptr,
-               bltinlookup("IFS"), /* can be NULL */
-               read_flags,
-               opt_n,
-               opt_p,
-               opt_t,
-               opt_u,
-               opt_d
-       );
+       r = shell_builtin_read(&params);
        INT_ON;
 
        if ((uintptr_t)r == 1 && errno == EINTR) {
@@ -13912,6 +13995,7 @@ init(void)
                        }
                }
 
+               setvareq((char*)defifsvar, VTEXTFIXED);
                setvareq((char*)defoptindvar, VTEXTFIXED);
 
                setvar0("PPID", utoa(getppid()));
@@ -13941,7 +14025,7 @@ init(void)
 
 
 //usage:#define ash_trivial_usage
-//usage:       "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:       "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
 //usage:#define ash_full_usage "\n\n"
 //usage:       "Unix shell interpreter"
 
@@ -13958,12 +14042,16 @@ procargs(char **argv)
 
        xargv = argv;
        login_sh = xargv[0] && xargv[0][0] == '-';
+#if NUM_SCRIPTS > 0
+       if (minusc)
+               goto setarg0;
+#endif
        arg0 = xargv[0];
        /* if (xargv[0]) - mmm, this is always true! */
                xargv++;
+       argptr = xargv;
        for (i = 0; i < NOPTS; i++)
                optlist[i] = 2;
-       argptr = xargv;
        if (options(/*cmdline:*/ 1, &login_sh)) {
                /* it already printed err message */
                raise_exception(EXERROR);
@@ -14065,7 +14153,12 @@ extern int etext();
  * is used to figure out how far we had gotten.
  */
 int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if NUM_SCRIPTS > 0
+int ash_main(int argc, char **argv)
+#else
 int ash_main(int argc UNUSED_PARAM, char **argv)
+#endif
+/* note: 'argc' is used only if embedded scripts are enabled */
 {
        volatile smallint state;
        struct jmploc jmploc;
@@ -14119,6 +14212,12 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
 
        init();
        setstackmark(&smark);
+
+#if NUM_SCRIPTS > 0
+       if (argc < 0)
+               /* Non-NULL minusc tells procargs that an embedded script is being run */
+               minusc = get_script_content(-argc - 1);
+#endif
        login_sh = procargs(argv);
 #if DEBUG
        TRACE(("Shell args: "));