From 4b7b4a960bab5b3e331e130b257fe8280fd9da43 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Mon, 19 Aug 2024 08:06:19 +0100 Subject: ash: optimise running of scripts The BusyBox shell detects certain cases where forking a command is unnecessary (last command in a script or subshell, for example) and calls execve(2) instead. This doesn't help in the Windows port because execve(2) is implemented by creating a process. There is one case where it is possible to apply this optimisation: if the command is a script and the script interpreter is an applet. - Have evalcommand() pass a flag to indicate this situation to shellexec(). Also, allocate two spare elements before the start of the argv array. - If the flag is TRUE shellexec() passes the shell's PATH variable down to tryexec() so it can perform a test for applet override. - If tryexec() finds that all the necessary conditions apply it can run a script by directly invoking the interpreter's main(). Adds 192-224 bytes. --- include/mingw.h | 8 +++++++ shell/ash.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++------ win32/process.c | 9 +------- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/include/mingw.h b/include/mingw.h index adb810ec5..ed7884e39 100644 --- a/include/mingw.h +++ b/include/mingw.h @@ -566,6 +566,14 @@ int utimes(const char *file_name, const struct timeval times[2]); #define is_unc_path(x) (strlen(x) > 4 && is_dir_sep(x[0]) && \ is_dir_sep(x[1]) && !is_dir_sep(x[2])) +typedef struct { + char *path; + char *name; + char *opts; + char buf[100]; +} interp_t; + +int FAST_FUNC parse_interpreter(const char *cmd, interp_t *interp); char ** FAST_FUNC grow_argv(char **argv, int n); pid_t FAST_FUNC mingw_spawn(char **argv); intptr_t FAST_FUNC mingw_spawn_detach(char **argv); diff --git a/shell/ash.c b/shell/ash.c index aa291b99d..2e4181b3f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9091,8 +9091,16 @@ static int builtinloc = -1; /* index in path of %builtin, or -1 */ static void +#if ENABLE_PLATFORM_MINGW32 +tryexec(IF_FEATURE_SH_STANDALONE(int applet_no, const char *nfpath,) + const char *cmd, char **argv, char **envp) +#else tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, char **envp) +#endif { +#if ENABLE_PLATFORM_MINGW32 && ENABLE_FEATURE_SH_STANDALONE + interp_t interp; +#endif #if ENABLE_FEATURE_SH_STANDALONE if (applet_no >= 0) { # if ENABLE_PLATFORM_MINGW32 @@ -9101,6 +9109,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c struct forkshell *fs = (struct forkshell *)sticky_mem_start; if (applet_main[applet_no] != ash_main || (fs && fs->fpid == FS_SHELLEXEC)) { + run_noexec: /* mingw-w64's getopt() uses __argv[0] as the program name */ __argv[0] = (char *)cmd; /* 'which' wants to know if it was invoked from a standalone @@ -9141,6 +9150,28 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c /* cmd was allocated on the stack with room for an extension */ add_win32_extension((char *)cmd); + +# if ENABLE_FEATURE_SH_STANDALONE + /* If nfpath is non-NULL, evalcommand() has determined this + * command doesn't need a fork(). If the command is a script + * with an interpreter which is an applet we can run it as if + * it were a noexec applet. */ + if (nfpath && parse_interpreter(cmd, &interp)) { + applet_no = find_applet_by_name_for_sh(interp.name, nfpath); + if (applet_no >= 0) { + argv[0] = (char *)cmd; + /* evalcommand() has added two elements before argv */ + if (interp.opts) { + argv--; + argv[0] = (char *)interp.opts; + } + argv--; + cmd = argv[0] = (char *)interp.name; + goto run_noexec; + } + } +# endif + execve(cmd, argv, envp); /* skip POSIX-mandated retry on ENOEXEC */ #else /* !ENABLE_PLATFORM_MINGW32 */ @@ -9182,20 +9213,29 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c #endif /* !ENABLE_PLATFORM_MINGW32 */ } +#if !ENABLE_PLATFORM_MINGW32 || !ENABLE_FEATURE_SH_STANDALONE +# define shellexec(prg, a, pth, i, n) shellexec(prg, a, pth, i) +#endif + /* * Exec a program. Never returns. If you change this routine, you may * have to change the find_command routine as well. * argv[-1] must exist and be writable! See tryexec() for why. */ static struct builtincmd *find_builtin(const char *name); -static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN; -static void shellexec(char *prog, char **argv, const char *path, int idx) +static void shellexec(char *prog, char **argv, const char *path, int idx, + int nofork) NORETURN; +static void shellexec(char *prog, char **argv, const char *path, int idx, + int nofork) { char *cmdname; int e; char **envp; int exerrno; int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */ +#if ENABLE_PLATFORM_MINGW32 && ENABLE_FEATURE_SH_STANDALONE + const char *nfpath = nofork ? path : NULL; +#endif envp = listvars(VEXPORT, VUNSET, /*strlist:*/ NULL, /*end:*/ NULL); #if ENABLE_FEATURE_SH_STANDALONE && ENABLE_PLATFORM_MINGW32 && defined(_UCRT) @@ -9217,7 +9257,8 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) ) { #if ENABLE_PLATFORM_MINGW32 char *progext = stack_add_ext_space(prog); - tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) progext, argv, envp); + tryexec(IF_FEATURE_SH_STANDALONE(applet_no, nfpath,) + progext, argv, envp); #else tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) prog, argv, envp); #endif @@ -9234,7 +9275,7 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) const char *name = bb_basename(prog); # if ENABLE_FEATURE_SH_STANDALONE if ((applet_no = find_applet_by_name_for_sh(name, path)) >= 0) { - tryexec(applet_no, name, argv, envp); + tryexec(applet_no, nfpath, name, argv, envp); e = errno; } # endif @@ -9250,7 +9291,12 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) while (padvance(&path, argv[0]) >= 0) { cmdname = stackblock(); if (--idx < 0 && pathopt == NULL) { +#if ENABLE_PLATFORM_MINGW32 + tryexec(IF_FEATURE_SH_STANDALONE(-1, nfpath,) + cmdname, argv, envp); +#else tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); +#endif if (errno != ENOENT && errno != ENOTDIR) e = errno; } @@ -11223,7 +11269,7 @@ execcmd(int argc UNUSED_PARAM, char **argv) prog = argv[0]; if (optionarg) argv[0] = optionarg; - shellexec(prog, argv, pathval(), 0); + shellexec(prog, argv, pathval(), 0, FALSE); /* NOTREACHED */ } return 0; @@ -11581,7 +11627,13 @@ evalcommand(union node *cmd, int flags) localvar_stop = pushlocalvars(vlocal); #if ENABLE_PLATFORM_MINGW32 +# if ENABLE_FEATURE_SH_STANDALONE + /* Reserve two extra spots at the front for shellexec. */ + nargv = stalloc(sizeof(char *) * (argc + 3)); + argv = nargv = nargv + 2; +# else argv = nargv = stalloc(sizeof(char *) * (argc + 1)); +# endif #else /* Reserve one extra spot at the front for shellexec. */ nargv = stalloc(sizeof(char *) * (argc + 2)); @@ -11773,7 +11825,7 @@ evalcommand(union node *cmd, int flags) /* fall through to exec'ing external program */ } #endif - shellexec(argv[0], argv, path, cmdentry.u.index); + shellexec(argv[0], argv, path, cmdentry.u.index, TRUE); /* NOTREACHED */ } /* default */ case CMDBUILTIN: @@ -16492,7 +16544,7 @@ forkshell_shellexec(struct forkshell *fs) char *path = fs->path; FORCE_INT_ON; - shellexec(argv[0], argv, path, idx); + shellexec(argv[0], argv, path, idx, FALSE); } static void diff --git a/win32/process.c b/win32/process.c index 097a1d71c..e3ce95ca0 100644 --- a/win32/process.c +++ b/win32/process.c @@ -53,14 +53,7 @@ pid_t mingw_wait3(pid_t pid, int *status, int options, struct rusage *rusage) return -1; } -typedef struct { - char *path; - char *name; - char *opts; - char buf[100]; -} interp_t; - -static int +int FAST_FUNC parse_interpreter(const char *cmd, interp_t *interp) { char *path, *t; -- cgit v1.2.3-55-g6feb