text: //===-- FileSystem.cpp ------------------------------------------*- C++ -*-===//
  //
  // 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 <errno.h>
  #include <fcntl.h>
  #include <limits.h>
  #include <stdarg.h>
  #include <stdio.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<FileCollector> 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) {
    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 {
    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 {
    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 {
    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 {
    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 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 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 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 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) {
    // 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) {
    if (m_collector)
      m_collector->addFile(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, -1, 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;
  }
  
  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) {
    if (m_collector)
      m_collector->addFile(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::Entry *> E =
        static_cast<vfs::RedirectingFileSystem &>(*m_fs).lookupPath(path);
    if (!E) {
      if (E.getError() == llvm::errc::no_such_file_or_directory) {
        return path.str();
      }
      return E.getError();
    }
  
    auto *F = dyn_cast<vfs::RedirectingFileSystem::RedirectingFileEntry>(*E);
    if (!F)
      return make_error_code(llvm::errc::not_supported);
  
    return F->getExternalContentsPath().str();
  }
  
  ErrorOr<std::string> FileSystem::GetExternalPath(const FileSpec &file_spec) {
    return GetExternalPath(file_spec.GetPath());
  }
  