// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/main/cpp/util/file_platform.h" #include // DIR, dirent, opendir, closedir #include #include // O_RDONLY #include // PATH_MAX #include // getenv #include // strncmp #include #include // access, open, close, fsync #include // utime #include #include #include "src/main/cpp/util/errors.h" #include "src/main/cpp/util/exit_code.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" #include "src/main/cpp/util/path.h" #include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" namespace blaze_util { using std::string; // 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() || IsRootDirectory(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 = 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; } class PosixPipe : public IPipe { public: PosixPipe(int recv_socket, int send_socket) : _recv_socket(recv_socket), _send_socket(send_socket) {} PosixPipe() = delete; virtual ~PosixPipe() { close(_recv_socket); close(_send_socket); } bool Send(const void *buffer, int size) override { return size >= 0 && write(_send_socket, buffer, size) == size; } int Receive(void *buffer, int size, int *error) override { if (size < 0) { if (error != nullptr) { *error = IPipe::OTHER_ERROR; } return -1; } int result = read(_recv_socket, buffer, size); if (error != nullptr) { *error = result >= 0 ? IPipe::SUCCESS : ((errno == EINTR) ? IPipe::INTERRUPTED : IPipe::OTHER_ERROR); } return result; } private: int _recv_socket; int _send_socket; }; IPipe* CreatePipe() { int fd[2]; if (pipe(fd) < 0) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "pipe() failed: " << GetLastErrorString(); } if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) == -1) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "fcntl(F_SETFD, FD_CLOEXEC) failed: " << GetLastErrorString(); } if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == -1) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "fcntl(F_SETFD, FD_CLOEXEC) failed: " << GetLastErrorString(); } return new PosixPipe(fd[0], fd[1]); } int ReadFromHandle(file_handle_type fd, void *data, size_t size, int *error) { int result = read(fd, data, size); if (error != nullptr) { if (result >= 0) { *error = ReadFileResult::SUCCESS; } else { if (errno == EINTR) { *error = ReadFileResult::INTERRUPTED; } else if (errno == EAGAIN) { *error = ReadFileResult::AGAIN; } else { *error = ReadFileResult::OTHER_ERROR; } } } return result; } bool ReadFile(const string &filename, string *content, int max_size) { int fd = open(filename.c_str(), O_RDONLY); if (fd == -1) return false; bool result = ReadFrom(fd, content, max_size); close(fd); return result; } bool ReadFile(const string &filename, void *data, size_t size) { int fd = open(filename.c_str(), O_RDONLY); if (fd == -1) return false; bool result = ReadFrom(fd, data, size); close(fd); return result; } bool WriteFile(const void *data, size_t size, const string &filename, unsigned int perm) { UnlinkPath(filename); // We don't care about the success of this. int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, perm); if (fd == -1) { return false; } int result = write(fd, data, size); if (close(fd)) { return false; // Can fail on NFS. } return result == static_cast(size); } int WriteToStdOutErr(const void *data, size_t size, bool to_stdout) { size_t r = fwrite(data, 1, size, to_stdout ? stdout : stderr); return (r == size) ? WriteResult::SUCCESS : ((errno == EPIPE) ? WriteResult::BROKEN_PIPE : WriteResult::OTHER_ERROR); } int RenameDirectory(const std::string &old_name, const std::string &new_name) { if (rename(old_name.c_str(), new_name.c_str()) == 0) { return kRenameDirectorySuccess; } else { return errno == ENOTEMPTY ? kRenameDirectoryFailureNotEmpty : kRenameDirectoryFailureOtherError; } } bool ReadDirectorySymlink(const string &name, string *result) { char buf[PATH_MAX + 1]; int len = readlink(name.c_str(), buf, PATH_MAX); if (len < 0) { return false; } buf[len] = 0; *result = buf; return true; } bool UnlinkPath(const string &file_path) { return unlink(file_path.c_str()) == 0; } bool PathExists(const string& path) { return access(path.c_str(), F_OK) == 0; } string MakeCanonical(const char *path) { char *resolved_path = realpath(path, NULL); if (resolved_path == NULL) { return ""; } else { string ret = resolved_path; free(resolved_path); return ret; } } static bool CanAccess(const string &path, bool read, bool write, bool exec) { int mode = 0; if (read) { mode |= R_OK; } if (write) { mode |= W_OK; } if (exec) { mode |= X_OK; } return access(path.c_str(), mode) == 0; } bool CanReadFile(const std::string &path) { return !IsDirectory(path) && CanAccess(path, true, false, false); } bool CanExecuteFile(const std::string &path) { return !IsDirectory(path) && CanAccess(path, false, false, true); } bool CanAccessDirectory(const std::string &path) { return IsDirectory(path) && CanAccess(path, true, true, true); } bool IsDirectory(const string& path) { struct stat buf; return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode); } void SyncFile(const string& path) { const char* file_path = path.c_str(); int fd = open(file_path, O_RDONLY); if (fd < 0) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "failed to open '" << file_path << "' for syncing: " << GetLastErrorString(); } if (fsync(fd) < 0) { BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << "failed to sync '" << file_path << "': " << GetLastErrorString(); } close(fd); } class PosixFileMtime : public IFileMtime { public: PosixFileMtime() : near_future_(GetFuture(9)), distant_future_({GetFuture(10), GetFuture(10)}) {} bool IsUntampered(const string &path) override; bool SetToNow(const string &path) override; bool SetToDistantFuture(const string &path) override; private: // 9 years in the future. const time_t near_future_; // 10 years in the future. const struct utimbuf distant_future_; static bool Set(const string &path, const struct utimbuf &mtime); static time_t GetNow(); static time_t GetFuture(unsigned int years); }; bool PosixFileMtime::IsUntampered(const string &path) { struct stat buf; if (stat(path.c_str(), &buf)) { return false; } // Compare the mtime with `near_future_`, not with `GetNow()` or // `distant_future_`. // This way we don't need to call GetNow() every time we want to compare and // we also don't need to worry about potentially unreliable time equality // check (in case it uses floats or something crazy). return S_ISDIR(buf.st_mode) || (buf.st_mtime > near_future_); } bool PosixFileMtime::SetToNow(const string &path) { time_t now(GetNow()); struct utimbuf times = {now, now}; return Set(path, times); } bool PosixFileMtime::SetToDistantFuture(const string &path) { return Set(path, distant_future_); } bool PosixFileMtime::Set(const string &path, const struct utimbuf &mtime) { return utime(path.c_str(), &mtime) == 0; } time_t PosixFileMtime::GetNow() { time_t result = time(NULL); if (result == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "time(NULL) failed: " << GetLastErrorString(); } return result; } time_t PosixFileMtime::GetFuture(unsigned int years) { return GetNow() + 3600 * 24 * 365 * years; } IFileMtime *CreateFileMtime() { return new PosixFileMtime(); } // mkdir -p path. Returns true 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 false and sets errno. bool MakeDirectories(const string &path, unsigned int mode) { return MakeDirectories(path, mode, true); } string GetCwd() { char cwdbuf[PATH_MAX]; if (getcwd(cwdbuf, sizeof cwdbuf) == NULL) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "getcwd() failed: " << GetLastErrorString(); } return string(cwdbuf); } bool ChangeDirectory(const string& path) { return chdir(path.c_str()) == 0; } void ForEachDirectoryEntry(const string &path, DirectoryEntryConsumer *consume) { DIR *dir; struct dirent *ent; if ((dir = opendir(path.c_str())) == NULL) { // This is not a directory or it cannot be opened. return; } while ((ent = readdir(dir)) != NULL) { if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { continue; } string filename(blaze_util::JoinPath(path, ent->d_name)); bool is_directory; // 'd_type' field isn't part of the POSIX spec. #ifdef _DIRENT_HAVE_D_TYPE if (ent->d_type != DT_UNKNOWN) { is_directory = (ent->d_type == DT_DIR); } else // NOLINT (the brace is on the next line) #endif { struct stat buf; if (lstat(filename.c_str(), &buf) == -1) { BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) << "stat failed for filename '" << filename << "': " << GetLastErrorString(); } is_directory = S_ISDIR(buf.st_mode); } consume->Consume(filename, is_directory); } closedir(dir); } } // namespace blaze_util