do not fail build if MAXSYMLINKS isn't defined
[oweals/busybox.git] / shell / ash.c
index b20c32b89a0325f7e10f10e9cd5cfaec3179a882..71ef9a690f5941f567e35b7c911b7a248fde45d4 100644 (file)
@@ -385,6 +385,9 @@ static void trace_vprintf(const char *fmt, va_list va);
 /* ============ 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 */
@@ -2008,27 +2011,6 @@ getoptsreset(const char *value)
 }
 #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
@@ -2304,7 +2286,7 @@ unsetvar(const char *s)
                        free(vp);
                        INT_ON;
                } else {
-                       setvar(s, 0, 0);
+                       setvar2(s, 0);
                        vp->flags &= ~VEXPORT;
                }
  ok:
@@ -3564,7 +3546,8 @@ set_curjob(struct job *jp, unsigned mode)
                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
@@ -6338,7 +6321,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        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);
 
@@ -6355,7 +6339,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
 
        switch (subtype) {
        case VSASSIGN:
-               setvar(varname, startp, 0);
+               setvar2(varname, startp);
                amount = startp - expdest;
                STADJUST(amount, expdest);
                return startp;
@@ -6451,9 +6435,9 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
        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;
 
@@ -8570,7 +8554,7 @@ evalfor(union node *n, int flags)
        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) {
@@ -8674,6 +8658,17 @@ expredir(union node *n)
                        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;
@@ -9038,6 +9033,9 @@ static int getoptscmd(int, char **) FAST_FUNC;
 #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
@@ -9111,6 +9109,9 @@ static const struct builtincmd builtintab[] = {
 #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    },
@@ -9310,11 +9311,11 @@ evalcommand(union node *cmd, int flags)
 
        /* 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) {
@@ -9450,7 +9451,7 @@ evalcommand(union node *cmd, int flags)
                 * '_' in 'vi' command mode during line editing...
                 * However I implemented that within libedit itself.
                 */
-               setvar("_", lastarg, 0);
+               setvar2("_", lastarg);
        }
        popstackmark(&smark);
 }
@@ -9656,7 +9657,12 @@ preadfd(void)
                 * _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 */
@@ -11637,8 +11643,9 @@ parsebackq: {
        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;
@@ -12262,8 +12269,10 @@ dotcmd(int argc, char **argv)
        /* "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 */
@@ -12273,6 +12282,9 @@ dotcmd(int argc, char **argv)
                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);
@@ -12619,6 +12631,15 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
 }
 #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.
  */
@@ -12629,9 +12650,27 @@ exportcmd(int argc UNUSED_PARAM, char **argv)
        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) {
@@ -12642,15 +12681,19 @@ exportcmd(int argc UNUSED_PARAM, char **argv)
                                } 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;
 }
@@ -12803,6 +12846,10 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                }
        }
 
+       /* "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 */
@@ -12812,6 +12859,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                opt_t,
                opt_u
        );
+       INT_ON;
 
        if ((uintptr_t)r > 1)
                ash_msg_and_raise_error(r);
@@ -12963,8 +13011,11 @@ init(void)
                        }
                }
 
-               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)
@@ -13168,27 +13219,21 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
        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;
@@ -13220,6 +13265,15 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
 #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