// 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 #include #include #include #include #include #include #include // 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" 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_WindowsProcesses_getpid( 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, 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(error_.c_str()), error_.size()); error_ = L""; return result; } private: HANDLE handle_; std::wstring error_; std::atomic 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, 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; // "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, 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; // "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, 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; // "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(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(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(process_long); return process->WriteStdin(env, java_bytes, offset, length); } 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(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(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(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(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(process_long); return static_cast(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(process_long); int res = process->WaitFor(static_cast(java_timeout)); process->CloseStdin(); return static_cast(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(process_long); return static_cast(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(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(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(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(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(stream_long); return stream->GetLastErrorAsString(env); }