From 650f67507f2718dec0d4282afea619cfe7a53305 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Mon, 26 Feb 2018 20:39:23 +0000 Subject: win32: move detection of file formats to stat(2) Move the code to detect shell scripts and binary executables from mingw_access to a separate function, has_exec_format. Call this function in do_lstat to decide whether to set the executable bits in the file mode. This will slow down stat but has a couple of advantages: - shell scripts are highlighted in ls output - the test applet can use stat(2) to detect executable files The new function is used to handle another corner case in spawnveq: binary executables without the usual .exe extension are only run by spawnve if the file name ends with '.'. Two minor changes: - file_is_win32_executable has been renamed add_win32_extension to clarify what it does - a call to file_is_executable has been removed from find_command in ash as it resulted in unhelpful error messages. --- coreutils/test.c | 15 ------- debianutils/which.c | 2 +- include/mingw.h | 3 +- libbb/executable.c | 2 +- shell/ash.c | 6 +-- win32/mingw.c | 111 ++++++++++++++++++++++++++++------------------------ win32/process.c | 20 ++++++++-- 7 files changed, 82 insertions(+), 77 deletions(-) diff --git a/coreutils/test.c b/coreutils/test.c index ed708c6d3..a8286525a 100644 --- a/coreutils/test.c +++ b/coreutils/test.c @@ -637,21 +637,6 @@ static int filstat(char *nm, enum token mode) return 0; } -#if ENABLE_PLATFORM_MINGW32 - if (mode == FILEX) { - char *p; - - if (file_is_executable(nm)) { - return 1; - } - else if ((p=file_is_win32_executable(nm))) { - free(p); - return 1; - } - return 0; - } -#endif - if (stat(nm, &s) != 0) return 0; if (mode == FILEXIST) diff --git a/debianutils/which.c b/debianutils/which.c index 13f922615..9060a5b47 100644 --- a/debianutils/which.c +++ b/debianutils/which.c @@ -71,7 +71,7 @@ int which_main(int argc UNUSED_PARAM, char **argv) puts(*argv); } #if ENABLE_PLATFORM_MINGW32 - else if ((p=file_is_win32_executable(*argv)) != NULL) { + else if ((p=add_win32_extension(*argv)) != NULL) { missing = 0; puts(p); free(p); diff --git a/include/mingw.h b/include/mingw.h index 09c140d31..c116e551a 100644 --- a/include/mingw.h +++ b/include/mingw.h @@ -476,7 +476,8 @@ void init_winsock(void); int has_exe_suffix(const char *p); int has_bat_suffix(const char *p); -char *file_is_win32_executable(const char *p); +char *add_win32_extension(const char *p); +int has_exec_format(const char *name); int err_win_to_posix(DWORD winerr); diff --git a/libbb/executable.c b/libbb/executable.c index 8e2f99732..76b10f790 100644 --- a/libbb/executable.c +++ b/libbb/executable.c @@ -77,7 +77,7 @@ char* FAST_FUNC find_executable(const char *filename, char **PATHp) return p; } #if ENABLE_PLATFORM_MINGW32 - else if ((w=file_is_win32_executable(p))) { + else if ((w=add_win32_extension(p))) { *PATHp = n; free(p); return w; diff --git a/shell/ash.c b/shell/ash.c index 30f3b558b..7dec5dfc7 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13654,7 +13654,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) if (act & DO_ABS) { while (stat(name, &statb) < 0 #if ENABLE_PLATFORM_MINGW32 - && (fullname=file_is_win32_executable(name)) == NULL + && (fullname=add_win32_extension(name)) == NULL #endif ) { #ifdef SYSV @@ -13798,10 +13798,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) e = errno; goto loop; } - if (!file_is_executable(fullname)) { - e = ENOEXEC; - goto loop; - } } } #else diff --git a/win32/mingw.c b/win32/mingw.c index bd4d9b34a..713778ff1 100644 --- a/win32/mingw.c +++ b/win32/mingw.c @@ -300,7 +300,8 @@ static int do_lstat(int follow, const char *file_name, struct mingw_stat *buf) buf->st_gid = DEFAULT_GID; buf->st_nlink = 1; buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes); - if (has_exe_suffix(file_name)) + if (S_ISREG(buf->st_mode) && + (has_exe_suffix(file_name) || has_exec_format(file_name))) buf->st_mode |= S_IXUSR|S_IXGRP|S_IXOTH; buf->st_size = fdata.nFileSizeLow | (((off64_t)fdata.nFileSizeHigh)<<32); @@ -982,9 +983,6 @@ int mingw_access(const char *name, int mode) { int ret; struct stat s; - int fd, n, sig; - unsigned int offset; - unsigned char buf[1024]; /* Windows can only handle test for existence, read or write */ if (mode == F_OK || (mode & ~X_OK)) { @@ -994,52 +992,8 @@ int mingw_access(const char *name, int mode) } } - if (!mingw_stat(name, &s) && S_ISREG(s.st_mode)) { - - /* stat marks files as executable according to their suffix */ - if ((s.st_mode&S_IEXEC)) { - return 0; - } - - fd = open(name, O_RDONLY); - if (fd < 0) - return -1; - n = read(fd, buf, sizeof(buf)-1); - close(fd); - if (n < 4) /* at least '#!/x' and not error */ - return -1; - - /* shell script */ - if (buf[0] == '#' && buf[1] == '!') { - return 0; - } - - /* - * Poke about in file to see if it's a PE binary. I've just copied - * the magic from the file command. - */ - if (buf[0] == 'M' && buf[1] == 'Z') { - offset = (buf[0x19] << 8) + buf[0x18]; - if (offset > 0x3f) { - offset = (buf[0x3f] << 24) + (buf[0x3e] << 16) + - (buf[0x3d] << 8) + buf[0x3c]; - if (offset < sizeof(buf)-100) { - if (memcmp(buf+offset, "PE\0\0", 4) == 0) { - sig = (buf[offset+25] << 8) + buf[offset+24]; - if (sig == 0x10b || sig == 0x20b) { - sig = (buf[offset+23] << 8) + buf[offset+22]; - if ((sig & 0x2000) != 0) { - /* DLL */ - return -1; - } - sig = buf[offset+92]; - return !(sig == 1 || sig == 2 || - sig == 3 || sig == 7); - } - } - } - } - } + if (!mingw_stat(name, &s) && S_ISREG(s.st_mode) && (s.st_mode&S_IXUSR)) { + return 0; } return -1; @@ -1085,7 +1039,7 @@ int has_exe_suffix(const char *name) * * if path already has a suffix don't even bother trying */ -char *file_is_win32_executable(const char *p) +char *add_win32_extension(const char *p) { char *path; int i, len; @@ -1109,6 +1063,61 @@ char *file_is_win32_executable(const char *p) return NULL; } +/* + * Examine a file's contents to determine if it can be executed. This + * should be a last resort: in most cases it's much more efficient to + * check the file extension. + * + * We look for two types of file: shell scripts and binary executables. + */ +int has_exec_format(const char *name) +{ + int fd, n, sig; + unsigned int offset; + unsigned char buf[1024]; + + 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; + + /* shell script */ + if (buf[0] == '#' && buf[1] == '!') { + return 1; + } + + /* + * Poke about in file to see if it's a PE binary. I've just copied + * the magic from the file command. + */ + if (buf[0] == 'M' && buf[1] == 'Z') { + offset = (buf[0x19] << 8) + buf[0x18]; + if (offset > 0x3f) { + offset = (buf[0x3f] << 24) + (buf[0x3e] << 16) + + (buf[0x3d] << 8) + buf[0x3c]; + if (offset < sizeof(buf)-100) { + if (memcmp(buf+offset, "PE\0\0", 4) == 0) { + sig = (buf[offset+25] << 8) + buf[offset+24]; + if (sig == 0x10b || sig == 0x20b) { + sig = (buf[offset+23] << 8) + buf[offset+22]; + if ((sig & 0x2000) != 0) { + /* DLL */ + return 0; + } + sig = buf[offset+92]; + return (sig == 1 || sig == 2 || sig == 3 || sig == 7); + } + } + } + } + } + + return 0; +} + #undef opendir DIR *mingw_opendir(const char *path) { diff --git a/win32/process.c b/win32/process.c index da83d1c96..eda143e0e 100644 --- a/win32/process.c +++ b/win32/process.c @@ -192,6 +192,7 @@ static intptr_t spawnveq(int mode, const char *path, char *const *argv, char *const *env) { char **new_argv; + char *new_path = NULL; int i, argc = -1; intptr_t ret; @@ -214,7 +215,7 @@ spawnveq(int mode, const char *path, char *const *argv, char *const *env) p = strdup(new_argv[0]); } else { - p = file_is_win32_executable(new_argv[0]); + p = add_win32_extension(new_argv[0]); } if (p != NULL && has_bat_suffix(p)) { @@ -231,12 +232,25 @@ spawnveq(int mode, const char *path, char *const *argv, char *const *env) } } - ret = spawnve(mode, path, new_argv, env); + /* + * Another special case: if a binary executable doesn't have an + * extension spawnve will only run it if the filename ends with a '.'. + */ + if (!has_exe_suffix(path)) { + int len = strlen(path); + + if (path[len-1] != '.' && has_exec_format(path)) { + new_path = xasprintf("%s.", path); + } + } + + ret = spawnve(mode, new_path ? new_path : path, new_argv, env); for (i = 0;i < argc;i++) if (new_argv[i] != argv[i]) free(new_argv[i]); free(new_argv); + free(new_path); return ret; } @@ -275,7 +289,7 @@ mingw_spawn_interpreter(int mode, const char *prog, char *const *argv, char *con memcpy(new_argv+nopts+2, argv+1, sizeof(*argv)*argc); if (file_is_executable(int_path) || - (fullpath=file_is_win32_executable(int_path)) != NULL) { + (fullpath=add_win32_extension(int_path)) != NULL) { new_argv[0] = fullpath ? fullpath : int_path; ret = spawnveq(mode, new_argv[0], new_argv, envp); } else -- cgit v1.2.3-55-g6feb