| // |
| // Copyright 2014 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| |
| // test_utils_win.cpp: Implementation of OS-specific functions for Windows |
| |
| #include "util/test_utils.h" |
| |
| #include <aclapi.h> |
| #include <stdarg.h> |
| #include <versionhelpers.h> |
| #include <windows.h> |
| #include <array> |
| #include <iostream> |
| #include <vector> |
| |
| #include "anglebase/no_destructor.h" |
| #include "common/angleutils.h" |
| |
| namespace angle |
| { |
| namespace |
| { |
| struct ScopedPipe |
| { |
| ~ScopedPipe() |
| { |
| closeReadHandle(); |
| closeWriteHandle(); |
| } |
| bool closeReadHandle() |
| { |
| if (readHandle) |
| { |
| if (::CloseHandle(readHandle) == FALSE) |
| { |
| std::cerr << "Error closing write handle: " << GetLastError(); |
| return false; |
| } |
| readHandle = nullptr; |
| } |
| |
| return true; |
| } |
| bool closeWriteHandle() |
| { |
| if (writeHandle) |
| { |
| if (::CloseHandle(writeHandle) == FALSE) |
| { |
| std::cerr << "Error closing write handle: " << GetLastError(); |
| return false; |
| } |
| writeHandle = nullptr; |
| } |
| |
| return true; |
| } |
| |
| bool valid() const { return readHandle != nullptr || writeHandle != nullptr; } |
| |
| bool initPipe(SECURITY_ATTRIBUTES *securityAttribs) |
| { |
| if (::CreatePipe(&readHandle, &writeHandle, securityAttribs, 0) == FALSE) |
| { |
| std::cerr << "Error creating pipe: " << GetLastError() << "\n"; |
| return false; |
| } |
| |
| #if !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| // Ensure the read handles to the pipes are not inherited. |
| if (::SetHandleInformation(readHandle, HANDLE_FLAG_INHERIT, 0) == FALSE) |
| { |
| std::cerr << "Error setting handle info on pipe: " << GetLastError() << "\n"; |
| return false; |
| } |
| #endif // !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| |
| return true; |
| } |
| |
| HANDLE readHandle = nullptr; |
| HANDLE writeHandle = nullptr; |
| }; |
| |
| // Returns false on EOF or error. |
| void ReadFromFile(bool blocking, HANDLE handle, std::string *out) |
| { |
| char buffer[8192]; |
| DWORD bytesRead = 0; |
| |
| while (true) |
| { |
| if (!blocking) |
| { |
| BOOL success = ::PeekNamedPipe(handle, nullptr, 0, nullptr, &bytesRead, nullptr); |
| if (success == FALSE || bytesRead == 0) |
| return; |
| } |
| |
| BOOL success = ::ReadFile(handle, buffer, sizeof(buffer), &bytesRead, nullptr); |
| if (success == FALSE || bytesRead == 0) |
| return; |
| |
| out->append(buffer, bytesRead); |
| } |
| |
| // unreachable. |
| } |
| |
| // Returns the Win32 last error code or ERROR_SUCCESS if the last error code is |
| // ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND. This is useful in cases where |
| // the absence of a file or path is a success condition (e.g., when attempting |
| // to delete an item in the filesystem). |
| bool ReturnSuccessOnNotFound() |
| { |
| const DWORD error_code = ::GetLastError(); |
| return (error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_PATH_NOT_FOUND); |
| } |
| |
| // Job objects seems to have problems on the Chromium CI and Windows 7. |
| bool ShouldUseJobObjects() |
| { |
| #if defined(ANGLE_ENABLE_WINDOWS_UWP) |
| return false; |
| #else |
| return (::IsWindows10OrGreater()); |
| #endif |
| } |
| |
| class WindowsProcess : public Process |
| { |
| public: |
| WindowsProcess(const std::vector<const char *> &commandLineArgs, |
| bool captureStdOut, |
| bool captureStdErr) |
| { |
| mProcessInfo.hProcess = INVALID_HANDLE_VALUE; |
| mProcessInfo.hThread = INVALID_HANDLE_VALUE; |
| |
| std::vector<char> commandLineString; |
| for (const char *arg : commandLineArgs) |
| { |
| if (arg) |
| { |
| if (!commandLineString.empty()) |
| { |
| commandLineString.push_back(' '); |
| } |
| commandLineString.insert(commandLineString.end(), arg, arg + strlen(arg)); |
| } |
| } |
| commandLineString.push_back('\0'); |
| |
| // Set the bInheritHandle flag so pipe handles are inherited. |
| SECURITY_ATTRIBUTES securityAttribs; |
| securityAttribs.nLength = sizeof(SECURITY_ATTRIBUTES); |
| securityAttribs.bInheritHandle = TRUE; |
| securityAttribs.lpSecurityDescriptor = nullptr; |
| |
| STARTUPINFOA startInfo = {}; |
| |
| // Create pipes for stdout and stderr. |
| startInfo.cb = sizeof(STARTUPINFOA); |
| startInfo.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); |
| if (captureStdOut) |
| { |
| if (!mStdoutPipe.initPipe(&securityAttribs)) |
| { |
| return; |
| } |
| startInfo.hStdOutput = mStdoutPipe.writeHandle; |
| } |
| else |
| { |
| startInfo.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); |
| } |
| |
| if (captureStdErr) |
| { |
| if (!mStderrPipe.initPipe(&securityAttribs)) |
| { |
| return; |
| } |
| startInfo.hStdError = mStderrPipe.writeHandle; |
| } |
| else |
| { |
| startInfo.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); |
| } |
| |
| #if !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| if (captureStdOut || captureStdErr) |
| { |
| startInfo.dwFlags |= STARTF_USESTDHANDLES; |
| } |
| |
| if (ShouldUseJobObjects()) |
| { |
| // Create job object. Job objects allow us to automatically force child processes to |
| // exit if the parent process is unexpectedly killed. This should prevent ghost |
| // processes from hanging around. |
| mJobHandle = ::CreateJobObjectA(nullptr, nullptr); |
| if (mJobHandle == NULL) |
| { |
| std::cerr << "Error creating job object: " << GetLastError() << "\n"; |
| return; |
| } |
| |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = {}; |
| limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
| if (::SetInformationJobObject(mJobHandle, JobObjectExtendedLimitInformation, &limitInfo, |
| sizeof(limitInfo)) == FALSE) |
| { |
| std::cerr << "Error setting job information: " << GetLastError() << "\n"; |
| return; |
| } |
| } |
| #endif // !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| |
| // Create the child process. |
| if (::CreateProcessA(nullptr, commandLineString.data(), nullptr, nullptr, |
| TRUE, // Handles are inherited. |
| 0, nullptr, nullptr, &startInfo, &mProcessInfo) == FALSE) |
| { |
| std::cerr << "CreateProcessA Error code: " << GetLastError() << "\n"; |
| return; |
| } |
| |
| #if !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| if (mJobHandle != nullptr) |
| { |
| if (::AssignProcessToJobObject(mJobHandle, mProcessInfo.hProcess) == FALSE) |
| { |
| std::cerr << "AssignProcessToJobObject failed: " << GetLastError() << "\n"; |
| return; |
| } |
| } |
| #endif // !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| |
| // Close the write end of the pipes, so EOF can be generated when child exits. |
| if (!mStdoutPipe.closeWriteHandle() || !mStderrPipe.closeWriteHandle()) |
| return; |
| |
| mStarted = true; |
| mTimer.start(); |
| } |
| |
| ~WindowsProcess() override |
| { |
| if (mProcessInfo.hProcess != INVALID_HANDLE_VALUE) |
| { |
| ::CloseHandle(mProcessInfo.hProcess); |
| } |
| if (mProcessInfo.hThread != INVALID_HANDLE_VALUE) |
| { |
| ::CloseHandle(mProcessInfo.hThread); |
| } |
| if (mJobHandle != nullptr) |
| { |
| ::CloseHandle(mJobHandle); |
| } |
| } |
| |
| bool started() override { return mStarted; } |
| |
| bool finish() override |
| { |
| if (mStdoutPipe.valid()) |
| { |
| ReadFromFile(true, mStdoutPipe.readHandle, &mStdout); |
| } |
| |
| if (mStderrPipe.valid()) |
| { |
| ReadFromFile(true, mStderrPipe.readHandle, &mStderr); |
| } |
| |
| DWORD result = ::WaitForSingleObject(mProcessInfo.hProcess, INFINITE); |
| mTimer.stop(); |
| return result == WAIT_OBJECT_0; |
| } |
| |
| bool finished() override |
| { |
| if (!mStarted) |
| return false; |
| |
| // Pipe stdin and stdout. |
| if (mStdoutPipe.valid()) |
| { |
| ReadFromFile(false, mStdoutPipe.readHandle, &mStdout); |
| } |
| |
| if (mStderrPipe.valid()) |
| { |
| ReadFromFile(false, mStderrPipe.readHandle, &mStderr); |
| } |
| |
| DWORD result = ::WaitForSingleObject(mProcessInfo.hProcess, 0); |
| if (result == WAIT_OBJECT_0) |
| { |
| mTimer.stop(); |
| return true; |
| } |
| if (result == WAIT_TIMEOUT) |
| return false; |
| |
| mTimer.stop(); |
| std::cerr << "Unexpected result from WaitForSingleObject: " << result |
| << ". Last error: " << ::GetLastError() << "\n"; |
| return false; |
| } |
| |
| int getExitCode() override |
| { |
| if (!mStarted) |
| return -1; |
| |
| if (mProcessInfo.hProcess == INVALID_HANDLE_VALUE) |
| return -1; |
| |
| DWORD exitCode = 0; |
| if (::GetExitCodeProcess(mProcessInfo.hProcess, &exitCode) == FALSE) |
| return -1; |
| |
| return static_cast<int>(exitCode); |
| } |
| |
| bool kill() override |
| { |
| if (!mStarted) |
| return true; |
| |
| HANDLE newHandle; |
| if (::DuplicateHandle(::GetCurrentProcess(), mProcessInfo.hProcess, ::GetCurrentProcess(), |
| &newHandle, PROCESS_ALL_ACCESS, false, |
| DUPLICATE_CLOSE_SOURCE) == FALSE) |
| { |
| std::cerr << "Error getting permission to terminate process: " << ::GetLastError() |
| << "\n"; |
| return false; |
| } |
| mProcessInfo.hProcess = newHandle; |
| |
| #if !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| if (::TerminateThread(mProcessInfo.hThread, 1) == FALSE) |
| { |
| std::cerr << "TerminateThread failed: " << GetLastError() << "\n"; |
| return false; |
| } |
| #endif // !defined(ANGLE_ENABLE_WINDOWS_UWP) |
| |
| if (::TerminateProcess(mProcessInfo.hProcess, 1) == FALSE) |
| { |
| std::cerr << "TerminateProcess failed: " << GetLastError() << "\n"; |
| return false; |
| } |
| |
| mStarted = false; |
| mTimer.stop(); |
| return true; |
| } |
| |
| private: |
| bool mStarted = false; |
| ScopedPipe mStdoutPipe; |
| ScopedPipe mStderrPipe; |
| PROCESS_INFORMATION mProcessInfo = {}; |
| HANDLE mJobHandle = nullptr; |
| }; |
| } // namespace |
| |
| void Sleep(unsigned int milliseconds) |
| { |
| ::Sleep(static_cast<DWORD>(milliseconds)); |
| } |
| |
| void WriteDebugMessage(const char *format, ...) |
| { |
| va_list args; |
| va_start(args, format); |
| int size = vsnprintf(nullptr, 0, format, args); |
| va_end(args); |
| |
| std::vector<char> buffer(size + 2); |
| va_start(args, format); |
| vsnprintf(buffer.data(), size + 1, format, args); |
| va_end(args); |
| |
| OutputDebugStringA(buffer.data()); |
| } |
| |
| Process *LaunchProcess(const std::vector<const char *> &args, |
| bool captureStdout, |
| bool captureStderr) |
| { |
| return new WindowsProcess(args, captureStdout, captureStderr); |
| } |
| |
| bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen) |
| { |
| DWORD pathLen = ::GetTempPathA(maxDirNameLen, tempDirOut); |
| return (pathLen < MAX_PATH && pathLen > 0); |
| } |
| |
| bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen) |
| { |
| char fileName[MAX_PATH + 1]; |
| if (::GetTempFileNameA(dir, "ANGLE", 0, fileName) == 0) |
| return false; |
| |
| strncpy(tempFileNameOut, fileName, maxFileNameLen); |
| return true; |
| } |
| |
| bool DeleteFile(const char *path) |
| { |
| if (strlen(path) >= MAX_PATH) |
| return false; |
| |
| const DWORD attr = ::GetFileAttributesA(path); |
| // Report success if the file or path does not exist. |
| if (attr == INVALID_FILE_ATTRIBUTES) |
| { |
| return ReturnSuccessOnNotFound(); |
| } |
| |
| // Clear the read-only bit if it is set. |
| if ((attr & FILE_ATTRIBUTE_READONLY) && |
| !::SetFileAttributesA(path, attr & ~FILE_ATTRIBUTE_READONLY)) |
| { |
| // It's possible for |path| to be gone now under a race with other deleters. |
| return ReturnSuccessOnNotFound(); |
| } |
| |
| // We don't handle directories right now. |
| if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) |
| { |
| return false; |
| } |
| |
| return !!::DeleteFileA(path) ? true : ReturnSuccessOnNotFound(); |
| } |
| } // namespace angle |