// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gn/file_writer.h"

#include "base/files/file_path.h"
#include "base/logging.h"
#include "gn/filesystem_utils.h"

#if defined(OS_WIN)
#include <windows.h>
#include "base/strings/utf_string_conversions.h"
#include "util/sys_info.h"
#else
#include <fcntl.h>
#include <unistd.h>
#include "base/posix/eintr_wrapper.h"
#endif

FileWriter::~FileWriter() = default;

#if defined(OS_WIN)

bool FileWriter::Create(const base::FilePath& file_path) {
  // On Windows, provide a custom implementation of base::WriteFile. Sometimes
  // the base version fails, especially on the bots. The guess is that Windows
  // Defender or other antivirus programs still have the file open (after
  // checking for the read) when the write happens immediately after. This
  // version opens with FILE_SHARE_READ (normally not what you want when
  // replacing the entire contents of the file) which lets us continue even if
  // another program has the file open for reading. See
  // http://crbug.com/468437
  const std::u16string& path = file_path.value();

  file_path_ = base::UTF16ToUTF8(path);
  file_ = base::win::ScopedHandle(
      ::CreateFile(reinterpret_cast<LPCWSTR>(path.c_str()), GENERIC_WRITE,
                   FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL));

  valid_ = file_.IsValid();
  if (!valid_) {
    PLOG(ERROR) << "CreateFile failed for path " << file_path_;

    // Determine whether the path need long path support.
    if (path.size() >= MAX_PATH && !IsLongPathsSupportEnabled()) {
      LOG(ERROR)
          << "You might need to enable Long Path Support on Windows: "
          << "https://learn.microsoft.com/en-us/windows/win32/fileio/"
             "maximum-file-path-limitation?tabs=registry#enable-long-paths"
             "-in-windows-10-version-1607-and-later";
    }
  }
  return valid_;
}

bool FileWriter::Write(std::string_view str) {
  if (!valid_)
    return false;

  DWORD written;
  BOOL result =
      ::WriteFile(file_.Get(), str.data(), str.size(), &written, nullptr);
  if (!result) {
    PLOG(ERROR) << "writing file " << file_path_ << " failed";
    valid_ = false;
    return false;
  }
  if (static_cast<size_t>(written) != str.size()) {
    PLOG(ERROR) << "wrote " << written << " bytes to " << file_path_
                << " expected " << str.size();
    valid_ = false;
    return false;
  }
  return true;
}

bool FileWriter::Close() {
  // NOTE: file_.Close() is not used here because it cannot return an error.
  HANDLE handle = file_.Take();
  if (handle && !::CloseHandle(handle))
    return false;

  return valid_;
}

#else  // !OS_WIN

bool FileWriter::Create(const base::FilePath& file_path) {
  fd_.reset(HANDLE_EINTR(::creat(file_path.value().c_str(), 0666)));
  valid_ = fd_.is_valid();
  if (!valid_) {
    PLOG(ERROR) << "creat() failed for path " << file_path.value();
  }
  return valid_;
}

bool FileWriter::Write(std::string_view str) {
  if (!valid_)
    return false;

  while (!str.empty()) {
    ssize_t written = HANDLE_EINTR(::write(fd_.get(), str.data(), str.size()));
    if (written <= 0) {
      valid_ = false;
      return false;
    }
    str.remove_prefix(static_cast<size_t>(written));
  }
  return true;
}

bool FileWriter::Close() {
  // The ScopedFD reset() method will crash on EBADF and ignore other errors
  // intentionally, so no need to check anything here.
  fd_.reset();
  return valid_;
}

#endif  // !OS_WIN
