The new tests/test-spawn-pipe-script.c test reveals that create_pipe_in returns an odd errno value when the program is an executable script without '#!' marker. This patch fixes it.
2020-12-24 Bruno Haible <br...@clisp.org> findprog-in: Improve errno upon failure on native Windows. * lib/findprog-in.c (find_in_given_path): If the file basename has no dot and the search with a suffix returned no result, do also a search without a suffix, and set errno = ENOEXEC if we find a file in this way. * tests/test-spawn-pipe-script.c (main): Update expected errno. diff --git a/lib/findprog-in.c b/lib/findprog-in.c index 63b8419..d0505fb 100644 --- a/lib/findprog-in.c +++ b/lib/findprog-in.c @@ -118,6 +118,8 @@ find_in_given_path (const char *progname, const char *path, if (ISSLASH (*p)) progbasename = p + 1; } + + bool progbasename_has_dot = (strchr (progbasename, '.') != NULL); #endif /* Try all platform-dependent suffixes. */ @@ -129,7 +131,7 @@ find_in_given_path (const char *progname, const char *path, #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */ /* File names without a '.' are not considered executable, and for file names with a '.' no additional suffix is tried. */ - if ((*suffix != '\0') != (strchr (progbasename, '.') != NULL)) + if ((*suffix != '\0') != progbasename_has_dot) #endif { /* Concatenate directory_as_prefix, progname, suffix. */ @@ -174,6 +176,36 @@ find_in_given_path (const char *progname, const char *path, free (progpathname); } } + #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */ + if (failure_errno == ENOENT && !progbasename_has_dot) + { + /* In the loop above, we skipped suffix = "". Do this loop + round now, merely to provide a better errno than ENOENT. */ + + char *progpathname = + concatenated_filename (directory_as_prefix, progname, ""); + + if (progpathname == NULL) + return NULL; /* errno is set here */ + + if (eaccess (progpathname, X_OK) == 0) + { + struct stat statbuf; + + if (stat (progpathname, &statbuf) >= 0) + { + if (! S_ISDIR (statbuf.st_mode)) + errno = ENOEXEC; + else + errno = EACCES; + } + } + + failure_errno = errno; + + free (progpathname); + } + #endif errno = failure_errno; return NULL; @@ -196,6 +228,10 @@ find_in_given_path (const char *progname, const char *path, char *path_rest; char *cp; + #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */ + bool progname_has_dot = (strchr (progname, '.') != NULL); + #endif + failure_errno = ENOENT; for (path_rest = path_copy; ; path_rest = cp + 1) { @@ -243,7 +279,7 @@ find_in_given_path (const char *progname, const char *path, #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */ /* File names without a '.' are not considered executable, and for file names with a '.' no additional suffix is tried. */ - if ((*suffix != '\0') != (strchr (progname, '.') != NULL)) + if ((*suffix != '\0') != progname_has_dot) #endif { /* Concatenate dir_as_prefix, progname, and suffix. */ @@ -311,6 +347,41 @@ find_in_given_path (const char *progname, const char *path, free (progpathname); } } + #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */ + if (failure_errno == ENOENT && !progname_has_dot) + { + /* In the loop above, we skipped suffix = "". Do this loop + round now, merely to provide a better errno than ENOENT. */ + + char *progpathname = + concatenated_filename (dir_as_prefix, progname, ""); + + if (progpathname == NULL) + { + /* errno is set here. */ + failure_errno = errno; + free (dir_as_prefix_to_free); + goto failed; + } + + if (eaccess (progpathname, X_OK) == 0) + { + struct stat statbuf; + + if (stat (progpathname, &statbuf) >= 0) + { + if (! S_ISDIR (statbuf.st_mode)) + errno = ENOEXEC; + else + errno = EACCES; + } + } + + failure_errno = errno; + + free (progpathname); + } + #endif free (dir_as_prefix_to_free); diff --git a/tests/test-spawn-pipe-script.c b/tests/test-spawn-pipe-script.c index 44f9d2c..dbd28ed 100644 --- a/tests/test-spawn-pipe-script.c +++ b/tests/test-spawn-pipe-script.c @@ -56,11 +56,7 @@ main () else { ASSERT (pid == -1); -#if defined _WIN32 && !defined __CYGWIN__ - ASSERT (errno == ENOENT); -#else ASSERT (errno == ENOEXEC); -#endif } }