blob: a11f1cbbbf8d8c7eda69e555c0cc3af9da195236 [file] [log] [blame]
// 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);
}