//===-- File.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/File.h"

#include <cerrno>
#include <climits>
#include <cstdarg>
#include <cstdio>
#include <fcntl.h>
#include <optional>

#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 "lldb/Host/Config.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/VASPrintf.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/raw_ostream.h"

using namespace lldb;
using namespace lldb_private;
using llvm::Expected;

Expected<const char *>
File::GetStreamOpenModeFromOptions(File::OpenOptions options) {
  File::OpenOptions rw =
      options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly |
                 File::eOpenOptionReadWrite);

  if (options & File::eOpenOptionAppend) {
    if (rw == File::eOpenOptionReadWrite) {
      if (options & File::eOpenOptionCanCreateNewOnly)
        return "a+x";
      else
        return "a+";
    } else if (rw == File::eOpenOptionWriteOnly) {
      if (options & File::eOpenOptionCanCreateNewOnly)
        return "ax";
      else
        return "a";
    }
  } else if (rw == File::eOpenOptionReadWrite) {
    if (options & File::eOpenOptionCanCreate) {
      if (options & File::eOpenOptionCanCreateNewOnly)
        return "w+x";
      else
        return "w+";
    } else
      return "r+";
  } else if (rw == File::eOpenOptionWriteOnly) {
    return "w";
  } else if (rw == File::eOpenOptionReadOnly) {
    return "r";
  }
  return llvm::createStringError(
      llvm::inconvertibleErrorCode(),
      "invalid options, cannot convert to mode string");
}

Expected<File::OpenOptions> File::GetOptionsFromMode(llvm::StringRef mode) {
  OpenOptions opts =
      llvm::StringSwitch<OpenOptions>(mode)
          .Cases({"r", "rb"}, eOpenOptionReadOnly)
          .Cases({"w", "wb"}, eOpenOptionWriteOnly)
          .Cases({"a", "ab"}, eOpenOptionWriteOnly | eOpenOptionAppend |
                                  eOpenOptionCanCreate)
          .Cases({"r+", "rb+", "r+b"}, eOpenOptionReadWrite)
          .Cases({"w+", "wb+", "w+b"}, eOpenOptionReadWrite |
                                           eOpenOptionCanCreate |
                                           eOpenOptionTruncate)
          .Cases({"a+", "ab+", "a+b"}, eOpenOptionReadWrite |
                                           eOpenOptionAppend |
                                           eOpenOptionCanCreate)
          .Default(eOpenOptionInvalid);
  if (opts != eOpenOptionInvalid)
    return opts;
  return llvm::createStringError(
      llvm::inconvertibleErrorCode(),
      "invalid mode, cannot convert to File::OpenOptions");
}

int File::kInvalidDescriptor = -1;
FILE *File::kInvalidStream = nullptr;

Status File::Read(void *buf, size_t &num_bytes) {
  return std::error_code(ENOTSUP, std::system_category());
}
Status File::Write(const void *buf, size_t &num_bytes) {
  return std::error_code(ENOTSUP, std::system_category());
}

bool File::IsValid() const { return false; }

Status File::Close() { return Flush(); }

IOObject::WaitableHandle File::GetWaitableHandle() {
  return IOObject::kInvalidHandleValue;
}

Status File::GetFileSpec(FileSpec &file_spec) const {
  file_spec.Clear();
  return std::error_code(ENOTSUP, std::system_category());
}

int File::GetDescriptor() const { return kInvalidDescriptor; }

FILE *File::GetStream() { return nullptr; }

off_t File::SeekFromStart(off_t offset, Status *error_ptr) {
  if (error_ptr)
    *error_ptr = std::error_code(ENOTSUP, std::system_category());
  return -1;
}

off_t File::SeekFromCurrent(off_t offset, Status *error_ptr) {
  if (error_ptr)
    *error_ptr = std::error_code(ENOTSUP, std::system_category());
  return -1;
}

off_t File::SeekFromEnd(off_t offset, Status *error_ptr) {
  if (error_ptr)
    *error_ptr = std::error_code(ENOTSUP, std::system_category());
  return -1;
}

Status File::Read(void *dst, size_t &num_bytes, off_t &offset) {
  return std::error_code(ENOTSUP, std::system_category());
}

Status File::Write(const void *src, size_t &num_bytes, off_t &offset) {
  return std::error_code(ENOTSUP, std::system_category());
}

Status File::Flush() { return Status(); }

Status File::Sync() { return Flush(); }

void File::CalculateInteractiveAndTerminal() {
  const int fd = GetDescriptor();
  if (!DescriptorIsValid(fd)) {
    m_is_interactive = eLazyBoolNo;
    m_is_real_terminal = eLazyBoolNo;
    m_supports_colors = eLazyBoolNo;
    return;
  }
  m_is_interactive = eLazyBoolNo;
  m_is_real_terminal = eLazyBoolNo;
#if defined(_WIN32)
  if (_isatty(fd)) {
    m_is_interactive = eLazyBoolYes;
    m_is_real_terminal = eLazyBoolYes;
#if defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
    m_supports_colors = eLazyBoolYes;
#endif
  }
#else
  if (isatty(fd)) {
    m_is_interactive = eLazyBoolYes;
    struct winsize window_size;
    if (::ioctl(fd, TIOCGWINSZ, &window_size) == 0) {
      if (window_size.ws_col > 0) {
        m_is_real_terminal = eLazyBoolYes;
        if (llvm::sys::Process::FileDescriptorHasColors(fd))
          m_supports_colors = eLazyBoolYes;
      }
    }
  }
#endif
}

bool File::GetIsInteractive() {
  if (m_is_interactive == eLazyBoolCalculate)
    CalculateInteractiveAndTerminal();
  return m_is_interactive == eLazyBoolYes;
}

bool File::GetIsRealTerminal() {
  if (m_is_real_terminal == eLazyBoolCalculate)
    CalculateInteractiveAndTerminal();
  return m_is_real_terminal == eLazyBoolYes;
}

bool File::GetIsTerminalWithColors() {
  if (m_supports_colors == eLazyBoolCalculate)
    CalculateInteractiveAndTerminal();
  return m_supports_colors == eLazyBoolYes;
}

size_t File::Printf(const char *format, ...) {
  va_list args;
  va_start(args, format);
  size_t result = PrintfVarArg(format, args);
  va_end(args);
  return result;
}

size_t File::PrintfVarArg(const char *format, va_list args) {
  llvm::SmallString<0> s;
  if (VASprintf(s, format, args)) {
    size_t written = s.size();
    Write(s.data(), written);
    return written;
  }
  return 0;
}

Expected<File::OpenOptions> File::GetOptions() const {
  return llvm::createStringError(
      llvm::inconvertibleErrorCode(),
      "GetOptions() not implemented for this File class");
}

uint32_t File::GetPermissions(Status &error) const {
  int fd = GetDescriptor();
  if (!DescriptorIsValid(fd)) {
    error = std::error_code(ENOTSUP, std::system_category());
    return 0;
  }
  struct stat file_stats;
  if (::fstat(fd, &file_stats) == -1) {
    error = Status::FromErrno();
    return 0;
  }
  error.Clear();
  return file_stats.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
}

NativeFile::NativeFile() = default;

NativeFile::NativeFile(FILE *fh, OpenOptions options, bool transfer_ownership)
    : m_stream(fh), m_options(options), m_own_stream(transfer_ownership) {
#ifdef _WIN32
  // In order to properly display non ASCII characters in Windows, we need to
  // use Windows APIs to print to the console. This is only required if the
  // stream outputs to a console.
  int fd = _fileno(fh);
  is_windows_console =
      ::GetFileType((HANDLE)::_get_osfhandle(fd)) == FILE_TYPE_CHAR;
#else
#ifndef NDEBUG
  int fd = fileno(fh);
  if (fd != -1) {
    int required_mode = ConvertOpenOptionsForPOSIXOpen(options) & O_ACCMODE;
    int mode = fcntl(fd, F_GETFL);
    if (mode != -1) {
      mode &= O_ACCMODE;
      // Check that the file is open with a valid subset of the requested file
      // access mode, e.g. if we expected the file to be writable then ensure it
      // was opened with O_WRONLY or O_RDWR.
      assert(
          (required_mode == O_RDWR && mode == O_RDWR) ||
          (required_mode == O_RDONLY && (mode == O_RDWR || mode == O_RDONLY) ||
           (required_mode == O_WRONLY &&
            (mode == O_RDWR || mode == O_WRONLY))) &&
              "invalid file access mode");
    }
  }
#endif
#endif
}

NativeFile::NativeFile(int fd, OpenOptions options, bool transfer_ownership)
    : m_descriptor(fd), m_own_descriptor(transfer_ownership),
      m_options(options) {
#ifdef _WIN32
  // In order to properly display non ASCII characters in Windows, we need to
  // use Windows APIs to print to the console. This is only required if the
  // file outputs to a console.
  is_windows_console =
      ::GetFileType((HANDLE)::_get_osfhandle(fd)) == FILE_TYPE_CHAR;
#endif
}

bool NativeFile::IsValid() const {
  std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex,
                                                m_stream_mutex);
  return DescriptorIsValidUnlocked() || StreamIsValidUnlocked();
}

Expected<File::OpenOptions> NativeFile::GetOptions() const { return m_options; }

int NativeFile::GetDescriptor() const {
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
    return m_descriptor;
  }

  // Don't open the file descriptor if we don't need to, just get it from the
  // stream if we have one.
  if (ValueGuard stream_guard = StreamIsValid()) {
#if defined(_WIN32)
    return _fileno(m_stream);
#else
    return fileno(m_stream);
#endif
  }

  // Invalid descriptor and invalid stream, return invalid descriptor.
  return kInvalidDescriptor;
}

IOObject::WaitableHandle NativeFile::GetWaitableHandle() {
#ifdef _WIN32
  return (HANDLE)_get_osfhandle(GetDescriptor());
#else
  return GetDescriptor();
#endif
}

FILE *NativeFile::GetStream() {
  ValueGuard stream_guard = StreamIsValid();
  if (!stream_guard) {
    if (ValueGuard descriptor_guard = DescriptorIsValid()) {
      auto mode = GetStreamOpenModeFromOptions(m_options);
      if (!mode)
        llvm::consumeError(mode.takeError());
      else {
        if (!m_own_descriptor) {
// We must duplicate the file descriptor if we don't own it because when you
// call fdopen, the stream will own the fd
#ifdef _WIN32
          m_descriptor = ::_dup(m_descriptor);
#else
          m_descriptor = dup(m_descriptor);
#endif
          m_own_descriptor = true;
        }

        m_stream = llvm::sys::RetryAfterSignal(nullptr, ::fdopen, m_descriptor,
                                               mode.get());

        // If we got a stream, then we own the stream and should no longer own
        // the descriptor because fclose() will close it for us

        if (m_stream) {
          m_own_stream = true;
          m_own_descriptor = false;
        }
      }
    }
  }
  return m_stream;
}

Status NativeFile::Close() {
  std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex,
                                                m_stream_mutex);

  Status error;

  if (StreamIsValidUnlocked()) {
    if (m_own_stream) {
      if (::fclose(m_stream) == EOF)
        error = Status::FromErrno();
    } else {
      File::OpenOptions rw =
          m_options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly |
                       File::eOpenOptionReadWrite);

      if (rw == eOpenOptionWriteOnly || rw == eOpenOptionReadWrite) {
        if (::fflush(m_stream) == EOF)
          error = Status::FromErrno();
      }
    }
  }

  if (DescriptorIsValidUnlocked() && m_own_descriptor) {
    if (::close(m_descriptor) != 0)
      error = Status::FromErrno();
  }

  m_stream = kInvalidStream;
  m_own_stream = false;
  m_descriptor = kInvalidDescriptor;
  m_own_descriptor = false;
  m_options = OpenOptions(0);
  m_is_interactive = eLazyBoolCalculate;
  m_is_real_terminal = eLazyBoolCalculate;
  return error;
}

Status NativeFile::GetFileSpec(FileSpec &file_spec) const {
  Status error;
#ifdef F_GETPATH
  if (IsValid()) {
    char path[PATH_MAX];
    if (::fcntl(GetDescriptor(), F_GETPATH, path) == -1)
      error = Status::FromErrno();
    else
      file_spec.SetFile(path, FileSpec::Style::native);
  } else {
    error = Status::FromErrorString("invalid file handle");
  }
#elif defined(__linux__)
  char proc[64];
  char path[PATH_MAX];
  if (::snprintf(proc, sizeof(proc), "/proc/self/fd/%d", GetDescriptor()) < 0)
    error = Status::FromErrorString("cannot resolve file descriptor");
  else {
    ssize_t len;
    if ((len = ::readlink(proc, path, sizeof(path) - 1)) == -1)
      error = Status::FromErrno();
    else {
      path[len] = '\0';
      file_spec.SetFile(path, FileSpec::Style::native);
    }
  }
#else
  error = Status::FromErrorString(
      "NativeFile::GetFileSpec is not supported on this platform");
#endif

  if (error.Fail())
    file_spec.Clear();
  return error;
}

off_t NativeFile::SeekFromStart(off_t offset, Status *error_ptr) {
  off_t result = 0;
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
    result = ::lseek(m_descriptor, offset, SEEK_SET);

    if (error_ptr) {
      if (result == -1)
        *error_ptr = Status::FromErrno();
      else
        error_ptr->Clear();
    }
    return result;
  }

  if (ValueGuard stream_guard = StreamIsValid()) {
    result = ::fseek(m_stream, offset, SEEK_SET);

    if (error_ptr) {
      if (result == -1)
        *error_ptr = Status::FromErrno();
      else
        error_ptr->Clear();
    }
    return result;
  }

  if (error_ptr)
    *error_ptr = Status::FromErrorString("invalid file handle");
  return result;
}

off_t NativeFile::SeekFromCurrent(off_t offset, Status *error_ptr) {
  off_t result = -1;
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
    result = ::lseek(m_descriptor, offset, SEEK_CUR);

    if (error_ptr) {
      if (result == -1)
        *error_ptr = Status::FromErrno();
      else
        error_ptr->Clear();
    }
    return result;
  }

  if (ValueGuard stream_guard = StreamIsValid()) {
    result = ::fseek(m_stream, offset, SEEK_CUR);

    if (error_ptr) {
      if (result == -1)
        *error_ptr = Status::FromErrno();
      else
        error_ptr->Clear();
    }
    return result;
  }

  if (error_ptr)
    *error_ptr = Status::FromErrorString("invalid file handle");
  return result;
}

off_t NativeFile::SeekFromEnd(off_t offset, Status *error_ptr) {
  off_t result = -1;
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
    result = ::lseek(m_descriptor, offset, SEEK_END);

    if (error_ptr) {
      if (result == -1)
        *error_ptr = Status::FromErrno();
      else
        error_ptr->Clear();
    }
    return result;
  }

  if (ValueGuard stream_guard = StreamIsValid()) {
    result = ::fseek(m_stream, offset, SEEK_END);

    if (error_ptr) {
      if (result == -1)
        *error_ptr = Status::FromErrno();
      else
        error_ptr->Clear();
    }
  }

  if (error_ptr)
    *error_ptr = Status::FromErrorString("invalid file handle");
  return result;
}

Status NativeFile::Flush() {
  Status error;
  if (ValueGuard stream_guard = StreamIsValid()) {
    if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF)
      error = Status::FromErrno();
    return error;
  }

  {
    ValueGuard descriptor_guard = DescriptorIsValid();
    if (!descriptor_guard)
      error = Status::FromErrorString("invalid file handle");
  }
  return error;
}

Status NativeFile::Sync() {
  Status error;
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
#ifdef _WIN32
    int err = FlushFileBuffers((HANDLE)_get_osfhandle(m_descriptor));
    if (err == 0)
      error = Status::FromErrorString("unknown error");
#else
    if (llvm::sys::RetryAfterSignal(-1, ::fsync, m_descriptor) == -1)
      error = Status::FromErrno();
#endif
  } else {
    error = Status::FromErrorString("invalid file handle");
  }
  return error;
}

#if defined(__APPLE__)
// Darwin kernels only can read/write <= INT_MAX bytes
#define MAX_READ_SIZE INT_MAX
#define MAX_WRITE_SIZE INT_MAX
#endif

Status NativeFile::Read(void *buf, size_t &num_bytes) {
  Status error;

  // Ensure the file is open for reading.
  if ((m_options & File::OpenOptionsModeMask) == eOpenOptionWriteOnly)
    return Status(std::make_error_code(std::errc::bad_file_descriptor));

#if defined(MAX_READ_SIZE)
  if (num_bytes > MAX_READ_SIZE) {
    uint8_t *p = (uint8_t *)buf;
    size_t bytes_left = num_bytes;
    // Init the num_bytes read to zero
    num_bytes = 0;

    while (bytes_left > 0) {
      size_t curr_num_bytes;
      if (bytes_left > MAX_READ_SIZE)
        curr_num_bytes = MAX_READ_SIZE;
      else
        curr_num_bytes = bytes_left;

      error = Read(p + num_bytes, curr_num_bytes);

      // Update how many bytes were read
      num_bytes += curr_num_bytes;
      if (bytes_left < curr_num_bytes)
        bytes_left = 0;
      else
        bytes_left -= curr_num_bytes;

      if (error.Fail())
        break;
    }
    return error;
  }
#endif

  ssize_t bytes_read = -1;
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
    bytes_read =
        llvm::sys::RetryAfterSignal(-1, ::read, m_descriptor, buf, num_bytes);
    if (bytes_read == -1) {
      error = Status::FromErrno();
      num_bytes = 0;
    } else
      num_bytes = bytes_read;
    return error;
  }

  if (ValueGuard file_lock = StreamIsValid()) {
    bytes_read = ::fread(buf, 1, num_bytes, m_stream);

    if (bytes_read == 0) {
      if (::feof(m_stream))
        error = Status::FromErrorString("feof");
      else if (::ferror(m_stream))
        error = Status::FromErrorString("ferror");
      num_bytes = 0;
    } else
      num_bytes = bytes_read;
    return error;
  }

  num_bytes = 0;
  error = Status::FromErrorString("invalid file handle");
  return error;
}

Status NativeFile::Write(const void *buf, size_t &num_bytes) {
  Status error;

  // Ensure the file is open for writing.
  if ((m_options & File::OpenOptionsModeMask) == File::eOpenOptionReadOnly)
    return Status(std::make_error_code(std::errc::bad_file_descriptor));

#if defined(MAX_WRITE_SIZE)
  if (num_bytes > MAX_WRITE_SIZE) {
    const uint8_t *p = (const uint8_t *)buf;
    size_t bytes_left = num_bytes;
    // Init the num_bytes written to zero
    num_bytes = 0;

    while (bytes_left > 0) {
      size_t curr_num_bytes;
      if (bytes_left > MAX_WRITE_SIZE)
        curr_num_bytes = MAX_WRITE_SIZE;
      else
        curr_num_bytes = bytes_left;

      error = Write(p + num_bytes, curr_num_bytes);

      // Update how many bytes were read
      num_bytes += curr_num_bytes;
      if (bytes_left < curr_num_bytes)
        bytes_left = 0;
      else
        bytes_left -= curr_num_bytes;

      if (error.Fail())
        break;
    }
    return error;
  }
#endif

  ssize_t bytes_written = -1;
  if (ValueGuard descriptor_guard = DescriptorIsValid()) {
    bytes_written =
        llvm::sys::RetryAfterSignal(-1, ::write, m_descriptor, buf, num_bytes);
    if (bytes_written == -1) {
      error = Status::FromErrno();
      num_bytes = 0;
    } else
      num_bytes = bytes_written;
    return error;
  }

  if (ValueGuard stream_guard = StreamIsValid()) {
#ifdef _WIN32
    if (is_windows_console) {
      llvm::raw_fd_ostream(_fileno(m_stream), false)
          .write((const char *)buf, num_bytes);
      return error;
    }
#endif
    bytes_written = ::fwrite(buf, 1, num_bytes, m_stream);

    if (bytes_written == 0) {
      if (::feof(m_stream))
        error = Status::FromErrorString("feof");
      else if (::ferror(m_stream))
        error = Status::FromErrorString("ferror");
      num_bytes = 0;
    } else
      num_bytes = bytes_written;
    return error;
  }

  num_bytes = 0;
  error = Status::FromErrorString("invalid file handle");
  return error;
}

Status NativeFile::Read(void *buf, size_t &num_bytes, off_t &offset) {
  Status error;

#if defined(MAX_READ_SIZE)
  if (num_bytes > MAX_READ_SIZE) {
    uint8_t *p = (uint8_t *)buf;
    size_t bytes_left = num_bytes;
    // Init the num_bytes read to zero
    num_bytes = 0;

    while (bytes_left > 0) {
      size_t curr_num_bytes;
      if (bytes_left > MAX_READ_SIZE)
        curr_num_bytes = MAX_READ_SIZE;
      else
        curr_num_bytes = bytes_left;

      error = Read(p + num_bytes, curr_num_bytes, offset);

      // Update how many bytes were read
      num_bytes += curr_num_bytes;
      if (bytes_left < curr_num_bytes)
        bytes_left = 0;
      else
        bytes_left -= curr_num_bytes;

      if (error.Fail())
        break;
    }
    return error;
  }
#endif

#ifndef _WIN32
  int fd = GetDescriptor();
  if (fd != kInvalidDescriptor) {
    ssize_t bytes_read =
        llvm::sys::RetryAfterSignal(-1, ::pread, fd, buf, num_bytes, offset);
    if (bytes_read < 0) {
      num_bytes = 0;
      error = Status::FromErrno();
    } else {
      offset += bytes_read;
      num_bytes = bytes_read;
    }
  } else {
    num_bytes = 0;
    error = Status::FromErrorString("invalid file handle");
  }
#else
  std::lock_guard<std::mutex> guard(offset_access_mutex);
  long cur = ::lseek(m_descriptor, 0, SEEK_CUR);
  SeekFromStart(offset);
  error = Read(buf, num_bytes);
  if (!error.Fail())
    SeekFromStart(cur);
#endif
  return error;
}

Status NativeFile::Write(const void *buf, size_t &num_bytes, off_t &offset) {
  Status error;

#if defined(MAX_WRITE_SIZE)
  if (num_bytes > MAX_WRITE_SIZE) {
    const uint8_t *p = (const uint8_t *)buf;
    size_t bytes_left = num_bytes;
    // Init the num_bytes written to zero
    num_bytes = 0;

    while (bytes_left > 0) {
      size_t curr_num_bytes;
      if (bytes_left > MAX_WRITE_SIZE)
        curr_num_bytes = MAX_WRITE_SIZE;
      else
        curr_num_bytes = bytes_left;

      error = Write(p + num_bytes, curr_num_bytes, offset);

      // Update how many bytes were read
      num_bytes += curr_num_bytes;
      if (bytes_left < curr_num_bytes)
        bytes_left = 0;
      else
        bytes_left -= curr_num_bytes;

      if (error.Fail())
        break;
    }
    return error;
  }
#endif

  int fd = GetDescriptor();
  if (fd != kInvalidDescriptor) {
#ifndef _WIN32
    ssize_t bytes_written = llvm::sys::RetryAfterSignal(
        -1, ::pwrite, m_descriptor, buf, num_bytes, offset);
    if (bytes_written < 0) {
      num_bytes = 0;
      error = Status::FromErrno();
    } else {
      offset += bytes_written;
      num_bytes = bytes_written;
    }
#else
    std::lock_guard<std::mutex> guard(offset_access_mutex);
    long cur = ::lseek(m_descriptor, 0, SEEK_CUR);
    SeekFromStart(offset);
    error = Write(buf, num_bytes);
    long after = ::lseek(m_descriptor, 0, SEEK_CUR);

    if (!error.Fail())
      SeekFromStart(cur);

    offset = after;
#endif
  } else {
    num_bytes = 0;
    error = Status::FromErrorString("invalid file handle");
  }
  return error;
}

size_t NativeFile::PrintfVarArg(const char *format, va_list args) {
  if (StreamIsValid()) {
    return ::vfprintf(m_stream, format, args);
  } else {
    return File::PrintfVarArg(format, args);
  }
}

mode_t File::ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options) {
  mode_t mode = 0;
  File::OpenOptions rw =
      open_options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly |
                      File::eOpenOptionReadWrite);
  if (rw == eOpenOptionReadWrite)
    mode |= O_RDWR;
  else if (rw == eOpenOptionWriteOnly)
    mode |= O_WRONLY;
  else if (rw == eOpenOptionReadOnly)
    mode |= O_RDONLY;

  if (open_options & eOpenOptionAppend)
    mode |= O_APPEND;

  if (open_options & eOpenOptionTruncate)
    mode |= O_TRUNC;

  if (open_options & eOpenOptionNonBlocking)
    mode |= O_NONBLOCK;

  if (open_options & eOpenOptionCanCreateNewOnly)
    mode |= O_CREAT | O_EXCL;
  else if (open_options & eOpenOptionCanCreate)
    mode |= O_CREAT;

  return mode;
}

llvm::Expected<SerialPort::Options>
SerialPort::OptionsFromURL(llvm::StringRef urlqs) {
  SerialPort::Options serial_options;
  for (llvm::StringRef x : llvm::split(urlqs, '&')) {
    if (x.consume_front("baud=")) {
      unsigned int baud_rate;
      if (!llvm::to_integer(x, baud_rate, 10))
        return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                       "Invalid baud rate: %s",
                                       x.str().c_str());
      serial_options.BaudRate = baud_rate;
    } else if (x.consume_front("parity=")) {
      serial_options.Parity =
          llvm::StringSwitch<std::optional<Terminal::Parity>>(x)
              .Case("no", Terminal::Parity::No)
              .Case("even", Terminal::Parity::Even)
              .Case("odd", Terminal::Parity::Odd)
              .Case("mark", Terminal::Parity::Mark)
              .Case("space", Terminal::Parity::Space)
              .Default(std::nullopt);
      if (!serial_options.Parity)
        return llvm::createStringError(
            llvm::inconvertibleErrorCode(),
            "Invalid parity (must be no, even, odd, mark or space): %s",
            x.str().c_str());
    } else if (x.consume_front("parity-check=")) {
      serial_options.ParityCheck =
          llvm::StringSwitch<std::optional<Terminal::ParityCheck>>(x)
              .Case("no", Terminal::ParityCheck::No)
              .Case("replace", Terminal::ParityCheck::ReplaceWithNUL)
              .Case("ignore", Terminal::ParityCheck::Ignore)
              // "mark" mode is not currently supported as it requires special
              // input processing
              // .Case("mark", Terminal::ParityCheck::Mark)
              .Default(std::nullopt);
      if (!serial_options.ParityCheck)
        return llvm::createStringError(
            llvm::inconvertibleErrorCode(),
            "Invalid parity-check (must be no, replace, ignore or mark): %s",
            x.str().c_str());
    } else if (x.consume_front("stop-bits=")) {
      unsigned int stop_bits;
      if (!llvm::to_integer(x, stop_bits, 10) ||
          (stop_bits != 1 && stop_bits != 2))
        return llvm::createStringError(
            llvm::inconvertibleErrorCode(),
            "Invalid stop bit number (must be 1 or 2): %s", x.str().c_str());
      serial_options.StopBits = stop_bits;
    } else
      return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                     "Unknown parameter: %s", x.str().c_str());
  }
  return serial_options;
}

llvm::Expected<std::unique_ptr<SerialPort>>
SerialPort::Create(int fd, OpenOptions options, Options serial_options,
                   bool transfer_ownership) {
  std::unique_ptr<SerialPort> out{
      new SerialPort(fd, options, serial_options, transfer_ownership)};

  if (!out->GetIsInteractive())
    return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                   "the specified file is not a teletype");

  Terminal term{fd};
  if (llvm::Error error = term.SetRaw())
    return std::move(error);
  if (serial_options.BaudRate) {
    if (llvm::Error error = term.SetBaudRate(*serial_options.BaudRate))
      return std::move(error);
  }
  if (serial_options.Parity) {
    if (llvm::Error error = term.SetParity(*serial_options.Parity))
      return std::move(error);
  }
  if (serial_options.ParityCheck) {
    if (llvm::Error error = term.SetParityCheck(*serial_options.ParityCheck))
      return std::move(error);
  }
  if (serial_options.StopBits) {
    if (llvm::Error error = term.SetStopBits(*serial_options.StopBits))
      return std::move(error);
  }

  return std::move(out);
}

SerialPort::SerialPort(int fd, OpenOptions options,
                       SerialPort::Options serial_options,
                       bool transfer_ownership)
    : NativeFile(fd, options, transfer_ownership), m_state(fd) {}

Status SerialPort::Close() {
  m_state.Restore();
  return NativeFile::Close();
}

char File::ID = 0;
char NativeFile::ID = 0;
char SerialPort::ID = 0;
