Windows, JNI: create process from relative path

The JNI library now supports creating processes
from a relative path.

The path is absolutized (relative to the current
directory) and normalized if necessary.

With this, we can "bazel run" tests with
--incompatible_windows_bashless_run_command and
--incompatible_windows_native_test_wrapper (but
not yet with
--noincompatible_windows_native_test_wrapper).

Closes #8476.

PiperOrigin-RevId: 250445511
diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc
index b7fb4b3..cc79ead 100644
--- a/src/main/native/windows/file.cc
+++ b/src/main/native/windows/file.cc
@@ -45,14 +45,6 @@
   return bazel::windows::HasUncPrefix(path.c_str()) ? path.substr(4) : path;
 }
 
-bool HasDriveSpecifierPrefix(const wstring& p) {
-  if (HasUncPrefix(p.c_str())) {
-    return p.size() >= 7 && iswalpha(p[4]) && p[5] == ':' && p[6] == '\\';
-  } else {
-    return p.size() >= 3 && iswalpha(p[0]) && p[1] == ':' && p[2] == '\\';
-  }
-}
-
 bool IsAbsoluteNormalizedWindowsPath(const wstring& p) {
   if (p.empty()) {
     return false;
@@ -64,7 +56,7 @@
     return false;
   }
 
-  return HasDriveSpecifierPrefix(p) && p.find(L".\\") != 0 &&
+  return HasDriveSpecifierPrefix(p.c_str()) && p.find(L".\\") != 0 &&
          p.find(L"\\.\\") == wstring::npos && p.find(L"\\.") != p.size() - 2 &&
          p.find(L"..\\") != 0 && p.find(L"\\..\\") == wstring::npos &&
          p.find(L"\\..") != p.size() - 3;
@@ -773,5 +765,21 @@
 
 std::wstring Normalize(const std::wstring& p) { return NormalizeImpl(p); }
 
+bool GetCwd(std::wstring* result, DWORD* err_code) {
+  // Maximum path is 32767 characters, with null terminator that is 0x8000.
+  static constexpr DWORD kMaxPath = 0x8000;
+  WCHAR buf[kMaxPath];
+  DWORD len = GetCurrentDirectoryW(kMaxPath, buf);
+  if (len > 0 && len < kMaxPath) {
+    *result = buf;
+    return true;
+  } else {
+    if (err_code) {
+      *err_code = GetLastError();
+    }
+    return false;
+  }
+}
+
 }  // namespace windows
 }  // namespace bazel
diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h
index 5b73c3e..f8651e6 100644
--- a/src/main/native/windows/file.h
+++ b/src/main/native/windows/file.h
@@ -45,6 +45,15 @@
          (path[2] == 'L' || path[2] == 'l');
 }
 
+template <typename char_type>
+bool HasDriveSpecifierPrefix(const char_type* p) {
+  if (HasUncPrefix(p)) {
+    return iswalpha(p[4]) && p[5] == ':' && (p[6] == '\\' || p[6] == '/');
+  } else {
+    return iswalpha(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
+  }
+}
+
 std::wstring AddUncPrefixMaybe(const std::wstring& path);
 
 std::wstring RemoveUncPrefixMaybe(const std::wstring& path);
@@ -184,6 +193,8 @@
 std::string Normalize(const std::string& p);
 std::wstring Normalize(const std::wstring& p);
 
+bool GetCwd(std::wstring* result, DWORD* err_code);
+
 }  // namespace windows
 }  // namespace bazel
 
diff --git a/src/main/native/windows/util.cc b/src/main/native/windows/util.cc
index 0aac172..0e46dee 100644
--- a/src/main/native/windows/util.cc
+++ b/src/main/native/windows/util.cc
@@ -27,6 +27,8 @@
 #include <sstream>
 #include <string>
 
+#include "src/main/native/windows/file.h"
+
 namespace bazel {
 namespace windows {
 
@@ -214,8 +216,7 @@
     return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"AsShortPath", path,
                             L"path is just a file name but too long");
   }
-  if (HasSeparator(path) &&
-      !(isalpha(path[0]) && path[1] == L':' && IsSeparator(path[2]))) {
+  if (HasSeparator(path) && !HasDriveSpecifierPrefix(path.c_str())) {
     return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"AsShortPath", path,
                             L"path is not absolute");
   }
@@ -261,12 +262,26 @@
   return L"";
 }
 
-wstring AsExecutablePathForCreateProcess(const wstring& path, wstring* result) {
+wstring AsExecutablePathForCreateProcess(wstring path, wstring* result) {
   if (path.empty()) {
     return MakeErrorMessage(WSTR(__FILE__), __LINE__,
                             L"AsExecutablePathForCreateProcess", path,
                             L"path should not be empty");
   }
+  if (IsSeparator(path[0])) {
+    return MakeErrorMessage(WSTR(__FILE__), __LINE__,
+                            L"AsExecutablePathForCreateProcess", path,
+                            L"path is absolute without a drive letter");
+  }
+  if (HasSeparator(path) && !HasDriveSpecifierPrefix(path.c_str())) {
+    wstring cwd;
+    DWORD err;
+    if (!GetCwd(&cwd, &err)) {
+      return MakeErrorMessage(WSTR(__FILE__), __LINE__,
+                              L"AsExecutablePathForCreateProcess", path, err);
+    }
+    path = cwd + L"\\" + path;
+  }
   wstring error = AsShortPath(path, result);
   if (!error.empty()) {
     return MakeErrorMessage(WSTR(__FILE__), __LINE__,
diff --git a/src/main/native/windows/util.h b/src/main/native/windows/util.h
index 2380314..16357a1 100644
--- a/src/main/native/windows/util.h
+++ b/src/main/native/windows/util.h
@@ -148,7 +148,7 @@
 // `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.
-wstring AsExecutablePathForCreateProcess(const wstring& path, wstring* result);
+wstring AsExecutablePathForCreateProcess(wstring path, wstring* result);
 
 }  // namespace windows
 }  // namespace bazel
diff --git a/src/test/native/windows/util_test.cc b/src/test/native/windows/util_test.cc
index 9f6a3ca..857518c 100644
--- a/src/test/native/windows/util_test.cc
+++ b/src/test/native/windows/util_test.cc
@@ -256,7 +256,6 @@
   ASSERT_SHORTENING_FAILS(L"\"cmd.exe\"", L"path should not be quoted");
   ASSERT_SHORTENING_FAILS(L"/dev/null", L"path is absolute without a drive");
   ASSERT_SHORTENING_FAILS(L"/usr/bin/bash", L"path is absolute without a");
-  ASSERT_SHORTENING_FAILS(L"foo\\bar.exe", L"path is not absolute");
   ASSERT_SHORTENING_FAILS(L"foo\\..\\bar.exe", L"path is not normalized");
   ASSERT_SHORTENING_FAILS(L"\\bar.exe", L"path is absolute");
 
@@ -266,6 +265,13 @@
   }
   dummy += L".exe";
   ASSERT_SHORTENING_FAILS(dummy.c_str(), L"a file name but too long");
+
+  // Relative paths are fine, they are absolutized.
+  std::wstring rel(L"foo\\bar.exe");
+  std::wstring actual;
+  EXPECT_EQ(AsExecutablePathForCreateProcess(rel, &actual), L"");
+  EXPECT_GT(actual.size(), rel.size());
+  EXPECT_EQ(actual.rfind(rel), actual.size() - rel.size() - 1);
 }
 
 TEST(WindowsUtilTest, TestAsExecutablePathForCreateProcessConversions) {