PR/48843: Jarmo Jaakkola: dot commands mess up scope nesting tracking
Evaluation of commands goes completely haywire if a file containing
a break/continue/return command outside its "intended" scope is sourced
using a dot command inside its "intended" scope. The main symptom is
not exiting from the sourced file when supposed to, leading to evaluation
of commands that were not supposed to be evaluated. A secondary symptom
is that these extra commands are not evaluated correctly, as some of them
are skipped. Some examples are listed in the How-To-Repeat section.
According to the POSIX standard, this is how it should work:
dot:
The shell shall execute commands from the file in the current
environment.
break:
The break utility shall exit from the smallest enclosing for, while,
or until loop, [...]
continue:
The continue utility shall return to the top of the smallest
enclosing for, while, or until loop, [...]
return:
The return utility shall cause the shell to stop executing
the current function or dot script. If the shell is not currently
executing a function or dot script, the results are unspecified.
It is clear that return should return from a sourced file, which
it does not do. Whether break and continue should work from the sourced
file might be debatable. Because the dot command says "in the current
environment", I'd say yes. In any case, it should not fail in weird
ways like it does now!
The problems occur with return (a) and break/continue (b) because:
1) dotcmd() does not record the function nesting level prior to
sourcing the file nor does it touch the loopnest variable,
leading to either
2 a) returncmd() being unable to detect that it should not set
evalskip to SKIPFUNC but SKIPFILE, or
b) breakcmd() setting evalskip to SKIPCONT or SKIPBREAK,
leading to
3) cmdloop() not detecting that it should skip the rest of
the file, due to only checking for SKIPFILE.
The result is that cmdloop() keeps executing lines from the file
whilst evalskip is set, which is the main symptom. Because
evalskip is checked in multiple places in eval.c, the secondary
symptom appears.
>How-To-Repeat:
Run the following script:
printf "break\necho break1; echo break2" >break
printf "continue\necho continue1; echo continue2" >continue
printf "return\necho return1; echo return2" >return
while true; do . ./break; done
for i in 1 2; do . ./continue; done
func() {
. ./return
}
func
No output should be produced, but instead this is the result:
break1
continue1
continue1
return1
The main symptom is evident from the unexpected output and the secondary
one from the fact that there are no lines with '2' in them.
>Fix:
Here is patch to src/bin/sh to fix the above problems. It keeps
track of the function nesting level at the beginning of a dot command
to enable the return command to work properly.
I also changed the undefined-by-standard functionality of the return
command when it's not in a dot command or function from (indirectly)
exiting the shell to being silently ignored. This was done because
the previous way has at least one bug: the shell exits without asking
for confirmation when there are stopped jobs.
Because I read the standard to mean that break and continue should have
an effect outside the sourced file, that's how I implemented it. For what
it's worth, this also seems to be what bash does. Also laziness, because
this way required no changes to loopnesting tracking. If this is not
wanted, it might make sense to move the nesting tracking to the inputfile
stack.
The patch also does some clean-up to reduce the amount of global
variables by moving the dotcmd() and the find_dot_file() functions from
main.c to eval.c and making in_function() a proper function.
2014-05-31 18:42:18 +04:00
|
|
|
/* $NetBSD: main.c,v 1.58 2014/05/31 14:42:18 christos Exp $ */
|
1995-03-21 12:01:59 +03:00
|
|
|
|
1993-03-21 12:45:37 +03:00
|
|
|
/*-
|
1994-05-11 21:09:42 +04:00
|
|
|
* Copyright (c) 1991, 1993
|
|
|
|
* The Regents of the University of California. All rights reserved.
|
1993-03-21 12:45:37 +03:00
|
|
|
*
|
|
|
|
* This code is derived from software contributed to Berkeley by
|
|
|
|
* Kenneth Almquist.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
2003-08-07 13:05:01 +04:00
|
|
|
* 3. Neither the name of the University nor the names of its contributors
|
1993-03-21 12:45:37 +03:00
|
|
|
* may be used to endorse or promote products derived from this software
|
|
|
|
* without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
1997-07-05 01:01:48 +04:00
|
|
|
#include <sys/cdefs.h>
|
1993-03-21 12:45:37 +03:00
|
|
|
#ifndef lint
|
2008-07-20 04:52:39 +04:00
|
|
|
__COPYRIGHT("@(#) Copyright (c) 1991, 1993\
|
|
|
|
The Regents of the University of California. All rights reserved.");
|
1993-03-21 12:45:37 +03:00
|
|
|
#endif /* not lint */
|
|
|
|
|
|
|
|
#ifndef lint
|
1995-03-21 12:01:59 +03:00
|
|
|
#if 0
|
1995-07-20 19:04:16 +04:00
|
|
|
static char sccsid[] = "@(#)main.c 8.7 (Berkeley) 7/19/95";
|
1995-03-21 12:01:59 +03:00
|
|
|
#else
|
PR/48843: Jarmo Jaakkola: dot commands mess up scope nesting tracking
Evaluation of commands goes completely haywire if a file containing
a break/continue/return command outside its "intended" scope is sourced
using a dot command inside its "intended" scope. The main symptom is
not exiting from the sourced file when supposed to, leading to evaluation
of commands that were not supposed to be evaluated. A secondary symptom
is that these extra commands are not evaluated correctly, as some of them
are skipped. Some examples are listed in the How-To-Repeat section.
According to the POSIX standard, this is how it should work:
dot:
The shell shall execute commands from the file in the current
environment.
break:
The break utility shall exit from the smallest enclosing for, while,
or until loop, [...]
continue:
The continue utility shall return to the top of the smallest
enclosing for, while, or until loop, [...]
return:
The return utility shall cause the shell to stop executing
the current function or dot script. If the shell is not currently
executing a function or dot script, the results are unspecified.
It is clear that return should return from a sourced file, which
it does not do. Whether break and continue should work from the sourced
file might be debatable. Because the dot command says "in the current
environment", I'd say yes. In any case, it should not fail in weird
ways like it does now!
The problems occur with return (a) and break/continue (b) because:
1) dotcmd() does not record the function nesting level prior to
sourcing the file nor does it touch the loopnest variable,
leading to either
2 a) returncmd() being unable to detect that it should not set
evalskip to SKIPFUNC but SKIPFILE, or
b) breakcmd() setting evalskip to SKIPCONT or SKIPBREAK,
leading to
3) cmdloop() not detecting that it should skip the rest of
the file, due to only checking for SKIPFILE.
The result is that cmdloop() keeps executing lines from the file
whilst evalskip is set, which is the main symptom. Because
evalskip is checked in multiple places in eval.c, the secondary
symptom appears.
>How-To-Repeat:
Run the following script:
printf "break\necho break1; echo break2" >break
printf "continue\necho continue1; echo continue2" >continue
printf "return\necho return1; echo return2" >return
while true; do . ./break; done
for i in 1 2; do . ./continue; done
func() {
. ./return
}
func
No output should be produced, but instead this is the result:
break1
continue1
continue1
return1
The main symptom is evident from the unexpected output and the secondary
one from the fact that there are no lines with '2' in them.
>Fix:
Here is patch to src/bin/sh to fix the above problems. It keeps
track of the function nesting level at the beginning of a dot command
to enable the return command to work properly.
I also changed the undefined-by-standard functionality of the return
command when it's not in a dot command or function from (indirectly)
exiting the shell to being silently ignored. This was done because
the previous way has at least one bug: the shell exits without asking
for confirmation when there are stopped jobs.
Because I read the standard to mean that break and continue should have
an effect outside the sourced file, that's how I implemented it. For what
it's worth, this also seems to be what bash does. Also laziness, because
this way required no changes to loopnesting tracking. If this is not
wanted, it might make sense to move the nesting tracking to the inputfile
stack.
The patch also does some clean-up to reduce the amount of global
variables by moving the dotcmd() and the find_dot_file() functions from
main.c to eval.c and making in_function() a proper function.
2014-05-31 18:42:18 +04:00
|
|
|
__RCSID("$NetBSD: main.c,v 1.58 2014/05/31 14:42:18 christos Exp $");
|
1995-03-21 12:01:59 +03:00
|
|
|
#endif
|
1993-03-21 12:45:37 +03:00
|
|
|
#endif /* not lint */
|
|
|
|
|
2000-04-14 09:54:20 +04:00
|
|
|
#include <errno.h>
|
1994-05-11 21:54:32 +04:00
|
|
|
#include <stdio.h>
|
1993-03-21 12:45:37 +03:00
|
|
|
#include <signal.h>
|
1994-05-11 21:54:32 +04:00
|
|
|
#include <sys/stat.h>
|
1994-05-12 21:03:32 +04:00
|
|
|
#include <unistd.h>
|
2010-02-21 02:15:17 +03:00
|
|
|
#include <stdlib.h>
|
2002-12-11 22:12:18 +03:00
|
|
|
#include <locale.h>
|
1993-03-21 12:45:37 +03:00
|
|
|
#include <fcntl.h>
|
1995-05-12 01:28:33 +04:00
|
|
|
|
|
|
|
|
1993-03-21 12:45:37 +03:00
|
|
|
#include "shell.h"
|
|
|
|
#include "main.h"
|
|
|
|
#include "mail.h"
|
|
|
|
#include "options.h"
|
2011-06-19 01:18:46 +04:00
|
|
|
#include "builtins.h"
|
1993-03-21 12:45:37 +03:00
|
|
|
#include "output.h"
|
|
|
|
#include "parser.h"
|
|
|
|
#include "nodes.h"
|
1995-05-28 22:09:48 +04:00
|
|
|
#include "expand.h"
|
1993-03-21 12:45:37 +03:00
|
|
|
#include "eval.h"
|
|
|
|
#include "jobs.h"
|
|
|
|
#include "input.h"
|
|
|
|
#include "trap.h"
|
|
|
|
#include "var.h"
|
1995-05-12 01:28:33 +04:00
|
|
|
#include "show.h"
|
1993-03-21 12:45:37 +03:00
|
|
|
#include "memalloc.h"
|
|
|
|
#include "error.h"
|
|
|
|
#include "init.h"
|
|
|
|
#include "mystring.h"
|
1994-05-11 21:54:32 +04:00
|
|
|
#include "exec.h"
|
1995-11-20 02:27:37 +03:00
|
|
|
#include "cd.h"
|
1993-03-21 12:45:37 +03:00
|
|
|
|
|
|
|
#define PROFILE 0
|
|
|
|
|
|
|
|
int rootpid;
|
|
|
|
int rootshell;
|
2010-02-21 02:15:17 +03:00
|
|
|
int posix;
|
1993-03-21 12:45:37 +03:00
|
|
|
#if PROFILE
|
|
|
|
short profile_buf[16384];
|
|
|
|
extern int etext();
|
|
|
|
#endif
|
|
|
|
|
2002-11-25 01:35:38 +03:00
|
|
|
STATIC void read_profile(const char *);
|
|
|
|
int main(int, char **);
|
1993-03-21 12:45:37 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Main routine. We initialize things, parse the arguments, execute
|
|
|
|
* profiles if we're a login shell, and then call cmdloop to execute
|
|
|
|
* commands. The setjmp call sets up the location to jump to when an
|
|
|
|
* exception occurs. When an exception occurs the variable "state"
|
|
|
|
* is used to figure out how far we had gotten.
|
|
|
|
*/
|
|
|
|
|
1994-12-04 10:11:37 +03:00
|
|
|
int
|
2002-11-25 01:35:38 +03:00
|
|
|
main(int argc, char **argv)
|
1994-12-04 10:11:37 +03:00
|
|
|
{
|
1993-03-21 12:45:37 +03:00
|
|
|
struct jmploc jmploc;
|
|
|
|
struct stackmark smark;
|
|
|
|
volatile int state;
|
|
|
|
char *shinit;
|
|
|
|
|
2002-12-11 22:12:18 +03:00
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
|
2010-02-21 02:15:17 +03:00
|
|
|
posix = getenv("POSIXLY_CORRECT") != NULL;
|
1993-03-21 12:45:37 +03:00
|
|
|
#if PROFILE
|
|
|
|
monitor(4, etext, profile_buf, sizeof profile_buf, 50);
|
|
|
|
#endif
|
|
|
|
state = 0;
|
|
|
|
if (setjmp(jmploc.loc)) {
|
|
|
|
/*
|
|
|
|
* When a shell procedure is executed, we raise the
|
|
|
|
* exception EXSHELLPROC to clean up before executing
|
|
|
|
* the shell procedure.
|
|
|
|
*/
|
1996-10-16 18:35:42 +04:00
|
|
|
switch (exception) {
|
|
|
|
case EXSHELLPROC:
|
1993-03-21 12:45:37 +03:00
|
|
|
rootpid = getpid();
|
|
|
|
rootshell = 1;
|
|
|
|
minusc = NULL;
|
|
|
|
state = 3;
|
1996-10-16 18:35:42 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EXEXEC:
|
|
|
|
exitstatus = exerrno;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXERROR:
|
|
|
|
exitstatus = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exception != EXSHELLPROC) {
|
2002-11-25 01:35:38 +03:00
|
|
|
if (state == 0 || iflag == 0 || ! rootshell)
|
|
|
|
exitshell(exitstatus);
|
1996-10-16 18:35:42 +04:00
|
|
|
}
|
1993-03-21 12:45:37 +03:00
|
|
|
reset();
|
|
|
|
if (exception == EXINT
|
1994-05-11 21:09:42 +04:00
|
|
|
#if ATTY
|
|
|
|
&& (! attyset() || equal(termval(), "emacs"))
|
1993-03-21 12:45:37 +03:00
|
|
|
#endif
|
1994-05-11 21:09:42 +04:00
|
|
|
) {
|
1993-03-21 12:45:37 +03:00
|
|
|
out2c('\n');
|
|
|
|
flushout(&errout);
|
|
|
|
}
|
|
|
|
popstackmark(&smark);
|
|
|
|
FORCEINTON; /* enable interrupts */
|
|
|
|
if (state == 1)
|
|
|
|
goto state1;
|
|
|
|
else if (state == 2)
|
|
|
|
goto state2;
|
1994-05-11 21:09:42 +04:00
|
|
|
else if (state == 3)
|
1993-03-21 12:45:37 +03:00
|
|
|
goto state3;
|
1994-05-11 21:09:42 +04:00
|
|
|
else
|
|
|
|
goto state4;
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
|
|
|
handler = &jmploc;
|
|
|
|
#ifdef DEBUG
|
2003-09-14 16:09:29 +04:00
|
|
|
#if DEBUG == 2
|
|
|
|
debug = 1;
|
|
|
|
#endif
|
1993-03-21 12:45:37 +03:00
|
|
|
opentrace();
|
|
|
|
trputs("Shell args: "); trargs(argv);
|
|
|
|
#endif
|
|
|
|
rootpid = getpid();
|
|
|
|
rootshell = 1;
|
|
|
|
init();
|
2005-10-11 01:14:42 +04:00
|
|
|
initpwd();
|
1993-03-21 12:45:37 +03:00
|
|
|
setstackmark(&smark);
|
|
|
|
procargs(argc, argv);
|
|
|
|
if (argv[0] && argv[0][0] == '-') {
|
|
|
|
state = 1;
|
|
|
|
read_profile("/etc/profile");
|
|
|
|
state1:
|
|
|
|
state = 2;
|
|
|
|
read_profile(".profile");
|
1996-10-16 18:35:42 +04:00
|
|
|
}
|
1993-03-21 12:45:37 +03:00
|
|
|
state2:
|
|
|
|
state = 3;
|
2010-02-21 12:54:57 +03:00
|
|
|
if ((iflag || !posix) &&
|
|
|
|
getuid() == geteuid() && getgid() == getegid()) {
|
1994-08-03 20:25:26 +04:00
|
|
|
if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
|
|
|
|
state = 3;
|
|
|
|
read_profile(shinit);
|
|
|
|
}
|
1994-05-11 21:09:42 +04:00
|
|
|
}
|
|
|
|
state3:
|
|
|
|
state = 4;
|
1999-03-27 16:46:19 +03:00
|
|
|
if (sflag == 0 || minusc) {
|
1999-02-06 00:21:27 +03:00
|
|
|
static int sigs[] = {
|
|
|
|
SIGINT, SIGQUIT, SIGHUP,
|
1999-02-04 14:20:40 +03:00
|
|
|
#ifdef SIGTSTP
|
1999-02-06 00:21:27 +03:00
|
|
|
SIGTSTP,
|
1999-02-04 14:20:40 +03:00
|
|
|
#endif
|
1999-02-06 00:21:27 +03:00
|
|
|
SIGPIPE
|
1999-02-04 14:20:40 +03:00
|
|
|
};
|
1999-02-06 00:21:27 +03:00
|
|
|
#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0]))
|
2009-01-18 03:24:29 +03:00
|
|
|
size_t i;
|
1999-02-04 14:20:40 +03:00
|
|
|
|
|
|
|
for (i = 0; i < SIGSSIZE; i++)
|
2002-09-27 22:56:50 +04:00
|
|
|
setsignal(sigs[i], 0);
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
1999-03-27 16:46:19 +03:00
|
|
|
|
|
|
|
if (minusc)
|
2000-01-28 02:39:38 +03:00
|
|
|
evalstring(minusc, 0);
|
1999-03-27 16:46:19 +03:00
|
|
|
|
1993-03-21 12:45:37 +03:00
|
|
|
if (sflag || minusc == NULL) {
|
1994-05-11 21:09:42 +04:00
|
|
|
state4: /* XXX ??? - why isn't this before the "if" statement */
|
1993-03-21 12:45:37 +03:00
|
|
|
cmdloop(1);
|
|
|
|
}
|
|
|
|
#if PROFILE
|
|
|
|
monitor(0);
|
|
|
|
#endif
|
|
|
|
exitshell(exitstatus);
|
1998-07-28 15:41:40 +04:00
|
|
|
/* NOTREACHED */
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read and execute commands. "Top" is nonzero for the top level command
|
|
|
|
* loop; it turns on prompting if the shell is interactive.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
2002-11-25 01:35:38 +03:00
|
|
|
cmdloop(int top)
|
1994-12-04 10:11:37 +03:00
|
|
|
{
|
1993-03-21 12:45:37 +03:00
|
|
|
union node *n;
|
|
|
|
struct stackmark smark;
|
|
|
|
int inter;
|
1994-05-11 21:09:42 +04:00
|
|
|
int numeof = 0;
|
PR/48843: Jarmo Jaakkola: dot commands mess up scope nesting tracking
Evaluation of commands goes completely haywire if a file containing
a break/continue/return command outside its "intended" scope is sourced
using a dot command inside its "intended" scope. The main symptom is
not exiting from the sourced file when supposed to, leading to evaluation
of commands that were not supposed to be evaluated. A secondary symptom
is that these extra commands are not evaluated correctly, as some of them
are skipped. Some examples are listed in the How-To-Repeat section.
According to the POSIX standard, this is how it should work:
dot:
The shell shall execute commands from the file in the current
environment.
break:
The break utility shall exit from the smallest enclosing for, while,
or until loop, [...]
continue:
The continue utility shall return to the top of the smallest
enclosing for, while, or until loop, [...]
return:
The return utility shall cause the shell to stop executing
the current function or dot script. If the shell is not currently
executing a function or dot script, the results are unspecified.
It is clear that return should return from a sourced file, which
it does not do. Whether break and continue should work from the sourced
file might be debatable. Because the dot command says "in the current
environment", I'd say yes. In any case, it should not fail in weird
ways like it does now!
The problems occur with return (a) and break/continue (b) because:
1) dotcmd() does not record the function nesting level prior to
sourcing the file nor does it touch the loopnest variable,
leading to either
2 a) returncmd() being unable to detect that it should not set
evalskip to SKIPFUNC but SKIPFILE, or
b) breakcmd() setting evalskip to SKIPCONT or SKIPBREAK,
leading to
3) cmdloop() not detecting that it should skip the rest of
the file, due to only checking for SKIPFILE.
The result is that cmdloop() keeps executing lines from the file
whilst evalskip is set, which is the main symptom. Because
evalskip is checked in multiple places in eval.c, the secondary
symptom appears.
>How-To-Repeat:
Run the following script:
printf "break\necho break1; echo break2" >break
printf "continue\necho continue1; echo continue2" >continue
printf "return\necho return1; echo return2" >return
while true; do . ./break; done
for i in 1 2; do . ./continue; done
func() {
. ./return
}
func
No output should be produced, but instead this is the result:
break1
continue1
continue1
return1
The main symptom is evident from the unexpected output and the secondary
one from the fact that there are no lines with '2' in them.
>Fix:
Here is patch to src/bin/sh to fix the above problems. It keeps
track of the function nesting level at the beginning of a dot command
to enable the return command to work properly.
I also changed the undefined-by-standard functionality of the return
command when it's not in a dot command or function from (indirectly)
exiting the shell to being silently ignored. This was done because
the previous way has at least one bug: the shell exits without asking
for confirmation when there are stopped jobs.
Because I read the standard to mean that break and continue should have
an effect outside the sourced file, that's how I implemented it. For what
it's worth, this also seems to be what bash does. Also laziness, because
this way required no changes to loopnesting tracking. If this is not
wanted, it might make sense to move the nesting tracking to the inputfile
stack.
The patch also does some clean-up to reduce the amount of global
variables by moving the dotcmd() and the find_dot_file() functions from
main.c to eval.c and making in_function() a proper function.
2014-05-31 18:42:18 +04:00
|
|
|
enum skipstate skip;
|
1993-03-21 12:45:37 +03:00
|
|
|
|
|
|
|
TRACE(("cmdloop(%d) called\n", top));
|
|
|
|
setstackmark(&smark);
|
|
|
|
for (;;) {
|
|
|
|
if (pendingsigs)
|
|
|
|
dotrap();
|
|
|
|
inter = 0;
|
2005-07-15 21:49:43 +04:00
|
|
|
if (iflag == 1 && top) {
|
2002-11-25 01:35:38 +03:00
|
|
|
inter = 1;
|
|
|
|
showjobs(out2, SHOW_CHANGED);
|
1993-03-21 12:45:37 +03:00
|
|
|
chkmail(0);
|
2002-11-25 01:35:38 +03:00
|
|
|
flushout(&errout);
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
|
|
|
n = parsecmd(inter);
|
1994-05-11 21:09:42 +04:00
|
|
|
/* showtree(n); DEBUG */
|
1993-03-21 12:45:37 +03:00
|
|
|
if (n == NEOF) {
|
1994-05-11 21:09:42 +04:00
|
|
|
if (!top || numeof >= 50)
|
1993-03-21 12:45:37 +03:00
|
|
|
break;
|
1994-05-11 21:09:42 +04:00
|
|
|
if (!stoppedjobs()) {
|
|
|
|
if (!Iflag)
|
|
|
|
break;
|
|
|
|
out2str("\nUse \"exit\" to leave shell.\n");
|
|
|
|
}
|
1993-03-21 12:45:37 +03:00
|
|
|
numeof++;
|
|
|
|
} else if (n != NULL && nflag == 0) {
|
1994-05-11 21:09:42 +04:00
|
|
|
job_warning = (job_warning == 2) ? 1 : 0;
|
|
|
|
numeof = 0;
|
2002-09-28 05:25:01 +04:00
|
|
|
evaltree(n, 0);
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
|
|
|
popstackmark(&smark);
|
2000-11-01 22:56:01 +03:00
|
|
|
setstackmark(&smark);
|
PR/48843: Jarmo Jaakkola: dot commands mess up scope nesting tracking
Evaluation of commands goes completely haywire if a file containing
a break/continue/return command outside its "intended" scope is sourced
using a dot command inside its "intended" scope. The main symptom is
not exiting from the sourced file when supposed to, leading to evaluation
of commands that were not supposed to be evaluated. A secondary symptom
is that these extra commands are not evaluated correctly, as some of them
are skipped. Some examples are listed in the How-To-Repeat section.
According to the POSIX standard, this is how it should work:
dot:
The shell shall execute commands from the file in the current
environment.
break:
The break utility shall exit from the smallest enclosing for, while,
or until loop, [...]
continue:
The continue utility shall return to the top of the smallest
enclosing for, while, or until loop, [...]
return:
The return utility shall cause the shell to stop executing
the current function or dot script. If the shell is not currently
executing a function or dot script, the results are unspecified.
It is clear that return should return from a sourced file, which
it does not do. Whether break and continue should work from the sourced
file might be debatable. Because the dot command says "in the current
environment", I'd say yes. In any case, it should not fail in weird
ways like it does now!
The problems occur with return (a) and break/continue (b) because:
1) dotcmd() does not record the function nesting level prior to
sourcing the file nor does it touch the loopnest variable,
leading to either
2 a) returncmd() being unable to detect that it should not set
evalskip to SKIPFUNC but SKIPFILE, or
b) breakcmd() setting evalskip to SKIPCONT or SKIPBREAK,
leading to
3) cmdloop() not detecting that it should skip the rest of
the file, due to only checking for SKIPFILE.
The result is that cmdloop() keeps executing lines from the file
whilst evalskip is set, which is the main symptom. Because
evalskip is checked in multiple places in eval.c, the secondary
symptom appears.
>How-To-Repeat:
Run the following script:
printf "break\necho break1; echo break2" >break
printf "continue\necho continue1; echo continue2" >continue
printf "return\necho return1; echo return2" >return
while true; do . ./break; done
for i in 1 2; do . ./continue; done
func() {
. ./return
}
func
No output should be produced, but instead this is the result:
break1
continue1
continue1
return1
The main symptom is evident from the unexpected output and the secondary
one from the fact that there are no lines with '2' in them.
>Fix:
Here is patch to src/bin/sh to fix the above problems. It keeps
track of the function nesting level at the beginning of a dot command
to enable the return command to work properly.
I also changed the undefined-by-standard functionality of the return
command when it's not in a dot command or function from (indirectly)
exiting the shell to being silently ignored. This was done because
the previous way has at least one bug: the shell exits without asking
for confirmation when there are stopped jobs.
Because I read the standard to mean that break and continue should have
an effect outside the sourced file, that's how I implemented it. For what
it's worth, this also seems to be what bash does. Also laziness, because
this way required no changes to loopnesting tracking. If this is not
wanted, it might make sense to move the nesting tracking to the inputfile
stack.
The patch also does some clean-up to reduce the amount of global
variables by moving the dotcmd() and the find_dot_file() functions from
main.c to eval.c and making in_function() a proper function.
2014-05-31 18:42:18 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Any SKIP* can occur here! SKIP(FUNC|BREAK|CONT) occur when
|
|
|
|
* a dotcmd is in a loop or a function body and appropriate
|
|
|
|
* built-ins occurs in file scope in the sourced file. Values
|
|
|
|
* other than SKIPFILE are reset by the appropriate eval*()
|
|
|
|
* that contained the dotcmd() call.
|
|
|
|
*/
|
|
|
|
skip = current_skipstate();
|
|
|
|
if (skip != SKIPNONE) {
|
|
|
|
if (skip == SKIPFILE)
|
|
|
|
stop_skipping();
|
1995-09-11 21:05:41 +04:00
|
|
|
break;
|
|
|
|
}
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
2000-11-01 22:56:01 +03:00
|
|
|
popstackmark(&smark);
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read /etc/profile or .profile. Return on error.
|
|
|
|
*/
|
|
|
|
|
|
|
|
STATIC void
|
2002-11-25 01:35:38 +03:00
|
|
|
read_profile(const char *name)
|
1999-02-04 03:27:07 +03:00
|
|
|
{
|
1993-03-21 12:45:37 +03:00
|
|
|
int fd;
|
1999-02-04 03:27:07 +03:00
|
|
|
int xflag_set = 0;
|
|
|
|
int vflag_set = 0;
|
1993-03-21 12:45:37 +03:00
|
|
|
|
|
|
|
INTOFF;
|
|
|
|
if ((fd = open(name, O_RDONLY)) >= 0)
|
|
|
|
setinputfd(fd, 1);
|
|
|
|
INTON;
|
|
|
|
if (fd < 0)
|
|
|
|
return;
|
1999-02-04 03:27:07 +03:00
|
|
|
/* -q turns off -x and -v just when executing init files */
|
|
|
|
if (qflag) {
|
|
|
|
if (xflag)
|
|
|
|
xflag = 0, xflag_set = 1;
|
|
|
|
if (vflag)
|
|
|
|
vflag = 0, vflag_set = 1;
|
|
|
|
}
|
1993-03-21 12:45:37 +03:00
|
|
|
cmdloop(0);
|
1999-02-04 03:27:07 +03:00
|
|
|
if (qflag) {
|
|
|
|
if (xflag_set)
|
|
|
|
xflag = 1;
|
|
|
|
if (vflag_set)
|
|
|
|
vflag = 1;
|
|
|
|
}
|
1993-03-21 12:45:37 +03:00
|
|
|
popfile();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read a file containing shell functions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
2002-11-25 01:35:38 +03:00
|
|
|
readcmdfile(char *name)
|
1994-12-05 22:07:32 +03:00
|
|
|
{
|
1993-03-21 12:45:37 +03:00
|
|
|
int fd;
|
|
|
|
|
|
|
|
INTOFF;
|
|
|
|
if ((fd = open(name, O_RDONLY)) >= 0)
|
|
|
|
setinputfd(fd, 1);
|
|
|
|
else
|
|
|
|
error("Can't open %s", name);
|
|
|
|
INTON;
|
|
|
|
cmdloop(0);
|
|
|
|
popfile();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
1994-12-04 10:11:37 +03:00
|
|
|
int
|
2002-11-25 01:35:38 +03:00
|
|
|
exitcmd(int argc, char **argv)
|
1994-12-04 10:11:37 +03:00
|
|
|
{
|
1994-05-11 21:09:42 +04:00
|
|
|
if (stoppedjobs())
|
1995-05-12 01:28:33 +04:00
|
|
|
return 0;
|
1993-03-21 12:45:37 +03:00
|
|
|
if (argc > 1)
|
|
|
|
exitstatus = number(argv[1]);
|
|
|
|
exitshell(exitstatus);
|
1998-07-28 15:41:40 +04:00
|
|
|
/* NOTREACHED */
|
1993-03-21 12:45:37 +03:00
|
|
|
}
|