Print error message if cannot open `.blazerc` file

PiperOrigin-RevId: 474406363
Change-Id: Ice887df811751a6840b9f30465ee6db3ffdd2208
diff --git a/src/main/cpp/rc_file.cc b/src/main/cpp/rc_file.cc
index 266cc13..912bfa4 100644
--- a/src/main/cpp/rc_file.cc
+++ b/src/main/cpp/rc_file.cc
@@ -55,9 +55,11 @@
                                      string* error_text) {
   BAZEL_LOG(INFO) << "Parsing the RcFile " << filename;
   string contents;
-  if (!blaze_util::ReadFile(filename, &contents)) {
+  string error_message;
+  if (!blaze_util::ReadFile(filename, &contents, &error_message)) {
     blaze_util::StringPrintf(error_text,
-        "Unexpected error reading .blazerc file '%s'", filename.c_str());
+        "Unexpected error reading .blazerc file '%s': %s",
+        filename.c_str(), error_message.c_str());
     return ParseError::UNREADABLE_FILE;
   }
   const std::string canonical_filename =
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index 4e8e254..a0860de 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -90,8 +90,12 @@
 // Replaces 'content' with contents of file 'filename'.
 // If `max_size` is positive, the method reads at most that many bytes;
 // otherwise the method reads the whole file.
+// If fails to read content from the file, `error_message` can provide extra
+// information about the failure.
 // Returns false on error. Can be called from a signal handler.
 bool ReadFile(const std::string &filename, std::string *content,
+              std::string *error_message, int max_size = 0);
+bool ReadFile(const std::string &filename, std::string *content,
               int max_size = 0);
 bool ReadFile(const Path &path, std::string *content, int max_size = 0);
 
diff --git a/src/main/cpp/util/file_posix.cc b/src/main/cpp/util/file_posix.cc
index d89c34f..8b1d541 100644
--- a/src/main/cpp/util/file_posix.cc
+++ b/src/main/cpp/util/file_posix.cc
@@ -265,16 +265,27 @@
   return result;
 }
 
-bool ReadFile(const string &filename, string *content, int max_size) {
+bool ReadFile(const string &filename, string *content, string *error_message,
+              int max_size) {
   int fd = open(filename.c_str(), O_RDONLY);
-  if (fd == -1) return false;
+  if (fd == -1) {
+    if (error_message != nullptr) {
+      *error_message = blaze_util::GetLastErrorString();
+    }
+    return false;
+  }
   bool result = ReadFrom(fd, content, max_size);
   close(fd);
   return result;
 }
 
+bool ReadFile(const string &filename, string *content, int max_size) {
+  return ReadFile(filename, content, /* error_message= */nullptr, max_size);
+}
+
 bool ReadFile(const Path &path, std::string *content, int max_size) {
-  return ReadFile(path.AsNativePath(), content, max_size);
+  return ReadFile(
+    path.AsNativePath(), content, /* error_message= */nullptr, max_size);
 }
 
 bool ReadFile(const string &filename, void *data, size_t size) {
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index 5798ffa..e49a1a8 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -254,6 +254,11 @@
 }
 
 bool ReadFile(const string& filename, string* content, int max_size) {
+  return ReadFile(filename, content, nullptr, max_size);
+}
+
+bool ReadFile(const string& filename, string* content, string* error_message,
+              int max_size) {
   if (IsDevNull(filename.c_str())) {
     // mimic read(2) behavior: we can always read 0 bytes from /dev/null
     content->clear();
@@ -265,8 +270,11 @@
   std::string errorText;
   Path path = Path(filename, &errorText);
   if (!errorText.empty()) {
-    BAZEL_LOG(WARNING) << "Path is not valid: " << filename << " :"
-        << errorText;
+    std::string message = "Path is not valid: " + filename + " :" + errorText;
+    BAZEL_LOG(WARNING) << message;
+    if (error_message != nullptr) {
+      *error_message = std::move(message);
+    }
     return false;
   }
   return ReadFile(path, content, max_size);
diff --git a/src/test/cpp/rc_options_test.cc b/src/test/cpp/rc_options_test.cc
index 354ed87..44bf26e 100644
--- a/src/test/cpp/rc_options_test.cc
+++ b/src/test/cpp/rc_options_test.cc
@@ -31,6 +31,12 @@
 using std::vector;
 using ::testing::MatchesRegex;
 
+#if _WIN32
+constexpr bool kIsWindows = true;
+#else
+constexpr bool kIsWindows = false;
+#endif
+
 class RcOptionsTest : public ::testing::Test {
  protected:
   RcOptionsTest()
@@ -400,8 +406,10 @@
   EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
   ASSERT_THAT(
       error_text,
-      MatchesRegex(
-          "Unexpected error reading .blazerc file '.*not_a_file.bazelrc'"));
+      MatchesRegex(kIsWindows
+        ? "Unexpected error reading \\.blazerc file '.*not_a_file\\.bazelrc':.*"
+        : "Unexpected error reading \\.blazerc file '.*not_a_file\\.bazelrc': "
+          "\\(error: 2\\): No such file or directory"));
 }
 
 TEST_F(RcOptionsTest, ImportedFileDoesNotExist) {
@@ -413,7 +421,14 @@
   std::unique_ptr<RcFile> rc =
       Parse("import_fake_file.bazelrc", &error, &error_text);
   EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
-  ASSERT_EQ(error_text, "Unexpected error reading .blazerc file 'somefile'");
+  if (kIsWindows) {
+    ASSERT_THAT(error_text, MatchesRegex(
+      "Unexpected error reading \\.blazerc file 'somefile':.*"));
+  } else {
+    ASSERT_EQ(error_text,
+      "Unexpected error reading .blazerc file 'somefile': (error: 2): No such "
+      "file or directory");
+  }
 }
 
 TEST_F(RcOptionsTest, TryImportedFileDoesNotExist) {