| // 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. | 
 |  | 
 | #ifndef WIN32_LEAN_AND_MEAN | 
 | #define WIN32_LEAN_AND_MEAN | 
 | #endif | 
 |  | 
 | #include <windows.h> | 
 |  | 
 | #include <stdint.h> | 
 | #include <wchar.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/file.h" | 
 | #include "src/main/native/windows/jni-util.h" | 
 | #include "src/main/native/windows/process.h" | 
 | #include "src/main/native/windows/util.h" | 
 |  | 
 | // Pipe buffer size, to match the Linux/MacOS default. | 
 | #define PIPE_SIZE 65536 | 
 |  | 
 | template <typename T> | 
 | static std::wstring ToString(const T& e) { | 
 |   std::wstringstream s; | 
 |   s << e; | 
 |   return s.str(); | 
 | } | 
 |  | 
 | 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, nullptr) | 
 |                  : 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_; | 
 | }; | 
 |  | 
 | class NativeOutputStream { | 
 |  public: | 
 |   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_, nullptr); | 
 |     CloseHandle(handle_); | 
 |     handle_ = INVALID_HANDLE_VALUE; | 
 |   } | 
 |  | 
 |   void SetHandle(HANDLE handle) { handle_ = handle; } | 
 |  | 
 |   jint StreamBytesAvailable(JNIEnv* env) { | 
 |     if (closed_.load() || handle_ == INVALID_HANDLE_VALUE) { | 
 |       error_ = L""; | 
 |       return 0; | 
 |     } | 
 |  | 
 |     DWORD avail = 0; | 
 |     if (!::PeekNamedPipe(handle_, nullptr, 0, nullptr, &avail, nullptr)) { | 
 |       // 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 || closed_.load()) { | 
 |         error_ = L""; | 
 |         return 0; | 
 |       } else { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                   L"nativeStreamBytesAvailable", | 
 |                                                   L"", err_code); | 
 |         return -1; | 
 |       } | 
 |     } else { | 
 |       error_ = L""; | 
 |     } | 
 |     return avail; | 
 |   } | 
 |  | 
 |   jint ReadStream(JNIEnv* env, jbyteArray java_bytes, jint offset, | 
 |                   jint length) { | 
 |     JavaByteArray bytes(env, java_bytes); | 
 |     if (offset < 0 || length <= 0 || offset > bytes.size() - length) { | 
 |       error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                 L"nativeReadStream", L"", | 
 |                                                 L"Array index out of bounds"); | 
 |       return -1; | 
 |     } | 
 |  | 
 |     if (handle_ == INVALID_HANDLE_VALUE || closed_.load()) { | 
 |       error_ = L""; | 
 |       return 0; | 
 |     } | 
 |  | 
 |     DWORD bytes_read; | 
 |     if (!::ReadFile(handle_, bytes.ptr() + offset, length, &bytes_read, | 
 |                     nullptr)) { | 
 |       // 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 || closed_.load()) { | 
 |         // End of file. | 
 |         error_ = L""; | 
 |         bytes_read = 0; | 
 |       } else { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeReadStream", L"", err_code); | 
 |         bytes_read = -1; | 
 |       } | 
 |     } else { | 
 |       error_ = L""; | 
 |     } | 
 |  | 
 |     return bytes_read; | 
 |   } | 
 |  | 
 |   // Return the last error as a human-readable string and clear it. | 
 |   jstring GetLastErrorAsString(JNIEnv* env) { | 
 |     jstring result = env->NewString( | 
 |         reinterpret_cast<const jchar*>(error_.c_str()), error_.size()); | 
 |     error_ = L""; | 
 |     return result; | 
 |   } | 
 |  | 
 |  private: | 
 |   HANDLE handle_; | 
 |   std::wstring error_; | 
 |   std::atomic<bool> closed_; | 
 | }; | 
 |  | 
 | class NativeProcess { | 
 |  public: | 
 |   NativeProcess() : stdout_(), stderr_(), error_(L"") {} | 
 |  | 
 |   ~NativeProcess() { | 
 |     stdout_.Close(); | 
 |     stderr_.Close(); | 
 |   } | 
 |  | 
 |   jboolean Create(JNIEnv* env, jstring java_argv0, jstring java_argv_rest, | 
 |                   jbyteArray java_env, jstring java_cwd, | 
 |                   jstring java_stdout_redirect, jstring java_stderr_redirect, | 
 |                   jboolean redirectErrorStream) { | 
 |     std::wstring wpath(bazel::windows::GetJavaWpath(env, java_argv0)); | 
 |  | 
 |     std::wstring stdout_redirect = bazel::windows::AddUncPrefixMaybe( | 
 |         bazel::windows::GetJavaWpath(env, java_stdout_redirect)); | 
 |     std::wstring stderr_redirect = bazel::windows::AddUncPrefixMaybe( | 
 |         bazel::windows::GetJavaWpath(env, java_stderr_redirect)); | 
 |  | 
 |     const bool stdout_is_stream = stdout_redirect.empty(); | 
 |     const bool stderr_is_stream = | 
 |         redirectErrorStream ? stdout_is_stream : stderr_redirect.empty(); | 
 |     const bool stderr_same_handle_as_stdout = | 
 |         redirectErrorStream || | 
 |         (!stderr_redirect.empty() && | 
 |          stderr_redirect.size() == stdout_redirect.size() && | 
 |          _wcsnicmp(stderr_redirect.c_str(), stdout_redirect.c_str(), | 
 |                    stderr_redirect.size()) == 0); | 
 |  | 
 |     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; | 
 |  | 
 |     JavaByteArray env_map(env, java_env); | 
 |     if (env_map.ptr() != nullptr) { | 
 |       if (env_map.size() < 4) { | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, | 
 |             std::wstring( | 
 |                 L"the environment must be at least 4 bytes long, was ") + | 
 |                 ToString(env_map.size()) + L" bytes"); | 
 |         return false; | 
 |       } else if (env_map.ptr()[env_map.size() - 1] != 0 || | 
 |                  env_map.ptr()[env_map.size() - 2] != 0 || | 
 |                  env_map.ptr()[env_map.size() - 3] != 0 || | 
 |                  env_map.ptr()[env_map.size() - 4] != 0) { | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, | 
 |             L"environment array must end with 4 null bytes"); | 
 |         return false; | 
 |       } | 
 |     } | 
 |  | 
 |     // Set up childs stdin pipe. | 
 |     { | 
 |       HANDLE pipe_read_h, pipe_write_h; | 
 |       if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, PIPE_SIZE)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |       stdin_process = pipe_read_h; | 
 |       stdin_ = pipe_write_h; | 
 |  | 
 |       // "Our" end of the pipe must not be inherited by the child process | 
 |       if (!SetHandleInformation(pipe_write_h, HANDLE_FLAG_INHERIT, 0)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |     } | 
 |  | 
 |     if (!stdout_is_stream) { | 
 |       stdout_.Close(); | 
 |  | 
 |       stdout_process = CreateFileW( | 
 |           /* lpFileName */ stdout_redirect.c_str(), | 
 |           /* dwDesiredAccess */ GENERIC_WRITE, | 
 |           // Must share for reading, otherwise symlink-following file existence | 
 |           // checks (e.g. java.nio.file.Files.exists()) fail. | 
 |           /* dwShareMode */ FILE_SHARE_READ, | 
 |           /* lpSecurityAttributes */ &sa, | 
 |           /* dwCreationDisposition */ OPEN_ALWAYS, | 
 |           /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, | 
 |           /* hTemplateFile */ nullptr); | 
 |  | 
 |       if (!stdout_process.IsValid()) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                   L"nativeCreateProcess", | 
 |                                                   stdout_redirect, err_code); | 
 |         return false; | 
 |       } | 
 |       if (!SetFilePointerEx(stdout_process, {0}, nullptr, FILE_END)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                   L"nativeCreateProcess", | 
 |                                                   stdout_redirect, err_code); | 
 |         return false; | 
 |       } | 
 |     } else { | 
 |       HANDLE pipe_read_h, pipe_write_h; | 
 |       if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, PIPE_SIZE)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |       stdout_.SetHandle(pipe_read_h); | 
 |       stdout_process = pipe_write_h; | 
 |       // "Our" end of the pipe must not be inherited by the child process | 
 |       if (!SetHandleInformation(pipe_read_h, HANDLE_FLAG_INHERIT, 0)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |     } | 
 |  | 
 |     if (stderr_same_handle_as_stdout) { | 
 |       HANDLE stdout_process_dup_h; | 
 |       if (!DuplicateHandle(GetCurrentProcess(), stdout_process, | 
 |                            GetCurrentProcess(), &stdout_process_dup_h, 0, TRUE, | 
 |                            DUPLICATE_SAME_ACCESS)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |       if (!stderr_is_stream) { | 
 |         stderr_.Close(); | 
 |       } | 
 |  | 
 |       stderr_process = stdout_process_dup_h; | 
 |     } else if (!stderr_redirect.empty()) { | 
 |       stderr_.Close(); | 
 |       stderr_process = CreateFileW( | 
 |           /* lpFileName */ stderr_redirect.c_str(), | 
 |           /* dwDesiredAccess */ GENERIC_WRITE, | 
 |           // Must share for reading, otherwise symlink-following file existence | 
 |           // checks (e.g. java.nio.file.Files.exists()) fail. | 
 |           /* dwShareMode */ FILE_SHARE_READ, | 
 |           /* lpSecurityAttributes */ &sa, | 
 |           /* dwCreationDisposition */ OPEN_ALWAYS, | 
 |           /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, | 
 |           /* hTemplateFile */ nullptr); | 
 |  | 
 |       if (!stderr_process.IsValid()) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                   L"nativeCreateProcess", | 
 |                                                   stderr_redirect, err_code); | 
 |         return false; | 
 |       } | 
 |       if (!SetFilePointerEx(stderr_process, {0}, nullptr, FILE_END)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                   L"nativeCreateProcess", | 
 |                                                   stderr_redirect, err_code); | 
 |         return false; | 
 |       } | 
 |     } else { | 
 |       HANDLE pipe_read_h, pipe_write_h; | 
 |       if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, PIPE_SIZE)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |       stderr_.SetHandle(pipe_read_h); | 
 |       stderr_process = pipe_write_h; | 
 |       // "Our" end of the pipe must not be inherited by the child process | 
 |       if (!SetHandleInformation(pipe_read_h, HANDLE_FLAG_INHERIT, 0)) { | 
 |         DWORD err_code = GetLastError(); | 
 |         error_ = bazel::windows::MakeErrorMessage( | 
 |             WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); | 
 |         return false; | 
 |       } | 
 |     } | 
 |     return proc_.Create( | 
 |         wpath, bazel::windows::GetJavaWstring(env, java_argv_rest), | 
 |         env_map.ptr(), bazel::windows::GetJavaWpath(env, java_cwd), | 
 |         stdin_process, stdout_process, stderr_process, nullptr, &error_); | 
 |   } | 
 |  | 
 |   void CloseStdin() { | 
 |     if (stdin_.IsValid()) { | 
 |       stdin_ = INVALID_HANDLE_VALUE; | 
 |     } | 
 |   } | 
 |  | 
 |   // Wait for this process to exit (or timeout). | 
 |   int WaitFor(int64_t timeout_msec) { | 
 |     return proc_.WaitFor(timeout_msec, nullptr, &error_); | 
 |   } | 
 |  | 
 |   // Returns the exit code of the process if it has already exited. If the | 
 |   // process is still running, returns STILL_ACTIVE (= 259). | 
 |   int GetExitCode() { return proc_.GetExitCode(&error_); } | 
 |  | 
 |   DWORD GetPid() const { return proc_.GetPid(); } | 
 |  | 
 |   jint WriteStdin(JNIEnv* env, jbyteArray java_bytes, jint offset, | 
 |                   jint length) { | 
 |     JavaByteArray bytes(env, java_bytes); | 
 |     if (offset < 0 || length <= 0 || offset > bytes.size() - length) { | 
 |       error_ = bazel::windows::MakeErrorMessage( | 
 |           WSTR(__FILE__), __LINE__, L"NativeProcess:WriteStdin", | 
 |           ToString(GetPid()), L"Array index out of bounds"); | 
 |       return -1; | 
 |     } | 
 |  | 
 |     DWORD bytes_written; | 
 |  | 
 |     if (!::WriteFile(stdin_, bytes.ptr() + offset, length, &bytes_written, | 
 |                      nullptr)) { | 
 |       DWORD err_code = GetLastError(); | 
 |       error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                 L"NativeProcess:WriteStdin", | 
 |                                                 ToString(GetPid()), err_code); | 
 |       return -1; | 
 |     } | 
 |  | 
 |     error_ = L""; | 
 |     return bytes_written; | 
 |   } | 
 |  | 
 |   NativeOutputStream* GetStdoutStream() { return &stdout_; } | 
 |  | 
 |   NativeOutputStream* GetStderrStream() { return &stderr_; } | 
 |  | 
 |   // Terminates this process (and subprocesses, if job objects are available). | 
 |   bool Terminate() { return proc_.Terminate(&error_); } | 
 |  | 
 |   // Return the last error as a human-readable string and clear it. | 
 |   jstring GetLastErrorAsString(JNIEnv* env) { | 
 |     jstring result = env->NewString( | 
 |         reinterpret_cast<const jchar*>(error_.c_str()), error_.size()); | 
 |     error_ = L""; | 
 |     return result; | 
 |   } | 
 |  | 
 |  private: | 
 |   bazel::windows::AutoHandle stdin_; | 
 |   NativeOutputStream stdout_; | 
 |   NativeOutputStream stderr_; | 
 |   std::wstring error_; | 
 |   bazel::windows::WaitableProcess proc_; | 
 | }; | 
 |  | 
 | // 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); } | 
 |  | 
 | extern "C" JNIEXPORT jlong JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_createProcess( | 
 |     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(); | 
 |   // TODO(philwo) The `Create` method returns false in case of an error. But | 
 |   // there seems to be no good way to signal an error at this point to Bazel. | 
 |   // The way the code currently works is that the Java code explicitly calls | 
 |   // nativeProcessGetLastError(), so it's OK, but it would be nice if we | 
 |   // could just throw an exception here. | 
 |   result->Create(env, java_argv0, java_argv_rest, java_env, java_cwd, | 
 |                  java_stdout_redirect, java_stderr_redirect, | 
 |                  redirectErrorStream); | 
 |   return PtrAsJlong(result); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jint JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_writeStdin( | 
 |     JNIEnv* env, jclass clazz, jlong process_long, jbyteArray java_bytes, | 
 |     jint offset, jint length) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return process->WriteStdin(env, java_bytes, offset, length); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_closeStdin( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   process->CloseStdin(); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jlong JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_getStdout( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return PtrAsJlong(process->GetStdoutStream()); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jlong JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_getStderr( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return PtrAsJlong(process->GetStderrStream()); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jint JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_readStream( | 
 |     JNIEnv* env, jclass clazz, jlong stream_long, jbyteArray java_bytes, | 
 |     jint offset, jint length) { | 
 |   NativeOutputStream* stream = | 
 |       reinterpret_cast<NativeOutputStream*>(stream_long); | 
 |   return stream->ReadStream(env, java_bytes, offset, length); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jint JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_streamBytesAvailable( | 
 |     JNIEnv* env, jclass clazz, jlong stream_long) { | 
 |   NativeOutputStream* stream = | 
 |       reinterpret_cast<NativeOutputStream*>(stream_long); | 
 |   return stream->StreamBytesAvailable(env); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jint JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_getExitCode( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return static_cast<jint>(process->GetExitCode()); | 
 | } | 
 |  | 
 | // 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_WindowsProcesses_waitFor( | 
 |     JNIEnv* env, jclass clazz, jlong process_long, jlong java_timeout) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   int res = process->WaitFor(static_cast<int64_t>(java_timeout)); | 
 |   return static_cast<jint>(res); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jint JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_getProcessPid( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return static_cast<jint>(process->GetPid()); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jboolean JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_terminate( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return process->Terminate() ? JNI_TRUE : JNI_FALSE; | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_deleteProcess( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   delete reinterpret_cast<NativeProcess*>(process_long); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_closeStream( | 
 |     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_WindowsProcesses_processGetLastError( | 
 |     JNIEnv* env, jclass clazz, jlong process_long) { | 
 |   NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long); | 
 |   return process->GetLastErrorAsString(env); | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jstring JNICALL | 
 | Java_com_google_devtools_build_lib_windows_WindowsProcesses_streamGetLastError( | 
 |     JNIEnv* env, jclass clazz, jlong stream_long) { | 
 |   NativeOutputStream* stream = | 
 |       reinterpret_cast<NativeOutputStream*>(stream_long); | 
 |   return stream->GetLastErrorAsString(env); | 
 | } |