windows,client: fix error reporting

Fix error reporting in the path conversion methods
of the Bazel client. Previously the error
reporting logic used GetLastErrorString in places
where it was not appropriate (i.e. it was not a
failed Windows API call that caused an error).

This cleanup prepares removing the concept of the
MSYS root from the Bazel client, since MSYS paths
are no longer supported and we want to cut Bazel's
dependency on Bash (thus MSYS) completely.

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

Change-Id: Ie50a20e0ee0c572592f637340a2f2948c7f53088

Closes #5072.

Change-Id: Ie50a20e0ee0c572592f637340a2f2948c7f53088
PiperOrigin-RevId: 194052665
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index 95e3bb5..efd0d72 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -276,10 +276,10 @@
                               const std::vector<string>& args_vector) {
   std::ostringstream cmdline;
   string short_exe;
-  if (!blaze_util::AsShortWindowsPath(exe, &short_exe)) {
+  string error;
+  if (!blaze_util::AsShortWindowsPath(exe, &short_exe, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "CreateCommandLine: AsShortWindowsPath(" << exe
-        << "): " << GetLastErrorString();
+        << "CreateCommandLine: AsShortWindowsPath(" << exe << "): " << error;
   }
   bool first = true;
   for (const auto& s : args_vector) {
@@ -440,10 +440,12 @@
                   const string& server_dir,
                   BlazeServerStartup** server_startup) {
   wstring wdaemon_output;
-  if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output)) {
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output,
+                                         &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "ExecuteDaemon(" << exe << "): AsAbsoluteWindowsPath("
-        << daemon_output << ") failed: " << GetLastErrorString();
+        << daemon_output << ") failed: " << error;
   }
 
   SECURITY_ATTRIBUTES sa;
@@ -653,10 +655,11 @@
 
 string PathAsJvmFlag(const string& path) {
   string spath;
-  if (!blaze_util::AsShortWindowsPath(path, &spath)) {
+  string error;
+  if (!blaze_util::AsShortWindowsPath(path, &spath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "PathAsJvmFlag(" << path
-        << "): AsShortWindowsPath failed: " << GetLastErrorString();
+        << "): AsShortWindowsPath failed: " << error;
   }
   // Convert backslashes to forward slashes, in order to avoid the JVM parsing
   // Windows paths as if they contained escaped characters.
@@ -668,10 +671,11 @@
 string ConvertPath(const string& path) {
   // The path may not be Windows-style and may not be normalized, so convert it.
   wstring wpath;
-  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "ConvertPath(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
   }
   std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower);
   return string(blaze_util::WstringToCstring(
@@ -682,18 +686,17 @@
 bool SymlinkDirectories(const string &posix_target, const string &posix_name) {
   wstring name;
   wstring target;
-  if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name)) {
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "SymlinkDirectories(" << posix_target << ", " << posix_name
-        << "): AsAbsoluteWindowsPath(" << posix_target
-        << ") failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath(" << posix_target << ") failed: " << error;
     return false;
   }
-  if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target)) {
+  if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "SymlinkDirectories(" << posix_target << ", " << posix_name
-        << "): AsAbsoluteWindowsPath(" << posix_name
-        << ") failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath(" << posix_name << ") failed: " << error;
     return false;
   }
   wstring werror(CreateJunction(name, target));
@@ -961,10 +964,11 @@
                      BlazeLock* blaze_lock) {
   string lockfile = blaze_util::JoinPath(output_base, "lock");
   wstring wlockfile;
-  if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile)) {
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "AcquireLock(" << output_base << "): AsAbsoluteWindowsPath("
-        << lockfile << ") failed: " << GetLastErrorString();
+        << lockfile << ") failed: " << error;
   }
 
   blaze_lock->handle = INVALID_HANDLE_VALUE;
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index 8640960..5b96133 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -213,15 +213,18 @@
 #if defined(COMPILER_MSVC) || defined(__CYGWIN__)
 const wchar_t *RemoveUncPrefixMaybe(const wchar_t *ptr);
 
-bool AsWindowsPath(const std::string &path, std::string *result);
+bool AsWindowsPath(const std::string &path, std::string *result,
+                   std::string *error);
 
-bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath);
+bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath,
+                           std::string *error);
 
 // Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
 // Result will never have a UNC prefix, nor a trailing "/" or "\".
 // Works also for non-existent paths; shortens as much of them as it can.
 // Also works for non-existent drives.
-bool AsShortWindowsPath(const std::string &path, std::string *result);
+bool AsShortWindowsPath(const std::string &path, std::string *result,
+                        std::string *error);
 #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 b495d8c..e51161c 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -184,12 +184,11 @@
     return true;
   }
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "WindowsFileMtime::GetIfInDistantFuture(" << path
-        << "): AsAbsoluteWindowsPath "
-           "failed: "
-        << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
   }
 
   AutoHandle handle(::CreateFileW(
@@ -238,10 +237,11 @@
     return false;
   }
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "WindowsFileMtime::Set(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
 
@@ -443,7 +443,7 @@
   }
 }
 
-bool AsWindowsPath(const string& path, string* result) {
+bool AsWindowsPath(const string& path, string* result, string* error) {
   if (path.empty()) {
     result->clear();
     return true;
@@ -460,11 +460,17 @@
   if (IsPathSeparator(path[0]) && path.size() > 1 && IsPathSeparator(path[1])) {
     // Unsupported path: "\\" or "\\server\path", or some degenerate form of
     // these, such as "//foo".
+    if (error) {
+      *error = "network paths are unsupported";
+    }
     return false;
   }
   if (HasDriveSpecifierPrefix(path.c_str()) &&
       (path.size() < 3 || !IsPathSeparator(path[2]))) {
     // Unsupported path: "c:" or "c:foo"
+    if (error) {
+      *error = "working-directory relative paths are unsupported";
+    }
     return false;
   }
 
@@ -486,6 +492,9 @@
       // The path is a normal MSYS path e.g. "/usr". Prefix it with the MSYS
       // root.
       if (!MsysRoot::IsValid()) {
+        if (error) {
+          *error = "MSYS root is invalid";
+        }
         return false;
       }
       mutable_path = JoinPath(MsysRoot::GetPath(), path);
@@ -517,9 +526,9 @@
 // envvar) to absolute MSYS paths, so e.g. "/usr" becomes "c:\tools\msys64\usr".
 // Recognizes current-drive-relative Windows paths ("\foo") turning them into
 // absolute paths ("c:\foo").
-bool AsWindowsPath(const string& path, wstring* result) {
+bool AsWindowsPath(const string& path, wstring* result, string* error) {
   string normalized_win_path;
-  if (!AsWindowsPath(path, &normalized_win_path)) {
+  if (!AsWindowsPath(path, &normalized_win_path, error)) {
     return false;
   }
 
@@ -527,7 +536,7 @@
   return true;
 }
 
-bool AsAbsoluteWindowsPath(const string& path, wstring* result) {
+bool AsAbsoluteWindowsPath(const string& path, wstring* result, string* error) {
   if (path.empty()) {
     result->clear();
     return true;
@@ -536,7 +545,7 @@
     result->assign(L"NUL");
     return true;
   }
-  if (!AsWindowsPath(path, result)) {
+  if (!AsWindowsPath(path, result, error)) {
     return false;
   }
   if (!IsRootOrAbsolute(*result, /* must_be_root */ false)) {
@@ -548,7 +557,7 @@
   return true;
 }
 
-bool AsShortWindowsPath(const string& path, string* result) {
+bool AsShortWindowsPath(const string& path, string* result, string* error) {
   if (IsDevNull(path.c_str())) {
     result->assign("NUL");
     return true;
@@ -557,10 +566,7 @@
   result->clear();
   wstring wpath;
   wstring wsuffix;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
-    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "AsShortWindowsPath(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+  if (!AsAbsoluteWindowsPath(path, &wpath, error)) {
     return false;
   }
   DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
@@ -600,10 +606,14 @@
     unique_ptr<WCHAR[]> wshort(
         new WCHAR[size]);  // size includes null-terminator
     if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
-      BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-          << "AsShortWindowsPath(" << path << "): GetShortPathNameW("
-          << blaze_util::WstringToString(wpath)
-          << ") failed: " << GetLastErrorString();
+      if (error) {
+        string last_error = GetLastErrorString();
+        std::stringstream msg;
+        msg << "AsShortWindowsPath(" << path << "): GetShortPathNameW("
+            << blaze_util::WstringToString(wpath) << ") failed: " << last_error;
+        *error = msg.str();
+      }
+      return false;
     }
     // GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
     wresult = wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix;
@@ -623,10 +633,11 @@
     return true;
   }
   wstring wfilename;
-  if (!AsAbsoluteWindowsPath(filename, &wfilename)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(filename, &wfilename, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "OpenFileForReading(" << filename
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
   }
   *result = ::CreateFileW(
       /* lpFileName */ wfilename.c_str(),
@@ -693,10 +704,11 @@
     return true;  // mimic write(2) behavior with /dev/null
   }
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(filename, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(filename, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "WriteFile(" << filename
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
 
@@ -736,20 +748,19 @@
 
 int RenameDirectory(const std::string& old_name, const std::string& new_name) {
   wstring wold_name;
-  if (!AsAbsoluteWindowsPath(old_name, &wold_name)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(old_name, &wold_name, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "RenameDirectory(" << old_name << ", " << new_name
-        << "): AsAbsoluteWindowsPath(" << old_name
-        << ") failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath(" << old_name << ") failed: " << error;
     return kRenameDirectoryFailureOtherError;
   }
 
   wstring wnew_name;
-  if (!AsAbsoluteWindowsPath(new_name, &wnew_name)) {
+  if (!AsAbsoluteWindowsPath(new_name, &wnew_name, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "RenameDirectory(" << old_name << ", " << new_name
-        << "): AsAbsoluteWindowsPath(" << new_name
-        << ") failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath(" << new_name << ") failed: " << error;
     return kRenameDirectoryFailureOtherError;
   }
 
@@ -788,10 +799,11 @@
   }
 
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(file_path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(file_path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "UnlinkPath(" << file_path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   return UnlinkPathW(wpath);
@@ -921,10 +933,11 @@
 
 bool ReadDirectorySymlink(const string& name, string* result) {
   wstring wname;
-  if (!AsAbsoluteWindowsPath(name, &wname)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(name, &wname, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "ReadDirectorySymlink(" << name
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   unique_ptr<WCHAR[]> result_ptr;
@@ -943,10 +956,11 @@
     return true;
   }
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "PathExists(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   return JunctionResolver().Resolve(wpath.c_str(), nullptr);
@@ -960,10 +974,11 @@
   if (path == nullptr || path[0] == 0) {
     return "";
   }
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "MakeCanonical(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
   }
 
   // Resolve all segments of the path. Do this from leaf to root, so we always
@@ -1025,10 +1040,10 @@
   // Resolve all 8dot3 style segments of the path, if any. The input path may
   // have had some. Junctions may also refer to 8dot3 names.
   unique_ptr<WCHAR[]> long_realpath;
-  wstring error(GetLongPath(realpath.c_str(), &long_realpath));
-  if (!error.empty()) {
+  wstring werror(GetLongPath(realpath.c_str(), &long_realpath));
+  if (!werror.empty()) {
     // TODO(laszlocsomor): refactor MakeCanonical to return an error message,
-    // return `error` here.
+    // return `werror` here.
     return "";
   }
 
@@ -1067,10 +1082,11 @@
 
 bool CanReadFile(const std::string& path) {
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "CanReadFile(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   return CanReadFileW(wpath);
@@ -1078,10 +1094,11 @@
 
 bool CanExecuteFile(const std::string& path) {
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "CanExecuteFile(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   return CanReadFileW(wpath) && (ends_with(wpath, wstring(L".exe")) ||
@@ -1092,10 +1109,11 @@
 
 bool CanAccessDirectory(const std::string& path) {
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "CanAccessDirectory(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   DWORD attr = ::GetFileAttributesW(wpath.c_str());
@@ -1160,10 +1178,11 @@
     return false;
   }
   wstring wpath;
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "IsDirectory(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   return IsDirectoryW(wpath);
@@ -1212,10 +1231,11 @@
   wstring wpath;
   // According to MSDN, CreateDirectory's limit without the UNC prefix is
   // 248 characters (so it could fit another filename before reaching MAX_PATH).
-  if (!AsAbsoluteWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "MakeDirectories(" << path
-        << "): AsAbsoluteWindowsPath failed: " << GetLastErrorString();
+        << "): AsAbsoluteWindowsPath failed: " << error;
     return false;
   }
   return MakeDirectoriesW(wpath);
@@ -1247,8 +1267,12 @@
 
 bool ChangeDirectory(const string& path) {
   string spath;
-  return AsShortWindowsPath(path, &spath) &&
-         ::SetCurrentDirectoryA(spath.c_str()) == TRUE;
+  string error;
+  if (!AsShortWindowsPath(path, &spath, &error)) {
+    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+        << "ChangeDirectory(" << path << "): failed: " << error;
+  }
+  return ::SetCurrentDirectoryA(spath.c_str()) == TRUE;
 }
 
 void ForEachDirectoryEntry(const string &path,
@@ -1257,7 +1281,8 @@
   if (path.empty() || IsDevNull(path.c_str())) {
     return;
   }
-  if (!AsWindowsPath(path, &wpath)) {
+  string error;
+  if (!AsWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "ForEachDirectoryEntry(" << path
         << "): AsWindowsPath failed: " << GetLastErrorString();
diff --git a/src/test/cpp/util/file_windows_test.cc b/src/test/cpp/util/file_windows_test.cc
index 5b3809b..4322404 100644
--- a/src/test/cpp/util/file_windows_test.cc
+++ b/src/test/cpp/util/file_windows_test.cc
@@ -40,7 +40,7 @@
 using std::wstring;
 
 // Methods defined in file_windows.cc that are only visible for testing.
-bool AsWindowsPath(const string& path, wstring* result);
+bool AsWindowsPath(const string& path, wstring* result, string* error);
 void ResetMsysRootForTesting();
 string NormalizeWindowsPath(string path);
 
@@ -62,8 +62,8 @@
   {                                                                           \
     wstring wname;                                                            \
     wstring wtarget;                                                          \
-    EXPECT_TRUE(AsWindowsPath(name, &wname));                                 \
-    EXPECT_TRUE(AsWindowsPath(target, &wtarget));                             \
+    EXPECT_TRUE(AsWindowsPath(name, &wname, nullptr));                        \
+    EXPECT_TRUE(AsWindowsPath(target, &wtarget, nullptr));                    \
     EXPECT_EQ(L"", CreateJunction(wname, wtarget));                           \
   }
 
@@ -181,56 +181,60 @@
   wstring actual;
 
   // Null and empty input produces empty result.
-  ASSERT_TRUE(AsWindowsPath("", &actual));
+  ASSERT_TRUE(AsWindowsPath("", &actual, nullptr));
   ASSERT_EQ(wstring(L""), actual);
 
   // If the path has a "\\?\" prefix, AsWindowsPath assumes it's a correct
   // Windows path. If it's not, the Windows API function that we pass the path
   // to will fail anyway.
-  ASSERT_TRUE(AsWindowsPath("\\\\?\\anything/..", &actual));
+  ASSERT_TRUE(AsWindowsPath("\\\\?\\anything/..", &actual, nullptr));
   ASSERT_EQ(wstring(L"\\\\?\\anything/.."), actual);
 
   // Trailing slash or backslash is removed.
-  ASSERT_TRUE(AsWindowsPath("foo/", &actual));
+  ASSERT_TRUE(AsWindowsPath("foo/", &actual, nullptr));
   ASSERT_EQ(wstring(L"foo"), actual);
-  ASSERT_TRUE(AsWindowsPath("foo\\", &actual));
+  ASSERT_TRUE(AsWindowsPath("foo\\", &actual, nullptr));
   ASSERT_EQ(wstring(L"foo"), actual);
 
   // Slashes are converted to backslash.
-  ASSERT_TRUE(AsWindowsPath("foo/bar", &actual));
+  ASSERT_TRUE(AsWindowsPath("foo/bar", &actual, nullptr));
   ASSERT_EQ(wstring(L"foo\\bar"), actual);
-  ASSERT_TRUE(AsWindowsPath("c:/", &actual));
+  ASSERT_TRUE(AsWindowsPath("c:/", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\"), actual);
-  ASSERT_TRUE(AsWindowsPath("c:\\", &actual));
+  ASSERT_TRUE(AsWindowsPath("c:\\", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\"), actual);
 
   // Invalid paths
-  ASSERT_FALSE(AsWindowsPath("c:", &actual));
-  ASSERT_FALSE(AsWindowsPath("c:foo", &actual));
-  ASSERT_FALSE(AsWindowsPath("\\\\foo", &actual));
+  string error;
+  ASSERT_FALSE(AsWindowsPath("c:", &actual, &error));
+  EXPECT_TRUE(error.find("working-directory relative paths") != string::npos);
+  ASSERT_FALSE(AsWindowsPath("c:foo", &actual, &error));
+  EXPECT_TRUE(error.find("working-directory relative paths") != string::npos);
+  ASSERT_FALSE(AsWindowsPath("\\\\foo", &actual, &error));
+  EXPECT_TRUE(error.find("network paths") != string::npos);
 
   // /dev/null and NUL produce NUL.
-  ASSERT_TRUE(AsWindowsPath("/dev/null", &actual));
+  ASSERT_TRUE(AsWindowsPath("/dev/null", &actual, nullptr));
   ASSERT_EQ(wstring(L"NUL"), actual);
-  ASSERT_TRUE(AsWindowsPath("Nul", &actual));
+  ASSERT_TRUE(AsWindowsPath("Nul", &actual, nullptr));
   ASSERT_EQ(wstring(L"NUL"), actual);
 
   // MSYS path with drive letter.
-  ASSERT_TRUE(AsWindowsPath("/c", &actual));
+  ASSERT_TRUE(AsWindowsPath("/c", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\"), actual);
-  ASSERT_TRUE(AsWindowsPath("/c/", &actual));
+  ASSERT_TRUE(AsWindowsPath("/c/", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\"), actual);
-  ASSERT_TRUE(AsWindowsPath("/c/blah", &actual));
+  ASSERT_TRUE(AsWindowsPath("/c/blah", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\blah"), actual);
-  ASSERT_TRUE(AsWindowsPath("/d/progra~1/micros~1", &actual));
+  ASSERT_TRUE(AsWindowsPath("/d/progra~1/micros~1", &actual, nullptr));
   ASSERT_EQ(wstring(L"d:\\progra~1\\micros~1"), actual);
 
   // Absolute MSYS path without drive letter is relative to MSYS root.
-  ASSERT_TRUE(AsWindowsPath("/foo", &actual));
+  ASSERT_TRUE(AsWindowsPath("/foo", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\some\\long\\path\\foo"), actual);
 
   // Absolute-on-current-drive path gets a drive letter.
-  ASSERT_TRUE(AsWindowsPath("\\foo", &actual));
+  ASSERT_TRUE(AsWindowsPath("\\foo", &actual, nullptr));
   ASSERT_EQ(wstring(1, GetCwd()[0]) + L":\\foo", actual);
 
   // Even for long paths, AsWindowsPath doesn't add a "\\?\" prefix (it's the
@@ -242,7 +246,7 @@
     longpath += longpath;
   }
   wlongpath.pop_back();  // remove trailing "\"
-  ASSERT_TRUE(AsWindowsPath(longpath, &actual));
+  ASSERT_TRUE(AsWindowsPath(longpath, &actual, nullptr));
   ASSERT_EQ(wlongpath, actual);
 }
 
@@ -251,10 +255,10 @@
   ResetMsysRootForTesting();
   wstring actual;
 
-  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/", &actual));
+  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/", &actual, nullptr));
   ASSERT_EQ(L"\\\\?\\c:\\", actual);
 
-  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual));
+  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual, nullptr));
   ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual);
 
   WCHAR cwd[MAX_PATH];
@@ -262,41 +266,41 @@
   wstring expected =
       wstring(L"\\\\?\\") + cwdw +
       ((cwdw.back() == L'\\') ? L"non-existent" : L"\\non-existent");
-  ASSERT_TRUE(AsAbsoluteWindowsPath("non-existent", &actual));
+  ASSERT_TRUE(AsAbsoluteWindowsPath("non-existent", &actual, nullptr));
   ASSERT_EQ(actual, expected);
 }
 
 TEST_F(FileWindowsTest, TestAsShortWindowsPath) {
   string actual;
-  ASSERT_TRUE(AsShortWindowsPath("/dev/null", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("/dev/null", &actual, nullptr));
   ASSERT_EQ(string("NUL"), actual);
 
-  ASSERT_TRUE(AsShortWindowsPath("nul", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("nul", &actual, nullptr));
   ASSERT_EQ(string("NUL"), actual);
 
-  ASSERT_TRUE(AsShortWindowsPath("C://", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("C://", &actual, nullptr));
   ASSERT_EQ(string("c:\\"), actual);
-  ASSERT_TRUE(AsShortWindowsPath("/C//", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("/C//", &actual, nullptr));
   ASSERT_EQ(string("c:\\"), actual);
 
   // The A drive usually doesn't exist but AsShortWindowsPath should still work.
   // Here we even have multiple trailing slashes, that should be handled too.
-  ASSERT_TRUE(AsShortWindowsPath("A://", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("A://", &actual, nullptr));
   ASSERT_EQ(string("a:\\"), actual);
-  ASSERT_TRUE(AsShortWindowsPath("/A//", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("/A//", &actual, nullptr));
   ASSERT_EQ(string("a:\\"), actual);
 
   // Assert that we can shorten the TEST_TMPDIR.
   string tmpdir;
   GET_TEST_TMPDIR(tmpdir);
   string short_tmpdir;
-  ASSERT_TRUE(AsShortWindowsPath(tmpdir, &short_tmpdir));
+  ASSERT_TRUE(AsShortWindowsPath(tmpdir, &short_tmpdir, nullptr));
   ASSERT_LT(0, short_tmpdir.size());
   ASSERT_TRUE(PathExists(short_tmpdir));
 
   // Assert that a trailing "/" doesn't change the shortening logic and it will
   // be stripped from the result.
-  ASSERT_TRUE(AsShortWindowsPath(tmpdir + "/", &actual));
+  ASSERT_TRUE(AsShortWindowsPath(tmpdir + "/", &actual, nullptr));
   ASSERT_EQ(actual, short_tmpdir);
   ASSERT_NE(actual.back(), '/');
   ASSERT_NE(actual.back(), '\\');
@@ -305,14 +309,15 @@
   string dirname(JoinPath(short_tmpdir, "LONGpathNAME"));
   ASSERT_EQ(0, mkdir(dirname.c_str()));
   ASSERT_TRUE(PathExists(dirname));
-  ASSERT_TRUE(AsShortWindowsPath(dirname, &actual));
+  ASSERT_TRUE(AsShortWindowsPath(dirname, &actual, nullptr));
   ASSERT_EQ(short_tmpdir + "\\longpa~1", actual);
 
   // Assert shortening non-existent paths.
-  ASSERT_TRUE(AsShortWindowsPath(JoinPath(tmpdir, "NonExistent/FOO"), &actual));
+  ASSERT_TRUE(AsShortWindowsPath(JoinPath(tmpdir, "NonExistent/FOO"), &actual,
+                                 nullptr));
   ASSERT_EQ(short_tmpdir + "\\nonexistent\\foo", actual);
   // Assert shortening non-existent root paths.
-  ASSERT_TRUE(AsShortWindowsPath("/c/NonExistent/FOO", &actual));
+  ASSERT_TRUE(AsShortWindowsPath("/c/NonExistent/FOO", &actual, nullptr));
   ASSERT_EQ("c:\\nonexistent\\foo", actual);
 }
 
@@ -323,33 +328,37 @@
   // Forward slashes are converted to backslashes.
   SetEnvironmentVariableA("BAZEL_SH", "c:/foo\\bin/some_bash.exe");
   ResetMsysRootForTesting();
-  ASSERT_TRUE(AsWindowsPath("/blah", &actual));
+  ASSERT_TRUE(AsWindowsPath("/blah", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\foo\\blah"), actual);
 
   SetEnvironmentVariableA("BAZEL_SH", "c:\\foo/MSYS64/usr\\bin/dummy.exe");
   ResetMsysRootForTesting();
-  ASSERT_TRUE(AsWindowsPath("/blah", &actual));
+  ASSERT_TRUE(AsWindowsPath("/blah", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\foo\\MSYS64\\blah"), actual);
 
   // We just need "bin/<something>" or "usr/bin/<something>".
   SetEnvironmentVariableA("BAZEL_SH", "c:/bin/kitty.exe");
   ResetMsysRootForTesting();
-  ASSERT_TRUE(AsWindowsPath("/blah", &actual));
+  ASSERT_TRUE(AsWindowsPath("/blah", &actual, nullptr));
   ASSERT_EQ(wstring(L"c:\\blah"), actual);
 
   // Just having "msys" in the path isn't enough.
   SetEnvironmentVariableA("BAZEL_SH", "c:/msys/foo/bash.exe");
   ResetMsysRootForTesting();
-  ASSERT_FALSE(AsWindowsPath("/blah", &actual));
+  string error;
+  ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error));
+  EXPECT_TRUE(error.find("MSYS root is invalid") != string::npos);
 
   // We need "bin/<something>" or "usr/bin/<something>", not "usr/<something>".
   SetEnvironmentVariableA("BAZEL_SH", "c:/msys/usr/bash.exe");
   ResetMsysRootForTesting();
-  ASSERT_FALSE(AsWindowsPath("/blah", &actual));
+  ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error));
+  EXPECT_TRUE(error.find("MSYS root is invalid") != string::npos);
 
   SetEnvironmentVariableA("BAZEL_SH", "c:/qux.exe");
   ResetMsysRootForTesting();
-  ASSERT_FALSE(AsWindowsPath("/blah", &actual));
+  ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error));
+  EXPECT_TRUE(error.find("MSYS root is invalid") != string::npos);
 
   SetEnvironmentVariableA("BAZEL_SH", nullptr);
   ResetMsysRootForTesting();
@@ -538,7 +547,7 @@
   // Create a dummy file: $TEST_TMPDIR/directory/subdirectory/foo.txt
   string foo(JoinPath(dir2, "foo.txt"));
   wstring wfoo;
-  EXPECT_TRUE(AsAbsoluteWindowsPath(foo, &wfoo));
+  EXPECT_TRUE(AsAbsoluteWindowsPath(foo, &wfoo, nullptr));
   EXPECT_TRUE(CreateDummyFile(wfoo));
   EXPECT_TRUE(CanReadFile(foo));
   // Create junctions next to directory and subdirectory, pointing to them.
diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc
index dd01a39..573a996 100644
--- a/src/tools/launcher/util/launcher_util.cc
+++ b/src/tools/launcher/util/launcher_util.cc
@@ -74,8 +74,10 @@
 
 wstring AsAbsoluteWindowsPath(const char* path) {
   wstring wpath;
-  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) {
-    die("Couldn't convert %s to absoulte Windows path.", path);
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath, &error)) {
+    die("Couldn't convert %s to absolute Windows path: %s", path,
+        error.c_str());
   }
   return wpath;
 }
@@ -173,8 +175,9 @@
 }
 
 bool NormalizePath(const string& path, string* result) {
-  if (!blaze_util::AsWindowsPath(path, result)) {
-    PrintError("Failed to normalize %s", path.c_str());
+  string error;
+  if (!blaze_util::AsWindowsPath(path, result, &error)) {
+    PrintError("Failed to normalize %s: %s", path.c_str(), error.c_str());
     return false;
   }
   std::transform(result->begin(), result->end(), result->begin(), ::tolower);
diff --git a/third_party/ijar/mapped_file_windows.cc b/third_party/ijar/mapped_file_windows.cc
index b3327ed..91bff71 100644
--- a/third_party/ijar/mapped_file_windows.cc
+++ b/third_party/ijar/mapped_file_windows.cc
@@ -48,38 +48,40 @@
   errmsg_ = errmsg;
 
   wstring wname;
-  if (!blaze_util::AsAbsoluteWindowsPath(name, &wname)) {
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(name, &wname, &error)) {
     BAZEL_DIE(255) << "MappedInputFile(" << name
-                   << "): AsAbsoluteWindowsPath failed: "
-                   << blaze_util::GetLastErrorString();
+                   << "): AsAbsoluteWindowsPath failed: " << error;
   }
   HANDLE file = CreateFileW(wname.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
                             OPEN_EXISTING, 0, NULL);
   if (file == INVALID_HANDLE_VALUE) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedInputFile(" << name << "): CreateFileW("
                    << blaze_util::WstringToString(wname)
-                   << ") failed: " << blaze_util::GetLastErrorString();
+                   << ") failed: " << errormsg;
   }
 
   LARGE_INTEGER size;
   if (!GetFileSizeEx(file, &size)) {
-    BAZEL_DIE(255) << "MappedInputFile(" << name << "): GetFileSizeEx failed: "
-                   << blaze_util::GetLastErrorString();
+    string errormsg = blaze_util::GetLastErrorString();
+    BAZEL_DIE(255) << "MappedInputFile(" << name
+                   << "): GetFileSizeEx failed: " << errormsg;
   }
 
   HANDLE mapping = CreateFileMapping(file, NULL, PAGE_READONLY,
       size.HighPart, size.LowPart, NULL);
   if (mapping == NULL || mapping == INVALID_HANDLE_VALUE) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedInputFile(" << name
-                   << "): CreateFileMapping failed: "
-                   << blaze_util::GetLastErrorString();
+                   << "): CreateFileMapping failed: " << errormsg;
   }
 
   void *view = MapViewOfFileEx(mapping, FILE_MAP_READ, 0, 0, 0, NULL);
   if (view == NULL) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedInputFile(" << name
-                   << "): MapViewOfFileEx failed: "
-                   << blaze_util::GetLastErrorString();
+                   << "): MapViewOfFileEx failed: " << errormsg;
   }
 
   impl_ = new MappedInputFileImpl(file, mapping);
@@ -100,18 +102,21 @@
 
 int MappedInputFile::Close() {
   if (!UnmapViewOfFile(buffer_)) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedInputFile::Close: UnmapViewOfFile failed: "
-                   << blaze_util::GetLastErrorString();
+                   << errormsg;
   }
 
   if (!CloseHandle(impl_->mapping_)) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedInputFile::Close: CloseHandle for mapping failed: "
-                   << blaze_util::GetLastErrorString();
+                   << errormsg;
   }
 
   if (!CloseHandle(impl_->file_)) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedInputFile::Close: CloseHandle for file failed: "
-                   << blaze_util::GetLastErrorString();
+                   << errormsg;
   }
 
   return 0;
@@ -133,31 +138,32 @@
   errmsg_ = errmsg;
 
   wstring wname;
-  if (!blaze_util::AsAbsoluteWindowsPath(name, &wname)) {
+  string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(name, &wname, &error)) {
     BAZEL_DIE(255) << "MappedOutputFile(" << name
-                   << "): AsAbsoluteWindowsPath failed: "
-                   << blaze_util::GetLastErrorString();
+                   << "): AsAbsoluteWindowsPath failed: " << error;
   }
   HANDLE file = CreateFileW(wname.c_str(), GENERIC_READ | GENERIC_WRITE, 0,
                             NULL, CREATE_ALWAYS, 0, NULL);
   if (file == INVALID_HANDLE_VALUE) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedOutputFile(" << name << "): CreateFileW("
                    << blaze_util::WstringToString(wname)
-                   << ") failed: " << blaze_util::GetLastErrorString();
+                   << ") failed: " << errormsg;
   }
 
   HANDLE mapping = CreateFileMapping(file, NULL, PAGE_READWRITE,
       estimated_size >> 32, estimated_size & 0xffffffffUL, NULL);
   if (mapping == NULL || mapping == INVALID_HANDLE_VALUE) {
     BAZEL_DIE(255) << "MappedOutputFile(" << name
-                   << "): CreateFileMapping failed: ";
+                   << "): CreateFileMapping failed";
   }
 
   void *view = MapViewOfFileEx(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0, NULL);
   if (view == NULL) {
+    string errormsg = blaze_util::GetLastErrorString();
     BAZEL_DIE(255) << "MappedOutputFile(" << name
-                   << "): MapViewOfFileEx failed: "
-                   << blaze_util::GetLastErrorString();
+                   << "): MapViewOfFileEx failed: " << errormsg;
     CloseHandle(mapping);
     CloseHandle(file);
     return;
diff --git a/third_party/ijar/platform_utils.cc b/third_party/ijar/platform_utils.cc
index 9633eb7..eeb4c40 100644
--- a/third_party/ijar/platform_utils.cc
+++ b/third_party/ijar/platform_utils.cc
@@ -39,9 +39,10 @@
 bool stat_file(const char* path, Stat* result) {
 #if defined(COMPILER_MSVC) || defined(__CYGWIN__)
   std::wstring wpath;
-  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) {
+  std::string error;
+  if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath, &error)) {
     BAZEL_DIE(255) << "stat_file: AsAbsoluteWindowsPath(" << path
-                   << ") failed: " << blaze_util::GetLastErrorString();
+                   << ") failed: " << error;
   }
   bool success = false;
   BY_HANDLE_FILE_INFORMATION info;