diff --git a/src/platform_win.cc b/src/platform_win.cc index cb852cf2..2feb7d0c 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -58,9 +58,171 @@ void FreeUnusedMemory() {} // TODO Wait for debugger to attach void TraceMe() {} +class Utf8To16 { +public: + Utf8To16(const char *utf8String); + + operator const wchar_t * () const {return m_str.c_str();} + operator const std::wstring & () const {return m_str;} + + const std::wstring &asWstring() const {return m_str;} + const wchar_t *asWchar_t() const {return m_str.c_str();} + +private: + std::wstring m_str; +}; + +Utf8To16::Utf8To16(const char *utf8Str) { + const std::size_t inSize = ::strlen(utf8Str); + + // find out how many utf16 characters we need + const int requiredU16Chars = + MultiByteToWideChar(CP_UTF8, 0, utf8Str, inSize, nullptr, 0); + m_str.resize(requiredU16Chars); + + // finally, do the conversion + MultiByteToWideChar(CP_UTF8, 0, utf8Str, inSize, &m_str[0], m_str.size()); +} + +void closeHandleIfValid(HANDLE &f_handle) { + if(f_handle != INVALID_HANDLE_VALUE) { + CloseHandle(f_handle); + f_handle = INVALID_HANDLE_VALUE; + } +} + +std::string readAllFromPipe(HANDLE pipe) { + std::string ret; + const int BUFFER_SIZE = 1024; + CHAR buf[BUFFER_SIZE]; + DWORD bytesRead = 0; + bool moreToRead = true; + + while (moreToRead) { + if (ReadFile(pipe, buf, BUFFER_SIZE, &bytesRead, NULL)) { + ret.append(buf, bytesRead); + } else { + const DWORD err = GetLastError(); + + if(err == ERROR_BROKEN_PIPE) { + // child process terminated (this is not an error) + } else { + LOG_S(ERROR) << "Error while reading from child process: " << err; + } + moreToRead = false; + } + } + return ret; +} + std::string GetExternalCommandOutput(const std::vector &command, std::string_view input) { - return ""; + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(saAttr); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = nullptr; + + enum { READ_END, WRITE_END, NUM_HANDLES }; + + HANDLE stdIn[NUM_HANDLES]; + HANDLE stdOut[NUM_HANDLES]; + HANDLE stdErr[NUM_HANDLES]; + + // create anonymous pipes for the child process + if (!CreatePipe(&stdIn[READ_END], &stdIn[WRITE_END], &saAttr, 0) || + !CreatePipe(&stdOut[READ_END], &stdOut[WRITE_END], &saAttr, 0) || + !CreatePipe(&stdErr[READ_END], &stdErr[WRITE_END], &saAttr, 0)) { + LOG_S(ERROR) << "Error creating pipes for child process"; + return ""; + } + + // the child is not supposed to gain access to the pipes' parent end + if(!SetHandleInformation(stdIn[WRITE_END], HANDLE_FLAG_INHERIT, 0) || + !SetHandleInformation(stdOut[READ_END], HANDLE_FLAG_INHERIT, 0) || + !SetHandleInformation(stdErr[READ_END], HANDLE_FLAG_INHERIT, 0)) + { + LOG_S(ERROR) << "Error in SetHandleInformation: " << GetLastError(); + return ""; + } + + // set up STARTUPINFO structure. It tells CreateProcess to use the pipes + // we just created as stdin and stdout for the new process. + STARTUPINFOW siStartInfo; + memset(&siStartInfo, 0, sizeof(siStartInfo)); + siStartInfo.cb = sizeof(siStartInfo); + siStartInfo.hStdInput = stdIn[READ_END]; + siStartInfo.hStdOutput = stdOut[WRITE_END]; + siStartInfo.hStdError = stdErr[WRITE_END]; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION process; + memset(&process, 0, sizeof(process)); + + // fold the command line parts into a single string, quoting the arguments + std::string cmdString; + for (int i = 0; i < command.size(); i++) { + cmdString += ((i > 0) ? " \"" : "\"") + command[i] + "\""; + } + + std::wstring cmdString_u16 = Utf8To16(cmdString.c_str()); + if(!CreateProcessW(NULL, // app name: we pass it through lpCommandLine + &cmdString_u16[0], + NULL, // security attrs + NULL, // thread security attrs + TRUE, // handles are inherited + CREATE_UNICODE_ENVIRONMENT, // creation flags + NULL, // environment + NULL, // cwd + &siStartInfo, // in: stdin, stdout, stderr pipes + &process // out: info about the new process + )) { + LOG_S(ERROR) << "Error creating process " << GetLastError(); + return ""; + } + + // we need to close our handles to the write end of these pipe. Otherwise, + // ReadFile() will not return when the child process terminates. + closeHandleIfValid(stdOut[WRITE_END]); + closeHandleIfValid(stdErr[WRITE_END]); + closeHandleIfValid(stdIn[READ_END]); + + // write input data to process' stdin + DWORD numBytesOut = 0; + WriteFile(stdIn[WRITE_END], input.data(), input.size(), &numBytesOut, NULL); + closeHandleIfValid(stdIn[WRITE_END]); // close the handle to signal data end + + // wait for the process to finish + if(process.hProcess != INVALID_HANDLE_VALUE) { + DWORD res = WaitForSingleObject(process.hProcess, INFINITE); + if(res != WAIT_OBJECT_0) { + LOG_S(ERROR) << "Error waiting for process to finish: " << res; + return ""; + } + } + + // Process return code is not used at the moment, but if it's required later, + // here's the necessary code for it: + // DWORD retCode; + // GetExitCodeProcess(process.hProcess, &retCode); + + // read all stdout/stderr that the process has produced: + std::string output = readAllFromPipe(stdOut[READ_END]); + std::string err = readAllFromPipe(stdErr[READ_END]); + + if (err.size() > 0) { + LOG_S(ERROR) << "Stderr from child process:\n" << err; + } + + // close all the handles + for(int i=0; i