diff --git a/docs/src/guide/processes.rst b/docs/src/guide/processes.rst index 503e185f9..b042fb5a2 100644 --- a/docs/src/guide/processes.rst +++ b/docs/src/guide/processes.rst @@ -94,6 +94,9 @@ modifies the child process behaviour: Changing the UID/GID is only supported on Unix, ``uv_spawn`` will fail on Windows with ``UV_ENOTSUP``. +* ``UV_PROCESS_WINDOWS_RESOLVE_BATCH`` - Considers .cmd and .bat files + when resolving an executable without an explicit extension on Windows. + Ignored on Unix. * ``UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS`` - No quoting or escaping of ``uv_process_options_t.args`` is done on Windows. Ignored on Unix. * ``UV_PROCESS_DETACHED`` - Starts the child process in a new session, which diff --git a/docs/src/process.rst b/docs/src/process.rst index c6bcd9fa8..ef6c45e79 100644 --- a/docs/src/process.rst +++ b/docs/src/process.rst @@ -92,7 +92,13 @@ Data types * search for the exact file name before trying variants with * extensions like '.exe' or '.cmd'. */ - UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7) + UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7), + /* + * Consider '.cmd' and '.bat' files when resolving an executable + * without an explicit extension. This option is only meaningful + * on Windows systems. On Unix it is silently ignored. + */ + UV_PROCESS_WINDOWS_RESOLVE_BATCH = (1 << 8) }; .. c:type:: uv_stdio_container_t diff --git a/include/uv.h b/include/uv.h index d435a8de3..721eacff7 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1161,7 +1161,13 @@ enum uv_process_flags { * search for the exact file name before trying variants with * extensions like '.exe' or '.cmd'. */ - UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7) + UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7), + /* + * Consider '.cmd' and '.bat' files when resolving an executable + * without an explicit extension. This option is only meaningful + * on Windows systems. On Unix it is silently ignored. + */ + UV_PROCESS_WINDOWS_RESOLVE_BATCH = (1 << 8) }; /* diff --git a/src/unix/process.c b/src/unix/process.c index 0a1b2de93..08c1a5fc6 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -1028,6 +1028,7 @@ int uv_spawn(uv_loop_t* loop, UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | UV_PROCESS_WINDOWS_HIDE_GUI | + UV_PROCESS_WINDOWS_RESOLVE_BATCH | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); diff --git a/src/win/process.c b/src/win/process.c index c4471cd45..a8e1e3c29 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -249,7 +249,8 @@ static WCHAR* path_search_walk_ext(const WCHAR *dir, size_t name_len, WCHAR *cwd, size_t cwd_len, - int name_has_ext) { + int name_has_ext, + int resolve_batch) { WCHAR* result; /* If the name itself has a nonempty extension, try this extension first */ @@ -281,6 +282,28 @@ static WCHAR* path_search_walk_ext(const WCHAR *dir, return result; } + if (!resolve_batch) { + return NULL; + } + + /* Try .cmd extension */ + result = search_path_join_test(dir, dir_len, + name, name_len, + L"cmd", 3, + cwd, cwd_len); + if (result != NULL) { + return result; + } + + /* Try .bat extension */ + result = search_path_join_test(dir, dir_len, + name, name_len, + L"bat", 3, + cwd, cwd_len); + if (result != NULL) { + return result; + } + return NULL; } @@ -340,6 +363,7 @@ static WCHAR* search_path(const WCHAR *file, const WCHAR *dir_start, *dir_end, *dir_path; size_t dir_len; int name_has_ext; + int resolve_batch = flags & UV_PROCESS_WINDOWS_RESOLVE_BATCH; size_t file_len = wcslen(file); size_t cwd_len = wcslen(cwd); @@ -373,7 +397,8 @@ static WCHAR* search_path(const WCHAR *file, file, file_name_start - file, file_name_start, file_len - (file_name_start - file), cwd, cwd_len, - name_has_ext || (flags & UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME)); + name_has_ext || (flags & UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME), + resolve_batch); } else { dir_end = path; @@ -383,7 +408,7 @@ static WCHAR* search_path(const WCHAR *file, result = path_search_walk_ext(L"", 0, file, file_len, cwd, cwd_len, - name_has_ext); + name_has_ext, resolve_batch); } while (result == NULL) { @@ -433,7 +458,7 @@ static WCHAR* search_path(const WCHAR *file, result = path_search_walk_ext(dir_path, dir_len, file, file_len, cwd, cwd_len, - name_has_ext); + name_has_ext, resolve_batch); } } @@ -928,6 +953,7 @@ int uv_spawn(uv_loop_t* loop, UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | UV_PROCESS_WINDOWS_HIDE_GUI | + UV_PROCESS_WINDOWS_RESOLVE_BATCH | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); err = uv__utf8_to_utf16_alloc(options->file, &application); diff --git a/test/test-list.h b/test/test-list.h index 5ce84e9f6..5a7f65fa7 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -352,6 +352,7 @@ TEST_DECLARE (spawn_quoted_path) TEST_DECLARE (spawn_tcp_server) TEST_DECLARE (spawn_exercise_sigchld_issue) TEST_DECLARE (spawn_relative_path) +TEST_DECLARE (spawn_batch_file) TEST_DECLARE (fs_poll) TEST_DECLARE (fs_poll_getpath) TEST_DECLARE (fs_poll_close_request) @@ -1049,6 +1050,7 @@ TASK_LIST_START TEST_ENTRY (spawn_tcp_server) TEST_ENTRY (spawn_exercise_sigchld_issue) TEST_ENTRY (spawn_relative_path) + TEST_ENTRY (spawn_batch_file) TEST_ENTRY (fs_poll) TEST_ENTRY (fs_poll_getpath) TEST_ENTRY (fs_poll_close_request) diff --git a/test/test-spawn.c b/test/test-spawn.c index eaaa8bbea..7d1e62194 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -1532,6 +1532,7 @@ TEST_IMPL(spawn_setuid_fails) { #endif /* These flags should be ignored on Unices. */ + options.flags |= UV_PROCESS_WINDOWS_RESOLVE_BATCH; options.flags |= UV_PROCESS_WINDOWS_HIDE; options.flags |= UV_PROCESS_WINDOWS_HIDE_CONSOLE; options.flags |= UV_PROCESS_WINDOWS_HIDE_GUI; @@ -2111,3 +2112,36 @@ TEST_IMPL(spawn_relative_path) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } + +TEST_IMPL(spawn_batch_file) { +#ifndef _WIN32 + RETURN_SKIP("Test for Windows"); +#else + char filename[1024]; + FILE* file; + uv_process_t process2; + + snprintf(exepath, sizeof(exepath), "test_file_foo"); + snprintf(filename, sizeof(filename), "%s.bat", exepath); + file = fopen(filename, "w"); + fprintf(file, "exit /B 1\r\n"); + fclose(file); + + args[0] = exepath; + args[1] = NULL; + options.file = args[0]; + options.args = args; + options.exit_cb = exit_cb; + options.flags = 0; + + ASSERT_EQ(uv_spawn(uv_default_loop(), &process, &options), UV_ENOENT); + + options.flags |= UV_PROCESS_WINDOWS_RESOLVE_BATCH; + ASSERT_OK(uv_spawn(uv_default_loop(), &process2, &options)); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + remove(filename); + return 0; +#endif +}