From 7209ab16b8ef7ac9d7a298434aaf456d1cc6a982 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Tue, 17 Apr 2012 11:47:16 +0100 Subject: Detect and execute shell scripts based on presence of '#!' --- libbb/execable.c | 30 ++++++++++++++++++----- shell/ash.c | 15 ++++++++++-- win32/process.c | 74 +++++++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 91 insertions(+), 28 deletions(-) diff --git a/libbb/execable.c b/libbb/execable.c index e35dad208..afb957e43 100644 --- a/libbb/execable.c +++ b/libbb/execable.c @@ -16,15 +16,31 @@ int FAST_FUNC execable_file(const char *name) { struct stat s; - if (ENABLE_PLATFORM_MINGW32) { - int len = strlen(name); - return len > 4 && +#if ENABLE_PLATFORM_MINGW32 + if (!stat(name, &s) && S_ISREG(s.st_mode)) { + int len, fd, n; + char buf[100]; + + if ((len=strlen(name)) > 4 && (!strcasecmp(name+len-4, ".exe") || - !strcasecmp(name+len-4, ".com")) && - !stat(name, &s) && - S_ISREG(s.st_mode); + !strcasecmp(name+len-4, ".com"))) { + return 1; + } + + fd = open(name, O_RDONLY); + if (fd < 0) + return 0; + n = read(fd, buf, sizeof(buf)-1); + close(fd); + if (n < 4) /* at least '#!/x' and not error */ + return 0; + + return (buf[0] == '#' && buf[1] == '!'); } + return 0; +#else return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); +#endif } /* search (*PATHp) for an executable file; @@ -67,11 +83,13 @@ char* FAST_FUNC find_execable(const char *filename, char **PATHp) memcpy(np+len, ".exe", 5); if (execable_file(np)) { *PATHp = n; + free(p); return np; } memcpy(np+len, ".com", 5); if (execable_file(np)) { *PATHp = n; + free(p); return np; } } diff --git a/shell/ash.c b/shell/ash.c index a809bf181..54c299d7e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -21,7 +21,9 @@ * * - Environment variables from Windows will all be turned to uppercase. * - PATH accepts both ; and : as separator, but can't be mixed - * - command without ".exe" is still understood as executable (option to turn off?) + * - command without ".exe" extension is still understood as executable + * - shell scripts on the path are detected by the presence of '#!'; + * the path to the interpreter is ignored, PATH is searched to find it * - both / and \ are supported in PATH. Usually you must use / * - trap/job does not work * - /dev/null is supported for redirection @@ -12947,7 +12949,16 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) if (stat(fullname, &statb) < 0) { if (errno != ENOENT && errno != ENOTDIR) e = errno; - goto loop; + fullname[len] = '\0'; + if (stat(fullname, &statb) < 0) { + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + if (!execable_file(fullname)) { + e = ENOEXEC; + goto loop; + } } } fullname[len] = '\0'; diff --git a/win32/process.c b/win32/process.c index 3ce152c45..2cfec4fdf 100644 --- a/win32/process.c +++ b/win32/process.c @@ -30,13 +30,18 @@ next_path_sep(const char *path) return strchr(has_dos_drive_prefix(path) ? path+2 : path, ':'); } +#define MAX_OPT 10 + static const char * -parse_interpreter(const char *cmd) +parse_interpreter(const char *cmd, char ***opts, int *nopts) { static char buf[100]; - char *p, *opt; + char *p, *s, *t, *opt[MAX_OPT]; int n, fd; + *nopts = 0; + *opts = opt; + /* don't even try a .exe */ n = strlen(cmd); if (n >= 4 && @@ -58,13 +63,34 @@ parse_interpreter(const char *cmd) p = strchr(buf, '\n'); if (!p) return NULL; - *p = '\0'; + + /* remove trailing whitespace */ + while ( isspace(*--p) ) { + *p = '\0'; + } + if (!(p = strrchr(buf+2, '/')) && !(p = strrchr(buf+2, '\\'))) return NULL; - /* strip options */ - if ((opt = strchr(p+1, ' '))) - *opt = '\0'; + + /* move to end of interpreter name */ + for ( s=p; *s && !isspace(*s); ++s ) { + } + + n = 0; + if ( *s != '\0' ) { + /* there are options */ + *s++ = '\0'; + + while ( (t=strtok(s, " \t")) && n < MAX_OPT ) { + s = NULL; + opt[n++] = t; + } + } + + *nopts = n; + *opts = opt; + return p+1; } @@ -179,35 +205,43 @@ static pid_t mingw_spawn_interpreter(int mode, const char *prog, const char *const *argv, const char *const *envp) { int ret; - const char *interpr = parse_interpreter(prog); + char **opts; + int nopts; + const char *interpr = parse_interpreter(prog, &opts, &nopts); + const char **new_argv; + int argc = 0; if (!interpr) return spawnveq(mode, prog, argv, envp); - if (ENABLE_FEATURE_PREFER_APPLETS && !strcmp(interpr, "sh")) { - const char **new_argv; - int argc = 0; - - while (argv[argc]) - argc++; - new_argv = malloc(sizeof(*argv)*(argc+2)); - memcpy(new_argv+1, argv, sizeof(*argv)*(argc+1)); - new_argv[0] = prog; /* pass absolute path */ - ret = mingw_spawn_applet(mode, "sh", new_argv, envp); - free(new_argv); + + while (argv[argc]) + argc++; + new_argv = malloc(sizeof(*argv)*(argc+nopts+2)); + memcpy(new_argv+1, opts, sizeof(*opts)*nopts); + memcpy(new_argv+nopts+2, argv+1, sizeof(*argv)*argc); + new_argv[nopts+1] = prog; /* pass absolute path */ + + if (ENABLE_FEATURE_PREFER_APPLETS && find_applet_by_name(interpr) >= 0) { + new_argv[0] = interpr; + ret = mingw_spawn_applet(mode, interpr, new_argv, envp); } else { char *path = xstrdup(getenv("PATH")); char *tmp = path; char *iprog = find_execable(interpr, &tmp); free(path); - if (!prog) { + if (!iprog) { + free(new_argv); errno = ENOENT; return -1; } - ret = spawnveq(mode, iprog, argv, envp); + new_argv[0] = iprog; + ret = spawnveq(mode, iprog, new_argv, envp); free(iprog); } + + free(new_argv); return ret; } -- cgit v1.2.3-55-g6feb