Windows: implement and use AsShortWindowsPath

Because CreateProcessW doesn't support long paths,
not even with the "\\?\" prefix [1], we need to
convert long paths to short ones to spawn
processes. This change implements the
corresponding function and uses it in
blaze_util_windows.

[1] https://github.com/bazelbuild/bazel/issues/2181#issuecomment-270696173

See https://github.com/bazelbuild/bazel/issues/2107
See https://github.com/bazelbuild/bazel/issues/2181

--
PiperOrigin-RevId: 144062404
MOS_MIGRATED_REVID=144062404
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index 12aa3e3..0595de6 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -373,6 +373,12 @@
 static void CreateCommandLine(CmdLine* result, const string& exe,
                               const vector<string>& args_vector) {
   std::ostringstream cmdline;
+  string short_exe;
+  if (!blaze_util::AsShortWindowsPath(exe + ".exe", &short_exe)) {
+    pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
+         "CreateCommandLine: AsShortWindowsPath(%s.exe) failed, err=%d",
+         exe.c_str(), GetLastError());
+  }
   bool first = true;
   for (const auto& s : args_vector) {
     if (first) {
@@ -456,23 +462,23 @@
   }
 
   PROCESS_INFORMATION processInfo = {0};
-  STARTUPINFOW startupInfo = {0};
+  STARTUPINFOA startupInfo = {0};
   startupInfo.hStdError = pipe_write;
   startupInfo.hStdOutput = pipe_write;
   startupInfo.dwFlags |= STARTF_USESTDHANDLES;
 
-  wstring wjava_exe;
-  if (!blaze_util::AsWindowsPath(java_exe, &wjava_exe)) {
+  string win_java_exe;
+  if (!blaze_util::AsShortWindowsPath(java_exe + ".exe", &win_java_exe)) {
     CloseHandle(pipe_read);
     CloseHandle(pipe_write);
-    pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "AsWindowsPath(%s)",
-         java_exe);
+    pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
+         "GetJvmVersion: AsWindowsPath(%s.exe)", java_exe.c_str());
   }
-  wjava_exe = wstring(L"\"") + wjava_exe + L".exe\" -version";
+  win_java_exe = string("\"") + win_java_exe + "\" -version";
 
-  WCHAR cmdline[MAX_CMDLINE_LENGTH];
-  wcscpy(cmdline, wjava_exe.c_str());
-  BOOL ok = CreateProcessW(
+  char cmdline[MAX_CMDLINE_LENGTH];
+  strncpy(cmdline, win_java_exe.c_str(), win_java_exe.size() + 1);
+  BOOL ok = CreateProcessA(
       /* lpApplicationName */ NULL,
       /* lpCommandLine */ cmdline,
       /* lpProcessAttributes */ NULL,
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index 9c95468..91fc0866 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -133,6 +133,10 @@
 // prepend the UNC prefix in case they need to pass it to a WinAPI function
 // (some require the prefix, some don't), or to quote the path if necessary.
 bool AsWindowsPath(const std::string &path, std::wstring *result);
+
+// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
+// Result will never have a UNC prefix.
+bool AsShortWindowsPath(const std::string &path, std::string *result);
 #endif  // defined(COMPILER_MSVC) || defined(__CYGWIN__)
 
 }  // namespace blaze_util
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index 75555bb..3fa01a4 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -308,6 +308,8 @@
 
 static bool AsWindowsPathWithUncPrefix(const string& path, wstring* wpath) {
   if (!AsWindowsPath(path, wpath)) {
+    PrintError("AsWindowsPathWithUncPrefix(%s): AsWindowsPath failed, err=%d\n",
+               path.c_str(), GetLastError());
     return false;
   }
   if (!IsAbsolute(path)) {
@@ -317,6 +319,31 @@
   return true;
 }
 
+bool AsShortWindowsPath(const string& path, string* result) {
+  result->clear();
+  wstring wpath;
+  if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
+    return false;
+  }
+  DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
+  if (size == 0) {
+    return false;
+  }
+
+  unique_ptr<WCHAR[]> wshort(new WCHAR[size]);  // size includes null-terminator
+  if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
+    pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
+         "AsShortWindowsPath(%s): GetShortPathNameW(%S) failed, err=%d",
+         path.c_str(), wpath.c_str(), GetLastError());
+  }
+  // GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
+  WCHAR* result_ptr = wshort.get() + (HasUncPrefix(wshort.get()) ? 4 : 0);
+
+  result->assign(WstringToCstring(result_ptr).get());
+  ToLower(result);
+  return true;
+}
+
 bool ReadFile(const string& filename, string* content, int max_size) {
   if (filename.empty()) {
     return false;
diff --git a/src/test/cpp/util/file_windows_test.cc b/src/test/cpp/util/file_windows_test.cc
index 983763a..1cf49d8 100644
--- a/src/test/cpp/util/file_windows_test.cc
+++ b/src/test/cpp/util/file_windows_test.cc
@@ -178,6 +178,25 @@
   ASSERT_EQ(wlongpath, actual);
 }
 
+TEST(FileTest, TestAsShortWindowsPath) {
+  string tmpdir(GetTestTmpDir());
+  ASSERT_LT(0, tmpdir.size());
+
+  string short_tmpdir;
+  ASSERT_TRUE(AsShortWindowsPath(tmpdir, &short_tmpdir));
+  ASSERT_LT(0, short_tmpdir.size());
+  ASSERT_TRUE(PathExists(short_tmpdir));
+
+  string dirname(JoinPath(short_tmpdir, "LONGpathNAME"));
+  ASSERT_EQ(0, mkdir(dirname.c_str()));
+  ASSERT_TRUE(PathExists(dirname));
+
+  string actual;
+  ASSERT_TRUE(AsShortWindowsPath(dirname, &actual));
+  ASSERT_EQ(short_tmpdir + "\\longpa~1", actual);
+  ASSERT_EQ(0, rmdir(dirname.c_str()));
+}
+
 TEST(FileTest, TestMsysRootRetrieval) {
   wstring actual;