diff --git a/bin/sh/eval.c b/bin/sh/eval.c index 8b7f44e3c786..adb409e6fc43 100644 --- a/bin/sh/eval.c +++ b/bin/sh/eval.c @@ -1,4 +1,4 @@ -/* $NetBSD: eval.c,v 1.108 2014/01/26 22:38:20 christos Exp $ */ +/* $NetBSD: eval.c,v 1.109 2014/05/31 14:42:18 christos Exp $ */ /*- * Copyright (c) 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; #else -__RCSID("$NetBSD: eval.c,v 1.108 2014/01/26 22:38:20 christos Exp $"); +__RCSID("$NetBSD: eval.c,v 1.109 2014/05/31 14:42:18 christos Exp $"); #endif #endif /* not lint */ @@ -89,11 +89,20 @@ __RCSID("$NetBSD: eval.c,v 1.108 2014/01/26 22:38:20 christos Exp $"); #define EV_TESTED 02 /* exit status is checked; ignore -e flag */ #define EV_BACKCMD 04 /* command executing within back quotes */ -int evalskip; /* set if we are skipping commands */ +STATIC enum skipstate evalskip; /* != SKIPNONE if we are skipping commands */ STATIC int skipcount; /* number of levels to skip */ -MKINIT int loopnest; /* current loop nesting level */ -int funcnest; /* depth of function calls */ +STATIC int loopnest; /* current loop nesting level */ +STATIC int funcnest; /* depth of function calls */ STATIC int builtin_flags; /* evalcommand flags for builtins */ +/* + * Base function nesting level inside a dot command. Set to 0 initially + * and to (funcnest + 1) before every dot command to enable + * 1) detection of being in a file sourced by a dot command and + * 2) counting of function nesting in that file for the implementation + * of the return command. + * The value is reset to its previous value after the dot command. + */ +STATIC int dot_funcnest; const char *commandname; @@ -111,6 +120,7 @@ STATIC void evalpipe(union node *); STATIC void evalcommand(union node *, int, struct backcmd *); STATIC void prehash(union node *); +STATIC char *find_dot_file(char *); /* * Called to reset things after an exception. @@ -120,9 +130,7 @@ STATIC void prehash(union node *); INCLUDE "eval.h" RESET { - evalskip = 0; - loopnest = 0; - funcnest = 0; + reset_eval(); } SHELLPROC { @@ -130,6 +138,15 @@ SHELLPROC { } #endif +void +reset_eval(void) +{ + evalskip = SKIPNONE; + dot_funcnest = 0; + loopnest = 0; + funcnest = 0; +} + static int sh_pipe(int fds[2]) { @@ -327,11 +344,11 @@ evalloop(union node *n, int flags) evaltree(n->nbinary.ch1, EV_TESTED); if (evalskip) { skipping: if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; + evalskip = SKIPNONE; continue; } if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; + evalskip = SKIPNONE; break; } if (n->type == NWHILE) { @@ -377,11 +394,11 @@ evalfor(union node *n, int flags) status = exitstatus; if (evalskip) { if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; + evalskip = SKIPNONE; continue; } if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; + evalskip = SKIPNONE; break; } } @@ -964,7 +981,7 @@ normal_fork: popredir(); INTON; if (evalskip == SKIPFUNC) { - evalskip = 0; + evalskip = SKIPNONE; skipcount = 0; } if (flags & EV_EXIT) @@ -1104,7 +1121,24 @@ prehash(union node *n) pathval()); } +STATIC int +in_function(void) +{ + return funcnest; +} +STATIC enum skipstate +current_skipstate(void) +{ + return evalskip; +} + +STATIC void +stop_skipping(void) +{ + evalskip = SKIPNONE; + skipcount = 0; +} /* * Builtin commands. Builtin commands whose functions are closely @@ -1151,9 +1185,84 @@ breakcmd(int argc, char **argv) return 0; } +int +dotcmd(int argc, char **argv) +{ + exitstatus = 0; + + if (argc >= 2) { /* That's what SVR2 does */ + char *fullname; + /* + * dot_funcnest needs to be 0 when not in a dotcmd, so it + * cannot be restored with (funcnest + 1). + */ + int dot_funcnest_old; + struct stackmark smark; + + setstackmark(&smark); + fullname = find_dot_file(argv[1]); + setinputfile(fullname, 1); + commandname = fullname; + dot_funcnest_old = dot_funcnest; + dot_funcnest = funcnest + 1; + cmdloop(0); + dot_funcnest = dot_funcnest_old; + popfile(); + popstackmark(&smark); + } + return exitstatus; +} + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + +STATIC char * +find_dot_file(char *basename) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(basename, '/')) + return basename; + + while ((fullname = padvance(&path, basename)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + + /* not found in the PATH */ + error("%s: not found", basename); + /* NOTREACHED */ +} + + /* * The return command. + * + * Quoth the POSIX standard: + * 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. + * + * As for the unspecified part, there seems to be no de-facto standard: bash + * ignores the return with a warning, zsh ignores the return in interactive + * mode but seems to liken it to exit in a script. (checked May 2014) + * + * We choose to silently ignore the return. Older versions of this shell + * set evalskip to SKIPFILE causing the shell to (indirectly) exit. This + * had at least the problem of circumventing the check for stopped jobs, + * which would occur for exit or ^D. */ int @@ -1161,17 +1270,19 @@ returncmd(int argc, char **argv) { int ret = argc > 1 ? number(argv[1]) : exitstatus; - if (funcnest) { + if ((dot_funcnest == 0 && funcnest) + || (dot_funcnest > 0 && funcnest - (dot_funcnest - 1) > 0)) { evalskip = SKIPFUNC; skipcount = 1; - return ret; - } - else { - /* Do what ksh does; skip the rest of the file */ + } else if (dot_funcnest > 0) { evalskip = SKIPFILE; skipcount = 1; - return ret; + } else { + /* XXX: should a warning be issued? */ + ret = 0; } + + return ret; } diff --git a/bin/sh/eval.h b/bin/sh/eval.h index 7c0652af13f7..151a0b7e6d1a 100644 --- a/bin/sh/eval.h +++ b/bin/sh/eval.h @@ -1,4 +1,4 @@ -/* $NetBSD: eval.h,v 1.15 2008/02/15 17:26:06 matt Exp $ */ +/* $NetBSD: eval.h,v 1.16 2014/05/31 14:42:18 christos Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -53,12 +53,21 @@ void evaltree(union node *, int); void evalbackcmd(union node *, struct backcmd *); /* in_function returns nonzero if we are currently evaluating a function */ -#define in_function() funcnest -extern int funcnest; -extern int evalskip; +int in_function(void); /* return non-zero, if evaluating a function */ /* reasons for skipping commands (see comment on breakcmd routine) */ -#define SKIPBREAK 1 -#define SKIPCONT 2 -#define SKIPFUNC 3 -#define SKIPFILE 4 +enum skipstate { + SKIPNONE = 0, /* not skipping */ + SKIPBREAK, /* break */ + SKIPCONT, /* continue */ + SKIPFUNC, /* return in a function */ + SKIPFILE /* return in a dot command */ +}; + +enum skipstate current_skipstate(void); +void stop_skipping(void); /* reset internal skipping state to SKIPNONE */ + +/* + * Only for use by reset() in init.c! + */ +void reset_eval(void); diff --git a/bin/sh/main.c b/bin/sh/main.c index 35082a9553cc..abe50067bdf4 100644 --- a/bin/sh/main.c +++ b/bin/sh/main.c @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.57 2011/06/18 21:18:46 christos Exp $ */ +/* $NetBSD: main.c,v 1.58 2014/05/31 14:42:18 christos Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -42,7 +42,7 @@ __COPYRIGHT("@(#) Copyright (c) 1991, 1993\ #if 0 static char sccsid[] = "@(#)main.c 8.7 (Berkeley) 7/19/95"; #else -__RCSID("$NetBSD: main.c,v 1.57 2011/06/18 21:18:46 christos Exp $"); +__RCSID("$NetBSD: main.c,v 1.58 2014/05/31 14:42:18 christos Exp $"); #endif #endif /* not lint */ @@ -89,7 +89,6 @@ extern int etext(); #endif STATIC void read_profile(const char *); -STATIC char *find_dot_file(char *); int main(int, char **); /* @@ -239,6 +238,7 @@ cmdloop(int top) struct stackmark smark; int inter; int numeof = 0; + enum skipstate skip; TRACE(("cmdloop(%d) called\n", top)); setstackmark(&smark); @@ -270,8 +270,18 @@ cmdloop(int top) } popstackmark(&smark); setstackmark(&smark); - if (evalskip == SKIPFILE) { - evalskip = 0; + + /* + * 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(); break; } } @@ -337,60 +347,6 @@ readcmdfile(char *name) -/* - * Take commands from a file. To be compatible we should do a path - * search for the file, which is necessary to find sub-commands. - */ - - -STATIC char * -find_dot_file(char *basename) -{ - char *fullname; - const char *path = pathval(); - struct stat statb; - - /* don't try this for absolute or relative paths */ - if (strchr(basename, '/')) - return basename; - - while ((fullname = padvance(&path, basename)) != NULL) { - if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { - /* - * Don't bother freeing here, since it will - * be freed by the caller. - */ - return fullname; - } - stunalloc(fullname); - } - - /* not found in the PATH */ - error("%s: not found", basename); - /* NOTREACHED */ -} - -int -dotcmd(int argc, char **argv) -{ - exitstatus = 0; - - if (argc >= 2) { /* That's what SVR2 does */ - char *fullname; - struct stackmark smark; - - setstackmark(&smark); - fullname = find_dot_file(argv[1]); - setinputfile(fullname, 1); - commandname = fullname; - cmdloop(0); - popfile(); - popstackmark(&smark); - } - return exitstatus; -} - - int exitcmd(int argc, char **argv) { diff --git a/bin/sh/sh.1 b/bin/sh/sh.1 index 3645811dc914..d7a8d15720ff 100644 --- a/bin/sh/sh.1 +++ b/bin/sh/sh.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: sh.1,v 1.112 2014/01/20 14:05:51 roy Exp $ +.\" $NetBSD: sh.1,v 1.113 2014/05/31 14:42:18 christos Exp $ .\" Copyright (c) 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" @@ -1192,10 +1192,23 @@ be built in for efficiency (e.g. .Xr test 1 , etc). .Bl -tag -width 5n -.It : +.It : [ Ar arg ... ] A null command that returns a 0 (true) exit value. +Any arguments are ignored. .It \&. file -The commands in the specified file are read and executed by the shell. +The dot command reads and executes the commands from the specified +.Ar file +in the current shell environment. +The file does not need to be executable and is looked up from the directories +listed in the +.Ev PATH +variable if it does not contain a directory separator +.Pq Sq / . +The return command can be used for a premature return from the sourced file. +.Pp +A non-obvious consequence of the file executing in the current environment +is that loop control keywords (continue and break) can be used in the file +to control loops surrounding the dot command. .It alias Op Ar name Ns Op Ar "=string ..." If .Ar name=string @@ -1632,6 +1645,19 @@ With the .Fl p option specified the output will be formatted suitably for non-interactive use. .Pp +.It return [ Ar n ] +Stop executing the current function or a dot command with return value of +.Ar n +or the value of the last executed command, if not specified. +For portability, +.Ar n +should be in the range from 0 to 255. +.Pp +The effects of using a return command outside a function or a dot command +are not standardized. +This implementation (currently) treats such a return as a no-op with +a return value of 0 (success, true). +Use the exit command if you want to return from a script or exit your shell. .It set Oo { Fl options | Cm +options | Cm \-- } Oc Ar arg ... The .Ic set