| // 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 <VersionHelpers.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/util.h" |
| |
| template <typename T> |
| static std::wstring ToString(const T& e) { |
| std::wstringstream s; |
| s << e; |
| return s.str(); |
| } |
| |
| namespace bazel { |
| namespace windows { |
| |
| class WaitableProcess { |
| public: |
| // These are the possible return values from the NativeProcess::WaitFor() |
| // method. |
| enum { |
| kWaitSuccess = 0, |
| kWaitTimeout = 1, |
| kWaitError = 2, |
| }; |
| |
| static bool Create(const std::wstring& wpath, const std::wstring& argv_rest, |
| void* env, const std::wstring& wcwd, HANDLE stdin_process, |
| HANDLE stdout_process, HANDLE stderr_process, |
| AutoHandle* out_job, AutoHandle* out_ioport, |
| AutoHandle* out_process, DWORD* out_pid, |
| std::wstring* error) { |
| std::wstring cwd; |
| std::wstring error_msg(bazel::windows::AsShortPath(wcwd, &cwd)); |
| if (!error_msg.empty()) { |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, error_msg); |
| return false; |
| } |
| |
| std::wstring argv0; |
| error_msg = bazel::windows::AsExecutablePathForCreateProcess(wpath, &argv0); |
| if (!error_msg.empty()) { |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, error_msg); |
| return false; |
| } |
| |
| std::wstring commandline = |
| argv_rest.empty() ? argv0 : (argv0 + L" " + argv_rest); |
| std::unique_ptr<WCHAR[]> mutable_commandline( |
| new WCHAR[commandline.size() + 1]); |
| wcsncpy(mutable_commandline.get(), commandline.c_str(), |
| commandline.size() + 1); |
| // 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. |
| *out_job = CreateJobObject(NULL, NULL); |
| if (!out_job->IsValid()) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); |
| return false; |
| } |
| |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0}; |
| job_info.BasicLimitInformation.LimitFlags = |
| JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
| if (!SetInformationJobObject(*out_job, JobObjectExtendedLimitInformation, |
| &job_info, sizeof(job_info))) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); |
| return false; |
| } |
| |
| *out_ioport = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1); |
| if (!out_ioport->IsValid()) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); |
| return false; |
| } |
| JOBOBJECT_ASSOCIATE_COMPLETION_PORT port; |
| port.CompletionKey = *out_job; |
| port.CompletionPort = *out_ioport; |
| if (!SetInformationJobObject(*out_job, |
| JobObjectAssociateCompletionPortInformation, |
| &port, sizeof(port))) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); |
| return false; |
| } |
| |
| std::unique_ptr<bazel::windows::AutoAttributeList> attr_list; |
| if (!bazel::windows::AutoAttributeList::Create( |
| stdin_process, stdout_process, stderr_process, &attr_list, |
| &error_msg)) { |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", L"", error_msg); |
| return false; |
| } |
| |
| // kMaxCmdline value: see lpCommandLine parameter of CreateProcessW. |
| static constexpr size_t kMaxCmdline = 32767; |
| |
| std::wstring cmd_sample = mutable_commandline.get(); |
| if (cmd_sample.size() > 200) { |
| cmd_sample = cmd_sample.substr(0, 195) + L"(...)"; |
| } |
| if (wcsnlen_s(mutable_commandline.get(), kMaxCmdline) == kMaxCmdline) { |
| std::wstringstream error_msg; |
| error_msg << L"command is longer than CreateProcessW's limit (" |
| << kMaxCmdline << L" characters)"; |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"CreateProcessWithExplicitHandles", |
| cmd_sample, error_msg.str().c_str()); |
| return false; |
| } |
| |
| PROCESS_INFORMATION process_info = {0}; |
| STARTUPINFOEXW info; |
| attr_list->InitStartupInfoExW(&info); |
| if (!CreateProcessW( |
| /* lpApplicationName */ NULL, |
| /* lpCommandLine */ mutable_commandline.get(), |
| /* 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 | CREATE_UNICODE_ENVIRONMENT, |
| /* lpEnvironment */ env, |
| /* lpCurrentDirectory */ cwd.empty() ? NULL : cwd.c_str(), |
| /* lpStartupInfo */ &info.StartupInfo, |
| /* lpProcessInformation */ &process_info)) { |
| DWORD err = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"CreateProcessW", cmd_sample, err); |
| return false; |
| } |
| |
| *out_pid = process_info.dwProcessId; |
| *out_process = process_info.hProcess; |
| bazel::windows::AutoHandle thread(process_info.hThread); |
| |
| if (!AssignProcessToJobObject(*out_job, *out_process)) { |
| BOOL is_in_job = false; |
| if (IsProcessInJob(*out_process, NULL, &is_in_job) && is_in_job && |
| !IsWindows8OrGreater()) { |
| // Pre-Windows 8 systems don't support nested jobs, and 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. |
| *out_job = INVALID_HANDLE_VALUE; |
| *out_ioport = INVALID_HANDLE_VALUE; |
| } else { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); |
| return false; |
| } |
| } |
| |
| // Now that we put the process in a new job object, we can start executing |
| // it |
| if (ResumeThread(thread) == -1) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"nativeCreateProcess", wpath, err_code); |
| return false; |
| } |
| |
| *error = L""; |
| return true; |
| } |
| |
| static int WaitFor(int64_t timeout_msec, DWORD pid, AutoHandle* in_out_job, |
| AutoHandle* in_out_ioport, AutoHandle* in_out_process, |
| DWORD* out_exit_code, std::wstring* error) { |
| DWORD win32_timeout = timeout_msec < 0 ? INFINITE : timeout_msec; |
| int result; |
| switch (WaitForSingleObject(*in_out_process, win32_timeout)) { |
| case WAIT_OBJECT_0: |
| result = kWaitSuccess; |
| break; |
| |
| case WAIT_TIMEOUT: |
| result = kWaitTimeout; |
| break; |
| |
| // Any other case is an error and should be reported back to Bazel. |
| default: |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, |
| L"NativeProcess:WaitFor", |
| ToString(pid), err_code); |
| return kWaitError; |
| } |
| |
| // Ensure that the process is really terminated (if WaitForSingleObject |
| // above timed out, we have to explicitly kill it) and that it doesn't |
| // leave behind any subprocesses. |
| if (!Terminate(*in_out_job, *in_out_process, pid, out_exit_code, error)) { |
| return kWaitError; |
| } |
| |
| if (in_out_job->IsValid()) { |
| // Wait for the job object to complete, signalling that all subprocesses |
| // have exited. |
| DWORD CompletionCode; |
| ULONG_PTR CompletionKey; |
| LPOVERLAPPED Overlapped; |
| while (GetQueuedCompletionStatus(*in_out_ioport, &CompletionCode, |
| &CompletionKey, &Overlapped, INFINITE) && |
| !((HANDLE)CompletionKey == (HANDLE)*in_out_job && |
| CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) { |
| // Still waiting... |
| } |
| |
| *in_out_job = INVALID_HANDLE_VALUE; |
| *in_out_ioport = INVALID_HANDLE_VALUE; |
| } |
| |
| // Fetch and store the exit code in case Bazel asks us for it later, |
| // because we cannot do this anymore after we closed the handle. |
| GetExitCode(*in_out_process, pid, out_exit_code, error); |
| |
| if (in_out_process->IsValid()) { |
| *in_out_process = INVALID_HANDLE_VALUE; |
| } |
| |
| return result; |
| } |
| |
| static int GetExitCode(const AutoHandle& process, DWORD pid, |
| DWORD* out_exit_code, std::wstring* error) { |
| if (*out_exit_code == STILL_ACTIVE) { |
| if (!GetExitCodeProcess(process, out_exit_code)) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, |
| L"NativeProcess::GetExitCode", |
| ToString(pid), err_code); |
| return -1; |
| } |
| } |
| |
| return *out_exit_code; |
| } |
| |
| static bool Terminate(const AutoHandle& job, const AutoHandle& process, |
| DWORD pid, DWORD* out_exit_code, std::wstring* error) { |
| static constexpr UINT exit_code = 130; // 128 + SIGINT, like on Linux |
| |
| if (job.IsValid()) { |
| if (!TerminateJobObject(job, exit_code)) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, |
| L"NativeProcess::Terminate", |
| ToString(pid), err_code); |
| return false; |
| } |
| } else if (process.IsValid()) { |
| if (!TerminateProcess(process, exit_code)) { |
| DWORD err_code = GetLastError(); |
| std::wstring our_error = bazel::windows::MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"NativeProcess::Terminate", |
| ToString(pid), err_code); |
| |
| // If the process exited, despite TerminateProcess having failed, we're |
| // still happy and just ignore the error. It might have been a race |
| // where the process exited by itself just before we tried to kill it. |
| // However, if the process is *still* running at this point (evidenced |
| // by its exit code still being STILL_ACTIVE) then something went |
| // really unexpectedly wrong and we should report that error. |
| if (GetExitCode(process, pid, out_exit_code, error) == STILL_ACTIVE) { |
| // Restore the error message from TerminateProcess - it will be much |
| // more helpful for debugging in case something goes wrong here. |
| *error = our_error; |
| return false; |
| } |
| } |
| |
| if (WaitForSingleObject(process, INFINITE) != WAIT_OBJECT_0) { |
| DWORD err_code = GetLastError(); |
| *error = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, |
| L"NativeProcess::Terminate", |
| ToString(pid), err_code); |
| return false; |
| } |
| } |
| |
| *error = L""; |
| return true; |
| } |
| }; |
| |
| } // namespace windows |
| } // namespace bazel |
| |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeGetpid( |
| JNIEnv* env, jclass clazz) { |
| return GetCurrentProcessId(); |
| } |
| |
| 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_; |
| }; |
| |
| 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_, NULL); |
| CloseHandle(handle_); |
| handle_ = INVALID_HANDLE_VALUE; |
| } |
| |
| void SetHandle(HANDLE handle) { handle_ = handle; } |
| |
| 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, 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 || 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_(), exit_code_(STILL_ACTIVE), 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; |
| } |
| } |
| |
| { |
| HANDLE pipe_read_h, pipe_write_h; |
| if (!CreatePipe(&pipe_read_h, &pipe_write_h, &sa, 0)) { |
| 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; |
| } |
| |
| if (!stdout_is_stream) { |
| stdout_.Close(); |
| |
| stdout_process = CreateFileW( |
| /* lpFileName */ stdout_redirect.c_str(), |
| /* dwDesiredAccess */ GENERIC_WRITE, |
| /* dwShareMode */ 0, |
| /* lpSecurityAttributes */ &sa, |
| /* dwCreationDisposition */ OPEN_ALWAYS, |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ NULL); |
| |
| 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}, NULL, 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, 0)) { |
| 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; |
| } |
| |
| 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, |
| /* dwShareMode */ 0, |
| /* lpSecurityAttributes */ &sa, |
| /* dwCreationDisposition */ OPEN_ALWAYS, |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ NULL); |
| |
| 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}, NULL, 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, 0)) { |
| 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; |
| } |
| return bazel::windows::WaitableProcess::Create( |
| wpath, bazel::windows::GetJavaWstring(env, java_argv_rest), |
| env_map.ptr(), bazel::windows::GetJavaWpath(env, java_cwd), |
| stdin_process, stdout_process, stderr_process, &job_, &ioport_, |
| &process_, &pid_, &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 bazel::windows::WaitableProcess::WaitFor( |
| timeout_msec, pid_, &job_, &ioport_, &process_, &exit_code_, &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 bazel::windows::WaitableProcess::GetExitCode(process_, pid_, |
| &exit_code_, &error_); |
| } |
| |
| DWORD GetPid() { return pid_; } |
| |
| 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(pid_), |
| L"Array index out of bounds"); |
| return -1; |
| } |
| |
| DWORD bytes_written; |
| |
| if (!::WriteFile(stdin_, bytes.ptr() + offset, length, &bytes_written, |
| NULL)) { |
| DWORD err_code = GetLastError(); |
| error_ = bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, |
| L"NativeProcess:WriteStdin", |
| ToString(pid_), 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 bazel::windows::WaitableProcess::Terminate(job_, process_, pid_, |
| &exit_code_, &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_; |
| bazel::windows::AutoHandle process_; |
| bazel::windows::AutoHandle job_; |
| bazel::windows::AutoHandle ioport_; |
| DWORD pid_; |
| DWORD exit_code_; |
| std::wstring error_; |
| }; |
| |
| // 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_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(); |
| // 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_jni_WindowsProcesses_nativeWriteStdin( |
| 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 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->GetStdoutStream()); |
| } |
| |
| 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->GetStderrStream()); |
| } |
| |
| 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); |
| return stream->ReadStream(env, java_bytes, offset, length); |
| } |
| |
| 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); |
| 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_jni_WindowsProcesses_nativeWaitFor( |
| 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)); |
| process->CloseStdin(); |
| return static_cast<jint>(res); |
| } |
| |
| 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); |
| return static_cast<jint>(process->GetPid()); |
| } |
| |
| extern "C" JNIEXPORT jboolean JNICALL |
| Java_com_google_devtools_build_lib_windows_jni_WindowsProcesses_nativeTerminate( |
| 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_jni_WindowsProcesses_nativeDeleteProcess( |
| 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_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); |
| return process->GetLastErrorAsString(env); |
| } |
| |
| 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); |
| return stream->GetLastErrorAsString(env); |
| } |