From cefa9a2059237f76610273500b3be308490e1e68 Mon Sep 17 00:00:00 2001 From: Laszlo Csomor Date: Tue, 22 Nov 2016 10:50:07 +0000 Subject: [PATCH] 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_ - implement envvar-handling in blaze_util_ and use it everywhere See https://github.com/bazelbuild/bazel/issues/2107 -- MOS_MIGRATED_REVID=139887503 --- src/main/cpp/blaze.cc | 34 +++--- src/main/cpp/blaze_util.cc | 126 +++------------------ src/main/cpp/blaze_util.h | 4 - src/main/cpp/blaze_util_darwin.cc | 6 +- src/main/cpp/blaze_util_freebsd.cc | 7 +- src/main/cpp/blaze_util_platform.h | 11 ++ src/main/cpp/blaze_util_posix.cc | 110 ++++++++++++++++++- src/main/cpp/blaze_util_windows.cc | 171 ++++++++++++++++++++++++++++- src/main/cpp/option_processor.cc | 4 +- src/main/cpp/startup_options.cc | 4 +- src/test/cpp/blaze_util_test.cc | 23 ++-- src/tools/singlejar/test_util.cc | 1 + 12 files changed, 343 insertions(+), 158 deletions(-) diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc index 96335651ef9509..abb16ecd64d399 100644 --- a/src/main/cpp/blaze.cc +++ b/src/main/cpp/blaze.cc @@ -721,7 +721,7 @@ static void StartServerAndConnect(BlazeServer *server) { // 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 @@ class ExtractBlazeZipProcessor : public devtools_ijar::ZipExtractorProcessor { 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 @@ class ExtractBlazeZipProcessor : public devtools_ijar::ZipExtractorProcessor { 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 @@ static void ComputeBaseDirectories(const string &self_path) { 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 ComputeBaseDirectories(const string &self_path) { } 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 @@ static void CheckEnvironment() { // 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 b7aa84abc0bd8b..9e1bd2df383d74 100644 --- a/src/main/cpp/blaze_util.cc +++ b/src/main/cpp/blaze_util.cc @@ -51,8 +51,10 @@ const char kServerPidFile[] = "server.pid.txt"; 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 @@ string MakeAbsolute(const string &path) { 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 @@ bool ReadFileDescriptor(int fd, string *content, size_t max_size) { } content->append(buf, r); if (max_size > 0) { - if (max_size > r) { + if (max_size > static_cast(r)) { max_size -= r; } else { break; @@ -235,14 +143,13 @@ bool UnlinkPath(const string &file_path) { } 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= (where 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 @@ bool IsEmacsTerminal() { // (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 @@ int GetTerminalColumns() { 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 @@ bool GetNullaryOption(const char *arg, const char *key) { 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 666b06e89c1561..7241682cba1c5a 100644 --- a/src/main/cpp/blaze_util.h +++ b/src/main/cpp/blaze_util.h @@ -41,10 +41,6 @@ std::string GetUserName(); // 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 c4a35c0c7fb0de..63bfcec7c85a07 100644 --- a/src/main/cpp/blaze_util_darwin.cc +++ b/src/main/cpp/blaze_util_darwin.cc @@ -153,9 +153,9 @@ bool IsSharedLibrary(const string &filename) { } 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 c4ac11ae93f4b2..f1177b219204ee 100644 --- a/src/main/cpp/blaze_util_freebsd.cc +++ b/src/main/cpp/blaze_util_freebsd.cc @@ -145,11 +145,8 @@ bool IsSharedLibrary(const string &filename) { 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 6d68e737079aea..a4c9c201c132d7 100644 --- a/src/main/cpp/blaze_util_platform.h +++ b/src/main/cpp/blaze_util_platform.h @@ -148,6 +148,17 @@ std::string GetHashedBaseDir(const std::string& root, // 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 b70d95c21e07f3..55cb59b563ae8c 100644 --- a/src/main/cpp/blaze_util_posix.cc +++ b/src/main/cpp/blaze_util_posix.cc @@ -236,7 +236,7 @@ void CreateSecureOutputRoot(const string& path) { 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 @@ void CreateSecureOutputRoot(const string& path) { 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 2905673b253225..87406f90bd1fd7 100644 --- a/src/main/cpp/blaze_util_windows.cc +++ b/src/main/cpp/blaze_util_windows.cc @@ -14,7 +14,6 @@ #include // errno, ENAMETOOLONG #include -#include // strerror #ifndef COMPILER_MSVC #include @@ -100,6 +99,9 @@ string GetProcessIdAsString() { } 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 @@ void SetScheduling(bool batch_cpu_scheduling, int io_nice_level) { } 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 @@ string GetProcessCWD(int pid) { } return string(server_cwd); +#endif // COMPILER_MSVC } bool IsSharedLibrary(const string &filename) { @@ -340,6 +348,10 @@ string RunProgram( // 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 @@ static bool DaemonizeOnWindows() { // 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 @@ void BatchWaiterThread(HANDLE java_handle) { 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 @@ static void MingwSignalHandler(int signum) { // 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 @@ void ExecuteProgram( // 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 @@ void ExecuteProgram( 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 ConvertPath(const string& path) { string result(wpath); free(wpath); return result; +#endif // COMPILER_MSVC } // Convert a Unix path list to Windows path list @@ -613,12 +641,18 @@ string ConvertPathList(const string& path_list) { 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(cygwin_create_path( CCP_WIN_A_TO_POSIX, static_cast(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 @@ void CreateSecureOutputRoot(const string& path) { 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 @@ void CreateSecureOutputRoot(const string& path) { #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 6be88237d12ffe..f7340db783672e 100644 --- a/src/main/cpp/option_processor.cc +++ b/src/main/cpp/option_processor.cc @@ -274,8 +274,8 @@ blaze_exit_code::ExitCode OptionProcessor::FindUserBlazerc( 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 fd026281998b79..06f5126760f93f 100644 --- a/src/main/cpp/startup_options.cc +++ b/src/main/cpp/startup_options.cc @@ -54,9 +54,9 @@ StartupOptions::StartupOptions(const string &product_name) 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 e8b5b28fef579a..bb0faf01823e99 100644 --- a/src/test/cpp/blaze_util_test.cc +++ b/src/test/cpp/blaze_util_test.cc @@ -21,6 +21,7 @@ #include #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 @@ TEST_F(BlazeUtilTest, MakeDirectories) { 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 @@ TEST_F(BlazeUtilTest, MakeDirectories) { // 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 @@ TEST_F(BlazeUtilTest, MakeDirectories) { // 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 @@ TEST_F(BlazeUtilTest, HammerMakeDirectories) { 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 f5cbca96e3584f..d0263889b7628a 100644 --- a/src/tools/singlejar/test_util.cc +++ b/src/tools/singlejar/test_util.cc @@ -21,6 +21,7 @@ #include #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"