//===-- FileSystem.cpp ------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/Host/windows/windows.h"

#include <shellapi.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "lldb/Host/FileSystem.h"
#include "lldb/Host/windows/AutoHandle.h"

#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/FileSystem.h"

using namespace lldb_private;

const char *FileSystem::DEV_NULL = "nul";

const char *FileSystem::PATH_CONVERSION_ERROR =
    "Error converting path between UTF-8 and native encoding";

FileSpec::PathSyntax FileSystem::GetNativePathSyntax() {
  return FileSpec::ePathSyntaxWindows;
}

Error FileSystem::MakeDirectory(const FileSpec &file_spec,
                                uint32_t file_permissions) {
  // On Win32, the mode parameter is ignored, as Windows files and directories
  // support a
  // different permission model than POSIX.
  Error error;
  const auto err_code =
      llvm::sys::fs::create_directories(file_spec.GetPath(), true);
  if (err_code) {
    error.SetErrorString(err_code.message().c_str());
  }

  return error;
}

Error FileSystem::DeleteDirectory(const FileSpec &file_spec, bool recurse) {
  Error error;
  std::wstring path_buffer;
  if (!llvm::ConvertUTF8toWide(file_spec.GetPath(), path_buffer)) {
    error.SetErrorString(PATH_CONVERSION_ERROR);
    return error;
  }
  if (!recurse) {
    BOOL result = ::RemoveDirectoryW(path_buffer.c_str());
    if (!result)
      error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
  } else {
    // SHFileOperation() accepts a list of paths, and so must be
    // double-null-terminated to
    // indicate the end of the list. The first null terminator is there only in
    // the backing
    // store but not the actual vector contents, and so we need to push twice.
    path_buffer.push_back(0);
    path_buffer.push_back(0);

    SHFILEOPSTRUCTW shfos = {};
    shfos.wFunc = FO_DELETE;
    shfos.pFrom = (LPCWSTR)path_buffer.data();
    shfos.fFlags = FOF_NO_UI;

    int result = ::SHFileOperationW(&shfos);
    // TODO(zturner): Correctly handle the intricacies of SHFileOperation return
    // values.
    if (result != 0)
      error.SetErrorStringWithFormat("SHFileOperation failed");
  }
  return error;
}

Error FileSystem::GetFilePermissions(const FileSpec &file_spec,
                                     uint32_t &file_permissions) {
  Error error;
  // Beware that Windows's permission model is different from Unix's, and it's
  // not clear if this API is supposed to check ACLs.  To match the caller's
  // expectations as closely as possible, we'll use Microsoft's _stat, which
  // attempts to emulate POSIX stat.  This should be good enough for basic
  // checks like FileSpec::Readable.
  struct _stat file_stats;
  if (::_stat(file_spec.GetCString(), &file_stats) == 0) {
    // The owner permission bits in "st_mode" currently match the definitions
    // for the owner file mode bits.
    file_permissions = file_stats.st_mode & (_S_IREAD | _S_IWRITE | _S_IEXEC);
  } else {
    error.SetErrorToErrno();
  }

  return error;
}

Error FileSystem::SetFilePermissions(const FileSpec &file_spec,
                                     uint32_t file_permissions) {
  Error error;
  error.SetErrorStringWithFormat("%s is not supported on this host",
                                 LLVM_PRETTY_FUNCTION);
  return error;
}

lldb::user_id_t FileSystem::GetFileSize(const FileSpec &file_spec) {
  return file_spec.GetByteSize();
}

bool FileSystem::GetFileExists(const FileSpec &file_spec) {
  return file_spec.Exists();
}

Error FileSystem::Hardlink(const FileSpec &src, const FileSpec &dst) {
  Error error;
  std::wstring wsrc, wdst;
  if (!llvm::ConvertUTF8toWide(src.GetCString(), wsrc) ||
      !llvm::ConvertUTF8toWide(dst.GetCString(), wdst))
    error.SetErrorString(PATH_CONVERSION_ERROR);
  else if (!::CreateHardLinkW(wsrc.c_str(), wdst.c_str(), nullptr))
    error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
  return error;
}

int FileSystem::GetHardlinkCount(const FileSpec &file_spec) {
  std::wstring path;
  if (!llvm::ConvertUTF8toWide(file_spec.GetCString(), path))
    return -1;

  HANDLE file_handle =
      ::CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ,
                    nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);

  if (file_handle == INVALID_HANDLE_VALUE)
    return -1;

  AutoHandle auto_file_handle(file_handle);
  BY_HANDLE_FILE_INFORMATION file_info;
  if (::GetFileInformationByHandle(file_handle, &file_info))
    return file_info.nNumberOfLinks;

  return -1;
}

Error FileSystem::Symlink(const FileSpec &src, const FileSpec &dst) {
  Error error;
  std::wstring wsrc, wdst;
  if (!llvm::ConvertUTF8toWide(src.GetCString(), wsrc) ||
      !llvm::ConvertUTF8toWide(dst.GetCString(), wdst))
    error.SetErrorString(PATH_CONVERSION_ERROR);
  if (error.Fail())
    return error;
  DWORD attrib = ::GetFileAttributesW(wdst.c_str());
  if (attrib == INVALID_FILE_ATTRIBUTES) {
    error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
    return error;
  }
  bool is_directory = !!(attrib & FILE_ATTRIBUTE_DIRECTORY);
  DWORD flag = is_directory ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
  BOOL result = ::CreateSymbolicLinkW(wsrc.c_str(), wdst.c_str(), flag);
  if (!result)
    error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
  return error;
}

Error FileSystem::Unlink(const FileSpec &file_spec) {
  Error error;
  std::wstring path;
  if (!llvm::ConvertUTF8toWide(file_spec.GetCString(), path)) {
    error.SetErrorString(PATH_CONVERSION_ERROR);
    return error;
  }
  BOOL result = ::DeleteFileW(path.c_str());
  if (!result)
    error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
  return error;
}

Error FileSystem::Readlink(const FileSpec &src, FileSpec &dst) {
  Error error;
  std::wstring wsrc;
  if (!llvm::ConvertUTF8toWide(src.GetCString(), wsrc)) {
    error.SetErrorString(PATH_CONVERSION_ERROR);
    return error;
  }

  HANDLE h = ::CreateFileW(wsrc.c_str(), GENERIC_READ,
                           FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                           OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL);
  if (h == INVALID_HANDLE_VALUE) {
    error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
    return error;
  }

  std::vector<wchar_t> buf(PATH_MAX + 1);
  // Subtract 1 from the path length since this function does not add a null
  // terminator.
  DWORD result = ::GetFinalPathNameByHandleW(
      h, buf.data(), buf.size() - 1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
  std::string path;
  if (result == 0)
    error.SetError(::GetLastError(), lldb::eErrorTypeWin32);
  else if (!llvm::convertWideToUTF8(buf.data(), path))
    error.SetErrorString(PATH_CONVERSION_ERROR);
  else
    dst.SetFile(path, false);

  ::CloseHandle(h);
  return error;
}

Error FileSystem::ResolveSymbolicLink(const FileSpec &src, FileSpec &dst) {
  return Error("ResolveSymbolicLink() isn't implemented on Windows");
}

bool FileSystem::IsLocal(const FileSpec &spec) {
  if (spec) {
    // TODO: return true if the file is on a locally mounted file system
    return true;
  }

  return false;
}

FILE *FileSystem::Fopen(const char *path, const char *mode) {
  std::wstring wpath, wmode;
  if (!llvm::ConvertUTF8toWide(path, wpath))
    return nullptr;
  if (!llvm::ConvertUTF8toWide(mode, wmode))
    return nullptr;
  FILE *file;
  if (_wfopen_s(&file, wpath.c_str(), wmode.c_str()) != 0)
    return nullptr;
  return file;
}

int FileSystem::Stat(const char *path, struct stat *stats) {
  std::wstring wpath;
  if (!llvm::ConvertUTF8toWide(path, wpath)) {
    errno = EINVAL;
    return -EINVAL;
  }
  int stat_result;
#ifdef _USE_32BIT_TIME_T
  struct _stat32 file_stats;
  stat_result = ::_wstat32(wpath.c_str(), &file_stats);
#else
  struct _stat64i32 file_stats;
  stat_result = ::_wstat64i32(wpath.c_str(), &file_stats);
#endif
  if (stat_result == 0) {
    static_assert(sizeof(struct stat) == sizeof(file_stats),
                  "stat and _stat32/_stat64i32 must have the same layout");
    *stats = *reinterpret_cast<struct stat *>(&file_stats);
  }
  return stat_result;
}
