blob: feae70e09c2d64e65e9d7f92689b2d62468a3672 [file] [log] [blame]
// 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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>
#include <string>
#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/file_platform.h"
#include "src/main/cpp/util/logging.h"
#include "src/main/cpp/util/path.h"
#include "src/main/cpp/util/path_platform.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;
}
string CreateTempDir(const std::string &prefix) {
std::string parent = Dirname(prefix);
// Need parent to exist first.
if (!blaze_util::PathExists(parent) &&
!blaze_util::MakeDirectories(parent, 0777)) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "couldn't create '" << parent << "': "
<< blaze_util::GetLastErrorString();
}
std::string result(prefix + "XXXXXX");
if (mkdtemp(&result[0]) == nullptr) {
std::string err = GetLastErrorString();
BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
<< "could not create temporary directory under " << parent
<< " to extract install base into (" << err << ")";
}
// There's no better way to get the current umask than to set and reset it.
const mode_t um = umask(0);
umask(um);
chmod(result.c_str(), 0777 & ~um);
return result;
}
static bool RemoveDirRecursively(const std::string &path) {
DIR *dir;
if ((dir = opendir(path.c_str())) == nullptr) {
return false;
}
struct dirent *ent;
while ((ent = readdir(dir)) != nullptr) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
continue;
}
if (!RemoveRecursively(blaze_util::JoinPath(path, ent->d_name))) {
closedir(dir);
return false;
}
}
if (closedir(dir) != 0) {
return false;
}
return rmdir(path.c_str()) == 0;
}
bool RemoveRecursively(const std::string &path) {
struct stat stat_buf;
if (lstat(path.c_str(), &stat_buf) == -1) {
// Non-existent is good enough.
return errno == ENOENT;
}
if (S_ISDIR(stat_buf.st_mode) && !S_ISLNK(stat_buf.st_mode)) {
return RemoveDirRecursively(path);
} else {
return UnlinkPath(path);
}
}
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, string *error_message,
int max_size) {
int fd = open(filename.c_str(), O_RDONLY);
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, /* error_message= */nullptr, max_size);
}
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 ReadFile(const Path &filename, void *data, size_t size) {
return ReadFile(filename.AsNativePath(), data, size);
}
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;
}
const char *const char_data = reinterpret_cast<const char *>(data);
size_t written = 0;
while (written < size) {
// write fails with EINVAL on MacOs for count > INT32_MAX.
auto result = write(fd, char_data + written,
std::min<size_t>(INT32_MAX, size - written));
if (result == -1) {
close(fd);
return false;
}
written += result;
}
return close(fd) == 0; // Can fail on NFS.
}
bool WriteFile(const void *data, size_t size, const Path &path,
unsigned int perm) {
return WriteFile(data, size, path.AsNativePath(), perm);
}
void InitializeStdOutErrForUtf8() {}
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 {
if (errno == ENOTEMPTY || errno == EEXIST) {
return kRenameDirectoryFailureNotEmpty;
} else {
return kRenameDirectoryFailureOtherError;
}
}
}
bool ReadDirectorySymlink(const blaze_util::Path &name, string *result) {
char buf[PATH_MAX + 1];
int len = readlink(name.AsNativePath().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 UnlinkPath(const Path &file_path) {
return UnlinkPath(file_path.AsNativePath());
}
bool PathExists(const string& path) {
return access(path.c_str(), F_OK) == 0;
}
bool PathExists(const Path &path) { return PathExists(path.AsNativePath()); }
string MakeCanonical(const char *path) {
char *resolved_path = realpath(path, nullptr);
if (resolved_path == nullptr) {
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 CanReadFile(const Path &path) {
return CanReadFile(path.AsNativePath());
}
bool CanExecuteFile(const std::string &path) {
return !IsDirectory(path) && CanAccess(path, false, false, true);
}
bool CanExecuteFile(const Path &path) {
return CanExecuteFile(path.AsNativePath());
}
bool CanAccessDirectory(const std::string &path) {
return IsDirectory(path) && CanAccess(path, true, true, true);
}
bool CanAccessDirectory(const Path &path) {
return CanAccessDirectory(path.AsNativePath());
}
bool IsDirectory(const string& path) {
struct stat buf;
return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode);
}
bool IsDirectory(const Path &path) { return IsDirectory(path.AsNativePath()); }
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);
}
void SyncFile(const Path &path) { SyncFile(path.AsNativePath()); }
static time_t GetNow() {
time_t result = time(nullptr);
if (result == -1) {
BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
<< "time(nullptr) failed: " << GetLastErrorString();
}
return result;
}
static time_t GetYearsInFuture(int years) {
return GetNow() + 3600 * 24 * 365 * years;
}
static bool SetMtime(const Path &path, time_t mtime) {
struct utimbuf times = {mtime, mtime};
return utime(path.AsNativePath().c_str(), &times) == 0;
}
static const time_t kNearFuture = GetYearsInFuture(9);
static const time_t kDistantFuture = GetYearsInFuture(10);
bool IsUntampered(const Path &path) {
struct stat buf;
if (stat(path.AsNativePath().c_str(), &buf)) {
return false;
}
// Compare with kNearFuture, not kDistantFuture.
// This way we don't need to worry about a potentially unreliable equality
// check if precision isn't preserved.
return S_ISDIR(buf.st_mode) || (buf.st_mtime > kNearFuture);
}
bool SetMtimeToNow(const Path &path) {
time_t now = GetNow();
return SetMtime(path, now);
}
bool SetMtimeToNowIfPossible(const Path &path) {
bool okay = SetMtimeToNow(path);
if (!okay) {
// `SetToNow`/`Set` are backed by `utime(2)` which can return `EROFS` and
// `EPERM` when there's a permissions issue:
if (errno == EROFS || errno == EPERM) {
okay = true;
}
}
return okay;
}
bool SetMtimeToDistantFuture(const Path &path) {
return SetMtime(path, kDistantFuture);
}
// 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);
}
bool MakeDirectories(const Path &path, unsigned int mode) {
return MakeDirectories(path.AsNativePath(), mode);
}
string GetCwd() {
char cwdbuf[PATH_MAX];
if (getcwd(cwdbuf, sizeof cwdbuf) == nullptr) {
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())) == nullptr) {
// This is not a directory or it cannot be opened.
return;
}
while ((ent = readdir(dir)) != nullptr) {
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