aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/native/windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/native/windows')
-rw-r--r--src/main/native/windows/BUILD43
-rw-r--r--src/main/native/windows/file.cc186
-rw-r--r--src/main/native/windows/file.h86
-rw-r--r--src/main/native/windows/processes.cc643
-rw-r--r--src/main/native/windows/util.cc168
-rw-r--r--src/main/native/windows/util.h95
6 files changed, 1221 insertions, 0 deletions
diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD
new file mode 100644
index 0000000000..c7dead2469
--- /dev/null
+++ b/src/main/native/windows/BUILD
@@ -0,0 +1,43 @@
+package(default_visibility = ["//visibility:private"])
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//src/main/native:__pkg__"],
+)
+
+# TODO(xingao): verify that this filegroup contains exactly what it has to wrt.
+# where it is used (//src/main/native:embedded_tools).
+# Context: https://github.com/bazelbuild/bazel/commit/33d05f6b551cf2fdb257cb536a5e864d095144a1
+filegroup(
+ name = "embedded_tools",
+ srcs = [":srcs"],
+ visibility = ["//src/main/native:__pkg__"],
+)
+
+filegroup(
+ name = "raw-sources",
+ srcs = glob([
+ "*.cc",
+ "*.h",
+ ]),
+ visibility = ["//src/main/native:__pkg__"],
+)
+
+cc_library(
+ name = "lib-file",
+ srcs = ["file.cc"],
+ hdrs = ["file.h"],
+ visibility = [
+ "//src/main/cpp:__subpackages__",
+ "//src/test/cpp:__subpackages__",
+ "//src/test/native:__subpackages__",
+ ],
+ deps = [":lib-util"],
+)
+
+cc_library(
+ name = "lib-util",
+ srcs = ["util.cc"],
+ hdrs = ["util.h"],
+)
diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc
new file mode 100644
index 0000000000..b5dc5974c8
--- /dev/null
+++ b/src/main/native/windows/file.cc
@@ -0,0 +1,186 @@
+// 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.
+
+#include <windows.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "src/main/native/windows/file.h"
+#include "src/main/native/windows/util.h"
+
+namespace bazel {
+namespace windows {
+
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+int IsJunctionOrDirectorySymlink(const WCHAR* path) {
+ DWORD attrs = ::GetFileAttributesW(path);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ return IS_JUNCTION_ERROR;
+ } else {
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY) &&
+ (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ return IS_JUNCTION_YES;
+ } else {
+ return IS_JUNCTION_NO;
+ }
+ }
+}
+
+bool GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result) {
+ DWORD size = ::GetLongPathNameW(path, NULL, 0);
+ if (size == 0) {
+ return false;
+ }
+ result->reset(new WCHAR[size]);
+ ::GetLongPathNameW(path, result->get(), size);
+ return true;
+}
+
+HANDLE OpenDirectory(const WCHAR* path, bool read_write) {
+ return ::CreateFileW(
+ /* lpFileName */ path,
+ /* dwDesiredAccess */
+ read_write ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ,
+ /* dwShareMode */ 0,
+ /* lpSecurityAttributes */ NULL,
+ /* dwCreationDisposition */ OPEN_EXISTING,
+ /* dwFlagsAndAttributes */ FILE_FLAG_OPEN_REPARSE_POINT |
+ FILE_FLAG_BACKUP_SEMANTICS,
+ /* hTemplateFile */ NULL);
+}
+
+#pragma pack(push, 4)
+typedef struct _JunctionDescription {
+ typedef struct _Header {
+ DWORD ReparseTag;
+ WORD ReparseDataLength;
+ WORD Reserved;
+ } Header;
+
+ typedef struct _WriteDesc {
+ WORD SubstituteNameOffset;
+ WORD SubstituteNameLength;
+ WORD PrintNameOffset;
+ WORD PrintNameLength;
+ } WriteDesc;
+
+ Header header;
+ WriteDesc write;
+ WCHAR PathBuffer[ANYSIZE_ARRAY];
+} JunctionDescription;
+#pragma pack(pop)
+
+string CreateJunction(const wstring& junction_name,
+ const wstring& junction_target) {
+ const wstring target = HasUncPrefix(junction_target.c_str())
+ ? junction_target.substr(4)
+ : junction_target;
+ // The entire JunctionDescription cannot be larger than
+ // MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes.
+ //
+ // The structure's layout is:
+ // [JunctionDescription::Header]
+ // [JunctionDescription::WriteDesc]
+ // ---- start of JunctionDescription::PathBuffer ----
+ // [4 WCHARs] : "\??\" prefix
+ // [target.size() WCHARs] : junction target name
+ // [1 WCHAR] : null-terminator
+ // [target.size() WCHARs] : junction target displayed name
+ // [1 WCHAR] : null-terminator
+ // The sum of these must not exceed MAXIMUM_REPARSE_DATA_BUFFER_SIZE.
+ // We can rearrange this to get the limit for target.size().
+ static const size_t kMaxJunctionTargetLen =
+ ((MAXIMUM_REPARSE_DATA_BUFFER_SIZE - sizeof(JunctionDescription::Header) -
+ sizeof(JunctionDescription::WriteDesc) -
+ /* one "\??\" prefix */ sizeof(WCHAR) * 4 -
+ /* two null terminators */ sizeof(WCHAR) * 2) /
+ /* two copies of the string are stored */ 2) /
+ sizeof(WCHAR);
+ if (target.size() > kMaxJunctionTargetLen) {
+ std::stringstream error;
+ error << "junction target is too long (" << target.size()
+ << " characters, limit: " << kMaxJunctionTargetLen << ")";
+ return error.str();
+ }
+ const wstring name = HasUncPrefix(junction_name.c_str())
+ ? junction_name
+ : (wstring(L"\\\\?\\") + junction_name);
+
+ // Junctions are directories, so create one
+ if (!::CreateDirectoryW(name.c_str(), NULL)) {
+ return string("CreateDirectoryW failed");
+ }
+
+ AutoHandle handle(OpenDirectory(name.c_str(), true));
+ if (!handle.IsValid()) {
+ return string("OpenDirectory failed");
+ }
+
+ char reparse_buffer_bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ JunctionDescription* reparse_buffer =
+ reinterpret_cast<JunctionDescription*>(reparse_buffer_bytes);
+ memset(reparse_buffer_bytes, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+
+ // "\??\" is meaningful to the kernel, it's a synomym for the "\DosDevices\"
+ // object path. (NOT to be confused with "\\?\" which is meaningful for the
+ // Win32 API.) We need to use this prefix to tell the kernel where the reparse
+ // point is pointing to.
+ memcpy(reparse_buffer->PathBuffer, L"\\??\\", 4 * sizeof(WCHAR));
+ memcpy(reparse_buffer->PathBuffer + 4, target.c_str(),
+ target.size() * sizeof(WCHAR));
+
+ // In addition to their target, junctions also have another string which is a
+ // user-visible name of where the junction points, as listed by "dir". This
+ // can be any string and won't affect the usability of the junction.
+ // MKLINK uses the target path without the "\??\" prefix as the display name,
+ // so let's do that here too. This is also in line with how UNIX behaves.
+ // Using a dummy or fake display name would be pure evil, it would make the
+ // output of `dir` look like:
+ // 2017-01-18 01:37 PM <JUNCTION> juncname [dummy string]
+ memcpy(reparse_buffer->PathBuffer + 4 + target.size() + 1, target.c_str(),
+ target.size() * sizeof(WCHAR));
+
+ reparse_buffer->write.SubstituteNameOffset = 0;
+ reparse_buffer->write.SubstituteNameLength =
+ (4 + target.size()) * sizeof(WCHAR);
+ reparse_buffer->write.PrintNameOffset =
+ reparse_buffer->write.SubstituteNameLength +
+ /* null-terminator */ sizeof(WCHAR);
+ reparse_buffer->write.PrintNameLength = target.size() * sizeof(WCHAR);
+
+ reparse_buffer->header.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+ reparse_buffer->header.ReparseDataLength =
+ sizeof(JunctionDescription::WriteDesc) +
+ reparse_buffer->write.SubstituteNameLength +
+ reparse_buffer->write.PrintNameLength +
+ /* 2 null-terminators */ (2 * sizeof(WCHAR));
+ reparse_buffer->header.Reserved = 0;
+
+ DWORD bytes_returned;
+ if (!::DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, reparse_buffer,
+ reparse_buffer->header.ReparseDataLength +
+ sizeof(JunctionDescription::Header),
+ NULL, 0, &bytes_returned, NULL)) {
+ return string("DeviceIoControl(FSCTL_SET_REPARSE_POINT) failed");
+ }
+ return "";
+}
+
+} // namespace windows
+} // namespace bazel
diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h
new file mode 100644
index 0000000000..b1a57101ff
--- /dev/null
+++ b/src/main/native/windows/file.h
@@ -0,0 +1,86 @@
+// Copyright 2017 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 BAZEL_SRC_MAIN_NATIVE_WINDOWS_FILE_H_
+#define BAZEL_SRC_MAIN_NATIVE_WINDOWS_FILE_H_
+
+#include <windows.h>
+
+#include <memory>
+#include <string>
+
+namespace bazel {
+namespace windows {
+
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+template <typename char_type>
+bool HasUncPrefix(const char_type* path) {
+ return path[0] == '\\' && (path[1] == '\\' || path[1] == '?') &&
+ (path[2] == '.' || path[2] == '?') && path[3] == '\\';
+}
+
+// Keep in sync with j.c.g.devtools.build.lib.windows.WindowsFileOperations
+enum {
+ IS_JUNCTION_YES = 0,
+ IS_JUNCTION_NO = 1,
+ IS_JUNCTION_ERROR = 2,
+};
+
+// Determines whether `path` is a junction (or directory symlink).
+//
+// `path` should be an absolute, normalized, Windows-style path, with "\\?\"
+// prefix if it's longer than MAX_PATH.
+//
+// To read about differences between junctions and directory symlinks,
+// see http://superuser.com/a/343079. In Bazel we only ever create junctions.
+//
+// Returns:
+// - IS_JUNCTION_YES, if `path` exists and is either a directory junction or a
+// directory symlink
+// - IS_JUNCTION_NO, if `path` exists but is neither a directory junction nor a
+// directory symlink; also when `path` is a symlink to a directory but it was
+// created using "mklink" instead of "mklink /d", as such symlinks don't
+// behave the same way as directories (e.g. they can't be listed)
+// - IS_JUNCTION_ERROR, if `path` doesn't exist or some error occurred
+int IsJunctionOrDirectorySymlink(const WCHAR* path);
+
+// Computes the long version of `path` if it has any 8dot3 style components.
+// Returns true upon success and sets `result` to point to the buffer.
+// `path` must be an absolute, normalized, Windows style path, with a "\\?\"
+// prefix if it's longer than MAX_PATH. The result will have a "\\?\" prefix if
+// and only if `path` had one as well. (It's the caller's responsibility to keep
+// or remove this prefix.)
+bool GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result);
+
+// Opens a directory using CreateFileW.
+// `path` must be a valid Windows path, with "\\?\" prefix if it's long.
+// If `read_write` is true then the directory is opened for reading and writing,
+// otherwise only for reading.
+HANDLE OpenDirectory(const WCHAR* path, bool read_write);
+
+// Creates a junction at `name`, pointing to `target`.
+// Returns the empty string upon success, or a human-readable error message upon
+// failure.
+// Neither `junction_name` nor `junction_target` needs to have a "\\?\" prefix,
+// not even if they are longer than MAX_PATH, though it's okay if they do. This
+// function will add the right prefixes as necessary.
+string CreateJunction(const wstring& junction_name,
+ const wstring& junction_target);
+
+} // namespace windows
+} // namespace bazel
+
+#endif // BAZEL_SRC_MAIN_NATIVE_WINDOWS_FILE_H_
diff --git a/src/main/native/windows/processes.cc b/src/main/native/windows/processes.cc
new file mode 100644
index 0000000000..89001733ea
--- /dev/null
+++ b/src/main/native/windows/processes.cc
@@ -0,0 +1,643 @@
+// 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 WINVER 0x0601
+#define _WIN32_WINNT 0x0601
+
+#include <ctype.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <windows.h>
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <string>
+#include <type_traits> // static_assert
+
+#include "src/main/native/windows/file.h"
+#include "src/main/native/windows/util.h"
+
+// Ensure we can safely cast (const) jchar* to (const) WCHAR* and LP(C)WSTR.
+// This is true with MSVC but not always with GCC.
+static_assert(sizeof(jchar) == sizeof(WCHAR),
+ "jchar and WCHAR should be the same size");
+
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeGetpid(
+ JNIEnv* env, jclass clazz) {
+ return GetCurrentProcessId();
+}
+
+struct NativeOutputStream {
+ HANDLE handle_;
+ std::string error_;
+ std::atomic<bool> closed_;
+ NativeOutputStream()
+ : handle_(INVALID_HANDLE_VALUE), error_(""), closed_(false) {}
+
+ void close() {
+ closed_.store(true);
+ if (handle_ == INVALID_HANDLE_VALUE) {
+ return;
+ }
+
+ CancelIoEx(handle_, NULL);
+ CloseHandle(handle_);
+ handle_ = INVALID_HANDLE_VALUE;
+ }
+};
+
+struct NativeProcess {
+ HANDLE stdin_;
+ NativeOutputStream stdout_;
+ NativeOutputStream stderr_;
+ HANDLE process_;
+ HANDLE job_;
+ DWORD pid_;
+ std::string error_;
+
+ NativeProcess()
+ : stdin_(INVALID_HANDLE_VALUE),
+ stdout_(),
+ stderr_(),
+ process_(INVALID_HANDLE_VALUE),
+ job_(INVALID_HANDLE_VALUE),
+ error_("") {}
+};
+
+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<OSVERSIONINFO*>(&version_info))) {
+ return false;
+ }
+
+ return version_info.dwMajorVersion > 6 ||
+ version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 2;
+}
+
+static void MaybeReportLastError(const std::string& reason, JNIEnv* env,
+ jobjectArray error_msg_holder) {
+ if (error_msg_holder != nullptr &&
+ env->GetArrayLength(error_msg_holder) > 0) {
+ std::string error_str = bazel::windows::GetLastErrorString(reason);
+ jstring error_msg = env->NewStringUTF(error_str.c_str());
+ env->SetObjectArrayElement(error_msg_holder, 0, error_msg);
+ }
+}
+
+static std::string GetJavaUTFString(JNIEnv* env, jstring str) {
+ std::string result;
+ if (str != nullptr) {
+ const char* jstr = env->GetStringUTFChars(str, nullptr);
+ result.assign(jstr);
+ env->ReleaseStringUTFChars(str, jstr);
+ }
+ return result;
+}
+
+static std::wstring GetJavaWstring(JNIEnv* env, jstring str) {
+ std::wstring result;
+ if (str != nullptr) {
+ const jchar* jstr = env->GetStringChars(str, nullptr);
+ // We can safely reinterpret_cast because of the static_assert checking that
+ // sizeof(jchar) = sizeof(WCHAR).
+ result.assign(reinterpret_cast<const WCHAR*>(jstr));
+ env->ReleaseStringChars(str, jstr);
+ }
+ return result;
+}
+
+static std::wstring AddUncPrefixMaybe(const std::wstring& path) {
+ return (path.size() >= MAX_PATH) ? (std::wstring(L"\\\\?\\") + path) : path;
+}
+
+static jlong PtrAsJlong(void* p) { return reinterpret_cast<jlong>(p); }
+
+static std::string AsExecutableForCreateProcess(JNIEnv* env, jstring path,
+ std::string* result) {
+ return bazel::windows::AsExecutablePathForCreateProcess(
+ GetJavaUTFString(env, path),
+ [env, path]() { return GetJavaWstring(env, path); }, result);
+}
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_com_google_devtools_build_lib_windows_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) {
+ NativeProcess* result = new NativeProcess();
+
+ std::string argv0;
+ std::string error_msg(AsExecutableForCreateProcess(env, java_argv0, &argv0));
+ if (!error_msg.empty()) {
+ result->error_ = error_msg;
+ return PtrAsJlong(result);
+ }
+
+ std::string commandline = argv0 + " " + GetJavaUTFString(env, java_argv_rest);
+ std::wstring stdout_redirect =
+ AddUncPrefixMaybe(GetJavaWstring(env, java_stdout_redirect));
+ std::wstring stderr_redirect =
+ AddUncPrefixMaybe(GetJavaWstring(env, java_stderr_redirect));
+ std::string cwd;
+ error_msg = bazel::windows::AsShortPath(
+ GetJavaUTFString(env, java_cwd),
+ [env, java_cwd]() { return GetJavaWstring(env, java_cwd); }, &cwd);
+ if (!error_msg.empty()) {
+ result->error_ = error_msg;
+ return PtrAsJlong(result);
+ }
+
+ std::unique_ptr<char[]> mutable_commandline(new char[commandline.size() + 1]);
+ strncpy(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};
+ STARTUPINFOA 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_ = "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_ = "Environment array must end with two null bytes";
+ return PtrAsJlong(result);
+ }
+ }
+
+ if (!CreatePipe(&stdin_process.handle, &result->stdin_, &sa, 0)) {
+ result->error_ = bazel::windows::GetLastErrorString("CreatePipe(stdin)");
+ return PtrAsJlong(result);
+ }
+
+ if (!stdout_redirect.empty()) {
+ result->stdout_.close();
+
+ stdout_process.handle = 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()) {
+ result->error_ = bazel::windows::GetLastErrorString("CreateFile(stdout)");
+ return PtrAsJlong(result);
+ }
+ } else {
+ if (!CreatePipe(&result->stdout_.handle_, &stdout_process.handle, &sa, 0)) {
+ result->error_ = bazel::windows::GetLastErrorString("CreatePipe(stdout)");
+ return PtrAsJlong(result);
+ }
+ }
+
+ // 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 (!stderr_redirect.empty()) {
+ result->stderr_.close();
+ if (stdout_redirect == stderr_redirect) {
+ stderr_handle = stdout_process.handle;
+ // 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) {
+ result->error_ =
+ bazel::windows::GetLastErrorString("CreateFile(stderr)");
+ return PtrAsJlong(result);
+ }
+ // stderr_process != stdout_process, so set its handle, so the AutoHandle
+ // d'tor will close it
+ stderr_process.handle = stderr_handle;
+ }
+ } else {
+ if (!CreatePipe(&result->stderr_.handle_, &stderr_handle, &sa, 0)) {
+ result->error_ = bazel::windows::GetLastErrorString("CreatePipe(stderr)");
+ return PtrAsJlong(result);
+ }
+ stderr_process.handle = 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) {
+ result->error_ = bazel::windows::GetLastErrorString("CreateJobObject()");
+ 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))) {
+ result->error_ =
+ bazel::windows::GetLastErrorString("SetInformationJobObject()");
+ return PtrAsJlong(result);
+ }
+
+ startup_info.hStdInput = stdin_process.handle;
+ startup_info.hStdOutput = stdout_process.handle;
+ startup_info.hStdError = stderr_handle;
+ startup_info.dwFlags |= STARTF_USESTDHANDLES;
+
+ BOOL ok = CreateProcessA(
+ /* lpApplicationName */ NULL,
+ /* lpCommandLine */ mutable_commandline.get(),
+ /* lpProcessAttributes */ NULL,
+ /* lpThreadAttributes */ NULL,
+ /* bInheritHandles */ TRUE,
+ /* dwCreationFlags */ CREATE_NO_WINDOW // Don't create a console window
+ | CREATE_NEW_PROCESS_GROUP // So that Ctrl-Break is not propagated
+ | CREATE_SUSPENDED, // So that it doesn't start a new job itself
+ /* lpEnvironment */ env_map.ptr(),
+ /* lpCurrentDirectory */ cwd.empty() ? nullptr : cwd.c_str(),
+ /* lpStartupInfo */ &startup_info,
+ /* lpProcessInformation */ &process_info);
+
+ if (!ok) {
+ result->error_ = bazel::windows::GetLastErrorString("CreateProcess()");
+ return PtrAsJlong(result);
+ }
+
+ result->pid_ = process_info.dwProcessId;
+ result->process_ = process_info.hProcess;
+ thread.handle = 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 {
+ result->error_ =
+ bazel::windows::GetLastErrorString("AssignProcessToJobObject()");
+ return PtrAsJlong(result);
+ }
+ }
+
+ // Now that we put the process in a new job object, we can start executing it
+ if (ResumeThread(thread) == -1) {
+ result->error_ = bazel::windows::GetLastErrorString("ResumeThread()");
+ return PtrAsJlong(result);
+ }
+
+ result->error_ = "";
+ return PtrAsJlong(result);
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeWriteStdin(
+ JNIEnv* env, jclass clazz, jlong process_long, jbyteArray java_bytes,
+ jint offset, jint length) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+
+ JavaByteArray bytes(env, java_bytes);
+ if (offset < 0 || length <= 0 || offset > bytes.size() - length) {
+ process->error_ = "Array index out of bounds";
+ return -1;
+ }
+
+ DWORD bytes_written;
+
+ if (!::WriteFile(process->stdin_, bytes.ptr() + offset, length,
+ &bytes_written, NULL)) {
+ process->error_ = bazel::windows::GetLastErrorString("WriteFile()");
+ bytes_written = -1;
+ }
+
+ process->error_ = "";
+ return bytes_written;
+}
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeGetStdout(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+ return PtrAsJlong(&process->stdout_);
+}
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeGetStderr(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+ return PtrAsJlong(&process->stderr_);
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeReadStream(
+ JNIEnv* env, jclass clazz, jlong stream_long, jbyteArray java_bytes,
+ jint offset, jint length) {
+ NativeOutputStream* stream =
+ reinterpret_cast<NativeOutputStream*>(stream_long);
+
+ JavaByteArray bytes(env, java_bytes);
+ if (offset < 0 || length <= 0 || offset > bytes.size() - length) {
+ stream->error_ = "Array index out of bounds";
+ return -1;
+ }
+
+ if (stream->handle_ == INVALID_HANDLE_VALUE || stream->closed_.load()) {
+ stream->error_ = "";
+ 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_ = "";
+ bytes_read = 0;
+ } else {
+ stream->error_ = bazel::windows::GetLastErrorString("ReadFile()");
+ bytes_read = -1;
+ }
+ } else {
+ stream->error_ = "";
+ }
+
+ return bytes_read;
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeGetExitCode(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+ DWORD exit_code;
+ if (!GetExitCodeProcess(process->process_, &exit_code)) {
+ process->error_ =
+ bazel::windows::GetLastErrorString("GetExitCodeProcess()");
+ 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_WindowsProcesses_nativeWaitFor(
+ JNIEnv* env, jclass clazz, jlong process_long, jlong java_timeout) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(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:
+ process->error_ = "WaitForMultipleObjects() returned unknown result";
+ result = 2;
+ break;
+ }
+
+ // Close the pipe handles so that any pending nativeReadStream() calls
+ // return. This will call CancelIoEx() on the file handles in order to make
+ // ReadFile() in nativeReadStream() return; otherwise, CloseHandle() would
+ // hang.
+ //
+ // This protects against a subprocess being created, it passing the write
+ // side of the stdout/stderr pipes to a subprocess, then dying. In that case,
+ // if we didn't do this, the Java side of the code would hang waiting for the
+ // streams to finish.
+ //
+ // An alternative implementation would be to rely on job control terminating
+ // the subprocesses, but we don't want to assume that it's always available.
+ process->stdout_.close();
+ process->stderr_.close();
+
+ 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_WindowsProcesses_nativeGetProcessPid(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+ process->error_ = "";
+ return GetProcessId(process->process_); // MSDN says that this cannot fail
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeTerminate(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ static const UINT exit_code = 130; // 128 + SIGINT, like on Linux
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+
+ if (process->job_ != INVALID_HANDLE_VALUE) {
+ if (!TerminateJobObject(process->job_, exit_code)) {
+ process->error_ =
+ bazel::windows::GetLastErrorString("TerminateJobObject()");
+ return JNI_FALSE;
+ }
+ } else if (process->process_ != INVALID_HANDLE_VALUE) {
+ if (!TerminateProcess(process->process_, exit_code)) {
+ process->error_ =
+ bazel::windows::GetLastErrorString("TerminateProcess()");
+ return JNI_FALSE;
+ }
+ }
+
+ process->error_ = "";
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeDeleteProcess(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(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_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_WindowsProcesses_nativeProcessGetLastError(
+ JNIEnv* env, jclass clazz, jlong process_long) {
+ NativeProcess* process = reinterpret_cast<NativeProcess*>(process_long);
+ jstring result = env->NewStringUTF(process->error_.c_str());
+ process->error_ = "";
+ return result;
+}
+
+extern "C" JNIEXPORT jstring JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsProcesses_nativeStreamGetLastError(
+ JNIEnv* env, jclass clazz, jlong stream_long) {
+ NativeOutputStream* stream =
+ reinterpret_cast<NativeOutputStream*>(stream_long);
+ jstring result = env->NewStringUTF(stream->error_.c_str());
+ stream->error_ = "";
+ return result;
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeIsJunction(
+ JNIEnv* env, jclass clazz, jstring path, jobjectArray error_msg_holder) {
+ int result = bazel::windows::IsJunctionOrDirectorySymlink(
+ GetJavaWstring(env, path).c_str());
+ if (result == bazel::windows::IS_JUNCTION_ERROR) {
+ MaybeReportLastError(
+ std::string("GetFileAttributes(") + GetJavaUTFString(env, path) + ")",
+ env, error_msg_holder);
+ }
+ return result;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeGetLongPath(
+ JNIEnv* env, jclass clazz, jstring path, jobjectArray result_holder,
+ jobjectArray error_msg_holder) {
+ std::unique_ptr<WCHAR[]> result;
+ bool success =
+ bazel::windows::GetLongPath(GetJavaWstring(env, path).c_str(), &result);
+ if (!success) {
+ MaybeReportLastError(
+ std::string("GetLongPathName(") + GetJavaUTFString(env, path) + ")",
+ env, error_msg_holder);
+ return JNI_FALSE;
+ }
+ env->SetObjectArrayElement(
+ result_holder, 0,
+ env->NewString(reinterpret_cast<const jchar*>(result.get()),
+ wcslen(result.get())));
+ return JNI_TRUE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeCreateJunction(
+ JNIEnv* env, jclass clazz, jstring name, jstring target,
+ jobjectArray error_msg_holder) {
+ std::string error = bazel::windows::CreateJunction(
+ GetJavaWstring(env, name), GetJavaWstring(env, target));
+ if (!error.empty()) {
+ MaybeReportLastError(error, env, error_msg_holder);
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
diff --git a/src/main/native/windows/util.cc b/src/main/native/windows/util.cc
new file mode 100644
index 0000000000..8f43fe7b74
--- /dev/null
+++ b/src/main/native/windows/util.cc
@@ -0,0 +1,168 @@
+// 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.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "src/main/native/windows/util.h"
+
+namespace bazel {
+namespace windows {
+
+using std::function;
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+string GetLastErrorString(const string& cause) {
+ DWORD last_error = GetLastError();
+ if (last_error == 0) {
+ return "";
+ }
+
+ LPSTR message;
+ DWORD size = FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&message, 0, NULL);
+
+ if (size == 0) {
+ char buf[256];
+ snprintf(buf, sizeof(buf),
+ "%s: Error %d (cannot format message due to error %d)",
+ cause.c_str(), last_error, GetLastError());
+ buf[sizeof(buf) - 1] = 0;
+ }
+
+ string result = string(message);
+ LocalFree(message);
+ return cause + ": " + result;
+}
+
+static void QuotePath(const string& path, string* result) {
+ *result = string("\"") + path + "\"";
+}
+
+static bool IsSeparator(char c) { return c == '/' || c == '\\'; }
+
+static bool HasSeparator(const string& s) {
+ return s.find_first_of('/') != string::npos ||
+ s.find_first_of('\\') != string::npos;
+}
+
+static bool Contains(const string& s, const char* substr) {
+ return s.find(substr) != string::npos;
+}
+
+string AsShortPath(string path, function<wstring()> path_as_wstring,
+ string* result) {
+ if (path.empty()) {
+ result->clear();
+ return "";
+ }
+ if (path[0] == '"') {
+ return string("path should not be quoted");
+ }
+ if (IsSeparator(path[0])) {
+ return string("path='") + path + "' is absolute";
+ }
+ if (Contains(path, "/./") || Contains(path, "\\.\\") ||
+ Contains(path, "/..") || Contains(path, "\\..")) {
+ return string("path='") + path + "' is not normalized";
+ }
+ if (path.size() >= MAX_PATH && !HasSeparator(path)) {
+ return string("path='") + path + "' is just a file name but too long";
+ }
+ if (HasSeparator(path) &&
+ !(isalpha(path[0]) && path[1] == ':' && IsSeparator(path[2]))) {
+ return string("path='") + path + "' is not an absolute path";
+ }
+ // At this point we know the path is either just a file name (shorter than
+ // MAX_PATH), or an absolute, normalized, Windows-style path (of any length).
+
+ std::replace(path.begin(), path.end(), '/', '\\');
+ // Fast-track: the path is already short.
+ if (path.size() < MAX_PATH) {
+ *result = path;
+ return "";
+ }
+ // At this point we know that the path is at least MAX_PATH long and that it's
+ // absolute, normalized, and Windows-style.
+
+ // Retrieve string as UTF-16 path, add "\\?\" prefix.
+ wstring wlong = wstring(L"\\\\?\\") + path_as_wstring();
+
+ // Experience shows that:
+ // - GetShortPathNameW's result has a "\\?\" prefix if and only if the input
+ // did too (though this behavior is not documented on MSDN)
+ // - CreateProcess{A,W} only accept an executable of MAX_PATH - 1 length
+ // Therefore for our purposes the acceptable shortened length is
+ // MAX_PATH + 4 (null-terminated). That is, MAX_PATH - 1 for the shortened
+ // path, plus a potential "\\?\" prefix that's only there if `wlong` also had
+ // it and which we'll omit from `result`, plus a null terminator.
+ static const size_t kMaxShortPath = MAX_PATH + 4;
+
+ WCHAR wshort[kMaxShortPath];
+ DWORD wshort_size = ::GetShortPathNameW(wlong.c_str(), NULL, 0);
+ if (wshort_size == 0) {
+ return GetLastErrorString(string("GetShortPathName failed (path=") + path +
+ ")");
+ }
+
+ if (wshort_size >= kMaxShortPath) {
+ return string("GetShortPathName would not shorten the path enough (path=") +
+ path + ")";
+ }
+ GetShortPathNameW(wlong.c_str(), wshort, kMaxShortPath);
+
+ // Convert the result to UTF-8.
+ char mbs_short[MAX_PATH];
+ size_t mbs_size = wcstombs(
+ mbs_short,
+ wshort + 4, // we know it has a "\\?\" prefix, because `wlong` also did
+ MAX_PATH);
+ if (mbs_size < 0 || mbs_size >= MAX_PATH) {
+ return string("wcstombs failed (path=") + path + ")";
+ }
+ mbs_short[mbs_size] = 0;
+
+ *result = mbs_short;
+ return "";
+}
+
+string AsExecutablePathForCreateProcess(const string& path,
+ function<wstring()> path_as_wstring,
+ string* result) {
+ if (path.empty()) {
+ return string("path should not be empty");
+ }
+ string error = AsShortPath(path, path_as_wstring, result);
+ if (error.empty()) {
+ // Quote the path in case it's something like "c:\foo\app name.exe".
+ // Do this unconditionally, there's no harm in quoting. Quotes are not
+ // allowed inside paths so we don't need to escape quotes.
+ QuotePath(*result, result);
+ }
+ return error;
+}
+
+} // namespace windows
+} // namespace bazel
diff --git a/src/main/native/windows/util.h b/src/main/native/windows/util.h
new file mode 100644
index 0000000000..1e3c2ceaf1
--- /dev/null
+++ b/src/main/native/windows/util.h
@@ -0,0 +1,95 @@
+// 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 BAZEL_SRC_MAIN_NATIVE_WINDOWS_UTIL_H__
+#define BAZEL_SRC_MAIN_NATIVE_WINDOWS_UTIL_H__
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace bazel {
+namespace windows {
+
+using std::function;
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+// A wrapper for the `HANDLE` type that calls CloseHandle in its d'tor.
+// WARNING: do not use for HANDLE returned by FindFirstFile; those must be
+// closed with FindClose (otherwise they aren't closed properly).
+struct AutoHandle {
+ AutoHandle(HANDLE _handle = INVALID_HANDLE_VALUE) : handle(_handle) {}
+
+ ~AutoHandle() {
+ ::CloseHandle(handle); // succeeds if handle == INVALID_HANDLE_VALUE
+ handle = INVALID_HANDLE_VALUE;
+ }
+
+ bool IsValid() { return handle != INVALID_HANDLE_VALUE && handle != NULL; }
+
+ AutoHandle& operator=(const HANDLE& rhs) {
+ ::CloseHandle(handle);
+ handle = rhs;
+ return *this;
+ }
+
+ operator HANDLE() const { return handle; }
+
+ HANDLE handle;
+};
+
+string GetLastErrorString(const string& cause);
+
+// Same as `AsExecutablePathForCreateProcess` except it won't quote the result.
+string AsShortPath(string path, function<wstring()> path_as_wstring,
+ string* result);
+
+// Computes a path suitable as the executable part in CreateProcessA's cmdline.
+//
+// The null-terminated executable path for CreateProcessA has to fit into
+// MAX_PATH, therefore the limit for the executable's path is MAX_PATH - 1
+// (not including null terminator). This method attempts to convert the input
+// `path` to a short format to fit it into the MAX_PATH - 1 limit.
+//
+// `path` must be either an absolute, normalized, Windows-style path with drive
+// letter (e.g. "c:\foo\bar.exe", but no "\foo\bar.exe"), or must be just a file
+// name (e.g. "cmd.exe") that's shorter than MAX_PATH (without null-terminator).
+// In both cases, `path` must be unquoted.
+//
+// `path_as_wstring` must be a function that retrieves `path` as (or converts it
+// to) a wstring, without performing any transformations on the path.
+//
+// If this function succeeds, it returns an empty string (indicating no error),
+// and sets `result` to the resulting path, which is always quoted, and is
+// always at most MAX_PATH + 1 long (MAX_PATH - 1 without null terminator, plus
+// two quotes). If there's any error, this function returns the error message.
+//
+// If `path` is at most MAX_PATH - 1 long (not including null terminator), the
+// result will be that (plus quotes).
+// Otherwise this method attempts to compute an 8dot3 style short name for
+// `path`, and if that succeeds and the result is at most MAX_PATH - 1 long (not
+// including null terminator), then that will be the result (plus quotes).
+// Otherwise this function fails and returns an error message.
+string AsExecutablePathForCreateProcess(const string& path,
+ function<wstring()> path_as_wstring,
+ string* result);
+
+} // namespace windows
+} // namespace bazel
+
+#endif // BAZEL_SRC_MAIN_NATIVE_WINDOWS_UTIL_H__