llvm-project/lldb/source/Host/common/FileSystem.cpp

516 lines
15 KiB
C++

//===-- FileSystem.cpp ----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Host/FileSystem.h"
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/TildeExpressionResolver.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/Threading.h"
#include <cerrno>
#include <climits>
#include <cstdarg>
#include <cstdio>
#include <fcntl.h>
#ifdef _WIN32
#include "lldb/Host/windows/windows.h"
#else
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
#endif
#include <algorithm>
#include <fstream>
#include <vector>
using namespace lldb;
using namespace lldb_private;
using namespace llvm;
FileSystem &FileSystem::Instance() { return *InstanceImpl(); }
void FileSystem::Initialize() {
lldbassert(!InstanceImpl() && "Already initialized.");
InstanceImpl().emplace();
}
void FileSystem::Initialize(std::shared_ptr<FileCollectorBase> collector) {
lldbassert(!InstanceImpl() && "Already initialized.");
InstanceImpl().emplace(collector);
}
llvm::Error FileSystem::Initialize(const FileSpec &mapping) {
lldbassert(!InstanceImpl() && "Already initialized.");
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer =
llvm::vfs::getRealFileSystem()->getBufferForFile(mapping.GetPath());
if (!buffer)
return llvm::errorCodeToError(buffer.getError());
InstanceImpl().emplace(llvm::vfs::getVFSFromYAML(std::move(buffer.get()),
nullptr, mapping.GetPath()),
true);
return llvm::Error::success();
}
void FileSystem::Initialize(IntrusiveRefCntPtr<vfs::FileSystem> fs) {
lldbassert(!InstanceImpl() && "Already initialized.");
InstanceImpl().emplace(fs);
}
void FileSystem::Terminate() {
lldbassert(InstanceImpl() && "Already terminated.");
InstanceImpl().reset();
}
Optional<FileSystem> &FileSystem::InstanceImpl() {
static Optional<FileSystem> g_fs;
return g_fs;
}
vfs::directory_iterator FileSystem::DirBegin(const FileSpec &file_spec,
std::error_code &ec) {
if (!file_spec) {
ec = std::error_code(static_cast<int>(errc::no_such_file_or_directory),
std::system_category());
return {};
}
return DirBegin(file_spec.GetPath(), ec);
}
vfs::directory_iterator FileSystem::DirBegin(const Twine &dir,
std::error_code &ec) {
return m_fs->dir_begin(dir, ec);
}
llvm::ErrorOr<vfs::Status>
FileSystem::GetStatus(const FileSpec &file_spec) const {
if (!file_spec)
return std::error_code(static_cast<int>(errc::no_such_file_or_directory),
std::system_category());
return GetStatus(file_spec.GetPath());
}
llvm::ErrorOr<vfs::Status> FileSystem::GetStatus(const Twine &path) const {
return m_fs->status(path);
}
sys::TimePoint<>
FileSystem::GetModificationTime(const FileSpec &file_spec) const {
if (!file_spec)
return sys::TimePoint<>();
return GetModificationTime(file_spec.GetPath());
}
sys::TimePoint<> FileSystem::GetModificationTime(const Twine &path) const {
ErrorOr<vfs::Status> status = m_fs->status(path);
if (!status)
return sys::TimePoint<>();
return status->getLastModificationTime();
}
uint64_t FileSystem::GetByteSize(const FileSpec &file_spec) const {
if (!file_spec)
return 0;
return GetByteSize(file_spec.GetPath());
}
uint64_t FileSystem::GetByteSize(const Twine &path) const {
ErrorOr<vfs::Status> status = m_fs->status(path);
if (!status)
return 0;
return status->getSize();
}
uint32_t FileSystem::GetPermissions(const FileSpec &file_spec) const {
return GetPermissions(file_spec.GetPath());
}
uint32_t FileSystem::GetPermissions(const FileSpec &file_spec,
std::error_code &ec) const {
if (!file_spec)
return sys::fs::perms::perms_not_known;
return GetPermissions(file_spec.GetPath(), ec);
}
uint32_t FileSystem::GetPermissions(const Twine &path) const {
std::error_code ec;
return GetPermissions(path, ec);
}
uint32_t FileSystem::GetPermissions(const Twine &path,
std::error_code &ec) const {
ErrorOr<vfs::Status> status = m_fs->status(path);
if (!status) {
ec = status.getError();
return sys::fs::perms::perms_not_known;
}
return status->getPermissions();
}
bool FileSystem::Exists(const Twine &path) const { return m_fs->exists(path); }
bool FileSystem::Exists(const FileSpec &file_spec) const {
return file_spec && Exists(file_spec.GetPath());
}
bool FileSystem::Readable(const Twine &path) const {
return GetPermissions(path) & sys::fs::perms::all_read;
}
bool FileSystem::Readable(const FileSpec &file_spec) const {
return file_spec && Readable(file_spec.GetPath());
}
bool FileSystem::IsDirectory(const Twine &path) const {
ErrorOr<vfs::Status> status = m_fs->status(path);
if (!status)
return false;
return status->isDirectory();
}
bool FileSystem::IsDirectory(const FileSpec &file_spec) const {
return file_spec && IsDirectory(file_spec.GetPath());
}
bool FileSystem::IsLocal(const Twine &path) const {
bool b = false;
m_fs->isLocal(path, b);
return b;
}
bool FileSystem::IsLocal(const FileSpec &file_spec) const {
return file_spec && IsLocal(file_spec.GetPath());
}
void FileSystem::EnumerateDirectory(Twine path, bool find_directories,
bool find_files, bool find_other,
EnumerateDirectoryCallbackType callback,
void *callback_baton) {
std::error_code EC;
vfs::recursive_directory_iterator Iter(*m_fs, path, EC);
vfs::recursive_directory_iterator End;
for (; Iter != End && !EC; Iter.increment(EC)) {
const auto &Item = *Iter;
ErrorOr<vfs::Status> Status = m_fs->status(Item.path());
if (!Status)
break;
if (!find_files && Status->isRegularFile())
continue;
if (!find_directories && Status->isDirectory())
continue;
if (!find_other && Status->isOther())
continue;
auto Result = callback(callback_baton, Status->getType(), Item.path());
if (Result == eEnumerateDirectoryResultQuit)
return;
if (Result == eEnumerateDirectoryResultNext) {
// Default behavior is to recurse. Opt out if the callback doesn't want
// this behavior.
Iter.no_push();
}
}
}
std::error_code FileSystem::MakeAbsolute(SmallVectorImpl<char> &path) const {
return m_fs->makeAbsolute(path);
}
std::error_code FileSystem::MakeAbsolute(FileSpec &file_spec) const {
SmallString<128> path;
file_spec.GetPath(path, false);
auto EC = MakeAbsolute(path);
if (EC)
return EC;
FileSpec new_file_spec(path, file_spec.GetPathStyle());
file_spec = new_file_spec;
return {};
}
std::error_code FileSystem::GetRealPath(const Twine &path,
SmallVectorImpl<char> &output) const {
return m_fs->getRealPath(path, output);
}
void FileSystem::Resolve(SmallVectorImpl<char> &path) {
if (path.empty())
return;
// Resolve tilde in path.
SmallString<128> resolved(path.begin(), path.end());
StandardTildeExpressionResolver Resolver;
Resolver.ResolveFullPath(llvm::StringRef(path.begin(), path.size()),
resolved);
// Try making the path absolute if it exists.
SmallString<128> absolute(resolved.begin(), resolved.end());
MakeAbsolute(absolute);
path.clear();
if (Exists(absolute)) {
path.append(absolute.begin(), absolute.end());
} else {
path.append(resolved.begin(), resolved.end());
}
}
void FileSystem::Resolve(FileSpec &file_spec) {
if (!file_spec)
return;
// Extract path from the FileSpec.
SmallString<128> path;
file_spec.GetPath(path);
// Resolve the path.
Resolve(path);
// Update the FileSpec with the resolved path.
if (file_spec.GetFilename().IsEmpty())
file_spec.GetDirectory().SetString(path);
else
file_spec.SetPath(path);
file_spec.SetIsResolved(true);
}
std::shared_ptr<DataBufferLLVM>
FileSystem::CreateDataBuffer(const llvm::Twine &path, uint64_t size,
uint64_t offset) {
Collect(path);
const bool is_volatile = !IsLocal(path);
const ErrorOr<std::string> external_path = GetExternalPath(path);
if (!external_path)
return nullptr;
std::unique_ptr<llvm::WritableMemoryBuffer> buffer;
if (size == 0) {
auto buffer_or_error =
llvm::WritableMemoryBuffer::getFile(*external_path, is_volatile);
if (!buffer_or_error)
return nullptr;
buffer = std::move(*buffer_or_error);
} else {
auto buffer_or_error = llvm::WritableMemoryBuffer::getFileSlice(
*external_path, size, offset, is_volatile);
if (!buffer_or_error)
return nullptr;
buffer = std::move(*buffer_or_error);
}
return std::shared_ptr<DataBufferLLVM>(new DataBufferLLVM(std::move(buffer)));
}
std::shared_ptr<DataBufferLLVM>
FileSystem::CreateDataBuffer(const FileSpec &file_spec, uint64_t size,
uint64_t offset) {
return CreateDataBuffer(file_spec.GetPath(), size, offset);
}
bool FileSystem::ResolveExecutableLocation(FileSpec &file_spec) {
// If the directory is set there's nothing to do.
ConstString directory = file_spec.GetDirectory();
if (directory)
return false;
// We cannot look for a file if there's no file name.
ConstString filename = file_spec.GetFilename();
if (!filename)
return false;
// Search for the file on the host.
const std::string filename_str(filename.GetCString());
llvm::ErrorOr<std::string> error_or_path =
llvm::sys::findProgramByName(filename_str);
if (!error_or_path)
return false;
// findProgramByName returns "." if it can't find the file.
llvm::StringRef path = *error_or_path;
llvm::StringRef parent = llvm::sys::path::parent_path(path);
if (parent.empty() || parent == ".")
return false;
// Make sure that the result exists.
FileSpec result(*error_or_path);
if (!Exists(result))
return false;
file_spec = result;
return true;
}
bool FileSystem::GetHomeDirectory(SmallVectorImpl<char> &path) const {
if (!m_home_directory.empty()) {
path.assign(m_home_directory.begin(), m_home_directory.end());
return true;
}
return llvm::sys::path::home_directory(path);
}
bool FileSystem::GetHomeDirectory(FileSpec &file_spec) const {
SmallString<128> home_dir;
if (!GetHomeDirectory(home_dir))
return false;
file_spec.SetPath(home_dir);
return true;
}
static int OpenWithFS(const FileSystem &fs, const char *path, int flags,
int mode) {
return const_cast<FileSystem &>(fs).Open(path, flags, mode);
}
static int GetOpenFlags(uint32_t options) {
const bool read = options & File::eOpenOptionRead;
const bool write = options & File::eOpenOptionWrite;
int open_flags = 0;
if (write) {
if (read)
open_flags |= O_RDWR;
else
open_flags |= O_WRONLY;
if (options & File::eOpenOptionAppend)
open_flags |= O_APPEND;
if (options & File::eOpenOptionTruncate)
open_flags |= O_TRUNC;
if (options & File::eOpenOptionCanCreate)
open_flags |= O_CREAT;
if (options & File::eOpenOptionCanCreateNewOnly)
open_flags |= O_CREAT | O_EXCL;
} else if (read) {
open_flags |= O_RDONLY;
#ifndef _WIN32
if (options & File::eOpenOptionDontFollowSymlinks)
open_flags |= O_NOFOLLOW;
#endif
}
#ifndef _WIN32
if (options & File::eOpenOptionNonBlocking)
open_flags |= O_NONBLOCK;
if (options & File::eOpenOptionCloseOnExec)
open_flags |= O_CLOEXEC;
#else
open_flags |= O_BINARY;
#endif
return open_flags;
}
static mode_t GetOpenMode(uint32_t permissions) {
mode_t mode = 0;
if (permissions & lldb::eFilePermissionsUserRead)
mode |= S_IRUSR;
if (permissions & lldb::eFilePermissionsUserWrite)
mode |= S_IWUSR;
if (permissions & lldb::eFilePermissionsUserExecute)
mode |= S_IXUSR;
if (permissions & lldb::eFilePermissionsGroupRead)
mode |= S_IRGRP;
if (permissions & lldb::eFilePermissionsGroupWrite)
mode |= S_IWGRP;
if (permissions & lldb::eFilePermissionsGroupExecute)
mode |= S_IXGRP;
if (permissions & lldb::eFilePermissionsWorldRead)
mode |= S_IROTH;
if (permissions & lldb::eFilePermissionsWorldWrite)
mode |= S_IWOTH;
if (permissions & lldb::eFilePermissionsWorldExecute)
mode |= S_IXOTH;
return mode;
}
Expected<FileUP> FileSystem::Open(const FileSpec &file_spec,
File::OpenOptions options,
uint32_t permissions, bool should_close_fd) {
Collect(file_spec.GetPath());
const int open_flags = GetOpenFlags(options);
const mode_t open_mode =
(open_flags & O_CREAT) ? GetOpenMode(permissions) : 0;
auto path = GetExternalPath(file_spec);
if (!path)
return errorCodeToError(path.getError());
int descriptor = llvm::sys::RetryAfterSignal(
-1, OpenWithFS, *this, path->c_str(), open_flags, open_mode);
if (!File::DescriptorIsValid(descriptor))
return llvm::errorCodeToError(
std::error_code(errno, std::system_category()));
auto file = std::unique_ptr<File>(
new NativeFile(descriptor, options, should_close_fd));
assert(file->IsValid());
return std::move(file);
}
ErrorOr<std::string> FileSystem::GetExternalPath(const llvm::Twine &path) {
if (!m_mapped)
return path.str();
// If VFS mapped we know the underlying FS is a RedirectingFileSystem.
ErrorOr<vfs::RedirectingFileSystem::LookupResult> Result =
static_cast<vfs::RedirectingFileSystem &>(*m_fs).lookupPath(path.str());
if (!Result) {
if (Result.getError() == llvm::errc::no_such_file_or_directory) {
return path.str();
}
return Result.getError();
}
if (Optional<StringRef> ExtRedirect = Result->getExternalRedirect())
return std::string(*ExtRedirect);
return make_error_code(llvm::errc::not_supported);
}
ErrorOr<std::string> FileSystem::GetExternalPath(const FileSpec &file_spec) {
return GetExternalPath(file_spec.GetPath());
}
void FileSystem::Collect(const FileSpec &file_spec) {
Collect(file_spec.GetPath());
}
void FileSystem::Collect(const llvm::Twine &file) {
if (!m_collector)
return;
if (llvm::sys::fs::is_directory(file))
m_collector->addDirectory(file);
else
m_collector->addFile(file);
}
void FileSystem::SetHomeDirectory(std::string home_directory) {
m_home_directory = std::move(home_directory);
}