Bazel client: mock out read/write calls

Make blaze::ReadFileDescriptor(int fd, ...) and
blaze::WriteFile(int fd, ...) platform-independent
by mocking out the read(2) and write(2) calls.

Also rename ReadFileDescriptor to ReadFrom and
introduce a new WriteTo method that encapsulates
WriteFile's prior logic.

In particular these functions now take a
read_func/write_func function argument instead of
a file descriptor, so the read(2)/write(2) calls
can be mocked out.

This allows us to use these functions on Windows
too, where read(2)/write(2) are not implemented,
and we can inject a different
read_func/write_func.

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

--
MOS_MIGRATED_REVID=140195973
diff --git a/src/main/cpp/blaze_util.cc b/src/main/cpp/blaze_util.cc
index cfa2b92..0ab9b76 100644
--- a/src/main/cpp/blaze_util.cc
+++ b/src/main/cpp/blaze_util.cc
@@ -77,14 +77,15 @@
   return cwd + separator + path;
 }
 
-bool ReadFileDescriptor(int fd, string *content, int max_size) {
+bool ReadFrom(const std::function<int(void *, int)> &read_func, string *content,
+              int max_size) {
   content->clear();
   char buf[4096];
   // OPT:  This loop generates one spurious read on regular files.
-  while (int r = read(fd, buf,
-                      max_size > 0
-                          ? std::min(max_size, static_cast<int>(sizeof buf))
-                          : sizeof buf)) {
+  while (int r = read_func(
+             buf, max_size > 0
+                      ? std::min(max_size, static_cast<int>(sizeof buf))
+                      : sizeof buf)) {
     if (r == -1) {
       if (errno == EINTR || errno == EAGAIN) continue;
       return false;
@@ -98,40 +99,49 @@
       }
     }
   }
-  close(fd);
   return true;
 }
 
 bool ReadFile(const string &filename, string *content, int max_size) {
   int fd = open(filename.c_str(), O_RDONLY);
   if (fd == -1) return false;
-  return ReadFileDescriptor(fd, content, max_size);
+  bool result =
+      ReadFrom([fd](void *buf, int len) { return read(fd, buf, len); }, content,
+               max_size);
+  close(fd);
+  return result;
 }
 
-// Writes 'content' into file 'filename', and makes it executable.
-// Returns false on failure, sets errno.
-bool WriteFile(const string &content, const string &filename) {
-  return WriteFile(content.data(), content.size(), filename);
-}
-
-bool WriteFile(const void *data, size_t size, const std::string &filename) {
+bool WriteFile(const void* data, size_t size, const string &filename) {
   UnlinkPath(filename);  // We don't care about the success of this.
   int fd = open(filename.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0755);  // chmod +x
   if (fd == -1) {
     return false;
   }
-  int r = write(fd, data, size);
-  if (r == -1) {
-    return false;
-  }
+  bool result = WriteTo(
+      [fd](const void *buf, size_t bufsize) { return write(fd, buf, bufsize); },
+      data, size);
   int saved_errno = errno;
   if (close(fd)) {
     return false;  // Can fail on NFS.
   }
   errno = saved_errno;  // Caller should see errno from write().
+  return result;
+}
+
+bool WriteTo(const std::function<int(const void *, size_t)> &write_func,
+             const void *data, size_t size) {
+  int r = write_func(data, size);
+  if (r == -1) {
+    return false;
+  }
   return static_cast<uint>(r) == size;
 }
 
+bool WriteFile(const std::string &content, const std::string &filename) {
+  return WriteFile(content.c_str(), content.size(), filename);
+}
+
 bool UnlinkPath(const string &file_path) {
   return unlink(file_path.c_str()) == 0;
 }
diff --git a/src/main/cpp/blaze_util.h b/src/main/cpp/blaze_util.h
index 06c75ed..cdef849 100644
--- a/src/main/cpp/blaze_util.h
+++ b/src/main/cpp/blaze_util.h
@@ -21,6 +21,7 @@
 
 #include <sys/types.h>
 
+#include <functional>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -52,11 +53,12 @@
 bool ReadFile(const std::string &filename, std::string *content,
               int max_size = 0);
 
-// Replaces 'content' with contents of file descriptor 'fd'.
-// If `max_size` is positive, the method reads at most that many bytes; if it
-// otherwise the method reads the whole file.
+// Replaces 'content' with data read from a source using `read_func`.
+// If `max_size` is positive, the method reads at most that many bytes;
+// otherwise the method reads everything.
 // Returns false on error. Can be called from a signal handler.
-bool ReadFileDescriptor(int fd, std::string *content, int max_size = 0);
+bool ReadFrom(const std::function<int(void *, int)> &read_func,
+              std::string *content, int max_size = 0);
 
 // Writes 'content' into file 'filename', and makes it executable.
 // Returns false on failure, sets errno.
@@ -66,6 +68,11 @@
 // Returns false on failure, sets errno.
 bool WriteFile(const void* data, size_t size, const std::string &filename);
 
+// Writes `size` bytes from `data` into a destination using `write_func`.
+// Returns false on failure, sets errno.
+bool WriteTo(const std::function<int(const void *, size_t)> &write_func,
+             const void *data, size_t size);
+
 // Unlinks the file given by 'file_path'.
 // Returns true on success. In case of failure sets errno.
 bool UnlinkPath(const std::string &file_path);
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index ed0d9b3..5fdf3dd 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -261,23 +261,30 @@
   if (pipe(fds)) {
     pdie(blaze_exit_code::INTERNAL_ERROR, "pipe creation failed");
   }
+  int recv_socket = fds[0];
+  int send_socket = fds[1];
 
   int child = fork();
   if (child == -1) {
     pdie(blaze_exit_code::INTERNAL_ERROR, "fork() failed");
   } else if (child > 0) {  // we're the parent
-    close(fds[1]);         // parent keeps only the reading side
+    close(send_socket);    // parent keeps only the reading side
     string result;
-    if (!ReadFileDescriptor(fds[0], &result)) {
+    bool success = ReadFrom(
+        [recv_socket](void* buf, int size) {
+          return read(recv_socket, buf, size);
+        },
+        &result);
+    close(recv_socket);
+    if (!success) {
       pdie(blaze_exit_code::INTERNAL_ERROR, "Cannot read subprocess output");
     }
-
     return result;
-  } else {          // We're the child
-    close(fds[0]);  // child keeps only the writing side
+  } else {                 // We're the child
+    close(recv_socket);    // child keeps only the writing side
     // Redirect output to the writing side of the dup.
-    dup2(fds[1], STDOUT_FILENO);
-    dup2(fds[1], STDERR_FILENO);
+    dup2(send_socket, STDOUT_FILENO);
+    dup2(send_socket, STDERR_FILENO);
     // Execute the binary
     ExecuteProgram(exe, args_vector);
     pdie(blaze_exit_code::INTERNAL_ERROR, "Failed to run %s", exe.c_str());
diff --git a/src/test/cpp/blaze_util_test.cc b/src/test/cpp/blaze_util_test.cc
index bb0faf0..9199ec3 100644
--- a/src/test/cpp/blaze_util_test.cc
+++ b/src/test/cpp/blaze_util_test.cc
@@ -91,14 +91,17 @@
     return fds[0];
   }
 
-  static void AssertReadFileDescriptor2(string input1, string input2) {
+  static void AssertReadFrom2(string input1, string input2) {
     int fd = WriteFileDescriptor2(input1, input2);
     if (fd < 0) {
       FAIL() << "Unable to create a pipe!";
     } else {
       string result;
-      if (!ReadFileDescriptor(fd, &result)) {
-        perror("ReadFileDescriptor");
+      bool success = ReadFrom(
+          [fd](void* buf, int size) { return read(fd, buf, size); }, &result);
+      close(fd);
+      if (!success) {
+        perror("ReadFrom");
         FAIL() << "Unable to read file descriptor!";
       } else {
         ASSERT_EQ(input1 + input2, result);
@@ -106,22 +109,21 @@
     }
   }
 
-  static void AssertReadFileDescriptor(string input) {
-    AssertReadFileDescriptor2(input, "");
-  }
+  static void AssertReadFrom(string input) { AssertReadFrom2(input, ""); }
 
   static void AssertReadJvmVersion(string expected, string input) {
     ASSERT_EQ(expected, ReadJvmVersion(input));
   }
 
-  void ReadFileDescriptorTest() const {
-    AssertReadFileDescriptor("DummyJDK Blabla\n"
-                             "More DummyJDK Blabla\n");
-    AssertReadFileDescriptor("dummyjdk version \"1.42.qual\"\n"
-                         "DummyJDK Blabla\n"
-                             "More DummyJDK Blabla\n");
-    AssertReadFileDescriptor2("first_line\n",
-                              "second line version \"1.4.2_0\"\n");
+  void ReadFromTest() const {
+    AssertReadFrom(
+        "DummyJDK Blabla\n"
+        "More DummyJDK Blabla\n");
+    AssertReadFrom(
+        "dummyjdk version \"1.42.qual\"\n"
+        "DummyJDK Blabla\n"
+        "More DummyJDK Blabla\n");
+    AssertReadFrom2("first_line\n", "second line version \"1.4.2_0\"\n");
   }
 
   void ReadJvmVersionTest() const {
@@ -168,9 +170,7 @@
   CheckJavaVersionIsAtLeastTest();
 }
 
-TEST_F(BlazeUtilTest, ReadFileDescriptor) {
-  ReadFileDescriptorTest();
-}
+TEST_F(BlazeUtilTest, ReadFrom) { ReadFromTest(); }
 
 TEST_F(BlazeUtilTest, ReadJvmVersion) {
   ReadJvmVersionTest();