ash: fix nofork bug where environment is not properly passed to a command
authorDenys Vlasenko <vda.linux@googlemail.com>
Fri, 3 Nov 2017 13:16:25 +0000 (14:16 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 3 Nov 2017 13:16:25 +0000 (14:16 +0100)
function                                             old     new   delta
listvars                                             144     252    +108
evalcommand                                         1500    1546     +46
showvars                                             142     147      +5
shellexec                                            242     245      +3
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/0 up/down: 162/0)             Total: 162 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/ash.c
shell/ash_test/ash-standalone/nofork_env.right [new file with mode: 0644]
shell/ash_test/ash-standalone/nofork_env.tests [new file with mode: 0755]
shell/hush_test/hush-standalone/nofork_env.right [new file with mode: 0644]
shell/hush_test/hush-standalone/nofork_env.tests [new file with mode: 0755]

index 7a0b88c68573e2a243ae7c90275c1975aeaf417c..e69ddb4ffb820a1fc18984ff2bfbd5555576c9c0 100644 (file)
@@ -2380,8 +2380,11 @@ listsetvar(struct strlist *list_set_var, int flags)
 /*
  * Generate a list of variables satisfying the given conditions.
  */
+#if !ENABLE_FEATURE_SH_NOFORK
+# define listvars(on, off, lp, end) listvars(on, off, end)
+#endif
 static char **
-listvars(int on, int off, char ***end)
+listvars(int on, int off, struct strlist *lp, char ***end)
 {
        struct var **vpp;
        struct var *vp;
@@ -2394,12 +2397,40 @@ listvars(int on, int off, char ***end)
        do {
                for (vp = *vpp; vp; vp = vp->next) {
                        if ((vp->flags & mask) == on) {
+#if ENABLE_FEATURE_SH_NOFORK
+                               /* If variable with the same name is both
+                                * exported and temporarily set for a command:
+                                *  export ZVAR=5
+                                *  ZVAR=6 printenv
+                                * then "ZVAR=6" will be both in vartab and
+                                * lp lists. Do not pass it twice to printenv.
+                                */
+                               struct strlist *lp1 = lp;
+                               while (lp1) {
+                                       if (strcmp(lp1->text, vp->var_text) == 0)
+                                               goto skip;
+                                       lp1 = lp1->next;
+                               }
+#endif
                                if (ep == stackstrend())
                                        ep = growstackstr();
                                *ep++ = (char*)vp->var_text;
+#if ENABLE_FEATURE_SH_NOFORK
+ skip: ;
+#endif
                        }
                }
        } while (++vpp < vartab + VTABSIZE);
+
+#if ENABLE_FEATURE_SH_NOFORK
+       while (lp) {
+               if (ep == stackstrend())
+                       ep = growstackstr();
+               *ep++ = lp->text;
+               lp = lp->next;
+       }
+#endif
+
        if (ep == stackstrend())
                ep = growstackstr();
        if (end)
@@ -7860,7 +7891,7 @@ static void shellexec(char *prog, char **argv, const char *path, int idx)
        int exerrno;
        int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
 
-       envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
+       envp = listvars(VEXPORT, VUNSET, /*strlist:*/ NULL, /*end:*/ NULL);
        if (strchr(prog, '/') != NULL
 #if ENABLE_FEATURE_SH_STANDALONE
         || (applet_no = find_applet_by_name(prog)) >= 0
@@ -9930,7 +9961,11 @@ evalcommand(union node *cmd, int flags)
                /* find_command() encodes applet_no as (-2 - applet_no) */
                int applet_no = (- cmdentry.u.index - 2);
                if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
-                       listsetvar(varlist.list, VEXPORT|VSTACK);
+                       char **sv_environ;
+
+                       INT_OFF;
+                       sv_environ = environ;
+                       environ = listvars(VEXPORT, VUNSET, varlist.list, /*end:*/ NULL);
                        /*
                         * Run <applet>_main().
                         * Signals (^C) can't interrupt here.
@@ -9939,8 +9974,8 @@ evalcommand(union node *cmd, int flags)
                         * and/or wait for user input ineligible for NOFORK:
                         * for example, "yes" or "rm" (rm -i waits for input).
                         */
-                       INT_OFF;
                        status = run_nofork_applet(applet_no, argv);
+                       environ = sv_environ;
                        /*
                         * Try enabling NOFORK for "yes" applet.
                         * ^C _will_ stop it (write returns EINTR),
@@ -10854,7 +10889,7 @@ showvars(const char *sep_prefix, int on, int off)
        const char *sep;
        char **ep, **epend;
 
-       ep = listvars(on, off, &epend);
+       ep = listvars(on, off, /*strlist:*/ NULL, &epend);
        qsort(ep, epend - ep, sizeof(char *), vpcmp);
 
        sep = *sep_prefix ? " " : sep_prefix;
diff --git a/shell/ash_test/ash-standalone/nofork_env.right b/shell/ash_test/ash-standalone/nofork_env.right
new file mode 100644 (file)
index 0000000..3f16ff4
--- /dev/null
@@ -0,0 +1,9 @@
+ZVAR=1
+ZVAR=2
+ZVAR=3
+ZVAR=4
+ZVAR=5
+ZVAR=6
+ZVAR=7
+ZVAR=8
+Ok:0
diff --git a/shell/ash_test/ash-standalone/nofork_env.tests b/shell/ash_test/ash-standalone/nofork_env.tests
new file mode 100755 (executable)
index 0000000..111e564
--- /dev/null
@@ -0,0 +1,15 @@
+# ash had a bug where NOFORKed applet (env/printenv) was not seeing new exported variables
+
+(export ZVAR=1; printenv) | grep ^ZVAR=
+(ZVAR=2 printenv) | grep ^ZVAR=
+
+(export ZVAR=3; env) | grep ^ZVAR=
+(ZVAR=4 env) | grep ^ZVAR=
+
+export ZVAR=5; printenv | grep ^ZVAR=
+ZVAR=6 printenv | grep ^ZVAR=
+
+export ZVAR=7; env | grep ^ZVAR=
+ZVAR=8 env | grep ^ZVAR=
+
+echo Ok:$?
diff --git a/shell/hush_test/hush-standalone/nofork_env.right b/shell/hush_test/hush-standalone/nofork_env.right
new file mode 100644 (file)
index 0000000..3f16ff4
--- /dev/null
@@ -0,0 +1,9 @@
+ZVAR=1
+ZVAR=2
+ZVAR=3
+ZVAR=4
+ZVAR=5
+ZVAR=6
+ZVAR=7
+ZVAR=8
+Ok:0
diff --git a/shell/hush_test/hush-standalone/nofork_env.tests b/shell/hush_test/hush-standalone/nofork_env.tests
new file mode 100755 (executable)
index 0000000..111e564
--- /dev/null
@@ -0,0 +1,15 @@
+# ash had a bug where NOFORKed applet (env/printenv) was not seeing new exported variables
+
+(export ZVAR=1; printenv) | grep ^ZVAR=
+(ZVAR=2 printenv) | grep ^ZVAR=
+
+(export ZVAR=3; env) | grep ^ZVAR=
+(ZVAR=4 env) | grep ^ZVAR=
+
+export ZVAR=5; printenv | grep ^ZVAR=
+ZVAR=6 printenv | grep ^ZVAR=
+
+export ZVAR=7; env | grep ^ZVAR=
+ZVAR=8 env | grep ^ZVAR=
+
+echo Ok:$?