Bazel client: reduce dependency on POSIX API

We can now compile blaze_util_windows.cc with
MSVC, yay! (when building //src:bazel
--cpu=x64_windows_msvc -k).

There are a lot of #ifdef's and TODOs so this
is a modest victory for now.

In this change:

- change blaze::MakeDirectories to return bool
instead of int, since that's how it was used
anyway, and to expect the permission mask as
unsigned int instead of mode_t, since the
former is good enough and compatible with
mode_t on POSIX while mode_t is not defined on
Windows

- move blaze::MakeDirectories into
blaze_util_<platform>

- implement envvar-handling in
blaze_util_<platform> and use it everywhere

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

--
MOS_MIGRATED_REVID=139887503
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 9633565..abb16ec 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -721,7 +721,7 @@
 
   // The server dir has the socket, so we don't allow access by other
   // users.
-  if (MakeDirectories(server_dir, 0700) == -1) {
+  if (!MakeDirectories(server_dir, 0700)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
          "server directory '%s' could not be created", server_dir.c_str());
   }
@@ -797,7 +797,7 @@
   virtual void Process(const char *filename, const devtools_ijar::u4 attr,
                        const devtools_ijar::u1 *data, const size_t size) {
     string path = blaze_util::JoinPath(embedded_binaries_, filename);
-    if (MakeDirectories(blaze_util::Dirname(path), 0777) == -1) {
+    if (!MakeDirectories(blaze_util::Dirname(path), 0777)) {
       pdie(blaze_exit_code::INTERNAL_ERROR,
            "couldn't create '%s'", path.c_str());
     }
@@ -818,7 +818,7 @@
 static void ActuallyExtractData(const string &argv0,
                                 const string &embedded_binaries) {
   ExtractBlazeZipProcessor processor(embedded_binaries);
-  if (MakeDirectories(embedded_binaries, 0777) == -1) {
+  if (!MakeDirectories(embedded_binaries, 0777)) {
     pdie(blaze_exit_code::INTERNAL_ERROR, "couldn't create '%s'",
          embedded_binaries.c_str());
   }
@@ -1254,7 +1254,7 @@
 
   const char *output_base = globals->options->output_base.c_str();
   if (!blaze_util::PathExists(globals->options->output_base)) {
-    if (MakeDirectories(globals->options->output_base, 0777) == -1) {
+    if (!MakeDirectories(globals->options->output_base, 0777)) {
       pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
            "Output base directory '%s' could not be created",
            output_base);
@@ -1280,32 +1280,32 @@
 }
 
 static void CheckEnvironment() {
-  if (getenv("http_proxy") != NULL) {
+  if (!blaze::GetEnv("http_proxy").empty()) {
     fprintf(stderr, "Warning: ignoring http_proxy in environment.\n");
-    unsetenv("http_proxy");
+    blaze::UnsetEnv("http_proxy");
   }
 
-  if (getenv("LD_ASSUME_KERNEL") != NULL) {
+  if (!blaze::GetEnv("LD_ASSUME_KERNEL").empty()) {
     // Fix for bug: if ulimit -s and LD_ASSUME_KERNEL are both
     // specified, the JVM fails to create threads.  See thread_stack_regtest.
     // This is also provoked by LD_LIBRARY_PATH=/usr/lib/debug,
     // or anything else that causes the JVM to use LinuxThreads.
     fprintf(stderr, "Warning: ignoring LD_ASSUME_KERNEL in environment.\n");
-    unsetenv("LD_ASSUME_KERNEL");
+    blaze::UnsetEnv("LD_ASSUME_KERNEL");
   }
 
-  if (getenv("LD_PRELOAD") != NULL) {
+  if (!blaze::GetEnv("LD_PRELOAD").empty()) {
     fprintf(stderr, "Warning: ignoring LD_PRELOAD in environment.\n");
-    unsetenv("LD_PRELOAD");
+    blaze::UnsetEnv("LD_PRELOAD");
   }
 
-  if (getenv("_JAVA_OPTIONS") != NULL) {
+  if (!blaze::GetEnv("_JAVA_OPTIONS").empty()) {
     // This would override --host_jvm_args
     fprintf(stderr, "Warning: ignoring _JAVA_OPTIONS in environment.\n");
-    unsetenv("_JAVA_OPTIONS");
+    blaze::UnsetEnv("_JAVA_OPTIONS");
   }
 
-  if (getenv("TEST_TMPDIR") != NULL) {
+  if (!blaze::GetEnv("TEST_TMPDIR").empty()) {
     fprintf(stderr, "INFO: $TEST_TMPDIR defined: output root default is "
                     "'%s'.\n", globals->options->output_root.c_str());
   }
@@ -1316,10 +1316,10 @@
   // Make the JVM use ISO-8859-1 for parsing its command line because "blaze
   // run" doesn't handle non-ASCII command line arguments. This is apparently
   // the most reliable way to select the platform default encoding.
-  setenv("LANG", "en_US.ISO-8859-1", 1);
-  setenv("LANGUAGE", "en_US.ISO-8859-1", 1);
-  setenv("LC_ALL", "en_US.ISO-8859-1", 1);
-  setenv("LC_CTYPE", "en_US.ISO-8859-1", 1);
+  blaze::SetEnv("LANG", "en_US.ISO-8859-1");
+  blaze::SetEnv("LANGUAGE", "en_US.ISO-8859-1");
+  blaze::SetEnv("LC_ALL", "en_US.ISO-8859-1");
+  blaze::SetEnv("LC_CTYPE", "en_US.ISO-8859-1");
 }
 
 static void SetupStreams() {
diff --git a/src/main/cpp/blaze_util.cc b/src/main/cpp/blaze_util.cc
index b7aa84a..9e1bd2d 100644
--- a/src/main/cpp/blaze_util.cc
+++ b/src/main/cpp/blaze_util.cc
@@ -51,8 +51,10 @@
 const char kServerPidSymlink[] = "server.pid";
 
 string GetUserName() {
-  const char *user = getenv("USER");
-  if (user && user[0] != '\0') return user;
+  string user = GetEnv("USER");
+  if (!user.empty()) {
+    return user;
+  }
   errno = 0;
   passwd *pwent = getpwuid(getuid());  // NOLINT (single-threaded)
   if (pwent == NULL || pwent->pw_name == NULL) {
@@ -75,100 +77,6 @@
   return cwd + separator + path;
 }
 
-// Runs "stat" on `path`. Returns -1 and sets errno if stat fails or
-// `path` isn't a directory. If check_perms is true, this will also
-// make sure that `path` is owned by the current user and has `mode`
-// permissions (observing the umask). It attempts to run chmod to
-// correct the mode if necessary. If `path` is a symlink, this will
-// check ownership of the link, not the underlying directory.
-static int GetDirectoryStat(const string& path, mode_t mode, bool check_perms) {
-  struct stat filestat = {};
-  if (stat(path.c_str(), &filestat) == -1) {
-    return -1;
-  }
-
-  if (!S_ISDIR(filestat.st_mode)) {
-    errno = ENOTDIR;
-    return -1;
-  }
-
-  if (check_perms) {
-    // If this is a symlink, run checks on the link. (If we did lstat above
-    // then it would return false for ISDIR).
-    struct stat linkstat = {};
-    if (lstat(path.c_str(), &linkstat) != 0) {
-      return -1;
-    }
-    if (linkstat.st_uid != geteuid()) {
-      // The directory isn't owned by me.
-      errno = EACCES;
-      return -1;
-    }
-
-    mode_t mask = umask(022);
-    umask(mask);
-    mode = (mode & ~mask);
-    if ((filestat.st_mode & 0777) != mode
-        && chmod(path.c_str(), mode) == -1) {
-      // errno set by chmod.
-      return -1;
-    }
-  }
-  return 0;
-}
-
-static int MakeDirectories(const string& path, mode_t mode, bool childmost) {
-  if (path.empty() || path == "/") {
-    errno = EACCES;
-    return -1;
-  }
-
-  int retval = GetDirectoryStat(path, mode, childmost);
-  if (retval == 0) {
-    return 0;
-  }
-
-  if (errno == ENOENT) {
-    // Path does not exist, attempt to create its parents, then it.
-    string parent = blaze_util::Dirname(path);
-    if (MakeDirectories(parent, mode, false) == -1) {
-      // errno set by stat.
-      return -1;
-    }
-
-    if (mkdir(path.c_str(), mode) == -1) {
-      if (errno == EEXIST) {
-        if (childmost) {
-          // If there are multiple bazel calls at the same time then the
-          // directory could be created between the MakeDirectories and mkdir
-          // calls. This is okay, but we still have to check the permissions.
-          return GetDirectoryStat(path, mode, childmost);
-        } else {
-          // If this isn't the childmost directory, we don't care what the
-          // permissions were. If it's not even a directory then that error will
-          // get caught when we attempt to create the next directory down the
-          // chain.
-          return 0;
-        }
-      }
-      // errno set by mkdir.
-      return -1;
-    }
-    return 0;
-  }
-
-  return retval;
-}
-
-// mkdir -p path. Returns 0 if the path was created or already exists and could
-// be chmod-ed to exactly the given permissions. If final part of the path is a
-// symlink, this ensures that the destination of the symlink has the desired
-// permissions. It also checks that the directory or symlink is owned by us.
-// On failure, this returns -1 and sets errno.
-int MakeDirectories(const string& path, mode_t mode) {
-  return MakeDirectories(path, mode, true);
-}
-
 // Replaces 'contents' with contents of 'fd' file descriptor.
 // If `max_size` is positive, the method reads at most that many bytes; if it
 // is 0, the method reads the whole file.
@@ -185,7 +93,7 @@
     }
     content->append(buf, r);
     if (max_size > 0) {
-      if (max_size > r) {
+      if (max_size > static_cast<size_t>(r)) {
         max_size -= r;
       } else {
         break;
@@ -235,14 +143,13 @@
 }
 
 bool IsEmacsTerminal() {
-  string emacs = getenv("EMACS") == nullptr ? "" : getenv("EMACS");
-  string inside_emacs =
-      getenv("INSIDE_EMACS") == nullptr ? "" : getenv("INSIDE_EMACS");
+  string emacs = GetEnv("EMACS");
+  string inside_emacs = GetEnv("INSIDE_EMACS");
   // GNU Emacs <25.1 (and ~all non-GNU emacsen) set EMACS=t, but >=25.1 doesn't
   // do that and instead sets INSIDE_EMACS=<stuff> (where <stuff> can look like
   // e.g. "25.1.1,comint").  So we check both variables for maximum
   // compatibility.
-  return emacs == "t" || inside_emacs != "";
+  return emacs == "t" || !inside_emacs.empty();
 }
 
 // Returns true iff both stdout and stderr are connected to a
@@ -250,9 +157,10 @@
 // (this is computed heuristically based on the values of
 // environment variables).
 bool IsStandardTerminal() {
-  string term = getenv("TERM") == nullptr ? "" : getenv("TERM");
-  if (term == "" || term == "dumb" || term == "emacs" || term == "xterm-mono" ||
-      term == "symbolics" || term == "9term" || IsEmacsTerminal()) {
+  string term = GetEnv("TERM");
+  if (term.empty() || term == "dumb" || term == "emacs" ||
+      term == "xterm-mono" || term == "symbolics" || term == "9term" ||
+      IsEmacsTerminal()) {
     return false;
   }
   return isatty(STDOUT_FILENO) && isatty(STDERR_FILENO);
@@ -265,10 +173,10 @@
   if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
     return ws.ws_col;
   }
-  const char* columns_env = getenv("COLUMNS");
-  if (columns_env != NULL && columns_env[0] != '\0') {
+  string columns_env = GetEnv("COLUMNS");
+  if (!columns_env.empty()) {
     char* endptr;
-    int columns = blaze_util::strto32(columns_env, &endptr, 10);
+    int columns = blaze_util::strto32(columns_env.c_str(), &endptr, 10);
     if (*endptr == '\0') {  // $COLUMNS is a valid number
       return columns;
     }
@@ -305,9 +213,7 @@
   return true;
 }
 
-bool VerboseLogging() {
-  return getenv("VERBOSE_BLAZE_CLIENT") != NULL;
-}
+bool VerboseLogging() { return !GetEnv("VERBOSE_BLAZE_CLIENT").empty(); }
 
 // Read the Jvm version from a file descriptor. The read fd
 // should contains a similar output as the java -version output.
diff --git a/src/main/cpp/blaze_util.h b/src/main/cpp/blaze_util.h
index 666b06e..7241682 100644
--- a/src/main/cpp/blaze_util.h
+++ b/src/main/cpp/blaze_util.h
@@ -41,10 +41,6 @@
 //   MakeAbsolute("C:/foo") ---> "C:/foo"
 std::string MakeAbsolute(const std::string &path);
 
-// mkdir -p path. All newly created directories use the given mode.
-// Returns -1 on failure, sets errno.
-int MakeDirectories(const std::string &path, mode_t mode);
-
 // Replaces 'content' with contents of file 'filename'.
 // If `max_size` is positive, the method reads at most that many bytes; if it
 // is 0, the method reads the whole file.
diff --git a/src/main/cpp/blaze_util_darwin.cc b/src/main/cpp/blaze_util_darwin.cc
index c4a35c0..63bfcec 100644
--- a/src/main/cpp/blaze_util_darwin.cc
+++ b/src/main/cpp/blaze_util_darwin.cc
@@ -153,9 +153,9 @@
 }
 
 string GetDefaultHostJavabase() {
-  const char *java_home = getenv("JAVA_HOME");
-  if (java_home) {
-    return std::string(java_home);
+  string java_home = GetEnv("JAVA_HOME");
+  if (!java_home.empty()) {
+    return java_home;
   }
 
   FILE *output = popen("/usr/libexec/java_home -v 1.7+", "r");
diff --git a/src/main/cpp/blaze_util_freebsd.cc b/src/main/cpp/blaze_util_freebsd.cc
index c4ac11a..f1177b21 100644
--- a/src/main/cpp/blaze_util_freebsd.cc
+++ b/src/main/cpp/blaze_util_freebsd.cc
@@ -145,11 +145,8 @@
 
 string GetDefaultHostJavabase() {
   // if JAVA_HOME is defined, then use it as default.
-  const char *javahome = getenv("JAVA_HOME");
-  if (javahome != NULL) {
-    return string(javahome);
-  }
-  return "/usr/local/openjdk8";
+  string javahome = getenv("JAVA_HOME");
+  return !javahome.empty() ? javahome : "/usr/local/openjdk8";
 }
 
 void WriteSystemSpecificProcessIdentifier(const string& server_dir) {
diff --git a/src/main/cpp/blaze_util_platform.h b/src/main/cpp/blaze_util_platform.h
index 6d68e73..a4c9c20 100644
--- a/src/main/cpp/blaze_util_platform.h
+++ b/src/main/cpp/blaze_util_platform.h
@@ -148,6 +148,17 @@
 // user, and not accessible to anyone else.
 void CreateSecureOutputRoot(const std::string& path);
 
+// mkdir -p path. All newly created directories use the given mode.
+// `mode` should be an octal permission mask, e.g. 0755
+// Returns false on failure, sets errno.
+bool MakeDirectories(const std::string &path, unsigned int mode);
+
+std::string GetEnv(const std::string& name);
+
+void SetEnv(const std::string& name, const std::string& value);
+
+void UnsetEnv(const std::string& name);
+
 }  // namespace blaze
 
 #endif  // BAZEL_SRC_MAIN_CPP_BLAZE_UTIL_PLATFORM_H_
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index b70d95c..55cb59b 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -236,7 +236,7 @@
   const char* root = path.c_str();
   struct stat fileinfo = {};
 
-  if (MakeDirectories(root, 0755) == -1) {
+  if (!MakeDirectories(root, 0755)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "mkdir('%s')", root);
   }
 
@@ -273,4 +273,112 @@
   ExcludePathFromBackup(root);
 }
 
+// Runs "stat" on `path`. Returns -1 and sets errno if stat fails or
+// `path` isn't a directory. If check_perms is true, this will also
+// make sure that `path` is owned by the current user and has `mode`
+// permissions (observing the umask). It attempts to run chmod to
+// correct the mode if necessary. If `path` is a symlink, this will
+// check ownership of the link, not the underlying directory.
+static bool GetDirectoryStat(const string& path, mode_t mode,
+                             bool check_perms) {
+  struct stat filestat = {};
+  if (stat(path.c_str(), &filestat) == -1) {
+    return false;
+  }
+
+  if (!S_ISDIR(filestat.st_mode)) {
+    errno = ENOTDIR;
+    return false;
+  }
+
+  if (check_perms) {
+    // If this is a symlink, run checks on the link. (If we did lstat above
+    // then it would return false for ISDIR).
+    struct stat linkstat = {};
+    if (lstat(path.c_str(), &linkstat) != 0) {
+      return false;
+    }
+    if (linkstat.st_uid != geteuid()) {
+      // The directory isn't owned by me.
+      errno = EACCES;
+      return false;
+    }
+
+    mode_t mask = umask(022);
+    umask(mask);
+    mode = (mode & ~mask);
+    if ((filestat.st_mode & 0777) != mode
+        && chmod(path.c_str(), mode) == -1) {
+      // errno set by chmod.
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool MakeDirectories(const string& path, mode_t mode, bool childmost) {
+  if (path.empty() || path == "/") {
+    errno = EACCES;
+    return false;
+  }
+
+  bool stat_succeeded = GetDirectoryStat(path, mode, childmost);
+  if (stat_succeeded) {
+    return true;
+  }
+
+  if (errno == ENOENT) {
+    // Path does not exist, attempt to create its parents, then it.
+    string parent = blaze_util::Dirname(path);
+    if (!MakeDirectories(parent, mode, false)) {
+      // errno set by stat.
+      return false;
+    }
+
+    if (mkdir(path.c_str(), mode) == -1) {
+      if (errno == EEXIST) {
+        if (childmost) {
+          // If there are multiple bazel calls at the same time then the
+          // directory could be created between the MakeDirectories and mkdir
+          // calls. This is okay, but we still have to check the permissions.
+          return GetDirectoryStat(path, mode, childmost);
+        } else {
+          // If this isn't the childmost directory, we don't care what the
+          // permissions were. If it's not even a directory then that error will
+          // get caught when we attempt to create the next directory down the
+          // chain.
+          return true;
+        }
+      }
+      // errno set by mkdir.
+      return false;
+    }
+    return true;
+  }
+
+  return stat_succeeded;
+}
+
+// mkdir -p path. Returns 0 if the path was created or already exists and could
+// be chmod-ed to exactly the given permissions. If final part of the path is a
+// symlink, this ensures that the destination of the symlink has the desired
+// permissions. It also checks that the directory or symlink is owned by us.
+// On failure, this returns -1 and sets errno.
+bool MakeDirectories(const string& path, unsigned int mode) {
+  return MakeDirectories(path, mode, true);
+}
+
+string GetEnv(const string& name) {
+  char* result = getenv(name.c_str());
+  return result != NULL ? string(result) : "";
+}
+
+void SetEnv(const string& name, const string& value) {
+  setenv(name.c_str(), value.c_str(), 1);
+}
+
+void UnsetEnv(const string& name) {
+  unsetenv(name.c_str());
+}
+
 }   // namespace blaze.
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index 2905673..87406f9 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -14,7 +14,6 @@
 
 #include <errno.h>  // errno, ENAMETOOLONG
 #include <limits.h>
-#include <string.h>  // strerror
 
 #ifndef COMPILER_MSVC
 #include <sys/cygwin.h>
@@ -100,6 +99,9 @@
 }
 
 string GetSelfPath() {
+#ifdef COMPILER_MSVC
+  const size_t PATH_MAX = 4096;
+#endif  // COMPILER_MSVC
   char buffer[PATH_MAX] = {};
   if (!GetModuleFileName(0, buffer, sizeof(buffer))) {
     pdie(255, "Error %u getting executable file name\n", GetLastError());
@@ -153,6 +155,11 @@
 }
 
 string GetProcessCWD(int pid) {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and
+  // implement or delete.
+  return "";
+#else   // not COMPILER_MSVC
   char server_cwd[PATH_MAX] = {};
   if (readlink(
           ("/proc/" + ToString(pid) + "/cwd").c_str(),
@@ -161,6 +168,7 @@
   }
 
   return string(server_cwd);
+#endif  // COMPILER_MSVC
 }
 
 bool IsSharedLibrary(const string &filename) {
@@ -340,6 +348,10 @@
 // So, we first pretend to be a POSIX daemon so that msys2 knows about our
 // intentions and *then* we call CreateProcess(). Life ain't easy.
 static bool DaemonizeOnWindows() {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team) 2016-11-18: implement this.
+  return false;
+#else  // not COMPILER_MSVC
   if (fork() > 0) {
     // We are the original client process.
     return true;
@@ -356,6 +368,7 @@
   // descriptors here. CreateProcess() will take care of that and it's useful
   // to see the error messages in ExecuteDaemon() on the console of the client.
   return false;
+#endif  // COMPILER_MSVC
 }
 
 // Keeping an eye on the server process on Windows is not implemented yet.
@@ -463,6 +476,9 @@
   WaitForSingleObject(java_handle, INFINITE);
 }
 
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team): implement signal handling.
+#else  // not COMPILER_MSVC
 static void MingwSignalHandler(int signum) {
   // Java process will be terminated because we set the job to terminate if its
   // handle is closed.
@@ -476,6 +492,7 @@
   // allow breakaway processes.
   exit(blaze_exit_code::ExitCode::INTERRUPTED);
 }
+#endif  // COMPILER_MSVC
 
 // Returns whether assigning the given process to a job failed because nested
 // jobs are not available on the current system.
@@ -570,7 +587,12 @@
 
   // msys doesn't deliver signals while a Win32 call is pending so we need to
   // do the blocking call in another thread
+
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team): implement signal handling.
+#else  // not COMPILER_MSVC
   signal(SIGINT, MingwSignalHandler);
+#endif  // COMPILER_MSVC
   std::thread batch_waiter_thread([=]() {
     BatchWaiterThread(processInfo.hProcess);
   });
@@ -587,6 +609,11 @@
 string ListSeparator() { return ";"; }
 
 string ConvertPath(const string& path) {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team): implement this.
+  pdie(255, "blaze::ConvertPath is not implemented on Windows");
+  return "";
+#else  // not COMPILER_MSVC
   // If the path looks like %USERPROFILE%/foo/bar, don't convert.
   if (path.empty() || path[0] == '%') {
     return path;
@@ -596,6 +623,7 @@
   string result(wpath);
   free(wpath);
   return result;
+#endif  // COMPILER_MSVC
 }
 
 // Convert a Unix path list to Windows path list
@@ -613,12 +641,18 @@
   return w_list;
 }
 
-string ConvertPathToPosix(const string& win_path) {
+static string ConvertPathToPosix(const string& win_path) {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team) 2016-11-18: verify that this function is not needed on
+  // Windows.
+  return win_path;
+#else   // not COMPILER_MSVC
   char* posix_path = static_cast<char*>(cygwin_create_path(
       CCP_WIN_A_TO_POSIX, static_cast<const void*>(win_path.c_str())));
   string result(posix_path);
   free(posix_path);
   return result;
+#endif  // COMPILER_MSVC
 }
 
 // Cribbed from ntifs.h, not present in windows.h
@@ -867,7 +901,7 @@
   const char* root = path.c_str();
   struct stat fileinfo = {};
 
-  if (MakeDirectories(root, 0755) == -1) {
+  if (!MakeDirectories(root, 0755)) {
     pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "mkdir('%s')", root);
   }
 
@@ -905,6 +939,137 @@
 #endif  // COMPILER_MSVC
 }
 
+#ifdef COMPILER_MSVC
+bool MakeDirectories(const string& path, unsigned int mode) {
+  // TODO(bazel-team): implement this.
+  pdie(255, "blaze::MakeDirectories is not implemented on Windows");
+  return false;
+}
+#else   // not COMPILER_MSVC
+// Runs "stat" on `path`. Returns -1 and sets errno if stat fails or
+// `path` isn't a directory. If check_perms is true, this will also
+// make sure that `path` is owned by the current user and has `mode`
+// permissions (observing the umask). It attempts to run chmod to
+// correct the mode if necessary. If `path` is a symlink, this will
+// check ownership of the link, not the underlying directory.
+static bool GetDirectoryStat(const string& path, mode_t mode,
+                             bool check_perms) {
+  struct stat filestat = {};
+  if (stat(path.c_str(), &filestat) == -1) {
+    return false;
+  }
+
+  if (!S_ISDIR(filestat.st_mode)) {
+    errno = ENOTDIR;
+    return false;
+  }
+
+  if (check_perms) {
+    // If this is a symlink, run checks on the link. (If we did lstat above
+    // then it would return false for ISDIR).
+    struct stat linkstat = {};
+    if (lstat(path.c_str(), &linkstat) != 0) {
+      return false;
+    }
+    if (linkstat.st_uid != geteuid()) {
+      // The directory isn't owned by me.
+      errno = EACCES;
+      return false;
+    }
+
+    mode_t mask = umask(022);
+    umask(mask);
+    mode = (mode & ~mask);
+    if ((filestat.st_mode & 0777) != mode && chmod(path.c_str(), mode) == -1) {
+      // errno set by chmod.
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool MakeDirectories(const string& path, mode_t mode, bool childmost) {
+  if (path.empty() || path == "/") {
+    errno = EACCES;
+    return false;
+  }
+
+  bool stat_succeeded = GetDirectoryStat(path, mode, childmost);
+  if (stat_succeeded) {
+    return true;
+  }
+
+  if (errno == ENOENT) {
+    // Path does not exist, attempt to create its parents, then it.
+    string parent = blaze_util::Dirname(path);
+    if (!MakeDirectories(parent, mode, false)) {
+      // errno set by stat.
+      return false;
+    }
+
+    if (mkdir(path.c_str(), mode) == -1) {
+      if (errno == EEXIST) {
+        if (childmost) {
+          // If there are multiple bazel calls at the same time then the
+          // directory could be created between the MakeDirectories and mkdir
+          // calls. This is okay, but we still have to check the permissions.
+          return GetDirectoryStat(path, mode, childmost);
+        } else {
+          // If this isn't the childmost directory, we don't care what the
+          // permissions were. If it's not even a directory then that error will
+          // get caught when we attempt to create the next directory down the
+          // chain.
+          return true;
+        }
+      }
+      // errno set by mkdir.
+      return false;
+    }
+    return true;
+  }
+
+  return stat_succeeded;
+}
+
+// mkdir -p path. Returns 0 if the path was created or already exists and could
+// be chmod-ed to exactly the given permissions. If final part of the path is a
+// symlink, this ensures that the destination of the symlink has the desired
+// permissions. It also checks that the directory or symlink is owned by us.
+// On failure, this returns -1 and sets errno.
+bool MakeDirectories(const string& path, mode_t mode) {
+  return MakeDirectories(path, mode, true);
+}
+#endif  // COMPILER_MSVC
+
+string GetEnv(const string& name) {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team): implement this.
+  pdie(255, "blaze::GetEnv is not implemented on Windows");
+  return "";
+#else  // not COMPILER_MSVC
+  char* result = getenv(name.c_str());
+  return result != NULL ? string(result) : "";
+#endif  // COMPILER_MSVC
+}
+
+void SetEnv(const string& name, const string& value) {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team): implement this.
+  pdie(255, "blaze::SetEnv is not implemented on Windows");
+#else  // not COMPILER_MSVC
+  setenv(name.c_str(), value.c_str(), 1);
+#endif  // COMPILER_MSVC
+}
+
+void UnsetEnv(const string& name) {
+#ifdef COMPILER_MSVC
+  // TODO(bazel-team): implement this.
+  pdie(255, "blaze::UnsetEnv is not implemented on Windows");
+#else  // not COMPILER_MSVC
+  unsetenv(name.c_str());
+#endif  // COMPILER_MSVC
+}
+
 LARGE_INTEGER WindowsClock::GetFrequency() {
   LARGE_INTEGER result;
   if (!QueryPerformanceFrequency(&result)) {
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index 6be8823..f7340db 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -274,8 +274,8 @@
     return blaze_exit_code::SUCCESS;
   }
 
-  const char* home = getenv("HOME");
-  if (home == NULL) {
+  string home = blaze::GetEnv("HOME");
+  if (home.empty()) {
     *blaze_rc_file = "";
     return blaze_exit_code::SUCCESS;
   }
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc
index fd02628..06f5126 100644
--- a/src/main/cpp/startup_options.cc
+++ b/src/main/cpp/startup_options.cc
@@ -54,9 +54,9 @@
       connect_timeout_secs(10),
       invocation_policy(NULL),
       client_debug(false) {
-  bool testing = getenv("TEST_TMPDIR") != NULL;
+  bool testing = !blaze::GetEnv("TEST_TMPDIR").empty();
   if (testing) {
-    output_root = MakeAbsolute(getenv("TEST_TMPDIR"));
+    output_root = MakeAbsolute(blaze::GetEnv("TEST_TMPDIR"));
   } else {
     output_root = WorkspaceLayout::GetOutputRoot();
   }
diff --git a/src/test/cpp/blaze_util_test.cc b/src/test/cpp/blaze_util_test.cc
index e8b5b28..bb0faf0 100644
--- a/src/test/cpp/blaze_util_test.cc
+++ b/src/test/cpp/blaze_util_test.cc
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "src/main/cpp/blaze_util.h"
+#include "src/main/cpp/blaze_util_platform.h"
 #include "src/main/cpp/util/file.h"
 #include "gtest/gtest.h"
 
@@ -182,12 +183,12 @@
   ASSERT_STRNE(NULL, test_src_dir);
 
   string dir = blaze_util::JoinPath(tmp_dir, "x/y/z");
-  int ok = MakeDirectories(dir, 0755);
-  ASSERT_EQ(0, ok);
+  bool ok = MakeDirectories(dir, 0755);
+  ASSERT_TRUE(ok);
 
   // Changing permissions on an existing dir should work.
   ok = MakeDirectories(dir, 0750);
-  ASSERT_EQ(0, ok);
+  ASSERT_TRUE(ok);
   struct stat filestat = {};
   ASSERT_EQ(0, stat(dir.c_str(), &filestat));
   ASSERT_EQ(0750, filestat.st_mode & 0777);
@@ -196,27 +197,27 @@
   // TODO(ulfjack): Fix this!
 //  string srcdir = blaze_util::JoinPath(test_src_dir, "x/y/z");
 //  ok = MakeDirectories(srcdir, 0755);
-//  ASSERT_EQ(-1, ok);
+//  ASSERT_FALSE(ok);
 //  ASSERT_EQ(EACCES, errno);
 
   // Can't make a dir out of a file.
   string non_dir = blaze_util::JoinPath(dir, "w");
   ASSERT_TRUE(CreateEmptyFile(non_dir));
   ok = MakeDirectories(non_dir, 0755);
-  ASSERT_EQ(-1, ok);
+  ASSERT_FALSE(ok);
   ASSERT_EQ(ENOTDIR, errno);
 
   // Valid symlink should work.
   string symlink = blaze_util::JoinPath(tmp_dir, "z");
   ASSERT_TRUE(Symlink(dir, symlink));
   ok = MakeDirectories(symlink, 0755);
-  ASSERT_EQ(0, ok);
+  ASSERT_TRUE(ok);
 
   // Error: Symlink to a file.
   symlink = blaze_util::JoinPath(tmp_dir, "w");
   ASSERT_TRUE(Symlink(non_dir, symlink));
   ok = MakeDirectories(symlink, 0755);
-  ASSERT_EQ(-1, ok);
+  ASSERT_FALSE(ok);
   ASSERT_EQ(ENOTDIR, errno);
 
   // Error: Symlink to a dir with wrong perms.
@@ -226,13 +227,13 @@
   // These perms will force a chmod()
   // TODO(ulfjack): Fix this!
 //  ok = MakeDirectories(symlink, 0000);
-//  ASSERT_EQ(-1, ok);
+//  ASSERTFALSE(ok);
 //  ASSERT_EQ(EPERM, errno);
 
   // Edge cases.
-  ASSERT_EQ(-1, MakeDirectories("", 0755));
+  ASSERT_FALSE(MakeDirectories("", 0755));
   ASSERT_EQ(EACCES, errno);
-  ASSERT_EQ(-1, MakeDirectories("/", 0755));
+  ASSERT_FALSE(MakeDirectories("/", 0755));
   ASSERT_EQ(EACCES, errno);
 }
 
@@ -243,7 +244,7 @@
   string path = blaze_util::JoinPath(tmp_dir, "x/y/z");
   // TODO(ulfjack): Fix this!
 //  ASSERT_LE(0, fork());
-//  ASSERT_EQ(0, MakeDirectories(path, 0755));
+//  ASSERT_TRUE(MakeDirectories(path, 0755));
 }
 
 }  // namespace blaze
diff --git a/src/tools/singlejar/test_util.cc b/src/tools/singlejar/test_util.cc
index f5cbca9..d026388 100644
--- a/src/tools/singlejar/test_util.cc
+++ b/src/tools/singlejar/test_util.cc
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "src/main/cpp/blaze_util.h"
+#include "src/main/cpp/blaze_util_platform.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/strings.h"