|  | // Copyright 2016 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #define WIN32_LEAN_AND_MEAN | 
|  |  | 
|  | #include <wchar.h> | 
|  | #include <windows.h> | 
|  |  | 
|  | #include <atomic> | 
|  | #include <memory> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <type_traits>  // static_assert | 
|  |  | 
|  | #include "src/main/native/jni.h" | 
|  | #include "src/main/native/windows/jni-util.h" | 
|  | #include "src/main/native/windows/util.h" | 
|  |  | 
|  | // Maximum command line length is 2^15 characters including the null terminator. | 
|  | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx | 
|  | static const size_t MAX_CMDLINE = 1 << 15; | 
|  |  | 
|  | template <typename T> | 
|  | static std::wstring ToString(const T& e) { | 
|  | std::wstringstream s; | 
|  | s << e; | 
|  | return s.str(); | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jint JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeGetpid( | 
|  | JNIEnv* env, jclass clazz) { | 
|  | return GetCurrentProcessId(); | 
|  | } | 
|  |  | 
|  | struct NativeOutputStream { | 
|  | HANDLE handle_; | 
|  | std::wstring error_; | 
|  | std::atomic<bool> closed_; | 
|  | NativeOutputStream() | 
|  | : handle_(INVALID_HANDLE_VALUE), error_(L""), closed_(false) {} | 
|  |  | 
|  | void close() { | 
|  | closed_.store(true); | 
|  | if (handle_ == INVALID_HANDLE_VALUE) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // CancelIoEx only cancels I/O operations in the current process. | 
|  | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363792(v=vs.85).aspx | 
|  | // | 
|  | // Therefore if this process bequested `handle_` to a child process, we | 
|  | // cannot cancel I/O in the child process. | 
|  | CancelIoEx(handle_, NULL); | 
|  | CloseHandle(handle_); | 
|  | handle_ = INVALID_HANDLE_VALUE; | 
|  | } | 
|  | }; | 
|  |  | 
|  | static std::wstring AddUncPrefixMaybe(const std::wstring& path) { | 
|  | return (path.size() >= MAX_PATH) ? (std::wstring(L"\\\\?\\") + path) : path; | 
|  | } | 
|  |  | 
|  | struct NativeProcess { | 
|  | HANDLE stdin_; | 
|  | NativeOutputStream stdout_; | 
|  | NativeOutputStream stderr_; | 
|  | HANDLE process_; | 
|  | HANDLE job_; | 
|  | DWORD pid_; | 
|  | std::wstring error_; | 
|  |  | 
|  | NativeProcess() | 
|  | : stdin_(INVALID_HANDLE_VALUE), | 
|  | stdout_(), | 
|  | stderr_(), | 
|  | process_(INVALID_HANDLE_VALUE), | 
|  | job_(INVALID_HANDLE_VALUE), | 
|  | error_(L"") {} | 
|  | }; | 
|  |  | 
|  | class JavaByteArray { | 
|  | public: | 
|  | JavaByteArray(JNIEnv* env, jbyteArray java_array) | 
|  | : env_(env), | 
|  | array_(java_array), | 
|  | size_(java_array != nullptr ? env->GetArrayLength(java_array) : 0), | 
|  | ptr_(java_array != nullptr ? env->GetByteArrayElements(java_array, NULL) | 
|  | : nullptr) {} | 
|  |  | 
|  | ~JavaByteArray() { | 
|  | if (array_ != nullptr) { | 
|  | env_->ReleaseByteArrayElements(array_, ptr_, 0); | 
|  | array_ = nullptr; | 
|  | size_ = 0; | 
|  | ptr_ = nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | jsize size() { return size_; } | 
|  | jbyte* ptr() { return ptr_; } | 
|  |  | 
|  | private: | 
|  | JNIEnv* env_; | 
|  | jbyteArray array_; | 
|  | jsize size_; | 
|  | jbyte* ptr_; | 
|  | }; | 
|  |  | 
|  | static bool NestedJobsSupported() { | 
|  | OSVERSIONINFOEX version_info; | 
|  | version_info.dwOSVersionInfoSize = sizeof(version_info); | 
|  | if (!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return version_info.dwMajorVersion > 6 || | 
|  | (version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 2); | 
|  | } | 
|  |  | 
|  | // Ensure we can safely cast jlong to void*. | 
|  | static_assert(sizeof(jlong) == sizeof(void*), | 
|  | "jlong and void* should be the same size"); | 
|  |  | 
|  | static_assert(sizeof(jchar) == sizeof(WCHAR), | 
|  | "jchar and WCHAR should be the same size"); | 
|  |  | 
|  | static jlong PtrAsJlong(void* p) { return reinterpret_cast<jlong>(p); } | 
|  |  | 
|  | // The following CreateProcessWithExplicitHandles function is based on an | 
|  | // implementation of the same function in the following OldNewThing blog post: | 
|  | // https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873 | 
|  | // We need this function to prevent the child process from inheriting unintended | 
|  | // handles. See http://support.microsoft.com/kb/315939 | 
|  | static std::wstring CreateProcessWithExplicitHandles( | 
|  | /* __inout_opt */ LPWSTR lpCommandLine, | 
|  | /* __in_opt    */ LPVOID lpEnvironment, | 
|  | /* __in_opt    */ LPCWSTR lpCurrentDirectory, | 
|  | /* __in        */ LPSTARTUPINFOW lpStartupInfo, | 
|  | /* __out       */ LPPROCESS_INFORMATION lpProcessInformation, | 
|  | /* __in        */ DWORD cHandlesToInherit, | 
|  | /* __in_ecount(cHandlesToInherit) */ HANDLE* handlesToInherit) { | 
|  | if (wcsnlen_s(lpCommandLine, MAX_CMDLINE) == MAX_CMDLINE) { | 
|  | std::wstring cmd_sample(lpCommandLine, 200); | 
|  | cmd_sample.append(L"(...)"); | 
|  | std::wstringstream error_msg; | 
|  | error_msg << L"command is longer than CreateProcessW's limit (" | 
|  | << MAX_CMDLINE << L" characters)"; | 
|  | return bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"CreateProcessWithExplicitHandles", | 
|  | cmd_sample.c_str(), error_msg.str().c_str()); | 
|  | } | 
|  |  | 
|  | if (cHandlesToInherit >= 0xFFFFFFFF / sizeof(HANDLE)) { | 
|  | return bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"CreateProcessWithExplicitHandles", | 
|  | lpCommandLine, L"too many handles to inherit"); | 
|  | } | 
|  |  | 
|  | if (lpStartupInfo->cb != sizeof(*lpStartupInfo)) { | 
|  | return bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"CreateProcessWithExplicitHandles", | 
|  | lpCommandLine, L"bad lpStartupInfo"); | 
|  | } | 
|  |  | 
|  | SIZE_T size = 0; | 
|  | if (!InitializeProcThreadAttributeList(NULL, 1, 0, &size) && | 
|  | GetLastError() != ERROR_INSUFFICIENT_BUFFER) { | 
|  | DWORD err_code = GetLastError(); | 
|  | return bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"CreateProcessWithExplicitHandles", | 
|  | lpCommandLine, err_code); | 
|  | } | 
|  |  | 
|  | LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = | 
|  | reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>( | 
|  | HeapAlloc(GetProcessHeap(), 0, size)); | 
|  | if (lpAttributeList == NULL) { | 
|  | DWORD err_code = GetLastError(); | 
|  | return bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"CreateProcessWithExplicitHandles", | 
|  | lpCommandLine, err_code); | 
|  | } | 
|  |  | 
|  | if (!InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | return bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"CreateProcessWithExplicitHandles", | 
|  | lpCommandLine, err_code); | 
|  | } | 
|  | if (!UpdateProcThreadAttribute( | 
|  | lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, | 
|  | handlesToInherit, cHandlesToInherit * sizeof(HANDLE), NULL, NULL)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | return bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"CreateProcessWithExplicitHandles", | 
|  | lpCommandLine, err_code); | 
|  | } | 
|  |  | 
|  | STARTUPINFOEXW info; | 
|  | ZeroMemory(&info, sizeof(info)); | 
|  | info.StartupInfo = *lpStartupInfo; | 
|  | info.StartupInfo.cb = sizeof(info); | 
|  | info.lpAttributeList = lpAttributeList; | 
|  | DWORD createproc_err = 0; | 
|  | if (!CreateProcessW( | 
|  | /* lpApplicationName */ NULL, | 
|  | /* lpCommandLine */ lpCommandLine, | 
|  | /* lpProcessAttributes */ NULL, | 
|  | /* lpThreadAttributes */ NULL, | 
|  | /* bInheritHandles */ TRUE, | 
|  | /* dwCreationFlags */ CREATE_NO_WINDOW  // Don't create console window | 
|  | | CREATE_NEW_PROCESS_GROUP  // So that Ctrl-Break isn't propagated | 
|  | | CREATE_SUSPENDED  // So that it doesn't start a new job itself | 
|  | | EXTENDED_STARTUPINFO_PRESENT, | 
|  | /* lpEnvironment */ lpEnvironment, | 
|  | /* lpCurrentDirectory */ lpCurrentDirectory, | 
|  | /* lpStartupInfo */ &info.StartupInfo, | 
|  | /* lpProcessInformation */ lpProcessInformation)) { | 
|  | createproc_err = GetLastError(); | 
|  | } | 
|  |  | 
|  | DeleteProcThreadAttributeList(lpAttributeList); | 
|  | if (lpAttributeList) { | 
|  | HeapFree(GetProcessHeap(), 0, lpAttributeList); | 
|  | } | 
|  | if (createproc_err) { | 
|  | return bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"CreateProcessW", lpCommandLine, | 
|  | createproc_err); | 
|  | } | 
|  | return L""; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jlong JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeCreateProcess( | 
|  | JNIEnv* env, jclass clazz, jstring java_argv0, jstring java_argv_rest, | 
|  | jbyteArray java_env, jstring java_cwd, jstring java_stdout_redirect, | 
|  | jstring java_stderr_redirect, jboolean redirectErrorStream) { | 
|  | NativeProcess* result = new NativeProcess(); | 
|  |  | 
|  | std::wstring argv0; | 
|  | std::wstring wpath(bazel::windows::GetJavaWstring(env, java_argv0)); | 
|  | std::wstring error_msg( | 
|  | bazel::windows::AsExecutablePathForCreateProcess(wpath, &argv0)); | 
|  | if (!error_msg.empty()) { | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, error_msg); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | std::wstring commandline = | 
|  | argv0 + L" " + bazel::windows::GetJavaWstring(env, java_argv_rest); | 
|  | std::wstring stdout_redirect = AddUncPrefixMaybe( | 
|  | bazel::windows::GetJavaWstring(env, java_stdout_redirect)); | 
|  | std::wstring stderr_redirect = AddUncPrefixMaybe( | 
|  | bazel::windows::GetJavaWstring(env, java_stderr_redirect)); | 
|  | std::wstring cwd; | 
|  | std::wstring wcwd(bazel::windows::GetJavaWstring(env, java_cwd)); | 
|  | error_msg = bazel::windows::AsShortPath(wcwd, &cwd); | 
|  | if (!error_msg.empty()) { | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, error_msg); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<WCHAR[]> mutable_commandline( | 
|  | new WCHAR[commandline.size() + 1]); | 
|  | wcsncpy(mutable_commandline.get(), commandline.c_str(), | 
|  | commandline.size() + 1); | 
|  |  | 
|  | SECURITY_ATTRIBUTES sa = {0}; | 
|  | sa.nLength = sizeof(SECURITY_ATTRIBUTES); | 
|  | sa.bInheritHandle = TRUE; | 
|  |  | 
|  | // Standard file handles are closed even if the process was successfully | 
|  | // created. If this was not so, operations on these file handles would not | 
|  | // return immediately if the process is terminated. | 
|  | // Therefore we make these handles auto-closing (by using AutoHandle). | 
|  | bazel::windows::AutoHandle stdin_process; | 
|  | bazel::windows::AutoHandle stdout_process; | 
|  | bazel::windows::AutoHandle stderr_process; | 
|  | bazel::windows::AutoHandle thread; | 
|  | PROCESS_INFORMATION process_info = {0}; | 
|  | STARTUPINFOW startup_info = {0}; | 
|  | JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0}; | 
|  |  | 
|  | JavaByteArray env_map(env, java_env); | 
|  | if (env_map.ptr() != nullptr) { | 
|  | if (env_map.size() < 2) { | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, | 
|  | L"the environment must be at least two bytes long"); | 
|  | return PtrAsJlong(result); | 
|  | } else if (env_map.ptr()[env_map.size() - 1] != 0 || | 
|  | env_map.ptr()[env_map.size() - 2] != 0) { | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, | 
|  | L"environment array must end with two null bytes"); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | } | 
|  |  | 
|  | HANDLE temp_stdin_handle = INVALID_HANDLE_VALUE; | 
|  | if (!CreatePipe(&temp_stdin_handle, &result->stdin_, &sa, 0)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | CloseHandle(temp_stdin_handle); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | stdin_process = temp_stdin_handle; | 
|  |  | 
|  | if (!stdout_redirect.empty()) { | 
|  | result->stdout_.close(); | 
|  |  | 
|  | stdout_process = CreateFileW( | 
|  | /* lpFileName */ stdout_redirect.c_str(), | 
|  | /* dwDesiredAccess */ FILE_APPEND_DATA, | 
|  | /* dwShareMode */ 0, | 
|  | /* lpSecurityAttributes */ &sa, | 
|  | /* dwCreationDisposition */ OPEN_ALWAYS, | 
|  | /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, | 
|  | /* hTemplateFile */ NULL); | 
|  |  | 
|  | if (!stdout_process.IsValid()) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | } else { | 
|  | HANDLE temp_stdout_handle = INVALID_HANDLE_VALUE; | 
|  | if (!CreatePipe(&result->stdout_.handle_, &temp_stdout_handle, &sa, 0)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | CloseHandle(temp_stdout_handle); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | stdout_process = temp_stdout_handle; | 
|  | } | 
|  |  | 
|  | // The value of the stderr HANDLE. | 
|  | // If stdout and stderr are redirected to the same file, this will be equal to | 
|  | // stdout_process.handle and stderr_process.handle will be | 
|  | // INVALID_HANDLE_VALUE, so the two AutoHandle objects' d'tors will not | 
|  | // attempt to close stdout's handle twice. | 
|  | // If stdout != stderr, then stderr_handle = stderr_process.handle, and these | 
|  | // are distinct from stdout_process.handle, so again the d'tors will do the | 
|  | // right thing, closing the handles. | 
|  | // In both cases, we DO NOT close stderr_handle, since it's either | 
|  | // stdout_process's or stderr_process's d'tor doing so. | 
|  | HANDLE stderr_handle = INVALID_HANDLE_VALUE; | 
|  |  | 
|  | if (redirectErrorStream) { | 
|  | stderr_handle = stdout_process; | 
|  | } else if (!stderr_redirect.empty()) { | 
|  | result->stderr_.close(); | 
|  | if (stdout_redirect == stderr_redirect) { | 
|  | stderr_handle = stdout_process; | 
|  | // do not set stderr_process.handle; it equals stdout_process.handle and | 
|  | // the AutoHandle d'tor would attempt to close it again | 
|  | } else { | 
|  | stderr_handle = CreateFileW( | 
|  | /* lpFileName */ stderr_redirect.c_str(), | 
|  | /* dwDesiredAccess */ FILE_APPEND_DATA, | 
|  | /* dwShareMode */ 0, | 
|  | /* lpSecurityAttributes */ &sa, | 
|  | /* dwCreationDisposition */ OPEN_ALWAYS, | 
|  | /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, | 
|  | /* hTemplateFile */ NULL); | 
|  |  | 
|  | if (stderr_handle == INVALID_HANDLE_VALUE) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | // stderr_process != stdout_process, so set its handle, so the AutoHandle | 
|  | // d'tor will close it | 
|  | stderr_process = stderr_handle; | 
|  | } | 
|  | } else { | 
|  | if (!CreatePipe(&result->stderr_.handle_, &stderr_handle, &sa, 0)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | stderr_process = stderr_handle; | 
|  | } | 
|  |  | 
|  | // MDSN says that the default for job objects is that breakaway is not | 
|  | // allowed. Thus, we don't need to do any more setup here. | 
|  | HANDLE job = CreateJobObject(NULL, NULL); | 
|  | if (job == NULL) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | result->job_ = job; | 
|  |  | 
|  | job_info.BasicLimitInformation.LimitFlags = | 
|  | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; | 
|  | if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, | 
|  | &job_info, sizeof(job_info))) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | startup_info.cb = sizeof(STARTUPINFOW); | 
|  | startup_info.hStdInput = stdin_process; | 
|  | startup_info.hStdOutput = stdout_process; | 
|  | startup_info.hStdError = stderr_handle; | 
|  | startup_info.dwFlags |= STARTF_USESTDHANDLES; | 
|  |  | 
|  | HANDLE handlesToInherit[3] = {stdin_process, stdout_process, stderr_handle}; | 
|  | std::wstring err_msg(CreateProcessWithExplicitHandles( | 
|  | /* lpCommandLine */ mutable_commandline.get(), | 
|  | /* lpEnvironment */ env_map.ptr(), | 
|  | /* lpCurrentDirectory */ cwd.empty() ? nullptr : cwd.c_str(), | 
|  | /* lpStartupInfo */ &startup_info, | 
|  | /* lpProcessInformation */ &process_info, | 
|  | /* cHandlesToInherit */ (stderr_handle == stdout_process) ? 2 : 3, | 
|  | /* handlesToInherit */ handlesToInherit)); | 
|  |  | 
|  | if (!err_msg.empty()) { | 
|  | result->error_ = err_msg; | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | result->pid_ = process_info.dwProcessId; | 
|  | result->process_ = process_info.hProcess; | 
|  | thread = process_info.hThread; | 
|  |  | 
|  | if (!AssignProcessToJobObject(result->job_, result->process_)) { | 
|  | BOOL is_in_job = false; | 
|  | if (IsProcessInJob(result->process_, NULL, &is_in_job) && is_in_job && | 
|  | !NestedJobsSupported()) { | 
|  | // We are on a pre-Windows 8 system and the Bazel is already in a job. | 
|  | // We can't create nested jobs, so just revert to TerminateProcess() and | 
|  | // hope for the best. In batch mode, the launcher puts Bazel in a job so | 
|  | // that will take care of cleanup once the command finishes. | 
|  | CloseHandle(result->job_); | 
|  | result->job_ = INVALID_HANDLE_VALUE; | 
|  | } else { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now that we put the process in a new job object, we can start executing it | 
|  | if (ResumeThread(thread) == -1) { | 
|  | DWORD err_code = GetLastError(); | 
|  | result->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | result->error_ = L""; | 
|  | return PtrAsJlong(result); | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jint JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeWriteStdin( | 
|  | JNIEnv* env, jclass clazz, jlong process_long, jbyteArray java_bytes, | 
|  | jint offset, jint length) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  |  | 
|  | JavaByteArray bytes(env, java_bytes); | 
|  | if (offset < 0 || length <= 0 || offset > bytes.size() - length) { | 
|  | process->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeWriteStdin", ToString(process->pid_), | 
|  | L"Array index out of bounds"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | DWORD bytes_written; | 
|  |  | 
|  | if (!::WriteFile(process->stdin_, bytes.ptr() + offset, length, | 
|  | &bytes_written, NULL)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | process->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeWriteStdin", ToString(process->pid_), | 
|  | err_code); | 
|  | bytes_written = -1; | 
|  | } | 
|  |  | 
|  | process->error_ = L""; | 
|  | return bytes_written; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jlong JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeGetStdout( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  | return PtrAsJlong(&process->stdout_); | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jlong JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeGetStderr( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  | return PtrAsJlong(&process->stderr_); | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jint JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeReadStream( | 
|  | JNIEnv* env, jclass clazz, jlong stream_long, jbyteArray java_bytes, | 
|  | jint offset, jint length) { | 
|  | NativeOutputStream* stream = | 
|  | reinterpret_cast<NativeOutputStream*>(stream_long); | 
|  |  | 
|  | JavaByteArray bytes(env, java_bytes); | 
|  | if (offset < 0 || length <= 0 || offset > bytes.size() - length) { | 
|  | stream->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeReadStream", L"", | 
|  | L"Array index out of bounds"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (stream->handle_ == INVALID_HANDLE_VALUE || stream->closed_.load()) { | 
|  | stream->error_ = L""; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | DWORD bytes_read; | 
|  | if (!::ReadFile(stream->handle_, bytes.ptr() + offset, length, &bytes_read, | 
|  | NULL)) { | 
|  | // Check if either the other end closed the pipe or we did it with | 
|  | // NativeOutputStream.close() . In the latter case, we'll get a "system | 
|  | // call interrupted" error. | 
|  | if (GetLastError() == ERROR_BROKEN_PIPE || stream->closed_.load()) { | 
|  | // End of file. | 
|  | stream->error_ = L""; | 
|  | bytes_read = 0; | 
|  | } else { | 
|  | DWORD err_code = GetLastError(); | 
|  | stream->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeReadStream", L"", err_code); | 
|  | bytes_read = -1; | 
|  | } | 
|  | } else { | 
|  | stream->error_ = L""; | 
|  | } | 
|  |  | 
|  | return bytes_read; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jint JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeGetExitCode( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  | DWORD exit_code; | 
|  | if (!GetExitCodeProcess(process->process_, &exit_code)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | process->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeGetExitCode", ToString(process->pid_), | 
|  | err_code); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return exit_code; | 
|  | } | 
|  |  | 
|  | // return values: | 
|  | // 0: Wait completed successfully | 
|  | // 1: Timeout | 
|  | // 2: Wait returned with an error | 
|  | extern "C" JNIEXPORT jint JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeWaitFor( | 
|  | JNIEnv* env, jclass clazz, jlong process_long, jlong java_timeout) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  | HANDLE handles[1] = {process->process_}; | 
|  | DWORD win32_timeout = java_timeout < 0 ? INFINITE : java_timeout; | 
|  | jint result; | 
|  | switch (WaitForMultipleObjects(1, handles, FALSE, win32_timeout)) { | 
|  | case 0: | 
|  | result = 0; | 
|  | break; | 
|  |  | 
|  | case WAIT_TIMEOUT: | 
|  | result = 1; | 
|  | break; | 
|  |  | 
|  | case WAIT_FAILED: | 
|  | result = 2; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | DWORD err_code = GetLastError(); | 
|  | process->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeWaitFor", ToString(process->pid_), | 
|  | err_code); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (process->stdin_ != INVALID_HANDLE_VALUE) { | 
|  | CloseHandle(process->stdin_); | 
|  | process->stdin_ = INVALID_HANDLE_VALUE; | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jint JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeGetProcessPid( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  | process->error_ = L""; | 
|  | return GetProcessId(process->process_);  // MSDN says that this cannot fail | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jboolean JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeTerminate( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | static const UINT exit_code = 130;  // 128 + SIGINT, like on Linux | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  |  | 
|  | if (process->job_ != INVALID_HANDLE_VALUE) { | 
|  | if (!TerminateJobObject(process->job_, exit_code)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | process->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeTerminate", ToString(process->pid_), | 
|  | err_code); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | } else if (process->process_ != INVALID_HANDLE_VALUE) { | 
|  | if (!TerminateProcess(process->process_, exit_code)) { | 
|  | DWORD err_code = GetLastError(); | 
|  | process->error_ = bazel::windows::MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"nativeTerminate", ToString(process->pid_), | 
|  | err_code); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | process->error_ = L""; | 
|  | return JNI_TRUE; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT void JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeDeleteProcess( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  |  | 
|  | if (process->stdin_ != INVALID_HANDLE_VALUE) { | 
|  | CloseHandle(process->stdin_); | 
|  | } | 
|  |  | 
|  | process->stdout_.close(); | 
|  | process->stderr_.close(); | 
|  |  | 
|  | if (process->process_ != INVALID_HANDLE_VALUE) { | 
|  | CloseHandle(process->process_); | 
|  | } | 
|  |  | 
|  | if (process->job_ != INVALID_HANDLE_VALUE) { | 
|  | CloseHandle(process->job_); | 
|  | } | 
|  |  | 
|  | delete process; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT void JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeCloseStream( | 
|  | JNIEnv* env, jclass clazz, jlong stream_long) { | 
|  | NativeOutputStream* stream = | 
|  | reinterpret_cast<NativeOutputStream*>(stream_long); | 
|  | stream->close(); | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jstring JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeProcessGetLastError( | 
|  | JNIEnv* env, jclass clazz, jlong process_long) { | 
|  | NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
|  | jstring result = | 
|  | env->NewString(reinterpret_cast<const jchar*>(process->error_.c_str()), | 
|  | process->error_.size()); | 
|  | process->error_ = L""; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | extern "C" JNIEXPORT jstring JNICALL | 
|  | Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeStreamGetLastError( | 
|  | JNIEnv* env, jclass clazz, jlong stream_long) { | 
|  | NativeOutputStream* stream = | 
|  | reinterpret_cast<NativeOutputStream*>(stream_long); | 
|  | jstring result = | 
|  | env->NewString(reinterpret_cast<const jchar*>(stream->error_.c_str()), | 
|  | stream->error_.size()); | 
|  | stream->error_ = L""; | 
|  | return result; | 
|  | } |