// 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 #include #include #include #include #include #include // 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 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 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(&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(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( 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 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(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(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(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(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(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(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(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(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(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(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(process_long); jstring result = env->NewString(reinterpret_cast(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(stream_long); jstring result = env->NewString(reinterpret_cast(stream->error_.c_str()), stream->error_.size()); stream->error_ = L""; return result; }