Bazel client, Windows: implement AsWindowsPath

This method converts MSYS paths to Windows path.
It uses the BAZEL_SH envvar to obtain the MSYS
root directory, to which all Unix paths (except
for mounts) are relative.

We cannot handle mounts because we don't want to
read /etc/mtab every time there's a file operation
so we simply apply a heuristic similar to
https://github.com/bazelbuild/bazel/blob/cd4cc09fa6ef96380a3d0888f825dfd1dbada651/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java#L52-L63

Also clean up the #ifdefs surrounding SyncFile.

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

--
PiperOrigin-RevId: 142531986
MOS_MIGRATED_REVID=142531986
diff --git a/src/main/cpp/util/errors.cc b/src/main/cpp/util/errors.cc
index 49248a1..8497545 100644
--- a/src/main/cpp/util/errors.cc
+++ b/src/main/cpp/util/errors.cc
@@ -41,4 +41,13 @@
   exit(exit_status);
 }
 
+void PrintError(const char *format, ...) {
+  fprintf(stderr, "Error: ");
+  va_list ap;
+  va_start(ap, format);
+  vfprintf(stderr, format, ap);
+  va_end(ap);
+  fprintf(stderr, ": %s\n", strerror(errno));
+}
+
 }  // namespace blaze_util
diff --git a/src/main/cpp/util/errors.h b/src/main/cpp/util/errors.h
index 84de39d..833a6ee 100644
--- a/src/main/cpp/util/errors.h
+++ b/src/main/cpp/util/errors.h
@@ -28,6 +28,7 @@
 // Prints "Error: <formatted-message>: <strerror(errno)>\n",  and exits nonzero.
 void pdie(const int exit_status, const char *format, ...) ATTRIBUTE_NORETURN
     PRINTF_ATTRIBUTE(2, 3);
+void PrintError(const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
 
 }  // namespace blaze_util
 
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index e6df884..15bfd5d 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -120,6 +120,26 @@
 void ForEachDirectoryEntry(const std::string &path,
                            DirectoryEntryConsumer *consume);
 
+#if defined(COMPILER_MSVC) || defined(__CYGWIN__)
+// Converts a UTF8-encoded `path` to a widechar Windows path.
+//
+// Returns true if conversion succeeded and sets the contents of `result` to it.
+//
+// The `path` may be absolute or relative, and may be a Windows or MSYS path.
+// In every case, this method replaces forward slashes with backslashes if
+// necessary.
+//
+// Recognizes the drive letter in MSYS paths, so e.g. "/c/windows" becomes
+// "c:\windows". Prepends the MSYS root (computed from the BAZEL_SH envvar) to
+// absolute MSYS paths, so e.g. "/usr" becomes "c:\tools\msys64\usr".
+//
+// The result may be longer than MAX_PATH. It's the caller's responsibility to
+// prepend the long path prefix ("\\?\") in case they need to pass it to a
+// Windows API function (some require the prefix, some don't), or to quote the
+// path if necessary.
+bool AsWindowsPath(const std::string &path, std::wstring *result);
+#endif  // defined(COMPILER_MSVC) || defined(__CYGWIN__)
+
 }  // namespace blaze_util
 
 #endif  // BAZEL_SRC_MAIN_CPP_UTIL_FILE_PLATFORM_H_
diff --git a/src/main/cpp/util/file_posix.cc b/src/main/cpp/util/file_posix.cc
index b256a68..81e9bd6 100644
--- a/src/main/cpp/util/file_posix.cc
+++ b/src/main/cpp/util/file_posix.cc
@@ -278,11 +278,8 @@
 }
 
 bool IsAbsolute(const string &path) { return !path.empty() && path[0] == '/'; }
-#endif  // not __CYGWIN__
 
 void SyncFile(const string& path) {
-// fsync always fails on Cygwin with "Permission denied" for some reason.
-#ifndef __CYGWIN__
   const char* file_path = path.c_str();
   int fd = open(file_path, O_RDONLY);
   if (fd < 0) {
@@ -294,8 +291,8 @@
          file_path);
   }
   close(fd);
-#endif  // not __CYGWIN__
 }
+#endif  // not __CYGWIN__
 
 time_t GetMtimeMillisec(const string& path) {
   struct stat buf;
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index bf6e79c..021e96d 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -16,14 +16,19 @@
 #include <ctype.h>  // isalpha
 #include <windows.h>
 
+#include <memory>  // unique_ptr
+
 #include "src/main/cpp/util/errors.h"
 #include "src/main/cpp/util/exit_code.h"
 #include "src/main/cpp/util/file.h"
+#include "src/main/cpp/util/strings.h"
 
 namespace blaze_util {
 
 using std::pair;
 using std::string;
+using std::unique_ptr;
+using std::wstring;
 
 class WindowsPipe : public IPipe {
  public:
@@ -132,6 +137,86 @@
   return std::make_pair("", path);
 }
 
+class MsysRoot {
+ public:
+  MsysRoot() : data_(Get()) {}
+  bool IsValid() const { return data_.first; }
+  const string& GetPath() const { return data_.second; }
+
+ private:
+  const std::pair<bool, string> data_;
+  static std::pair<bool, string> Get();
+};
+
+std::pair<bool, string> MsysRoot::Get() {
+  string result;
+  char value[MAX_PATH];
+  DWORD len = GetEnvironmentVariableA("BAZEL_SH", value, MAX_PATH);
+  if (len > 0) {
+    result = value;
+  } else {
+    const char* value2 = getenv("BAZEL_SH");
+    if (value2 == nullptr || value2[0] == '\0') {
+      PrintError(
+          "BAZEL_SH environment variable is not defined, cannot convert MSYS "
+          "paths to Windows paths");
+      return std::make_pair(false, "");
+    }
+    result = value2;
+  }
+  // BAZEL_SH is usually "c:\tools\msys64\bin\bash.exe", we need to return
+  // "c:\tools\msys64".
+  return std::make_pair(true, std::move(Dirname(Dirname(result))));
+}
+
+bool AsWindowsPath(const string& path, wstring* result) {
+  if (path.empty()) {
+    result->clear();
+    return true;
+  }
+
+  string mutable_path = path;
+  if (path[0] == '/') {
+    // This is an absolute MSYS path.
+    if (path.size() == 2 || (path.size() > 2 && path[2] == '/')) {
+      // The path is either "/x" or "/x/" or "/x/something". In all three cases
+      // "x" is the drive letter.
+      // TODO(laszlocsomor): use GetLogicalDrives to retrieve the list of drives
+      // and only apply this heuristic for the valid drives. It's possible that
+      // the user has a directory "/a" but no "A:\" drive, so in that case we
+      // should prepend the MSYS root.
+      mutable_path = path.substr(1, 1) + ":\\";
+      if (path.size() > 2) {
+        mutable_path += path.substr(3);
+      }
+    } else {
+      // The path is a normal MSYS path e.g. "/usr". Prefix it with the MSYS
+      // root.
+      // Define kMsysRoot only in this scope. This way we only initialize it
+      // and thus check for BAZEL_SH if we really need to, i.e. the caller
+      // passed an MSYS path and we have to convert it. If all paths ever passed
+      // are Windows paths, we don't need to check whether BAZEL_SH is defined.
+      static const MsysRoot kMsysRoot;
+      if (!kMsysRoot.IsValid()) {
+        return false;
+      }
+      mutable_path = JoinPath(kMsysRoot.GetPath(), path);
+    }
+  }  // otherwise this is a relative path, or absolute Windows path.
+
+  unique_ptr<WCHAR[]> mutable_wpath(CstringToWstring(mutable_path.c_str()));
+  WCHAR* p = mutable_wpath.get();
+  // Replace forward slashes with backslashes.
+  while (*p != L'\0') {
+    if (*p == L'/') {
+      *p = L'\\';
+    }
+    ++p;
+  }
+  result->assign(mutable_wpath.get());
+  return true;
+}
+
 #ifdef COMPILER_MSVC
 bool ReadFile(const string& filename, string* content, int max_size) {
   // TODO(bazel-team): implement this.
@@ -209,12 +294,10 @@
 
 bool IsAbsolute(const string& path) { return IsRootOrAbsolute(path, false); }
 
-#ifdef COMPILER_MSVC
 void SyncFile(const string& path) {
   // No-op on Windows native; unsupported by Cygwin.
+  // fsync always fails on Cygwin with "Permission denied" for some reason.
 }
-#else  // not COMPILER_MSVC
-#endif  // COMPILER_MSVC
 
 #ifdef COMPILER_MSVC
 time_t GetMtimeMillisec(const string& path) {