diff --git a/src/util/run.cpp b/src/util/run.cpp index ee509c19ec1..9ab46282cbc 100644 --- a/src/util/run.cpp +++ b/src/util/run.cpp @@ -12,6 +12,7 @@ Date: August 2012 #ifdef _WIN32 #include +#include #else #include @@ -37,14 +38,85 @@ int run(const std::string &what, const std::vector &argv) return run(what, argv, "", "", ""); } -#ifndef _WIN32 +#ifdef _WIN32 +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +using fdt = HANDLE; +#else +using fdt = int; +#endif + /// open given file to replace either stdin, stderr, stdout -static int stdio_redirection(int fd, const std::string &file) +static fdt stdio_redirection(int fd, const std::string &file) { - int result_fd = fd; + fdt result_fd; + +#ifdef _WIN32 + std::string name; + + SECURITY_ATTRIBUTES SecurityAttributes; + ZeroMemory(&SecurityAttributes, sizeof SecurityAttributes); + SecurityAttributes.bInheritHandle = true; + + switch(fd) + { + case STDIN_FILENO: + name = "stdin"; + if(file.empty()) + result_fd = GetStdHandle(STD_INPUT_HANDLE); + else + result_fd = CreateFileW( + widen(file).c_str(), + GENERIC_READ, + 0, + &SecurityAttributes, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY, + NULL); + break; + + case STDOUT_FILENO: + name = "stdout"; + if(file.empty()) + result_fd = GetStdHandle(STD_OUTPUT_HANDLE); + else + result_fd = CreateFileW( + widen(file).c_str(), + GENERIC_WRITE, + 0, + &SecurityAttributes, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + break; + + case STDERR_FILENO: + name = "stderr"; + if(file.empty()) + result_fd = GetStdHandle(STD_ERROR_HANDLE); + else + result_fd = CreateFileW( + widen(file).c_str(), + GENERIC_WRITE, + 0, + &SecurityAttributes, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + break; + + default: + UNREACHABLE; + } + + if(result_fd == INVALID_HANDLE_VALUE) + perror(("Failed to open " + name + " file " + file).c_str()); + +#else if(file.empty()) - return result_fd; + return fd; int flags = 0, mode = 0; std::string name; @@ -68,74 +140,160 @@ static int stdio_redirection(int fd, const std::string &file) } result_fd = open(file.c_str(), flags, mode); + if(result_fd == -1) perror(("Failed to open " + name + " file " + file).c_str()); +#endif return result_fd; } -#endif -int run( - const std::string &what, - const std::vector &argv, - const std::string &std_input, - const std::string &std_output, - const std::string &std_error) +#ifdef _WIN32 +// Read +// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ +std::wstring quote_windows_arg(const std::wstring &src) { - #ifdef _WIN32 - // we use the cmd.exe shell to do stdin/stdout/stderr redirection on Windows - if(!std_input.empty() || !std_output.empty() || !std_error.empty()) + if(src.find_first_of(L" \t\n\v\"") == src.npos) + return src; + + std::wstring result = L"\""; + + for(auto it = src.begin();; ++it) { - std::vector new_argv = argv; - new_argv.insert(new_argv.begin(), "cmd.exe"); - new_argv.insert(new_argv.begin() + 1, "/c"); + std::size_t NumberBackslashes = 0; - if(!std_input.empty()) + while(it != src.end() && *it == L'\\') { - new_argv.push_back("<"); - new_argv.push_back(std_input); + ++it; + ++NumberBackslashes; } - if(!std_output.empty()) + if(it == src.end()) { - new_argv.push_back(">"); - new_argv.push_back(std_output); + // + // Escape all backslashes, but let the terminating + // double quotation mark we add below be interpreted + // as a metacharacter. + // + + result.append(NumberBackslashes * 2, L'\\'); + break; } + else if(*it == L'"') + { + // + // Escape all backslashes and the following + // double quotation mark. + // - if(!std_error.empty()) + result.append(NumberBackslashes * 2 + 1, L'\\'); + result.push_back(*it); + } + else { - new_argv.push_back("2>"); - new_argv.push_back(std_error); + // + // Backslashes aren't special here. + // + + result.append(NumberBackslashes, L'\\'); + result.push_back(*it); } + } + + result.push_back(L'"'); + + return result; +} +#endif + +int run( + const std::string &what, + const std::vector &argv, + const std::string &std_input, + const std::string &std_output, + const std::string &std_error) +{ +#ifdef _WIN32 + // unicode commandline, quoted + std::wstring cmdline; + + // we replace argv[0] by what + cmdline = quote_windows_arg(widen(what)); - // this is recursive - return run(new_argv[0], new_argv, "", "", ""); + for(std::size_t i = 1; i < argv.size(); i++) + { + cmdline += L" "; + cmdline += quote_windows_arg(widen(argv[i])); } - // unicode version of the arguments - std::vector wargv; + PROCESS_INFORMATION piProcInfo; + STARTUPINFOW siStartInfo; + + ZeroMemory(&piProcInfo, sizeof piProcInfo); + ZeroMemory(&siStartInfo, sizeof siStartInfo); - wargv.resize(argv.size()); + siStartInfo.cb = sizeof siStartInfo; - for(std::size_t i=0; i _argv(argv.size()+1); + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; - for(std::size_t i=0; i mutable_cmdline(cmdline.begin(), cmdline.end()); + mutable_cmdline.push_back(0); // zero termination + wchar_t *cmdline_ptr = mutable_cmdline.data(); - _argv[argv.size()]=NULL; + BOOL bSuccess = CreateProcessW( + NULL, // application name + cmdline_ptr, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + true, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO + &piProcInfo); // PROCESS_INFORMATION - // warning: the arguments may still need escaping, - // as windows will concatenate the argv strings back together, - // separating them with spaces + if(!bSuccess) + { + if(!std_input.empty()) + CloseHandle(siStartInfo.hStdInput); + if(!std_output.empty()) + CloseHandle(siStartInfo.hStdOutput); + if(!std_error.empty()) + CloseHandle(siStartInfo.hStdError); + return -1; + } - std::wstring wide_what=widen(what); - int status=_wspawnvp(_P_WAIT, wide_what.c_str(), _argv.data()); - return status; + // wait for child to finish + WaitForSingleObject(piProcInfo.hProcess, INFINITE); - #else + if(!std_input.empty()) + CloseHandle(siStartInfo.hStdInput); + if(!std_output.empty()) + CloseHandle(siStartInfo.hStdOutput); + if(!std_error.empty()) + CloseHandle(siStartInfo.hStdError); + + DWORD exit_code; + + // get exit code + if(!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) + { + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + return -1; + } + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + + return exit_code; + +#else int stdin_fd = stdio_redirection(STDIN_FILENO, std_input); int stdout_fd = stdio_redirection(STDOUT_FILENO, std_output); int stderr_fd = stdio_redirection(STDERR_FILENO, std_error); @@ -226,5 +384,5 @@ int run( return 1; } - #endif +#endif }